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;
|
||||
|
||||
public partial class App : Application
|
||||
public class App : Application
|
||||
{
|
||||
private static readonly ServiceCollection Services = [];
|
||||
private static ServiceProvider? _serviceProvider;
|
||||
|
@ -2,7 +2,7 @@ using System;
|
||||
|
||||
namespace BetterRaid.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = true)]
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
|
||||
public class PubSubAttribute : Attribute
|
||||
{
|
||||
public PubSubType Type { get; }
|
||||
|
@ -7,5 +7,7 @@ public enum PubSubType
|
||||
Subscriptions,
|
||||
ChannelPoints,
|
||||
Bits,
|
||||
Raids
|
||||
Raids,
|
||||
StreamUp,
|
||||
StreamDown
|
||||
}
|
@ -20,7 +20,7 @@ public static class Tools
|
||||
if (_oauthListener == null)
|
||||
{
|
||||
_oauthListener = new HttpListener();
|
||||
_oauthListener.Prefixes.Add(Constants.TwitchOAuthRedirectUrl);
|
||||
_oauthListener.Prefixes.Add(Constants.TwitchOAuthRedirectUrl + "/");
|
||||
_oauthListener.Start();
|
||||
|
||||
Task.Run(() => WaitForCallback(callback, token), token);
|
||||
@ -110,7 +110,7 @@ public static class Tools
|
||||
var dataService = App.ServiceProvider?.GetService(typeof(ITwitchDataService));
|
||||
if (dataService is ITwitchDataService twitchDataService)
|
||||
{
|
||||
twitchDataService.ConnectApi(Constants.TwitchClientId, accessToken!);
|
||||
twitchDataService.ConnectApiAsync(Constants.TwitchClientId, accessToken!);
|
||||
}
|
||||
|
||||
res.StatusCode = 200;
|
||||
|
@ -45,6 +45,8 @@ public class TwitchChannel : INotifyPropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
[PubSub(PubSubType.StreamUp, nameof(BroadcasterId))]
|
||||
[PubSub(PubSubType.StreamDown, nameof(BroadcasterId))]
|
||||
public bool IsLive
|
||||
{
|
||||
get => _isLive;
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using BetterRaid.Models;
|
||||
using TwitchLib.Api;
|
||||
|
||||
@ -11,11 +12,12 @@ public interface ITwitchDataService
|
||||
public TwitchAPI TwitchApi { get; }
|
||||
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 bool TryGetUserChannel(out TwitchChannel? userChannel);
|
||||
public string GetOAuthUrl();
|
||||
public void StartRaid(string from, string to);
|
||||
public bool CanStartRaidCommand(object? arg);
|
||||
public void StartRaidCommand(object? arg);
|
||||
public void StopRaid();
|
||||
public void StopRaidCommand();
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using BetterRaid.Misc;
|
||||
using BetterRaid.Models;
|
||||
using TwitchLib.Api;
|
||||
@ -45,7 +46,7 @@ public class TwitchDataService : ITwitchDataService, INotifyPropertyChanged, INo
|
||||
if (TryLoadAccessToken(out var token))
|
||||
{
|
||||
Console.WriteLine($"[INFO][{nameof(TwitchDataService)}] Found access token.");
|
||||
ConnectApi(Constants.TwitchClientId, token);
|
||||
Task.Run(() => ConnectApiAsync(Constants.TwitchClientId, token));
|
||||
}
|
||||
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 ...");
|
||||
|
||||
@ -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)}] Failed to connect to Twitch API.");
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private bool TryLoadAccessToken(out string token)
|
||||
@ -129,6 +132,11 @@ public class TwitchDataService : ITwitchDataService, INotifyPropertyChanged, INo
|
||||
IsRaidStarted = true;
|
||||
}
|
||||
|
||||
public bool CanStartRaidCommand(object? arg)
|
||||
{
|
||||
return UserChannel?.IsLive == true && IsRaidStarted == false;
|
||||
}
|
||||
|
||||
public void StartRaidCommand(object? arg)
|
||||
{
|
||||
if (arg == null || UserChannel?.BroadcasterId == null)
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using BetterRaid.Attributes;
|
||||
using BetterRaid.Extensions;
|
||||
using BetterRaid.Models;
|
||||
@ -13,45 +14,127 @@ namespace BetterRaid.Services.Implementations;
|
||||
public class TwitchPubSubService : ITwitchPubSubService
|
||||
{
|
||||
private readonly Dictionary<PubSubType, List<PubSubListener>> _targets = new();
|
||||
private readonly TwitchPubSub _sub;
|
||||
private readonly ITwitchDataService _dataService;
|
||||
private TwitchPubSub? _sub;
|
||||
|
||||
public TwitchPubSubService(ITwitchDataService dataService)
|
||||
{
|
||||
_dataService = dataService;
|
||||
|
||||
Task.Run(InitializePubSubAsync);
|
||||
}
|
||||
|
||||
private async Task InitializePubSubAsync()
|
||||
{
|
||||
while (_dataService.UserChannel == null)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
_sub = new TwitchPubSub();
|
||||
|
||||
|
||||
_sub.OnPubSubServiceConnected += OnSubOnOnPubSubServiceConnected;
|
||||
_sub.OnPubSubServiceError += OnSubOnOnPubSubServiceError;
|
||||
_sub.OnPubSubServiceClosed += OnSubOnOnPubSubServiceClosed;
|
||||
_sub.OnListenResponse += OnSubOnOnListenResponse;
|
||||
_sub.OnViewCount += OnSubOnOnViewCount;
|
||||
|
||||
_sub.OnStreamUp += OnStreamUp;
|
||||
_sub.OnStreamDown += OnStreamDown;
|
||||
|
||||
_sub.Connect();
|
||||
|
||||
|
||||
if (_dataService.UserChannel != null)
|
||||
{
|
||||
RegisterReceiver(_dataService.UserChannel);
|
||||
}
|
||||
|
||||
|
||||
_dataService.PropertyChanging += (_, args) =>
|
||||
{
|
||||
if (args.PropertyName != nameof(_dataService.UserChannel))
|
||||
return;
|
||||
|
||||
|
||||
if (_dataService.UserChannel != null)
|
||||
UnregisterReceiver(_dataService.UserChannel);
|
||||
};
|
||||
|
||||
|
||||
_dataService.PropertyChanged += (_, args) =>
|
||||
{
|
||||
if (args.PropertyName != nameof(_dataService.UserChannel))
|
||||
return;
|
||||
|
||||
|
||||
if (_dataService.UserChannel != null)
|
||||
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)
|
||||
@ -61,26 +144,29 @@ public class TwitchPubSubService : ITwitchPubSubService
|
||||
.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}");
|
||||
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}");
|
||||
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}");
|
||||
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));
|
||||
|
||||
if (_sub == null)
|
||||
{
|
||||
Console.WriteLine($"[ERROR][{nameof(TwitchPubSubService)}] PubSub is not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"[DEBUG][{nameof(TwitchPubSubService)}] Registering {receiver.GetType().Name}");
|
||||
|
||||
|
||||
var type = typeof(T);
|
||||
var publicTargets = type
|
||||
.GetProperties()
|
||||
@ -120,93 +212,79 @@ public class TwitchPubSubService : ITwitchPubSubService
|
||||
|
||||
foreach (var target in publicTargets)
|
||||
{
|
||||
if (target.GetCustomAttribute<PubSubAttribute>() is not { } attr)
|
||||
if (target.GetCustomAttributes<PubSubAttribute>() is not { } attrs)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var channelId =
|
||||
type.GetProperty(attr.ChannelIdField)?.GetValue(receiver)?.ToString() ??
|
||||
type.GetField(attr.ChannelIdField)?.GetValue(receiver)?.ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(channelId))
|
||||
|
||||
foreach (var attr in attrs)
|
||||
{
|
||||
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))
|
||||
var channelId =
|
||||
type.GetProperty(attr.ChannelIdField)?.GetValue(receiver)?.ToString() ??
|
||||
type.GetField(attr.ChannelIdField)?.GetValue(receiver)?.ToString();
|
||||
|
||||
if (channelId == null)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[ERROR][{nameof(TwitchPubSubService)}] {target.Name} is missing ChannelIdField named {attr.ChannelIdField}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(channelId))
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[ERROR][{nameof(TwitchPubSubService)}] {target.Name} ChannelIdField named {attr.ChannelIdField} is empty");
|
||||
continue;
|
||||
}
|
||||
|
||||
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,
|
||||
Instance = receiver,
|
||||
Listener = target
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_targets.Add(PubSubType.VideoPlayback, [
|
||||
new PubSubListener
|
||||
{
|
||||
ChannelId = channelId,
|
||||
Instance = receiver,
|
||||
Listener = target
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
_sub.ListenToVideoPlayback(channelId);
|
||||
_sub.SendTopics(_dataService.AccessToken);
|
||||
|
||||
break;
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
_sub.ListenToVideoPlayback(channelId);
|
||||
_sub.SendTopics(_dataService.AccessToken, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void UnregisterReceiver<T>(T receiver) where T : class
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(receiver, nameof(receiver));
|
||||
|
||||
|
||||
if (_sub == null)
|
||||
{
|
||||
Console.WriteLine($"[ERROR][{nameof(TwitchPubSubService)}] PubSub is not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (topic, listeners) in _targets)
|
||||
{
|
||||
var listener = listeners.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(_dataService.AccessToken, true);
|
||||
break;
|
||||
}
|
||||
_sub.ListenToVideoPlayback(l.ChannelId);
|
||||
_sub.SendTopics(_dataService.AccessToken, true);
|
||||
}
|
||||
|
||||
|
||||
_targets[topic].RemoveAll(x => x.Instance == receiver);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -12,17 +13,17 @@ using BetterRaid.Views;
|
||||
|
||||
namespace BetterRaid.ViewModels;
|
||||
|
||||
public partial class MainWindowViewModel : ViewModelBase
|
||||
public class MainWindowViewModel : ViewModelBase
|
||||
{
|
||||
private string? _filter;
|
||||
private ObservableCollection<TwitchChannel> _channels = [];
|
||||
private BetterRaidDatabase? _db;
|
||||
private readonly BetterRaidDatabase? _db;
|
||||
private readonly ITwitchPubSubService _pubSub;
|
||||
|
||||
public BetterRaidDatabase? Database
|
||||
{
|
||||
get => _db;
|
||||
set
|
||||
private init
|
||||
{
|
||||
if (SetProperty(ref _db, value) && _db != null)
|
||||
{
|
||||
@ -37,6 +38,8 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
set => SetProperty(ref _channels, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<TwitchChannel> FilteredChannels => GetFilteredChannels();
|
||||
|
||||
public ITwitchDataService DataService { get; }
|
||||
|
||||
public string? Filter
|
||||
@ -51,10 +54,12 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
{
|
||||
_pubSub = pubSub;
|
||||
DataService = dataService;
|
||||
DataService.PropertyChanged += OnDataServicePropertyChanged;
|
||||
|
||||
Database = BetterRaidDatabase.LoadFromFile(Constants.DatabaseFilePath);
|
||||
Database.PropertyChanged += OnDatabasePropertyChanged;
|
||||
}
|
||||
|
||||
|
||||
public void ExitApplication()
|
||||
{
|
||||
//TODO polish later
|
||||
@ -107,4 +112,40 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
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>
|
||||
|
||||
<Grid HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
VerticalAlignment="Stretch"
|
||||
ColumnDefinitions="Auto,*"
|
||||
RowDefinitions="50,*">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Orientation="Horizontal">
|
||||
<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"
|
||||
Grid.Row="0"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
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"
|
||||
IsChecked="{Binding Database.OnlyOnline, Mode=TwoWay}" />
|
||||
IsChecked="{Binding Database.OnlyOnline, Mode=TwoWay, FallbackValue=False}" />
|
||||
|
||||
<TextBox Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
Width="200"
|
||||
Margin="2"
|
||||
<TextBox Width="200"
|
||||
Margin="5, 10, 5, 10"
|
||||
Watermark="Filter Channels"
|
||||
Text="{Binding Filter, Mode=TwoWay}"
|
||||
HorizontalAlignment="Right" />
|
||||
|
||||
</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!"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="1"
|
||||
IsVisible="{Binding !IsLoggedIn, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
FontSize="24"
|
||||
Foreground="#f1f1f1" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Login with Twitch"
|
||||
Foreground="#f1f1f1"
|
||||
FontSize="18"
|
||||
VerticalAlignment="Center" />
|
||||
<Image Source="avares://BetterRaid/Assets/glitch_flat_white.png"
|
||||
Width="24"
|
||||
Height="24"
|
||||
VerticalAlignment="Center"
|
||||
Margin="5, 0, 0, 0" />
|
||||
</StackPanel>
|
||||
|
||||
</Button>
|
||||
|
||||
<ScrollViewer Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
@ -113,12 +115,21 @@
|
||||
IsScrollInertiaEnabled="True" />
|
||||
</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>
|
||||
<DataTemplate>
|
||||
<Grid ColumnDefinitions="100, *, 120"
|
||||
RowDefinitions="100">
|
||||
|
||||
|
||||
<ai:AdvancedImage Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Source="{Binding ThumbnailUrl, TargetNullValue={x:Static misc:Constants.ChannelPlaceholderImageUrl}}" />
|
||||
|
@ -8,4 +8,12 @@ public partial class MainWindow : Window
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void SelectingItemsControl_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is ListBox listBox)
|
||||
{
|
||||
listBox.SelectedItems?.Clear();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user