twitch vod downloading done

ffmpeg essentials

fix

Project reorganized

git lfs

ffmpeg removed

ffmpeg added
This commit is contained in:
2024-02-14 02:07:22 +01:00
Unverified
parent 91f9b645bd
commit e3ec5c3a48
264 changed files with 6239 additions and 4014 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.exe filter=lfs diff=lfs merge=lfs -text

View File

@@ -1,24 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Common.Models;
namespace VDownload.Common
{
public abstract class Playlist : ObservableObject, IEnumerable<Video>
{
public IEnumerator<Video> GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Common.Models;
namespace VDownload.Common.Services
{
public interface ISourceSearchService
{
Task<Video> SearchVideo(string url);
Task<Playlist> SearchPlaylist(string url, int maxVideoCount);
}
}

View File

@@ -1,58 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Common.Models
{
public abstract partial class Video : ObservableObject
{
#region PROPERTIES
[ObservableProperty]
protected string _title;
[ObservableProperty]
protected string _description;
[ObservableProperty]
protected string _author;
[ObservableProperty]
protected DateTime _publishDate;
[ObservableProperty]
protected TimeSpan _duration;
[ObservableProperty]
protected int _viewCount;
[ObservableProperty]
protected string? _thumbnailUrl;
[ObservableProperty]
protected ObservableCollection<VideoStream> _streams;
[ObservableProperty]
protected string _url;
[ObservableProperty]
protected Source _source;
#endregion
#region CONSTRUCTORS
protected Video()
{
_streams = new ObservableCollection<VideoStream>();
}
#endregion
}
}

View File

@@ -1,22 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Common
{
public abstract partial class VideoStream : ObservableObject
{
#region PROPERTIES
[ObservableProperty]
protected string _streamIdentifier;
[ObservableProperty]
private int _width;
#endregion
}
}

View File

@@ -0,0 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AuthenticationButtonSignIn.Value" xml:space="preserve">
<value>Sign in</value>
</data>
<data name="AuthenticationButtonSignOut.Value" xml:space="preserve">
<value>Sign out</value>
</data>
<data name="AuthenticationDescriptionLoading.Value" xml:space="preserve">
<value>Loading...</value>
</data>
<data name="Header.Text" xml:space="preserve">
<value>Authentication</value>
</data>
<data name="TwitchAuthenticationDescriptionAuthenticated" xml:space="preserve">
<value>Signed in as {0}. Expiration date: {1}</value>
</data>
<data name="TwitchAuthenticationDescriptionAuthenticationInvalid" xml:space="preserve">
<value>Token expired or is invalid. Please log in again</value>
</data>
<data name="TwitchAuthenticationDescriptionNotAuthenticated" xml:space="preserve">
<value>You are not authenticated. Please sign in</value>
</data>
<data name="TwitchAuthenticationDialogTitle" xml:space="preserve">
<value>Twitch authentication error</value>
</data>
<data name="TwitchAuthenticationWindowTitle" xml:space="preserve">
<value>Sign in to Twitch</value>
</data>
</root>

View File

@@ -117,7 +117,10 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Main_OptionBar_PlaylistSearchTB.PlaceholderText" xml:space="preserve"> <data name="AuthenticationNavigationViewItem" xml:space="preserve">
<value>Test</value> <value>Authentication</value>
</data>
<data name="HomeNavigationViewItem" xml:space="preserve">
<value>Home</value>
</data> </data>
</root> </root>

View File

@@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="MediaTypeOnlyAudio.Text" xml:space="preserve">
<value>Only audio</value>
</data>
<data name="MediaTypeOnlyVideo.Text" xml:space="preserve">
<value>Only video</value>
</data>
<data name="MediaTypeOriginal.Text" xml:space="preserve">
<value>Original</value>
</data>
<data name="StatusCancelled.Text" xml:space="preserve">
<value>Cancelled</value>
</data>
<data name="StatusDone.Text" xml:space="preserve">
<value>Done</value>
</data>
<data name="StatusDownloading.Text" xml:space="preserve">
<value>Downloading</value>
</data>
<data name="StatusError.Text" xml:space="preserve">
<value>Error</value>
</data>
<data name="StatusFinalizing.Text" xml:space="preserve">
<value>Finalizing</value>
</data>
<data name="StatusIdle.Text" xml:space="preserve">
<value>Idle</value>
</data>
<data name="StatusInitializing.Text" xml:space="preserve">
<value>Initializing</value>
</data>
<data name="StatusProcessing.Text" xml:space="preserve">
<value>Processing</value>
</data>
<data name="StatusQueued.Text" xml:space="preserve">
<value>Queued</value>
</data>
</root>

View File

@@ -0,0 +1,171 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CreateAndStartButton.Content" xml:space="preserve">
<value>Create download task and start</value>
</data>
<data name="CreateButton.Content" xml:space="preserve">
<value>Create download task</value>
</data>
<data name="DirectorySettingsCard.Header" xml:space="preserve">
<value>Directory</value>
</data>
<data name="DirectorySettingsCardButton.Content" xml:space="preserve">
<value>Browse</value>
</data>
<data name="FilenameSettingsCard.Header" xml:space="preserve">
<value>Filename</value>
</data>
<data name="FileOptionsHeader.Text" xml:space="preserve">
<value>File options</value>
</data>
<data name="FileTypeSettingsCard.Description" xml:space="preserve">
<value>If original video is not in selected type, it will be converted</value>
</data>
<data name="FileTypeSettingsCard.Header" xml:space="preserve">
<value>File type</value>
</data>
<data name="MediaOptionsHeader.Text" xml:space="preserve">
<value>Media options</value>
</data>
<data name="MediaTypeOnlyAudio.Text" xml:space="preserve">
<value>Only audio</value>
</data>
<data name="MediaTypeOnlyVideo.Text" xml:space="preserve">
<value>Only video</value>
</data>
<data name="MediaTypeOriginal.Text" xml:space="preserve">
<value>Original</value>
</data>
<data name="MediaTypeSettingsCard.Header" xml:space="preserve">
<value>Media type</value>
</data>
<data name="QualitySettingsCard.Header" xml:space="preserve">
<value>Quality</value>
</data>
<data name="TrimEndSettingsCard.Header" xml:space="preserve">
<value>End at</value>
</data>
<data name="TrimSettingsGroup.Header" xml:space="preserve">
<value>Trim</value>
</data>
<data name="TrimStartSettingsCard.Header" xml:space="preserve">
<value>Start at</value>
</data>
</root>

View File

@@ -0,0 +1,160 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="OptionBarDownloadAll.Label" xml:space="preserve">
<value>Download all</value>
</data>
<data name="OptionBarDownloadAll.Width" xml:space="preserve">
<value>100</value>
</data>
<data name="OptionBarLoadSubscription.Label" xml:space="preserve">
<value>Load from subscriptions</value>
</data>
<data name="OptionBarLoadSubscription.Width" xml:space="preserve">
<value>150</value>
</data>
<data name="OptionBarMessageLoading" xml:space="preserve">
<value>Loading...</value>
</data>
<data name="OptionBarPlaylistSearch.Label" xml:space="preserve">
<value>Playlist search</value>
</data>
<data name="OptionBarPlaylistSearch.Width" xml:space="preserve">
<value>100</value>
</data>
<data name="OptionBarPlaylistSearchContentNumberBox.ToolTipService.ToolTip" xml:space="preserve">
<value>Number of videos to get from playlist
0 = Get all</value>
</data>
<data name="OptionBarPlaylistSearchContentTextBox.PlaceholderText" xml:space="preserve">
<value>Playlist URL</value>
</data>
<data name="OptionBarSearchButton.Content" xml:space="preserve">
<value>Search</value>
</data>
<data name="OptionBarVideoSearch.Label" xml:space="preserve">
<value>Video search</value>
</data>
<data name="OptionBarVideoSearch.Width" xml:space="preserve">
<value>100</value>
</data>
<data name="OptionBarVideoSearchContentTextBox.PlaceholderText" xml:space="preserve">
<value>Video URL</value>
</data>
</root>

View File

@@ -0,0 +1,135 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Author" xml:space="preserve">
<value>Author</value>
</data>
<data name="Message" xml:space="preserve">
<value>Message</value>
</data>
<data name="OnSuccessfulTitle" xml:space="preserve">
<value>Task ended successfully</value>
</data>
<data name="OnUnsuccessfulTitle" xml:space="preserve">
<value>Task ended unsuccessfully</value>
</data>
<data name="Title" xml:space="preserve">
<value>Title</value>
</data>
</root>

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework> <TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion> <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>VDownload.Tasks</RootNamespace> <RootNamespace>VDownload.Core.Strings</RootNamespace>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI> <UseWinUI>true</UseWinUI>
<UseRidGraph>true</UseRidGraph> <UseRidGraph>true</UseRidGraph>
@@ -14,6 +14,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\VDownload.Common\VDownload.Common.csproj" /> <PRIResource Update="Strings\en-US\BaseViewResources.resw">
<Generator></Generator>
</PRIResource>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,229 @@
using CommunityToolkit.Mvvm.ComponentModel;
using VDownload.Models;
using VDownload.Services.Utility.FFmpeg;
using VDownload.Services.Data.Configuration;
using VDownload.Services.Data.Settings;
using Windows.System;
using System.Threading;
using System.Threading.Tasks;
using System;
using System.Linq;
using Windows.Storage;
using System.IO;
using VDownload.Services.UI.Notifications;
using VDownload.Services.UI.StringResources;
using System.Collections.Generic;
namespace VDownload.Core.Tasks
{
public partial class DownloadTask : ObservableObject
{
#region SERVICES
protected readonly IConfigurationService _configurationService;
protected readonly ISettingsService _settingsService;
protected readonly IFFmpegService _ffmpegService;
protected readonly IStringResourcesService _stringResourcesService;
protected readonly INotificationsService _notificationsService;
#endregion
#region FIELDS
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
private CancellationTokenSource? _cancellationTokenSource;
private Task _downloadTask;
#endregion
#region PROPERTIES
[ObservableProperty]
protected Guid _id;
[ObservableProperty]
protected Video _video;
[ObservableProperty]
protected VideoDownloadOptions _downloadOptions;
[ObservableProperty]
protected DownloadTaskStatus _status;
[ObservableProperty]
protected DateTime _createDate;
[ObservableProperty]
protected double _progress;
[ObservableProperty]
protected string? _error;
#endregion
#region CONSTRUCTORS
internal DownloadTask(Video video, VideoDownloadOptions downloadOptions, IConfigurationService configurationService, ISettingsService settingsService, IFFmpegService ffmpegService, IStringResourcesService stringResourcesService, INotificationsService notificationsService)
{
_configurationService = configurationService;
_settingsService = settingsService;
_ffmpegService = ffmpegService;
_stringResourcesService = stringResourcesService;
_notificationsService = notificationsService;
_video = video;
_downloadOptions = downloadOptions;
_id = Guid.NewGuid();
_status = DownloadTaskStatus.Idle;
_createDate = DateTime.Now;
_progress = 0;
}
#endregion
#region PUBLIC METHODS
public void Enqueue()
{
DownloadTaskStatus[] statuses =
[
DownloadTaskStatus.Idle,
DownloadTaskStatus.EndedUnsuccessfully,
DownloadTaskStatus.EndedSuccessfully,
DownloadTaskStatus.EndedCancelled,
];
if (statuses.Contains(Status))
{
Status = DownloadTaskStatus.Queued;
}
}
internal void Start()
{
_cancellationTokenSource = new CancellationTokenSource();
UpdateStatusWithDispatcher(DownloadTaskStatus.Initializing);
_downloadTask = Download();
}
public async Task Cancel()
{
if (_cancellationTokenSource is not null)
{
_cancellationTokenSource.Cancel();
await _downloadTask;
_cancellationTokenSource.Dispose();
_cancellationTokenSource = null;
}
}
#endregion
#region PRIVATE METHODS
private async Task Download()
{
CancellationToken token = _cancellationTokenSource.Token;
await _settingsService.Load();
string tempDirectory = $"{_settingsService.Data.Common.TempDirectory}\\{_configurationService.Common.Path.Temp.TasksDirectory}\\{_id}";
Directory.CreateDirectory(tempDirectory);
List<string> content = new List<string>()
{
$"{_stringResourcesService.NotificationsResources.Get("Title")}: {Video.Title}",
$"{_stringResourcesService.NotificationsResources.Get("Author")}: {Video.Author}"
};
try
{
IProgress<double> onProgressDownloading = new Progress<double>((value) =>
{
UpdateStatusWithDispatcher(DownloadTaskStatus.Downloading);
UpdateProgressWithDispatcher(value);
});
VideoStreamDownloadResult downloadResult = await DownloadOptions.SelectedStream.Download(tempDirectory, onProgressDownloading, token, DownloadOptions.TrimStart, DownloadOptions.TrimEnd);
Action<double> onProgressProcessing = (value) =>
{
UpdateStatusWithDispatcher(DownloadTaskStatus.Processing);
UpdateProgressWithDispatcher(value);
};
string outputPath = $"{tempDirectory}\\processed.{DownloadOptions.Extension}";
FFmpegBuilder ffmpegBuilder = _ffmpegService.CreateBuilder();
if (downloadResult.NewTrimStart != TimeSpan.Zero)
{
ffmpegBuilder.TrimStart(downloadResult.NewTrimStart);
}
if (downloadResult.NewTrimEnd != downloadResult.NewDuration)
{
ffmpegBuilder.TrimEnd(downloadResult.NewTrimEnd);
}
ffmpegBuilder.SetMediaType(DownloadOptions.MediaType)
.JoinCancellationToken(token)
.JoinProgressReporter(onProgressProcessing, downloadResult.NewDuration);
await ffmpegBuilder.RunAsync(downloadResult.File, outputPath);
UpdateStatusWithDispatcher(DownloadTaskStatus.Finalizing);
string destination = $"{DownloadOptions.Directory}\\{DownloadOptions.Filename}.{DownloadOptions.Extension}";
File.Copy(outputPath, destination, true);
UpdateStatusWithDispatcher(DownloadTaskStatus.EndedSuccessfully);
if (_settingsService.Data.Common.Notifications.OnSuccessful)
{
string title = _stringResourcesService.NotificationsResources.Get("OnSuccessfulTitle");
_notificationsService.SendNotification(title, content);
}
}
catch (OperationCanceledException)
{
UpdateStatusWithDispatcher(DownloadTaskStatus.EndedCancelled);
}
catch (Exception ex)
{
UpdateErrorWithDispatcher(ex.Message);
UpdateStatusWithDispatcher(DownloadTaskStatus.EndedUnsuccessfully);
if (_settingsService.Data.Common.Notifications.OnUnsuccessful)
{
string title = _stringResourcesService.NotificationsResources.Get("OnSuccessfulTitle");
content.Add($"{_stringResourcesService.NotificationsResources.Get("Message")}: {ex.Message}");
_notificationsService.SendNotification(title, content);
}
}
finally
{
if (Status != DownloadTaskStatus.EndedUnsuccessfully || _settingsService.Data.Common.DeleteTempOnError)
{
Directory.Delete(tempDirectory, true);
}
}
}
private void UpdateStatusWithDispatcher(DownloadTaskStatus status) => _dispatcherQueue.TryEnqueue(() => Status = status);
private void UpdateProgressWithDispatcher(double progress) => _dispatcherQueue.TryEnqueue(() => Progress = progress);
private void UpdateErrorWithDispatcher(string message) => _dispatcherQueue.TryEnqueue(() => Error = message);
#endregion
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Models;
using VDownload.Services.Data.Configuration;
using VDownload.Services.Data.Settings;
using VDownload.Services.UI.Notifications;
using VDownload.Services.UI.StringResources;
using VDownload.Services.Utility.FFmpeg;
namespace VDownload.Core.Tasks
{
public interface IDownloadTaskFactoryService
{
DownloadTask Create(Video video, VideoDownloadOptions downloadOptions);
}
public class DownloadTaskFactoryService : IDownloadTaskFactoryService
{
#region SERVICES
protected readonly IConfigurationService _configurationService;
protected readonly ISettingsService _settingsService;
protected readonly IFFmpegService _ffmpegService;
protected readonly IStringResourcesService _stringResourcesService;
protected readonly INotificationsService _notificationsService;
#endregion
#region CONSTRUCTORS
public DownloadTaskFactoryService(IConfigurationService configurationService, ISettingsService settingsService, IFFmpegService ffmpegService, IStringResourcesService stringResourcesService, INotificationsService notificationsService)
{
_configurationService = configurationService;
_settingsService = settingsService;
_ffmpegService = ffmpegService;
_stringResourcesService = stringResourcesService;
_notificationsService = notificationsService;
}
#endregion
#region PUBLIC METHODS
public DownloadTask Create(Video video, VideoDownloadOptions downloadOptions)
{
return new DownloadTask(video, downloadOptions, _configurationService, _settingsService, _ffmpegService, _stringResourcesService, _notificationsService);
}
#endregion
}
}

View File

@@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using VDownload.Models;
using VDownload.Services.Data.Settings;
namespace VDownload.Core.Tasks
{
public interface IDownloadTaskManager
{
#region PROPERTIES
ReadOnlyObservableCollection<DownloadTask> Tasks { get; }
#endregion
#region EVENTS
event NotifyCollectionChangedEventHandler TaskCollectionChanged;
#endregion
#region METHODS
DownloadTask AddTask(Video video, VideoDownloadOptions downloadOptions);
void RemoveTask(DownloadTask task);
#endregion
}
public class DownloadTaskManager : IDownloadTaskManager
{
#region SERVICES
protected readonly IDownloadTaskFactoryService _downloadTaskFactoryService;
protected readonly ISettingsService _settingsService;
#endregion
#region FIELDS
private readonly Thread _taskMonitorThread;
private readonly ObservableCollection<DownloadTask> _tasksMain;
#endregion
#region PROPERTIES
public ReadOnlyObservableCollection<DownloadTask> Tasks { get; protected set; }
#endregion
#region EVENTS
public event NotifyCollectionChangedEventHandler TaskCollectionChanged;
#endregion
#region CONSTRUCTORS
public DownloadTaskManager(IDownloadTaskFactoryService downloadTaskFactoryService, ISettingsService settingsService)
{
_downloadTaskFactoryService = downloadTaskFactoryService;
_settingsService = settingsService;
_tasksMain = new ObservableCollection<DownloadTask>();
_tasksMain.CollectionChanged += Tasks_CollectionChanged;
Tasks = new ReadOnlyObservableCollection<DownloadTask>(_tasksMain);
_taskMonitorThread = new Thread(TaskMonitor)
{
IsBackground = true
};
_taskMonitorThread.Start();
}
#endregion
#region PUBLIC METHODS
public DownloadTask AddTask(Video video, VideoDownloadOptions downloadOptions)
{
DownloadTask task = _downloadTaskFactoryService.Create(video, downloadOptions);
_tasksMain.Add(task);
return task;
}
public void RemoveTask(DownloadTask task)
{
_tasksMain.Remove(task);
}
#endregion
#region PRIVATE METHODS
private async void TaskMonitor()
{
await _settingsService.Load();
DownloadTaskStatus[] pendingStatuses =
[
DownloadTaskStatus.Initializing,
DownloadTaskStatus.Downloading,
DownloadTaskStatus.Processing,
DownloadTaskStatus.Finalizing
];
while (true)
{
try
{
IEnumerable<DownloadTask> pendingTasks = Tasks.Where(x => pendingStatuses.Contains(x.Status));
int freeSlots = _settingsService.Data.Common.MaxNumberOfRunningTasks - pendingTasks.Count();
if (freeSlots > 0)
{
IEnumerable<DownloadTask> queuedTasks = Tasks.Where(x => x.Status == DownloadTaskStatus.Queued).OrderBy(x => x.CreateDate).Take(freeSlots);
foreach (DownloadTask queuedTask in queuedTasks)
{
queuedTask.Start();
}
}
}
catch (InvalidOperationException)
{
Console.WriteLine("TaskMonitor: Collection locked - skipping");
}
}
}
#endregion
#region EVENT HANDLERS
private void Tasks_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
TaskCollectionChanged?.Invoke(this, e);
}
#endregion
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Core.Tasks
{
public class DownloadTaskNotInitializedException : Exception
{
#region CONSTRUCTORS
public DownloadTaskNotInitializedException() : base() { }
public DownloadTaskNotInitializedException(string message) : base(message) { }
public DownloadTaskNotInitializedException(string message, Exception inner) : base(message, inner) { }
#endregion
}
}

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace VDownload.Common namespace VDownload.Core.Tasks
{ {
public enum DownloadTaskStatus public enum DownloadTaskStatus
{ {
@@ -14,8 +14,8 @@ namespace VDownload.Common
EndedCancelled, EndedCancelled,
Queued, Queued,
Initializing, Initializing,
Finalizing,
Processing, Processing,
Downloading, Downloading,
Finalizing,
} }
} }

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.Core.Tasks</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" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\VDownload.Models\VDownload.Models.csproj" />
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.Data\VDownload.Services.Data.Configuration\VDownload.Services.Data.Configuration.csproj" />
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.Data\VDownload.Services.Data.Settings\VDownload.Services.Data.Settings.csproj" />
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.Notifications\VDownload.Services.UI.Notifications.csproj" />
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.StringResources\VDownload.Services.UI.StringResources.csproj" />
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.Utility\VDownload.Services.Utility.FFmpeg\VDownload.Services.Utility.FFmpeg.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,19 +1,18 @@
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml.Controls;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using VDownload.GUI.Services.Dialog; using VDownload.Services.Data.Configuration;
using VDownload.GUI.Services.WebView; using VDownload.Services.UI.Dialogs;
using VDownload.Sources.Twitch; using VDownload.Services.UI.StringResources;
using VDownload.Services.UI.WebView;
using VDownload.Sources.Twitch.Authentication; using VDownload.Sources.Twitch.Authentication;
namespace VDownload.GUI.ViewModels namespace VDownload.Core.ViewModels.Authentication
{ {
public partial class AuthenticationViewModel : ObservableObject public partial class AuthenticationViewModel : ObservableObject
{ {
@@ -32,9 +31,11 @@ namespace VDownload.GUI.ViewModels
#region SERVICES #region SERVICES
private IDialogService _dialogService; protected readonly IDialogsService _dialogsService;
private IWebViewService _webViewService; protected readonly IWebViewService _webViewService;
private ITwitchAuthenticationService _twitchAuthenticationService; protected readonly IConfigurationService _configurationService;
protected readonly IStringResourcesService _stringResourcesService;
protected readonly ITwitchAuthenticationService _twitchAuthenticationService;
#endregion #endregion
@@ -43,10 +44,10 @@ namespace VDownload.GUI.ViewModels
#region PROPERTIES #region PROPERTIES
[ObservableProperty] [ObservableProperty]
private string _twitchDescription; private AuthenticationButton _twitchButtonState = AuthenticationButton.Loading;
[ObservableProperty] [ObservableProperty]
private AuthenticationButton _twitchButtonState; private string _twitchDescription;
#endregion #endregion
@@ -54,13 +55,13 @@ namespace VDownload.GUI.ViewModels
#region CONSTRUCTORS #region CONSTRUCTORS
public AuthenticationViewModel(IDialogService dialogService, IWebViewService webViewService, ITwitchAuthenticationService twitchAuthenticationService) public AuthenticationViewModel(IDialogsService dialogsService, IWebViewService webViewService, IConfigurationService configurationService, IStringResourcesService stringResourcesService, ITwitchAuthenticationService twitchAuthenticationService)
{ {
_dialogService = dialogService; _dialogsService = dialogsService;
_webViewService = webViewService; _webViewService = webViewService;
_configurationService = configurationService;
_stringResourcesService = stringResourcesService;
_twitchAuthenticationService = twitchAuthenticationService; _twitchAuthenticationService = twitchAuthenticationService;
TwitchButtonState = AuthenticationButton.Loading;
} }
#endregion #endregion
@@ -69,6 +70,16 @@ namespace VDownload.GUI.ViewModels
#region PUBLIC METHODS #region PUBLIC METHODS
[RelayCommand]
public async Task Navigation()
{
List<Task> refreshTasks = new List<Task>
{
TwitchAuthenticationRefresh()
};
await Task.WhenAll(refreshTasks);
}
[RelayCommand] [RelayCommand]
public async Task TwitchAuthentication() public async Task TwitchAuthentication()
{ {
@@ -81,9 +92,13 @@ namespace VDownload.GUI.ViewModels
} }
else else
{ {
string url = await _webViewService.Show(new Uri(_twitchAuthenticationService.AuthenticationPageUrl), _twitchAuthenticationService.AuthenticationPageClosePredicate, "Twitch authentication"); Sources.Twitch.Configuration.Models.Authentication auth = _configurationService.Twitch.Authentication;
string authUrl = string.Format(auth.Url, auth.ClientId, auth.RedirectUrl, auth.ResponseType, string.Join(' ', auth.Scopes));
Match match = _twitchAuthenticationService.AuthenticationPageRedirectUrlRegex.Match(url); string url = await _webViewService.Show(new Uri(authUrl), (url) => url.StartsWith(auth.RedirectUrl), _stringResourcesService.AuthenticationViewResources.Get("TwitchAuthenticationWindowTitle"));
Regex regex = new Regex(auth.RedirectUrlRegex);
Match match = regex.Match(url);
if (match.Success) if (match.Success)
{ {
@@ -92,22 +107,12 @@ namespace VDownload.GUI.ViewModels
} }
else else
{ {
await _dialogService.ShowOk("Twitch authentication error", "An error occured"); await _dialogsService.ShowOk(_stringResourcesService.AuthenticationViewResources.Get("TwitchAuthenticationDialogTitle"), "An unknown error occured"); // TODO : Change to string resource
} }
} }
await TwitchAuthenticationRefresh(); await TwitchAuthenticationRefresh();
} }
[RelayCommand]
public async Task Navigation()
{
List<Task> refreshTasks = new List<Task>
{
TwitchAuthenticationRefresh()
};
await Task.WhenAll(refreshTasks);
}
#endregion #endregion
@@ -122,7 +127,7 @@ namespace VDownload.GUI.ViewModels
if (token is null) if (token is null)
{ {
TwitchDescription = "You are not authenticated. Please sign in"; TwitchDescription = _stringResourcesService.AuthenticationViewResources.Get("TwitchAuthenticationDescriptionNotAuthenticated");
TwitchButtonState = AuthenticationButton.SignIn; TwitchButtonState = AuthenticationButton.SignIn;
} }
else else
@@ -130,13 +135,13 @@ namespace VDownload.GUI.ViewModels
TwitchValidationResult validationResult = await _twitchAuthenticationService.ValidateToken(token); TwitchValidationResult validationResult = await _twitchAuthenticationService.ValidateToken(token);
if (validationResult.Success) if (validationResult.Success)
{ {
TwitchDescription = $"Signed in as {validationResult.TokenData.Login}. Expiration date: {validationResult.TokenData.ExpirationDate:dd.MM.yyyy HH:mm}"; TwitchDescription = string.Format(_stringResourcesService.AuthenticationViewResources.Get("TwitchAuthenticationDescriptionAuthenticated"), validationResult.TokenData.Login, validationResult.TokenData.ExpirationDate);
TwitchButtonState = AuthenticationButton.SignOut; TwitchButtonState = AuthenticationButton.SignOut;
} }
else else
{ {
await _twitchAuthenticationService.DeleteToken(); await _twitchAuthenticationService.DeleteToken();
TwitchDescription = "Token expired or is invalid. Please log in again"; TwitchDescription = _stringResourcesService.AuthenticationViewResources.Get("TwitchAuthenticationDescriptionAuthenticationInvalid");
TwitchButtonState = AuthenticationButton.SignIn; TwitchButtonState = AuthenticationButton.SignIn;
} }
} }

View File

@@ -0,0 +1,110 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Core.ViewModels.Authentication;
using VDownload.Core.ViewModels.Home;
using VDownload.Core.ViewModels.Settings;
using VDownload.Services.UI.DictionaryResources;
using VDownload.Services.UI.StringResources;
using VDownload.Toolkit.UI.Models;
namespace VDownload.Core.ViewModels
{
public partial class BaseViewModel : ObservableObject
{
#region SERVICES
protected readonly IStringResourcesService _stringResourcesService;
protected readonly IDictionaryResourcesService _dictionaryResourcesService;
#endregion
#region FIELDS
protected readonly Type _settingsViewModel = typeof(SettingsViewModel);
#endregion
#region PROPERTIES
[ObservableProperty]
private Type _currentViewModel;
[ObservableProperty]
private NavigationViewItem _selectedItem;
public ReadOnlyObservableCollection<NavigationViewItem> Items { get; protected set; }
public ReadOnlyObservableCollection<NavigationViewItem> FooterItems { get; protected set; }
#endregion
#region CONSTRUCTORS
public BaseViewModel(IStringResourcesService stringResourcesService, IDictionaryResourcesService dictionaryResourcesService)
{
_stringResourcesService = stringResourcesService;
_dictionaryResourcesService = dictionaryResourcesService;
Items = new ReadOnlyObservableCollection<NavigationViewItem>
(
new ObservableCollection<NavigationViewItem>
{
new NavigationViewItem()
{
Name = _stringResourcesService.BaseViewResources.Get("HomeNavigationViewItem"),
IconSource = _dictionaryResourcesService.Get<string>("ImageBaseViewHome"),
ViewModel = typeof(HomeViewModel),
}
}
);
FooterItems = new ReadOnlyObservableCollection<NavigationViewItem>
(
new ObservableCollection<NavigationViewItem>
{
new NavigationViewItem()
{
Name = _stringResourcesService.BaseViewResources.Get("AuthenticationNavigationViewItem"),
IconSource = _dictionaryResourcesService.Get<string>("ImageBaseViewAuthentication"),
ViewModel = typeof(AuthenticationViewModel),
}
}
);
SelectedItem = Items.First();
CurrentViewModel = SelectedItem.ViewModel;
}
#endregion
#region PUBLIC METHODS
[RelayCommand]
public void Navigate(Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs e)
{
if (e.IsSettingsInvoked)
{
CurrentViewModel = _settingsViewModel;
}
else
{
NavigationViewItem item = e.InvokedItemContainer.DataContext as NavigationViewItem;
CurrentViewModel = item.ViewModel;
}
}
#endregion
}
}

View File

@@ -0,0 +1,90 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Core.Tasks;
namespace VDownload.Core.ViewModels.Home
{
public partial class HomeDownloadsViewModel : ObservableObject
{
#region SERVICES
protected readonly IDownloadTaskManager _tasksManager;
#endregion
#region PROPERTIES
public ReadOnlyObservableCollection<DownloadTask> Tasks => _tasksManager.Tasks;
[ObservableProperty]
private bool _taskListIsEmpty;
#endregion
#region CONSTRUCTORS
public HomeDownloadsViewModel(IDownloadTaskManager tasksManager)
{
_tasksManager = tasksManager;
_tasksManager.TaskCollectionChanged += Tasks_CollectionChanged;
_taskListIsEmpty = _tasksManager.Tasks.Count == 0;
}
#endregion
#region PUBLIC METHODS
[RelayCommand]
public async Task StartCancelTask(DownloadTask task)
{
DownloadTaskStatus[] idleStatuses =
[
DownloadTaskStatus.Idle,
DownloadTaskStatus.EndedUnsuccessfully,
DownloadTaskStatus.EndedSuccessfully,
DownloadTaskStatus.EndedCancelled
];
if (idleStatuses.Contains(task.Status))
{
task.Enqueue();
}
else
{
await task.Cancel();
}
}
[RelayCommand]
public async Task RemoveTask(DownloadTask task)
{
await task.Cancel();
_tasksManager.RemoveTask(task);
}
#endregion
#region PRIVATE METHODS
private void Tasks_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
TaskListIsEmpty = Tasks.Count == 0;
}
#endregion
}
}

View File

@@ -0,0 +1,185 @@
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.Core.Tasks;
using VDownload.Models;
using VDownload.Services.Data.Settings;
using VDownload.Services.UI.StoragePicker;
namespace VDownload.Core.ViewModels.Home
{
public partial class HomeVideoViewModel : ObservableObject
{
#region SERVICES
protected readonly IDownloadTaskManager _tasksManager;
protected readonly ISettingsService _settingsService;
protected readonly IStoragePickerService _storagePickerService;
#endregion
#region FIELDS
protected Video _video;
#endregion
#region PROPERTIES
[ObservableProperty]
protected Uri _thumbnailUrl;
[ObservableProperty]
protected string _title;
[ObservableProperty]
protected string _author;
[ObservableProperty]
protected DateTime _publishDate;
[ObservableProperty]
protected TimeSpan _duration;
[ObservableProperty]
protected long _views;
[ObservableProperty]
protected ObservableCollection<VideoStream> _streams;
[ObservableProperty]
protected VideoStream _selectedStream;
[ObservableProperty]
protected MediaType _mediaType;
[ObservableProperty]
protected TimeSpan _trimStart;
[ObservableProperty]
protected TimeSpan _trimEnd;
[ObservableProperty]
protected string _directoryPath;
[ObservableProperty]
protected string _filename;
[ObservableProperty]
protected VideoExtension _videoExtension;
[ObservableProperty]
protected AudioExtension _audioExtension;
#endregion
#region EVENTS
public event EventHandler TaskAdded;
#endregion
#region CONSTRUCTORS
public HomeVideoViewModel(IDownloadTaskManager tasksManager, ISettingsService settingsService, IStoragePickerService storagePickerService)
{
_tasksManager = tasksManager;
_settingsService = settingsService;
_storagePickerService = storagePickerService;
}
#endregion
#region PUBLIC METHODS
public async void LoadVideo(Video video)
{
await _settingsService.Load();
_video = video;
ThumbnailUrl = video.ThumbnailUrl;
Title = video.Title;
Author = video.Author;
PublishDate = video.PublishDate;
Duration = video.Duration;
Views = video.Views;
Streams = [.. video.Streams];
SelectedStream = Streams[0];
MediaType = _settingsService.Data.Common.DefaultTaskSettings.MediaType;
TrimStart = TimeSpan.Zero;
TrimEnd = Duration;
DirectoryPath = _settingsService.Data.Common.DefaultTaskSettings.OutputDirectory;
Filename = Title.Length > 50 ? Title.Substring(0, 50) : Title;
VideoExtension = _settingsService.Data.Common.DefaultTaskSettings.VideoExtension;
AudioExtension = _settingsService.Data.Common.DefaultTaskSettings.AudioExtension;
}
[RelayCommand]
public async Task Browse()
{
string? newDirectory = await _storagePickerService.OpenDirectory();
if (newDirectory is not null)
{
this.DirectoryPath = newDirectory;
}
}
[RelayCommand]
public void CreateTask()
{
_tasksManager.AddTask(_video, BuildDownloadOptions());
TaskAdded?.Invoke(this, EventArgs.Empty);
}
[RelayCommand]
public void CreateTaskAndDownload()
{
DownloadTask task = _tasksManager.AddTask(_video, BuildDownloadOptions());
TaskAdded?.Invoke(this, EventArgs.Empty);
task.Enqueue();
}
#endregion
#region PRIVATE METHODS
protected VideoDownloadOptions BuildDownloadOptions()
{
return new VideoDownloadOptions(Duration)
{
MediaType = this.MediaType,
SelectedStream = this.SelectedStream,
TrimStart = this.TrimStart,
TrimEnd = this.TrimEnd,
Directory = this.DirectoryPath,
Filename = string.Join("_", this.Filename.Split(Path.GetInvalidFileNameChars())),
Extension = (this.MediaType == MediaType.OnlyAudio ? this.AudioExtension.ToString() : this.VideoExtension.ToString()).ToLower(),
};
}
#endregion
}
}

View File

@@ -2,19 +2,17 @@
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using VDownload.Common; using VDownload.Models;
using VDownload.Common.Exceptions; using VDownload.Services.Data.Configuration;
using VDownload.Common.Models; using VDownload.Services.Data.Settings;
using VDownload.GUI.Services.StoragePicker; using VDownload.Services.UI.StringResources;
using VDownload.Services.Search; using VDownload.Sources;
using VDownload.Tasks; using VDownload.Sources.Common;
namespace VDownload.GUI.ViewModels namespace VDownload.Core.ViewModels.Home
{ {
public partial class HomeViewModel : ObservableObject public partial class HomeViewModel : ObservableObject
{ {
@@ -39,9 +37,21 @@ namespace VDownload.GUI.ViewModels
#region SERVICES #region SERVICES
private IStoragePickerService _storagePickerService; protected readonly IConfigurationService _configurationService;
private ISearchService _searchService; protected readonly ISettingsService _settingsService;
private IDownloadTasksManager _tasksService; protected readonly IStringResourcesService _stringResourcesService;
protected readonly ISearchService _searchService;
protected readonly HomeVideoViewModel _videoViewModel;
#endregion
#region FIELDS
protected readonly Type _downloadsView = typeof(HomeDownloadsViewModel);
protected readonly Type _videoView = typeof(HomeVideoViewModel);
#endregion #endregion
@@ -49,27 +59,10 @@ namespace VDownload.GUI.ViewModels
#region PROPERTIES #region PROPERTIES
// MAIN
[ObservableProperty] [ObservableProperty]
private MainContentType _mainContent; private Type _mainContent;
// DOWNLOADS
public ObservableCollection<DownloadTask> Tasks => _tasksService.Tasks;
[ObservableProperty]
private bool _taskListIsEmpty;
// VIDEO
[ObservableProperty]
private DownloadTask _task;
// OPTION BAR
[ObservableProperty] [ObservableProperty]
private OptionBarContentType _optionBarContent; private OptionBarContentType _optionBarContent;
@@ -103,14 +96,15 @@ namespace VDownload.GUI.ViewModels
#region CONSTRUCTORS #region CONSTRUCTORS
public HomeViewModel(IStoragePickerService storagePickerService, ISearchService searchService, IDownloadTasksManager tasksService) public HomeViewModel(IConfigurationService configurationService, ISettingsService settingsService, IStringResourcesService stringResourcesService, ISearchService searchService, HomeVideoViewModel videoViewModel)
{ {
_storagePickerService = storagePickerService; _configurationService = configurationService;
_settingsService = settingsService;
_stringResourcesService = stringResourcesService;
_searchService = searchService; _searchService = searchService;
_tasksService = tasksService;
_tasksService.Tasks.CollectionChanged += Tasks_CollectionChanged;
_taskListIsEmpty = _tasksService.Tasks.Count == 0; _videoViewModel = videoViewModel;
_videoViewModel.TaskAdded += VideoViewModel_TaskAdded;
} }
#endregion #endregion
@@ -120,9 +114,11 @@ namespace VDownload.GUI.ViewModels
#region COMMANDS #region COMMANDS
[RelayCommand] [RelayCommand]
public void Navigation() public async Task Navigation()
{ {
MainContent = MainContentType.Downloads; await _settingsService.Load();
MainContent = _downloadsView;
OptionBarContent = OptionBarContentType.None; OptionBarContent = OptionBarContentType.None;
OptionBarMessage = null; OptionBarMessage = null;
@@ -130,74 +126,14 @@ namespace VDownload.GUI.ViewModels
OptionBarPlaylistSearchButtonChecked = false; OptionBarPlaylistSearchButtonChecked = false;
OptionBarSearchNotPending = true; OptionBarSearchNotPending = true;
OptionBarVideoSearchTBValue = string.Empty; OptionBarVideoSearchTBValue = string.Empty;
OptionBarPlaylistSearchNBValue = 1; // TODO: load from settings OptionBarPlaylistSearchNBValue = _settingsService.Data.Common.MaxNumberOfVideosToGetFromPlaylist;
OptionBarPlaylistSearchTBValue = string.Empty; 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] [RelayCommand]
public void LoadFromSubscription() public void LoadFromSubscription()
{ {
MainContent = MainContentType.Downloads; MainContent = _downloadsView;
OptionBarContent = OptionBarContentType.None; OptionBarContent = OptionBarContentType.None;
OptionBarVideoSearchButtonChecked = false; OptionBarVideoSearchButtonChecked = false;
@@ -210,7 +146,7 @@ namespace VDownload.GUI.ViewModels
[RelayCommand] [RelayCommand]
public void VideoSearchShow() public void VideoSearchShow()
{ {
MainContent = MainContentType.Downloads; MainContent = _downloadsView;
if (OptionBarContent != OptionBarContentType.VideoSearch) if (OptionBarContent != OptionBarContentType.VideoSearch)
{ {
@@ -226,7 +162,7 @@ namespace VDownload.GUI.ViewModels
[RelayCommand] [RelayCommand]
public void PlaylistSearchShow() public void PlaylistSearchShow()
{ {
MainContent = MainContentType.Downloads; MainContent = _downloadsView;
if (OptionBarContent != OptionBarContentType.PlaylistSearch) if (OptionBarContent != OptionBarContentType.PlaylistSearch)
{ {
@@ -244,7 +180,7 @@ namespace VDownload.GUI.ViewModels
{ {
OptionBarSearchNotPending = false; OptionBarSearchNotPending = false;
OptionBarLoading = true; OptionBarLoading = true;
OptionBarMessage = "Loading..."; OptionBarMessage = _stringResourcesService.HomeViewResources.Get("OptionBarMessageLoading");
Video video; Video video;
try try
@@ -259,9 +195,9 @@ namespace VDownload.GUI.ViewModels
return; return;
} }
Task = new DownloadTask(video); _videoViewModel.LoadVideo(video);
MainContent = MainContentType.Video; MainContent = _videoView;
OptionBarSearchNotPending = true; OptionBarSearchNotPending = true;
OptionBarLoading = false; OptionBarLoading = false;
@@ -273,7 +209,7 @@ namespace VDownload.GUI.ViewModels
{ {
OptionBarSearchNotPending = false; OptionBarSearchNotPending = false;
OptionBarLoading = true; OptionBarLoading = true;
OptionBarMessage = "Loading..."; OptionBarMessage = _stringResourcesService.HomeViewResources.Get("OptionBarMessageLoading");
Playlist playlist; Playlist playlist;
try try
@@ -303,12 +239,9 @@ namespace VDownload.GUI.ViewModels
#region EVENT HANDLERS #region PRIVATE METHODS
private void Tasks_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) private async void VideoViewModel_TaskAdded(object sender, EventArgs e) => await Navigation();
{
TaskListIsEmpty = Tasks.Count == 0;
}
#endregion #endregion
} }

View File

@@ -4,16 +4,9 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace VDownload.GUI.ViewModels namespace VDownload.Core.ViewModels.Settings
{ {
public class SettingsViewModel public class SettingsViewModel
{ {
#region CONSTRUCTORS
public SettingsViewModel()
{
}
#endregion
} }
} }

View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>VDownload.Core.ViewModels</RootNamespace>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<UseRidGraph>true</UseRidGraph>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.240211001" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.Data\VDownload.Services.Data.Settings\VDownload.Services.Data.Settings.csproj" />
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.Dialogs\VDownload.Services.UI.Dialogs.csproj" />
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.DictionaryResources\VDownload.Services.UI.DictionaryResources.csproj" />
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.StoragePicker\VDownload.Services.UI.StoragePicker.csproj" />
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.StringResources\VDownload.Services.UI.StringResources.csproj" />
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.WebView\VDownload.Services.UI.WebView.csproj" />
<ProjectReference Include="..\..\VDownload.Sources\VDownload.Sources.Twitch\VDownload.Sources.Twitch.Authentication\VDownload.Sources.Twitch.Authentication.csproj" />
<ProjectReference Include="..\..\VDownload.Sources\VDownload.Sources\VDownload.Sources.csproj" />
<ProjectReference Include="..\..\VDownload.Toolkit\VDownload.Toolkit.UI\VDownload.Toolkit.UI.Models\VDownload.Toolkit.UI.Models.csproj" />
<ProjectReference Include="..\VDownload.Core.Tasks\VDownload.Core.Tasks.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Page <Page
x:Class="VDownload.GUI.Views.AuthenticationView" x:Class="VDownload.Core.Views.Authentication.AuthenticationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.GUI.Views" xmlns:local="using:VDownload.Core.Views.Authentication"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ctuc="using:CommunityToolkit.WinUI.UI.Controls" xmlns:ctuc="using:CommunityToolkit.WinUI.UI.Controls"
@@ -18,20 +18,21 @@
<ic:InvokeCommandAction Command="{Binding NavigationCommand}"/> <ic:InvokeCommandAction Command="{Binding NavigationCommand}"/>
</ic:EventTriggerBehavior> </ic:EventTriggerBehavior>
</i:Interaction.Behaviors> </i:Interaction.Behaviors>
<Grid Padding="20" RowSpacing="20"> <Grid Padding="20"
RowSpacing="20">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition/> <RowDefinition/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Grid.Row="0" <TextBlock x:Uid="/VDownload.Core.Strings/AuthenticationViewResources/Header"
Grid.Row="0"
FontSize="28" FontSize="28"
FontWeight="SemiBold" FontWeight="SemiBold"/>
Text="Authentication"/>
<StackPanel Grid.Row="1" <StackPanel Grid.Row="1"
Spacing="10"> Spacing="10">
<ctc:SettingsCard Header="Twitch"> <ctc:SettingsCard Header="Twitch">
<i:Interaction.Behaviors> <i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource EnumToStringConverter}}" <ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="NotEqual" ComparisonCondition="NotEqual"
Value="Loading"> Value="Loading">
<ic:ChangePropertyAction PropertyName="Description" <ic:ChangePropertyAction PropertyName="Description"
@@ -40,28 +41,28 @@
<ic:ChangePropertyAction.Value> <ic:ChangePropertyAction.Value>
<Button Command="{Binding TwitchAuthenticationCommand}"> <Button Command="{Binding TwitchAuthenticationCommand}">
<i:Interaction.Behaviors> <i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource EnumToStringConverter}}" <ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal" ComparisonCondition="Equal"
Value="SignIn"> Value="SignIn">
<ic:ChangePropertyAction PropertyName="Content" <ic:ChangePropertyAction x:Uid="/VDownload.Core.Strings/AuthenticationViewResources/AuthenticationButtonSignIn"
Value="Sign in"/> PropertyName="Content"/>
</ic:DataTriggerBehavior> </ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource EnumToStringConverter}}" <ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal" ComparisonCondition="Equal"
Value="SignOut"> Value="SignOut">
<ic:ChangePropertyAction PropertyName="Content" <ic:ChangePropertyAction x:Uid="/VDownload.Core.Strings/AuthenticationViewResources/AuthenticationButtonSignOut"
Value="Sign out"/> PropertyName="Content"/>
</ic:DataTriggerBehavior> </ic:DataTriggerBehavior>
</i:Interaction.Behaviors> </i:Interaction.Behaviors>
</Button> </Button>
</ic:ChangePropertyAction.Value> </ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction> </ic:ChangePropertyAction>
</ic:DataTriggerBehavior> </ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource EnumToStringConverter}}" <ic:DataTriggerBehavior Binding="{Binding TwitchButtonState, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal" ComparisonCondition="Equal"
Value="Loading"> Value="Loading">
<ic:ChangePropertyAction PropertyName="Description" <ic:ChangePropertyAction x:Uid="/VDownload.Core.Strings/AuthenticationViewResources/AuthenticationDescriptionLoading"
Value="Loading..."/> PropertyName="Description"/>
<ic:ChangePropertyAction PropertyName="Content"> <ic:ChangePropertyAction PropertyName="Content">
<ic:ChangePropertyAction.Value> <ic:ChangePropertyAction.Value>
<ProgressRing Width="20" <ProgressRing Width="20"

View File

@@ -0,0 +1,18 @@
using Microsoft.UI.Xaml.Controls;
using VDownload.Core.ViewModels.Authentication;
namespace VDownload.Core.Views.Authentication
{
public sealed partial class AuthenticationView : Page
{
#region CONSTRUCTORS
public AuthenticationView(AuthenticationViewModel viewModel)
{
this.InitializeComponent();
this.DataContext = viewModel;
}
#endregion
}
}

View File

@@ -1,49 +1,45 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Window <Window
x:Class="VDownload.MainWindow" x:Class="VDownload.Core.Views.BaseWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="using:Microsoft.Xaml.Interactivity" xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:ic="using:Microsoft.Xaml.Interactions.Core" xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
xmlns:cb="using:VDownload.GUI.Customs.Behaviors" xmlns:cb="using:VDownload.Toolkit.UI.Behaviors"
mc:Ignorable="d"> mc:Ignorable="d">
<Window.SystemBackdrop> <Window.SystemBackdrop>
<MicaBackdrop Kind="Base"/> <MicaBackdrop Kind="Base"/>
</Window.SystemBackdrop> </Window.SystemBackdrop>
<Grid x:Name="RootPanel" Loaded="RootPanel_Loaded"> <Grid x:Name="Root"
Loaded="Root_Loaded">
<Border x:Name="AppTitleBar" <Border x:Name="AppTitleBar"
IsHitTestVisible="True" IsHitTestVisible="True"
VerticalAlignment="Top" VerticalAlignment="Top"
Background="Transparent"
Height="40" Height="40"
Canvas.ZIndex="1" Canvas.ZIndex="1"
Margin="55,4,0,0"> Margin="55,4,0,0">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<Image x:Name="TitleBarIcon" <Image HorizontalAlignment="Left"
HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
Source="{StaticResource ImageLogo}" Source="{StaticResource ImageLogo}"
Width="16" Width="16"
Height="16"/> Height="16"/>
<TextBlock x:Name="TitleBarTextBlock" <TextBlock Text="VDownload"
Text="VDownload"
VerticalAlignment="Center" VerticalAlignment="Center"
Margin="12, 0, 0, 0" Margin="12, 0, 0, 0"
Style="{StaticResource CaptionTextBlockStyle}" /> Style="{StaticResource CaptionTextBlockStyle}"/>
</StackPanel> </StackPanel>
</Border> </Border>
<NavigationView x:Name="NavigationView" <NavigationView IsTitleBarAutoPaddingEnabled="True"
IsTitleBarAutoPaddingEnabled="True"
IsBackButtonVisible="Collapsed" IsBackButtonVisible="Collapsed"
PaneDisplayMode="LeftCompact" PaneDisplayMode="LeftCompact"
Canvas.ZIndex="0" Canvas.ZIndex="0"
Background="Transparent"
MenuItemsSource="{Binding Items}" MenuItemsSource="{Binding Items}"
FooterMenuItemsSource="{Binding FooterItems}" FooterMenuItemsSource="{Binding FooterItems}"
SelectedItem="{Binding SelectedItem}"> SelectedItem="{Binding SelectedItem}"
Background="Transparent">
<i:Interaction.Behaviors> <i:Interaction.Behaviors>
<cb:EventToCommandBehavior Command="{Binding NavigateCommand}" <cb:EventToCommandBehavior Command="{Binding NavigateCommand}"
Event="ItemInvoked" Event="ItemInvoked"
@@ -65,11 +61,6 @@
<Frame Margin="0,48,0,0" <Frame Margin="0,48,0,0"
CornerRadius="10" CornerRadius="10"
Content="{Binding CurrentViewModel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource ViewModelToViewConverter}}"> Content="{Binding CurrentViewModel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource ViewModelToViewConverter}}">
<Frame.ContentTransitions>
<TransitionCollection>
<ContentThemeTransition/>
</TransitionCollection>
</Frame.ContentTransitions>
</Frame> </Frame>
</NavigationView> </NavigationView>
</Grid> </Grid>

View File

@@ -0,0 +1,59 @@
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.Core.ViewModels;
using Windows.Foundation;
using Windows.Foundation.Collections;
namespace VDownload.Core.Views
{
public sealed partial class BaseWindow : Window
{
#region PROPERTIES
public XamlRoot XamlRoot => this.Root.XamlRoot;
#endregion
#region EVENTS
public event EventHandler RootLoaded;
#endregion
#region CONSTRUCTORS
public BaseWindow(BaseViewModel viewModel)
{
this.InitializeComponent();
this.ExtendsContentIntoTitleBar = true;
this.SetTitleBar(this.AppTitleBar);
this.Root.DataContext = viewModel;
}
#endregion
#region PRIVATE METHODS
private void Root_Loaded(object sender, RoutedEventArgs e) => RootLoaded?.Invoke(this, EventArgs.Empty);
#endregion
}
}

View File

@@ -0,0 +1,397 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="VDownload.Core.Views.Home.HomeDownloadsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Core.Views.Home"
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:m="using:VDownload.Models"
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"
x:Name="Root"
Background="Transparent">
<ctuc:SwitchPresenter Value="{Binding TaskListIsEmpty, Converter={StaticResource ObjectToStringConverter}}">
<ctuc:Case Value="True">
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image Source="{StaticResource ImageHomeDownloadsViewNoTasks}"
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"
Margin="0,0,0,10">
<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 ImageHomeDownloadsViewQuality}"/>
<StackPanel Grid.Row="0"
Grid.Column="1"
VerticalAlignment="Center"
Orientation="Horizontal">
<ctuc:SwitchPresenter Value="{Binding DownloadOptions.MediaType, Converter={StaticResource ObjectToStringConverter}}">
<ctuc:Case Value="Original">
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/MediaTypeOriginal"
FontSize="{StaticResource TextSize}"/>
</ctuc:Case>
<ctuc:Case Value="OnlyVideo">
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/MediaTypeOnlyVideo"
FontSize="{StaticResource TextSize}"/>
</ctuc:Case>
<ctuc:Case Value="OnlyAudio">
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/MediaTypeOnlyAudio"
FontSize="{StaticResource TextSize}"/>
</ctuc:Case>
</ctuc:SwitchPresenter>
<TextBlock FontSize="{StaticResource TextSize}">
<Run Text=" ("/><Run Text="{Binding DownloadOptions.SelectedStream.Name}"/>)
</TextBlock>
</StackPanel>
<Image Grid.Row="1"
Grid.Column="0"
Source="{ThemeResource ImageHomeDownloadsViewTime}"/>
<StackPanel Grid.Row="1"
Grid.Column="1"
Orientation="Horizontal"
VerticalAlignment="Center">
<TextBlock Text="{Binding DownloadOptions.DurationAfterTrim}"
FontSize="{StaticResource TextSize}"/>
<TextBlock Visibility="{Binding DownloadOptions.IsTrimmed, Converter={StaticResource BoolToVisibilityConverter}}"
FontSize="{StaticResource TextSize}">
<Run Text=" "/>(<Run Text="{Binding DownloadOptions.TrimStart}"/> - <Run Text="{Binding DownloadOptions.TrimEnd}"/>)
</TextBlock>
</StackPanel>
<Image Grid.Row="2"
Grid.Column="0"
Source="{ThemeResource ImageHomeDownloadsViewFile}"/>
<TextBlock Grid.Row="2"
Grid.Column="1"
FontSize="{StaticResource TextSize}"
VerticalAlignment="Center">
<Run Text="{Binding DownloadOptions.Directory}"/>\<Run Text="{Binding DownloadOptions.Filename}"/>.<Run Text="{Binding DownloadOptions.Extension}"/>
</TextBlock>
<Image Grid.Row="3"
Grid.Column="0">
<i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="Idle">
<ic:ChangePropertyAction PropertyName="Source"
Value="{ThemeResource ImageHomeDownloadsViewIdle}"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="Queued">
<ic:ChangePropertyAction PropertyName="Source"
Value="{ThemeResource ImageHomeDownloadsViewQueued}"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="Initializing">
<ic:ChangePropertyAction PropertyName="Source"
Value="{ThemeResource ImageHomeDownloadsViewInitializing}"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="Downloading">
<ic:ChangePropertyAction PropertyName="Source"
Value="{ThemeResource ImageHomeDownloadsViewDownloading}"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="Processing">
<ic:ChangePropertyAction PropertyName="Source"
Value="{ThemeResource ImageHomeDownloadsViewProcessing}"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="Finalizing">
<ic:ChangePropertyAction PropertyName="Source"
Value="{ThemeResource ImageHomeDownloadsViewFinalizing}"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="EndedUnsuccessfully">
<ic:ChangePropertyAction PropertyName="Source"
Value="{ThemeResource ImageHomeDownloadsViewError}"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="EndedSuccessfully">
<ic:ChangePropertyAction PropertyName="Source"
Value="{ThemeResource ImageHomeDownloadsViewDone}"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="EndedCancelled">
<ic:ChangePropertyAction PropertyName="Source"
Value="{ThemeResource ImageHomeDownloadsViewCancelled}"/>
</ic:DataTriggerBehavior>
</i:Interaction.Behaviors>
</Image>
<Border Grid.Row="3"
Grid.Column="1"
VerticalAlignment="Center">
<i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="Idle">
<ic:ChangePropertyAction PropertyName="Child">
<ic:ChangePropertyAction.Value>
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusIdle"
FontSize="{StaticResource TextSize}"/>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="Queued">
<ic:ChangePropertyAction PropertyName="Child">
<ic:ChangePropertyAction.Value>
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusQueued"
FontSize="{StaticResource TextSize}"/>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="Initializing">
<ic:ChangePropertyAction PropertyName="Child">
<ic:ChangePropertyAction.Value>
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusInitializing"
FontSize="{StaticResource TextSize}"/>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="Downloading">
<ic:ChangePropertyAction PropertyName="Child">
<ic:ChangePropertyAction.Value>
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusDownloading"
FontSize="{StaticResource TextSize}"/>
<TextBlock Text="{Binding Progress, Converter={StaticResource StringFormatConverter}, ConverterParameter='{} ({0:0.##}%)'}"
FontSize="{StaticResource TextSize}"/>
</StackPanel>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="Processing">
<ic:ChangePropertyAction PropertyName="Child">
<ic:ChangePropertyAction.Value>
<TextBlock FontSize="{StaticResource TextSize}">
<Run x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusProcessing"/><Run Text=" ("/><Run Text="{Binding Progress, Converter={StaticResource StringFormatConverter}, ConverterParameter='{}{0:0.##}'}"/>%)
</TextBlock>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="Finalizing">
<ic:ChangePropertyAction PropertyName="Child">
<ic:ChangePropertyAction.Value>
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusFinalizing"
FontSize="{StaticResource TextSize}"/>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="EndedUnsuccessfully">
<ic:ChangePropertyAction PropertyName="Child">
<ic:ChangePropertyAction.Value>
<TextBlock FontSize="{StaticResource TextSize}">
<Run x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusError"/><Run Text=" ("/><Run Text="{Binding Error}"/>)
</TextBlock>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="EndedSuccessfully">
<ic:ChangePropertyAction PropertyName="Child">
<ic:ChangePropertyAction.Value>
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusDone"
FontSize="{StaticResource TextSize}"/>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="EndedCancelled">
<ic:ChangePropertyAction PropertyName="Child">
<ic:ChangePropertyAction.Value>
<TextBlock x:Uid="/VDownload.Core.Strings/HomeDownloadsViewResources/StatusCancelled"
FontSize="{StaticResource TextSize}"/>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
</i:Interaction.Behaviors>
</Border>
</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 ObjectToStringConverter}}"
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 ObjectToIntConverter}}"
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 ObjectToIntConverter}}"
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"
Command="{Binding ElementName=Root, Path=DataContext.RemoveTaskCommand}"
CommandParameter="{Binding}"/>
</Grid>
</Grid>
<ProgressBar Grid.Row="1"
Value="{Binding Progress}"
Maximum="100">
<i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToIntConverter}}"
ComparisonCondition="LessThan"
Value="5">
<ic:ChangePropertyAction PropertyName="Visibility" Value="Collapsed"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToIntConverter}}"
ComparisonCondition="GreaterThanOrEqual"
Value="5">
<ic:ChangePropertyAction PropertyName="Visibility" Value="Visible"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToIntConverter}}"
ComparisonCondition="LessThan"
Value="7">
<ic:ChangePropertyAction PropertyName="IsIndeterminate" Value="True"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding Status, Converter={StaticResource ObjectToIntConverter}}"
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>
</Page>

