feat(desktop): add very basic friend list

This commit is contained in:
Anna 2020-11-12 23:53:21 -05:00
parent c169c8099f
commit bc0b8d73d7
7 changed files with 193 additions and 3 deletions

View File

@ -17,6 +17,7 @@
<local:BoolToVisibility x:Key="InverseBoolToVisibilityConverter"
TrueValue="Collapsed"
FalseValue="Visible" />
<local:TitleCaseConverter x:Key="TitleCaseConverter" />
<ResourceDictionary.MergedDictionaries>
<ui:ThemeResources AccentColor="#02ccee" />

View File

@ -20,6 +20,7 @@ namespace XIVChat_Desktop {
private TcpClient? client;
private readonly Channel<string> outgoing = Channel.CreateUnbounded<string>();
private readonly Channel<byte[]> outgoingMessages = Channel.CreateUnbounded<byte[]>();
private readonly Channel<byte[]> incoming = Channel.CreateUnbounded<byte[]>();
private readonly Channel<byte> cancelChannel = Channel.CreateBounded<byte>(2);
@ -49,6 +50,13 @@ namespace XIVChat_Desktop {
this.outgoing.Writer.TryWrite(message);
}
public void RequestFriendList() {
var msg = new ClientPlayerList {
Type = PlayerListType.Friend,
};
this.outgoingMessages.Writer.TryWrite(msg.Encode());
}
public void Disconnect() {
this.cancel.Cancel();
for (var i = 0; i < 2; i++) {
@ -156,11 +164,12 @@ namespace XIVChat_Desktop {
var incoming = this.incoming.Reader.ReadAsync().AsTask();
var outgoing = this.outgoing.Reader.ReadAsync().AsTask();
var outgoingMessage = this.outgoingMessages.Reader.ReadAsync().AsTask();
var cancel = this.cancelChannel.Reader.ReadAsync().AsTask();
// listen for incoming and outgoing messages and cancel requests
while (!this.cancel.IsCancellationRequested) {
var result = await Task.WhenAny(incoming, outgoing, cancel);
var result = await Task.WhenAny(incoming, outgoing, outgoingMessage, cancel);
if (result == incoming) {
if (this.incoming.Reader.Completion.IsCompleted) {
break;
@ -186,6 +195,21 @@ namespace XIVChat_Desktop {
});
break;
}
} else if (result == outgoingMessage) {
var toSend = await outgoingMessage;
outgoingMessage = this.outgoingMessages.Reader.ReadAsync().AsTask();
try {
await SecretMessage.SendSecretMessage(stream, handshake.Keys.tx, toSend, this.cancel.Token);
} catch (Exception ex) {
this.app.Dispatch(() => {
this.app.Window.AddSystemMessage("Error sending message.");
// ReSharper disable once LocalizableElement
Console.WriteLine($"Error sending message: {ex.Message}");
Console.WriteLine(ex.StackTrace);
});
break;
}
} else if (result == cancel) {
try {
// NOTE: purposely not including cancellation token because it will already be cancelled here
@ -276,6 +300,17 @@ namespace XIVChat_Desktop {
break;
case ServerOperation.PlayerList:
var playerList = ServerPlayerList.Decode(payload);
if (playerList.Type == PlayerListType.Friend) {
this.app.Dispatch(() => {
this.app.Window.FriendList.Clear();
foreach (var player in playerList.Players) {
this.app.Window.FriendList.Add(player);
}
});
}
break;
case ServerOperation.LinkshellList:
break;

View File

@ -4,7 +4,6 @@ using System.Globalization;
using System.Text;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using XIVChatCommon;
namespace XIVChat_Desktop {
@ -75,6 +74,30 @@ namespace XIVChat_Desktop {
}
}
public class TitleCaseConverter : IValueConverter {
public object? Convert(object? value, Type targetType, object parameter, CultureInfo culture) {
var s = value?.ToString();
if (s == null) {
return null;
}
var lastWasSpace = true;
var newString = new StringBuilder();
foreach (var c in s.ToCharArray()) {
newString.Append(lastWasSpace ? char.ToUpperInvariant(c) : c);
lastWasSpace = c.IsWhitespace();
}
return newString.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
public class NotConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return !((bool)value);

View File

@ -0,0 +1,66 @@
<Window x:Class="XIVChat_Desktop.FriendList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:XIVChat_Desktop"
xmlns:ui="http://schemas.modernwpf.com/2019"
ui:WindowHelper.UseModernWindowStyle="True"
mc:Ignorable="d"
Title="Friend list"
WindowStartupLocation="CenterOwner"
Height="400"
Width="700"
Closed="FriendList_OnClosed"
d:DataContext="{d:DesignInstance local:FriendList}">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ProgressBar Grid.Row="0"
IsIndeterminate="True"
Visibility="{Binding Waiting, Converter={StaticResource BoolToVisibilityConverter}}" />
<DataGrid Grid.Row="0"
ItemsSource="{Binding App.Window.FriendList}"
AutoGenerateColumns="False"
SelectionUnit="FullRow"
SelectionMode="Single"
IsReadOnly="True"
CanUserReorderColumns="True"
Visibility="{Binding Waiting, Converter={StaticResource InverseBoolToVisibilityConverter}}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name"
Binding="{Binding Name}" />
<DataGridTextColumn Header="Job"
Binding="{Binding JobName, Converter={StaticResource TitleCaseConverter}}" />
<DataGridTextColumn Header="Location"
Binding="{Binding TerritoryName}" />
<DataGridTextColumn Header="FC"
Binding="{Binding FreeCompany}" />
</DataGrid.Columns>
</DataGrid>
<!-- <ListView Grid.Row="0" -->
<!-- ItemsSource="{Binding App.Window.FriendList}" -->
<!-- Visibility="{Binding Waiting, Converter={StaticResource InverseBoolToVisibilityConverter}}"> -->
<!-- <ListView.View> -->
<!-- <GridView> -->
<!-- <GridViewColumn Header="Name" -->
<!-- DisplayMemberBinding="{Binding Name}" /> -->
<!-- <GridViewColumn Header="Job" -->
<!-- DisplayMemberBinding="{Binding JobName}" /> -->
<!-- <GridViewColumn Header="Location" -->
<!-- DisplayMemberBinding="{Binding TerritoryName}" /> -->
<!-- <GridViewColumn Header="FC" -->
<!-- DisplayMemberBinding="{Binding FreeCompany}" /> -->
<!-- </GridView> -->
<!-- </ListView.View> -->
<!-- </ListView> -->
<Button Grid.Row="1"
IsEnabled="{Binding App.Connected}"
Content="Refresh"
Margin="0,8,0,0"
Click="Refresh_Click" />
</Grid>
</Window>

View File

@ -0,0 +1,54 @@
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace XIVChat_Desktop {
public partial class FriendList : INotifyPropertyChanged {
public App App => (App)Application.Current;
private bool waiting;
public bool Waiting {
get => this.waiting;
set {
this.waiting = value;
this.OnPropertyChanged(nameof(this.Waiting));
}
}
public FriendList(Window owner) {
this.Owner = owner;
this.InitializeComponent();
this.DataContext = this;
this.App.Window.FriendList.CollectionChanged += this.OnFriendListChanged;
}
private void Refresh_Click(object sender, RoutedEventArgs e) {
var conn = this.App.Connection;
if (conn == null) {
return;
}
this.Waiting = true;
conn.RequestFriendList();
}
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? propertyName = null) {
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void OnFriendListChanged(object sender, NotifyCollectionChangedEventArgs e) {
this.Waiting = false;
}
private void FriendList_OnClosed(object? sender, EventArgs e) {
this.App.Window.FriendList.CollectionChanged -= this.OnFriendListChanged;
}
}
}

View File

@ -52,6 +52,11 @@
<MenuItem Header="Manage"
Click="ManageTabs_Click" />
</MenuItem>
<MenuItem Header="Players"
WindowChrome.IsHitTestVisibleInChrome="{Binding App.Config.CompactMode}">
<MenuItem Header="Friend list"
Click="FriendList_Click" />
</MenuItem>
</Menu>
<TabControl x:Name="Tabs"
Margin="8,0,8,8"

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
@ -17,6 +18,7 @@ namespace XIVChat_Desktop {
public App App => (App)Application.Current;
public List<ServerMessage> Messages { get; } = new List<ServerMessage>();
public ObservableCollection<Player> FriendList { get; } = new ObservableCollection<Player>();
public string InputPlaceholder => this.App.Connection?.Available == true ? "Send a message..." : "Chat is currently unavailable";
@ -79,7 +81,7 @@ namespace XIVChat_Desktop {
this.insertAt = this.Messages.Count;
}
// detect if scroller is at the bottom
// detect iFriendList_Click the bottom
var scroller = this.FindElementByName<ScrollViewer>(this.Tabs, "scroller");
var wasAtBottom = Math.Abs(scroller!.VerticalOffset - scroller.ScrollableHeight) < .0001;
@ -194,5 +196,9 @@ namespace XIVChat_Desktop {
this.Close();
this.App.Shutdown();
}
private void FriendList_Click(object sender, RoutedEventArgs e) {
new FriendList(this).Show();
}
}
}