From a8a481d4f914d72a98f60b7e64b67b857168e907 Mon Sep 17 00:00:00 2001 From: Enrico Ludwig Date: Tue, 3 Sep 2024 20:30:29 +0200 Subject: [PATCH] Further work on ui-rework, also implemented services, re-implemented raiding functionality and PubSub --- App.axaml.cs | 28 ++- Attributes/PubSubAttribute.cs | 17 ++ Attributes/PubSubType.cs | 11 + BetterRaid.csproj | 1 - BetterRaid.generated.sln | 10 +- Converters/ChannelOnlineColorConverter.cs | 29 +++ Extensions/MemberInfoExtensions.cs | 55 +++++ Misc/Tools.cs | 73 +++---- Models/PubSubListener.cs | 10 + Models/TwitchChannel.cs | 41 +++- README.md | 1 - Services/ITwitchDataService.cs | 6 + Services/ITwitchPubSubService.cs | 7 + Services/Implementations/TwitchDataService.cs | 76 ++++++- .../Implementations/TwitchPubSubService.cs | 188 ++++++++++++++++++ ViewModels/AboutWindowViewModel.cs | 13 -- ViewModels/MainWindowViewModel.cs | 29 ++- Views/AboutWindow.axaml | 1 - Views/MainWindow.axaml | 99 ++++++--- 19 files changed, 587 insertions(+), 108 deletions(-) create mode 100644 Attributes/PubSubAttribute.cs create mode 100644 Attributes/PubSubType.cs create mode 100644 Converters/ChannelOnlineColorConverter.cs create mode 100644 Extensions/MemberInfoExtensions.cs create mode 100644 Models/PubSubListener.cs create mode 100644 Services/ITwitchPubSubService.cs create mode 100644 Services/Implementations/TwitchPubSubService.cs delete mode 100644 ViewModels/AboutWindowViewModel.cs diff --git a/App.axaml.cs b/App.axaml.cs index 12f8106..e66e68b 100644 --- a/App.axaml.cs +++ b/App.axaml.cs @@ -5,11 +5,14 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Data.Core.Plugins; using Avalonia.Markup.Xaml; +using BetterRaid.Extensions; using BetterRaid.Services; +using BetterRaid.Services.Implementations; using BetterRaid.ViewModels; using BetterRaid.Views; using Microsoft.Extensions.DependencyInjection; using TwitchLib.Api; +using TwitchLib.PubSub; namespace BetterRaid; @@ -43,6 +46,7 @@ public partial class App : Application public static TwitchAPI? TwitchApi => _twitchApi; public static bool HasUserZnSubbed => _hasUserZnSubbed; + public static string BetterRaidDataPath => _betterRaidDataPath; public IServiceProvider? Provider => _provider; public static string? TwitchBroadcasterId => _twitchBroadcasterId; @@ -59,8 +63,8 @@ public partial class App : Application public override void Initialize() { - InitializeServices(); LoadTwitchToken(); + InitializeServices(); AvaloniaXamlLoader.Load(_provider, this); } @@ -98,8 +102,8 @@ public partial class App : Application private void InitializeServices() { _services.AddSingleton(); + _services.AddSingleton(); _services.AddTransient(); - _services.AddTransient(); _provider = _services.BuildServiceProvider(); } @@ -154,7 +158,6 @@ public partial class App : Application } _twitchBroadcasterId = channel.Id; - Console.WriteLine(_twitchBroadcasterId); Console.WriteLine("[INFO] Connected to Twitch API as '{0}'!", user.DisplayName); @@ -188,25 +191,18 @@ public partial class App : Application { BindingPlugins.DataValidators.RemoveAt(0); - var vm = _provider?.GetRequiredService(); - switch (ApplicationLifetime) { case IClassicDesktopStyleApplicationLifetime desktop: - // Line below is needed to remove Avalonia data validation. - // Without this line you will get duplicate validations from both Avalonia and CT - - desktop.MainWindow = new MainWindow - { - DataContext = vm - }; + desktop.MainWindow = new MainWindow(); + desktop.MainWindow.InjectDataContext(); + break; case ISingleViewApplicationLifetime singleViewPlatform: - singleViewPlatform.MainView = new MainWindow - { - DataContext = vm - }; + singleViewPlatform.MainView = new MainWindow(); + singleViewPlatform.MainView.InjectDataContext(); + break; } diff --git a/Attributes/PubSubAttribute.cs b/Attributes/PubSubAttribute.cs new file mode 100644 index 0000000..20e0849 --- /dev/null +++ b/Attributes/PubSubAttribute.cs @@ -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; + } + +} \ No newline at end of file diff --git a/Attributes/PubSubType.cs b/Attributes/PubSubType.cs new file mode 100644 index 0000000..b9a0e03 --- /dev/null +++ b/Attributes/PubSubType.cs @@ -0,0 +1,11 @@ +namespace BetterRaid.Attributes; + +public enum PubSubType +{ + VideoPlayback, + Follows, + Subscriptions, + ChannelPoints, + Bits, + Raids +} \ No newline at end of file diff --git a/BetterRaid.csproj b/BetterRaid.csproj index 6fdc464..edd60cd 100644 --- a/BetterRaid.csproj +++ b/BetterRaid.csproj @@ -14,7 +14,6 @@ - diff --git a/BetterRaid.generated.sln b/BetterRaid.generated.sln index 74717f9..407d645 100644 --- a/BetterRaid.generated.sln +++ b/BetterRaid.generated.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.002.0 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,10 +11,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {49E459C8-9DCF-4D6D-95FC-75303243F248}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {49E459C8-9DCF-4D6D-95FC-75303243F248}.Debug|Any CPU.Build.0 = Debug|Any CPU - {49E459C8-9DCF-4D6D-95FC-75303243F248}.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}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C23C5237-3D18-424A-ACF2-62215BE5D557}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C23C5237-3D18-424A-ACF2-62215BE5D557}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C23C5237-3D18-424A-ACF2-62215BE5D557}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Converters/ChannelOnlineColorConverter.cs b/Converters/ChannelOnlineColorConverter.cs new file mode 100644 index 0000000..d60a90b --- /dev/null +++ b/Converters/ChannelOnlineColorConverter.cs @@ -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; + } +} \ No newline at end of file diff --git a/Extensions/MemberInfoExtensions.cs b/Extensions/MemberInfoExtensions.cs new file mode 100644 index 0000000..d8f647f --- /dev/null +++ b/Extensions/MemberInfoExtensions.cs @@ -0,0 +1,55 @@ +using System.Reflection; + +namespace BetterRaid.Extensions; + +public static class MemberInfoExtensions +{ + public static bool SetValue(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; + } +} \ No newline at end of file diff --git a/Misc/Tools.cs b/Misc/Tools.cs index b7cf484..de2a89e 100644 --- a/Misc/Tools.cs +++ b/Misc/Tools.cs @@ -15,44 +15,18 @@ public static class Tools private static Task? _oauthWaiterTask; // 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) - return; - - var _token = token ?? CancellationToken.None; - - _oauthListener = new HttpListener(); - _oauthListener.Prefixes.Add("http://localhost:9900/"); - _oauthListener.Start(); - - _oauthWaiterTask = WaitForCallback(callback, _token); - - try + if (_oauthListener == null) { - 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; - } + _oauthListener = new HttpListener(); + _oauthListener.Prefixes.Add("http://localhost:9900/"); + _oauthListener.Start(); + + _oauthWaiterTask = WaitForCallback(callback, token); } + + OpenUrl(url); } 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 = @" diff --git a/Models/PubSubListener.cs b/Models/PubSubListener.cs new file mode 100644 index 0000000..242537f --- /dev/null +++ b/Models/PubSubListener.cs @@ -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; } +} \ No newline at end of file diff --git a/Models/TwitchChannel.cs b/Models/TwitchChannel.cs index 5a04dcf..91c2f42 100644 --- a/Models/TwitchChannel.cs +++ b/Models/TwitchChannel.cs @@ -1,11 +1,15 @@ using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Runtime.CompilerServices; +using BetterRaid.Attributes; namespace BetterRaid.Models; public class TwitchChannel : INotifyPropertyChanged { + private string? _broadcasterId; private string? _viewerCount; private bool _isLive; private string? _name; @@ -17,9 +21,17 @@ public class TwitchChannel : INotifyPropertyChanged public string? BroadcasterId { - get; - set; + get => _broadcasterId; + set + { + if (value == _broadcasterId) + return; + + _broadcasterId = value; + OnPropertyChanged(); + } } + public string? Name { get => _name; @@ -32,6 +44,7 @@ public class TwitchChannel : INotifyPropertyChanged OnPropertyChanged(); } } + public bool IsLive { get => _isLive; @@ -44,6 +57,8 @@ public class TwitchChannel : INotifyPropertyChanged OnPropertyChanged(); } } + + [PubSub(PubSubType.VideoPlayback, nameof(BroadcasterId))] public string? ViewerCount { get => _viewerCount; @@ -127,6 +142,28 @@ public class TwitchChannel : INotifyPropertyChanged 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; private void OnPropertyChanged([CallerMemberName] string? propertyName = null) diff --git a/README.md b/README.md index 87d3f10..f854c1e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1 @@ # BetterRaid - diff --git a/Services/ITwitchDataService.cs b/Services/ITwitchDataService.cs index a3ec8db..e41a728 100644 --- a/Services/ITwitchDataService.cs +++ b/Services/ITwitchDataService.cs @@ -2,5 +2,11 @@ namespace BetterRaid.Services; 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); } \ No newline at end of file diff --git a/Services/ITwitchPubSubService.cs b/Services/ITwitchPubSubService.cs new file mode 100644 index 0000000..03d8338 --- /dev/null +++ b/Services/ITwitchPubSubService.cs @@ -0,0 +1,7 @@ +namespace BetterRaid.Services; + +public interface ITwitchPubSubService +{ + void RegisterReceiver(T receiver) where T : class; + void UnregisterReceiver(T receiver) where T : class; +} \ No newline at end of file diff --git a/Services/Implementations/TwitchDataService.cs b/Services/Implementations/TwitchDataService.cs index d5f0f81..e329db4 100644 --- a/Services/Implementations/TwitchDataService.cs +++ b/Services/Implementations/TwitchDataService.cs @@ -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(ref T field, T value, [CallerMemberName] string? propertyName = null) + { + if (EqualityComparer.Default.Equals(field, value)) return false; + field = value; + OnPropertyChanged(propertyName); + return true; + } } \ No newline at end of file diff --git a/Services/Implementations/TwitchPubSubService.cs b/Services/Implementations/TwitchPubSubService.cs new file mode 100644 index 0000000..21e02b6 --- /dev/null +++ b/Services/Implementations/TwitchPubSubService.cs @@ -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> _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 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() 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 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); + } + } +} \ No newline at end of file diff --git a/ViewModels/AboutWindowViewModel.cs b/ViewModels/AboutWindowViewModel.cs deleted file mode 100644 index ef4cda8..0000000 --- a/ViewModels/AboutWindowViewModel.cs +++ /dev/null @@ -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"); - } -} \ No newline at end of file diff --git a/ViewModels/MainWindowViewModel.cs b/ViewModels/MainWindowViewModel.cs index e82e0aa..ab6f4a2 100644 --- a/ViewModels/MainWindowViewModel.cs +++ b/ViewModels/MainWindowViewModel.cs @@ -1,7 +1,9 @@ using System; using System.Collections.ObjectModel; +using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Avalonia.Controls; using BetterRaid.Extensions; using BetterRaid.Misc; @@ -16,6 +18,7 @@ public partial class MainWindowViewModel : ViewModelBase private string? _filter; private ObservableCollection _channels = []; private BetterRaidDatabase? _db; + private readonly ITwitchPubSubService _pubSub; public BetterRaidDatabase? Database { @@ -34,6 +37,8 @@ public partial class MainWindowViewModel : ViewModelBase get => _channels; set => SetProperty(ref _channels, value); } + + public ITwitchDataService DataService { get; } public string? Filter { @@ -43,10 +48,12 @@ public partial class MainWindowViewModel : ViewModelBase public bool IsLoggedIn => App.TwitchApi != null; - public MainWindowViewModel(ITwitchDataService t) + public MainWindowViewModel(ITwitchPubSubService pubSub, ITwitchDataService dataService) { - Console.WriteLine(t); - Console.WriteLine("[DEBUG] MainWindowViewModel created"); + _pubSub = pubSub; + DataService = dataService; + + Database = BetterRaidDatabase.LoadFromFile(Path.Combine(App.BetterRaidDataPath, "db.json")); } public void ExitApplication() @@ -58,7 +65,6 @@ public partial class MainWindowViewModel : ViewModelBase public void ShowAboutWindow(Window owner) { var about = new AboutWindow(); - about.InjectDataContext(); about.ShowDialog(owner); about.CenterToOwner(); } @@ -80,15 +86,26 @@ public partial class MainWindowViewModel : ViewModelBase return; } + foreach (var channel in Channels) + { + _pubSub.UnregisterReceiver(channel); + } + Channels.Clear(); var channels = _db.Channels .Select(channelName => new TwitchChannel(channelName)) .ToList(); - foreach (var c in channels) + foreach (var channel in channels) { - Channels.Add(c); + Task.Run(() => + { + channel.InitChannel(); + _pubSub.RegisterReceiver(channel); + }); + + Channels.Add(channel); } } } diff --git a/Views/AboutWindow.axaml b/Views/AboutWindow.axaml index cf4345c..62e1740 100644 --- a/Views/AboutWindow.axaml +++ b/Views/AboutWindow.axaml @@ -5,7 +5,6 @@ xmlns:vm="clr-namespace:BetterRaid.ViewModels" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="BetterRaid.Views.AboutWindow" - x:DataType="vm:AboutWindowViewModel" Title="About" MaxWidth="300" MinWidth="300" diff --git a/Views/MainWindow.axaml b/Views/MainWindow.axaml index d36ccdc..f0dbc2a 100644 --- a/Views/MainWindow.axaml +++ b/Views/MainWindow.axaml @@ -2,10 +2,14 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="using:BetterRaid.ViewModels" xmlns:br="using:BetterRaid" + xmlns:con="using:BetterRaid.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 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" Height="800" x:Class="BetterRaid.Views.MainWindow" @@ -13,10 +17,10 @@ Icon="/Assets/logo.png" Title="BetterRaid" Background="DarkSlateGray"> - - - - + + + + @@ -113,8 +117,8 @@ - + - + Height="25" + MinWidth="25" + CornerRadius="12.5" + Background="{Binding IsLive, Converter={StaticResource ChannelOnlineColorConverter}}" + Padding="0" + Margin="0, 0, 5, 5"> + + + Margin="10, 0, 0, 0" + ColumnDefinitions="100, *" + RowDefinitions="20, 20, 40, 20"> + Text="{Binding Name, TargetNullValue='???'}" /> - - - - + + + +