View File

@@ -10,17 +10,17 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime; using System.Runtime.InteropServices.WindowsRuntime;
using VDownload.GUI.ViewModels; using VDownload.Core.ViewModels.Home;
using Windows.Foundation; using Windows.Foundation;
using Windows.Foundation.Collections; using Windows.Foundation.Collections;
namespace VDownload.GUI.Views namespace VDownload.Core.Views.Home
{ {
public sealed partial class SettingsView : Page public sealed partial class HomeDownloadsView : Page
{ {
#region CONSTRUCTORS #region CONSTRUCTORS
public SettingsView(SettingsViewModel viewModel) public HomeDownloadsView(HomeDownloadsViewModel viewModel)
{ {
this.InitializeComponent(); this.InitializeComponent();
this.DataContext = viewModel; this.DataContext = viewModel;

View File

@@ -0,0 +1,226 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="VDownload.Core.Views.Home.HomeVideoView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Core.Views.Home"
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:m="using:VDownload.Models"
xmlns:ct="using:CommunityToolkit.WinUI"
xmlns:ctc="using:CommunityToolkit.WinUI.Controls"
xmlns:ctuc="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:c="using:VDownload.Toolkit.UI.Controls"
mc:Ignorable="d"
Background="{ThemeResource ViewBackgroundColor}">
<Grid Padding="15"
RowSpacing="20">
<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 ThumbnailUrl}"
VerticalAlignment="Stretch"/>
</Border>
<StackPanel Grid.Column="1"
Spacing="15">
<TextBlock Text="{Binding 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 ImageHomeVideoViewAuthor}"
Width="{StaticResource IconSize}"/>
<TextBlock Grid.Column="1"
Grid.Row="0"
Text="{Binding Author}"/>
<Image Grid.Column="0"
Grid.Row="1"
Source="{ThemeResource ImageHomeVideoViewDate}"
Width="{StaticResource IconSize}"/>
<TextBlock Grid.Column="1"
Grid.Row="1"
Text="{Binding PublishDate}"/>
<Image Grid.Column="0"
Grid.Row="2"
Source="{ThemeResource ImageHomeVideoViewTime}"
Width="{StaticResource IconSize}"/>
<TextBlock Grid.Column="1"
Grid.Row="2"
Text="{Binding Duration}"/>
<Image Grid.Column="0"
Grid.Row="3"
Source="{ThemeResource ImageHomeVideoViewViews}"
Width="{StaticResource IconSize}"/>
<TextBlock Grid.Column="1"
Grid.Row="3"
Text="{Binding Views}"/>
</Grid>
</StackPanel>
</Grid>
<ScrollViewer Grid.Row="1">
<StackPanel Spacing="20">
<StackPanel Spacing="5">
<TextBlock x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/MediaOptionsHeader"
FontWeight="Bold"
FontSize="15"/>
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/QualitySettingsCard">
<ctc:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{ThemeResource ImageHomeVideoViewQuality}"/>
</ctc:SettingsCard.HeaderIcon>
<ComboBox ItemsSource="{Binding Streams}"
SelectedItem="{Binding SelectedStream, Mode=TwoWay}">
<!-- ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate -->
</ComboBox>
</ctc:SettingsCard>
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/MediaTypeSettingsCard">
<ctc:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{ThemeResource ImageHomeVideoViewMedia}"/>
</ctc:SettingsCard.HeaderIcon>
<ComboBox ItemsSource="{ct:EnumValues Type=m:MediaType}"
SelectedItem="{Binding MediaType, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<ctuc:SwitchPresenter Value="{Binding Converter={StaticResource ObjectToStringConverter}}">
<ctuc:Case Value="Original">
<TextBlock x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/MediaTypeOriginal"/>
</ctuc:Case>
<ctuc:Case Value="OnlyVideo">
<TextBlock x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/MediaTypeOnlyVideo"/>
</ctuc:Case>
<ctuc:Case Value="OnlyAudio">
<TextBlock x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/MediaTypeOnlyAudio"/>
</ctuc:Case>
</ctuc:SwitchPresenter>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ctc:SettingsCard>
<ctc:SettingsExpander x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/TrimSettingsGroup">
<ctc:SettingsExpander.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{ThemeResource ImageHomeVideoViewTrim}"/>
</ctc:SettingsExpander.HeaderIcon>
<ctc:SettingsExpander.Items>
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/TrimStartSettingsCard">
<c:TimeSpanControl Value="{Binding TrimStart, Mode=TwoWay}"
Maximum="{Binding TrimEnd, Mode=OneWay}"/>
</ctc:SettingsCard>
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/TrimEndSettingsCard">
<c:TimeSpanControl Minimum="{Binding TrimStart, Mode=OneWay}"
Value="{Binding TrimEnd, Mode=TwoWay}"
Maximum="{Binding Duration, Mode=OneWay}"/>
</ctc:SettingsCard>
</ctc:SettingsExpander.Items>
</ctc:SettingsExpander>
</StackPanel>
<StackPanel Spacing="5">
<TextBlock x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/FileOptionsHeader"
FontWeight="Bold"
FontSize="15"/>
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/DirectorySettingsCard"
Description="{Binding DirectoryPath}">
<ctc:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{ThemeResource ImageHomeVideoViewDirectory}"/>
</ctc:SettingsCard.HeaderIcon>
<Button x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/DirectorySettingsCardButton"
Command="{Binding BrowseCommand}"/>
</ctc:SettingsCard>
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/FilenameSettingsCard">
<ctc:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{ThemeResource ImageHomeVideoViewFilename}"/>
</ctc:SettingsCard.HeaderIcon>
<TextBox Text="{Binding Filename, Mode=TwoWay}"/>
</ctc:SettingsCard>
<ctc:SettingsCard x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/FileTypeSettingsCard">
<ctc:SettingsCard.HeaderIcon>
<BitmapIcon ShowAsMonochrome="False"
UriSource="{ThemeResource ImageHomeVideoViewExtension}"/>
</ctc:SettingsCard.HeaderIcon>
<i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding MediaType, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="OnlyAudio">
<ic:ChangePropertyAction PropertyName="Content">
<ic:ChangePropertyAction.Value>
<ComboBox ItemsSource="{ct:EnumValues Type=m:AudioExtension}"
SelectedItem="{Binding AudioExtension, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding MediaType, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="NotEqual"
Value="OnlyAudio">
<ic:ChangePropertyAction PropertyName="Content">
<ic:ChangePropertyAction.Value>
<ComboBox ItemsSource="{ct:EnumValues Type=m:VideoExtension}"
SelectedItem="{Binding VideoExtension, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ic:ChangePropertyAction.Value>
</ic:ChangePropertyAction>
</ic:DataTriggerBehavior>
</i:Interaction.Behaviors>
</ctc:SettingsCard>
</StackPanel>
</StackPanel>
</ScrollViewer>
<StackPanel Grid.Row="2"
HorizontalAlignment="Right"
Orientation="Horizontal"
Spacing="10">
<Button x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/CreateAndStartButton"
Command="{Binding CreateTaskAndDownloadCommand}"/>
<Button x:Uid="/VDownload.Core.Strings/HomeVideoViewResources/CreateButton"
Style="{StaticResource AccentButtonStyle}"
Command="{Binding CreateTaskCommand}"/>
</StackPanel>
</Grid>
</Page>

View File

@@ -10,17 +10,17 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime; using System.Runtime.InteropServices.WindowsRuntime;
using VDownload.GUI.ViewModels; using VDownload.Core.ViewModels.Home;
using Windows.Foundation; using Windows.Foundation;
using Windows.Foundation.Collections; using Windows.Foundation.Collections;
namespace VDownload.GUI.Views namespace VDownload.Core.Views.Home
{ {
public sealed partial class HomeView : Page public sealed partial class HomeVideoView : Page
{ {
#region CONSTRUCTORS #region CONSTRUCTORS
public HomeView(HomeViewModel viewModel) public HomeVideoView(HomeVideoViewModel viewModel)
{ {
this.InitializeComponent(); this.InitializeComponent();
this.DataContext = viewModel; this.DataContext = viewModel;

View File

@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="VDownload.Core.Views.Home.HomeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:i="using:Microsoft.Xaml.Interactivity"
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
xmlns:ctuc="using:CommunityToolkit.WinUI.UI.Controls"
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>
<Frame Content="{Binding MainContent, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource ViewModelToViewConverter}}">
</Frame>
<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 ObjectToStringConverter}}">
<i:Interaction.Behaviors>
<ic:DataTriggerBehavior Binding="{Binding OptionBarContent, Converter={StaticResource ObjectToStringConverter}}"
ComparisonCondition="Equal"
Value="None">
<ic:ChangePropertyAction PropertyName="Visibility"
Value="Collapsed"/>
</ic:DataTriggerBehavior>
<ic:DataTriggerBehavior Binding="{Binding OptionBarContent, Converter={StaticResource ObjectToStringConverter}}"
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 x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarVideoSearchContentTextBox"
Grid.Column="0"
VerticalAlignment="Center"
Text="{Binding OptionBarVideoSearchTBValue, Mode=TwoWay}"/>
<Button x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarSearchButton"
Grid.Column="1"
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 x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarPlaylistSearchContentTextBox"
Grid.Column="0"
VerticalAlignment="Center"
Text="{Binding OptionBarPlaylistSearchTBValue, Mode=TwoWay}"/>
<NumberBox x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarPlaylistSearchContentNumberBox"
Grid.Column="1"
VerticalAlignment="Center"
SpinButtonPlacementMode="Compact"
SmallChange="1"
LargeChange="10"
Value="{Binding OptionBarPlaylistSearchNBValue, Mode=TwoWay}"
Minimum="0"/>
<Button x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarSearchButton"
Grid.Column="2"
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 BoolToVisibilityConverter}}"/>
<TextBlock Text="{Binding OptionBarMessage}"/>
</StackPanel>
</ctuc:UniformGrid>
<StackPanel Grid.Column="2"
Orientation="Horizontal">
<AppBarButton x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarLoadSubscription"
Icon="Favorite"
IsEnabled="{Binding OptionBarSearchNotPending}"
Command="{Binding LoadFromSubscriptionCommand}"/>
<AppBarToggleButton x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarVideoSearch"
Icon="Video"
IsEnabled="{Binding OptionBarSearchNotPending}"
IsChecked="{Binding OptionBarVideoSearchButtonChecked, Mode=TwoWay}"
Command="{Binding VideoSearchShowCommand}"/>
<AppBarToggleButton x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarPlaylistSearch"
Icon="List"
IsEnabled="{Binding OptionBarSearchNotPending}"
IsChecked="{Binding OptionBarPlaylistSearchButtonChecked, Mode=TwoWay}"
Command="{Binding PlaylistSearchShowCommand}"/>
<AppBarSeparator/>
<AppBarButton x:Uid="/VDownload.Core.Strings/HomeViewResources/OptionBarDownloadAll"
Icon="Download"
Command="{Binding DownloadCommand}"/>
</StackPanel>
</Grid>
</Grid>
</Page>

View File

@@ -0,0 +1,18 @@
using Microsoft.UI.Xaml.Controls;
using VDownload.Core.ViewModels.Home;
namespace VDownload.Core.Views.Home
{
public sealed partial class HomeView : Page
{
#region CONSTRUCTORS
public HomeView(HomeViewModel viewModel)
{
this.InitializeComponent();
this.DataContext = viewModel;
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,26 @@
using Microsoft.UI.Xaml.Controls;
using VDownload.Core.ViewModels.Settings;
namespace VDownload.Core.Views.Settings
{
public sealed partial class SettingsView : Page
{
#region PROPERTIES
public new SettingsViewModel DataContext { get; set; }
#endregion
#region CONSTRUCTORS
public SettingsView(SettingsViewModel viewModel)
{
this.InitializeComponent();
this.DataContext = viewModel;
}
#endregion
}
}

View File

@@ -0,0 +1,81 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>VDownload.Core.Views</RootNamespace>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<UseRidGraph>true</UseRidGraph>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.WinUI" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.240211001" />
<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.Toolkit\VDownload.Toolkit.UI\VDownload.Toolkit.UI.Behaviors\VDownload.Toolkit.UI.Behaviors.csproj" />
<ProjectReference Include="..\..\VDownload.Toolkit\VDownload.Toolkit.UI\VDownload.Toolkit.UI.Controls\VDownload.Toolkit.UI.Controls.csproj" />
<ProjectReference Include="..\VDownload.Core.Strings\VDownload.Core.Strings.csproj" />
<ProjectReference Include="..\VDownload.Core.ViewModels\VDownload.Core.ViewModels.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Authentication\AuthenticationView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="BaseWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Home\HomeDownloadsView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Home\HomeVideoView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="Settings\SettingsView.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
</ItemGroup>
<ItemGroup>
<Page Update="Home\HomeVideoView.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="BaseWindow.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Home\HomeView.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Home\HomeDownloadsView.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Authentication\AuthenticationView.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Settings\SettingsView.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
</Project>

View File

@@ -1,15 +1,15 @@
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Data;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using VDownload.Core.ViewModels.Authentication;
using System.Threading.Tasks; using VDownload.Core.ViewModels.Home;
using VDownload.GUI.ViewModels; using VDownload.Core.ViewModels.Settings;
using VDownload.GUI.Views; using VDownload.Core.Views.Authentication;
using VDownload.Services; using VDownload.Core.Views.Home;
using VDownload.Core.Views.Settings;
namespace VDownload.GUI.Converters namespace VDownload.Core.Views
{ {
public class ViewModelToViewConverter : IValueConverter public class ViewModelToViewConverter : IValueConverter
{ {
@@ -18,21 +18,25 @@ namespace VDownload.GUI.Converters
private readonly Dictionary<Type, Type> _viewModelViewBinding = new Dictionary<Type, Type> private readonly Dictionary<Type, Type> _viewModelViewBinding = new Dictionary<Type, Type>
{ {
{ typeof(HomeViewModel), typeof(HomeView) }, { typeof(HomeViewModel), typeof(HomeView) },
{ typeof(HomeDownloadsViewModel), typeof(HomeDownloadsView) },
{ typeof(HomeVideoViewModel), typeof(HomeVideoView) },
{ typeof(SettingsViewModel), typeof(SettingsView) }, { typeof(SettingsViewModel), typeof(SettingsView) },
{ typeof(AuthenticationViewModel), typeof(AuthenticationView) } { 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 #endregion
#region METHODS #region PROPERTIES
public static IServiceProvider ServiceProvider { protected get; set; }
#endregion
#region PUBLIC METHODS
public object Convert(object value, Type targetType, object parameter, string language) public object Convert(object value, Type targetType, object parameter, string language)
{ {
@@ -42,20 +46,21 @@ namespace VDownload.GUI.Converters
} }
if (value is Type type && _viewModelViewBinding.ContainsKey(type)) if (value is Type type && _viewModelViewBinding.ContainsKey(type))
{ {
return ServiceProvider.Instance.GetService(_viewModelViewBinding[type]); return ServiceProvider.GetService(_viewModelViewBinding[type]);
} }
if (_viewModelViewBinding.ContainsKey(value.GetType())) if (_viewModelViewBinding.ContainsKey(value.GetType()))
{ {
return ServiceProvider.Instance.GetService(_viewModelViewBinding[value.GetType()]); return ServiceProvider.GetService(_viewModelViewBinding[value.GetType()]);
} }
return null; return null;
} }
public object ConvertBack(object value, Type targetType, object parameter, string language) public object ConvertBack(object value, Type targetType, object parameter, string language)
{ {
if (_viewViewModelBinding.ContainsKey(value.GetType())) Dictionary<Type, Type> viewViewModelBinding = _viewModelViewBinding.ToDictionary(x => x.Value, x => x.Key);
if (viewViewModelBinding.ContainsKey(value.GetType()))
{ {
return _viewViewModelBinding[value.GetType()]; return viewViewModelBinding[value.GetType()];
} }
return null; return null;
} }

View File

@@ -1,44 +0,0 @@
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

@@ -1,33 +0,0 @@
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

@@ -1,33 +0,0 @@
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

@@ -1,44 +0,0 @@
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

@@ -1,30 +0,0 @@
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

@@ -1,35 +0,0 @@
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

@@ -1,29 +0,0 @@
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

@@ -1,27 +0,0 @@
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

@@ -1,22 +0,0 @@
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

@@ -1,30 +0,0 @@
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

@@ -1,30 +0,0 @@
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

@@ -1,21 +0,0 @@
<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

@@ -1,19 +0,0 @@
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

@@ -1,59 +0,0 @@
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

@@ -1,64 +0,0 @@
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

@@ -1,27 +0,0 @@
<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

@@ -1,27 +0,0 @@
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

@@ -1,578 +0,0 @@
<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

@@ -1,50 +0,0 @@
<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>

View File

@@ -4,10 +4,13 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace VDownload.Common namespace VDownload.Models
{ {
public enum AudioExtension public enum AudioExtension
{ {
MP3 MP3,
FLAC,
WAV,
OGG
} }
} }

View File

@@ -5,7 +5,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace VDownload.Common namespace VDownload.Models
{ {
public enum MediaType public enum MediaType
{ {

View File

@@ -4,10 +4,9 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace VDownload.Common namespace VDownload.Models
{ {
public enum VideoExtension public abstract class Playlist
{ {
MP4
} }
} }

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Models
{
public enum ProcessingSpeed
{
VerySlow = 0,
Slower = 1,
Slow = 2,
Medium = 3,
Fast = 4,
Faster = 5,
VeryFast = 6,
SuperFast = 7,
UltraFast = 8
}
}

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace VDownload.Common namespace VDownload.Models
{ {
public enum Source public enum Source
{ {

37
VDownload.Models/Video.cs Normal file
View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Models
{
public abstract class Video
{
#region PROPERTIES
public string Title { get; set; }
public string Description { get; set; }
public string Author { get; set; }
public DateTime PublishDate { get; set; }
public TimeSpan Duration { get; set; }
public long Views { get; set; }
public Uri ThumbnailUrl { get; set; }
public ICollection<VideoStream> Streams { get; set; }
public Uri Url { get; set; }
public Source Source { get; set; }
#endregion
#region CONSTRUCTORS
protected Video()
{
Streams = new List<VideoStream>();
}
#endregion
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Models
{
public class VideoDownloadOptions
{
#region FIELDS
private readonly TimeSpan _originalDuration;
#endregion
#region PROPERTIES
public required VideoStream SelectedStream { get; set; }
public required TimeSpan TrimStart { get; set; }
public required TimeSpan TrimEnd { get; set; }
public required MediaType MediaType { get; set; }
public required string Directory { get; set; }
public required string Filename { get; set; }
public required string Extension { get; set; }
public TimeSpan DurationAfterTrim => TrimEnd - TrimStart;
public bool IsTrimmed => _originalDuration != DurationAfterTrim;
#endregion
#region CONSTRUCTORS
public VideoDownloadOptions(TimeSpan originalDuration)
{
_originalDuration = originalDuration;
}
#endregion
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Models
{
public enum VideoExtension
{
MP4,
MKV,
AVI,
MOV,
WEBM,
WMV,
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Models
{
public abstract class VideoStream
{
#region PROPERTIES
public string Name { get; set; }
#endregion
#region PUBLIC METHODS
public override string ToString() => Name;
public abstract Task<VideoStreamDownloadResult> Download(string taskTemporaryDirectory, IProgress<double> onProgress, CancellationToken token, TimeSpan trimStart, TimeSpan trimEnd);
#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.Models
{
public struct VideoStreamDownloadResult
{
#region PROPERTIES
public required string File { get; init; }
public required TimeSpan NewTrimStart { get; init; }
public required TimeSpan NewTrimEnd { get; init; }
public required TimeSpan NewDuration { get; init; }
#endregion
}
}

View File

@@ -1,30 +0,0 @@
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Services.Authentication
{
public class AuthenticationConfiguration
{
#region PROPERTIES
public string FilePath { get; private set; }
#endregion
#region CONSTRUCTORS
public AuthenticationConfiguration(IConfiguration configuration)
{
IConfigurationSection section = configuration.GetSection("authentication");
FilePath = section["file_path"];
}
#endregion
}
}

View File

@@ -1,30 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Services.Authentication
{
public class AuthenticationData
{
#region PROPERTY
[JsonProperty("twitch")]
public AuthenticationDataTwitch Twitch { get; internal set; }
#endregion
#region CONSTRUCTORS
public AuthenticationData()
{
Twitch = new AuthenticationDataTwitch();
}
#endregion
}
}

View File

@@ -1,97 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Extensions;
namespace VDownload.Services.Authentication
{
public interface IAuthenticationService
{
#region PROPERTIES
AuthenticationData? AuthenticationData { get; }
#endregion
#region METHODS
Task Load();
Task Save();
#endregion
}
public class AuthenticationService : IAuthenticationService
{
#region SERVICES
private AuthenticationConfiguration _configuration;
#endregion
#region FIELDS
private string _authenticationDataFile;
#endregion
#region PROPERTIES
public AuthenticationData AuthenticationData { get; private set; }
public bool AuthenticationDataLoaded => AuthenticationData is not null;
#endregion
#region CONSTRUCTORS
public AuthenticationService(AuthenticationConfiguration configuration)
{
_configuration = configuration;
_authenticationDataFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), _configuration.FilePath);
}
#endregion
#region PUBLIC METHODS
public async Task Load()
{
AuthenticationData = null;
if (File.Exists(_authenticationDataFile))
{
string content = await File.ReadAllTextAsync(_authenticationDataFile);
AuthenticationData = JsonConvert.DeserializeObject<AuthenticationData>(content);
}
AuthenticationData ??= new AuthenticationData();
}
public async Task Save()
{
Directory.CreateDirectory(Path.GetDirectoryName(_authenticationDataFile));
string content = JsonConvert.SerializeObject(AuthenticationData);
await File.WriteAllTextAsync(_authenticationDataFile, content);
}
#endregion
}
}

View File

@@ -0,0 +1,26 @@
using Newtonsoft.Json;
using VDownload.Services.Data.Authentication.Models;
namespace VDownload.Services.Data.Authentication
{
public class AuthenticationData
{
#region PROPERTIES
[JsonProperty("twitch")]
public TokenAuthenticationData Twitch { get; set; }
#endregion
#region CONSTRUCTORS
public AuthenticationData()
{
Twitch = new TokenAuthenticationData();
}
#endregion
}
}

View File

@@ -0,0 +1,95 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Services.Data.Configuration;
namespace VDownload.Services.Data.Authentication
{
public interface IAuthenticationDataService
{
#region PROPERTIES
AuthenticationData Data { get; }
#endregion
#region METHODS
Task Load();
Task Save();
#endregion
}
public class AuthenticationDataService : IAuthenticationDataService
{
#region SERVICES
protected readonly IConfigurationService _configurationService;
#endregion
#region FIELDS
protected readonly string _filePath;
#endregion
#region PROPERTIES
public AuthenticationData Data { get; protected set; }
#endregion
#region CONSTRUCTORS
public AuthenticationDataService(IConfigurationService configurationService)
{
_configurationService = configurationService;
string appdataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string appdataDirectoryName = _configurationService.Common.Path.Appdata.DirectoryName;
string appdataAuthenticationFilename = _configurationService.Common.Path.Appdata.AuthenticationFile;
_filePath = Path.Combine(appdataPath, appdataDirectoryName, appdataAuthenticationFilename);
}
#endregion
#region PUBLIC METHODS
public async Task Load()
{
Data = null;
if (File.Exists(_filePath))
{
string content = await File.ReadAllTextAsync(_filePath);
Data = JsonConvert.DeserializeObject<AuthenticationData>(content);
}
Data ??= new AuthenticationData();
}
public async Task Save()
{
Directory.CreateDirectory(Path.GetDirectoryName(_filePath));
string content = JsonConvert.SerializeObject(Data);
await File.WriteAllTextAsync(_filePath, content);
}
#endregion
}
}

View File

@@ -5,9 +5,9 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace VDownload.Services.Authentication namespace VDownload.Services.Data.Authentication.Models
{ {
public class AuthenticationDataTwitch public class TokenAuthenticationData
{ {
[JsonProperty("token")] [JsonProperty("token")]
public byte[]? Token { get; set; } public byte[]? Token { get; set; }

View File

@@ -7,12 +7,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\VDownload.Extensions\VDownload.Extensions.csproj" /> <ProjectReference Include="..\VDownload.Services.Data.Configuration\VDownload.Services.Data.Configuration.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,18 @@
using Microsoft.Extensions.Configuration;
using System.Text.Json.Serialization;
using VDownload.Services.Data.Configuration.Models;
namespace VDownload.Services.Data.Configuration
{
public class CommonConfiguration
{
[ConfigurationKeyName("path")]
public Models.Path Path { get; set; }
[ConfigurationKeyName("processing")]
public Processing Processing { get; set; }
[ConfigurationKeyName("string_resources_assembly")]
public string StringResourcesAssembly { get; set; }
}
}

View File

@@ -0,0 +1,48 @@
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Sources.Twitch.Configuration;
namespace VDownload.Services.Data.Configuration
{
public interface IConfigurationService
{
CommonConfiguration Common { get; }
TwitchConfiguration Twitch { get; }
}
public class ConfigurationService : IConfigurationService
{
#region SERVICES
protected readonly IConfiguration _configuration;
#endregion
#region PROPERTIES
public CommonConfiguration Common { get; protected set; }
public TwitchConfiguration Twitch { get; protected set; }
#endregion
#region CONSTRUCTORS
public ConfigurationService(IConfiguration configuration)
{
Common = configuration.GetSection("common").Get<CommonConfiguration>()!;
Twitch = configuration.GetSection("twitch").Get<TwitchConfiguration>()!;
}
#endregion
}
}

View File

@@ -0,0 +1,22 @@
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace VDownload.Services.Data.Configuration.Models
{
public class Appdata
{
[ConfigurationKeyName("directory_name")]
public string DirectoryName { get; set; }
[ConfigurationKeyName("authentication_file")]
public string AuthenticationFile { get; set; }
[ConfigurationKeyName("settings_file")]
public string SettingsFile { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace VDownload.Services.Data.Configuration.Models
{
public class Muxer
{
[ConfigurationKeyName("extension")]
public string Extension { get; set; }
[ConfigurationKeyName("video_codecs")]
public List<string> VideoCodecs { get; } = new List<string>();
[ConfigurationKeyName("audio_codecs")]
public List<string> AudioCodecs { get; } = new List<string>();
}
}

View File

@@ -0,0 +1,19 @@
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace VDownload.Services.Data.Configuration.Models
{
public class Path
{
[ConfigurationKeyName("appdata")]
public Appdata Appdata { get; set; }
[ConfigurationKeyName("temp")]
public Temp Temp { get; set; }
}
}

View File

@@ -0,0 +1,19 @@
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace VDownload.Services.Data.Configuration.Models
{
public class Processing
{
[ConfigurationKeyName("muxers")]
public List<Muxer> Muxers { get; } = new List<Muxer>();
[ConfigurationKeyName("processed_filename")]
public string ProcessedFilename { get; set; }
}
}

View File

@@ -0,0 +1,16 @@
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace VDownload.Services.Data.Configuration.Models
{
public class Temp
{
[ConfigurationKeyName("tasks_directory")]
public string TasksDirectory { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\VDownload.Sources\VDownload.Sources.Twitch\VDownload.Sources.Twitch.Configuration\VDownload.Sources.Twitch.Configuration.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,36 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Models;
using VDownload.Services.Data.Settings.Models;
namespace VDownload.Services.Data.Settings
{
public class CommonSettings
{
[JsonProperty("max_number_of_videos_to_get_from_playlist")]
public int MaxNumberOfVideosToGetFromPlaylist { get; set; } = 0;
[JsonProperty("max_number_of_running_tasks")]
public int MaxNumberOfRunningTasks { get; set; } = 5;
[JsonProperty("temp_directory")]
public string TempDirectory { get; set; } = $"{Path.GetTempPath()}\\VDownload";
[JsonProperty("delete_temp_on_error")]
public bool DeleteTempOnError { get; set; } = true;
[JsonProperty("default_task_settings")]
public DefaultTaskSettings DefaultTaskSettings { get; set; } = new DefaultTaskSettings();
[JsonProperty("notifications")]
public Notifications Notifications { get; set; } = new Notifications();
[JsonProperty("processing")]
public Processing Processing { get; set; } = new Processing();
}
}

View File

@@ -0,0 +1,28 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Models;
namespace VDownload.Services.Data.Settings.Models
{
public class DefaultTaskSettings
{
[JsonProperty("media_type")]
public MediaType MediaType { get; set; } = MediaType.Original;
[JsonProperty("video_extension")]
public VideoExtension VideoExtension { get; set; } = VideoExtension.MP4;
[JsonProperty("audio_extension")]
public AudioExtension AudioExtension { get; set; } = AudioExtension.MP3;
[JsonProperty("output_directory")]
public string OutputDirectory { get; set; } = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
[JsonProperty("save_last_output_directory")]
public bool SaveLastOutputDirectory { get; set; } = false;
}
}

View File

@@ -0,0 +1,18 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Services.Data.Settings.Models
{
public class Notifications
{
[JsonProperty("on_successful")]
public bool OnSuccessful { get; set; } = true;
[JsonProperty("on_unsuccessful")]
public bool OnUnsuccessful { get; set; } = true;
}
}

View File

@@ -0,0 +1,25 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Models;
namespace VDownload.Services.Data.Settings.Models
{
public class Processing
{
[JsonProperty("ffmpeg_location")]
public string FFmpegLocation { get; set; } = $"{AppDomain.CurrentDomain.BaseDirectory}\\FFmpeg";
[JsonProperty("use_multithreading")]
public bool UseMultithreading { get; set; } = true;
[JsonProperty("use_hardware_acceleration")]
public bool UseHardwareAcceleration { get; set; } = true;
[JsonProperty("speed")]
public ProcessingSpeed Speed { get; set; } = ProcessingSpeed.UltraFast;
}
}

View File

@@ -0,0 +1,30 @@
using Newtonsoft.Json;
using VDownload.Sources.Twitch.Settings;
namespace VDownload.Services.Data.Settings
{
public class SettingsData
{
#region PROPERTIES
[JsonProperty("common")]
public CommonSettings Common { get; set; }
[JsonProperty("twitch")]
public TwitchSettings Twitch { get; set; }
#endregion
#region CONSTRUCTORS
internal SettingsData()
{
Common = new CommonSettings();
Twitch = new TwitchSettings();
}
#endregion
}
}

View File

@@ -0,0 +1,104 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Services.Data.Configuration;
namespace VDownload.Services.Data.Settings
{
public interface ISettingsService
{
#region PROPERTIES
SettingsData Data { get; }
#endregion
#region METHODS
Task Load();
Task Save();
Task Restore();
#endregion
}
public class SettingsService : ISettingsService
{
#region SERVICES
protected readonly IConfigurationService _configurationService;
#endregion
#region FIELDS
protected readonly string _filePath;
#endregion
#region PROPERTIES
public SettingsData Data { get; private set; }
#endregion
#region CONSTRUCTORS
public SettingsService(IConfigurationService configurationService)
{
_configurationService = configurationService;
string appdataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string appdataDirectoryName = _configurationService.Common.Path.Appdata.DirectoryName;
string appdataAuthenticationFilename = _configurationService.Common.Path.Appdata.SettingsFile;
_filePath = Path.Combine(appdataPath, appdataDirectoryName, appdataAuthenticationFilename);
}
#endregion
#region PUBLIC METHODS
public async Task Load()
{
Data = null;
if (File.Exists(_filePath))
{
string content = await File.ReadAllTextAsync(_filePath);
Data = JsonConvert.DeserializeObject<SettingsData>(content);
}
Data ??= new SettingsData();
}
public async Task Save()
{
Directory.CreateDirectory(Path.GetDirectoryName(_filePath));
string content = JsonConvert.SerializeObject(Data);
await File.WriteAllTextAsync(_filePath, content);
}
public async Task Restore()
{
Data = new SettingsData();
await Save();
}
#endregion
}
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\VDownload.Models\VDownload.Models.csproj" />
<ProjectReference Include="..\..\..\VDownload.Sources\VDownload.Sources.Twitch\VDownload.Sources.Twitch.Settings\VDownload.Sources.Twitch.Settings.csproj" />
<ProjectReference Include="..\VDownload.Services.Data.Configuration\VDownload.Services.Data.Configuration.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\VDownload.Common\VDownload.Common.csproj" />
<ProjectReference Include="..\..\VDownload.Sources\VDownload.Sources.Twitch\VDownload.Sources.Twitch.Search\VDownload.Sources.Twitch.Search.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,101 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Common;
namespace VDownload.Services.Tasks
{
public interface ITasksService
{
#region PROPERTIES
ObservableCollection<DownloadTask> Tasks { get; }
#endregion
#region METHODS
void AddTask(DownloadTask task);
#endregion
}
public class TasksService : ITasksService
{
#region FIELDS
private readonly Task _taskMonitor;
#endregion
#region PROPERTIES
public ObservableCollection<DownloadTask> Tasks { get; protected set; }
#endregion
#region CONSTRUCTORS
public TasksService()
{
Tasks = new ObservableCollection<DownloadTask>();
_taskMonitor = Task.Run(TaskMonitor);
}
#endregion
#region PUBLIC METHODS
public void AddTask(DownloadTask task)
{
Tasks.Add(task);
}
#endregion
#region PRIVATE METHODS
private void TaskMonitor()
{
int maxTaskNumber = 5; //TODO: from settings
DownloadTaskStatus[] pendingStatuses =
[
DownloadTaskStatus.Initializing,
DownloadTaskStatus.Downloading,
DownloadTaskStatus.Processing,
DownloadTaskStatus.Finalizing
];
while (true)
{
IEnumerable<DownloadTask> pendingTasks = Tasks.Where(x => pendingStatuses.Contains(x.Status));
int freeSlots = maxTaskNumber - pendingTasks.Count();
if (freeSlots > 0)
{
IEnumerable<DownloadTask> queuedTasks = Tasks.Where(x => x.Status == DownloadTaskStatus.Queued).OrderBy(x => x.CreateDate).Take(freeSlots);
foreach (DownloadTask queuedTask in queuedTasks)
{
queuedTask.Start();
}
}
}
}
#endregion
}
}

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace VDownload.GUI.Services.Dialog namespace VDownload.Services.UI.Dialogs
{ {
public enum DialogResult public enum DialogResult
{ {

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace VDownload.GUI.Services.Dialog namespace VDownload.Services.UI.Dialogs
{ {
public enum DialogResultOkCancel public enum DialogResultOkCancel
{ {

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace VDownload.GUI.Services.Dialog namespace VDownload.Services.UI.Dialogs
{ {
public enum DialogResultYesNo public enum DialogResultYesNo
{ {

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace VDownload.GUI.Services.Dialog namespace VDownload.Services.UI.Dialogs
{ {
public enum DialogResultYesNoCancel public enum DialogResultYesNoCancel
{ {

View File

@@ -7,9 +7,9 @@ using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace VDownload.GUI.Services.Dialog namespace VDownload.Services.UI.Dialogs
{ {
public interface IDialogService public interface IDialogsService
{ {
#region PROPERTIES #region PROPERTIES
@@ -35,7 +35,7 @@ namespace VDownload.GUI.Services.Dialog
public class DialogService : IDialogService public class DialogsService : IDialogsService
{ {
#region PROPERTIES #region PROPERTIES

View File

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

View File

@@ -0,0 +1,41 @@
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Services.UI.DictionaryResources
{
public interface IDictionaryResourcesService
{
T Get<T>(string key);
}
public class DictionaryResourcesService : IDictionaryResourcesService
{
#region CONSTRUCTORS
public DictionaryResourcesService() { }
#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
}
}

Some files were not shown because too many files have changed in this diff Show More