Lots of performance optimization; Added category to RaidButton; Added AboutWindow

This commit is contained in:
Enrico Ludwig 2024-08-27 19:51:23 +02:00
parent 4311bfb43f
commit d0662351d7
11 changed files with 218 additions and 79 deletions

View File

@ -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", "{5F90F02B-A28E-4B50-9821-1F0D7226936C}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BetterRaid", "BetterRaid.csproj", "{6BE742A5-079D-4617-BDC1-2933274CDCB7}"
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
{5F90F02B-A28E-4B50-9821-1F0D7226936C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6BE742A5-079D-4617-BDC1-2933274CDCB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5F90F02B-A28E-4B50-9821-1F0D7226936C}.Debug|Any CPU.Build.0 = Debug|Any CPU {6BE742A5-079D-4617-BDC1-2933274CDCB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F90F02B-A28E-4B50-9821-1F0D7226936C}.Release|Any CPU.ActiveCfg = Release|Any CPU {6BE742A5-079D-4617-BDC1-2933274CDCB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5F90F02B-A28E-4B50-9821-1F0D7226936C}.Release|Any CPU.Build.0 = Release|Any CPU {6BE742A5-079D-4617-BDC1-2933274CDCB7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -57,14 +57,17 @@
Orientation="Vertical"> Orientation="Vertical">
<Label HorizontalAlignment="Stretch" <Label HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center" HorizontalContentAlignment="Center"
Content="{Binding Channel.DisplayName}" /> Content="{Binding Channel.DisplayName, FallbackValue=...}" />
<Label HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Content="{Binding Channel.Category, TargetNullValue=-, FallbackValue=...}" />
<Label HorizontalAlignment="Stretch" <Label HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center" HorizontalContentAlignment="Center"
Foreground="{Binding ViewerCountColor}" Foreground="{Binding ViewerCountColor}"
Content="{Binding Channel.ViewerCount}" /> Content="{Binding Channel.ViewerCount, TargetNullValue=(Offline), FallbackValue=...}" />
<Label HorizontalAlignment="Stretch" <Label HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center" HorizontalContentAlignment="Center"
Content="{Binding LastRaided, TargetNullValue=Never Raided}" /> Content="{Binding LastRaided, TargetNullValue=Never Raided, FallbackValue=...}" />
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@ -0,0 +1,19 @@
using Avalonia.Controls;
namespace BetterRaid.Extensions;
public static class WindowExtensions
{
public static void CenterToOwner(this Window window)
{
var owner = window.Owner as Window;
if (owner == null)
return;
window.Position = new Avalonia.PixelPoint(
(int)(owner.Position.X + owner.Width / 2 - window.Width / 2),
(int)(owner.Position.Y + owner.Height / 2 - window.Height / 2)
);
}
}

View File

@ -6,11 +6,12 @@ namespace BetterRaid.Models;
public class TwitchChannel : INotifyPropertyChanged public class TwitchChannel : INotifyPropertyChanged
{ {
private string? viewerCount; private string? _viewerCount;
private bool isLive; private bool _isLive;
private string? name; private string? _name;
private string? displayName; private string? _displayName;
private string? thumbnailUrl; private string? _thumbnailUrl;
private string? _category;
public string? BroadcasterId public string? BroadcasterId
{ {
@ -19,63 +20,76 @@ public class TwitchChannel : INotifyPropertyChanged
} }
public string? Name public string? Name
{ {
get => name; get => _name;
set set
{ {
if (value == name) if (value == _name)
return; return;
name = value; _name = value;
OnPropertyChanged(); OnPropertyChanged();
} }
} }
public bool IsLive public bool IsLive
{ {
get => isLive; get => _isLive;
set set
{ {
if (value == isLive) if (value == _isLive)
return; return;
isLive = value; _isLive = value;
OnPropertyChanged(); OnPropertyChanged();
} }
} }
public string? ViewerCount public string? ViewerCount
{ {
get => viewerCount; get => _viewerCount;
set set
{ {
if (value == viewerCount) if (value == _viewerCount)
return; return;
viewerCount = value; _viewerCount = value;
OnPropertyChanged(); OnPropertyChanged();
} }
} }
public string? ThumbnailUrl public string? ThumbnailUrl
{ {
get => thumbnailUrl; get => _thumbnailUrl;
set set
{ {
if (value == thumbnailUrl) if (value == _thumbnailUrl)
return; return;
thumbnailUrl = value; _thumbnailUrl = value;
OnPropertyChanged(); OnPropertyChanged();
} }
} }
public string? DisplayName public string? DisplayName
{ {
get => displayName; get => _displayName;
set set
{ {
if (value == displayName) if (value == _displayName)
return; return;
displayName = value; _displayName = value;
OnPropertyChanged();
}
}
public string? Category
{
get => _category;
set
{
if (value == _category)
return;
_category = value;
OnPropertyChanged(); OnPropertyChanged();
} }
} }

View File

@ -1,6 +1,9 @@
using System; using System;
using Avalonia; using Avalonia;
using Avalonia.Controls;
using BetterRaid.Extensions;
using BetterRaid.Models; using BetterRaid.Models;
using BetterRaid.Views;
namespace BetterRaid.ViewModels; namespace BetterRaid.ViewModels;
@ -27,4 +30,11 @@ public partial class MainWindowViewModel : ViewModelBase
//TODO polish later //TODO polish later
Environment.Exit(0); Environment.Exit(0);
} }
public void ShowAboutWindow(Window owner)
{
var about = new AboutWindow();
about.ShowDialog(owner);
about.CenterToOwner();
}
} }

