Started working on UI rework

This commit is contained in:
Enrico Ludwig 2024-09-02 19:08:26 +02:00
parent 019649b179
commit b42d313bff
7 changed files with 155 additions and 286 deletions

2
.gitignore vendored
View File

@ -20,7 +20,7 @@
mono_crash.* mono_crash.*
# Build results # Build results
[Bb]uild/ [Bb]iuld/
[Dd]ebug/ [Dd]ebug/
[Dd]ebugPublic/ [Dd]ebugPublic/
[Rr]elease/ [Rr]elease/

View File

@ -33,6 +33,8 @@ public partial class App : Application
+ $"&response_type={TwitchOAuthResponseType}" + $"&response_type={TwitchOAuthResponseType}"
+ $"&scope={string.Join("+", TwitchOAuthScopes)}"; + $"&scope={string.Join("+", TwitchOAuthScopes)}";
internal static readonly string ChannelPlaceholderImageUrl = "https://cdn.pixabay.com/photo/2018/11/13/22/01/avatar-3814081_1280.png";
public override void Initialize() public override void Initialize()
{ {
var userHomeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); var userHomeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
@ -137,7 +139,7 @@ public partial class App : Application
BindingPlugins.DataValidators.RemoveAt(0); BindingPlugins.DataValidators.RemoveAt(0);
desktop.MainWindow = new MainWindow desktop.MainWindow = new MainWindow
{ {
DataContext = new MainWindowViewModel(), //DataContext = new MainWindowViewModel()
}; };
} }

View File

@ -1,3 +1,4 @@
using System;
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Avalonia.Threading; using Avalonia.Threading;
@ -12,6 +13,8 @@ public class TwitchChannel : INotifyPropertyChanged
private string? _displayName; private string? _displayName;
private string? _thumbnailUrl; private string? _thumbnailUrl;
private string? _category; private string? _category;
private string? _title;
private DateTime? _lastRaided;
public string? BroadcasterId public string? BroadcasterId
{ {
@ -94,6 +97,32 @@ public class TwitchChannel : INotifyPropertyChanged
} }
} }
public string? Title
{
get => _title;
set
{
if (value == _title)
return;
_title = value;
OnPropertyChanged();
}
}
public DateTime? LastRaided
{
get => _lastRaided;
set
{
if (value == _lastRaided)
return;
_lastRaided = value;
OnPropertyChanged();
}
}
public TwitchChannel(string channelName) public TwitchChannel(string channelName)
{ {
Name = channelName; Name = channelName;

View File

@ -0,0 +1,6 @@
namespace BetterRaid.Services;
public class TwitchDataService
{
}

View File

@ -1,6 +1,12 @@
using System; using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Threading;
using BetterRaid.Extensions; using BetterRaid.Extensions;
using BetterRaid.Misc; using BetterRaid.Misc;
using BetterRaid.Models; using BetterRaid.Models;
@ -11,13 +17,25 @@ namespace BetterRaid.ViewModels;
public partial class MainWindowViewModel : ViewModelBase public partial class MainWindowViewModel : ViewModelBase
{ {
private string? _filter; private string? _filter;
private ObservableCollection<TwitchChannel> _channels = [];
private BetterRaidDatabase? _db; private BetterRaidDatabase? _db;
public BetterRaidDatabase? Database public BetterRaidDatabase? Database
{ {
get => _db; get => _db;
set => SetProperty(ref _db, value); set
{
if (SetProperty(ref _db, value) && _db != null)
{
LoadChannelsFromDb();
}
}
}
public ObservableCollection<TwitchChannel> Channels
{
get => _channels;
set => SetProperty(ref _channels, value);
} }
public string? Filter public string? Filter
@ -46,10 +64,29 @@ public partial class MainWindowViewModel : ViewModelBase
Tools.StartOAuthLogin(App.TwitchOAuthUrl, OnTwitchLoginCallback, CancellationToken.None); Tools.StartOAuthLogin(App.TwitchOAuthUrl, OnTwitchLoginCallback, CancellationToken.None);
} }
public void OnTwitchLoginCallback() private void OnTwitchLoginCallback()
{ {
App.InitTwitchClient(overrideToken: true); App.InitTwitchClient(overrideToken: true);
OnPropertyChanged(nameof(IsLoggedIn)); OnPropertyChanged(nameof(IsLoggedIn));
} }
private void LoadChannelsFromDb()
{
if (_db == null)
{
return;
}
Channels.Clear();
var channels = _db.Channels
.Select(channelName => new TwitchChannel(channelName))
.ToList();
foreach (var c in channels)
{
Channels.Add(c);
}
}
} }

View File

