1.0-dev17 (Subscription page created)
This commit is contained in:
15
VDownload.Core/Enums/SubscriptionStatus.cs
Normal file
15
VDownload.Core/Enums/SubscriptionStatus.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Enums
|
||||||
|
{
|
||||||
|
public enum SubscriptionStatus
|
||||||
|
{
|
||||||
|
Added,
|
||||||
|
Loaded,
|
||||||
|
Ready
|
||||||
|
}
|
||||||
|
}
|
||||||
15
VDownload.Core/Exceptions/SubscriptionExistsException.cs
Normal file
15
VDownload.Core/Exceptions/SubscriptionExistsException.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Exceptions
|
||||||
|
{
|
||||||
|
public class SubscriptionExistsException : Exception
|
||||||
|
{
|
||||||
|
public SubscriptionExistsException() { }
|
||||||
|
public SubscriptionExistsException(string message) : base(message) { }
|
||||||
|
public SubscriptionExistsException(string message, Exception inner) : base(message, inner) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
34
VDownload.Core/Interfaces/IPlaylist.cs
Normal file
34
VDownload.Core/Interfaces/IPlaylist.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Core.Enums;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Interfaces
|
||||||
|
{
|
||||||
|
public interface IPlaylist
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
// PLAYLIST PROPERTIES
|
||||||
|
string ID { get; }
|
||||||
|
PlaylistSource Source { get; }
|
||||||
|
Uri Url { get; }
|
||||||
|
string Name { get; }
|
||||||
|
IVideo[] Videos { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region METHODS
|
||||||
|
|
||||||
|
// GET PLAYLIST METADATA
|
||||||
|
Task GetMetadataAsync(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
// GET VIDEOS FROM PLAYLIST
|
||||||
|
Task GetVideosAsync(CancellationToken cancellationToken = default);
|
||||||
|
Task GetVideosAsync(int numberOfVideos, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
48
VDownload.Core/Interfaces/IVideo.cs
Normal file
48
VDownload.Core/Interfaces/IVideo.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Core.Enums;
|
||||||
|
using VDownload.Core.Structs;
|
||||||
|
using Windows.Storage;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Interfaces
|
||||||
|
{
|
||||||
|
public interface IVideo
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
// VIDEO PROPERTIES
|
||||||
|
VideoSource Source { get; }
|
||||||
|
string ID { get; }
|
||||||
|
Uri Url { get; }
|
||||||
|
Metadata Metadata { get; }
|
||||||
|
BaseStream[] BaseStreams { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region METHODS
|
||||||
|
|
||||||
|
// GET VIDEO METADATA
|
||||||
|
Task GetMetadataAsync(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
// GET VIDEO STREAMS
|
||||||
|
Task GetStreamsAsync(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
// DOWNLOAD VIDEO
|
||||||
|
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region EVENT HANDLERS
|
||||||
|
|
||||||
|
event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
|
||||||
|
event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
86
VDownload.Core/Services/Sources/Source.cs
Normal file
86
VDownload.Core/Services/Sources/Source.cs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using VDownload.Core.Enums;
|
||||||
|
using VDownload.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Services.Sources
|
||||||
|
{
|
||||||
|
public static class Source
|
||||||
|
{
|
||||||
|
#region CONSTANTS
|
||||||
|
|
||||||
|
// VIDEO SOURCES REGULAR EXPRESSIONS
|
||||||
|
private static readonly (Regex Regex, VideoSource Type)[] VideoSources = new (Regex Regex, VideoSource Type)[]
|
||||||
|
{
|
||||||
|
(new Regex(@"^https://www.twitch.tv/videos/(?<id>\d+)"), VideoSource.TwitchVod),
|
||||||
|
(new Regex(@"^https://www.twitch.tv/\S+/clip/(?<id>[^?]+)"), VideoSource.TwitchClip),
|
||||||
|
(new Regex(@"^https://clips.twitch.tv/(?<id>[^?]+)"), VideoSource.TwitchClip),
|
||||||
|
};
|
||||||
|
|
||||||
|
// PLAYLIST SOURCES REGULAR EXPRESSIONS
|
||||||
|
private static readonly (Regex Regex, PlaylistSource Type)[] PlaylistSources = new (Regex Regex, PlaylistSource Type)[]
|
||||||
|
{
|
||||||
|
(new Regex(@"^https://www.twitch.tv/(?<id>[^?/]+)"), PlaylistSource.TwitchChannel),
|
||||||
|
};
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region METHODS
|
||||||
|
|
||||||
|
// GET VIDEO SOURCE
|
||||||
|
public static IVideo GetVideo(string url)
|
||||||
|
{
|
||||||
|
VideoSource source = VideoSource.Null;
|
||||||
|
string id = string.Empty;
|
||||||
|
foreach ((Regex Regex, VideoSource Type) Source in VideoSources)
|
||||||
|
{
|
||||||
|
Match sourceMatch = Source.Regex.Match(url);
|
||||||
|
if (sourceMatch.Success)
|
||||||
|
{
|
||||||
|
source = Source.Type;
|
||||||
|
id = sourceMatch.Groups["id"].Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return GetVideo(source, id);
|
||||||
|
}
|
||||||
|
public static IVideo GetVideo(VideoSource source, string id)
|
||||||
|
{
|
||||||
|
IVideo videoService = null;
|
||||||
|
switch (source)
|
||||||
|
{
|
||||||
|
case VideoSource.TwitchVod: videoService = new Twitch.Vod(id); break;
|
||||||
|
case VideoSource.TwitchClip: videoService = new Twitch.Clip(id); break;
|
||||||
|
}
|
||||||
|
return videoService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET PLAYLIST SOURCE
|
||||||
|
public static IPlaylist GetPlaylist(string url)
|
||||||
|
{
|
||||||
|
PlaylistSource source = PlaylistSource.Null;
|
||||||
|
string id = string.Empty;
|
||||||
|
foreach ((Regex Regex, PlaylistSource Type) Source in PlaylistSources)
|
||||||
|
{
|
||||||
|
Match sourceMatch = Source.Regex.Match(url);
|
||||||
|
if (sourceMatch.Success)
|
||||||
|
{
|
||||||
|
source = Source.Type;
|
||||||
|
id = sourceMatch.Groups["id"].Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return GetPlaylist(source, id);
|
||||||
|
}
|
||||||
|
public static IPlaylist GetPlaylist(PlaylistSource source, string id)
|
||||||
|
{
|
||||||
|
IPlaylist playlistService = null;
|
||||||
|
switch (source)
|
||||||
|
{
|
||||||
|
case PlaylistSource.TwitchChannel: playlistService = new Twitch.Channel(id); break;
|
||||||
|
}
|
||||||
|
return playlistService;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
67
VDownload.Core/Services/Subscription.cs
Normal file
67
VDownload.Core/Services/Subscription.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Services
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public class Subscription
|
||||||
|
{
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public Subscription(IPlaylist playlist)
|
||||||
|
{
|
||||||
|
Playlist = playlist;
|
||||||
|
SavedVideos = Playlist.Videos;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
public IPlaylist Playlist { get; private set; }
|
||||||
|
public IVideo[] SavedVideos { get; private set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public async Task<IVideo[]> GetNewVideosAsync()
|
||||||
|
{
|
||||||
|
await Playlist.GetVideosAsync();
|
||||||
|
return GetUnsavedVideos();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IVideo[]> GetNewVideosAndUpdateAsync()
|
||||||
|
{
|
||||||
|
await Playlist.GetVideosAsync();
|
||||||
|
IVideo[] newVideos = GetUnsavedVideos();
|
||||||
|
SavedVideos = Playlist.Videos;
|
||||||
|
return newVideos;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
private IVideo[] GetUnsavedVideos()
|
||||||
|
{
|
||||||
|
List<IVideo> newVideos = Playlist.Videos.ToList();
|
||||||
|
foreach (IVideo savedVideo in SavedVideos)
|
||||||
|
{
|
||||||
|
newVideos.RemoveAll((v) => v.Source == savedVideo.Source && v.ID == savedVideo.ID);
|
||||||
|
}
|
||||||
|
return newVideos.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
79
VDownload.Core/Services/SubscriptionsCollectionManagement.cs
Normal file
79
VDownload.Core/Services/SubscriptionsCollectionManagement.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.Serialization.Formatters.Binary;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Core.Exceptions;
|
||||||
|
using Windows.Storage;
|
||||||
|
|
||||||
|
namespace VDownload.Core.Services
|
||||||
|
{
|
||||||
|
public static class SubscriptionsCollectionManagement
|
||||||
|
{
|
||||||
|
#region CONSTANTS
|
||||||
|
|
||||||
|
private static readonly StorageFolder SubscriptionFolderLocation = ApplicationData.Current.LocalFolder;
|
||||||
|
private static readonly string SubscriptionsFolderName = "Subscriptions";
|
||||||
|
private static readonly string SubscriptionFileExtension = "vsub";
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public static async Task<(Subscription Subscription, StorageFile SubscriptionFile)[]> GetSubscriptionsAsync()
|
||||||
|
{
|
||||||
|
List<(Subscription Subscription, StorageFile SubscriptionFile)> subscriptions = new List<(Subscription Subscription,StorageFile SubscriptionFile)> ();
|
||||||
|
StorageFolder subscriptionsFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(SubscriptionsFolderName, CreationCollisionOption.OpenIfExists);
|
||||||
|
BinaryFormatter formatter = new BinaryFormatter();
|
||||||
|
foreach (StorageFile file in await subscriptionsFolder.GetFilesAsync())
|
||||||
|
{
|
||||||
|
if (file.Name.EndsWith(SubscriptionFileExtension))
|
||||||
|
{
|
||||||
|
Stream fileStream = await file.OpenStreamForReadAsync();
|
||||||
|
Subscription subscription = (Subscription)formatter.Deserialize(fileStream);
|
||||||
|
subscriptions.Add((subscription, file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subscriptions.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<StorageFile> CreateSubscriptionFileAsync(Subscription subscription)
|
||||||
|
{
|
||||||
|
StorageFolder subscriptionsFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(SubscriptionsFolderName, CreationCollisionOption.OpenIfExists);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
StorageFile subscriptionFile = await subscriptionsFolder.CreateFileAsync($"{(int)subscription.Playlist.Source}-{subscription.Playlist.ID}.{SubscriptionFileExtension}", CreationCollisionOption.FailIfExists);
|
||||||
|
BinaryFormatter formatter = new BinaryFormatter();
|
||||||
|
Stream subscriptionFileStream = await subscriptionFile.OpenStreamForWriteAsync();
|
||||||
|
formatter.Serialize(subscriptionFileStream, subscription);
|
||||||
|
return subscriptionFile;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if ((uint)ex.HResult == 0x800700B7)
|
||||||
|
{
|
||||||
|
throw new SubscriptionExistsException($"Subscription with id \"{(int)subscription.Playlist.Source}-{subscription.Playlist.ID}\" already exists");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task UpdateSubscriptionFileAsync(Subscription subscription, StorageFile subscriptionFile)
|
||||||
|
{
|
||||||
|
BinaryFormatter formatter = new BinaryFormatter();
|
||||||
|
Stream subscriptionFileStream = await subscriptionFile.OpenStreamForWriteAsync();
|
||||||
|
formatter.Serialize(subscriptionFileStream, subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task DeleteSubscriptionFileAsync(StorageFile subscriptionFile) => await subscriptionFile.DeleteAsync(StorageDeleteOption.PermanentDelete);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
141
VDownload/Strings/en-US/DialogResources.resw
Normal file
141
VDownload/Strings/en-US/DialogResources.resw
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
<?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="Base_CloseButtonText" xml:space="preserve">
|
||||||
|
<value>OK</value>
|
||||||
|
</data>
|
||||||
|
<data name="Subscription_Adding_Base_Title" xml:space="preserve">
|
||||||
|
<value>Playlist adding error</value>
|
||||||
|
</data>
|
||||||
|
<data name="Subscription_Adding_InternetNotAvailable_Content" xml:space="preserve">
|
||||||
|
<value>Unable to connect to servers. Check your internet connection.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Subscription_Adding_PlaylistNotFound_Content" xml:space="preserve">
|
||||||
|
<value>Playlist not found. Check the URL.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Subscription_Adding_SubscriptionExists_Content" xml:space="preserve">
|
||||||
|
<value>This playlist has been already subscribed</value>
|
||||||
|
</data>
|
||||||
|
<data name="Subscription_Adding_TwitchAccessTokenNotFound_Content" xml:space="preserve">
|
||||||
|
<value>To be able to subscribe Twitch playlists (Channels), you have to link your Twitch account with VDownload. Go to Sources page to sign in.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Subscription_Adding_TwitchAccessTokenNotValid_Content" xml:space="preserve">
|
||||||
|
<value>There is a problem with linked Twitch account. Check Twitch login status in Sources page.</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
34
VDownload/Views/Sources/MainPage.xaml
Normal file
34
VDownload/Views/Sources/MainPage.xaml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<Page
|
||||||
|
x:Class="VDownload.Views.Sources.MainPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:VDownload.Views.Sources"
|
||||||
|
xmlns:cc="using:VDownload.Controls"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||||
|
|
||||||
|
|
||||||
|
<Page.Resources>
|
||||||
|
<ResourceDictionary Source="ms-appx:///Resources/Icons.xaml"/>
|
||||||
|
</Page.Resources>
|
||||||
|
|
||||||
|
|
||||||
|
<Grid Padding="20" RowSpacing="20">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock x:Uid="Sources_HeaderTextBlock" Grid.Row="0" FontSize="28" FontWeight="SemiBold"/>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1" Spacing="10">
|
||||||
|
<cc:SettingControl x:Name="TwitchSettingControl" x:Uid="Sources_TwitchSettingControl" Grid.Row="0" Icon="{StaticResource TwitchIcon}" Title="Twitch">
|
||||||
|
<cc:SettingControl.SettingContent>
|
||||||
|
<Button x:Name="TwitchSettingControlLoginButton" x:Uid="Sources_TwitchSettingControl_LoginButton" IsEnabled="False" Click="TwitchSettingControlLoginButton_Click"/>
|
||||||
|
</cc:SettingControl.SettingContent>
|
||||||
|
</cc:SettingControl>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
212
VDownload/Views/Sources/MainPage.xaml.cs
Normal file
212
VDownload/Views/Sources/MainPage.xaml.cs
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
using Microsoft.Toolkit.Uwp.Connectivity;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Windows.ApplicationModel.Resources;
|
||||||
|
using Windows.UI.WindowManagement;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Windows.UI.Xaml.Hosting;
|
||||||
|
using Windows.UI.Xaml.Navigation;
|
||||||
|
|
||||||
|
namespace VDownload.Views.Sources
|
||||||
|
{
|
||||||
|
public sealed partial class MainPage : Page
|
||||||
|
{
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public MainPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region EVENT HANDLERS
|
||||||
|
|
||||||
|
protected override async void OnNavigatedTo(NavigationEventArgs e)
|
||||||
|
{
|
||||||
|
Task[] checkingTasks = new Task[1];
|
||||||
|
|
||||||
|
checkingTasks[0] = CheckTwitch();
|
||||||
|
|
||||||
|
await Task.WhenAll(checkingTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private async Task CheckTwitch()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string twitchAccessToken = await Core.Services.Sources.Twitch.Helpers.Auth.ReadAccessTokenAsync();
|
||||||
|
#pragma warning disable IDE0042 // Deconstruct variable declaration
|
||||||
|
(bool IsValid, string Login, DateTime? ExpirationDate) twitchAccessTokenValidation = await Core.Services.Sources.Twitch.Helpers.Auth.ValidateAccessTokenAsync(twitchAccessToken);
|
||||||
|
#pragma warning restore IDE0042 // Deconstruct variable declaration
|
||||||
|
if (twitchAccessTokenValidation.IsValid)
|
||||||
|
{
|
||||||
|
TwitchSettingControl.Description = $"{ResourceLoader.GetForCurrentView().GetString("Sources_TwitchSettingControl_Description_LoggedIn")} {twitchAccessTokenValidation.Login}";
|
||||||
|
TwitchSettingControlLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("Sources_TwitchSettingControl_LoginButton_Content_LoggedIn");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (twitchAccessToken != null)
|
||||||
|
{
|
||||||
|
TwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("Sources_TwitchSettingControl_Description_AccessTokenExpired");
|
||||||
|
}
|
||||||
|
else if (twitchAccessTokenValidation.ExpirationDate < DateTime.Now)
|
||||||
|
{
|
||||||
|
TwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("Sources_TwitchSettingControl_Description_NotLoggedIn");
|
||||||
|
}
|
||||||
|
Debug.WriteLine(twitchAccessTokenValidation.ExpirationDate.Value.ToString("dd.MM.yyyy"));
|
||||||
|
TwitchSettingControlLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("Sources_TwitchSettingControl_LoginButton_Content_NotLoggedIn");
|
||||||
|
}
|
||||||
|
TwitchSettingControlLoginButton.IsEnabled = true;
|
||||||
|
}
|
||||||
|
catch (WebException ex)
|
||||||
|
{
|
||||||
|
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
|
||||||
|
{
|
||||||
|
TwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("Sources_TwitchSettingControl_Description_InternetNotAvailable");
|
||||||
|
TwitchSettingControlLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("Sources_TwitchSettingControl_LoginButton_Content_NotLoggedIn");
|
||||||
|
TwitchSettingControlLoginButton.IsEnabled = false;
|
||||||
|
}
|
||||||
|
else throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region TWITCH
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// TWITCH LOGIN BUTTON CLICKED
|
||||||
|
private async void TwitchSettingControlLoginButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string accessToken = await Core.Services.Sources.Twitch.Helpers.Auth.ReadAccessTokenAsync();
|
||||||
|
var accessTokenValidation = await Core.Services.Sources.Twitch.Helpers.Auth.ValidateAccessTokenAsync(accessToken);
|
||||||
|
if (accessTokenValidation.IsValid)
|
||||||
|
{
|
||||||
|
// Revoke access token
|
||||||
|
await Core.Services.Sources.Twitch.Helpers.Auth.RevokeAccessTokenAsync(accessToken);
|
||||||
|
|
||||||
|
// Delete access token
|
||||||
|
await Core.Services.Sources.Twitch.Helpers.Auth.DeleteAccessTokenAsync();
|
||||||
|
|
||||||
|
// Update Twitch SettingControl
|
||||||
|
TwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("Sources_TwitchSettingControl_Description_NotLoggedIn");
|
||||||
|
TwitchSettingControlLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("Sources_TwitchSettingControl_LoginButton_Content_NotLoggedIn");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Open new window
|
||||||
|
AppWindow TwitchAuthWindow = await AppWindow.TryCreateAsync();
|
||||||
|
TwitchAuthWindow.Title = "Twitch Authentication";
|
||||||
|
|
||||||
|
#pragma warning disable CS8305 // Type is for evaluation purposes only and is subject to change or removal in future updates.
|
||||||
|
WebView2 TwitchAuthWebView = new WebView2();
|
||||||
|
await TwitchAuthWebView.EnsureCoreWebView2Async();
|
||||||
|
TwitchAuthWebView.Source = Core.Services.Sources.Twitch.Helpers.Auth.AuthorizationUrl;
|
||||||
|
ElementCompositionPreview.SetAppWindowContent(TwitchAuthWindow, TwitchAuthWebView);
|
||||||
|
|
||||||
|
// NavigationStarting event (only when redirected)
|
||||||
|
TwitchAuthWebView.NavigationStarting += async (s, a) =>
|
||||||
|
{
|
||||||
|
if (new Uri(a.Uri).Host == Core.Services.Sources.Twitch.Helpers.Auth.RedirectUrl.Host)
|
||||||
|
{
|
||||||
|
// Close window
|
||||||
|
await TwitchAuthWindow.CloseAsync();
|
||||||
|
|
||||||
|
// Get response
|
||||||
|
string response = a.Uri.Replace(Core.Services.Sources.Twitch.Helpers.Auth.RedirectUrl.OriginalString, "");
|
||||||
|
|
||||||
|
if (response[1] == '#')
|
||||||
|
{
|
||||||
|
// Get access token
|
||||||
|
accessToken = response.Split('&')[0].Replace("/#access_token=", "");
|
||||||
|
|
||||||
|
// Check token
|
||||||
|
accessTokenValidation = await Core.Services.Sources.Twitch.Helpers.Auth.ValidateAccessTokenAsync(accessToken);
|
||||||
|
|
||||||
|
// Save token
|
||||||
|
await Core.Services.Sources.Twitch.Helpers.Auth.SaveAccessTokenAsync(accessToken);
|
||||||
|
|
||||||
|
// Update Twitch SettingControl
|
||||||
|
TwitchSettingControl.Description = $"{ResourceLoader.GetForCurrentView().GetString("Sources_TwitchSettingControl_Description_LoggedIn")} {accessTokenValidation.Login}";
|
||||||
|
TwitchSettingControlLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("Sources_TwitchSettingControl_LoginButton_Content_LoggedIn");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Ignored errors
|
||||||
|
string[] ignoredErrors = new[]
|
||||||
|
{
|
||||||
|
"The user denied you access",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Errors translation
|
||||||
|
Dictionary<string, string> errorsTranslation = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get error info
|
||||||
|
string errorInfo = (response.Split('&')[1].Replace("error_description=", "")).Replace('+', ' ');
|
||||||
|
if (!ignoredErrors.Contains(errorInfo))
|
||||||
|
{
|
||||||
|
// Error
|
||||||
|
ContentDialog loginErrorDialog = new ContentDialog
|
||||||
|
{
|
||||||
|
Title = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginErrorDialogTitle"),
|
||||||
|
Content = errorsTranslation.Keys.Contains(errorInfo) ? errorsTranslation[errorInfo] : $"{ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginErrorDialogDescriptionUnknown")} ({errorInfo})",
|
||||||
|
CloseButtonText = ResourceLoader.GetForCurrentView().GetString("CloseErrorDialogButtonText"),
|
||||||
|
};
|
||||||
|
await loginErrorDialog.ShowAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#pragma warning restore CS8305 // Type is for evaluation purposes only and is subject to change or removal in future updates.
|
||||||
|
|
||||||
|
await TwitchAuthWindow.TryShowAsync();
|
||||||
|
|
||||||
|
// Clear cache
|
||||||
|
TwitchAuthWebView.CoreWebView2.CookieManager.DeleteAllCookies();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (WebException wex)
|
||||||
|
{
|
||||||
|
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
|
||||||
|
{
|
||||||
|
TwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionInternetConnectionError");
|
||||||
|
TwitchSettingControlLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn");
|
||||||
|
TwitchSettingControlLoginButton.IsEnabled = false;
|
||||||
|
ContentDialog internetAccessErrorDialog = new ContentDialog
|
||||||
|
{
|
||||||
|
Title = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginErrorDialogTitle"),
|
||||||
|
Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginErrorDialogDescriptionInternetConnectionError"),
|
||||||
|
CloseButtonText = ResourceLoader.GetForCurrentView().GetString("CloseErrorDialogButtonText"),
|
||||||
|
};
|
||||||
|
await internetAccessErrorDialog.ShowAsync();
|
||||||
|
}
|
||||||
|
else throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<UserControl
|
||||||
|
x:Class="VDownload.Views.Subscriptions.Controls.SubscriptionPanel"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:VDownload.Views.Subscriptions.Controls"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="300"
|
||||||
|
d:DesignWidth="400"
|
||||||
|
CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="ms-appx:///Resources/Colors.xaml"/>
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<Grid Background="{ThemeResource SubscriptionsSubscriptionPanelBackgroundColor}" Padding="5" ColumnSpacing="5">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock x:Name="TitleTextBlock" Grid.Column="1" FontSize="16" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock x:Name="CountTextBlock" x:Uid="Subscriptions_SubscriptionPanel_CountTextBlock" Grid.Column="2" FontSize="12" VerticalAlignment="Center"/>
|
||||||
|
<AppBarButton x:Name="UpdateButton" Grid.Column="3" Width="40" Height="48" Margin="0,-4,0,-4" Icon="Sync" Click="UpdateButton_Click"/>
|
||||||
|
<AppBarButton x:Name="RemoveButton" Grid.Column="4" Width="40" Height="48" Margin="0,-4,0,-4" Icon="Delete" Click="RemoveButton_Click"/>
|
||||||
|
<AppBarButton x:Name="SourceButton" Grid.Column="5" Width="40" Height="48" Margin="0,-4,0,-4" Icon="Link" Click="SourceButton_Click"/>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Core.Services;
|
||||||
|
using Windows.ApplicationModel.Resources;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
using Windows.Storage;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Windows.UI.Xaml.Controls.Primitives;
|
||||||
|
using Windows.UI.Xaml.Data;
|
||||||
|
using Windows.UI.Xaml.Input;
|
||||||
|
using Windows.UI.Xaml.Media;
|
||||||
|
using Windows.UI.Xaml.Navigation;
|
||||||
|
|
||||||
|
namespace VDownload.Views.Subscriptions.Controls
|
||||||
|
{
|
||||||
|
public sealed partial class SubscriptionPanel : UserControl
|
||||||
|
{
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public SubscriptionPanel(Subscription subscription, StorageFile subscriptionFile)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
Subscription = subscription;
|
||||||
|
SubscriptionFile = subscriptionFile;
|
||||||
|
|
||||||
|
TitleTextBlock.Text = Subscription.Playlist.Name;
|
||||||
|
SourceButton.Icon = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{Subscription.Playlist.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
private Subscription Subscription { get; set; }
|
||||||
|
private StorageFile SubscriptionFile { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public async Task UpdateNewVideosCounterAsync()
|
||||||
|
{
|
||||||
|
CountTextBlock.Text = (await Subscription.GetNewVideosAsync()).Length.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region EVENT HANDLERS
|
||||||
|
|
||||||
|
private async void RemoveButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
await SubscriptionsCollectionManagement.DeleteSubscriptionFileAsync(SubscriptionFile);
|
||||||
|
((StackPanel)Parent).Children.Remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void UpdateButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
CountTextBlock.Text = ResourceLoader.GetForCurrentView().GetString("Subscriptions_SubscriptionPanel_CountTextBlock_SyncText");
|
||||||
|
await Subscription.GetNewVideosAndUpdateAsync();
|
||||||
|
await SubscriptionsCollectionManagement.UpdateSubscriptionFileAsync(Subscription, SubscriptionFile);
|
||||||
|
CountTextBlock.Text = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void SourceButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
await Windows.System.Launcher.LaunchUriAsync(Subscription.Playlist.Url);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
44
VDownload/Views/Subscriptions/MainPage.xaml
Normal file
44
VDownload/Views/Subscriptions/MainPage.xaml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<Page
|
||||||
|
x:Class="VDownload.Views.Subscriptions.MainPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:VDownload.Views.Subscriptions"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||||
|
|
||||||
|
<Page.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="ms-appx:///Resources/Colors.xaml"/>
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Page.Resources>
|
||||||
|
|
||||||
|
<Grid Padding="20" RowSpacing="20">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock x:Uid="Subscriptions_HeaderTextBlock" Grid.Row="0" FontSize="28" FontWeight="SemiBold"/>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="1">
|
||||||
|
<StackPanel x:Name="SubscriptionsListStackPanel" VerticalAlignment="Stretch" Spacing="10"/>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<Grid Grid.Row="2" VerticalAlignment="Center" Padding="8" CornerRadius="{ThemeResource ControlCornerRadius}" ColumnSpacing="10" Background="{ThemeResource SubscriptionsAddingPanelBackgroundColor}">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBox x:Name="AddingTextBox" x:Uid="Subscriptions_AddingTextBox" Grid.Column="0" VerticalAlignment="Center"/>
|
||||||
|
<Button x:Name="AddingButton" x:Uid="Subscriptions_AddingButton" Grid.Column="1" Click="AddingButton_Click"/>
|
||||||
|
<muxc:ProgressRing x:Name="AddingProgressRing" Grid.Column="2" Visibility="Collapsed" Width="15" Height="15" VerticalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
148
VDownload/Views/Subscriptions/MainPage.xaml.cs
Normal file
148
VDownload/Views/Subscriptions/MainPage.xaml.cs
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
using Microsoft.Toolkit.Uwp.Connectivity;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Runtime.Serialization.Formatters.Binary;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VDownload.Core.Exceptions;
|
||||||
|
using VDownload.Core.Interfaces;
|
||||||
|
using VDownload.Core.Services;
|
||||||
|
using VDownload.Core.Services.Sources;
|
||||||
|
using VDownload.Views.Subscriptions.Controls;
|
||||||
|
using Windows.ApplicationModel.Resources;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
using Windows.Storage;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
using Windows.UI.Xaml.Controls;
|
||||||
|
using Windows.UI.Xaml.Controls.Primitives;
|
||||||
|
using Windows.UI.Xaml.Data;
|
||||||
|
using Windows.UI.Xaml.Input;
|
||||||
|
using Windows.UI.Xaml.Media;
|
||||||
|
using Windows.UI.Xaml.Navigation;
|
||||||
|
|
||||||
|
namespace VDownload.Views.Subscriptions
|
||||||
|
{
|
||||||
|
public sealed partial class MainPage : Page
|
||||||
|
{
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public MainPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region EVENT HANDLERS
|
||||||
|
|
||||||
|
protected override async void OnNavigatedTo(NavigationEventArgs e)
|
||||||
|
{
|
||||||
|
(Subscription Subscription, StorageFile SubscriptionFile)[] subscriptions = await SubscriptionsCollectionManagement.GetSubscriptionsAsync();
|
||||||
|
foreach((Subscription Subscription, StorageFile SubscriptionFile) subscription in subscriptions)
|
||||||
|
{
|
||||||
|
AddSubscriptionToList(subscription.Subscription, subscription.SubscriptionFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void AddingButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
async Task ShowDialog(string localErrorKey)
|
||||||
|
{
|
||||||
|
ContentDialog errorDialog = new ContentDialog
|
||||||
|
{
|
||||||
|
Title = ResourceLoader.GetForCurrentView("DialogResources").GetString("Subscription_Adding_Base_Title"),
|
||||||
|
Content = ResourceLoader.GetForCurrentView("DialogResources").GetString($"Subscription_Adding_{localErrorKey}_Content"),
|
||||||
|
CloseButtonText = ResourceLoader.GetForCurrentView("DialogResources").GetString("Base_CloseButtonText"),
|
||||||
|
};
|
||||||
|
await errorDialog.ShowAsync();
|
||||||
|
AddingProgressRing.Visibility = Visibility.Collapsed;
|
||||||
|
AddingButton.IsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddingProgressRing.Visibility = Visibility.Visible;
|
||||||
|
AddingButton.IsEnabled = false;
|
||||||
|
|
||||||
|
IPlaylist playlist = Source.GetPlaylist(AddingTextBox.Text); // TODO: Change name of class (method returns Playlist object, not source of playlist)
|
||||||
|
|
||||||
|
if (playlist is null)
|
||||||
|
{
|
||||||
|
await ShowDialog("PlaylistNotFound");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await playlist.GetMetadataAsync();
|
||||||
|
await playlist.GetVideosAsync();
|
||||||
|
}
|
||||||
|
catch (MediaNotFoundException)
|
||||||
|
{
|
||||||
|
await ShowDialog("PlaylistNotFound");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (TwitchAccessTokenNotFoundException)
|
||||||
|
{
|
||||||
|
await ShowDialog("TwitchAccessTokenNotFound");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (TwitchAccessTokenNotValidException)
|
||||||
|
{
|
||||||
|
await ShowDialog("TwitchAccessTokenNotValid");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (WebException)
|
||||||
|
{
|
||||||
|
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
|
||||||
|
{
|
||||||
|
await ShowDialog("InternetNotAvailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Subscription subscription = new Subscription(playlist);
|
||||||
|
|
||||||
|
StorageFile subscriptionFile = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
subscriptionFile = await SubscriptionsCollectionManagement.CreateSubscriptionFileAsync(subscription);
|
||||||
|
}
|
||||||
|
catch (SubscriptionExistsException)
|
||||||
|
{
|
||||||
|
await ShowDialog("SubscriptionExists");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddSubscriptionToList(subscription, subscriptionFile);
|
||||||
|
|
||||||
|
AddingProgressRing.Visibility = Visibility.Collapsed;
|
||||||
|
AddingButton.IsEnabled = true;
|
||||||
|
AddingTextBox.Text = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
private void AddSubscriptionToList(Subscription subscription, StorageFile subscriptionFile)
|
||||||
|
{
|
||||||
|
SubscriptionPanel subscriptionPanel = new SubscriptionPanel(subscription, subscriptionFile);
|
||||||
|
SubscriptionsListStackPanel.Children.Add(subscriptionPanel);
|
||||||
|
subscriptionPanel.UpdateNewVideosCounterAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user