Lots of code cleanup, moved Twitch stuff to services, further improvements on UI
This commit is contained in:
parent
8a10573fbf
commit
814cb58742
@ -12,7 +12,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
|
|
||||||
namespace BetterRaid;
|
namespace BetterRaid;
|
||||||
|
|
||||||
public partial class App : Application
|
public class App : Application
|
||||||
{
|
{
|
||||||
private static readonly ServiceCollection Services = [];
|
private static readonly ServiceCollection Services = [];
|
||||||
private static ServiceProvider? _serviceProvider;
|
private static ServiceProvider? _serviceProvider;
|
||||||
|
@ -2,7 +2,7 @@ using System;
|
|||||||
|
|
||||||
namespace BetterRaid.Attributes;
|
namespace BetterRaid.Attributes;
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = true)]
|
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
|
||||||
public class PubSubAttribute : Attribute
|
public class PubSubAttribute : Attribute
|
||||||
{
|
{
|
||||||
public PubSubType Type { get; }
|
public PubSubType Type { get; }
|
||||||
|
@ -7,5 +7,7 @@ public enum PubSubType
|
|||||||
Subscriptions,
|
Subscriptions,
|
||||||
ChannelPoints,
|
ChannelPoints,
|
||||||
Bits,
|
Bits,
|
||||||
Raids
|
Raids,
|
||||||
|
StreamUp,
|
||||||
|
StreamDown
|
||||||
}
|
}
|
@ -20,7 +20,7 @@ public static class Tools
|
|||||||
if (_oauthListener == null)
|
if (_oauthListener == null)
|
||||||
{
|
{
|
||||||
_oauthListener = new HttpListener();
|
_oauthListener = new HttpListener();
|
||||||
_oauthListener.Prefixes.Add(Constants.TwitchOAuthRedirectUrl);
|
_oauthListener.Prefixes.Add(Constants.TwitchOAuthRedirectUrl + "/");
|
||||||
_oauthListener.Start();
|
_oauthListener.Start();
|
||||||
|
|
||||||
Task.Run(() => WaitForCallback(callback, token), token);
|
Task.Run(() => WaitForCallback(callback, token), token);
|
||||||
@ -110,7 +110,7 @@ public static class Tools
|
|||||||
var dataService = App.ServiceProvider?.GetService(typeof(ITwitchDataService));
|
var dataService = App.ServiceProvider?.GetService(typeof(ITwitchDataService));
|
||||||
if (dataService is ITwitchDataService twitchDataService)
|
if (dataService is ITwitchDataService twitchDataService)
|
||||||
{
|
{
|
||||||
twitchDataService.ConnectApi(Constants.TwitchClientId, accessToken!);
|
twitchDataService.ConnectApiAsync(Constants.TwitchClientId, accessToken!);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.StatusCode = 200;
|
res.StatusCode = 200;
|
||||||
|
@ -45,6 +45,8 @@ public class TwitchChannel : INotifyPropertyChanged
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[PubSub(PubSubType.StreamUp, nameof(BroadcasterId))]
|
||||||
|
[PubSub(PubSubType.StreamDown, nameof(BroadcasterId))]
|
||||||
public bool IsLive
|
public bool IsLive
|
||||||
{
|
{
|
||||||
get => _isLive;
|
get => _isLive;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using BetterRaid.Models;
|
using BetterRaid.Models;
|
||||||
using TwitchLib.Api;
|
using TwitchLib.Api;
|
||||||
|
|
||||||
@ -11,11 +12,12 @@ public interface ITwitchDataService
|
|||||||
public TwitchAPI TwitchApi { get; }
|
public TwitchAPI TwitchApi { get; }
|
||||||
public bool IsRaidStarted { get; set; }
|
public bool IsRaidStarted { get; set; }
|
||||||
|
|
||||||
public void ConnectApi(string clientId, string accessToken);
|
public Task ConnectApiAsync(string clientId, string accessToken);
|
||||||
public void SaveAccessToken(string token);
|
public void SaveAccessToken(string token);
|
||||||
public bool TryGetUserChannel(out TwitchChannel? userChannel);
|
public bool TryGetUserChannel(out TwitchChannel? userChannel);
|
||||||
public string GetOAuthUrl();
|
public string GetOAuthUrl();
|
||||||
public void StartRaid(string from, string to);
|
public void StartRaid(string from, string to);
|
||||||
|
public bool CanStartRaidCommand(object? arg);
|
||||||
public void StartRaidCommand(object? arg);
|
public void StartRaidCommand(object? arg);
|
||||||
public void StopRaid();
|
public void StopRaid();
|
||||||
public void StopRaidCommand();
|
public void StopRaidCommand();
|
||||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using BetterRaid.Misc;
|
using BetterRaid.Misc;
|
||||||
using BetterRaid.Models;
|
using BetterRaid.Models;
|
||||||
using TwitchLib.Api;
|
using TwitchLib.Api;
|
||||||
@ -45,7 +46,7 @@ public class TwitchDataService : ITwitchDataService, INotifyPropertyChanged, INo
|
|||||||
if (TryLoadAccessToken(out var token))
|
if (TryLoadAccessToken(out var token))
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[INFO][{nameof(TwitchDataService)}] Found access token.");
|
Console.WriteLine($"[INFO][{nameof(TwitchDataService)}] Found access token.");
|
||||||
ConnectApi(Constants.TwitchClientId, token);
|
Task.Run(() => ConnectApiAsync(Constants.TwitchClientId, token));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -53,7 +54,7 @@ public class TwitchDataService : ITwitchDataService, INotifyPropertyChanged, INo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConnectApi(string clientId, string accessToken)
|
public async Task ConnectApiAsync(string clientId, string accessToken)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[INFO][{nameof(TwitchDataService)}] Connecting to Twitch API ...");
|
Console.WriteLine($"[INFO][{nameof(TwitchDataService)}] Connecting to Twitch API ...");
|
||||||
|
|
||||||
@ -74,6 +75,8 @@ public class TwitchDataService : ITwitchDataService, INotifyPropertyChanged, INo
|
|||||||
Console.WriteLine($"[ERROR][{nameof(TwitchDataService)}] Could not get user channel.");
|
Console.WriteLine($"[ERROR][{nameof(TwitchDataService)}] Could not get user channel.");
|
||||||
Console.WriteLine($"[ERROR][{nameof(TwitchDataService)}] Failed to connect to Twitch API.");
|
Console.WriteLine($"[ERROR][{nameof(TwitchDataService)}] Failed to connect to Twitch API.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryLoadAccessToken(out string token)
|
private bool TryLoadAccessToken(out string token)
|
||||||
@ -129,6 +132,11 @@ public class TwitchDataService : ITwitchDataService, INotifyPropertyChanged, INo
|
|||||||
IsRaidStarted = true;
|
IsRaidStarted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CanStartRaidCommand(object? arg)
|
||||||
|
{
|
||||||
|
return UserChannel?.IsLive == true && IsRaidStarted == false;
|
||||||
|
}
|
||||||
|
|
||||||
public void StartRaidCommand(object? arg)
|
public void StartRaidCommand(object? arg)
|
||||||
{
|
{
|
||||||
if (arg == null || UserChannel?.BroadcasterId == null)
|
if (arg == null || UserChannel?.BroadcasterId == null)
|
||||||
|
@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using BetterRaid.Attributes;
|
using BetterRaid.Attributes;
|
||||||
using BetterRaid.Extensions;
|
using BetterRaid.Extensions;
|
||||||
using BetterRaid.Models;
|
using BetterRaid.Models;
|
||||||
@ -13,45 +14,127 @@ namespace BetterRaid.Services.Implementations;
|
|||||||
public class TwitchPubSubService : ITwitchPubSubService
|
public class TwitchPubSubService : ITwitchPubSubService
|
||||||
{
|
{
|
||||||
private readonly Dictionary<PubSubType, List<PubSubListener>> _targets = new();
|
private readonly Dictionary<PubSubType, List<PubSubListener>> _targets = new();
|
||||||
private readonly TwitchPubSub _sub;
|
|
||||||
private readonly ITwitchDataService _dataService;
|
private readonly ITwitchDataService _dataService;
|
||||||
|
private TwitchPubSub? _sub;
|
||||||
|
|
||||||
public TwitchPubSubService(ITwitchDataService dataService)
|
public TwitchPubSubService(ITwitchDataService dataService)
|
||||||
{
|
{
|
||||||
_dataService = dataService;
|
_dataService = dataService;
|
||||||
|
|
||||||
|
Task.Run(InitializePubSubAsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InitializePubSubAsync()
|
||||||
|
{
|
||||||
|
while (_dataService.UserChannel == null)
|
||||||
|
{
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
_sub = new TwitchPubSub();
|
_sub = new TwitchPubSub();
|
||||||
|
|
||||||
_sub.OnPubSubServiceConnected += OnSubOnOnPubSubServiceConnected;
|
_sub.OnPubSubServiceConnected += OnSubOnOnPubSubServiceConnected;
|
||||||
_sub.OnPubSubServiceError += OnSubOnOnPubSubServiceError;
|
_sub.OnPubSubServiceError += OnSubOnOnPubSubServiceError;
|
||||||
_sub.OnPubSubServiceClosed += OnSubOnOnPubSubServiceClosed;
|
_sub.OnPubSubServiceClosed += OnSubOnOnPubSubServiceClosed;
|
||||||
_sub.OnListenResponse += OnSubOnOnListenResponse;
|
_sub.OnListenResponse += OnSubOnOnListenResponse;
|
||||||
_sub.OnViewCount += OnSubOnOnViewCount;
|
_sub.OnViewCount += OnSubOnOnViewCount;
|
||||||
|
_sub.OnStreamUp += OnStreamUp;
|
||||||
|
_sub.OnStreamDown += OnStreamDown;
|
||||||
|
|
||||||
_sub.Connect();
|
_sub.Connect();
|
||||||
|
|
||||||
if (_dataService.UserChannel != null)
|
if (_dataService.UserChannel != null)
|
||||||
{
|
{
|
||||||
RegisterReceiver(_dataService.UserChannel);
|
RegisterReceiver(_dataService.UserChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
_dataService.PropertyChanging += (_, args) =>
|
_dataService.PropertyChanging += (_, args) =>
|
||||||
{
|
{
|
||||||
if (args.PropertyName != nameof(_dataService.UserChannel))
|
if (args.PropertyName != nameof(_dataService.UserChannel))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_dataService.UserChannel != null)
|
if (_dataService.UserChannel != null)
|
||||||
UnregisterReceiver(_dataService.UserChannel);
|
UnregisterReceiver(_dataService.UserChannel);
|
||||||
};
|
};
|
||||||
|
|
||||||
_dataService.PropertyChanged += (_, args) =>
|
_dataService.PropertyChanged += (_, args) =>
|
||||||
{
|
{
|
||||||
if (args.PropertyName != nameof(_dataService.UserChannel))
|
if (args.PropertyName != nameof(_dataService.UserChannel))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_dataService.UserChannel != null)
|
if (_dataService.UserChannel != null)
|
||||||
RegisterReceiver(_dataService.UserChannel);
|
RegisterReceiver(_dataService.UserChannel);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStreamDown(object? sender, OnStreamDownArgs args)
|
||||||
|
{
|
||||||
|
var listeners = _targets
|
||||||
|
.Where(x => x.Key == PubSubType.StreamDown)
|
||||||
|
.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, false) == false)
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[ERROR][{nameof(TwitchPubSubService)}] Failed to set {listener.Instance.GetType().Name}.{listener.Listener.Name} to true");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[DEBUG][{nameof(TwitchPubSubService)}] Setting {listener.Instance.GetType().Name}.{listener.Listener.Name} to true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[ERROR][{nameof(TwitchPubSubService)}] Exception while setting {listener.Instance?.GetType().Name}.{listener.Listener?.Name}: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStreamUp(object? sender, OnStreamUpArgs args)
|
||||||
|
{
|
||||||
|
var listeners = _targets
|
||||||
|
.Where(x => x.Key == PubSubType.StreamUp)
|
||||||
|
.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, true) == false)
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[ERROR][{nameof(TwitchPubSubService)}] Failed to set {listener.Instance.GetType().Name}.{listener.Listener.Name} to true");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[DEBUG][{nameof(TwitchPubSubService)}] Setting {listener.Instance.GetType().Name}.{listener.Listener.Name} to true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[ERROR][{nameof(TwitchPubSubService)}] Exception while setting {listener.Instance?.GetType().Name}.{listener.Listener?.Name}: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSubOnOnViewCount(object? sender, OnViewCountArgs args)
|
private void OnSubOnOnViewCount(object? sender, OnViewCountArgs args)
|
||||||
@ -61,26 +144,29 @@ public class TwitchPubSubService : ITwitchPubSubService
|
|||||||
.SelectMany(x => x.Value)
|
.SelectMany(x => x.Value)
|
||||||
.Where(x => x.ChannelId == args.ChannelId)
|
.Where(x => x.ChannelId == args.ChannelId)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
foreach (var listener in listeners)
|
foreach (var listener in listeners)
|
||||||
{
|
{
|
||||||
if (listener.Listener == null || listener.Instance == null)
|
if (listener.Listener == null || listener.Instance == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (listener.Listener.SetValue(listener.Instance, args.Viewers) == false)
|
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}");
|
Console.WriteLine(
|
||||||
|
$"[ERROR][{nameof(TwitchPubSubService)}] Failed to set {listener.Instance.GetType().Name}.{listener.Listener.Name} to {args.Viewers}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[DEBUG][{nameof(TwitchPubSubService)}] Setting {listener.Instance.GetType().Name}.{listener.Listener.Name} to {args.Viewers}");
|
Console.WriteLine(
|
||||||
|
$"[DEBUG][{nameof(TwitchPubSubService)}] Setting {listener.Instance.GetType().Name}.{listener.Listener.Name} to {args.Viewers}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[ERROR][{nameof(TwitchPubSubService)}] Exception while setting {listener.Instance?.GetType().Name}.{listener.Listener?.Name}: {e.Message}");
|
Console.WriteLine(
|
||||||
|
$"[ERROR][{nameof(TwitchPubSubService)}] Exception while setting {listener.Instance?.GetType().Name}.{listener.Listener?.Name}: {e.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,8 +195,14 @@ public class TwitchPubSubService : ITwitchPubSubService
|
|||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(receiver, nameof(receiver));
|
ArgumentNullException.ThrowIfNull(receiver, nameof(receiver));
|
||||||
|
|
||||||
|
if (_sub == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[ERROR][{nameof(TwitchPubSubService)}] PubSub is not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Console.WriteLine($"[DEBUG][{nameof(TwitchPubSubService)}] Registering {receiver.GetType().Name}");
|
Console.WriteLine($"[DEBUG][{nameof(TwitchPubSubService)}] Registering {receiver.GetType().Name}");
|
||||||
|
|
||||||
var type = typeof(T);
|
var type = typeof(T);
|
||||||
var publicTargets = type
|
var publicTargets = type
|
||||||
.GetProperties()
|
.GetProperties()
|
||||||
@ -120,93 +212,79 @@ public class TwitchPubSubService : ITwitchPubSubService
|
|||||||
|
|
||||||
foreach (var target in publicTargets)
|
foreach (var target in publicTargets)
|
||||||
{
|
{
|
||||||
if (target.GetCustomAttribute<PubSubAttribute>() is not { } attr)
|
if (target.GetCustomAttributes<PubSubAttribute>() is not { } attrs)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var channelId =
|
foreach (var attr in attrs)
|
||||||
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}");
|
var channelId =
|
||||||
continue;
|
type.GetProperty(attr.ChannelIdField)?.GetValue(receiver)?.ToString() ??
|
||||||
}
|
type.GetField(attr.ChannelIdField)?.GetValue(receiver)?.ToString();
|
||||||
|
|
||||||
switch (attr.Type)
|
if (channelId == null)
|
||||||
{
|
{
|
||||||
case PubSubType.Bits:
|
Console.WriteLine(
|
||||||
break;
|
$"[ERROR][{nameof(TwitchPubSubService)}] {target.Name} is missing ChannelIdField named {attr.ChannelIdField}");
|
||||||
case PubSubType.ChannelPoints:
|
continue;
|
||||||
break;
|
}
|
||||||
case PubSubType.Follows:
|
|
||||||
break;
|
if (string.IsNullOrWhiteSpace(channelId))
|
||||||
case PubSubType.Raids:
|
{
|
||||||
break;
|
Console.WriteLine(
|
||||||
case PubSubType.Subscriptions:
|
$"[ERROR][{nameof(TwitchPubSubService)}] {target.Name} ChannelIdField named {attr.ChannelIdField} is empty");
|
||||||
break;
|
continue;
|
||||||
case PubSubType.VideoPlayback:
|
}
|
||||||
Console.WriteLine($"[DEBUG][{nameof(TwitchPubSubService)}] Registering {target.Name} for {attr.Type}");
|
|
||||||
if (_targets.TryGetValue(PubSubType.VideoPlayback, out var value))
|
Console.WriteLine($"[DEBUG][{nameof(TwitchPubSubService)}] Registering {target.Name} for {attr.Type}");
|
||||||
|
if (_targets.TryGetValue(attr.Type, out var listeners))
|
||||||
|
{
|
||||||
|
listeners.Add(new PubSubListener
|
||||||
{
|
{
|
||||||
value.Add(new PubSubListener
|
ChannelId = channelId,
|
||||||
|
Instance = receiver,
|
||||||
|
Listener = target
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_targets.Add(attr.Type, [
|
||||||
|
new PubSubListener
|
||||||
{
|
{
|
||||||
ChannelId = channelId,
|
ChannelId = channelId,
|
||||||
Instance = receiver,
|
Instance = receiver,
|
||||||
Listener = target
|
Listener = target
|
||||||
});
|
}
|
||||||
}
|
]);
|
||||||
else
|
}
|
||||||
{
|
|
||||||
_targets.Add(PubSubType.VideoPlayback, [
|
_sub.ListenToVideoPlayback(channelId);
|
||||||
new PubSubListener
|
_sub.SendTopics(_dataService.AccessToken, true);
|
||||||
{
|
|
||||||
ChannelId = channelId,
|
|
||||||
Instance = receiver,
|
|
||||||
Listener = target
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
_sub.ListenToVideoPlayback(channelId);
|
|
||||||
_sub.SendTopics(_dataService.AccessToken);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnregisterReceiver<T>(T receiver) where T : class
|
public void UnregisterReceiver<T>(T receiver) where T : class
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(receiver, nameof(receiver));
|
ArgumentNullException.ThrowIfNull(receiver, nameof(receiver));
|
||||||
|
|
||||||
|
if (_sub == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[ERROR][{nameof(TwitchPubSubService)}] PubSub is not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var (topic, listeners) in _targets)
|
foreach (var (topic, listeners) in _targets)
|
||||||
{
|
{
|
||||||
var listener = listeners.Where(x => x.Instance == receiver).ToList();
|
var listener = listeners.Where(x => x.Instance == receiver).ToList();
|
||||||
|
|
||||||
foreach (var l in listener)
|
foreach (var l in listener)
|
||||||
{
|
{
|
||||||
switch (topic)
|
_sub.ListenToVideoPlayback(l.ChannelId);
|
||||||
{
|
_sub.SendTopics(_dataService.AccessToken, true);
|
||||||
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(_dataService.AccessToken, true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_targets[topic].RemoveAll(x => x.Instance == receiver);
|
_targets[topic].RemoveAll(x => x.Instance == receiver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -12,17 +13,17 @@ using BetterRaid.Views;
|
|||||||
|
|
||||||
namespace BetterRaid.ViewModels;
|
namespace BetterRaid.ViewModels;
|
||||||
|
|
||||||
public partial class MainWindowViewModel : ViewModelBase
|
public class MainWindowViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
private string? _filter;
|
private string? _filter;
|
||||||
private ObservableCollection<TwitchChannel> _channels = [];
|
private ObservableCollection<TwitchChannel> _channels = [];
|
||||||
private BetterRaidDatabase? _db;
|
private readonly BetterRaidDatabase? _db;
|
||||||
private readonly ITwitchPubSubService _pubSub;
|
private readonly ITwitchPubSubService _pubSub;
|
||||||
|
|
||||||
public BetterRaidDatabase? Database
|
public BetterRaidDatabase? Database
|
||||||
{
|
{
|
||||||
get => _db;
|
get => _db;
|
||||||
set
|
private init
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _db, value) && _db != null)
|
if (SetProperty(ref _db, value) && _db != null)
|
||||||
{
|
{
|
||||||
@ -37,6 +38,8 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
set => SetProperty(ref _channels, value);
|
set => SetProperty(ref _channels, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<TwitchChannel> FilteredChannels => GetFilteredChannels();
|
||||||
|
|
||||||
public ITwitchDataService DataService { get; }
|
public ITwitchDataService DataService { get; }
|
||||||
|
|
||||||
public string? Filter
|
public string? Filter
|
||||||
@ -51,10 +54,12 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
_pubSub = pubSub;
|
_pubSub = pubSub;
|
||||||
DataService = dataService;
|
DataService = dataService;
|
||||||
|
DataService.PropertyChanged += OnDataServicePropertyChanged;
|
||||||
|
|
||||||
Database = BetterRaidDatabase.LoadFromFile(Constants.DatabaseFilePath);
|
Database = BetterRaidDatabase.LoadFromFile(Constants.DatabaseFilePath);
|
||||||
|
Database.PropertyChanged += OnDatabasePropertyChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExitApplication()
|
public void ExitApplication()
|
||||||
{
|
{
|
||||||
//TODO polish later
|
//TODO polish later
|
||||||
@ -107,4 +112,40 @@ public partial class MainWindowViewModel : ViewModelBase
|
|||||||
Channels.Add(channel);
|
Channels.Add(channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ObservableCollection<TwitchChannel> GetFilteredChannels()
|
||||||
|
{
|
||||||
|
var filteredChannels = Channels
|
||||||
|
.Where(channel => Database?.OnlyOnline == false || channel.IsLive)
|
||||||
|
.Where(channel => string.IsNullOrWhiteSpace(Filter) || channel.Name?.Contains(Filter, StringComparison.OrdinalIgnoreCase) == true)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return new ObservableCollection<TwitchChannel>(filteredChannels);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDataServicePropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PropertyName != nameof(DataService.UserChannel))
|
||||||
|
return;
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(IsLoggedIn));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnPropertyChanged(e);
|
||||||
|
|
||||||
|
if (e.PropertyName == nameof(Filter))
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(FilteredChannels));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDatabasePropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PropertyName != nameof(BetterRaidDatabase.OnlyOnline))
|
||||||
|
return;
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(FilteredChannels));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,83 +22,85 @@
|
|||||||
</Window.Resources>
|
</Window.Resources>
|
||||||
|
|
||||||
<Grid HorizontalAlignment="Stretch"
|
<Grid HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch">
|
VerticalAlignment="Stretch"
|
||||||
|
ColumnDefinitions="Auto,*"
|
||||||
|
RowDefinitions="50,*">
|
||||||
|
|
||||||
<Grid.ColumnDefinitions>
|
<StackPanel Grid.Column="0"
|
||||||
<ColumnDefinition Width="*" />
|
Grid.Row="0"
|
||||||
<ColumnDefinition Width="Auto" />
|
Orientation="Horizontal">
|
||||||
</Grid.ColumnDefinitions>
|
<ai:AdvancedImage CornerRadius="20"
|
||||||
|
Width="40"
|
||||||
|
Height="40"
|
||||||
|
Margin="5"
|
||||||
|
Source="{Binding DataService.UserChannel.ThumbnailUrl,
|
||||||
|
FallbackValue={x:Static misc:Constants.ChannelPlaceholderImageUrl},
|
||||||
|
TargetNullValue={x:Static misc:Constants.ChannelPlaceholderImageUrl}}" />
|
||||||
|
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
Margin="5, 0, 0, 0"
|
||||||
|
FontWeight="Bold">
|
||||||
|
<TextBlock.Text>
|
||||||
|
<MultiBinding StringFormat="{}{0} ({1})">
|
||||||
|
<Binding Path="DataService.UserChannel.DisplayName"
|
||||||
|
FallbackValue="-" />
|
||||||
|
<Binding Path="DataService.UserChannel.ViewerCount"
|
||||||
|
FallbackValue="Offline"
|
||||||
|
TargetNullValue="Offline" />
|
||||||
|
</MultiBinding>
|
||||||
|
</TextBlock.Text>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<Menu Grid.Column="0"
|
|
||||||
Grid.ColumnSpan="2"
|
|
||||||
Grid.Row="0">
|
|
||||||
|
|
||||||
<MenuItem Header="File">
|
|
||||||
<MenuItem Header="About"
|
|
||||||
CommandParameter="{Binding $parent[Window]}"
|
|
||||||
Command="{Binding ShowAboutWindow}" />
|
|
||||||
<Separator />
|
|
||||||
<MenuItem Header="Exit"
|
|
||||||
Command="{Binding ExitApplication}" />
|
|
||||||
</MenuItem>
|
|
||||||
</Menu>
|
|
||||||
|
|
||||||
<StackPanel Grid.Column="1"
|
<StackPanel Grid.Column="1"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
Spacing="5">
|
Spacing="5">
|
||||||
|
|
||||||
<Button Command="{Binding LoginWithTwitch}"
|
|
||||||
IsVisible="{Binding !IsLoggedIn, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
|
|
||||||
|
|
||||||
<Button.Styles>
|
|
||||||
<Style Selector="Button">
|
|
||||||
<Setter Property="Background" Value="#6441a5" />
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
|
||||||
<Setter Property="Background" Value="#b9a3e3" />
|
|
||||||
</Style>
|
|
||||||
</Button.Styles>
|
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<TextBlock Text="Login"
|
|
||||||
Foreground="#f1f1f1"
|
|
||||||
FontSize="14" />
|
|
||||||
<Image Source="avares://BetterRaid/Assets/glitch_flat_white.png"
|
|
||||||
Width="16"
|
|
||||||
Height="16"
|
|
||||||
Margin="5, 0, 0, 0" />
|
|
||||||
</StackPanel>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<CheckBox Content="Only Online"
|
<CheckBox Content="Only Online"
|
||||||
IsChecked="{Binding Database.OnlyOnline, Mode=TwoWay}" />
|
IsChecked="{Binding Database.OnlyOnline, Mode=TwoWay, FallbackValue=False}" />
|
||||||
|
|
||||||
<TextBox Grid.Column="1"
|
<TextBox Width="200"
|
||||||
Grid.Row="0"
|
Margin="5, 10, 5, 10"
|
||||||
Width="200"
|
|
||||||
Margin="2"
|
|
||||||
Watermark="Filter Channels"
|
Watermark="Filter Channels"
|
||||||
Text="{Binding Filter, Mode=TwoWay}"
|
Text="{Binding Filter, Mode=TwoWay}"
|
||||||
HorizontalAlignment="Right" />
|
HorizontalAlignment="Right" />
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<Button Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Grid.Row="1"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Width="200"
|
||||||
|
Height="50"
|
||||||
|
Command="{Binding LoginWithTwitch}"
|
||||||
|
IsVisible="{Binding !IsLoggedIn, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
|
||||||
|
|
||||||
|
<Button.Styles>
|
||||||
|
<Style Selector="Button">
|
||||||
|
<Setter Property="Background" Value="#6441a5" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="#6C4CA5" />
|
||||||
|
</Style>
|
||||||
|
</Button.Styles>
|
||||||
|
|
||||||
<TextBlock Text="Please login first!"
|
<StackPanel Orientation="Horizontal">
|
||||||
Grid.Column="0"
|
<TextBlock Text="Login with Twitch"
|
||||||
Grid.ColumnSpan="2"
|
Foreground="#f1f1f1"
|
||||||
Grid.Row="1"
|
FontSize="18"
|
||||||
IsVisible="{Binding !IsLoggedIn, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
VerticalAlignment="Center" />
|
||||||
HorizontalAlignment="Center"
|
<Image Source="avares://BetterRaid/Assets/glitch_flat_white.png"
|
||||||
VerticalAlignment="Center"
|
Width="24"
|
||||||
TextAlignment="Center"
|
Height="24"
|
||||||
FontSize="24"
|
VerticalAlignment="Center"
|
||||||
Foreground="#f1f1f1" />
|
Margin="5, 0, 0, 0" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</Button>
|
||||||
|
|
||||||
<ScrollViewer Grid.Column="0"
|
<ScrollViewer Grid.Column="0"
|
||||||
Grid.ColumnSpan="2"
|
Grid.ColumnSpan="2"
|
||||||
@ -113,12 +115,21 @@
|
|||||||
IsScrollInertiaEnabled="True" />
|
IsScrollInertiaEnabled="True" />
|
||||||
</ScrollViewer.GestureRecognizers>
|
</ScrollViewer.GestureRecognizers>
|
||||||
|
|
||||||
<ListBox ItemsSource="{Binding Channels}">
|
<ListBox ItemsSource="{Binding FilteredChannels, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
SelectionMode="Single"
|
||||||
|
SelectionChanged="SelectingItemsControl_OnSelectionChanged">
|
||||||
|
|
||||||
|
<ListBox.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<VirtualizingStackPanel Orientation="Vertical" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ListBox.ItemsPanel>
|
||||||
|
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Grid ColumnDefinitions="100, *, 120"
|
<Grid ColumnDefinitions="100, *, 120"
|
||||||
RowDefinitions="100">
|
RowDefinitions="100">
|
||||||
|
|
||||||
<ai:AdvancedImage Grid.Column="0"
|
<ai:AdvancedImage Grid.Column="0"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Source="{Binding ThumbnailUrl, TargetNullValue={x:Static misc:Constants.ChannelPlaceholderImageUrl}}" />
|
Source="{Binding ThumbnailUrl, TargetNullValue={x:Static misc:Constants.ChannelPlaceholderImageUrl}}" />
|
||||||
|
@ -8,4 +8,12 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SelectingItemsControl_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is ListBox listBox)
|
||||||
|
{
|
||||||
|
listBox.SelectedItems?.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user