View File

@ -3,6 +3,7 @@ using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Threading;
using BetterRaid.Models; using BetterRaid.Models;
using TwitchLib.Api.Helix.Models.Raids.StartRaid; using TwitchLib.Api.Helix.Models.Raids.StartRaid;
using TwitchLib.Api.Helix.Models.Search; using TwitchLib.Api.Helix.Models.Search;
@ -15,13 +16,13 @@ public class RaidButtonViewModel : ViewModelBase
private TwitchChannel? _channel; private TwitchChannel? _channel;
private SolidColorBrush _viewerCountColor = new SolidColorBrush(Color.FromRgb(byte.MaxValue, byte.MaxValue, byte.MaxValue)); private SolidColorBrush _viewerCountColor = new SolidColorBrush(Color.FromRgb(byte.MaxValue, byte.MaxValue, byte.MaxValue));
public required string ChannelName public string ChannelName
{ {
get; get;
set; set;
} }
public TwitchChannel Channel => _channel ?? new TwitchChannel(ChannelName); public TwitchChannel? Channel => _channel ?? new TwitchChannel(ChannelName);
public SolidColorBrush ViewerCountColor public SolidColorBrush ViewerCountColor
{ {
@ -33,13 +34,14 @@ public class RaidButtonViewModel : ViewModelBase
public DateTime? LastRaided => MainVm?.Database?.GetLastRaided(ChannelName); public DateTime? LastRaided => MainVm?.Database?.GetLastRaided(ChannelName);
public RaidButtonViewModel(string channelName)
{
ChannelName = channelName;
}
public async Task<bool> GetOrUpdateChannelAsync() public async Task<bool> GetOrUpdateChannelAsync()
{ {
if (_channel == null) Console.WriteLine("[DEBUG] Updating channel '{0}' ...", ChannelName);
{
_channel = new TwitchChannel(ChannelName);
_channel.PropertyChanged += OnChannelDataChanged;
}
var currentChannelData = await GetChannelAsync(ChannelName); var currentChannelData = await GetChannelAsync(ChannelName);
@ -48,24 +50,42 @@ public class RaidButtonViewModel : ViewModelBase
var currentStreamData = await GetStreamAsync(currentChannelData); var currentStreamData = await GetStreamAsync(currentChannelData);
_channel.BroadcasterId = currentChannelData.Id; var swapChannel = new TwitchChannel(ChannelName)
_channel.Name = ChannelName; {
_channel.DisplayName = currentChannelData.DisplayName; BroadcasterId = currentChannelData.Id,
_channel.IsLive = currentChannelData.IsLive; Name = ChannelName,
_channel.ThumbnailUrl = currentChannelData.ThumbnailUrl; DisplayName = currentChannelData.DisplayName,
_channel.ViewerCount = currentStreamData?.ViewerCount == null IsLive = currentChannelData.IsLive,
ThumbnailUrl = currentChannelData.ThumbnailUrl,
ViewerCount = currentStreamData?.ViewerCount == null
? "(Offline)" ? "(Offline)"
: $"{currentStreamData?.ViewerCount} Viewers"; : $"{currentStreamData?.ViewerCount} Viewers",
Category = currentStreamData?.GameName
};
if (_channel.IsLive) if (_channel != null)
{ {
ViewerCountColor = new SolidColorBrush(Color.FromRgb(0, byte.MaxValue, 0)); _channel.PropertyChanged -= OnChannelDataChanged;
} }
else
Dispatcher.UIThread.Invoke(() => {
ViewerCountColor = new SolidColorBrush(Color.FromRgb(
r: swapChannel.IsLive ? (byte) 0 : byte.MaxValue,
g: swapChannel.IsLive ? byte.MaxValue : (byte) 0,
b: 0)
);
_channel = swapChannel;
OnPropertyChanged(nameof(Channel));
});
if (_channel != null)
{ {
ViewerCountColor = new SolidColorBrush(Color.FromRgb(byte.MaxValue, 0, 0)); _channel.PropertyChanged += OnChannelDataChanged;
} }
Console.WriteLine("[DEBUG] DONE Updating channel '{0}'", ChannelName);
return true; return true;
} }
@ -102,6 +122,9 @@ public class RaidButtonViewModel : ViewModelBase
if (App.TwitchAPI == null) if (App.TwitchAPI == null)
return; return;
if (Channel == null)
return;
StartRaidResponse? raid = null; StartRaidResponse? raid = null;
try try

57
Views/AboutWindow.axaml Normal file
View File

@ -0,0 +1,57 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="BetterRaid.Views.AboutWindow"
Title="About"
MaxWidth="300"
MinWidth="300"
Height="200"
Background="DarkSlateGray">
<ScrollViewer>
<StackPanel Orientation="Vertical">
<Label HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Content="About BetterRaid"
Margin="0, 10, 0, -10"
FontSize="28"
FontWeight="ExtraLight" />
<Label HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Content="v0.0.1"
Margin="0"
FontSize="12"
FontWeight="Light" />
<Separator Margin="0, 5, 0, 5" />
<TextBlock HorizontalAlignment="Left"
Text="Programming"
FontSize="14"
Margin="5, 5, 0, 5"
TextDecorations="Underline" />
<TextBlock HorizontalAlignment="Left"
Text="- Enrico Ludwig &lt;hi@zion-networks.de&gt;"
FontSize="12"
Margin="15, 0, 0, 0" />
<TextBlock HorizontalAlignment="Left"
Text="Graphics"
FontSize="14"
Margin="5, 5, 0, 5"
TextDecorations="Underline" />
<TextBlock HorizontalAlignment="Left"
Text="- UNKNOWN ARTIST"
FontSize="12"
Margin="15, 0, 0, 0" />
</StackPanel>
</ScrollViewer>
</Window>

View File

@ -0,0 +1,13 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace BetterRaid.Views;
public partial class AboutWindow : Window
{
public AboutWindow()
{
InitializeComponent();
}
}

View File

@ -10,8 +10,7 @@
x:DataType="vm:MainWindowViewModel" x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico" Icon="/Assets/avalonia-logo.ico"
Title="BetterRaid" Title="BetterRaid"
Background="DarkSlateGray" Background="DarkSlateGray">
WindowStartupLocation="CenterScreen">
<Design.DataContext> <Design.DataContext>
<vm:MainWindowViewModel/> <vm:MainWindowViewModel/>
@ -35,7 +34,12 @@
Grid.Row="0"> Grid.Row="0">
<MenuItem Header="File"> <MenuItem Header="File">
<MenuItem Header="Exit" Command="{Binding ExitApplication}" /> <MenuItem Header="About"
CommandParameter="{Binding $parent[Window]}"
Command="{Binding ShowAboutWindow}" />
<Separator />
<MenuItem Header="Exit"
Command="{Binding ExitApplication}" />
</MenuItem> </MenuItem>
</Menu> </Menu>

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Threading; using Avalonia.Threading;
using BetterRaid.Extensions;
using BetterRaid.Models; using BetterRaid.Models;
using BetterRaid.ViewModels; using BetterRaid.ViewModels;
@ -25,13 +26,14 @@ public partial class MainWindow : Window
InitializeComponent(); InitializeComponent();
_autoUpdater.WorkerSupportsCancellation = true;
_autoUpdater.DoWork += UpdateAllTiles; _autoUpdater.DoWork += UpdateAllTiles;
_autoUpdater.RunWorkerAsync();
} }
private void OnDatabaseChanged(object? sender, PropertyChangedEventArgs e) private void OnDatabaseChanged(object? sender, PropertyChangedEventArgs e)
{ {
InitializeRaidChannels(); // TODO: Only if new channel was added or existing were removed
// InitializeRaidChannels();
GenerateRaidGrid(); GenerateRaidGrid();
} }
@ -60,9 +62,14 @@ public partial class MainWindow : Window
private void InitializeRaidChannels() private void InitializeRaidChannels()
{ {
if (_autoUpdater?.IsBusy == false)
{
_autoUpdater?.CancelAsync();
}
foreach (var rbvm in _raidButtonVMs) foreach (var rbvm in _raidButtonVMs)
{ {
rbvm.PropertyChanged -= OnChannelDataChanged; rbvm.PropertyChanged -= OnRaidButtonViewModelChanged;
} }
_raidButtonVMs.Clear(); _raidButtonVMs.Clear();
@ -77,26 +84,25 @@ public partial class MainWindow : Window
if (string.IsNullOrEmpty(channel)) if (string.IsNullOrEmpty(channel))
continue; continue;
var rbvm = new RaidButtonViewModel var rbvm = new RaidButtonViewModel(channel)
{ {
ChannelName = channel,
MainVm = vm MainVm = vm
}; };
rbvm.PropertyChanged += OnChannelDataChanged; rbvm.PropertyChanged += OnRaidButtonViewModelChanged;
_raidButtonVMs.Add(rbvm); _raidButtonVMs.Add(rbvm);
} }
UpdateChannelData(); if (_autoUpdater?.IsBusy == false)
{
_autoUpdater?.RunWorkerAsync();
}
} }
private void OnChannelDataChanged(object? sender, PropertyChangedEventArgs e) private void OnRaidButtonViewModelChanged(object? sender, PropertyChangedEventArgs e)
{ {
if (e.PropertyName == nameof(RaidButtonViewModel.Channel))
{
GenerateRaidGrid();
}
} }
private void GenerateRaidGrid() private void GenerateRaidGrid()
@ -129,13 +135,15 @@ public partial class MainWindow : Window
} }
} }
if (vm.Database.OnlyOnline && channel.Channel.IsLive == false) if (vm.Database.OnlyOnline && channel.Channel?.IsLive == false)
{ {
visible = false; visible = false;
} }
return visible; return visible;
}).ToList(); }).OrderByDescending(c => c.Channel?.IsLive).ToList();
var rows = (int)Math.Ceiling((visibleChannels.Count + 1) / 3.0); var rows = (int)Math.Ceiling((visibleChannels.Count + 1) / 3.0);
for (var i = 0; i < rows; i++) for (var i = 0; i < rows; i++)
@ -163,11 +171,6 @@ public partial class MainWindow : Window
colIndex = 0; colIndex = 0;
rowIndex++; rowIndex++;
} }
if (btn.DataContext is RaidButtonViewModel rbvm)
{
Dispatcher.UIThread.InvokeAsync(rbvm.GetOrUpdateChannelAsync);
}
} }
var addButton = new Button var addButton = new Button
@ -193,10 +196,7 @@ public partial class MainWindow : Window
private void OnAddChannelButtonClicked(object? sender, RoutedEventArgs e) private void OnAddChannelButtonClicked(object? sender, RoutedEventArgs e)
{ {
var dialog = new AddChannelWindow(); var dialog = new AddChannelWindow();
dialog.Position = new Avalonia.PixelPoint( dialog.CenterToOwner();
(int)(Position.X + Width / 2 - dialog.Width / 2),
(int)(Position.Y + Height / 2 - dialog.Height / 2)
);
var vm = DataContext as MainWindowViewModel; var vm = DataContext as MainWindowViewModel;
@ -225,11 +225,7 @@ public partial class MainWindow : Window
{ {
foreach (var vm in _raidButtonVMs) foreach (var vm in _raidButtonVMs)
{ {
Dispatcher.UIThread.InvokeAsync(async () => Task.Run(vm.GetOrUpdateChannelAsync);
{
await vm.GetOrUpdateChannelAsync();
}
);
} }
} }
@ -237,8 +233,8 @@ public partial class MainWindow : Window
{ {
while (e.Cancel == false) while (e.Cancel == false)
{ {
Task.Delay(App.AutoUpdateDelay).Wait();
UpdateChannelData(); UpdateChannelData();
Task.Delay(App.AutoUpdateDelay).Wait();
} }
} }
} }

