new_version_init

This commit is contained in:
2024-02-13 02:59:40 +01:00
Unverified
parent e36c1404ee
commit 91f9b645bd
352 changed files with 6777 additions and 8326 deletions

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<UserControl
x:Class="VDownload.GUI.Controls.TimeSpanControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.GUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Name="Control"
Loaded="Control_Loaded">
<StackPanel Orientation="Horizontal" Spacing="4">
<NumberBox x:Name="Hours" SpinButtonPlacementMode="Compact" Minimum="0" Value="0" ValueChanged="ValueChanged"/>
<TextBlock VerticalAlignment="Center" Padding="0,0,0,5" Text=":"/>
<NumberBox x:Name="Minutes" SpinButtonPlacementMode="Compact" Minimum="0" Value="0" Maximum="59" ValueChanged="ValueChanged"/>
<TextBlock VerticalAlignment="Center" Padding="0,0,0,5" Text=":"/>
<NumberBox x:Name="Seconds" SpinButtonPlacementMode="Compact" Minimum="0" Value="0" Maximum="59" ValueChanged="ValueChanged"/>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,122 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace VDownload.GUI.Controls
{
public sealed partial class TimeSpanControl : UserControl
{
#region PROPERTIES
public TimeSpan Value
{
get => (TimeSpan)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(TimeSpan), typeof(TimeSpanControl), new PropertyMetadata(TimeSpan.Zero, new PropertyChangedCallback(ValuePropertyChanged)));
public TimeSpan Maximum
{
get => (TimeSpan)GetValue(MaximumProperty);
set => SetValue(MaximumProperty, value);
}
public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(TimeSpan), typeof(TimeSpanControl), new PropertyMetadata(TimeSpan.MaxValue, new PropertyChangedCallback(RangePropertyChanged)));
public TimeSpan Minimum
{
get => (TimeSpan)GetValue(MinimumProperty);
set => SetValue(MinimumProperty, value);
}
public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum", typeof(TimeSpan), typeof(TimeSpanControl), new PropertyMetadata(TimeSpan.Zero, new PropertyChangedCallback(RangePropertyChanged)));
#endregion
#region CONSTRUCTORS
public TimeSpanControl()
{
this.InitializeComponent();
}
#endregion
#region PRIVATE METHODS
private void UpdateOnChanges()
{
if (this.IsLoaded)
{
TimeSpan hoursTimeSpan = TimeSpan.FromHours(Hours.Value);
TimeSpan minutesTimeSpan = TimeSpan.FromMinutes(Minutes.Value);
TimeSpan secondsTimeSpan = TimeSpan.FromSeconds(Seconds.Value);
TimeSpan value = secondsTimeSpan + minutesTimeSpan + hoursTimeSpan;
if (value >= Maximum)
{
Hours.Value = Math.Floor(Maximum.TotalHours);
Minutes.Value = Maximum.Minutes;
Seconds.Value = Maximum.Seconds;
}
else if (value <= Minimum)
{
Hours.Value = Math.Floor(Minimum.TotalHours);
Minutes.Value = Minimum.Minutes;
Seconds.Value = Minimum.Seconds;
}
Value = value;
}
}
private void UpdateOnValueChange()
{
if (this.IsLoaded)
{
TimeSpan value = Value;
if (value > Maximum)
{
value = Maximum;
}
else if (value < Minimum)
{
value = Minimum;
}
Hours.Value = Math.Floor(value.TotalHours);
Minutes.Value = value.Minutes;
Seconds.Value = value.Seconds;
}
}
#endregion
#region EVENT HANDLERS
private void ValueChanged(NumberBox sender, NumberBoxValueChangedEventArgs args) => UpdateOnChanges();
private static void ValuePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) => ((TimeSpanControl)obj).UpdateOnValueChange();
private static void RangePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) => ((TimeSpanControl)obj).UpdateOnChanges();
private void Control_Loaded(object sender, RoutedEventArgs e) => UpdateOnValueChange();
#endregion
}
}

View File

