feat(desktop): add configurable notifications
This commit is contained in:
parent
34af294aaf
commit
588aa25822
|
@ -1,33 +1,44 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||||
<Product Id="*" Name="XIVChat for Windows" Language="1033" Version="1.0.0.0" Manufacturer="XIVChat" UpgradeCode="82f30f39-99e8-4040-9bc0-32c1eac159df">
|
<Product Id="*" Name="XIVChat for Windows" Language="1033" Version="1.0.0.0" Manufacturer="XIVChat"
|
||||||
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
|
UpgradeCode="82f30f39-99e8-4040-9bc0-32c1eac159df">
|
||||||
|
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine"/>
|
||||||
|
|
||||||
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
|
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed."/>
|
||||||
<MediaTemplate EmbedCab="yes" />
|
<MediaTemplate EmbedCab="yes"/>
|
||||||
|
|
||||||
<Feature Id="ProductFeature" Title="XIVChat for Windows" Level="1">
|
<Feature Id="ProductFeature" Title="XIVChat for Windows" Level="1">
|
||||||
<ComponentGroupRef Id="XIVChat_Desktop" />
|
<ComponentGroupRef Id="XIVChat_Desktop"/>
|
||||||
<ComponentRef Id="App_Start_Menu_Shortcut" />
|
<ComponentRef Id="App_Start_Menu_Shortcut"/>
|
||||||
</Feature>
|
</Feature>
|
||||||
</Product>
|
</Product>
|
||||||
|
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||||
<Directory Id="ProgramFiles64Folder">
|
<Directory Id="ProgramFiles64Folder">
|
||||||
<Directory Id="INSTALLFOLDER" Name="XIVChat for Windows" />
|
<Directory Id="INSTALLFOLDER" Name="XIVChat for Windows"/>
|
||||||
</Directory>
|
</Directory>
|
||||||
<Directory Id="ProgramMenuFolder">
|
<Directory Id="ProgramMenuFolder">
|
||||||
<Directory Id="ApplicationProgramsFolder" Name="XIVChat for Windows"/>
|
<Directory Id="ApplicationProgramsFolder" Name="XIVChat for Windows"/>
|
||||||
</Directory>
|
</Directory>
|
||||||
</Directory>
|
</Directory>
|
||||||
|
|
||||||
<DirectoryRef Id="ApplicationProgramsFolder">
|
<DirectoryRef Id="ApplicationProgramsFolder">
|
||||||
<Component Id="App_Start_Menu_Shortcut" Guid="6EF30C67-2EAA-4988-8470-E1149D5C1B50">
|
<Component Id="App_Start_Menu_Shortcut" Guid="6EF30C67-2EAA-4988-8470-E1149D5C1B50">
|
||||||
<Shortcut Id="ApplicationStartMenuShortcut" Name="XIVChat for Windows" Target="[INSTALLFOLDER]XIVChat Desktop.exe" WorkingDirectory="INSTALLFOLDER" />
|
<Shortcut Id="ApplicationStartMenuShortcut" Name="XIVChat for Windows"
|
||||||
<RemoveFolder Id="RemoveApplicationProgramsFolder" Directory="ApplicationProgramsFolder" On="uninstall"/>
|
Target="[INSTALLFOLDER]XIVChat Desktop.exe" WorkingDirectory="INSTALLFOLDER">
|
||||||
<RegistryValue Root="HKCU" Key="Software\XIVChat for Windows" Name="installed" Type="integer" Value="1" KeyPath="yes" />
|
<!-- AUMID -->
|
||||||
</Component>
|
<ShortcutProperty Key="System.AppUserModel.ID" Value="XIVChat.XIVChat_Desktop"/>
|
||||||
</DirectoryRef>
|
|
||||||
</Fragment>
|
<!-- COM CLSID -->
|
||||||
|
<ShortcutProperty Key="System.AppUserModel.ToastActivatorCLSID"
|
||||||
|
Value="{F12BCC85-6FEE-4A9A-BBB8-08DFAA7BE1A8}"/>
|
||||||
|
</Shortcut>
|
||||||
|
<RemoveFolder Id="RemoveApplicationProgramsFolder" Directory="ApplicationProgramsFolder"
|
||||||
|
On="uninstall"/>
|
||||||
|
<RegistryValue Root="HKCU" Key="Software\XIVChat for Windows" Name="installed" Type="integer" Value="1"
|
||||||
|
KeyPath="yes"/>
|
||||||
|
</Component>
|
||||||
|
</DirectoryRef>
|
||||||
|
</Fragment>
|
||||||
</Wix>
|
</Wix>
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Markup;
|
using System.Windows.Markup;
|
||||||
|
using Windows.UI.Notifications;
|
||||||
|
using Microsoft.Toolkit.Uwp.Notifications;
|
||||||
using ModernWpf;
|
using ModernWpf;
|
||||||
|
using XIVChatCommon.Message;
|
||||||
|
using XIVChatCommon.Message.Server;
|
||||||
|
|
||||||
// TODO: key word notification, notifications on message type, targeted message (like emote targeting you)
|
// TODO: search messages
|
||||||
|
// TODO: notifications for targeted messages (like emote targeting you)
|
||||||
|
|
||||||
namespace XIVChat_Desktop {
|
namespace XIVChat_Desktop {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -34,6 +40,8 @@ namespace XIVChat_Desktop {
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
private async void Application_Startup(object sender, StartupEventArgs e) {
|
private async void Application_Startup(object sender, StartupEventArgs e) {
|
||||||
|
Notifications.Initialise();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.Config = Configuration.Load() ?? new Configuration();
|
this.Config = Configuration.Load() ?? new Configuration();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
@ -121,6 +129,7 @@ namespace XIVChat_Desktop {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Connection = new Connection(this, host, port);
|
this.Connection = new Connection(this, host, port);
|
||||||
|
this.Connection.ReceiveMessage += this.OnReceiveMessage;
|
||||||
Task.Run(this.Connection.Connect);
|
Task.Run(this.Connection.Connect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,5 +141,37 @@ namespace XIVChat_Desktop {
|
||||||
this.Connection?.Disconnect();
|
this.Connection?.Disconnect();
|
||||||
this.Connection = null;
|
this.Connection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnReceiveMessage(ServerMessage message) {
|
||||||
|
if (!this.Config.Notifications.Any(notif => notif.Matches(message))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sender = message.GetSenderPlayer();
|
||||||
|
|
||||||
|
var builder = new ToastContentBuilder();
|
||||||
|
|
||||||
|
if (sender != null) {
|
||||||
|
var name = sender.Name;
|
||||||
|
|
||||||
|
if (sender.Server != 0) {
|
||||||
|
name += $" ({Util.WorldName(sender.Server)})";
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AddText(name);
|
||||||
|
} else {
|
||||||
|
builder.AddText("Notification");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder
|
||||||
|
.AddText(message.ContentText)
|
||||||
|
.AddAttributionText(message.Channel.Name());
|
||||||
|
|
||||||
|
var content = builder.GetToastContent();
|
||||||
|
|
||||||
|
var toast = new ToastNotification(content.GetXml());
|
||||||
|
|
||||||
|
DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,5 +84,41 @@
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
<TabItem Header="Notifications">
|
||||||
|
<Grid Margin="8">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<ListView Grid.Column="0"
|
||||||
|
x:Name="Notifications"
|
||||||
|
ItemsSource="{Binding Config.Notifications}"
|
||||||
|
MouseDoubleClick="Notifications_DoubleClick">
|
||||||
|
<ListView.View>
|
||||||
|
<GridView>
|
||||||
|
<GridViewColumn Header="Name"
|
||||||
|
DisplayMemberBinding="{Binding Name}" />
|
||||||
|
</GridView>
|
||||||
|
</ListView.View>
|
||||||
|
</ListView>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="1"
|
||||||
|
Margin="8,0,0,0"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<Button Margin="0,0,0,4"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Content="Add"
|
||||||
|
Click="Notifications_Add_Click" />
|
||||||
|
<Button Margin="0,0,0,4"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Content="Edit"
|
||||||
|
Click="Notifications_Edit_Click" />
|
||||||
|
<Button HorizontalAlignment="Stretch"
|
||||||
|
Content="Delete"
|
||||||
|
Click="Notifications_Delete_Click" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
</TabControl>
|
</TabControl>
|
||||||
</local:XivChatWindow>
|
</local:XivChatWindow>
|
||||||
|
|
|
@ -49,5 +49,35 @@ namespace XIVChat_Desktop {
|
||||||
var allDigits = e.Text.All(c => char.IsDigit(c));
|
var allDigits = e.Text.All(c => char.IsDigit(c));
|
||||||
e.Handled = !allDigits;
|
e.Handled = !allDigits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Notifications_DoubleClick(object sender, MouseButtonEventArgs e) {
|
||||||
|
var context = ((FrameworkElement)e.OriginalSource).DataContext;
|
||||||
|
if (!(context is Notification notification)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new ManageNotification(this, notification).Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Notifications_Add_Click(object sender, RoutedEventArgs e) {
|
||||||
|
new ManageNotification(this, null).Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Notifications_Edit_Click(object sender, RoutedEventArgs e) {
|
||||||
|
if (!(this.Notifications.SelectedItem is Notification notif)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new ManageNotification(this, notif).Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Notifications_Delete_Click(object sender, RoutedEventArgs e) {
|
||||||
|
if (!(this.Notifications.SelectedItem is Notification notif)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Config.Notifications.Remove(notif);
|
||||||
|
this.Config.Save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using XIVChatCommon.Message;
|
using XIVChatCommon.Message;
|
||||||
using XIVChatCommon.Message.Server;
|
using XIVChatCommon.Message.Server;
|
||||||
|
|
||||||
|
@ -73,6 +74,8 @@ namespace XIVChat_Desktop {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<Notification> Notifications { get; set; } = new ObservableCollection<Notification>();
|
||||||
|
|
||||||
private void OnPropertyChanged(string propName) {
|
private void OnPropertyChanged(string propName) {
|
||||||
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
|
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
|
||||||
}
|
}
|
||||||
|
@ -357,4 +360,70 @@ namespace XIVChat_Desktop {
|
||||||
return this.Types.Any(type => type.Allowed(code));
|
return this.Types.Any(type => type.Allowed(code));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonObject]
|
||||||
|
public class Notification {
|
||||||
|
public string Name { get; set; }
|
||||||
|
public List<ChatType> Channels { get; set; } = new List<ChatType>();
|
||||||
|
public List<string> Substrings { get; set; } = new List<string>();
|
||||||
|
|
||||||
|
private IReadOnlyCollection<String> regexes = new List<string>();
|
||||||
|
|
||||||
|
public IReadOnlyCollection<string> Regexes {
|
||||||
|
get => this.regexes;
|
||||||
|
set {
|
||||||
|
this.regexes = value;
|
||||||
|
this.ResetRegexes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public Lazy<List<Regex>> ParsedRegexes { get; private set; } = null!;
|
||||||
|
|
||||||
|
public Notification(string name) {
|
||||||
|
this.Name = name;
|
||||||
|
this.ResetRegexes();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetRegexes() {
|
||||||
|
this.ParsedRegexes = new Lazy<List<Regex>>(
|
||||||
|
() => {
|
||||||
|
try {
|
||||||
|
return this.ParseRegexes();
|
||||||
|
} catch (ArgumentException) {
|
||||||
|
return new List<Regex>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Regex> ParseRegexes() {
|
||||||
|
return this.Regexes
|
||||||
|
.Select(regex => new Regex(regex, RegexOptions.Compiled))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "ConvertIfStatementToReturnStatement")]
|
||||||
|
public bool Matches(ServerMessage message) {
|
||||||
|
if (!this.Channels.Contains(message.Channel)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Substrings.Count == 0 && this.Regexes.Count == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = message.ContentText;
|
||||||
|
|
||||||
|
if (this.Substrings.Any(substring => text.ContainsIgnoreCase(substring))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.ParsedRegexes.Value.Any(regex => regex.IsMatch(text))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,10 @@ namespace XIVChat_Desktop {
|
||||||
|
|
||||||
public readonly CancellationTokenSource cancel = new CancellationTokenSource();
|
public readonly CancellationTokenSource cancel = new CancellationTokenSource();
|
||||||
|
|
||||||
|
public delegate void ReceiveMessageDelegate(ServerMessage message);
|
||||||
|
|
||||||
|
public event ReceiveMessageDelegate? ReceiveMessage;
|
||||||
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
public string? CurrentChannel { get; private set; }
|
public string? CurrentChannel { get; private set; }
|
||||||
|
|
||||||
|
@ -269,6 +273,7 @@ namespace XIVChat_Desktop {
|
||||||
var message = ServerMessage.Decode(payload);
|
var message = ServerMessage.Decode(payload);
|
||||||
|
|
||||||
this.app.Dispatch(() => {
|
this.app.Dispatch(() => {
|
||||||
|
this.ReceiveMessage?.Invoke(message);
|
||||||
this.app.Window.AddMessage(message);
|
this.app.Window.AddMessage(message);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using System.Windows;
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Media;
|
||||||
using XIVChatCommon.Message.Server;
|
using XIVChatCommon.Message.Server;
|
||||||
|
|
||||||
namespace XIVChat_Desktop.Controls {
|
namespace XIVChat_Desktop.Controls {
|
||||||
|
@ -54,11 +56,15 @@ namespace XIVChat_Desktop.Controls {
|
||||||
}
|
}
|
||||||
|
|
||||||
var message = textBlock.Message;
|
var message = textBlock.Message;
|
||||||
|
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var config = ((App)Application.Current).Config;
|
||||||
|
if (config.Notifications.Any(notif => notif.Matches(message))) {
|
||||||
|
textBlock.Background = new SolidColorBrush(Color.FromArgb(128, 200, 100, 100));
|
||||||
|
}
|
||||||
|
|
||||||
textBlock.ClearValue(TextProperty);
|
textBlock.ClearValue(TextProperty);
|
||||||
textBlock.Inlines.Clear();
|
textBlock.Inlines.Clear();
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
@ -165,6 +166,7 @@ namespace XIVChat_Desktop {
|
||||||
this.Senders.Add(sender);
|
this.Senders.Add(sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "ConvertIfStatementToReturnStatement")]
|
||||||
public bool AllowedMinusSenders(ServerMessage message) {
|
public bool AllowedMinusSenders(ServerMessage message) {
|
||||||
if (!base.Allowed(message)) {
|
if (!base.Allowed(message)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -181,6 +183,7 @@ namespace XIVChat_Desktop {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "ConvertIfStatementToReturnStatement")]
|
||||||
public override bool Allowed(ServerMessage message) {
|
public override bool Allowed(ServerMessage message) {
|
||||||
if (!this.AllowedMinusSenders(message)) {
|
if (!this.AllowedMinusSenders(message)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
103
XIVChat Desktop/ManageNotification.xaml
Normal file
103
XIVChat Desktop/ManageNotification.xaml
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
<local:XivChatWindow x:Class="XIVChat_Desktop.ManageNotification"
|
||||||
|
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"
|
||||||
|
Closed="ManageNotification_OnClosed"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="Manage notification"
|
||||||
|
Height="800"
|
||||||
|
Width="450"
|
||||||
|
d:DataContext="{d:DesignInstance local:ManageNotification}">
|
||||||
|
<local:XivChatWindow.CommandBindings>
|
||||||
|
<CommandBinding Command="local:ManageNotification.AddEmpty"
|
||||||
|
Executed="AddEmpty_Execute"
|
||||||
|
CanExecute="AddEmpty_CanExecute" />
|
||||||
|
</local:XivChatWindow.CommandBindings>
|
||||||
|
<Grid Margin="8">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBox Grid.Row="0"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
ui:ControlHelper.PlaceholderText="Name"
|
||||||
|
ui:ControlHelper.Header="Name"
|
||||||
|
Text="{Binding Notification.Name}" />
|
||||||
|
|
||||||
|
<Label Grid.Row="1"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
Content="Regular expressions" />
|
||||||
|
|
||||||
|
<ScrollViewer Height="100"
|
||||||
|
Grid.Row="2">
|
||||||
|
<ItemsControl ItemsSource="{Binding Regexes}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBox Margin="0,0,0,8">
|
||||||
|
<TextBox.Text>
|
||||||
|
<Binding Path="Value">
|
||||||
|
<Binding.ValidationRules>
|
||||||
|
<local:RegexValidator />
|
||||||
|
</Binding.ValidationRules>
|
||||||
|
</Binding>
|
||||||
|
</TextBox.Text>
|
||||||
|
</TextBox>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<Button Grid.Row="3"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
Content="Add"
|
||||||
|
Command="local:ManageNotification.AddEmpty"
|
||||||
|
CommandParameter="{Binding Regexes}" />
|
||||||
|
|
||||||
|
<Label Grid.Row="4"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
Content="Substrings" />
|
||||||
|
|
||||||
|
<ScrollViewer Height="100"
|
||||||
|
Grid.Row="5">
|
||||||
|
<ItemsControl ItemsSource="{Binding Substrings}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBox Margin="0,0,0,8"
|
||||||
|
Text="{Binding Value}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<Button Grid.Row="6"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
Content="Add"
|
||||||
|
Command="local:ManageNotification.AddEmpty"
|
||||||
|
CommandParameter="{Binding Substrings}" />
|
||||||
|
|
||||||
|
<Label Grid.Row="7"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
Content="Channels" />
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="8">
|
||||||
|
<WrapPanel x:Name="Channels"
|
||||||
|
Orientation="Vertical" />
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</local:XivChatWindow>
|
21
XIVChat Desktop/Notifications.cs
Normal file
21
XIVChat Desktop/Notifications.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Microsoft.Toolkit.Uwp.Notifications;
|
||||||
|
|
||||||
|
namespace XIVChat_Desktop {
|
||||||
|
public class Notifications {
|
||||||
|
public static void Initialise() {
|
||||||
|
DesktopNotificationManagerCompat.RegisterAumidAndComServer<XivChatNotificationActivator>("XIVChat.XIVChat_Desktop");
|
||||||
|
DesktopNotificationManagerCompat.RegisterActivator<XivChatNotificationActivator>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[ClassInterface(ClassInterfaceType.None)]
|
||||||
|
[ComSourceInterfaces(typeof(INotificationActivationCallback))]
|
||||||
|
[Guid("F12BCC85-6FEE-4A9A-BBB8-08DFAA7BE1A8"), ComVisible(true)]
|
||||||
|
public class XivChatNotificationActivator : NotificationActivator {
|
||||||
|
public override void OnActivated(string invokedArgs, NotificationUserInput userInput, string appUserModelId) {
|
||||||
|
// TODO: Handle activation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Windows.Controls;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
|
||||||
namespace XIVChat_Desktop {
|
namespace XIVChat_Desktop {
|
||||||
|
@ -673,5 +678,63 @@ namespace XIVChat_Desktop {
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool ContainsIgnoreCase(this string haystack, string needle) {
|
||||||
|
return CultureInfo.InvariantCulture.CompareInfo.IndexOf(haystack, needle, CompareOptions.IgnoreCase) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsValidRegex(this string regex) {
|
||||||
|
try {
|
||||||
|
_ = new Regex(regex);
|
||||||
|
return true;
|
||||||
|
} catch (ArgumentException) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsValidRegex(this string regex, out ArgumentException exception) {
|
||||||
|
try {
|
||||||
|
_ = new Regex(regex);
|
||||||
|
exception = default!;
|
||||||
|
return true;
|
||||||
|
} catch (ArgumentException ex) {
|
||||||
|
exception = ex;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RegexValidator : ValidationRule {
|
||||||
|
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
|
||||||
|
if (!(value is string text)) {
|
||||||
|
return new ValidationResult(false, "Value is not text.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.IsValidRegex(out var ex)
|
||||||
|
? ValidationResult.ValidResult
|
||||||
|
: new ValidationResult(false, $"Invalid regular expression: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StringWrapper : INotifyPropertyChanged {
|
||||||
|
private string value;
|
||||||
|
|
||||||
|
public String Value {
|
||||||
|
get => this.value;
|
||||||
|
set {
|
||||||
|
this.value = value;
|
||||||
|
this.OnPropertyChanged(nameof(this.Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringWrapper(string value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
private void OnPropertyChanged([CallerMemberName] string? propertyName = null) {
|
||||||
|
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="6.1.1" />
|
||||||
<PackageReference Include="ModernWpfUI" Version="0.9.2" />
|
<PackageReference Include="ModernWpfUI" Version="0.9.2" />
|
||||||
<PackageReference Include="ModernWpfUI.MahApps" Version="0.9.2" />
|
<PackageReference Include="ModernWpfUI.MahApps" Version="0.9.2" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
|
|
|
@ -538,6 +538,94 @@ namespace XIVChatCommon.Message {
|
||||||
CrossLinkshell8 = 107,
|
CrossLinkshell8 = 107,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ChatTypeExt {
|
||||||
|
public static string? Name(this ChatType type) {
|
||||||
|
return type switch {
|
||||||
|
ChatType.Debug => "Debug",
|
||||||
|
ChatType.Urgent => "Urgent",
|
||||||
|
ChatType.Notice => "Notice",
|
||||||
|
ChatType.Say => "Say",
|
||||||
|
ChatType.Shout => "Shout",
|
||||||
|
ChatType.TellOutgoing => "Tell (Outgoing)",
|
||||||
|
ChatType.TellIncoming => "Tell (Incoming)",
|
||||||
|
ChatType.Party => "Party",
|
||||||
|
ChatType.Alliance => "Alliance",
|
||||||
|
ChatType.Linkshell1 => "Linkshell [1]",
|
||||||
|
ChatType.Linkshell2 => "Linkshell [2]",
|
||||||
|
ChatType.Linkshell3 => "Linkshell [3]",
|
||||||
|
ChatType.Linkshell4 => "Linkshell [4]",
|
||||||
|
ChatType.Linkshell5 => "Linkshell [5]",
|
||||||
|
ChatType.Linkshell6 => "Linkshell [6]",
|
||||||
|
ChatType.Linkshell7 => "Linkshell [7]",
|
||||||
|
ChatType.Linkshell8 => "Linkshell [8]",
|
||||||
|
ChatType.FreeCompany => "Free Company",
|
||||||
|
ChatType.NoviceNetwork => "Novice Network",
|
||||||
|
ChatType.CustomEmote => "Custom Emotes",
|
||||||
|
ChatType.StandardEmote => "Standard Emotes",
|
||||||
|
ChatType.Yell => "Yell",
|
||||||
|
ChatType.CrossParty => "Cross-world Party",
|
||||||
|
ChatType.PvpTeam => "PvP Team",
|
||||||
|
ChatType.CrossLinkshell1 => "Cross-world Linkshell [1]",
|
||||||
|
ChatType.Damage => "Damage dealt",
|
||||||
|
ChatType.Miss => "Failed attacks",
|
||||||
|
ChatType.Action => "Actions used",
|
||||||
|
ChatType.Item => "Items used",
|
||||||
|
ChatType.Healing => "Healing",
|
||||||
|
ChatType.GainBuff => "Beneficial effects granted",
|
||||||
|
ChatType.GainDebuff => "Detrimental effects inflicted",
|
||||||
|
ChatType.LoseBuff => "Beneficial effects lost",
|
||||||
|
ChatType.LoseDebuff => "Detrimental effects cured",
|
||||||
|
ChatType.Alarm => "Alarm Notifications",
|
||||||
|
ChatType.Echo => "Echo",
|
||||||
|
ChatType.System => "System Messages",
|
||||||
|
ChatType.BattleSystem => "Battle System Messages",
|
||||||
|
ChatType.GatheringSystem => "Gathering System Messages",
|
||||||
|
ChatType.Error => "Error Messages",
|
||||||
|
ChatType.NpcDialogue => "NPC Dialogue",
|
||||||
|
ChatType.LootNotice => "Loot Notices",
|
||||||
|
ChatType.Progress => "Progression Messages",
|
||||||
|
ChatType.LootRoll => "Loot Messages",
|
||||||
|
ChatType.Crafting => "Synthesis Messages",
|
||||||
|
ChatType.Gathering => "Gathering Messages",
|
||||||
|
ChatType.NpcAnnouncement => "NPC Dialogue (Announcements)",
|
||||||
|
ChatType.FreeCompanyAnnouncement => "Free Company Announcements",
|
||||||
|
ChatType.FreeCompanyLoginLogout => "Free Company Member Login Notifications",
|
||||||
|
ChatType.RetainerSale => "Retainer Sale Notifications",
|
||||||
|
ChatType.PeriodicRecruitmentNotification => "Periodic Recruitment Notifications",
|
||||||
|
ChatType.Sign => "Sign Messages for PC Targets",
|
||||||
|
ChatType.RandomNumber => "Random Number Messages",
|
||||||
|
ChatType.NoviceNetworkSystem => "Novice Network Notifications",
|
||||||
|
ChatType.Orchestrion => "Current Orchestrion Track Messages",
|
||||||
|
ChatType.PvpTeamAnnouncement => "PvP Team Announcements",
|
||||||
|
ChatType.PvpTeamLoginLogout => "PvP Team Member Login Notifications",
|
||||||
|
ChatType.MessageBook => "Message Book Alert",
|
||||||
|
ChatType.GmTell => "Tell (GM)",
|
||||||
|
ChatType.GmSay => "Say (GM)",
|
||||||
|
ChatType.GmShout => "Shout (GM)",
|
||||||
|
ChatType.GmYell => "Yell (GM)",
|
||||||
|
ChatType.GmParty => "Party (GM)",
|
||||||
|
ChatType.GmFreeCompany => "Free Company (GM)",
|
||||||
|
ChatType.GmLinkshell1 => "Linkshell [1] (GM)",
|
||||||
|
ChatType.GmLinkshell2 => "Linkshell [2] (GM)",
|
||||||
|
ChatType.GmLinkshell3 => "Linkshell [3] (GM)",
|
||||||
|
ChatType.GmLinkshell4 => "Linkshell [4] (GM)",
|
||||||
|
ChatType.GmLinkshell5 => "Linkshell [5] (GM)",
|
||||||
|
ChatType.GmLinkshell6 => "Linkshell [6] (GM)",
|
||||||
|
ChatType.GmLinkshell7 => "Linkshell [7] (GM)",
|
||||||
|
ChatType.GmLinkshell8 => "Linkshell [8] (GM)",
|
||||||
|
ChatType.GmNoviceNetwork => "Novice Network (GM)",
|
||||||
|
ChatType.CrossLinkshell2 => "Cross-world Linkshell [2]",
|
||||||
|
ChatType.CrossLinkshell3 => "Cross-world Linkshell [3]",
|
||||||
|
ChatType.CrossLinkshell4 => "Cross-world Linkshell [4]",
|
||||||
|
ChatType.CrossLinkshell5 => "Cross-world Linkshell [5]",
|
||||||
|
ChatType.CrossLinkshell6 => "Cross-world Linkshell [6]",
|
||||||
|
ChatType.CrossLinkshell7 => "Cross-world Linkshell [7]",
|
||||||
|
ChatType.CrossLinkshell8 => "Cross-world Linkshell [8]",
|
||||||
|
_ => type.ToString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1028:Enum Storage should be Int32")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1028:Enum Storage should be Int32")]
|
||||||
public enum ChatSource : ushort {
|
public enum ChatSource : ushort {
|
||||||
Self = 2,
|
Self = 2,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user