View File

@ -1 +1 @@
{"LastRaided":{"Cedricun":null,"ZanTal":null,"PropzMaster":null,"Artimus83":null,"HyperonsLive":null,"theshroomlife":null,"Robocraft999":null,"sllikson":null,"Aron_dc":null,"AIEsports":null,"TobinatorLP":null,"Lordabgrund":null,"GronkhTV":null},"OnlyOnline":false,"Channels":["Cedricun","ZanTal","PropzMaster","Artimus83","HyperonsLive","theshroomlife","Robocraft999","sllikson","Aron_dc","AIEsports","TobinatorLP","Lordabgrund","GronkhTV"],"AutoSave":true} {"LastRaided":{"Cedricun":null,"ZanTal":null,"PropzMaster":null,"Artimus83":null,"HyperonsLive":null,"theshroomlife":null,"Robocraft999":null,"sllikson":null,"Aron_dc":null,"AIEsports":null,"TobinatorLP":null,"Lordabgrund":null,"GronkhTV":null,"Bounty2Cooki":null,"ichdasFaultier":null,"codingPurpurTentakel":null,"DerMorzi":null},"OnlyOnline":false,"Channels":["Cedricun","ZanTal","PropzMaster","Artimus83","HyperonsLive","theshroomlife","Robocraft999","sllikson","Aron_dc","AIEsports","TobinatorLP","Lordabgrund","GronkhTV","Bounty2Cooki","ichdasFaultier","codingPurpurTentakel","DerMorzi"],"AutoSave":true}