@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>VDownload.GUI.Controls</RootNamespace>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<UseRidGraph>true</UseRidGraph>
</PropertyGroup>
<ItemGroup>
<None Remove="TimeSpanControl.xaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
</ItemGroup>
<ItemGroup>
<CustomAdditionalCompileInputs Remove="TimeSpanControl.xaml" />
</ItemGroup>
<ItemGroup>
<Resource Remove="TimeSpanControl.xaml" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,44 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Converters
{
public class BooleanToGridLengthConverter : IValueConverter
{
#region METHODS
public object Convert(object value, Type targetType, object parameter, string language)
{
GridLength notVisibleLength = new GridLength(0);
if (value is bool visible)
{
GridLength visibleLength = new GridLength(1, GridUnitType.Star);
if (parameter is string width)
{
if (width.ToLower() == "auto")
{
visibleLength = GridLength.Auto;
}
else if (int.TryParse(width, out int result))
{
visibleLength = new GridLength(result);
}
}
return visible ? visibleLength : notVisibleLength;
}
return notVisibleLength;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@@ -0,0 +1,33 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Converters
{
public class BooleanToGridLengthFillConverter : IValueConverter
{
#region METHODS
public object Convert(object value, Type targetType, object parameter, string language)
{
GridLength falseLength = GridLength.Auto;
if (value is bool boolean)
{
GridLength trueLength = new GridLength(1, GridUnitType.Star);
return boolean ? trueLength : falseLength;
}
return falseLength;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@@ -0,0 +1,33 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Converters
{
public class BooleanToGridLengthFillReversedConverter : IValueConverter
{
#region METHODS
public object Convert(object value, Type targetType, object parameter, string language)
{
GridLength falseLength = GridLength.Auto;
if (value is bool boolean)
{
GridLength trueLength = new GridLength(1, GridUnitType.Star);
return boolean ? falseLength : trueLength;
}
return falseLength;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@@ -0,0 +1,44 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Converters
{
public class BooleanToGridLengthReversedConverter : IValueConverter
{
#region METHODS
public object Convert(object value, Type targetType, object parameter, string language)
{
GridLength visibleLength = new GridLength(1, GridUnitType.Star);
if (value is bool visible)
{
GridLength notVisibleLength = new GridLength(0);
if (parameter is string width)
{
if (width.ToLower() == "auto")
{
visibleLength = GridLength.Auto;
}
else if (int.TryParse(width, out int result))
{
visibleLength = new GridLength(result);
}
}
return visible ? notVisibleLength : visibleLength;
}
return visibleLength;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@@ -0,0 +1,30 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Converters
{
public class BooleanToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool boolean)
{
return boolean.ToString();
}
return bool.FalseString;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is string str)
{
return str == bool.TrueString;
}
return false;
}
}
}

View File

@@ -0,0 +1,35 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Converters
{
public class BooleanToVisibilityConverter : IValueConverter
{
#region METHODS
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool isVisible)
{
return isVisible ? Visibility.Visible : Visibility.Collapsed;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is Visibility visibility)
{
return visibility == Visibility.Visible;
}
return false;
}
#endregion
}
}

View File

@@ -0,0 +1,29 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Converters
{
public class EnumToDescriptionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is Enum enumValue)
{
return enumValue.GetType().GetMember(enumValue.ToString()).FirstOrDefault()?.GetCustomAttribute<DescriptionAttribute>()?.Description;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,22 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Converters
{
public class EnumToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return (int)value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,26 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Converters
{
public class EnumToStringConverter : IValueConverter
{
#region METHODS
public object Convert(object value, Type targetType, object parameter, string language)
{
return value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@@ -0,0 +1,27 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Converters
{
public class ReverseBooleanConverter : IValueConverter
{
#region METHODS
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is bool boolean)
{
return !boolean;
}
return true;
}
public object ConvertBack(object value, Type targetType, object parameter, string language) => Convert(value, targetType, parameter, language);
#endregion
}
}

View File

@@ -0,0 +1,22 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Converters
{
public class StringToLowerConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return value.ToString().ToLower();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,30 @@
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Converters
{
public class StringToUriConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is string str)
{
return new Uri(str, UriKind.RelativeOrAbsolute);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is Uri uri)
{
return uri.OriginalString;
}
return null;
}
}
}

View File

@@ -0,0 +1,30 @@
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Converters
{
public class StringToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is not string str || string.IsNullOrWhiteSpace(str))
{
return Visibility.Collapsed;
}
else
{
return Visibility.Visible;
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>VDownload.GUI.Converters</RootNamespace>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<UseRidGraph>true</UseRidGraph>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services\VDownload.Services.csproj" />
<ProjectReference Include="..\VDownload.GUI.ViewModels\VDownload.GUI.ViewModels.csproj" />
<ProjectReference Include="..\VDownload.GUI.Views\VDownload.GUI.Views.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,65 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.GUI.ViewModels;
using VDownload.GUI.Views;
using VDownload.Services;
namespace VDownload.GUI.Converters
{
public class ViewModelToViewConverter : IValueConverter
{
#region FIELDS
private readonly Dictionary<Type, Type> _viewModelViewBinding = new Dictionary<Type, Type>
{
{ typeof(HomeViewModel), typeof(HomeView) },
{ typeof(SettingsViewModel), typeof(SettingsView) },
{ typeof(AuthenticationViewModel), typeof(AuthenticationView) }
};
private readonly Dictionary<Type, Type> _viewViewModelBinding = new Dictionary<Type, Type>
{
{ typeof(HomeView), typeof(HomeViewModel) },
{ typeof(SettingsView), typeof(SettingsViewModel) },
{ typeof(AuthenticationView), typeof(AuthenticationViewModel) }
};
#endregion
#region METHODS
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is null)
{
return null;
}
if (value is Type type && _viewModelViewBinding.ContainsKey(type))
{
return ServiceProvider.Instance.GetService(_viewModelViewBinding[type]);
}
if (_viewModelViewBinding.ContainsKey(value.GetType()))
{
return ServiceProvider.Instance.GetService(_viewModelViewBinding[value.GetType()]);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (_viewViewModelBinding.ContainsKey(value.GetType()))
{
return _viewViewModelBinding[value.GetType()];
}
return null;
}
#endregion
}
}

View File

@@ -0,0 +1,91 @@
using Microsoft.UI.Xaml;
using Microsoft.Xaml.Interactivity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace VDownload.GUI.Customs.Behaviors
{
public class EventToCommandBehavior : Behavior<FrameworkElement>
{
#region FIELDS
private Delegate _handler;
private EventInfo _oldEvent;
#endregion
#region PROPERTIES
public string Event { get { return (string)GetValue(EventProperty); } set { SetValue(EventProperty, value); } }
public static readonly DependencyProperty EventProperty = DependencyProperty.Register("Event", typeof(string), typeof(EventToCommandBehavior), new PropertyMetadata(null, OnEventChanged));
public ICommand Command { get { return (ICommand)GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } }
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommandBehavior), new PropertyMetadata(null));
public bool PassArguments { get { return (bool)GetValue(PassArgumentsProperty); } set { SetValue(PassArgumentsProperty, value); } }
public static readonly DependencyProperty PassArgumentsProperty = DependencyProperty.Register("PassArguments", typeof(bool), typeof(EventToCommandBehavior), new PropertyMetadata(false));
#endregion
#region PRIVATE METHODS
private static void OnEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
EventToCommandBehavior beh = (EventToCommandBehavior)d;
if (beh.AssociatedObject != null)
{
beh.AttachHandler((string)e.NewValue);
}
}
protected override void OnAttached()
{
AttachHandler(this.Event);
}
private void AttachHandler(string eventName)
{
if (_oldEvent != null)
{
_oldEvent.RemoveEventHandler(this.AssociatedObject, _handler);
}
if (!string.IsNullOrEmpty(eventName))
{
EventInfo ei = this.AssociatedObject.GetType().GetEvent(eventName);
if (ei != null)
{
MethodInfo mi = this.GetType().GetMethod("ExecuteCommand", BindingFlags.Instance | BindingFlags.NonPublic);
_handler = Delegate.CreateDelegate(ei.EventHandlerType, this, mi);
ei.AddEventHandler(this.AssociatedObject, _handler);
_oldEvent = ei;
}
else
{
throw new ArgumentException(string.Format("The event '{0}' was not found on type '{1}'", eventName, this.AssociatedObject.GetType().Name));
}
}
}
private void ExecuteCommand(object sender, object e)
{
object parameter = this.PassArguments ? e : null;
if (this.Command != null && this.Command.CanExecute(parameter))
{
this.Command.Execute(parameter);
}
}
#endregion
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Customs.Models
{
public class NavigationViewItem
{
#region PROPERTIES
public required string Name { get; init; }
public required string IconSource { get; init; }
public required Type ViewModel { get; init; }
#endregion
}
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>VDownload.GUI.Customs</RootNamespace>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<UseRidGraph>true</UseRidGraph>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Services.Dialog
{
public enum DialogResult
{
Primary,
Secondary,
Cancelled
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Services.Dialog
{
public enum DialogResultOkCancel
{
Ok,
Cancel
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Services.Dialog
{
public enum DialogResultYesNo
{
Yes,
No
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Services.Dialog
{
public enum DialogResultYesNoCancel
{
Yes,
No,
Cancelled
}
}

View File

@@ -0,0 +1,121 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Services.Dialog
{
public interface IDialogService
{
#region PROPERTIES
XamlRoot DefaultRoot { get; set; }
#endregion
#region METHODS
Task ShowClose(string title, string message);
Task<DialogResult> ShowDouble(string title, string message, string primaryButtonText, string secondaryButtonText);
Task ShowOk(string title, string message);
Task<DialogResultOkCancel> ShowOkCancel(string title, string message);
Task ShowSingle(string title, string message, string buttonText);
Task<DialogResult> ShowTriple(string title, string message, string primaryButtonText, string secondaryButtonText, string cancelButtonText);
Task<DialogResultYesNo> ShowYesNo(string title, string message);
Task<DialogResultYesNoCancel> ShowYesNoCancel(string title, string message);
#endregion
}
public class DialogService : IDialogService
{
#region PROPERTIES
public XamlRoot DefaultRoot { get; set; }
#endregion
#region PUBLIC METHODS
public async Task ShowOk(string title, string message) => await ShowSingle(title, message, "OK");
public async Task ShowClose(string title, string message) => await ShowSingle(title, message, "Close");
public async Task ShowSingle(string title, string message, string buttonText)
{
ContentDialog contentDialog = BuildDialog(title, message);
contentDialog.CloseButtonText = buttonText;
await ShowDialog(contentDialog);
}
public async Task<DialogResultOkCancel> ShowOkCancel(string title, string message) => await ShowDouble(title, message, "OK", "Cancel") switch
{
DialogResult.Primary => DialogResultOkCancel.Ok,
_ => DialogResultOkCancel.Cancel
};
public async Task<DialogResultYesNo> ShowYesNo(string title, string message) => await ShowDouble(title, message, "Yes", "No") switch
{
DialogResult.Primary => DialogResultYesNo.Yes,
_ => DialogResultYesNo.No
};
public async Task<DialogResult> ShowDouble(string title, string message, string primaryButtonText, string secondaryButtonText)
{
ContentDialog contentDialog = BuildDialog(title, message);
contentDialog.PrimaryButtonText = primaryButtonText;
contentDialog.SecondaryButtonText = secondaryButtonText;
return await ShowDialog(contentDialog);
}
public async Task<DialogResultYesNoCancel> ShowYesNoCancel(string title, string message) => await ShowTriple(title, message, "Yes", "No", "Cancel") switch
{
DialogResult.Primary => DialogResultYesNoCancel.Yes,
DialogResult.Secondary => DialogResultYesNoCancel.Yes,
_ => DialogResultYesNoCancel.Cancelled
};
public async Task<DialogResult> ShowTriple(string title, string message, string primaryButtonText, string secondaryButtonText, string cancelButtonText)
{
ContentDialog contentDialog = BuildDialog(title, message);
contentDialog.PrimaryButtonText = primaryButtonText;
contentDialog.SecondaryButtonText = secondaryButtonText;
contentDialog.CloseButtonText = cancelButtonText;
return await ShowDialog(contentDialog);
}
#endregion
#region PRIVATE METHODS
private ContentDialog BuildDialog(string title, string message)
{
return new ContentDialog()
{
Title = title,
Content = message,
XamlRoot = DefaultRoot
};
}
private async Task<DialogResult> ShowDialog(ContentDialog dialog)
{
ContentDialogResult result = await dialog.ShowAsync();
return result switch
{
ContentDialogResult.Primary => DialogResult.Primary,
ContentDialogResult.Secondary => DialogResult.Secondary,
_ => DialogResult.Cancelled
}; ;
}
#endregion
}
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>VDownload.GUI.Services.Dialog</RootNamespace>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<UseRidGraph>true</UseRidGraph>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,59 @@
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Services.ResourceDictionaries
{
public interface IImagesResourceDictionary
{
// LOGO
string Logo { get; }
// SOURCES
string SourcesTwitch { get; }
// NAVIGATION VIEW
string NavigationViewAuthentication { get; }
string NavigationViewHome { get; }
}
public class ImagesResourceDictionary : IImagesResourceDictionary
{
#region PROPERTIES
// LOGO
public string Logo { get; private set; }
// SOURCES
public string SourcesTwitch { get; private set; }
// NAVIGATION VIEW
public string NavigationViewAuthentication { get; private set; }
public string NavigationViewHome { get; private set; }
#endregion
#region CONSTRUCTORS
public ImagesResourceDictionary()
{
Logo = (string)Application.Current.Resources["ImageLogo"];
SourcesTwitch = (string)Application.Current.Resources["ImageSourcesTwitch"];
NavigationViewAuthentication = (string)Application.Current.Resources["ImageNavigationViewAuthentication"];
NavigationViewHome = (string)Application.Current.Resources["ImageNavigationViewHome"];
}
#endregion
}
}

View File

@@ -0,0 +1,64 @@
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Services.ResourceDictionaries
{
public interface IResourceDictionariesServices
{
#region PROPERTIES
IImagesResourceDictionary Images { get; }
#endregion
#region METHODS
T Get<T>(string key);
#endregion
}
public class ResourceDictionariesServices : IResourceDictionariesServices
{
#region PROPERTIES
public IImagesResourceDictionary Images { get; private set; }
#endregion
#region CONSTRUCTORS
public ResourceDictionariesServices(IImagesResourceDictionary imagesResourceDictionary)
{
Images = imagesResourceDictionary;
}
#endregion
#region PUBLIC METHODS
public T Get<T>(string key)
{
Application.Current.Resources.TryGetValue(key, out object value);
if (value is not null && value is T cast)
{
return cast;
}
throw new KeyNotFoundException();
}
#endregion
}
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>VDownload.GUI.Services.ResourceDictionaries</RootNamespace>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<UseRidGraph>true</UseRidGraph>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Services.StoragePicker
{
public class FileSavePickerFileTypeChoice
{
#region PROPERTIES
public string Description { get; private set; }
public IEnumerable<string> Extensions { get; private set; }
#endregion
#region CONSTRUCTORS
public FileSavePickerFileTypeChoice(string description, string[] extensions)
{
Description = description;
Extensions = extensions.Select(x => x.StartsWith('.') ? x : $".{x}");
}
#endregion
}
}

View File

@@ -0,0 +1,169 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Pickers;
using WinRT.Interop;
namespace VDownload.GUI.Services.StoragePicker
{
public interface IStoragePickerService
{
#region PROPERTIES
Window DefaultRoot { get; set; }
#endregion
#region METHODS
Task<string?> OpenDirectory();
Task<string?> OpenDirectory(StoragePickerStartLocation startLocation);
Task<IEnumerable<string>> OpenMultipleFiles();
Task<IEnumerable<string>> OpenMultipleFiles(StoragePickerStartLocation startLocation);
Task<IEnumerable<string>> OpenMultipleFiles(string[] fileTypes);
Task<IEnumerable<string>> OpenMultipleFiles(string[] fileTypes, StoragePickerStartLocation startLocation);
Task<string?> OpenSingleFile();
Task<string?> OpenSingleFile(StoragePickerStartLocation startLocation);
Task<string?> OpenSingleFile(string[] fileTypes);
Task<string?> OpenSingleFile(string[] fileTypes, StoragePickerStartLocation startLocation);
Task<string?> SaveFile(FileSavePickerFileTypeChoice[] fileTypes, string defaultFileType);
Task<string?> SaveFile(FileSavePickerFileTypeChoice[] fileTypes, string defaultFileType, StoragePickerStartLocation startLocation);
#endregion
}
public class StoragePickerService : IStoragePickerService
{
#region PROPERTIES
public Window DefaultRoot { get; set; }
#endregion
#region PUBLIC METHODS
public async Task<string?> OpenDirectory() => await OpenDirectory(StoragePickerStartLocation.Unspecified);
public async Task<string?> OpenDirectory(StoragePickerStartLocation startLocation)
{
FolderPicker picker = new FolderPicker();
InitializePicker(picker);
ConfigureFolderPicker(picker, startLocation);
StorageFolder directory = await picker.PickSingleFolderAsync();
return directory?.Path;
}
public async Task<string?> OpenSingleFile() => await OpenSingleFile(["*"], StoragePickerStartLocation.Unspecified);
public async Task<string?> OpenSingleFile(string[] fileTypes) => await OpenSingleFile(fileTypes, StoragePickerStartLocation.Unspecified);
public async Task<string?> OpenSingleFile(StoragePickerStartLocation startLocation) => await OpenSingleFile(["*"], startLocation);
public async Task<string?> OpenSingleFile(string[] fileTypes, StoragePickerStartLocation startLocation)
{
FileOpenPicker picker = new FileOpenPicker();
InitializePicker(picker);
ConfigureFileOpenPicker(picker, fileTypes, startLocation);
StorageFile storageFile = await picker.PickSingleFileAsync();
return storageFile?.Path;
}
public async Task<IEnumerable<string>> OpenMultipleFiles() => await OpenMultipleFiles(["*"], StoragePickerStartLocation.Unspecified);
public async Task<IEnumerable<string>> OpenMultipleFiles(string[] fileTypes) => await OpenMultipleFiles(fileTypes, StoragePickerStartLocation.Unspecified);
public async Task<IEnumerable<string>> OpenMultipleFiles(StoragePickerStartLocation startLocation) => await OpenMultipleFiles(["*"], startLocation);
public async Task<IEnumerable<string>> OpenMultipleFiles(string[] fileTypes, StoragePickerStartLocation startLocation)
{
FileOpenPicker picker = new FileOpenPicker();
InitializePicker(picker);
ConfigureFileOpenPicker(picker, fileTypes, startLocation);
IEnumerable<StorageFile> list = await picker.PickMultipleFilesAsync();
return list.Select(x => x.Path);
}
public async Task<string?> SaveFile(FileSavePickerFileTypeChoice[] fileTypes, string defaultFileType) => await SaveFile(fileTypes, defaultFileType, StoragePickerStartLocation.Unspecified);
public async Task<string?> SaveFile(FileSavePickerFileTypeChoice[] fileTypes, string defaultFileType, StoragePickerStartLocation startLocation)
{
FileSavePicker picker = new FileSavePicker();
InitializePicker(picker);
ConfigureFileSavePicker(picker, fileTypes, defaultFileType, startLocation);
StorageFile file = await picker.PickSaveFileAsync();
return file?.Path;
}
#endregion
#region PRIVATE METHODS
protected void InitializePicker(object picker)
{
var hwnd = WindowNative.GetWindowHandle(DefaultRoot);
InitializeWithWindow.Initialize(picker, hwnd);
}
protected void ConfigureFolderPicker(FolderPicker picker, StoragePickerStartLocation startLocation)
{
if (startLocation != StoragePickerStartLocation.Unspecified)
{
picker.SuggestedStartLocation = (PickerLocationId)startLocation;
}
}
protected void ConfigureFileOpenPicker(FileOpenPicker picker, string[] fileTypes, StoragePickerStartLocation startLocation)
{
foreach (string fileType in fileTypes)
{
picker.FileTypeFilter.Add(fileType);
}
if (startLocation != StoragePickerStartLocation.Unspecified)
{
picker.SuggestedStartLocation = (PickerLocationId)startLocation;
}
}
protected void ConfigureFileSavePicker(FileSavePicker picker, FileSavePickerFileTypeChoice[] fileTypes, string defaultFileType, StoragePickerStartLocation startLocation)
{
if (startLocation != StoragePickerStartLocation.Unspecified)
{
picker.SuggestedStartLocation = (PickerLocationId)startLocation;
}
foreach (FileSavePickerFileTypeChoice fileType in fileTypes)
{
picker.FileTypeChoices.Add(fileType.Description, fileType.Extensions.ToList());
}
if (!defaultFileType.StartsWith('.'))
{
defaultFileType = $".{defaultFileType}";
}
if (!fileTypes.Any(x => x.Extensions.Contains(defaultFileType)))
{
picker.FileTypeChoices.Add("Default", [defaultFileType]);
}
picker.DefaultFileExtension = defaultFileType;
}
#endregion
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Services.StoragePicker
{
public enum StoragePickerStartLocation
{
Documents = 0,
Computer = 1,
Desktop = 2,
Downloads = 3,
Music = 5,
Pictures = 6,
Videos = 7,
Unspecified = 999
}
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>VDownload.GUI.Services.StoragePicker</RootNamespace>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<UseRidGraph>true</UseRidGraph>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
</ItemGroup>
<ItemGroup>
<Compile Update="StoragePickerService.cs">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Compile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>VDownload.GUI.Services.WebView</RootNamespace>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<UseRidGraph>true</UseRidGraph>
</PropertyGroup>
<ItemGroup>
<None Remove="WebViewWindow.xaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
</ItemGroup>
<ItemGroup>
<Page Update="WebViewWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,29 @@
using Microsoft.Web.WebView2.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.Services.WebView
{
public interface IWebViewService
{
Task<string> Show(Uri url, Predicate<string> closePredicate, string name);
}
public class WebViewService : IWebViewService
{
#region METHODS
public async Task<string> Show(Uri url, Predicate<string> closePredicate, string name)
{
WebViewWindow window = new WebViewWindow(name);
return await window.Show(url, closePredicate);
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="VDownload.GUI.Services.WebView.WebViewWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.GUI.Services.WebView"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Closed="Window_Closed">
<WebView2 x:Name="WebView" NavigationCompleted="WebView_NavigationCompleted"/>
</Window>

View File

@@ -0,0 +1,89 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Microsoft.Web.WebView2.Core;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Foundation.Collections;
namespace VDownload.GUI.Services.WebView
{
public sealed partial class WebViewWindow : Window
{
#region FIEDLS
private readonly Predicate<string> _defaultClosePredicate = args => false;
private bool _isOpened;
private Predicate<string> _closePredicate;
#endregion
#region CONSTRUCTORS
public WebViewWindow(string name)
{
this.InitializeComponent();
this.Title = name;
_isOpened = false;
_closePredicate = _defaultClosePredicate;
}
#endregion
#region PUBLIC METHODS
internal async Task<string> Show(Uri url, Predicate<string> closePredicate)
{
this.WebView.Source = url;
_closePredicate = closePredicate;
this.Activate();
_isOpened = true;
while (_isOpened)
{
await Task.Delay(10);
}
_closePredicate = _defaultClosePredicate;
return this.WebView.Source.ToString();
}
#endregion
#region EVENT HANDLER
private void WebView_NavigationCompleted(WebView2 sender, CoreWebView2NavigationCompletedEventArgs args)
{
if (_closePredicate.Invoke(this.WebView.Source.ToString()))
{
this.Close();
}
}
private void Window_Closed(object sender, WindowEventArgs args)
{
_isOpened = false;
}
#endregion
}
}

View File

@@ -0,0 +1,147 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using VDownload.GUI.Services.Dialog;
using VDownload.GUI.Services.WebView;
using VDownload.Sources.Twitch;
using VDownload.Sources.Twitch.Authentication;
namespace VDownload.GUI.ViewModels
{
public partial class AuthenticationViewModel : ObservableObject
{
#region ENUMS
public enum AuthenticationButton
{
SignIn,
SignOut,
Loading
}
#endregion
#region SERVICES
private IDialogService _dialogService;
private IWebViewService _webViewService;
private ITwitchAuthenticationService _twitchAuthenticationService;
#endregion
#region PROPERTIES
[ObservableProperty]
private string _twitchDescription;
[ObservableProperty]
private AuthenticationButton _twitchButtonState;
#endregion
#region CONSTRUCTORS
public AuthenticationViewModel(IDialogService dialogService, IWebViewService webViewService, ITwitchAuthenticationService twitchAuthenticationService)
{
_dialogService = dialogService;
_webViewService = webViewService;
_twitchAuthenticationService = twitchAuthenticationService;
TwitchButtonState = AuthenticationButton.Loading;
}
#endregion
#region PUBLIC METHODS
[RelayCommand]
public async Task TwitchAuthentication()
{
AuthenticationButton state = TwitchButtonState;
TwitchButtonState = AuthenticationButton.Loading;
if (state == AuthenticationButton.SignOut)
{
await _twitchAuthenticationService.DeleteToken();
}
else
{
string url = await _webViewService.Show(new Uri(_twitchAuthenticationService.AuthenticationPageUrl), _twitchAuthenticationService.AuthenticationPageClosePredicate, "Twitch authentication");
Match match = _twitchAuthenticationService.AuthenticationPageRedirectUrlRegex.Match(url);
if (match.Success)
{
string token = match.Groups[1].Value;
await _twitchAuthenticationService.SetToken(Encoding.UTF8.GetBytes(token));
}
else
{
await _dialogService.ShowOk("Twitch authentication error", "An error occured");
}
}
await TwitchAuthenticationRefresh();
}
[RelayCommand]
public async Task Navigation()
{
List<Task> refreshTasks = new List<Task>
{
TwitchAuthenticationRefresh()
};
await Task.WhenAll(refreshTasks);
}
#endregion
#region PRIVATE METHODS
private async Task TwitchAuthenticationRefresh()
{
TwitchButtonState = AuthenticationButton.Loading;
byte[]? token = await _twitchAuthenticationService.GetToken();
if (token is null)
{
TwitchDescription = "You are not authenticated. Please sign in";
TwitchButtonState = AuthenticationButton.SignIn;
}
else
{
TwitchValidationResult validationResult = await _twitchAuthenticationService.ValidateToken(token);
if (validationResult.Success)
{
TwitchDescription = $"Signed in as {validationResult.TokenData.Login}. Expiration date: {validationResult.TokenData.ExpirationDate:dd.MM.yyyy HH:mm}";
TwitchButtonState = AuthenticationButton.SignOut;
}
else
{
await _twitchAuthenticationService.DeleteToken();
TwitchDescription = "Token expired or is invalid. Please log in again";
TwitchButtonState = AuthenticationButton.SignIn;
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,315 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Common;
using VDownload.Common.Exceptions;
using VDownload.Common.Models;
using VDownload.GUI.Services.StoragePicker;
using VDownload.Services.Search;
using VDownload.Tasks;
namespace VDownload.GUI.ViewModels
{
public partial class HomeViewModel : ObservableObject
{
#region ENUMS
public enum OptionBarContentType
{
None,
VideoSearch,
PlaylistSearch
}
public enum MainContentType
{
Downloads,
Video
}
#endregion
#region SERVICES
private IStoragePickerService _storagePickerService;
private ISearchService _searchService;
private IDownloadTasksManager _tasksService;
#endregion
#region PROPERTIES
// MAIN
[ObservableProperty]
private MainContentType _mainContent;
// DOWNLOADS
public ObservableCollection<DownloadTask> Tasks => _tasksService.Tasks;
[ObservableProperty]
private bool _taskListIsEmpty;
// VIDEO
[ObservableProperty]
private DownloadTask _task;
// OPTION BAR
[ObservableProperty]
private OptionBarContentType _optionBarContent;
[ObservableProperty]
private string _optionBarMessage;
[ObservableProperty]
private bool _optionBarLoading;
[ObservableProperty]
private bool _optionBarVideoSearchButtonChecked;
[ObservableProperty]
private bool _optionBarPlaylistSearchButtonChecked;
[ObservableProperty]
private bool _optionBarSearchNotPending;
[ObservableProperty]
private string _optionBarVideoSearchTBValue;
[ObservableProperty]
private string _optionBarPlaylistSearchTBValue;
[ObservableProperty]
private int _optionBarPlaylistSearchNBValue;
#endregion
#region CONSTRUCTORS
public HomeViewModel(IStoragePickerService storagePickerService, ISearchService searchService, IDownloadTasksManager tasksService)
{
_storagePickerService = storagePickerService;
_searchService = searchService;
_tasksService = tasksService;
_tasksService.Tasks.CollectionChanged += Tasks_CollectionChanged;
_taskListIsEmpty = _tasksService.Tasks.Count == 0;
}
#endregion
#region COMMANDS
[RelayCommand]
public void Navigation()
{
MainContent = MainContentType.Downloads;
OptionBarContent = OptionBarContentType.None;
OptionBarMessage = null;
OptionBarVideoSearchButtonChecked = false;
OptionBarPlaylistSearchButtonChecked = false;
OptionBarSearchNotPending = true;
OptionBarVideoSearchTBValue = string.Empty;
OptionBarPlaylistSearchNBValue = 1; // TODO: load from settings
OptionBarPlaylistSearchTBValue = string.Empty;
}
// DOWNLOADS
[RelayCommand]
public void StartCancelTask(DownloadTask task)
{
DownloadTaskStatus[] idleStatuses =
[
DownloadTaskStatus.Idle,
DownloadTaskStatus.EndedUnsuccessfully,
DownloadTaskStatus.EndedSuccessfully,
DownloadTaskStatus.EndedCancelled
];
if (idleStatuses.Contains(task.Status))
{
task.Enqueue();
}
else
{
task.Cancel();
}
}
// VIDEO
[RelayCommand]
public async Task Browse()
{
string? newDirectory = await _storagePickerService.OpenDirectory();
if (newDirectory is not null)
{
Task.DirectoryPath = newDirectory;
}
}
[RelayCommand]
public async Task CreateTask()
{
string extension = Task.MediaType switch
{
MediaType.OnlyAudio => Task.AudioExtension.ToString(),
_ => Task.VideoExtension.ToString()
};
Task.Filename = string.Join("_", Task.Filename.Split(Path.GetInvalidFileNameChars()));
string file = $"{Task.Filename}.{extension}";
string path = Path.Combine(Task.DirectoryPath, file);
await File.WriteAllBytesAsync(path, [0x00]);
File.Delete(path);
_tasksService.AddTask(Task);
Navigation();
}
// OPTION BAR
[RelayCommand]
public void LoadFromSubscription()
{
MainContent = MainContentType.Downloads;
OptionBarContent = OptionBarContentType.None;
OptionBarVideoSearchButtonChecked = false;
OptionBarPlaylistSearchButtonChecked = false;
OptionBarSearchNotPending = false;
//TODO: Load videos
}
[RelayCommand]
public void VideoSearchShow()
{
MainContent = MainContentType.Downloads;
if (OptionBarContent != OptionBarContentType.VideoSearch)
{
OptionBarContent = OptionBarContentType.VideoSearch;
OptionBarPlaylistSearchButtonChecked = false;
}
else
{
OptionBarContent = OptionBarContentType.None;
}
}
[RelayCommand]
public void PlaylistSearchShow()
{
MainContent = MainContentType.Downloads;
if (OptionBarContent != OptionBarContentType.PlaylistSearch)
{
OptionBarContent = OptionBarContentType.PlaylistSearch;
OptionBarVideoSearchButtonChecked = false;
}
else
{
OptionBarContent = OptionBarContentType.None;
}
}
[RelayCommand]
public async Task VideoSearchStart()
{
OptionBarSearchNotPending = false;
OptionBarLoading = true;
OptionBarMessage = "Loading...";
Video video;
try
{
video = await _searchService.SearchVideo(OptionBarVideoSearchTBValue);
}
catch (MediaSearchException ex)
{
OptionBarLoading = false;
OptionBarMessage = ex.Message;
OptionBarSearchNotPending = true;
return;
}
Task = new DownloadTask(video);
MainContent = MainContentType.Video;
OptionBarSearchNotPending = true;
OptionBarLoading = false;
OptionBarMessage = null;
}
[RelayCommand]
public async Task PlaylistSearchStart()
{
OptionBarSearchNotPending = false;
OptionBarLoading = true;
OptionBarMessage = "Loading...";
Playlist playlist;
try
{
playlist = await _searchService.SearchPlaylist(OptionBarPlaylistSearchTBValue, OptionBarPlaylistSearchNBValue);
}
catch (MediaSearchException ex)
{
OptionBarLoading = false;
OptionBarMessage = ex.Message;
OptionBarSearchNotPending = true;
return;
}
OptionBarSearchNotPending = true;
OptionBarLoading = false;
OptionBarMessage = null;
}
[RelayCommand]
public void Download()
{
}
#endregion
#region EVENT HANDLERS
private void Tasks_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
TaskListIsEmpty = Tasks.Count == 0;
}
#endregion
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.GUI.ViewModels
{
public class SettingsViewModel
{
#region CONSTRUCTORS
public SettingsViewModel()
{
}
#endregion
}
}

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>VDownload.GUI.ViewModels</RootNamespace>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<UseRidGraph>true</UseRidGraph>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\VDownload.Common\VDownload.Common.csproj" />
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.Search\VDownload.Services.Search.csproj" />
<ProjectReference Include="..\..\VDownload.Sources\VDownload.Sources.Twitch\VDownload.Sources.Twitch.Authentication\VDownload.Sources.Twitch.Authentication.csproj" />
<ProjectReference Include="..\..\VDownload.Sources\VDownload.Sources.Twitch\VDownload.Sources.Twitch\VDownload.Sources.Twitch.csproj" />
<ProjectReference Include="..\..\VDownload.Tasks\VDownload.Tasks.csproj" />
<ProjectReference Include="..\VDownload.GUI.Services\VDownload.GUI.Services.Dialog\VDownload.GUI.Services.Dialog.csproj" />
<ProjectReference Include="..\VDownload.GUI.Services\VDownload.GUI.Services.StoragePicker\VDownload.GUI.Services.StoragePicker.csproj" />
<ProjectReference Include="..\VDownload.GUI.Services\VDownload.GUI.Services.WebView\VDownload.GUI.Services.WebView.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="VDownload.GUI.Views.AuthenticationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.GUI.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ctuc="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:ctc="using:CommunityToolkit.WinUI.Controls"
xmlns:ct="using:CommunityToolkit.WinUI"
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
mc:Ignorable="d"
Background="{ThemeResource ViewBackgroundColor}">
<i:Interaction.Behaviors>
<ic:EventTriggerBehavior EventName="Loaded">
<ic:InvokeCommandAction Command="{Binding NavigationCommand}"/>
</ic:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Grid Padding="20" RowSpacing="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
FontSize="28"
FontWeight="SemiBold"
Text="Authentication"/>
<StackPanel Grid.Row="1"
Spacing="10">
<ctc:SettingsCard Header="Twitch">
<i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource EnumToStringConverter}}"
ComparisonCondition="NotEqual"
Value="Loading">
<ic:ChangePropertyAction PropertyName="Description"
Value="{Binding TwitchDescription}"/>
<ic:ChangePropertyAction PropertyName="Content">
<ic:ChangePropertyAction.Value>
<Button Command="{Binding TwitchAuthenticationCommand}">
<i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource EnumToStringConverter}}"
ComparisonCondition="Equal"
Value="SignIn">
<ic:ChangePropertyAction PropertyName="Content"
Value="Sign in"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource EnumToStringConverter}}"
ComparisonCondition="Equal"
Value="SignOut">
<ic:ChangePropertyAction PropertyName="Content"
Value="Sign out"/>
</ic:DataTriggerBehavior>
</i:Interaction.Behaviors>
</Button>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource EnumToStringConverter}}"
ComparisonCondition="Equal"
Value="Loading">
<ic:ChangePropertyAction PropertyName="Description"
Value="Loading..."/>
<ic:ChangePropertyAction PropertyName="Content">
<ic:ChangePropertyAction.Value>
<ProgressRing Width="20"
Height="20"/>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
</i:Interaction.Behaviors>
<ctc:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{StaticResource ImageSourcesTwitch}"/>
</ctc:SettingsCard.HeaderIcon>
</ctc:SettingsCard>
</StackPanel>
</Grid>
</Page>

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using VDownload.GUI.ViewModels;
namespace VDownload.GUI.Views
{
public sealed partial class AuthenticationView : Page
{
public AuthenticationView(AuthenticationViewModel viewModel)
{
this.InitializeComponent();
this.DataContext = viewModel;
}
}
}

View File

@@ -0,0 +1,578 @@
<Page
x:Class="VDownload.GUI.Views.HomeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.GUI.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
xmlns:cc="using:VDownload.GUI.Controls"
xmlns:cmn="using:VDownload.Common"
xmlns:ct="using:CommunityToolkit.WinUI"
xmlns:ctc="using:CommunityToolkit.WinUI.Controls"
xmlns:ctuc="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:ctb="using:CommunityToolkit.WinUI.Behaviors"
mc:Ignorable="d"
Background="Transparent"
x:Name="Root">
<i:Interaction.Behaviors>
<ic:EventTriggerBehavior EventName="Loaded">
<ic:InvokeCommandAction Command="{Binding NavigationCommand}"/>
</ic:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Grid RowSpacing="10"
Margin="10">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ctuc:SwitchPresenter Grid.Row="0"
Value="{Binding MainContent, Converter={StaticResource EnumToStringConverter}}"
CornerRadius="10">
<ctuc:Case Value="Downloads">
<ctuc:SwitchPresenter Value="{Binding TaskListIsEmpty, Converter={StaticResource BooleanToStringConverter}}">
<ctuc:Case Value="True">
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image Source="{StaticResource ImageDownloadsNoTasks}"
Width="100"/>
<TextBlock Text="Click Video/Playlist search button to add new tasks"
Foreground="{StaticResource GreyText}"/>
</StackPanel>
</ctuc:Case>
<ctuc:Case Value="False">
<ScrollViewer>
<ItemsControl ItemsSource="{Binding Tasks}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Background="{ThemeResource ViewBackgroundColor}"
CornerRadius="10"
Height="150">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0"
Source="{Binding Video.ThumbnailUrl}"
VerticalAlignment="Stretch"/>
<Grid Grid.Column="1"
Margin="10"
RowSpacing="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Grid.Row="0"
ColumnSpacing="10"
HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
FontWeight="SemiBold"
FontSize="18"
Text="{Binding Video.Title}"
TextTrimming="CharacterEllipsis"/>
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
FontWeight="Light"
FontSize="12"
Text="{Binding Video.Author}"/>
</Grid>
<Grid Grid.Row="1"
RowSpacing="10"
ColumnSpacing="10">
<Grid.Resources>
<x:Double x:Key="TextSize">12</x:Double>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Grid.Row="0"
Grid.Column="0"
Source="{ThemeResource ImageDownloadsQuality}"/>
<TextBlock Grid.Row="0"
Grid.Column="1"
FontSize="{StaticResource TextSize}"
VerticalAlignment="Center">
<Run Text="{Binding MediaType, Converter={StaticResource EnumToDescriptionConverter}}"/> (<Run Text="{Binding VideoStream.StreamIdentifier}"/>)
</TextBlock>
<Image Grid.Row="1"
Grid.Column="0"
Source="{ThemeResource ImageDownloadsTime}"/>
<StackPanel Grid.Row="1"
Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<TextBlock Text="{Binding DurationAfterTrim}"
FontSize="{StaticResource TextSize}"/>
<TextBlock Visibility="{Binding IsTrimmed, Converter={StaticResource BooleanToVisibilityConverter}}"
FontSize="{StaticResource TextSize}">
<Run Text=" "/>(<Run Text="{Binding TrimStart}"/> - <Run Text="{Binding TrimEnd}"/>)
</TextBlock>
</StackPanel>
<Image Grid.Row="2"
Grid.Column="0"
Source="{ThemeResource ImageDownloadsFile}"/>
<TextBlock Grid.Row="2"
Grid.Column="1"
FontSize="{StaticResource TextSize}"
VerticalAlignment="Center"
Text="{Binding FilePath}"/>
<Image Grid.Row="3"
Grid.Column="0">
<i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToStringConverter}}"
ComparisonCondition="Equal"
Value="Idle">
<ic:ChangePropertyAction PropertyName="Source"
Value="{ThemeResource ImageDownloadsIdle}"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToStringConverter}}"
ComparisonCondition="Equal"
Value="Queued">
<ic:ChangePropertyAction PropertyName="Source"
Value="{ThemeResource ImageDownloadsQueued}"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToStringConverter}}"
ComparisonCondition="Equal"
Value="Initializing">
<ic:ChangePropertyAction PropertyName="Source"
Value="{ThemeResource ImageDownloadsInitializing}"/>
</ic:DataTriggerBehavior>
</i:Interaction.Behaviors>
</Image>
<TextBlock Grid.Row="3"
Grid.Column="1"
FontSize="{StaticResource TextSize}"
VerticalAlignment="Center">
<i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToStringConverter}}"
ComparisonCondition="Equal"
Value="Idle">
<ic:ChangePropertyAction PropertyName="Text"
Value="Idle"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToStringConverter}}"
ComparisonCondition="Equal"
Value="Queued">
<ic:ChangePropertyAction PropertyName="Text"
Value="Queued"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToStringConverter}}"
ComparisonCondition="Equal"
Value="Initializing">
<ic:ChangePropertyAction PropertyName="Text"
Value="Initializing"/>
</ic:DataTriggerBehavior>
</i:Interaction.Behaviors>
</TextBlock>
</Grid>
</Grid>
<Grid Grid.Column="2"
Margin="0,0,5,0">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<AppBarButton Grid.Row="0"
Width="40"
Height="48">
<i:Interaction.Behaviors>
<ic:EventTriggerBehavior EventName="Click">
<ctb:NavigateToUriAction NavigateUri="{Binding Video.Url}"/>
</ic:EventTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Video.Source, Converter={StaticResource EnumToStringConverter}}"
ComparisonCondition="Equal"
Value="Twitch">
<ic:ChangePropertyAction PropertyName="Icon">
<ic:ChangePropertyAction.Value>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{StaticResource ImageSourcesTwitch}"/>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
</i:Interaction.Behaviors>
</AppBarButton>
<AppBarButton Grid.Row="1"
Width="40"
Height="48"
Command="{Binding ElementName=Root, Path=DataContext.StartCancelTaskCommand}"
CommandParameter="{Binding}">
<i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="LessThan"
Value="4">
<ic:ChangePropertyAction PropertyName="Icon">
<ic:ChangePropertyAction.Value>
<SymbolIcon Symbol="Download"/>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="GreaterThanOrEqual"
Value="4">
<ic:ChangePropertyAction PropertyName="Icon">
<ic:ChangePropertyAction.Value>
<SymbolIcon Symbol="Cancel"/>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
</i:Interaction.Behaviors>
</AppBarButton>
<AppBarButton Grid.Row="2"
Icon="Delete"
Width="40"
Height="48"/>
</Grid>
</Grid>
<ProgressBar Grid.Row="1"
Value="{Binding Progress}">
<i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="LessThan"
Value="5">
<ic:ChangePropertyAction PropertyName="Visibility" Value="Collapsed"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="GreaterThanOrEqual"
Value="5">
<ic:ChangePropertyAction PropertyName="Visibility" Value="Visible"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="LessThan"
Value="7">
<ic:ChangePropertyAction PropertyName="IsIndeterminate" Value="True"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="GreaterThanOrEqual"
Value="7">
<ic:ChangePropertyAction PropertyName="IsIndeterminate" Value="False"/>
</ic:DataTriggerBehavior>
</i:Interaction.Behaviors>
</ProgressBar>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</ctuc:Case>
</ctuc:SwitchPresenter>
</ctuc:Case>
<ctuc:Case Value="Video">
<Grid Padding="15"
RowSpacing="20"
Background="{ThemeResource ViewBackgroundColor}">
<Grid.RowDefinitions>
<RowDefinition Height="150"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0"
ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0"
CornerRadius="{ThemeResource ControlCornerRadius}">
<Image Source="{Binding Task.Video.ThumbnailUrl}"
VerticalAlignment="Stretch"/>
</Border>
<StackPanel Grid.Column="1"
Spacing="15">
<TextBlock Text="{Binding Task.Video.Title}"
FontWeight="Bold"
FontSize="20"
TextWrapping="WrapWholeWords"/>
<Grid ColumnSpacing="10"
RowSpacing="10">
<Grid.Resources>
<x:Double x:Key="IconSize">18</x:Double>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image Grid.Column="0"
Grid.Row="0"
Source="{ThemeResource ImageVideoAuthor}"
Width="{StaticResource IconSize}"/>
<TextBlock Grid.Column="1"
Grid.Row="0"
Text="{Binding Task.Video.Author}"/>
<Image Grid.Column="0"
Grid.Row="1"
Source="{ThemeResource ImageVideoDate}"
Width="{StaticResource IconSize}"/>
<TextBlock Grid.Column="1"
Grid.Row="1"
Text="{Binding Task.Video.PublishDate}"/>
<Image Grid.Column="0"
Grid.Row="2"
Source="{ThemeResource ImageVideoTime}"
Width="{StaticResource IconSize}"/>
<TextBlock Grid.Column="1"
Grid.Row="2"
Text="{Binding Task.Video.Duration}"/>
<Image Grid.Column="0"
Grid.Row="3"
Source="{ThemeResource ImageVideoView}"
Width="{StaticResource IconSize}"/>
<TextBlock Grid.Column="1"
Grid.Row="3"
Text="{Binding Task.Video.ViewCount}"/>
</Grid>
</StackPanel>
</Grid>
<ScrollViewer Grid.Row="1">
<StackPanel Spacing="20">
<StackPanel Spacing="5">
<TextBlock Text="Media options"
FontWeight="Bold"
FontSize="15"/>
<ctc:SettingsCard Header="Quality">
<ctc:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{ThemeResource ImageVideoQuality}"/>
</ctc:SettingsCard.HeaderIcon>
<ComboBox ItemsSource="{Binding Task.Video.Streams}"
SelectedItem="{Binding Task.VideoStream, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding StreamIdentifier}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ctc:SettingsCard>
<ctc:SettingsCard Header="Media type">
<ctc:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{ThemeResource ImageVideoMedia}"/>
</ctc:SettingsCard.HeaderIcon>
<ComboBox ItemsSource="{ct:EnumValues Type=cmn:MediaType}"
SelectedItem="{Binding Task.MediaType, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource EnumToDescriptionConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ctc:SettingsCard>
<ctc:SettingsExpander Header="Trim">
<ctc:SettingsExpander.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{ThemeResource ImageVideoTrim}"/>
</ctc:SettingsExpander.HeaderIcon>
<ctc:SettingsExpander.Items>
<ctc:SettingsCard Header="Start at">
<cc:TimeSpanControl Value="{Binding Task.TrimStart, Mode=TwoWay}"
Maximum="{Binding Task.TrimEnd, Mode=OneWay}"/>
</ctc:SettingsCard>
<ctc:SettingsCard Header="End at">
<cc:TimeSpanControl Minimum="{Binding Task.TrimStart, Mode=OneWay}"
Value="{Binding Task.TrimEnd, Mode=TwoWay}"
Maximum="{Binding Task.Video.Duration, Mode=OneWay}"/>
</ctc:SettingsCard>
</ctc:SettingsExpander.Items>
</ctc:SettingsExpander>
</StackPanel>
<StackPanel Spacing="5">
<TextBlock Text="File options"
FontWeight="Bold"
FontSize="15"/>
<ctc:SettingsCard Header="Directory"
Description="{Binding Task.DirectoryPath}">
<ctc:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False" UriSource="{ThemeResource ImageVideoDirectory}"/>
</ctc:SettingsCard.HeaderIcon>
<Button Content="Browse"
Command="{Binding BrowseCommand}"/>
</ctc:SettingsCard>
<ctc:SettingsCard Header="Filename">
<ctc:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False" UriSource="{ThemeResource ImageVideoFilename}"/>
</ctc:SettingsCard.HeaderIcon>
<TextBox Text="{Binding Task.Filename, Mode=TwoWay}"/>
</ctc:SettingsCard>
<ctc:SettingsCard Header="File type"
Description="If original video is not in selected type, it will be converted">
<ctc:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False" UriSource="{ThemeResource ImageVideoExtension}"/>
</ctc:SettingsCard.HeaderIcon>
<ComboBox>
<i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding Task.MediaType, Converter={StaticResource EnumToStringConverter}}"
ComparisonCondition="Equal"
Value="OnlyAudio">
<ic:ChangePropertyAction PropertyName="ItemsSource"
Value="{ct:EnumValues Type=cmn:AudioExtension}"/>
<ic:ChangePropertyAction PropertyName="SelectedItem"
Value="{Binding Task.AudioExtension}"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Task.MediaType, Converter={StaticResource EnumToStringConverter}}"
ComparisonCondition="NotEqual"
Value="OnlyAudio">
<ic:ChangePropertyAction PropertyName="ItemsSource"
Value="{ct:EnumValues Type=cmn:VideoExtension}"/>
<ic:ChangePropertyAction PropertyName="SelectedItem"
Value="{Binding Task.VideoExtension}"/>
</ic:DataTriggerBehavior>
</i:Interaction.Behaviors>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ctc:SettingsCard>
</StackPanel>
</StackPanel>
</ScrollViewer>
<Button Grid.Row="2"
HorizontalAlignment="Right"
Style="{StaticResource AccentButtonStyle}"
Content="Create download task"
Command="{Binding CreateTaskCommand}"/>
</Grid>
</ctuc:Case>
</ctuc:SwitchPresenter>
<Grid Grid.Row="1"
Background="{ThemeResource OptionBarBackgroundColor}"
CornerRadius="10">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ctuc:UniformGrid Grid.Column="0"
Rows="1"
Margin="15,0,0,0">
<ctuc:UniformGrid.RowDefinitions>
<RowDefinition/>
</ctuc:UniformGrid.RowDefinitions>
<ctuc:UniformGrid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</ctuc:UniformGrid.ColumnDefinitions>
<ctuc:SwitchPresenter Grid.Row="0"
VerticalAlignment="Stretch"
Margin="0,0,15,0"
Value="{Binding OptionBarContent, Converter={StaticResource EnumToStringConverter}}">
<i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding OptionBarContent, Converter={StaticResource EnumToStringConverter}}"
ComparisonCondition="Equal"
Value="None">
<ic:ChangePropertyAction PropertyName="Visibility"
Value="Collapsed"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding OptionBarContent, Converter={StaticResource EnumToStringConverter}}"
ComparisonCondition="NotEqual"
Value="None">
<ic:ChangePropertyAction PropertyName="Visibility"
Value="Visible"/>
</ic:DataTriggerBehavior>
</i:Interaction.Behaviors>
<ctuc:Case Value="VideoSearch">
<Grid ColumnSpacing="10"
VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
PlaceholderText="Video URL"
VerticalAlignment="Center"
Text="{Binding OptionBarVideoSearchTBValue, Mode=TwoWay}"/>
<Button Grid.Column="1"
Content="Search"
IsEnabled="{Binding OptionBarSearchNotPending}"
Command="{Binding VideoSearchStartCommand}"/>
</Grid>
</ctuc:Case>
<ctuc:Case Value="PlaylistSearch">
<Grid ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
VerticalAlignment="Center"
PlaceholderText="Playlist URL"
Text="{Binding OptionBarPlaylistSearchTBValue, Mode=TwoWay}"/>
<NumberBox Grid.Column="1"
VerticalAlignment="Center"
SpinButtonPlacementMode="Compact"
SmallChange="1"
LargeChange="10"
Value="{Binding OptionBarPlaylistSearchNBValue, Mode=TwoWay}"
Minimum="1"
ToolTipService.ToolTip="Number of videos to get from playlist"/>
<Button Grid.Column="2"
Content="Search"
IsEnabled="{Binding OptionBarSearchNotPending}"
Command="{Binding PlaylistSearchStartCommand}"/>
</Grid>
</ctuc:Case>
</ctuc:SwitchPresenter>
<StackPanel VerticalAlignment="Center"
Orientation="Horizontal">
<ProgressRing Width="20"
Height="20"
Margin="0,0,10,0"
Visibility="{Binding OptionBarLoading, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock Text="{Binding OptionBarMessage}"/>
</StackPanel>
</ctuc:UniformGrid>
<StackPanel Grid.Column="2"
Orientation="Horizontal">
<AppBarButton Width="150"
Label="Load from subscriptions"
Icon="Favorite"
IsEnabled="{Binding OptionBarSearchNotPending}"
Command="{Binding LoadFromSubscriptionCommand}"/>
<AppBarToggleButton Label="Video search"
Width="100"
Icon="Video"
IsEnabled="{Binding OptionBarSearchNotPending}"
IsChecked="{Binding OptionBarVideoSearchButtonChecked, Mode=TwoWay}"
Command="{Binding VideoSearchShowCommand}"/>
<AppBarToggleButton Label="Playlist search"
Width="100"
Icon="List"
IsEnabled="{Binding OptionBarSearchNotPending}"
IsChecked="{Binding OptionBarPlaylistSearchButtonChecked, Mode=TwoWay}"
Command="{Binding PlaylistSearchShowCommand}"/>
<AppBarSeparator/>
<AppBarButton Width="100"
Label="Download all"
Icon="Download"
Command="{Binding DownloadCommand}"/>
</StackPanel>
</Grid>
</Grid>
</Page>

View File

@@ -0,0 +1,31 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using VDownload.GUI.ViewModels;
using Windows.Foundation;
using Windows.Foundation.Collections;
namespace VDownload.GUI.Views
{
public sealed partial class HomeView : Page
{
#region CONSTRUCTORS
public HomeView(HomeViewModel viewModel)
{
this.InitializeComponent();
this.DataContext = viewModel;
}
#endregion
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="VDownload.GUI.Views.SettingsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.GUI.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<TextBlock Text="Settings"/>
</Grid>
</Page>

View File

@@ -0,0 +1,31 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using VDownload.GUI.ViewModels;
using Windows.Foundation;
using Windows.Foundation.Collections;
namespace VDownload.GUI.Views
{
public sealed partial class SettingsView : Page
{
#region CONSTRUCTORS
public SettingsView(SettingsViewModel viewModel)
{
this.InitializeComponent();
this.DataContext = viewModel;
}
#endregion
}
}

View File

@@ -0,0 +1,50 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>VDownload.GUI.Views</RootNamespace>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<UseRidGraph>true</UseRidGraph>
</PropertyGroup>
<ItemGroup>
<None Remove="AuthenticationView.xaml" />
<None Remove="HomeView.xaml" />
<None Remove="SettingsView.xaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\VDownload.GUI.Controls\VDownload.GUI.Controls.csproj" />
<ProjectReference Include="..\VDownload.GUI.Customs\VDownload.GUI.Customs.csproj" />
<ProjectReference Include="..\VDownload.GUI.ViewModels\VDownload.GUI.ViewModels.csproj" />
</ItemGroup>
<ItemGroup>
<Page Update="AuthenticationView.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="SettingsView.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="HomeView.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
</Project>