diff --git a/Unicord.Universal/Controls/EmotePicker.xaml.cs b/Unicord.Universal/Controls/EmotePicker.xaml.cs
index a3c21b41..c308b51b 100644
--- a/Unicord.Universal/Controls/EmotePicker.xaml.cs
+++ b/Unicord.Universal/Controls/EmotePicker.xaml.cs
@@ -35,7 +35,12 @@ public void Load()
{
try
{
- Source.Source = EmojiUtilities.GetEmoji(new ChannelViewModel(Channel.Id, true), searchBox.Text);
+ ChannelViewModel channelVm = null;
+ if (Channel != null)
+ {
+ channelVm = new ChannelViewModel(Channel.Id, true);
+ }
+ Source.Source = EmojiUtilities.GetEmoji(channelVm, searchBox.Text);
}
catch { }
}
diff --git a/Unicord.Universal/Controls/Flyouts/ChannelContextFlyout.xaml b/Unicord.Universal/Controls/Flyouts/ChannelContextFlyout.xaml
index 14dca06d..1825a527 100644
--- a/Unicord.Universal/Controls/Flyouts/ChannelContextFlyout.xaml
+++ b/Unicord.Universal/Controls/Flyouts/ChannelContextFlyout.xaml
@@ -15,6 +15,13 @@
+
+
diff --git a/Unicord.Universal/Controls/Flyouts/ChannelContextFlyout.xaml.cs b/Unicord.Universal/Controls/Flyouts/ChannelContextFlyout.xaml.cs
index 0d655183..df1b611a 100644
--- a/Unicord.Universal/Controls/Flyouts/ChannelContextFlyout.xaml.cs
+++ b/Unicord.Universal/Controls/Flyouts/ChannelContextFlyout.xaml.cs
@@ -1,4 +1,8 @@
-using Windows.UI.Xaml.Controls;
+using Unicord.Universal.Models.Channels;
+using Unicord.Universal.Pages.Overlay;
+using Unicord.Universal.Services;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
namespace Unicord.Universal.Controls.Flyouts
{
@@ -8,5 +12,14 @@ public ChannelContextFlyout()
{
InitializeComponent();
}
+
+ private async void ProfileItem_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is FrameworkElement element && element.DataContext is ChannelViewModel channel && channel.Recipient != null)
+ {
+ await OverlayService.GetForCurrentView()
+ .ShowOverlayAsync(channel.Recipient);
+ }
+ }
}
}
diff --git a/Unicord.Universal/Controls/Flyouts/DirectMessageContextFlyout.xaml b/Unicord.Universal/Controls/Flyouts/DirectMessageContextFlyout.xaml
index 01d08b74..a57fd0b8 100644
--- a/Unicord.Universal/Controls/Flyouts/DirectMessageContextFlyout.xaml
+++ b/Unicord.Universal/Controls/Flyouts/DirectMessageContextFlyout.xaml
@@ -15,6 +15,11 @@
+
diff --git a/Unicord.Universal/Controls/Flyouts/DirectMessageContextFlyout.xaml.cs b/Unicord.Universal/Controls/Flyouts/DirectMessageContextFlyout.xaml.cs
index 85ba72ca..8afad572 100644
--- a/Unicord.Universal/Controls/Flyouts/DirectMessageContextFlyout.xaml.cs
+++ b/Unicord.Universal/Controls/Flyouts/DirectMessageContextFlyout.xaml.cs
@@ -1,4 +1,8 @@
-using Windows.UI.Xaml.Controls;
+using Unicord.Universal.Models.Channels;
+using Unicord.Universal.Pages.Overlay;
+using Unicord.Universal.Services;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
namespace Unicord.Universal.Controls.Flyouts
{
@@ -8,5 +12,14 @@ public DirectMessageContextFlyout()
{
InitializeComponent();
}
+
+ private async void ProfileItem_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is FrameworkElement element && element.DataContext is ChannelViewModel channel && channel.Recipient != null)
+ {
+ await OverlayService.GetForCurrentView()
+ .ShowOverlayAsync(channel.Recipient);
+ }
+ }
}
}
diff --git a/Unicord.Universal/Controls/Flyouts/UserFlyout.xaml b/Unicord.Universal/Controls/Flyouts/UserFlyout.xaml
index 710f88a1..1a0c2589 100644
--- a/Unicord.Universal/Controls/Flyouts/UserFlyout.xaml
+++ b/Unicord.Universal/Controls/Flyouts/UserFlyout.xaml
@@ -11,60 +11,294 @@
xmlns:user="using:Unicord.Universal.Controls.Users"
mc:Ignorable="d">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @#
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Unicord.Universal/Controls/Flyouts/UserFlyout.xaml.cs b/Unicord.Universal/Controls/Flyouts/UserFlyout.xaml.cs
index a1c3f918..ed991ccc 100644
--- a/Unicord.Universal/Controls/Flyouts/UserFlyout.xaml.cs
+++ b/Unicord.Universal/Controls/Flyouts/UserFlyout.xaml.cs
@@ -1,19 +1,558 @@
-using Unicord.Universal.Utilities;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using DSharpPlus;
+using DSharpPlus.Entities;
+using Unicord.Universal.Services;
+using Unicord.Universal.Utilities;
+using Windows.ApplicationModel.DataTransfer;
+using Windows.UI.Core;
+using Windows.Foundation;
+using Windows.System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Media.Imaging;
namespace Unicord.Universal.Controls.Flyouts
{
public sealed partial class UserFlyout : AdaptiveFlyout
{
+ private static Thickness MutualIconsMarginForCount(int count)
+ {
+ var right = count switch
+ {
+ 1 => 18,
+ 2 => 12,
+ 3 => 6,
+ _ => 12,
+ };
+
+ return new Thickness(0, 0, right, 0);
+ }
+
+ private bool _rolesExpanded = false;
+ private bool _rolesAutoExpanded = false;
+
public UserFlyout(object param) : base(param)
{
InitializeComponent();
+ DataContextChanged += UserFlyout_DataContextChanged;
+ Loaded += UserFlyout_Loaded;
+ }
+
+ private void UserFlyout_Loaded(object sender, RoutedEventArgs e)
+ {
+ _rolesAutoExpanded = false;
+ UpdateRolesDisplay();
+ }
+
+ private void UserFlyout_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
+ {
+ UpdateMutualDisplay();
+ _rolesExpanded = false;
+ _rolesAutoExpanded = false;
+ UpdateRolesDisplay();
+ }
+
+ private static T FindDescendant(DependencyObject root) where T : DependencyObject
+ {
+ if (root == null)
+ return null;
+
+ var count = VisualTreeHelper.GetChildrenCount(root);
+ for (var i = 0; i < count; i++)
+ {
+ var child = VisualTreeHelper.GetChild(root, i);
+ if (child is T typed)
+ return typed;
+
+ var result = FindDescendant(child);
+ if (result != null)
+ return result;
+ }
+
+ return null;
+ }
+
+ private static Panel FindItemsHostPanel(ItemsControl itemsControl, int expectedChildCount)
+ {
+ if (itemsControl == null)
+ return null;
+
+ Panel best = null;
+
+ void Walk(DependencyObject node)
+ {
+ if (node == null)
+ return;
+
+ if (node is Panel panel)
+ {
+ // Prefer the panel that actually hosts the items
+ if (panel.Children?.Count == expectedChildCount)
+ {
+ best = panel;
+ return;
+ }
+ }
+
+ var childCount = VisualTreeHelper.GetChildrenCount(node);
+ for (var i = 0; i < childCount && best == null; i++)
+ Walk(VisualTreeHelper.GetChild(node, i));
+ }
+
+ Walk(itemsControl);
+ return best;
+ }
+
+ private int GetHiddenRolesCount(double collapsedMaxHeight, int roleCount)
+ {
+ // Ensure the visual tree is ready
+ RolesItemsControl.UpdateLayout();
+
+ var host = FindItemsHostPanel(RolesItemsControl, roleCount);
+ if (host == null || host.Children == null || host.Children.Count == 0)
+ return Math.Max(1, roleCount - 5);
+
+ var visibleCount = 0;
+
+ foreach (var child in host.Children.OfType())
+ {
+ if (child.ActualHeight <= 0)
+ continue;
+
+ // Position relative to the host panel
+ var topLeft = child.TransformToVisual(host).TransformPoint(new Point(0, 0));
+ var bottom = topLeft.Y + child.ActualHeight;
+
+ if (bottom <= collapsedMaxHeight + 0.5)
+ visibleCount++;
+ }
+
+ var hidden = roleCount - visibleCount;
+ return hidden <= 0 ? 1 : hidden;
+ }
+
+ private void UpdateRolesDisplay()
+ {
+ if (RolesItemsControl == null || RolesExpandButton == null)
+ return;
+
+ if (DataContext is not Unicord.Universal.Models.User.UserViewModel user || user.Roles == null)
+ {
+ RolesExpandButton.Visibility = Visibility.Collapsed;
+ RolesItemsControl.MaxHeight = double.PositiveInfinity;
+ return;
+ }
+
+ const double collapsedMaxHeight = 60; // ~2 rows at approximately 30px per row
+
+ // Determine whether content actually overflows the collapsed height.
+ // depending on wrap width and role pill sizes.
+ var availableWidth = RolesItemsControl.ActualWidth;
+ if (availableWidth <= 0)
+ availableWidth = Root?.ActualWidth > 0 ? Root.ActualWidth : 280;
+
+ // Measure full (unclipped) desired height
+ var previousMaxHeight = RolesItemsControl.MaxHeight;
+ RolesItemsControl.MaxHeight = double.PositiveInfinity;
+ RolesItemsControl.Measure(new Windows.Foundation.Size(availableWidth, double.PositiveInfinity));
+ var fullHeight = RolesItemsControl.DesiredSize.Height;
+
+ var needsExpander = fullHeight > (collapsedMaxHeight + 0.5);
+
+ if (!needsExpander)
+ {
+ // Everything fits already; show all and hide expander.
+ _rolesExpanded = true;
+ _rolesAutoExpanded = true;
+ RolesExpandButton.Visibility = Visibility.Collapsed;
+ RolesItemsControl.MaxHeight = double.PositiveInfinity;
+ return;
+ }
+
+ // Many roles (or narrow layout): show expander
+ RolesExpandButton.Visibility = Visibility.Visible;
+
+ RolesItemsControl.MaxHeight = collapsedMaxHeight;
+ RolesExpandIcon.Glyph = "\uE70D"; // ChevronDown
+ var hiddenCount = GetHiddenRolesCount(collapsedMaxHeight, user.Roles.Count);
+ RolesMoreCount.Text = $"+{hiddenCount} more";
+ RolesExpandTooltip.Text = "Show All Roles";
+ }
+
+ private void RolesExpandButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (RolesItemsControl == null || RolesExpandButton == null)
+ return;
+
+ if (DataContext is not Unicord.Universal.Models.User.UserViewModel user || user.Roles == null)
+ return;
+
+ const double collapsedMaxHeight = 60;
+
+ // Toggle between collapsed and expanded
+ var isCurrentlyExpanded = RolesItemsControl.MaxHeight == double.PositiveInfinity;
+
+ if (isCurrentlyExpanded)
+ {
+ // Currently expanded, collapse it
+ RolesItemsControl.MaxHeight = collapsedMaxHeight;
+ AnimateChevron(0); // Rotate to 0 degrees (down)
+ var hiddenCount = GetHiddenRolesCount(collapsedMaxHeight, user.Roles.Count);
+ RolesMoreCount.Text = $"+{hiddenCount} more";
+ RolesExpandTooltip.Text = "Show All Roles";
+ }
+ else
+ {
+ // Currently collapsed, expand it
+ RolesItemsControl.MaxHeight = double.PositiveInfinity;
+ AnimateChevron(-180); // Rotate to -180 degrees (up, clockwise)
+ RolesMoreCount.Text = "Show less";
+ RolesExpandTooltip.Text = "Collapse Roles";
+ }
+ }
+
+ private void AnimateChevron(double toAngle)
+ {
+ if (RolesExpandIconRotation == null)
+ return;
+
+ var storyboard = new Windows.UI.Xaml.Media.Animation.Storyboard();
+ var animation = new Windows.UI.Xaml.Media.Animation.DoubleAnimation
+ {
+ To = toAngle,
+ Duration = new Duration(TimeSpan.FromMilliseconds(200)),
+ EasingFunction = new Windows.UI.Xaml.Media.Animation.CubicEase { EasingMode = Windows.UI.Xaml.Media.Animation.EasingMode.EaseOut }
+ };
+
+ Windows.UI.Xaml.Media.Animation.Storyboard.SetTarget(animation, RolesExpandIconRotation);
+ Windows.UI.Xaml.Media.Animation.Storyboard.SetTargetProperty(animation, "Angle");
+ storyboard.Children.Add(animation);
+ storyboard.Begin();
+ }
+
+ private void UpdateMutualDisplay()
+ {
+ if (DataContext is not Unicord.Universal.Models.User.UserViewModel user)
+ return;
+
+ var mutualFriendsCount = user.MutualFriendsCount;
+ var mutualFriends = user.MutualFriends;
+ var mutualGuilds = user.MutualGuilds;
+ var mutualGuildsCount = mutualGuilds?.Count ?? 0;
+
+ // Reset to a clean baseline so spacing doesn't get stuck when switching users.
+ MutualFriendsButton.Visibility = Visibility.Collapsed;
+ MutualFriendIcons.Visibility = Visibility.Collapsed;
+ MutualServerIcons.Visibility = Visibility.Collapsed;
+ MutualSeparator.Visibility = Visibility.Collapsed;
+ MutualFriendIcons.Margin = MutualIconsMarginForCount(2);
+ MutualServerIcons.Margin = MutualIconsMarginForCount(2);
+ MutualFriendsGroup.Visibility = Visibility.Collapsed;
+ MutualServersGroup.Visibility = Visibility.Collapsed;
+
+ // If mutual friends exist, show them with friend icons and hide server icons
+ if (mutualFriendsCount > 0)
+ {
+ MutualFriendsGroup.Visibility = Visibility.Visible;
+ MutualServersGroup.Visibility = Visibility.Visible;
+ MutualFriendsButton.Visibility = Visibility.Visible;
+ MutualFriendIcons.Visibility = Visibility.Visible;
+ MutualServerIcons.Visibility = Visibility.Collapsed;
+ MutualSeparator.Visibility = mutualGuildsCount > 0 ? Visibility.Visible : Visibility.Collapsed;
+
+ // Populate up to 3 friend icons
+ var friendIconsToShow = System.Math.Min(3, mutualFriendsCount);
+
+ MutualFriendIcons.Margin = MutualIconsMarginForCount(friendIconsToShow);
+
+ FriendIcon1.Visibility = friendIconsToShow >= 1 ? Visibility.Visible : Visibility.Collapsed;
+ FriendIcon2.Visibility = friendIconsToShow >= 2 ? Visibility.Visible : Visibility.Collapsed;
+ FriendIcon3.Visibility = friendIconsToShow >= 3 ? Visibility.Visible : Visibility.Collapsed;
+
+ if (friendIconsToShow >= 1)
+ {
+ FriendIcon1.DisplayName = mutualFriends[0].DisplayName;
+ FriendIcon1.ProfilePicture = CreateImageSource(mutualFriends[0].AvatarUrl);
+ }
+ if (friendIconsToShow >= 2)
+ {
+ FriendIcon2.DisplayName = mutualFriends[1].DisplayName;
+ FriendIcon2.ProfilePicture = CreateImageSource(mutualFriends[1].AvatarUrl);
+ }
+ if (friendIconsToShow >= 3)
+ {
+ FriendIcon3.DisplayName = mutualFriends[2].DisplayName;
+ FriendIcon3.ProfilePicture = CreateImageSource(mutualFriends[2].AvatarUrl);
+ }
+ }
+ else if (mutualGuildsCount > 0)
+ {
+ MutualServersGroup.Visibility = Visibility.Visible;
+ // No mutual friends, show server icons (up to 3)
+ MutualFriendsButton.Visibility = Visibility.Collapsed;
+ MutualFriendIcons.Visibility = Visibility.Collapsed;
+ MutualServerIcons.Visibility = Visibility.Visible;
+ MutualSeparator.Visibility = Visibility.Collapsed;
+
+ // Populate up to 3 server icons
+ var iconsToShow = System.Math.Min(3, mutualGuildsCount);
+
+ MutualServerIcons.Margin = MutualIconsMarginForCount(iconsToShow);
+
+ ServerIcon1.Visibility = iconsToShow >= 1 ? Visibility.Visible : Visibility.Collapsed;
+ ServerIcon2.Visibility = iconsToShow >= 2 ? Visibility.Visible : Visibility.Collapsed;
+ ServerIcon3.Visibility = iconsToShow >= 3 ? Visibility.Visible : Visibility.Collapsed;
+
+ if (iconsToShow >= 1)
+ {
+ ServerIcon1.DisplayName = mutualGuilds[0].Name;
+ ServerIcon1.ProfilePicture = mutualGuilds[0].Icon;
+ }
+ if (iconsToShow >= 2)
+ {
+ ServerIcon2.DisplayName = mutualGuilds[1].Name;
+ ServerIcon2.ProfilePicture = mutualGuilds[1].Icon;
+ }
+ if (iconsToShow >= 3)
+ {
+ ServerIcon3.DisplayName = mutualGuilds[2].Name;
+ ServerIcon3.ProfilePicture = mutualGuilds[2].Icon;
+ }
+ }
+ }
+
+ private static ImageSource CreateImageSource(string url)
+ {
+ if (string.IsNullOrWhiteSpace(url))
+ return null;
+
+ if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
+ return null;
+
+ return new BitmapImage(uri);
+ }
+
+ private void ViewFullProfile_Click(object sender, RoutedEventArgs e)
+ {
+ // "View Full Profile" runs via the command binding;
+ CloseHostFlyout();
}
- // i dislike this
- private void IconLabelButton_Tapped(object sender, TappedRoutedEventArgs e)
+ private async void MutualFriends_Click(object sender, RoutedEventArgs e)
{
+ if (DataContext is Unicord.Universal.Models.User.UserViewModel user)
+ {
+ CloseHostFlyout();
+ await OverlayService.GetForCurrentView()
+ .ShowOverlayAsync((user, "mutualfriends"));
+ }
+ }
+ private async void MutualServers_Click(object sender, RoutedEventArgs e)
+ {
+ if (DataContext is Unicord.Universal.Models.User.UserViewModel user)
+ {
+ CloseHostFlyout();
+ await OverlayService.GetForCurrentView()
+ .ShowOverlayAsync((user, "mutual"));
+ }
+ }
+
+ private void CopyUserId_Click(object sender, RoutedEventArgs e)
+ {
+ if (DataContext is Unicord.Universal.Models.User.UserViewModel user && user.User?.Id != null)
+ {
+ var dataPackage = new DataPackage();
+ dataPackage.SetText(user.User.Id.ToString());
+ Clipboard.SetContent(dataPackage);
+ }
+ }
+
+ private void MessageTextBox_Loaded(object sender, RoutedEventArgs e)
+ {
+ if (sender is Windows.UI.Xaml.Controls.TextBox textBox && DataContext is Unicord.Universal.Models.User.UserViewModel user)
+ {
+ textBox.PlaceholderText = $"Message @{user.Username}";
+ }
+ }
+
+ private async void MessageTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
+ {
+ if (e.Key != VirtualKey.Enter)
+ return;
+
+ e.Handled = true;
+
+ var isShiftDown = Window.Current?.CoreWindow?.GetKeyState(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down) == true;
+ if (isShiftDown)
+ {
+ if (MessageTextBox == null)
+ return;
+
+ var cursorPosition = MessageTextBox.SelectionStart;
+ var text = MessageTextBox.Text ?? string.Empty;
+ var newline = "\r\n";
+
+ MessageTextBox.Text = text.Insert(cursorPosition, newline);
+ MessageTextBox.SelectionStart = cursorPosition + newline.Length;
+ return;
+ }
+
+ await TrySendMessageAsync();
+
+ // If it sent successfully, close.
+ // (TrySendMessageAsync handles empty text / missing DM channel.)
+ if (string.IsNullOrWhiteSpace(MessageTextBox?.Text))
+ CloseHostFlyout();
+ }
+
+ private async void SendButton_Click(object sender, RoutedEventArgs e)
+ {
+ var sent = await TrySendMessageAsync();
+ if (sent)
+ CloseHostFlyout();
+ }
+
+ private async Task TrySendMessageAsync()
+ {
+ try
+ {
+ var text = MessageTextBox?.Text;
+ if (string.IsNullOrWhiteSpace(text))
+ return false;
+
+ if (DataContext is not Unicord.Universal.Models.User.UserViewModel user)
+ return false;
+
+ DiscordDmChannel dmChannel = null;
+
+ // Prefer the member API when available
+ if (user.Member != null)
+ {
+ await user.Member.SendMessageAsync(text);
+
+ // Try to get the DM channel that was created
+ var discord = DiscordManager.Discord;
+ dmChannel = discord?.PrivateChannels.Values
+ .FirstOrDefault(c => c.Type == ChannelType.Private && c.Recipients.Count == 1 && c.Recipients[0].Id == user.Id);
+ }
+ else
+ {
+ // No member context (e.g., DM user). We can only send if a DM channel is already cached.
+ var discord = DiscordManager.Discord;
+ if (discord == null)
+ return false;
+
+ dmChannel = discord.PrivateChannels.Values
+ .FirstOrDefault(c => c.Type == ChannelType.Private && c.Recipients.Count == 1 && c.Recipients[0].Id == user.Id);
+
+ if (dmChannel == null)
+ return false;
+
+ await dmChannel.SendMessageAsync(text);
+ }
+
+ MessageTextBox.Text = string.Empty;
+
+ // Navigate to the DM conversation
+ if (dmChannel != null)
+ {
+ var navigationService = DiscordNavigationService.GetForCurrentView();
+ await navigationService.NavigateAsync(dmChannel);
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex);
+ return false;
+ }
+ }
+
+ private void EmoteButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (EmoteFlyout != null && EmoteButton != null)
+ {
+ if (EmoteButton.IsChecked == true)
+ {
+ EmoteFlyout.ShowAt(EmoteButton);
+ }
+ else
+ {
+ EmoteFlyout.Hide();
+ }
+ }
+ }
+
+ private void EmoteFlyout_Opening(object sender, object e)
+ {
+ if (DataContext is Unicord.Universal.Models.User.UserViewModel user && EmotePicker != null)
+ {
+ // If user has a guild context, use any channel from that guild
+ if (user.Guild != null)
+ {
+ var channelVm = user.Guild.AccessibleChannels?.FirstOrDefault();
+ if (channelVm != null)
+ {
+ EmotePicker.Channel = channelVm.Channel;
+ }
+ else
+ {
+ EmotePicker.Channel = null;
+ }
+ }
+ else
+ {
+ EmotePicker.Channel = null;
+ }
+
+ // Refresh items now that Channel may have changed
+ EmotePicker.Load();
+ }
+ }
+
+ private void EmoteFlyout_Closed(object sender, object e)
+ {
+ if (EmoteButton != null)
+ {
+ EmoteButton.IsChecked = false;
+ }
+ }
+
+ private void EmotePicker_EmojiPicked(object sender, Models.Emoji.EmojiViewModel e)
+ {
+ if (MessageTextBox != null)
+ {
+ var cursorPosition = MessageTextBox.SelectionStart;
+ var text = MessageTextBox.Text ?? "";
+
+ // Insert emoji at cursor position
+ MessageTextBox.Text = text.Insert(cursorPosition, e.ToString());
+
+ // Move cursor after the inserted emoji
+ MessageTextBox.SelectionStart = cursorPosition + e.ToString().Length;
+
+ // Focus back to the textbox
+ MessageTextBox.Focus(FocusState.Programmatic);
+ }
+
+ // Close the flyout and uncheck the button
+ EmoteFlyout?.Hide();
+ if (EmoteButton != null)
+ {
+ EmoteButton.IsChecked = false;
+ }
+ }
+
+ private async void EditProfile_Click(object sender, RoutedEventArgs e)
+ {
+ CloseHostFlyout();
+ await SettingsService.GetForCurrentView().OpenAsync(SettingsPageType.Accounts);
}
}
}
diff --git a/Unicord.Universal/Controls/Flyouts/UserListContextFlyout.xaml b/Unicord.Universal/Controls/Flyouts/UserListContextFlyout.xaml
index 22875215..fe4a46f9 100644
--- a/Unicord.Universal/Controls/Flyouts/UserListContextFlyout.xaml
+++ b/Unicord.Universal/Controls/Flyouts/UserListContextFlyout.xaml
@@ -16,7 +16,7 @@
-
+
diff --git a/Unicord.Universal/Controls/Flyouts/UserListContextFlyout.xaml.cs b/Unicord.Universal/Controls/Flyouts/UserListContextFlyout.xaml.cs
index 4bf3bb53..42982ce6 100644
--- a/Unicord.Universal/Controls/Flyouts/UserListContextFlyout.xaml.cs
+++ b/Unicord.Universal/Controls/Flyouts/UserListContextFlyout.xaml.cs
@@ -1,4 +1,8 @@
-using Windows.UI.Xaml.Controls;
+using Unicord.Universal.Models.User;
+using Unicord.Universal.Pages.Overlay;
+using Unicord.Universal.Services;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
namespace Unicord.Universal.Controls.Flyouts
{
@@ -8,5 +12,14 @@ public UserListContextFlyout()
{
InitializeComponent();
}
+
+ private async void ProfileItem_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is FrameworkElement element && element.DataContext is UserViewModel user)
+ {
+ await OverlayService.GetForCurrentView()
+ .ShowOverlayAsync(user);
+ }
+ }
}
}
diff --git a/Unicord.Universal/Dialogs/ProfileOverlay.xaml b/Unicord.Universal/Dialogs/ProfileOverlay.xaml
index 2232aedc..a3186b7b 100644
--- a/Unicord.Universal/Dialogs/ProfileOverlay.xaml
+++ b/Unicord.Universal/Dialogs/ProfileOverlay.xaml
@@ -9,122 +9,412 @@
xmlns:toolkit="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:media="using:Microsoft.UI.Xaml.Media"
xmlns:lib="using:Microsoft.UI.Xaml.Controls"
- xmlns:users="using:Unicord.Universal.Controls.Users" xmlns:guild="using:Unicord.Universal.Models.Guild"
- x:DefaultBindMode="OneWay" mc:Ignorable="d" MaxWidth="550">
-
-
- Segoe UI Variable Display
- Medium
-
+ xmlns:users="using:Unicord.Universal.Controls.Users"
+ xmlns:guild="using:Unicord.Universal.Models.Guild"
+ xmlns:user="using:Unicord.Universal.Models.User"
+ x:DefaultBindMode="OneWay"
+ mc:Ignorable="d"
+ MaxWidth="645"
+ Height="600">
-
-
-
-
-
-
-
-
-
-
-
-
-
+ BorderThickness="1"
+ CornerRadius="{ThemeResource ProfileOverlay_Dialog_CornerRadius}">
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Unicord.Universal/Dialogs/ProfileOverlay.xaml.cs b/Unicord.Universal/Dialogs/ProfileOverlay.xaml.cs
index 47ff7d93..c21e9d1b 100644
--- a/Unicord.Universal/Dialogs/ProfileOverlay.xaml.cs
+++ b/Unicord.Universal/Dialogs/ProfileOverlay.xaml.cs
@@ -1,14 +1,22 @@
-using Unicord.Universal.Models.User;
+using System.Linq;
+using DSharpPlus.Entities;
+using Unicord.Universal.Extensions;
+using Unicord.Universal.Models.User;
using Unicord.Universal.Services;
+using Windows.ApplicationModel.DataTransfer;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
+using Lib = Microsoft.UI.Xaml.Controls;
namespace Unicord.Universal.Dialogs
{
public sealed partial class ProfileOverlay : UserControl
{
+ private const double CompactThresholdWidth = 640;
+ private string _pendingNavigationTag;
+
public UserViewModel User
{
get => (UserViewModel)GetValue(UserProperty);
@@ -22,11 +30,107 @@ private static void OnUserChanged(DependencyObject d, DependencyPropertyChangedE
{
var overlay = (ProfileOverlay)d;
overlay.Bindings.Update();
+
+ overlay.ApplyPendingNavigationOrDefault();
}
public ProfileOverlay()
{
InitializeComponent();
+
+ SizeChanged += (_, __) => UpdatePaneMode();
+
+ Loaded += (s, e) =>
+ {
+ ApplyPendingNavigationOrDefault();
+
+ UpdatePaneMode();
+ };
+ }
+
+ public void NavigateToSection(string tag)
+ {
+ _pendingNavigationTag = tag;
+ ApplyPendingNavigationOrDefault();
+ }
+
+ private void ApplyPendingNavigationOrDefault()
+ {
+ if (NavView == null)
+ return;
+
+ var tag = _pendingNavigationTag;
+
+ Lib.NavigationViewItem targetItem = tag switch
+ {
+ "mutualfriends" => MutualFriendsItem,
+ "mutual" => MutualServersItem,
+ "activities" => ActivitiesItem,
+ _ => null
+ };
+
+ // If we have a pending navigation target, apply it
+ if (targetItem != null)
+ {
+ _pendingNavigationTag = null;
+ NavView.SelectedItem = targetItem;
+ }
+ // Only apply default Overview if nothing is selected yet
+ else if (NavView.SelectedItem == null && _pendingNavigationTag == null)
+ {
+ NavView.SelectedItem = OverviewItem;
+ }
+ }
+
+ private void UpdatePaneMode()
+ {
+ if (NavView == null)
+ return;
+
+ var isCompact = ActualWidth > 0 && ActualWidth < CompactThresholdWidth;
+
+ var desiredMode = isCompact
+ ? Lib.NavigationViewPaneDisplayMode.LeftCompact
+ : Lib.NavigationViewPaneDisplayMode.Left;
+
+ if (NavView.PaneDisplayMode != desiredMode)
+ NavView.PaneDisplayMode = desiredMode;
+
+ var desiredIsPaneOpen = !isCompact;
+ if (NavView.IsPaneOpen != desiredIsPaneOpen)
+ NavView.IsPaneOpen = desiredIsPaneOpen;
+
+ NavView.InvalidateMeasure();
+ NavView.UpdateLayout();
+ }
+
+ private void NavView_SelectionChanged(Lib.NavigationView sender, Lib.NavigationViewSelectionChangedEventArgs args)
+ {
+ if (args.SelectedItemContainer?.Tag is not string tag)
+ return;
+
+ // Hide all content
+ OverviewContent.Visibility = Visibility.Collapsed;
+ ActivitiesContent.Visibility = Visibility.Collapsed;
+ MutualFriendsContent.Visibility = Visibility.Collapsed;
+ MutualServersContent.Visibility = Visibility.Collapsed;
+
+ // Show selected content
+ switch (tag)
+ {
+ case "overview":
+ OverviewContent.Visibility = Visibility.Visible;
+ break;
+ case "activities":
+ ActivitiesContent.Visibility = Visibility.Visible;
+ break;
+ case "mutualfriends":
+ MutualFriendsContent.Visibility = Visibility.Visible;
+ break;
+ case "mutual":
+ MutualServersContent.Visibility = Visibility.Visible;
+ break;
+ }
}
private void DropShadowPanel_PreviewKeyUp(object sender, KeyRoutedEventArgs e)
@@ -37,5 +141,61 @@ private void DropShadowPanel_PreviewKeyUp(object sender, KeyRoutedEventArgs e)
.CloseOverlay();
}
}
+
+ private async void MutualServersList_ItemClick(object sender, ItemClickEventArgs e)
+ {
+ if (e.ClickedItem is Unicord.Universal.Models.Guild.GuildViewModel guild)
+ {
+ OverlayService.GetForCurrentView().CloseOverlay();
+
+ if (App.LocalSettings.Read(Constants.ENABLE_GUILD_BROWSING, Constants.ENABLE_GUILD_BROWSING_DEFAULT))
+ {
+ // Navigate to guild browsing view
+ var discordPage = Window.Current.Content.FindChild();
+ if (discordPage != null)
+ {
+ discordPage.LeftSidebarFrame.Navigate(typeof(Pages.Subpages.GuildChannelListPage), guild.Guild);
+ }
+ }
+ else
+ {
+ // Navigate to previously selected channel or first accessible text channel
+ var channelId = App.RoamingSettings.Read($"GuildPreviousChannels::{guild.Guild.Id}", 0UL);
+ if (!guild.Guild.Channels.TryGetValue(channelId, out var channel) || (!channel.IsAccessible() || !channel.IsText()))
+ {
+ channel = guild.Guild.Channels.Values
+ .Where(c => c.IsAccessible())
+ .Where(c => c.IsText())
+ .OrderBy(c => c.Position)
+ .FirstOrDefault();
+ }
+
+ if (channel != null)
+ {
+ await DiscordNavigationService.GetForCurrentView()
+ .NavigateAsync(channel);
+ }
+ }
+ }
+ }
+
+ private async void MutualFriendsList_ItemClick(object sender, ItemClickEventArgs e)
+ {
+ if (e.ClickedItem is Unicord.Universal.Models.User.UserViewModel friendUser)
+ {
+ await OverlayService.GetForCurrentView()
+ .ReplaceOverlayWithAnimationAsync(friendUser);
+ }
+ }
+
+ private void CopyUserId_Click(object sender, RoutedEventArgs e)
+ {
+ if (User == null)
+ return;
+
+ var dataPackage = new DataPackage();
+ dataPackage.SetText(User.Id.ToString());
+ Clipboard.SetContent(dataPackage);
+ }
}
}
diff --git a/Unicord.Universal/MainPage.xaml.cs b/Unicord.Universal/MainPage.xaml.cs
index 00bb400e..5a2631ce 100644
--- a/Unicord.Universal/MainPage.xaml.cs
+++ b/Unicord.Universal/MainPage.xaml.cs
@@ -348,6 +348,24 @@ public void HideCustomOverlay()
HideOverlayStoryboard.Begin();
}
+ public Task HideCustomOverlayAsync()
+ {
+ if (CustomOverlayGrid.Visibility == Visibility.Collapsed)
+ return Task.CompletedTask;
+
+ var tcs = new TaskCompletionSource