@ -3,6 +3,8 @@
xmlns:vm="using:BetterRaid.ViewModels" xmlns:vm="using:BetterRaid.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ai="using:AsyncImageLoader"
xmlns:betterRaid="clr-namespace:BetterRaid"
mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="800" mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="800"
Width="600" Width="600"
Height="800" Height="800"
@ -108,17 +110,81 @@
IsScrollInertiaEnabled="True" /> IsScrollInertiaEnabled="True" />
</ScrollViewer.GestureRecognizers> </ScrollViewer.GestureRecognizers>
<Grid HorizontalAlignment="Stretch" <ListBox ItemsSource="{Binding Channels}">
VerticalAlignment="Stretch" <ListBox.ItemTemplate>
x:Name="raidGrid"> <DataTemplate>
<Grid ColumnDefinitions="200,*"
RowDefinitions="200">
<Grid.ColumnDefinitions> <ai:AdvancedImage Grid.Column="0"
<ColumnDefinition Width="*" /> Grid.Row="0"
<ColumnDefinition Width="*" /> Source="{Binding ThumbnailUrl, TargetNullValue={x:Static betterRaid:App.ChannelPlaceholderImageUrl}}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> <Border Grid.Column="0"
Grid.Row="0"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Height="20"
MinWidth="20"
CornerRadius="10"
Background="#FFFFFF"
Padding="3"
Margin="0, 0, 10, 10">
<TextBlock Text="{Binding ViewerCount, TargetNullValue='-'}"/>
</Border>
<Grid Grid.Column="1"
Grid.Row="0"
ColumnDefinitions="30*, 70*"
RowDefinitions="40, 40, 40, 40, 40">
<TextBlock Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="2"
FontWeight="Bold"
TextDecorations="Underline"
Text="{Binding DisplayName, TargetNullValue='???'}" />
<TextBlock Grid.Column="0"
Grid.Row="1"
Text="Category:"
FontWeight="SemiBold" />
<TextBlock Grid.Column="0"
Grid.Row="2"
Text="Title:"
FontWeight="SemiBold" />
<TextBlock Grid.Column="0"
Grid.Row="3"
Text="Last Raided:"
FontWeight="SemiBold" />
<TextBlock Grid.Column="0"
Grid.Row="4"
Text=""
FontWeight="SemiBold" />
<TextBlock Grid.Column="1"
Grid.Row="1"
Text="{Binding Category, TargetNullValue='-'}" />
<TextBlock Grid.Column="1"
Grid.Row="2"
Text="{Binding Title, TargetNullValue='-'}" />
<TextBlock Grid.Column="1"
Grid.Row="3"
Text="{Binding LastRaided, TargetNullValue='Never Raided'}" />
<TextBlock Grid.Column="1"
Grid.Row="4"
Text="" />
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>

View File

