Compare commits
No commits in common. "ui-rework" and "main" have entirely different histories.
1
.gitignore
vendored
1
.gitignore
vendored
@ -20,7 +20,6 @@
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Bb]iuld/
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
|
199
App.axaml.cs
199
App.axaml.cs
@ -5,209 +5,140 @@ using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Data.Core.Plugins;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using BetterRaid.Services;
|
||||
using BetterRaid.ViewModels;
|
||||
using BetterRaid.Views;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using TwitchLib.Api;
|
||||
|
||||
namespace BetterRaid;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
private readonly ServiceCollection _services = [];
|
||||
private ServiceProvider? _provider;
|
||||
|
||||
private static TwitchAPI? _twitchApi;
|
||||
private static bool _hasUserZnSubbed;
|
||||
private static string _betterRaidDataPath = "";
|
||||
private static string _twitchBroadcasterId = "";
|
||||
private static string _twitchOAuthAccessToken = "";
|
||||
private static string _twitchOAuthAccessTokenFilePath = "";
|
||||
private const string TokenClientId = "kkxu4jorjrrc5jch1ito5i61hbev2o";
|
||||
private const string TwitchOAuthRedirectUrl = "http://localhost:9900";
|
||||
private const string TwitchOAuthResponseType = "token";
|
||||
|
||||
private static readonly string[] TwitchOAuthScopes = [
|
||||
internal static TwitchAPI? TwitchApi = null;
|
||||
internal static int AutoUpdateDelay = 10_000;
|
||||
internal static bool HasUserZnSubbed = false;
|
||||
internal static string BetterRaidDataPath = "";
|
||||
internal static string TwitchBroadcasterId = "";
|
||||
internal static string TwitchOAuthAccessToken = "";
|
||||
internal static string TwitchOAuthAccessTokenFilePath = "";
|
||||
internal static string TokenClientId = "kkxu4jorjrrc5jch1ito5i61hbev2o";
|
||||
internal static readonly string TwitchOAuthRedirectUrl = "http://localhost:9900";
|
||||
internal static readonly string TwitchOAuthResponseType = "token";
|
||||
internal static readonly string[] TwitchOAuthScopes = [
|
||||
"channel:manage:raids",
|
||||
"user:read:subscriptions"
|
||||
];
|
||||
|
||||
internal static readonly string TwitchOAuthUrl = $"https://id.twitch.tv/oauth2/authorize"
|
||||
+ $"?client_id={TokenClientId}"
|
||||
+ $"&redirect_uri={TwitchOAuthRedirectUrl}"
|
||||
+ "&redirect_uri=http://localhost:9900"
|
||||
+ $"&response_type={TwitchOAuthResponseType}"
|
||||
+ $"&scope={string.Join("+", TwitchOAuthScopes)}";
|
||||
|
||||
public const string ChannelPlaceholderImageUrl = "https://cdn.pixabay.com/photo/2018/11/13/22/01/avatar-3814081_1280.png";
|
||||
|
||||
public static TwitchAPI? TwitchApi => _twitchApi;
|
||||
public static bool HasUserZnSubbed => _hasUserZnSubbed;
|
||||
|
||||
public IServiceProvider? Provider => _provider;
|
||||
public static string? TwitchBroadcasterId => _twitchBroadcasterId;
|
||||
|
||||
public static string TwitchOAuthAccessToken
|
||||
{
|
||||
get => _twitchOAuthAccessToken;
|
||||
set
|
||||
{
|
||||
_twitchOAuthAccessToken = value;
|
||||
InitTwitchClient(true);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
InitializeServices();
|
||||
LoadTwitchToken();
|
||||
|
||||
AvaloniaXamlLoader.Load(_provider, this);
|
||||
}
|
||||
|
||||
private void LoadTwitchToken()
|
||||
{
|
||||
var userHomeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
|
||||
_betterRaidDataPath = Environment.OSVersion.Platform switch
|
||||
switch (Environment.OSVersion.Platform)
|
||||
{
|
||||
PlatformID.Win32NT => Path.Combine(userHomeDir, "AppData", "Roaming", "BetterRaid"),
|
||||
PlatformID.Unix => Path.Combine(userHomeDir, ".config", "BetterRaid"),
|
||||
PlatformID.MacOSX => Path.Combine(userHomeDir, "Library", "Application Support", "BetterRaid"),
|
||||
_ => throw new PlatformNotSupportedException($"Your platform '{Environment.OSVersion.Platform}' is not supported. Please report this issue here: https://www.github.com/zion-networks/BetterRaid/issues")
|
||||
};
|
||||
|
||||
if (!Directory.Exists(_betterRaidDataPath))
|
||||
{
|
||||
var di = Directory.CreateDirectory(_betterRaidDataPath);
|
||||
if (di.Exists == false)
|
||||
{
|
||||
throw new Exception($"Failed to create directory '{_betterRaidDataPath}'");
|
||||
}
|
||||
case PlatformID.Win32NT:
|
||||
BetterRaidDataPath = Path.Combine(userHomeDir, "AppData", "Roaming", "BetterRaid");
|
||||
break;
|
||||
case PlatformID.Unix:
|
||||
BetterRaidDataPath = Path.Combine(userHomeDir, ".config", "BetterRaid");
|
||||
break;
|
||||
case PlatformID.MacOSX:
|
||||
BetterRaidDataPath = Path.Combine(userHomeDir, "Library", "Application Support", "BetterRaid");
|
||||
break;
|
||||
}
|
||||
|
||||
_twitchOAuthAccessTokenFilePath = Path.Combine(_betterRaidDataPath, ".access_token");
|
||||
if (!Directory.Exists(BetterRaidDataPath))
|
||||
Directory.CreateDirectory(BetterRaidDataPath);
|
||||
|
||||
if (!File.Exists(_twitchOAuthAccessTokenFilePath))
|
||||
return;
|
||||
TwitchOAuthAccessTokenFilePath = Path.Combine(BetterRaidDataPath, ".access_token");
|
||||
|
||||
_twitchOAuthAccessToken = File.ReadAllText(_twitchOAuthAccessTokenFilePath);
|
||||
InitTwitchClient();
|
||||
}
|
||||
if (File.Exists(TwitchOAuthAccessTokenFilePath))
|
||||
{
|
||||
TwitchOAuthAccessToken = File.ReadAllText(TwitchOAuthAccessTokenFilePath);
|
||||
InitTwitchClient();
|
||||
}
|
||||
|
||||
private void InitializeServices()
|
||||
{
|
||||
_services.AddSingleton<ITwitchDataService, TwitchDataService>();
|
||||
_services.AddTransient<MainWindowViewModel>();
|
||||
_services.AddTransient<AboutWindowViewModel>();
|
||||
|
||||
_provider = _services.BuildServiceProvider();
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public static void InitTwitchClient(bool overrideToken = false)
|
||||
{
|
||||
Console.WriteLine("[INFO] Initializing Twitch Client...");
|
||||
|
||||
if (string.IsNullOrEmpty(_twitchOAuthAccessToken))
|
||||
{
|
||||
Console.WriteLine("[ERROR] Failed to initialize Twitch Client: Access Token is empty!");
|
||||
return;
|
||||
}
|
||||
|
||||
_twitchApi = new TwitchAPI
|
||||
{
|
||||
Settings =
|
||||
{
|
||||
ClientId = TokenClientId,
|
||||
AccessToken = _twitchOAuthAccessToken
|
||||
}
|
||||
};
|
||||
TwitchApi = new TwitchAPI();
|
||||
TwitchApi.Settings.ClientId = TokenClientId;
|
||||
TwitchApi.Settings.AccessToken = TwitchOAuthAccessToken;
|
||||
|
||||
Console.WriteLine("[INFO] Testing Twitch API connection...");
|
||||
|
||||
var user = _twitchApi.Helix.Users.GetUsersAsync().Result.Users.FirstOrDefault();
|
||||
var user = TwitchApi.Helix.Users.GetUsersAsync().Result.Users.FirstOrDefault();
|
||||
if (user == null)
|
||||
{
|
||||
_twitchApi = null;
|
||||
TwitchApi = null;
|
||||
Console.WriteLine("[ERROR] Failed to connect to Twitch API!");
|
||||
return;
|
||||
}
|
||||
|
||||
var channel = _twitchApi.Helix.Search
|
||||
var channel = TwitchApi.Helix.Search
|
||||
.SearchChannelsAsync(user.Login).Result.Channels
|
||||
.FirstOrDefault(c => c.BroadcasterLogin == user.Login);
|
||||
|
||||
var userSubs = _twitchApi.Helix.Subscriptions.CheckUserSubscriptionAsync(
|
||||
var userSubs = TwitchApi.Helix.Subscriptions.CheckUserSubscriptionAsync(
|
||||
userId: user.Id,
|
||||
broadcasterId: "1120558409"
|
||||
).Result.Data;
|
||||
|
||||
if (userSubs is { Length: > 0 } && userSubs.Any(s => s.BroadcasterId == "1120558409"))
|
||||
if (userSubs.Length > 0 && userSubs.Any(s => s.BroadcasterId == "1120558409"))
|
||||
{
|
||||
_hasUserZnSubbed = true;
|
||||
HasUserZnSubbed = true;
|
||||
}
|
||||
|
||||
if (channel == null)
|
||||
{
|
||||
Console.WriteLine($"[ERROR] Failed to get channel information for '{user.Login}'!");
|
||||
Console.WriteLine("[ERROR] User channel could not be found!");
|
||||
return;
|
||||
}
|
||||
|
||||
_twitchBroadcasterId = channel.Id;
|
||||
Console.WriteLine(_twitchBroadcasterId);
|
||||
TwitchBroadcasterId = channel.Id;
|
||||
System.Console.WriteLine(TwitchBroadcasterId);
|
||||
|
||||
Console.WriteLine("[INFO] Connected to Twitch API as '{0}'!", user.DisplayName);
|
||||
|
||||
if (!overrideToken)
|
||||
return;
|
||||
|
||||
File.WriteAllText(_twitchOAuthAccessTokenFilePath, _twitchOAuthAccessToken);
|
||||
|
||||
switch (Environment.OSVersion.Platform)
|
||||
if (overrideToken)
|
||||
{
|
||||
case PlatformID.Win32NT:
|
||||
File.SetAttributes(_twitchOAuthAccessTokenFilePath, File.GetAttributes(_twitchOAuthAccessTokenFilePath) | FileAttributes.Hidden);
|
||||
break;
|
||||
File.WriteAllText(TwitchOAuthAccessTokenFilePath, TwitchOAuthAccessToken);
|
||||
|
||||
case PlatformID.Unix:
|
||||
switch (Environment.OSVersion.Platform)
|
||||
{
|
||||
case PlatformID.Win32NT:
|
||||
File.SetAttributes(TwitchOAuthAccessTokenFilePath, File.GetAttributes(TwitchOAuthAccessTokenFilePath) | FileAttributes.Hidden);
|
||||
break;
|
||||
case PlatformID.Unix:
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
File.SetUnixFileMode(_twitchOAuthAccessTokenFilePath, UnixFileMode.UserRead);
|
||||
File.SetUnixFileMode(TwitchOAuthAccessTokenFilePath, UnixFileMode.UserRead);
|
||||
#pragma warning restore CA1416 // Validate platform compatibility
|
||||
break;
|
||||
|
||||
case PlatformID.MacOSX:
|
||||
File.SetAttributes(_twitchOAuthAccessTokenFilePath, File.GetAttributes(_twitchOAuthAccessTokenFilePath) | FileAttributes.Hidden);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new PlatformNotSupportedException($"Your platform '{Environment.OSVersion.Platform}' is not supported. Please report this issue here: https://www.github.com/zion-networks/BetterRaid/issues");
|
||||
break;
|
||||
case PlatformID.MacOSX:
|
||||
File.SetAttributes(TwitchOAuthAccessTokenFilePath, File.GetAttributes(TwitchOAuthAccessTokenFilePath) | FileAttributes.Hidden);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
BindingPlugins.DataValidators.RemoveAt(0);
|
||||
|
||||
var vm = _provider?.GetRequiredService<MainWindowViewModel>();
|
||||
|
||||
switch (ApplicationLifetime)
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
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
|
||||
};
|
||||
break;
|
||||
|
||||
case ISingleViewApplicationLifetime singleViewPlatform:
|
||||
singleViewPlatform.MainView = new MainWindow
|
||||
{
|
||||
DataContext = vm
|
||||
};
|
||||
break;
|
||||
// Line below is needed to remove Avalonia data validation.
|
||||
// Without this line you will get duplicate validations from both Avalonia and CT
|
||||
BindingPlugins.DataValidators.RemoveAt(0);
|
||||
desktop.MainWindow = new MainWindow
|
||||
{
|
||||
DataContext = new MainWindowViewModel(),
|
||||
};
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
|
@ -27,7 +27,6 @@
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.0" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="TwitchLib" Version="3.5.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -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", "{77D4100D-424A-4E36-BFF2-14A40F217605}"
|
||||
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
|
||||
{77D4100D-424A-4E36-BFF2-14A40F217605}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{77D4100D-424A-4E36-BFF2-14A40F217605}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{77D4100D-424A-4E36-BFF2-14A40F217605}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{77D4100D-424A-4E36-BFF2-14A40F217605}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
BIN
Build/BetterRaid-0.0.1-alpha.exe
Executable file
BIN
Build/BetterRaid-0.0.1-alpha.exe
Executable file
Binary file not shown.
BIN
Build/BetterRaid-0.0.1-alpha.x86_64
Executable file
BIN
Build/BetterRaid-0.0.1-alpha.x86_64
Executable file
Binary file not shown.
@ -1,22 +1,11 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BetterRaid.Extensions;
|
||||
|
||||
public static class DataContextExtensions
|
||||
{
|
||||
public static T? GetDataContextAs<T>(this T obj) where T : StyledElement
|
||||
public static T? GetDataContextAs<T>(this T obj) where T : Window
|
||||
{
|
||||
return obj.DataContext as T;
|
||||
}
|
||||
|
||||
public static void InjectDataContext<T>(this StyledElement e) where T : class
|
||||
{
|
||||
if (Application.Current is not App { Provider: not null } app)
|
||||
return;
|
||||
|
||||
var vm = app.Provider.GetRequiredService<T>();
|
||||
e.DataContext = vm;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace BetterRaid.Models;
|
||||
|
||||
@ -12,8 +12,6 @@ public class TwitchChannel : INotifyPropertyChanged
|
||||
private string? _displayName;
|
||||
private string? _thumbnailUrl;
|
||||
private string? _category;
|
||||
private string? _title;
|
||||
private DateTime? _lastRaided;
|
||||
|
||||
public string? BroadcasterId
|
||||
{
|
||||
@ -96,32 +94,6 @@ 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)
|
||||
{
|
||||
Name = channelName;
|
||||
|
@ -9,9 +9,8 @@ sealed class Program
|
||||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
[STAThread]
|
||||
public static void Main(string[] args) =>
|
||||
BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
|
@ -1,6 +0,0 @@
|
||||
namespace BetterRaid.Services;
|
||||
|
||||
public interface ITwitchDataService
|
||||
{
|
||||
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
namespace BetterRaid.Services;
|
||||
|
||||
public class TwitchDataService : ITwitchDataService
|
||||
{
|
||||
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -1,11 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace BetterRaid.ViewModels;
|
||||
|
||||
public class AddChannelWindowViewModel : ViewModelBase
|
||||
{
|
||||
public AddChannelWindowViewModel()
|
||||
{
|
||||
Console.WriteLine("[DEBUG] AddChannelWindowViewModel created");
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Avalonia.Controls;
|
||||
using BetterRaid.Extensions;
|
||||
using BetterRaid.Misc;
|
||||
using BetterRaid.Models;
|
||||
using BetterRaid.Services;
|
||||
using BetterRaid.Views;
|
||||
|
||||
namespace BetterRaid.ViewModels;
|
||||
@ -14,25 +11,13 @@ namespace BetterRaid.ViewModels;
|
||||
public partial class MainWindowViewModel : ViewModelBase
|
||||
{
|
||||
private string? _filter;
|
||||
private ObservableCollection<TwitchChannel> _channels = [];
|
||||
|
||||
private BetterRaidDatabase? _db;
|
||||
|
||||
public BetterRaidDatabase? Database
|
||||
{
|
||||
get => _db;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _db, value) && _db != null)
|
||||
{
|
||||
LoadChannelsFromDb();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<TwitchChannel> Channels
|
||||
{
|
||||
get => _channels;
|
||||
set => SetProperty(ref _channels, value);
|
||||
set => SetProperty(ref _db, value);
|
||||
}
|
||||
|
||||
public string? Filter
|
||||
@ -43,12 +28,6 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
|
||||
public bool IsLoggedIn => App.TwitchApi != null;
|
||||
|
||||
public MainWindowViewModel(ITwitchDataService t)
|
||||
{
|
||||
Console.WriteLine(t);
|
||||
Console.WriteLine("[DEBUG] MainWindowViewModel created");
|
||||
}
|
||||
|
||||
public void ExitApplication()
|
||||
{
|
||||
//TODO polish later
|
||||
@ -58,7 +37,6 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
public void ShowAboutWindow(Window owner)
|
||||
{
|
||||
var about = new AboutWindow();
|
||||
about.InjectDataContext<AboutWindowViewModel>();
|
||||
about.ShowDialog(owner);
|
||||
about.CenterToOwner();
|
||||
}
|
||||
@ -68,27 +46,10 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
Tools.StartOAuthLogin(App.TwitchOAuthUrl, OnTwitchLoginCallback, CancellationToken.None);
|
||||
}
|
||||
|
||||
private void OnTwitchLoginCallback()
|
||||
public void OnTwitchLoginCallback()
|
||||
{
|
||||
App.InitTwitchClient(overrideToken: true);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,5 +4,4 @@ namespace BetterRaid.ViewModels;
|
||||
|
||||
public class ViewModelBase : ObservableObject
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -2,10 +2,8 @@
|
||||
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"
|
||||
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"
|
||||
|
@ -1,11 +1,9 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:BetterRaid.ViewModels"
|
||||
xmlns:br="using:BetterRaid"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="450"
|
||||
Width="600"
|
||||
Height="800"
|
||||
x:Class="BetterRaid.Views.MainWindow"
|
||||
@ -110,81 +108,17 @@
|
||||
IsScrollInertiaEnabled="True" />
|
||||
</ScrollViewer.GestureRecognizers>
|
||||
|
||||
<ListBox ItemsSource="{Binding Channels}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid ColumnDefinitions="200,*"
|
||||
RowDefinitions="200">
|
||||
<Grid HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
x:Name="raidGrid">
|
||||
|
||||
<ai:AdvancedImage Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Source="{Binding ThumbnailUrl, TargetNullValue={x:Static br:App.ChannelPlaceholderImageUrl}}" />
|
||||
|
||||
<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.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
|
@ -15,8 +15,279 @@ namespace BetterRaid.Views;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private ObservableCollection<RaidButtonViewModel> _raidButtonVMs;
|
||||
private RaidButtonViewModel? _znButtonVm;
|
||||
private BackgroundWorker _autoUpdater;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
_raidButtonVMs = [];
|
||||
_znButtonVm = null;
|
||||
_autoUpdater = new();
|
||||
|
||||
DataContextChanged += OnDataContextChanged;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user