using Terminal.Gui.App; using Terminal.Gui.Views; using Terminal.Gui.ViewBase; using Terminal.Gui.Drawing; using Attribute = Terminal.Gui.Drawing.Attribute; namespace EchoHub.Client.UI; /// /// Result returned from the profile edit dialog. /// public record ProfileEditResult(string? DisplayName, string? Bio, string? NicknameColor, string? AvatarPath, bool? NotificationSoundEnabled, byte? NotificationVolume); /// /// A Terminal.Gui dialog for editing the user's profile (display name, bio, nickname color). /// public sealed class ProfileEditDialog { /// /// Shows the profile edit dialog and returns the result, or null if cancelled. /// public static ProfileEditResult? Show(IApplication app, string? currentDisplayName, string? currentBio, string? currentColor, bool notificationSoundEnabled = false, byte notificationVolume = 30) { ProfileEditResult? result = null; var dialog = new Dialog { Title = "Edit Profile", Width = 60, Height = 26 }; // Display Name var nameLabel = new Label { Text = "Display Name:", X = 1, Y = 1 }; var nameField = new TextField { Text = currentDisplayName ?? "", X = 17, Y = 1, Width = Dim.Fill(2) }; // Bio var bioLabel = new Label { Text = "Bio:", X = 1, Y = 3 }; var bioField = new TextField { Text = currentBio ?? "", X = 17, Y = 3, Width = Dim.Fill(2) }; // Nickname Color var colorLabel = new Label { Text = "Nickname Color:", X = 1, Y = 5 }; var colorField = new TextField { Text = currentColor ?? "", X = 17, Y = 5, Width = Dim.Fill(2) }; var colorHintLabel = new Label { Text = "(hex e.g. #FF5733)", X = 17, Y = 6 }; colorHintLabel.SetScheme(new Scheme { Normal = new Attribute(Color.DarkGray, Color.Blue) }); // Color Preview var previewLabel = new Label { Text = "Preview:", X = 1, Y = 8 }; var colorPreview = new Label { Text = "\u2588\u2588\u2588\u2588\u2588\u2588", X = 17, Y = 8 }; UpdateColorPreview(colorPreview, colorField.Text); colorField.TextChanged += (sender, e) => { UpdateColorPreview(colorPreview, colorField.Text); }; // Avatar var avatarLabel = new Label { Text = "Avatar:", X = 1, Y = 10 }; var avatarField = new TextField { Text = "", X = 17, Y = 10, Width = Dim.Fill(12) }; var browseButton = new Button { Text = "Browse", X = Pos.AnchorEnd(10), Y = 10 }; var avatarHintLabel = new Label { Text = "(file path or URL)", X = 17, Y = 11 }; avatarHintLabel.SetScheme(new Scheme { Normal = new Attribute(Color.DarkGray, Color.Blue) }); browseButton.Accepting += (s, e) => { e.Handled = true; var openDialog = new OpenDialog { Title = "Select Avatar Image", OpenMode = OpenMode.File, }; app.Run(openDialog); if (openDialog.FilePaths.Count > 0) { avatarField.Text = openDialog.FilePaths[0]; } }; // Notification Sound var notifCheckbox = new CheckBox { Text = "Notification sound on @mention", X = 1, Y = 13, Value = notificationSoundEnabled ? CheckState.Checked : CheckState.UnChecked }; var volumeLabel = new Label { Text = "Volume:", X = 1, Y = 15 }; var volumeField = new TextField { Text = notificationVolume.ToString(), X = 17, Y = 15, Width = 6 }; var volumeHintLabel = new Label { Text = "(0-100)", X = 24, Y = 15 }; volumeHintLabel.SetScheme(new Scheme { Normal = new Attribute(Color.DarkGray, Color.Blue) }); // Buttons var saveButton = new Button { Text = "Save", IsDefault = true, X = Pos.Center() - 10, Y = 18 }; var cancelButton = new Button { Text = "Cancel", X = Pos.Center() + 5, Y = 18 }; saveButton.Accepting += (s, e) => { var displayName = NullIfEmpty(nameField.Text?.Trim()); var bio = NullIfEmpty(bioField.Text?.Trim()); var nicknameColor = NullIfEmpty(colorField.Text?.Trim()); var avatarPath = NullIfEmpty(avatarField.Text?.Trim()); byte? volume = byte.TryParse(volumeField.Text, out var v) ? Math.Min(v, (byte)100) : null; result = new ProfileEditResult(displayName, bio, nicknameColor, avatarPath, notifCheckbox.Value == CheckState.Checked, volume); e.Handled = true; app.RequestStop(); }; cancelButton.Accepting += (s, e) => { result = null; e.Handled = true; app.RequestStop(); }; dialog.Add(nameLabel, nameField, bioLabel, bioField, colorLabel, colorField, colorHintLabel, previewLabel, colorPreview, avatarLabel, avatarField, browseButton, avatarHintLabel, notifCheckbox, volumeLabel, volumeField, volumeHintLabel, saveButton, cancelButton); nameField.SetFocus(); app.Run(dialog); return result; } /// /// Attempts to parse a hex color string and update the preview label color. /// private static void UpdateColorPreview(Label preview, string? hexColor) { if (string.IsNullOrWhiteSpace(hexColor)) { preview.SetScheme(new Scheme { Normal = new Attribute(Color.White, Color.Blue) }); return; } var color = ParseHexToTrueColor(hexColor.Trim()); preview.SetScheme(new Scheme { Normal = new Attribute(color, Color.Blue) }); } /// /// Parses a hex color string to a Terminal.Gui TrueColor Color. /// V2 supports TrueColor via new Color(r, g, b). /// private static Color ParseHexToTrueColor(string hex) { if (hex.StartsWith('#')) hex = hex[1..]; if (hex.Length != 6 || !int.TryParse(hex, System.Globalization.NumberStyles.HexNumber, null, out var rgb)) return Color.White; int r = (rgb >> 16) & 0xFF; int g = (rgb >> 8) & 0xFF; int b = rgb & 0xFF; return new Color(r, g, b); } private static string? NullIfEmpty(string? value) => string.IsNullOrWhiteSpace(value) ? null : value; }