@ -15,279 +15,8 @@ namespace BetterRaid.Views;
public partial class MainWindow : Window public partial class MainWindow : Window
{ {
private ObservableCollection<RaidButtonViewModel> _raidButtonVMs;
private RaidButtonViewModel? _znButtonVm;
private BackgroundWorker _autoUpdater;
public MainWindow() public MainWindow()
{ {
_raidButtonVMs = [];
_znButtonVm = null;
_autoUpdater = new();
DataContextChanged += OnDataContextChanged;
InitializeComponent(); InitializeComponent();
_autoUpdater.WorkerSupportsCancellation = true;
_autoUpdater.DoWork += UpdateAllTiles;
}
private void OnDatabaseChanged(object? sender, PropertyChangedEventArgs e)
{
// TODO: Only if new channel was added or existing were removed
// InitializeRaidChannels();
GenerateRaidGrid();
}
private void OnDataContextChanged(object? sender, EventArgs e)
{
if (DataContext is MainWindowViewModel vm)
{
var dbPath = Path.Combine(App.BetterRaidDataPath, "db.json");
try
{
vm.Database = BetterRaidDatabase.LoadFromFile(dbPath);
}
catch (FileNotFoundException)
{
var db = new BetterRaidDatabase();
db.Save(dbPath);
vm.Database = db;
}
vm.Database.AutoSave = true;
vm.Database.PropertyChanged += OnDatabaseChanged;
vm.PropertyChanged += OnViewModelChanged;
InitializeRaidChannels();
GenerateRaidGrid();
}
}
private void OnViewModelChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(MainWindowViewModel.Filter))
{
GenerateRaidGrid();
}
if (e.PropertyName == nameof(MainWindowViewModel.IsLoggedIn) && DataContext is MainWindowViewModel { IsLoggedIn: true })
{
InitializeRaidChannels();
GenerateRaidGrid();
}
}
private void InitializeRaidChannels()
{
if (DataContext is MainWindowViewModel { IsLoggedIn: false })
return;
if (_autoUpdater?.IsBusy == false)
{
_autoUpdater?.CancelAsync();
}
_raidButtonVMs.Clear();
var vm = DataContext as MainWindowViewModel;
if (vm?.Database == null)
return;
foreach (var channel in vm.Database.Channels)
{
if (string.IsNullOrEmpty(channel))
continue;
var rbvm = new RaidButtonViewModel(channel)
{
MainVm = vm
};
_raidButtonVMs.Add(rbvm);
}
if (App.HasUserZnSubbed)
{
_znButtonVm = null;
}
else
{
_znButtonVm = new RaidButtonViewModel("zionnetworks")
{
MainVm = vm,
HideDeleteButton = true,
IsAd = true
};
}
if (_autoUpdater?.IsBusy == false)
{
_autoUpdater?.RunWorkerAsync();
}
}
private void GenerateRaidGrid()
{
if (DataContext is MainWindowViewModel { IsLoggedIn: false })
return;
foreach (var child in raidGrid.Children)
{
if (child is Button btn)
{
btn.Click -= OnAddChannelButtonClicked;
}
}
raidGrid.Children.Clear();
var vm = DataContext as MainWindowViewModel;
if (vm?.Database == null)
{
return;
}
var visibleChannels = _raidButtonVMs.Where(channel =>
{
var visible = true;
if (string.IsNullOrWhiteSpace(vm.Filter) == false)
{
if (channel.ChannelName.Contains(vm.Filter, StringComparison.OrdinalIgnoreCase) == false)
{
visible = false;
}
}
if (vm.Database.OnlyOnline && channel.Channel?.IsLive == false)
{
visible = false;
}
return visible;
}).OrderByDescending(c => c.Channel?.IsLive).ToList();
var rows = (int)Math.Ceiling((visibleChannels.Count + (App.HasUserZnSubbed ? 1 : 2)) / 3.0);
for (var i = 0; i < rows; i++)
{
raidGrid.RowDefinitions.Add(new RowDefinition(GridLength.Parse("Auto")));
}
var colIndex = App.HasUserZnSubbed ? 0 : 1;
var rowIndex = 0;
foreach (var channel in visibleChannels)
{
var btn = new RaidButton
{
DataContext = channel
};
Grid.SetColumn(btn, colIndex);
Grid.SetRow(btn, rowIndex);
raidGrid.Children.Add(btn);
colIndex++;
if (colIndex % 3 == 0)
{
colIndex = 0;
rowIndex++;
}
}
var addButton = new Button
{
Content = "+",
FontSize = 72,
Margin = new Avalonia.Thickness(5),
MinHeight = 250,
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Stretch,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Stretch,
HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Center,
VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Center
};
addButton.Click += OnAddChannelButtonClicked;
Grid.SetColumn(addButton, colIndex);
Grid.SetRow(addButton, rowIndex);
raidGrid.Children.Add(addButton);
if (App.HasUserZnSubbed == false)
{
var znButton = new RaidButton
{
DataContext = _znButtonVm
};
Grid.SetColumn(znButton, 0);
Grid.SetRow(znButton, 0);
raidGrid.Children.Add(znButton);
}
}
private void OnAddChannelButtonClicked(object? sender, RoutedEventArgs e)
{
var dialog = new AddChannelWindow();
dialog.CenterToOwner();
var vm = DataContext as MainWindowViewModel;
if (vm?.Database == null)
return;
// TODO Button Command not working, Button remains disabled
// This is a dirty workaround
dialog.okBtn.Click += (sender, args) => {
if (string.IsNullOrWhiteSpace(dialog?.channelNameTxt.Text) == false)
{
vm.Database.AddChannel(dialog.channelNameTxt.Text);
vm.Database.Save();
}
dialog?.Close();
InitializeRaidChannels();
GenerateRaidGrid();
};
dialog.ShowDialog(this);
}
public void UpdateChannelData()
{
var loggedIn = Dispatcher.UIThread.Invoke(() => {
return (DataContext as MainWindowViewModel)?.IsLoggedIn ?? false;
});
if (loggedIn == false)
return;
foreach (var vm in _raidButtonVMs)
{
Task.Run(vm.GetOrUpdateChannelAsync);
}
if (_znButtonVm != null)
{
Task.Run(_znButtonVm.GetOrUpdateChannelAsync);
}
}
private void UpdateAllTiles(object? sender, DoWorkEventArgs e)
{
while (e.Cancel == false)
{
UpdateChannelData();
Task.Delay(App.AutoUpdateDelay).Wait();
}
} }
} }