Further work on ui-rework, also implemented services, re-implemented raiding functionality and PubSub
This commit is contained in:
parent
c2309599f2
commit
a8a481d4f9
28
App.axaml.cs
28
App.axaml.cs
@ -5,11 +5,14 @@ using Avalonia;
|
|||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Data.Core.Plugins;
|
using Avalonia.Data.Core.Plugins;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using BetterRaid.Extensions;
|
||||||
using BetterRaid.Services;
|
using BetterRaid.Services;
|
||||||
|
using BetterRaid.Services.Implementations;
|
||||||
using BetterRaid.ViewModels;
|
using BetterRaid.ViewModels;
|
||||||
using BetterRaid.Views;
|
using BetterRaid.Views;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using TwitchLib.Api;
|
using TwitchLib.Api;
|
||||||
|
using TwitchLib.PubSub;
|
||||||
|
|
||||||
namespace BetterRaid;
|
namespace BetterRaid;
|
||||||
|
|
||||||
@ -43,6 +46,7 @@ public partial class App : Application
|
|||||||
|
|
||||||
public static TwitchAPI? TwitchApi => _twitchApi;
|
public static TwitchAPI? TwitchApi => _twitchApi;
|
||||||
public static bool HasUserZnSubbed => _hasUserZnSubbed;
|
public static bool HasUserZnSubbed => _hasUserZnSubbed;
|
||||||
|
public static string BetterRaidDataPath => _betterRaidDataPath;
|
||||||
|
|
||||||
public IServiceProvider? Provider => _provider;
|
public IServiceProvider? Provider => _provider;
|
||||||
public static string? TwitchBroadcasterId => _twitchBroadcasterId;
|
public static string? TwitchBroadcasterId => _twitchBroadcasterId;
|
||||||
@ -59,8 +63,8 @@ public partial class App : Application
|
|||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
InitializeServices();
|
|
||||||
LoadTwitchToken();
|
LoadTwitchToken();
|
||||||
|
InitializeServices();
|
||||||
|
|
||||||
AvaloniaXamlLoader.Load(_provider, this);
|
AvaloniaXamlLoader.Load(_provider, this);
|
||||||
}
|
}
|
||||||
@ -98,8 +102,8 @@ public partial class App : Application
|
|||||||
private void InitializeServices()
|
private void InitializeServices()
|
||||||
{
|
{
|
||||||
_services.AddSingleton<ITwitchDataService, TwitchDataService>();
|
_services.AddSingleton<ITwitchDataService, TwitchDataService>();
|
||||||
|
_services.AddSingleton<ITwitchPubSubService, TwitchPubSubService>();
|
||||||
_services.AddTransient<MainWindowViewModel>();
|
_services.AddTransient<MainWindowViewModel>();
|
||||||
_services.AddTransient<AboutWindowViewModel>();
|
|
||||||
|
|
||||||
_provider = _services.BuildServiceProvider();
|
_provider = _services.BuildServiceProvider();
|
||||||
}
|
}
|
||||||
@ -154,7 +158,6 @@ public partial class App : Application
|
|||||||
}
|
}
|
||||||
|
|
||||||
_twitchBroadcasterId = channel.Id;
|
_twitchBroadcasterId = channel.Id;
|
||||||
Console.WriteLine(_twitchBroadcasterId);
|
|
||||||
|
|
||||||
Console.WriteLine("[INFO] Connected to Twitch API as '{0}'!", user.DisplayName);
|
Console.WriteLine("[INFO] Connected to Twitch API as '{0}'!", user.DisplayName);
|
||||||
|
|
||||||
@ -188,25 +191,18 @@ public partial class App : Application
|
|||||||
{
|
{
|
||||||
BindingPlugins.DataValidators.RemoveAt(0);
|
BindingPlugins.DataValidators.RemoveAt(0);
|
||||||
|
|
||||||
var vm = _provider?.GetRequiredService<MainWindowViewModel>();
|
|
||||||
|
|
||||||
switch (ApplicationLifetime)
|
switch (ApplicationLifetime)
|
||||||
{
|
{
|
||||||
case IClassicDesktopStyleApplicationLifetime desktop:
|
case IClassicDesktopStyleApplicationLifetime desktop:
|
||||||
// Line below is needed to remove Avalonia data validation.
|
desktop.MainWindow = new MainWindow();
|
||||||
// Without this line you will get duplicate validations from both Avalonia and CT
|
desktop.MainWindow.InjectDataContext<MainWindowViewModel>();
|
||||||
|
|
||||||
desktop.MainWindow = new MainWindow
|
|
||||||
{
|
|
||||||
DataContext = vm
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ISingleViewApplicationLifetime singleViewPlatform:
|
case ISingleViewApplicationLifetime singleViewPlatform:
|
||||||
singleViewPlatform.MainView = new MainWindow
|
singleViewPlatform.MainView = new MainWindow();
|
||||||
{
|
singleViewPlatform.MainView.InjectDataContext<MainWindowViewModel>();
|
||||||
DataContext = vm
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
17
Attributes/PubSubAttribute.cs
Normal file
17
Attributes/PubSubAttribute.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BetterRaid.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = true)]
|
||||||
|
public class PubSubAttribute : Attribute
|
||||||
|
{
|
||||||
|
public PubSubType Type { get; }
|
||||||
|
public string ChannelIdField { get; set; }
|
||||||
|
|
||||||
|
public PubSubAttribute(PubSubType type, string channelIdField)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
ChannelIdField = channelIdField;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
11
Attributes/PubSubType.cs
Normal file
11
Attributes/PubSubType.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace BetterRaid.Attributes;
|
||||||
|
|
||||||
|
public enum PubSubType
|
||||||
|
{
|
||||||
|
VideoPlayback,
|
||||||
|
Follows,
|
||||||
|
Subscriptions,
|
||||||
|
ChannelPoints,
|
||||||
|
Bits,
|
||||||
|
Raids
|
||||||
|
}
|
@ -14,7 +14,6 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Models\" />
|
|
||||||
<AvaloniaResource Include="Assets\**" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.5.002.0
|
VisualStudioVersion = 17.5.002.0
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BetterRaid", "BetterRaid.csproj", "{49E459C8-9DCF-4D6D-95FC-75303243F248}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BetterRaid", "BetterRaid.csproj", "{C23C5237-3D18-424A-ACF2-62215BE5D557}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@ -11,10 +11,10 @@ Global
|
|||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{49E459C8-9DCF-4D6D-95FC-75303243F248}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{C23C5237-3D18-424A-ACF2-62215BE5D557}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{49E459C8-9DCF-4D6D-95FC-75303243F248}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C23C5237-3D18-424A-ACF2-62215BE5D557}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{49E459C8-9DCF-4D6D-95FC-75303243F248}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{C23C5237-3D18-424A-ACF2-62215BE5D557}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{49E459C8-9DCF-4D6D-95FC-75303243F248}.Release|Any CPU.Build.0 = Release|Any CPU
|
{C23C5237-3D18-424A-ACF2-62215BE5D557}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
29
Converters/ChannelOnlineColorConverter.cs
Normal file
29
Converters/ChannelOnlineColorConverter.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using Avalonia.Media;
|
||||||
|
|
||||||
|
namespace BetterRaid.Converters;
|
||||||
|
|
||||||
|
public class ChannelOnlineColorConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is bool isOnline)
|
||||||
|
{
|
||||||
|
return isOnline ? new SolidColorBrush(Colors.GreenYellow) : new SolidColorBrush(Colors.OrangeRed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SolidColorBrush(Colors.OrangeRed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is SolidColorBrush brush)
|
||||||
|
{
|
||||||
|
return brush.Color == Colors.GreenYellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
55
Extensions/MemberInfoExtensions.cs
Normal file
55
Extensions/MemberInfoExtensions.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace BetterRaid.Extensions;
|
||||||
|
|
||||||
|
public static class MemberInfoExtensions
|
||||||
|
{
|
||||||
|
public static bool SetValue<T>(this MemberInfo member, object instance, T value)
|
||||||
|
{
|
||||||
|
var targetType = member switch
|
||||||
|
{
|
||||||
|
PropertyInfo p => p.PropertyType,
|
||||||
|
FieldInfo f => f.FieldType,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (targetType == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (member is PropertyInfo property)
|
||||||
|
{
|
||||||
|
if (targetType == typeof(T) || targetType.IsAssignableFrom(typeof(T)))
|
||||||
|
{
|
||||||
|
property.SetValue(instance, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetType == typeof(string))
|
||||||
|
{
|
||||||
|
property.SetValue(instance, value?.ToString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (member is FieldInfo field)
|
||||||
|
{
|
||||||
|
if (targetType == typeof(T) || targetType.IsAssignableFrom(typeof(T)))
|
||||||
|
{
|
||||||
|
field.SetValue(instance, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetType == typeof(string))
|
||||||
|
{
|
||||||
|
field.SetValue(instance, value?.ToString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -15,44 +15,18 @@ public static class Tools
|
|||||||
private static Task? _oauthWaiterTask;
|
private static Task? _oauthWaiterTask;
|
||||||
|
|
||||||
// Source: https://stackoverflow.com/a/43232486
|
// Source: https://stackoverflow.com/a/43232486
|
||||||
public static void StartOAuthLogin(string url, Action? callback = null, CancellationToken? token = null)
|
public static void StartOAuthLogin(string url, Action? callback = null, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
if (_oauthListener != null)
|
if (_oauthListener == null)
|
||||||
return;
|
|
||||||
|
|
||||||
var _token = token ?? CancellationToken.None;
|
|
||||||
|
|
||||||
_oauthListener = new HttpListener();
|
|
||||||
_oauthListener.Prefixes.Add("http://localhost:9900/");
|
|
||||||
_oauthListener.Start();
|
|
||||||
|
|
||||||
_oauthWaiterTask = WaitForCallback(callback, _token);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
Process.Start(url);
|
_oauthListener = new HttpListener();
|
||||||
}
|
_oauthListener.Prefixes.Add("http://localhost:9900/");
|
||||||
catch
|
_oauthListener.Start();
|
||||||
{
|
|
||||||
// hack because of this: https://github.com/dotnet/corefx/issues/10361
|
_oauthWaiterTask = WaitForCallback(callback, token);
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
||||||
{
|
|
||||||
url = url.Replace("&", "^&");
|
|
||||||
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
|
||||||
}
|
|
||||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
|
||||||
{
|
|
||||||
Process.Start("xdg-open", url);
|
|
||||||
}
|
|
||||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
|
||||||
{
|
|
||||||
Process.Start("open", url);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpenUrl(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task WaitForCallback(Action? callback, CancellationToken token)
|
private static async Task WaitForCallback(Action? callback, CancellationToken token)
|
||||||
@ -147,6 +121,35 @@ public static class Tools
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void OpenUrl(string url)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process.Start(url);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// hack because of this: https://github.com/dotnet/corefx/issues/10361
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
url = url.Replace("&", "^&");
|
||||||
|
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||||
|
{
|
||||||
|
Process.Start("xdg-open", url);
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||||
|
{
|
||||||
|
Process.Start("open", url);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private const string OAUTH_CLIENT_DOCUMENT =
|
private const string OAUTH_CLIENT_DOCUMENT =
|
||||||
@"
|
@"
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
10
Models/PubSubListener.cs
Normal file
10
Models/PubSubListener.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace BetterRaid.Models;
|
||||||
|
|
||||||
|
public class PubSubListener
|
||||||
|
{
|
||||||
|
public string ChannelId { get; set; }
|
||||||
|
public object? Instance { get; set; }
|
||||||
|
public MemberInfo? Listener { get; set; }
|
||||||
|
}
|
@ -1,11 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using BetterRaid.Attributes;
|
||||||
|
|
||||||
namespace BetterRaid.Models;
|
namespace BetterRaid.Models;
|
||||||
|
|
||||||
public class TwitchChannel : INotifyPropertyChanged
|
public class TwitchChannel : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
|
private string? _broadcasterId;
|
||||||
private string? _viewerCount;
|
private string? _viewerCount;
|
||||||
private bool _isLive;
|
private bool _isLive;
|
||||||
private string? _name;
|
private string? _name;
|
||||||
@ -17,9 +21,17 @@ public class TwitchChannel : INotifyPropertyChanged
|
|||||||
|
|
||||||
public string? BroadcasterId
|
public string? BroadcasterId
|
||||||
{
|
{
|
||||||
get;
|
get => _broadcasterId;
|
||||||
set;
|
set
|
||||||
|
{
|
||||||
|
if (value == _broadcasterId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_broadcasterId = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string? Name
|
public string? Name
|
||||||
{
|
{
|
||||||
get => _name;
|
get => _name;
|
||||||
@ -32,6 +44,7 @@ public class TwitchChannel : INotifyPropertyChanged
|
|||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsLive
|
public bool IsLive
|
||||||
{
|
{
|
||||||
get => _isLive;
|
get => _isLive;
|
||||||
@ -44,6 +57,8 @@ public class TwitchChannel : INotifyPropertyChanged
|
|||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[PubSub(PubSubType.VideoPlayback, nameof(BroadcasterId))]
|
||||||
public string? ViewerCount
|
public string? ViewerCount
|
||||||
{
|
{
|
||||||
get => _viewerCount;
|
get => _viewerCount;
|
||||||
@ -127,6 +142,28 @@ public class TwitchChannel : INotifyPropertyChanged
|
|||||||
Name = channelName;
|
Name = channelName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void InitChannel()
|
||||||
|
{
|
||||||
|
var channel = App.TwitchApi?.Helix.Search.SearchChannelsAsync(Name).Result.Channels
|
||||||
|
.FirstOrDefault(c => c.BroadcasterLogin.Equals(Name, StringComparison.CurrentCultureIgnoreCase));
|
||||||
|
|
||||||
|
if (channel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var stream = App.TwitchApi?.Helix.Streams.GetStreamsAsync(userLogins: [ Name ]).Result.Streams
|
||||||
|
.FirstOrDefault(s => s.UserLogin.Equals(Name, StringComparison.CurrentCultureIgnoreCase));
|
||||||
|
|
||||||
|
BroadcasterId = channel.Id;
|
||||||
|
DisplayName = channel.DisplayName;
|
||||||
|
ThumbnailUrl = channel.ThumbnailUrl;
|
||||||
|
Category = channel.GameName;
|
||||||
|
Title = channel.Title;
|
||||||
|
IsLive = channel.IsLive;
|
||||||
|
ViewerCount = stream?.ViewerCount == null
|
||||||
|
? null
|
||||||
|
: $"{stream.ViewerCount}";
|
||||||
|
}
|
||||||
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
@ -2,5 +2,11 @@ namespace BetterRaid.Services;
|
|||||||
|
|
||||||
public interface ITwitchDataService
|
public interface ITwitchDataService
|
||||||
{
|
{
|
||||||
|
public bool IsRaidStarted { get; set; }
|
||||||
|
|
||||||
|
public void StartRaid(string from, string to);
|
||||||
|
public void StartRaidCommand(object? arg);
|
||||||
|
public void StopRaid();
|
||||||
|
public void StopRaidCommand();
|
||||||
|
public void OpenChannelCommand(object? arg);
|
||||||
}
|
}
|
7
Services/ITwitchPubSubService.cs
Normal file
7
Services/ITwitchPubSubService.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace BetterRaid.Services;
|
||||||
|
|
||||||
|
public interface ITwitchPubSubService
|
||||||
|
{
|
||||||
|
void RegisterReceiver<T>(T receiver) where T : class;
|
||||||
|
void UnregisterReceiver<T>(T receiver) where T : class;
|
||||||
|
}
|
@ -1,6 +1,78 @@
|
|||||||
namespace BetterRaid.Services;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using BetterRaid.Misc;
|
||||||
|
|
||||||
public class TwitchDataService : ITwitchDataService
|
namespace BetterRaid.Services.Implementations;
|
||||||
|
|
||||||
|
public class TwitchDataService : ITwitchDataService, INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
|
private bool _isRaidStarted;
|
||||||
|
|
||||||
|
public bool IsRaidStarted
|
||||||
|
{
|
||||||
|
get => _isRaidStarted;
|
||||||
|
set => SetField(ref _isRaidStarted, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartRaid(string from, string to)
|
||||||
|
{
|
||||||
|
// TODO: Also check, if the logged in user is live
|
||||||
|
|
||||||
|
App.TwitchApi?.Helix.Raids.StartRaidAsync(from, to);
|
||||||
|
IsRaidStarted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartRaidCommand(object? arg)
|
||||||
|
{
|
||||||
|
if (arg == null || App.TwitchBroadcasterId == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var from = App.TwitchBroadcasterId;
|
||||||
|
var to = arg.ToString()!;
|
||||||
|
|
||||||
|
StartRaid(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopRaid()
|
||||||
|
{
|
||||||
|
if (IsRaidStarted == false)
|
||||||
|
return;
|
||||||
|
|
||||||
|
App.TwitchApi?.Helix.Raids.CancelRaidAsync(App.TwitchBroadcasterId);
|
||||||
|
IsRaidStarted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopRaidCommand()
|
||||||
|
{
|
||||||
|
StopRaid();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenChannelCommand(object? arg)
|
||||||
|
{
|
||||||
|
var channelName = arg?.ToString();
|
||||||
|
if (string.IsNullOrEmpty(channelName))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var url = $"https://twitch.tv/{channelName}";
|
||||||
|
|
||||||
|
Tools.OpenUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
|
||||||
|
field = value;
|
||||||
|
OnPropertyChanged(propertyName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
188
Services/Implementations/TwitchPubSubService.cs
Normal file
188
Services/Implementations/TwitchPubSubService.cs
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using BetterRaid.Attributes;
|
||||||
|
using BetterRaid.Extensions;
|
||||||
|
using BetterRaid.Models;
|
||||||
|
using TwitchLib.PubSub;
|
||||||
|
using TwitchLib.PubSub.Events;
|
||||||
|
|
||||||
|
namespace BetterRaid.Services.Implementations;
|
||||||
|
|
||||||
|
public class TwitchPubSubService : ITwitchPubSubService
|
||||||
|
{
|
||||||
|
private readonly Dictionary<PubSubType, List<PubSubListener>> _targets = new();
|
||||||
|
private readonly TwitchPubSub _sub;
|
||||||
|
|
||||||
|
public TwitchPubSubService()
|
||||||
|
{
|
||||||
|
_sub = new TwitchPubSub();
|
||||||
|
|
||||||
|
_sub.OnPubSubServiceConnected += OnSubOnOnPubSubServiceConnected;
|
||||||
|
_sub.OnPubSubServiceError += OnSubOnOnPubSubServiceError;
|
||||||
|
_sub.OnPubSubServiceClosed += OnSubOnOnPubSubServiceClosed;
|
||||||
|
_sub.OnListenResponse += OnSubOnOnListenResponse;
|
||||||
|
_sub.OnViewCount += OnSubOnOnViewCount;
|
||||||
|
|
||||||
|
_sub.Connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSubOnOnViewCount(object? sender, OnViewCountArgs args)
|
||||||
|
{
|
||||||
|
var listeners = _targets
|
||||||
|
.Where(x => x.Key == PubSubType.VideoPlayback)
|
||||||
|
.SelectMany(x => x.Value)
|
||||||
|
.Where(x => x.ChannelId == args.ChannelId)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var listener in listeners)
|
||||||
|
{
|
||||||
|
if (listener.Listener == null || listener.Instance == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (listener.Listener.SetValue(listener.Instance, args.Viewers) == false)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[ERROR][{nameof(TwitchPubSubService)}] Failed to set {listener.Instance.GetType().Name}.{listener.Listener.Name} to {args.Viewers}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[DEBUG][{nameof(TwitchPubSubService)}] Setting {listener.Instance.GetType().Name}.{listener.Listener.Name} to {args.Viewers}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[ERROR][{nameof(TwitchPubSubService)}] Exception while setting {listener.Instance?.GetType().Name}.{listener.Listener?.Name}: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSubOnOnListenResponse(object? sender, OnListenResponseArgs args)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Listen Response: {args.Topic}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSubOnOnPubSubServiceClosed(object? sender, EventArgs args)
|
||||||
|
{
|
||||||
|
Console.WriteLine("PubSub Closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSubOnOnPubSubServiceError(object? sender, OnPubSubServiceErrorArgs args)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"PubSub Error: {args.Exception.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSubOnOnPubSubServiceConnected(object? sender, EventArgs args)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Connected to PubSub");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterReceiver<T>(T receiver) where T : class
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(receiver, nameof(receiver));
|
||||||
|
|
||||||
|
Console.WriteLine($"[DEBUG][{nameof(TwitchPubSubService)}] Registering {receiver.GetType().Name}");
|
||||||
|
|
||||||
|
var type = typeof(T);
|
||||||
|
var publicTargets = type
|
||||||
|
.GetProperties()
|
||||||
|
.Concat(
|
||||||
|
type.GetFields() as MemberInfo[]
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (var target in publicTargets)
|
||||||
|
{
|
||||||
|
if (target.GetCustomAttribute<PubSubAttribute>() is not { } attr)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var channelId =
|
||||||
|
type.GetProperty(attr.ChannelIdField)?.GetValue(receiver)?.ToString() ??
|
||||||
|
type.GetField(attr.ChannelIdField)?.GetValue(receiver)?.ToString();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(channelId))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[ERROR][{nameof(TwitchPubSubService)}] {target.Name} is missing ChannelIdField named {attr.ChannelIdField}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (attr.Type)
|
||||||
|
{
|
||||||
|
case PubSubType.Bits:
|
||||||
|
break;
|
||||||
|
case PubSubType.ChannelPoints:
|
||||||
|
break;
|
||||||
|
case PubSubType.Follows:
|
||||||
|
break;
|
||||||
|
case PubSubType.Raids:
|
||||||
|
break;
|
||||||
|
case PubSubType.Subscriptions:
|
||||||
|
break;
|
||||||
|
case PubSubType.VideoPlayback:
|
||||||
|
Console.WriteLine($"[DEBUG][{nameof(TwitchPubSubService)}] Registering {target.Name} for {attr.Type}");
|
||||||
|
if (_targets.TryGetValue(PubSubType.VideoPlayback, out var value))
|
||||||
|
{
|
||||||
|
value.Add(new PubSubListener
|
||||||
|
{
|
||||||
|
ChannelId = channelId,
|
||||||
|
Instance = receiver,
|
||||||
|
Listener = target
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_targets.Add(PubSubType.VideoPlayback, [
|
||||||
|
new PubSubListener
|
||||||
|
{
|
||||||
|
ChannelId = channelId,
|
||||||
|
Instance = receiver,
|
||||||
|
Listener = target
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
_sub.ListenToVideoPlayback(channelId);
|
||||||
|
_sub.SendTopics(App.TwitchOAuthAccessToken);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnregisterReceiver<T>(T receiver) where T : class
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(receiver, nameof(receiver));
|
||||||
|
|
||||||
|
foreach (var target in _targets)
|
||||||
|
{
|
||||||
|
var topic = target.Key;
|
||||||
|
var listener = target.Value.Where(x => x.Instance == receiver).ToList();
|
||||||
|
|
||||||
|
foreach (var l in listener)
|
||||||
|
{
|
||||||
|
switch (topic)
|
||||||
|
{
|
||||||
|
case PubSubType.Bits:
|
||||||
|
break;
|
||||||
|
case PubSubType.ChannelPoints:
|
||||||
|
break;
|
||||||
|
case PubSubType.Follows:
|
||||||
|
break;
|
||||||
|
case PubSubType.Raids:
|
||||||
|
break;
|
||||||
|
case PubSubType.Subscriptions:
|
||||||
|
break;
|
||||||
|
case PubSubType.VideoPlayback:
|
||||||
|
_sub.ListenToVideoPlayback(l.ChannelId);
|
||||||
|
_sub.SendTopics(App.TwitchOAuthAccessToken, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_targets[topic].RemoveAll(x => x.Instance == receiver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
using BetterRaid.Services;
|
|
||||||
|
|
||||||
namespace BetterRaid.ViewModels;
|
|
||||||
|
|
||||||
public class AboutWindowViewModel : ViewModelBase
|
|
||||||
{
|
|
||||||
public AboutWindowViewModel(ITwitchDataService s)
|
|
||||||
{
|
|
||||||
Console.WriteLine(s);
|
|
||||||
Console.WriteLine("[DEBUG] AboutWindowViewModel created");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using BetterRaid.Extensions;
|
using BetterRaid.Extensions;
|
||||||
using BetterRaid.Misc;
|
using BetterRaid.Misc;
|
||||||
@ -16,6 +18,7 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
private string? _filter;
|
private string? _filter;
|
||||||
private ObservableCollection<TwitchChannel> _channels = [];
|
private ObservableCollection<TwitchChannel> _channels = [];
|
||||||
private BetterRaidDatabase? _db;
|
private BetterRaidDatabase? _db;
|
||||||
|
private readonly ITwitchPubSubService _pubSub;
|
||||||
|
|
||||||
public BetterRaidDatabase? Database
|
public BetterRaidDatabase? Database
|
||||||
{
|
{
|
||||||
@ -34,6 +37,8 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
get => _channels;
|
get => _channels;
|
||||||
set => SetProperty(ref _channels, value);
|
set => SetProperty(ref _channels, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ITwitchDataService DataService { get; }
|
||||||
|
|
||||||
public string? Filter
|
public string? Filter
|
||||||
{
|
{
|
||||||
@ -43,10 +48,12 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
|
|
||||||
public bool IsLoggedIn => App.TwitchApi != null;
|
public bool IsLoggedIn => App.TwitchApi != null;
|
||||||
|
|
||||||
public MainWindowViewModel(ITwitchDataService t)
|
public MainWindowViewModel(ITwitchPubSubService pubSub, ITwitchDataService dataService)
|
||||||
{
|
{
|
||||||
Console.WriteLine(t);
|
_pubSub = pubSub;
|
||||||
Console.WriteLine("[DEBUG] MainWindowViewModel created");
|
DataService = dataService;
|
||||||
|
|
||||||
|
Database = BetterRaidDatabase.LoadFromFile(Path.Combine(App.BetterRaidDataPath, "db.json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExitApplication()
|
public void ExitApplication()
|
||||||
@ -58,7 +65,6 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
public void ShowAboutWindow(Window owner)
|
public void ShowAboutWindow(Window owner)
|
||||||
{
|
{
|
||||||
var about = new AboutWindow();
|
var about = new AboutWindow();
|
||||||
about.InjectDataContext<AboutWindowViewModel>();
|
|
||||||
about.ShowDialog(owner);
|
about.ShowDialog(owner);
|
||||||
about.CenterToOwner();
|
about.CenterToOwner();
|
||||||
}
|
}
|
||||||
@ -80,15 +86,26 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var channel in Channels)
|
||||||
|
{
|
||||||
|
_pubSub.UnregisterReceiver(channel);
|
||||||
|
}
|
||||||
|
|
||||||
Channels.Clear();
|
Channels.Clear();
|
||||||
|
|
||||||
var channels = _db.Channels
|
var channels = _db.Channels
|
||||||
.Select(channelName => new TwitchChannel(channelName))
|
.Select(channelName => new TwitchChannel(channelName))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
foreach (var c in channels)
|
foreach (var channel in channels)
|
||||||
{
|
{
|
||||||
Channels.Add(c);
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
channel.InitChannel();
|
||||||
|
_pubSub.RegisterReceiver(channel);
|
||||||
|
});
|
||||||
|
|
||||||
|
Channels.Add(channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
xmlns:vm="clr-namespace:BetterRaid.ViewModels"
|
xmlns:vm="clr-namespace:BetterRaid.ViewModels"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="BetterRaid.Views.AboutWindow"
|
x:Class="BetterRaid.Views.AboutWindow"
|
||||||
x:DataType="vm:AboutWindowViewModel"
|
|
||||||
Title="About"
|
Title="About"
|
||||||
MaxWidth="300"
|
MaxWidth="300"
|
||||||
MinWidth="300"
|
MinWidth="300"
|
||||||
|
@ -2,10 +2,14 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:vm="using:BetterRaid.ViewModels"
|
xmlns:vm="using:BetterRaid.ViewModels"
|
||||||
xmlns:br="using:BetterRaid"
|
xmlns:br="using:BetterRaid"
|
||||||
|
xmlns:con="using:BetterRaid.Converters"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:ai="using:AsyncImageLoader"
|
xmlns:ai="using:AsyncImageLoader"
|
||||||
mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="800"
|
xmlns:misc="clr-namespace:BetterRaid.Misc"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignWidth="600"
|
||||||
|
d:DesignHeight="800"
|
||||||
Width="600"
|
Width="600"
|
||||||
Height="800"
|
Height="800"
|
||||||
x:Class="BetterRaid.Views.MainWindow"
|
x:Class="BetterRaid.Views.MainWindow"
|
||||||
@ -13,10 +17,10 @@
|
|||||||
Icon="/Assets/logo.png"
|
Icon="/Assets/logo.png"
|
||||||
Title="BetterRaid"
|
Title="BetterRaid"
|
||||||
Background="DarkSlateGray">
|
Background="DarkSlateGray">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Window.Resources>
|
||||||
<vm:MainWindowViewModel/>
|
<con:ChannelOnlineColorConverter x:Key="ChannelOnlineColorConverter" />
|
||||||
</Design.DataContext>
|
</Window.Resources>
|
||||||
|
|
||||||
<Grid HorizontalAlignment="Stretch"
|
<Grid HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch">
|
VerticalAlignment="Stretch">
|
||||||
@ -113,8 +117,8 @@
|
|||||||
<ListBox ItemsSource="{Binding Channels}">
|
<ListBox ItemsSource="{Binding Channels}">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Grid ColumnDefinitions="200,*"
|
<Grid ColumnDefinitions="100, *, 120"
|
||||||
RowDefinitions="200">
|
RowDefinitions="100">
|
||||||
|
|
||||||
<ai:AdvancedImage Grid.Column="0"
|
<ai:AdvancedImage Grid.Column="0"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@ -124,25 +128,33 @@
|
|||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Bottom"
|
VerticalAlignment="Bottom"
|
||||||
Height="20"
|
Height="25"
|
||||||
MinWidth="20"
|
MinWidth="25"
|
||||||
CornerRadius="10"
|
CornerRadius="12.5"
|
||||||
Background="#FFFFFF"
|
Background="{Binding IsLive, Converter={StaticResource ChannelOnlineColorConverter}}"
|
||||||
Padding="3"
|
Padding="0"
|
||||||
Margin="0, 0, 10, 10">
|
Margin="0, 0, 5, 5">
|
||||||
<TextBlock Text="{Binding ViewerCount, TargetNullValue='-'}"/>
|
|
||||||
|
<TextBlock Text="{Binding ViewerCount, TargetNullValue='-', Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
FontSize="12"
|
||||||
|
TextAlignment="Center"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Padding="0"
|
||||||
|
Margin="5"
|
||||||
|
Foreground="Black"/>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<Grid Grid.Column="1"
|
<Grid Grid.Column="1"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
ColumnDefinitions="30*, 70*"
|
Margin="10, 0, 0, 0"
|
||||||
RowDefinitions="40, 40, 40, 40, 40">
|
ColumnDefinitions="100, *"
|
||||||
|
RowDefinitions="20, 20, 40, 20">
|
||||||
<TextBlock Grid.Column="0"
|
<TextBlock Grid.Column="0"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.ColumnSpan="2"
|
Grid.ColumnSpan="2"
|
||||||
FontWeight="Bold"
|
FontWeight="Bold"
|
||||||
TextDecorations="Underline"
|
TextDecorations="Underline"
|
||||||
Text="{Binding DisplayName, TargetNullValue='???'}" />
|
Text="{Binding Name, TargetNullValue='???'}" />
|
||||||
|
|
||||||
<TextBlock Grid.Column="0"
|
<TextBlock Grid.Column="0"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
@ -159,27 +171,62 @@
|
|||||||
Text="Last Raided:"
|
Text="Last Raided:"
|
||||||
FontWeight="SemiBold" />
|
FontWeight="SemiBold" />
|
||||||
|
|
||||||
<TextBlock Grid.Column="0"
|
|
||||||
Grid.Row="4"
|
|
||||||
Text=""
|
|
||||||
FontWeight="SemiBold" />
|
|
||||||
|
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Text="{Binding Category, TargetNullValue='-'}" />
|
Text="{Binding Category, TargetNullValue='-'}" />
|
||||||
|
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
|
TextWrapping="Wrap"
|
||||||
Text="{Binding Title, TargetNullValue='-'}" />
|
Text="{Binding Title, TargetNullValue='-'}" />
|
||||||
|
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
Text="{Binding LastRaided, TargetNullValue='Never Raided'}" />
|
Text="{Binding LastRaided, TargetNullValue='Never Raided'}" />
|
||||||
|
|
||||||
<TextBlock Grid.Column="1"
|
|
||||||
Grid.Row="4"
|
|
||||||
Text="" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="2"
|
||||||
|
Grid.Row="0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch">
|
||||||
|
|
||||||
|
<Button Content="Start Raid"
|
||||||
|
Height="50"
|
||||||
|
Margin="0"
|
||||||
|
CornerRadius="0"
|
||||||
|
Background="ForestGreen"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
IsEnabled="{Binding IsLive}"
|
||||||
|
IsVisible="{Binding !$parent[Window].((vm:MainWindowViewModel)DataContext).DataService.IsRaidStarted}"
|
||||||
|
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).DataService.StartRaidCommand}"
|
||||||
|
CommandParameter="{Binding BroadcasterId}" />
|
||||||
|
|
||||||
|
<Button Content="Cancel Raid"
|
||||||
|
Height="50"
|
||||||
|
Margin="0"
|
||||||
|
CornerRadius="0"
|
||||||
|
Background="DarkRed"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
IsVisible="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).DataService.IsRaidStarted}"
|
||||||
|
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).DataService.StopRaidCommand}" />
|
||||||
|
|
||||||
|
<Button Content="View Channel"
|
||||||
|
Height="50"
|
||||||
|
CornerRadius="0"
|
||||||
|
Margin="0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Command="{Binding $parent[Window].((vm:MainWindowViewModel)DataContext).DataService.OpenChannelCommand}"
|
||||||
|
CommandParameter="{Binding Name}" />
|
||||||
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
|
Reference in New Issue
Block a user