Video adding and subscriptions finished

This commit is contained in:
2022-05-11 20:50:50 +02:00
Unverified
parent 7a57fb65f3
commit 7acaeb24de
92 changed files with 3917 additions and 3314 deletions

View File

@@ -0,0 +1,14 @@
namespace VDownload.Core.Enums
{
public enum DownloadTaskStatus
{
Idle,
Scheduled,
Queued,
Downloading,
Processing,
Finalizing,
EndedSuccessfully,
EndedUnsuccessfully,
}
}

View File

@@ -0,0 +1,9 @@
namespace VDownload.Core.Enums
{
public enum DownloadTasksAddingRequestSource
{
Video,
Playlist,
Subscriptions,
}
}

View File

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

View File

@@ -1,14 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Core.Enums
{
public enum TaskAddingRequestSource
{
Video,
Playlist
}
}

View File

@@ -1,10 +0,0 @@
namespace VDownload.Core.Enums
{
public enum TaskStatus
{
Idle,
Scheduled,
Waiting,
InProgress
}
}

View File

@@ -0,0 +1,24 @@
using System;
namespace VDownload.Core.EventArgs
{
public class DownloadTaskEndedSuccessfullyEventArgs : System.EventArgs
{
#region CONSTRUCTORS
public DownloadTaskEndedSuccessfullyEventArgs(TimeSpan elapsedTime)
{
ElapsedTime = elapsedTime;
}
#endregion
#region PROPERTIES
public TimeSpan ElapsedTime { get; private set; }
#endregion
}
}

View File

@@ -0,0 +1,24 @@
using System;
namespace VDownload.Core.EventArgs
{
public class DownloadTaskEndedUnsuccessfullyEventArgs : System.EventArgs
{
#region CONSTRUCTORS
public DownloadTaskEndedUnsuccessfullyEventArgs(Exception exception)
{
Exception = exception;
}
#endregion
#region PROPERTIES
public Exception Exception { get; private set; }
#endregion
}
}

View File

@@ -0,0 +1,24 @@
using System;
namespace VDownload.Core.EventArgs
{
public class DownloadTaskScheduledEventArgs : System.EventArgs
{
#region CONSTRUCTORS
public DownloadTaskScheduledEventArgs(DateTime scheduledFor)
{
ScheduledFor = scheduledFor;
}
#endregion
#region PROPERTIES
public DateTime ScheduledFor { get; private set; }
#endregion
}
}

View File

@@ -0,0 +1,27 @@
using VDownload.Core.Enums;
using VDownload.Core.Structs;
namespace VDownload.Core.EventArgs
{
public class DownloadTasksAddingRequestedEventArgs : System.EventArgs
{
#region CONSTRUCTORS
public DownloadTasksAddingRequestedEventArgs(DownloadTask[] downloadTasks, DownloadTasksAddingRequestSource requestSource)
{
DownloadTasks = downloadTasks;
RequestSource = requestSource;
}
#endregion
#region PROPERTIES
public DownloadTask[] DownloadTasks { get; private set; }
public DownloadTasksAddingRequestSource RequestSource { get; private set; }
#endregion
}
}

View File

@@ -4,6 +4,21 @@ namespace VDownload.Core.EventArgs
{
public class PlaylistSearchSuccessedEventArgs : System.EventArgs
{
public IPlaylist PlaylistService { get; set; }
#region CONSTRUCTORS
public PlaylistSearchSuccessedEventArgs(IPlaylist playlist)
{
Playlist = playlist;
}
#endregion
#region PROPERTIES
public IPlaylist Playlist { get; private set; }
#endregion
}
}

View File

@@ -16,8 +16,8 @@
#region PROPERTIES
public double Progress { get; set; }
public bool IsCompleted { get; set; }
public double Progress { get; private set; }
public bool IsCompleted { get; private set; }
#endregion
}

View File

@@ -0,0 +1,24 @@
using VDownload.Core.Interfaces;
namespace VDownload.Core.EventArgs
{
public class SubscriptionLoadSuccessedEventArgs : System.EventArgs
{
#region CONSTRUCTORS
public SubscriptionLoadSuccessedEventArgs(IVideo[] videos)
{
Videos = videos;
}
#endregion
#region PROPERTIES
public IVideo[] Videos { get; private set; }
#endregion
}
}

View File

@@ -1,11 +0,0 @@
using VDownload.Core.Enums;
using VDownload.Core.Structs;
namespace VDownload.Core.EventArgs
{
public class TasksAddingRequestedEventArgs : System.EventArgs
{
public TaskData[] TaskData { get; set; }
public TaskAddingRequestSource RequestSource { get; set; }
}
}

View File

@@ -4,6 +4,21 @@ namespace VDownload.Core.EventArgs
{
public class VideoSearchSuccessedEventArgs : System.EventArgs
{
#region CONSTRUCTORS
public VideoSearchSuccessedEventArgs(IVideo video)
{
Video = video;
}
#endregion
#region PROPERTIES
public IVideo Video { get; set; }
#endregion
}
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Linq;
using System.Text;
namespace VDownload.Core.Extensions
{
public static class TimeSpanExtension
{
// To string (TH:)MM:SS
public static string ToStringOptTHBaseMMSS(this TimeSpan timeSpan, params TimeSpan[] formatBase)
{
StringBuilder formattedTimeSpan = new StringBuilder();
int maxTHLength = 0;
foreach (TimeSpan format in formatBase.Concat(new TimeSpan[] { timeSpan }))
{
int THLength = Math.Floor(format.TotalHours) > 0 ? Math.Floor(timeSpan.TotalHours).ToString().Length : 0;
if (THLength > maxTHLength)
{
maxTHLength = THLength;
}
}
if (maxTHLength > 0)
{
formattedTimeSpan.Append($"{((int)Math.Floor(timeSpan.TotalHours)).ToString($"D{maxTHLength}")}:");
}
formattedTimeSpan.Append(maxTHLength == 0 ? $"{timeSpan.Minutes}:" : $"{timeSpan.Minutes:00}:");
formattedTimeSpan.Append($"{timeSpan.Seconds:00}");
return formattedTimeSpan.ToString();
}
// To string ((TH:)MM:)SS
public static string ToStringOptTHMMBaseSS(this TimeSpan timeSpan, params TimeSpan[] formatBase)
{
StringBuilder formattedTimeSpan = new StringBuilder();
int maxTHLength = 0;
foreach (TimeSpan format in formatBase.Concat(new TimeSpan[] { timeSpan }))
{
int THLength = Math.Floor(format.TotalHours) > 0 ? Math.Floor(timeSpan.TotalHours).ToString().Length : 0;
if (THLength > maxTHLength)
{
maxTHLength = THLength;
}
}
if (maxTHLength > 0)
{
formattedTimeSpan.Append($"{((int)Math.Floor(timeSpan.TotalHours)).ToString($"D{maxTHLength}")}:");
}
bool MM = false;
if (Math.Floor(timeSpan.TotalMinutes) > 0 || maxTHLength > 0)
{
formattedTimeSpan.Append(maxTHLength > 0 ? $"{timeSpan.Minutes:00}:" : $"{timeSpan.Minutes}:");
MM = true;
}
formattedTimeSpan.Append(MM ? $"{timeSpan.Seconds:00}" : $"{timeSpan.Seconds}");
return formattedTimeSpan.ToString();
}
}
}

View File

@@ -9,9 +9,8 @@ namespace VDownload.Core.Interfaces
{
#region PROPERTIES
// PLAYLIST PROPERTIES
string ID { get; }
PlaylistSource Source { get; }
string ID { get; }
Uri Url { get; }
string Name { get; }
IVideo[] Videos { get; }
@@ -22,10 +21,8 @@ namespace VDownload.Core.Interfaces
#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);

View File

@@ -12,11 +12,15 @@ namespace VDownload.Core.Interfaces
{
#region PROPERTIES
// VIDEO PROPERTIES
VideoSource Source { get; }
string ID { get; }
Uri Url { get; }
Metadata Metadata { get; }
string Title { get; }
string Author { get; }
DateTime Date { get; }
TimeSpan Duration { get; }
long Views { get; }
Uri Thumbnail { get; }
BaseStream[] BaseStreams { get; }
#endregion
@@ -25,20 +29,17 @@ namespace VDownload.Core.Interfaces
#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);
Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TrimData trim, CancellationToken cancellationToken = default);
#endregion
#region EVENT HANDLERS
#region EVENTS
event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;

View File

@@ -10,10 +10,6 @@ namespace VDownload.Core.Services
{
#region CONSTANTS
// SETTINGS CONTAINER
private static readonly ApplicationDataContainer SettingsContainer = ApplicationData.Current.LocalSettings;
// DEFAULT SETTINGS
private static readonly Dictionary<string, object> DefaultSettings = new Dictionary<string, object>()
{
{ "delete_temp_on_start", true },
@@ -45,37 +41,33 @@ namespace VDownload.Core.Services
#region METHODS
#region PUBLIC METHODS
// GET VALUE
public static object GetValue(string key)
{
return SettingsContainer.Values[key];
return ApplicationData.Current.LocalSettings.Values[key];
}
// SET VALUE
public static void SetValue(string key, object value)
{
SettingsContainer.Values[key] = value;
ApplicationData.Current.LocalSettings.Values[key] = value;
}
// SET DEFAULT
public static void SetDefault()
{
foreach (KeyValuePair<string, object> s in DefaultSettings)
{
SettingsContainer.Values[s.Key] = s.Value;
ApplicationData.Current.LocalSettings.Values[s.Key] = s.Value;
}
}
// REBUILD
public static void Rebuild()
{
foreach (KeyValuePair<string, object> s in DefaultSettings)
{
if (!SettingsContainer.Values.ContainsKey(s.Key))
if (!ApplicationData.Current.LocalSettings.Values.ContainsKey(s.Key))
{
SettingsContainer.Values[s.Key] = s.Value;
ApplicationData.Current.LocalSettings.Values[s.Key] = s.Value;
}
}
}

View File

@@ -0,0 +1,176 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.EventArgs;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using Windows.ApplicationModel.ExtendedExecution;
using Windows.Storage;
using Windows.Storage.AccessCache;
namespace VDownload.Core.Structs
{
public class DownloadTask
{
#region CONSTRUCTORS
public DownloadTask(string id, IVideo video, MediaType mediaType, BaseStream selectedStream, TrimData trim, OutputFile file, double schedule)
{
Id = id;
Video = video;
MediaType = mediaType;
SelectedStream = selectedStream;
Trim = trim;
File = file;
Schedule = schedule;
Status = DownloadTaskStatus.Idle;
CancellationTokenSource = new CancellationTokenSource();
}
#endregion
#region PROPERTIES
public string Id { get; set; }
public IVideo Video { get; set; }
public MediaType MediaType { get; set; }
public BaseStream SelectedStream { get; set; }
public TrimData Trim { get; set; }
public OutputFile File { get; set; }
public double Schedule { get; set; }
public DownloadTaskStatus Status { get; private set; }
public CancellationTokenSource CancellationTokenSource { get; private set; }
public DateTime ScheduledFor { get; private set; }
public double DownloadingProgress { get; private set; }
public double ProcessingProgress { get; private set; }
public TimeSpan ElapsedTime { get; private set; }
public Exception Exception { get; private set; }
#endregion
#region METHODS
public async Task Run(bool delayWhenOnMeteredConnection)
{
StatusChanged.Invoke(this, System.EventArgs.Empty);
CancellationTokenSource = new CancellationTokenSource();
if (Schedule > 0)
{
ScheduledFor = DateTime.Now.AddMinutes(Schedule);
Status = DownloadTaskStatus.Scheduled;
StatusChanged.Invoke(this, System.EventArgs.Empty);
while (DateTime.Now < ScheduledFor && !CancellationTokenSource.Token.IsCancellationRequested) await Task.Delay(100);
}
Status = DownloadTaskStatus.Queued;
StatusChanged.Invoke(this, System.EventArgs.Empty);
await DownloadTasksCollectionManagement.WaitInQueue(delayWhenOnMeteredConnection, CancellationTokenSource.Token);
if (!CancellationTokenSource.Token.IsCancellationRequested)
{
DownloadingProgress = 0;
Status = DownloadTaskStatus.Downloading;
StatusChanged.Invoke(this, System.EventArgs.Empty);
StorageFolder tempFolder;
if ((bool)Config.GetValue("custom_temp_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("custom_temp_location"))
{
tempFolder = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("custom_temp_location");
}
else
{
tempFolder = ApplicationData.Current.TemporaryFolder;
}
tempFolder = await tempFolder.CreateFolderAsync(Id);
bool endedWithError = false;
try
{
CancellationTokenSource.Token.ThrowIfCancellationRequested();
Stopwatch taskStopwatch = Stopwatch.StartNew();
ExtendedExecutionSession session = new ExtendedExecutionSession { Reason = ExtendedExecutionReason.Unspecified };
await session.RequestExtensionAsync();
CancellationTokenSource.Token.ThrowIfCancellationRequested();
Video.DownloadingProgressChanged += DownloadingProgressChanged;
Video.ProcessingProgressChanged += ProcessingProgressChanged;
StorageFile tempOutputFile = await Video.DownloadAndTranscodeAsync(tempFolder, SelectedStream, File.Extension, MediaType, Trim, CancellationTokenSource.Token);
session.Dispose();
Status = DownloadTaskStatus.Finalizing;
StatusChanged.Invoke(this, System.EventArgs.Empty);
StorageFile outputFile = await File.Create();
CancellationTokenSource.Token.ThrowIfCancellationRequested();
await tempOutputFile.MoveAndReplaceAsync(outputFile);
taskStopwatch.Stop();
ElapsedTime = taskStopwatch.Elapsed;
Status = DownloadTaskStatus.EndedSuccessfully;
StatusChanged.Invoke(this, System.EventArgs.Empty);
}
catch (Exception ex)
{
endedWithError = true;
Exception = ex;
Status = DownloadTaskStatus.EndedUnsuccessfully;
StatusChanged.Invoke(this, System.EventArgs.Empty);
}
finally
{
if (!endedWithError || (bool)Config.GetValue("delete_task_temp_when_ended_with_error"))
{
// Delete temporary files
await tempFolder.DeleteAsync();
}
}
}
else
{
Exception = new OperationCanceledException(CancellationTokenSource.Token);
Status = DownloadTaskStatus.EndedUnsuccessfully;
StatusChanged.Invoke(this, System.EventArgs.Empty);
}
}
private void DownloadingProgressChanged(object sender, ProgressChangedEventArgs e)
{
DownloadingProgress = e.Progress;
Status = DownloadTaskStatus.Downloading;
StatusChanged.Invoke(this, System.EventArgs.Empty);
}
private void ProcessingProgressChanged(object sender, ProgressChangedEventArgs e)
{
ProcessingProgress = e.Progress;
Status = DownloadTaskStatus.Processing;
StatusChanged.Invoke(this, System.EventArgs.Empty);
}
#endregion
#region EVENT
public event EventHandler StatusChanged;
#endregion
}
}

View File

@@ -0,0 +1,64 @@
using Microsoft.Toolkit.Uwp.Connectivity;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using VDownload.Core.Structs;
namespace VDownload.Core.Services
{
public static class DownloadTasksCollectionManagement
{
#region PROPERTIES
private static readonly Dictionary<string, DownloadTask> ChangeableDownloadTasksCollection = new Dictionary<string, DownloadTask>();
public static readonly ReadOnlyDictionary<string, DownloadTask> DownloadTasksCollection = new ReadOnlyDictionary<string, DownloadTask>(ChangeableDownloadTasksCollection);
#endregion
#region PUBLIC METHODS
public static string GenerateID()
{
char[] idChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
int idLength = 10;
string id;
do
{
id = "";
while (id.Length < idLength)
{
id += idChars[new Random().Next(0, idChars.Length)];
}
} while (DownloadTasksCollection.Keys.Contains(id));
return id;
}
public static void Add(DownloadTask downloadTask, string id)
{
ChangeableDownloadTasksCollection[id] = downloadTask;
}
public static void Remove(string id)
{
ChangeableDownloadTasksCollection.Remove(id);
}
public static async Task WaitInQueue(bool delayWhenOnMeteredConnection, CancellationToken cancellationToken = default)
{
while ((ChangeableDownloadTasksCollection.Values.Where((DownloadTask task) => task.Status == Enums.DownloadTaskStatus.Downloading || task.Status == Enums.DownloadTaskStatus.Processing || task.Status == Enums.DownloadTaskStatus.Finalizing).Count() >= (int)Config.GetValue("max_active_video_task") || (delayWhenOnMeteredConnection && NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)) && !cancellationToken.IsCancellationRequested)
{
await Task.Delay(100);
}
}
#endregion
}
}

View File

@@ -3,6 +3,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.Structs;
using Windows.Foundation;
using Windows.Media.Editing;
using Windows.Media.MediaProperties;
@@ -14,105 +15,83 @@ namespace VDownload.Core.Services
{
public class MediaProcessor
{
#region STANDARD METHODS
#region PUBLIC METHODS
// SINGLE AUDIO & VIDEO FILE PROCESSING
public async Task Run(StorageFile mediaFile, MediaFileExtension extension, MediaType mediaType, StorageFile outputFile, TimeSpan? trimStart = null, TimeSpan? trimEnd = null, CancellationToken cancellationToken = default)
public async Task Run(StorageFile mediaFile, MediaFileExtension extension, MediaType mediaType, StorageFile outputFile, TrimData trim, CancellationToken cancellationToken = default)
{
// Invoke event at start
cancellationToken.ThrowIfCancellationRequested();
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
// Init transcoder
MediaTranscoder mediaTranscoder = new MediaTranscoder
{
HardwareAccelerationEnabled = (bool)Config.GetValue("media_transcoding_use_hardware_acceleration"),
VideoProcessingAlgorithm = (MediaVideoProcessingAlgorithm)Config.GetValue("media_transcoding_algorithm"),
};
if (trimStart != null) mediaTranscoder.TrimStartTime = (TimeSpan)trimStart;
if (trimEnd != null) mediaTranscoder.TrimStopTime = (TimeSpan)trimEnd;
if (trim.Start != null) mediaTranscoder.TrimStartTime = trim.Start;
if (trim.End != null) mediaTranscoder.TrimStopTime = trim.End;
// Start transcoding operation
using (IRandomAccessStream openedOutputFile = await outputFile.OpenAsync(FileAccessMode.ReadWrite))
{
// Prepare transcode task
PrepareTranscodeResult transcodingPreparated = await mediaTranscoder.PrepareStreamTranscodeAsync(await mediaFile.OpenAsync(FileAccessMode.Read), openedOutputFile, await GetMediaEncodingProfile(mediaFile, extension, mediaType));
// Start transcoding
IAsyncActionWithProgress<double> transcodingTask = transcodingPreparated.TranscodeAsync();
await transcodingTask.AsTask(cancellationToken, new Progress<double>((percent) => { ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(percent)); }));
cancellationToken.ThrowIfCancellationRequested();
// Finalizing
await openedOutputFile.FlushAsync();
transcodingTask.Close();
}
// Invoke event at end
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
}
// SEPARATE AUDIO & VIDEO FILES PROCESSING
public async Task Run(StorageFile audioFile, StorageFile videoFile, VideoFileExtension extension, StorageFile outputFile, TimeSpan? trimStart = null, TimeSpan? trimEnd = null, CancellationToken cancellationToken = default)
public async Task Run(StorageFile audioFile, StorageFile videoFile, VideoFileExtension extension, StorageFile outputFile, TrimData trim, CancellationToken cancellationToken = default)
{
// Invoke event at start
cancellationToken.ThrowIfCancellationRequested();
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
// Init editor
MediaComposition mediaEditor = new MediaComposition();
// Add media files
cancellationToken.ThrowIfCancellationRequested();
Task<MediaClip> getVideoFileTask = MediaClip.CreateFromFileAsync(videoFile).AsTask();
Task<BackgroundAudioTrack> getAudioFileTask = BackgroundAudioTrack.CreateFromFileAsync(audioFile).AsTask();
await Task.WhenAll(getVideoFileTask, getAudioFileTask);
MediaClip videoElement = getVideoFileTask.Result;
if (trimStart != null) videoElement.TrimTimeFromStart = (TimeSpan)trimStart;
if (trimEnd != null) videoElement.TrimTimeFromEnd = (TimeSpan)trimEnd;
if (trim.Start != null) videoElement.TrimTimeFromStart = trim.Start;
if (trim.End != null) videoElement.TrimTimeFromEnd = trim.End;
BackgroundAudioTrack audioElement = getAudioFileTask.Result;
if (trimStart != null) audioElement.TrimTimeFromStart = (TimeSpan)trimStart;
if (trimEnd != null) audioElement.TrimTimeFromEnd = (TimeSpan)trimEnd;
if (trim.Start != null) audioElement.TrimTimeFromStart = trim.Start;
if (trim.End != null) audioElement.TrimTimeFromEnd = trim.End;
mediaEditor.Clips.Add(videoElement);
mediaEditor.BackgroundAudioTracks.Add(audioElement);
// Start rendering operation
var renderOperation = mediaEditor.RenderToFileAsync(outputFile, (MediaTrimmingPreference)Config.GetValue("media_editing_algorithm"), await GetMediaEncodingProfile(videoFile, audioFile, (MediaFileExtension)extension, MediaType.AudioVideo));
renderOperation.Progress += (info, progress) => { ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(progress)); };
cancellationToken.ThrowIfCancellationRequested();
await renderOperation.AsTask(cancellationToken);
// Invoke event at end
ProgressChanged(this, new EventArgs.ProgressChangedEventArgs(100, true));
}
// AUDIO FILE PROCESSING
public async Task Run(StorageFile audioFile, AudioFileExtension extension, StorageFile outputFile, TimeSpan? trimStart = null, TimeSpan? trimEnd = null, CancellationToken cancellationToken = default)
public async Task Run(StorageFile audioFile, AudioFileExtension extension, StorageFile outputFile, TrimData trim, CancellationToken cancellationToken = default)
{
await Run(audioFile, (MediaFileExtension)extension, MediaType.OnlyAudio, outputFile, trimStart, trimEnd, cancellationToken);
await Run(audioFile, (MediaFileExtension)extension, MediaType.OnlyAudio, outputFile, trim, cancellationToken);
}
// VIDEO FILE PROCESSING
public async Task Run(StorageFile videoFile, VideoFileExtension extension, StorageFile outputFile, TimeSpan? trimStart = null, TimeSpan? trimEnd = null, CancellationToken cancellationToken = default)
public async Task Run(StorageFile videoFile, VideoFileExtension extension, StorageFile outputFile, TrimData trim, CancellationToken cancellationToken = default)
{
await Run(videoFile, (MediaFileExtension)extension, MediaType.OnlyVideo, outputFile, trimStart, trimEnd, cancellationToken);
await Run(videoFile, (MediaFileExtension)extension, MediaType.OnlyVideo, outputFile, trim, cancellationToken);
}
#endregion
#region LOCAL METHODS
#region PRIVATE METHODS
// GET ENCODING PROFILE
private static async Task<MediaEncodingProfile> GetMediaEncodingProfile(StorageFile videoFile, StorageFile audioFile, MediaFileExtension extension, MediaType mediaType)
{
// Create profile object
MediaEncodingProfile profile;
// Set extension
switch (extension)
{
default:
@@ -127,7 +106,6 @@ namespace VDownload.Core.Services
case MediaFileExtension.WMA: profile = MediaEncodingProfile.CreateWma(AudioEncodingQuality.High); break;
}
// Set video parameters
if (mediaType != MediaType.OnlyAudio)
{
var videoData = await videoFile.Properties.GetVideoPropertiesAsync();
@@ -135,16 +113,12 @@ namespace VDownload.Core.Services
profile.Video.Width = videoData.Width;
profile.Video.Bitrate = videoData.Bitrate;
}
// Set audio parameters
if (mediaType != MediaType.OnlyVideo)
{
var audioData = await audioFile.Properties.GetMusicPropertiesAsync();
profile.Audio.Bitrate = audioData.Bitrate;
if (mediaType == MediaType.AudioVideo) profile.Video.Bitrate -= audioData.Bitrate;
}
// Delete audio tracks
if (mediaType == MediaType.OnlyVideo)
{
var audioTracks = profile.GetAudioTracks();
@@ -152,7 +126,6 @@ namespace VDownload.Core.Services
profile.SetAudioTracks(audioTracks.AsEnumerable());
}
// Return profile
return profile;
}
private static async Task<MediaEncodingProfile> GetMediaEncodingProfile(StorageFile audioVideoFile, MediaFileExtension extension, MediaType mediaType)
@@ -164,7 +137,7 @@ namespace VDownload.Core.Services
#region EVENT HANDLERS
#region EVENTS
public event EventHandler<EventArgs.ProgressChangedEventArgs> ProgressChanged;

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using Windows.Storage;
namespace VDownload.Core.Services
{
public class OutputFile
{
#region CONSTRUCTORS
public OutputFile(string name, MediaFileExtension extension, StorageFolder location)
{
Name = name;
Extension = extension;
Location = location;
}
public OutputFile(string name, MediaFileExtension extension)
{
Name = name;
Extension = extension;
Location = null;
}
#endregion
#region PROPERTIES
public string Name { get; private set; }
public MediaFileExtension Extension { get; private set; }
public StorageFolder Location { get; private set; }
#endregion
#region PUBLIC METHODS
public async Task<StorageFile> Create()
{
string filename = $"{Name}.{Extension.ToString().ToLower()}";
CreationCollisionOption collisionOption = (bool)Config.GetValue("replace_output_file_if_exists") ? CreationCollisionOption.ReplaceExisting : CreationCollisionOption.GenerateUniqueName;
return await(!(Location is null) ? Location.CreateFileAsync(filename, collisionOption) : DownloadsFolder.CreateFileAsync(filename, collisionOption));
}
public string GetPath() => $@"{(Location != null ? Location.Path : $@"{UserDataPaths.GetDefault().Downloads}\VDownload")}\{Name}.{Extension.ToString().ToLower()}";
#endregion
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using VDownload.Core.Interfaces;
@@ -32,15 +33,15 @@ namespace VDownload.Core.Services
#region PUBLIC METHODS
public async Task<IVideo[]> GetNewVideosAsync()
public async Task<IVideo[]> GetNewVideosAsync(CancellationToken cancellationToken = default)
{
await Playlist.GetVideosAsync();
await Playlist.GetVideosAsync(cancellationToken);
return GetUnsavedVideos();
}
public async Task<IVideo[]> GetNewVideosAndUpdateAsync()
public async Task<IVideo[]> GetNewVideosAndUpdateAsync(CancellationToken cancellationToken = default)
{
await Playlist.GetVideosAsync();
await Playlist.GetVideosAsync(cancellationToken);
IVideo[] newVideos = GetUnsavedVideos();
SavedVideos = Playlist.Videos;
return newVideos;

View File

@@ -27,7 +27,7 @@ namespace VDownload.Core.Services
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);
StorageFolder subscriptionsFolder = await SubscriptionFolderLocation.CreateFolderAsync(SubscriptionsFolderName, CreationCollisionOption.OpenIfExists);
BinaryFormatter formatter = new BinaryFormatter();
foreach (StorageFile file in await subscriptionsFolder.GetFilesAsync())
{
@@ -43,7 +43,7 @@ namespace VDownload.Core.Services
public static async Task<StorageFile> CreateSubscriptionFileAsync(Subscription subscription)
{
StorageFolder subscriptionsFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(SubscriptionsFolderName, CreationCollisionOption.OpenIfExists);
StorageFolder subscriptionsFolder = await SubscriptionFolderLocation.CreateFolderAsync(SubscriptionsFolderName, CreationCollisionOption.OpenIfExists);
try
{
StorageFile subscriptionFile = await subscriptionsFolder.CreateFileAsync($"{(int)subscription.Playlist.Source}-{subscription.Playlist.ID}.{SubscriptionFileExtension}", CreationCollisionOption.FailIfExists);

View File

@@ -1,47 +0,0 @@
using System;
using System.Collections.Generic;
namespace VDownload.Core.Services
{
public static class TaskId
{
#region CONSTANTS
// ID SETTINGS
private static readonly char[] IDChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
private static readonly int IDLength = 10;
// IDS LIST
private static readonly List<string> IDList = new List<string>();
#endregion
#region METHODS
// GET TASK ID
public static string Get()
{
string id;
do
{
id = "";
while (id.Length < IDLength)
{
id += IDChars[new Random().Next(0, IDChars.Length)];
}
} while (IDList.Contains(id));
IDList.Add(id);
return id;
}
// DISPOSE TASK ID
public static void Dispose(string id)
{
IDList.Remove(id);
}
#endregion
}
}

View File

@@ -1,54 +0,0 @@
using System;
using System.Diagnostics;
using System.Linq;
namespace VDownload.Core.Services
{
public static class TimeSpanCustomFormat
{
// (TH:)MM:SS
public static string ToOptTHBaseMMSS(TimeSpan timeSpan, params TimeSpan[] formatBase)
{
string formattedTimeSpan = string.Empty;
int maxTHLength = 0;
foreach (TimeSpan format in formatBase.Concat(new TimeSpan[] { timeSpan }))
{
int THLength = Math.Floor(format.TotalHours) > 0 ? Math.Floor(timeSpan.TotalHours).ToString().Length : 0;
if (THLength > maxTHLength) maxTHLength = THLength;
}
formattedTimeSpan += $"{((int)Math.Floor(timeSpan.TotalHours)).ToString($"D{maxTHLength}")}:";
formattedTimeSpan += maxTHLength == 0 ? $"{timeSpan.Minutes}:" : $"{timeSpan.Minutes:00}:";
formattedTimeSpan += $"{timeSpan.Seconds:00}";
return formattedTimeSpan;
}
// ((TH:)MM:)SS
public static string ToOptTHMMBaseSS(TimeSpan timeSpan, params TimeSpan[] formatBase)
{
string formattedTimeSpan = string.Empty;
int maxTHLength = 0;
foreach (TimeSpan format in formatBase.Concat(new TimeSpan[] { timeSpan }))
{
int THLength = Math.Floor(format.TotalHours) > 0 ? Math.Floor(timeSpan.TotalHours).ToString().Length : 0;
if (THLength > maxTHLength) maxTHLength = THLength;
}
formattedTimeSpan += $"{((int)Math.Floor(timeSpan.TotalHours)).ToString($"D{maxTHLength}")}:";
bool MM = false;
if (Math.Floor(timeSpan.TotalMinutes) > 0 || maxTHLength > 0)
{
formattedTimeSpan += maxTHLength > 0 ? $"{timeSpan.Minutes:00}:" : $"{timeSpan.Minutes}:";
MM = true;
}
formattedTimeSpan += MM ? $"{timeSpan.Seconds:00}:" : $"{timeSpan.Seconds}:";
return formattedTimeSpan;
}
}
}

View File

@@ -0,0 +1,11 @@
using Windows.Storage;
namespace VDownload.Core.Sources
{
internal static class AuthorizationData
{
internal static StorageFolder FolderLocation = ApplicationData.Current.LocalCacheFolder;
internal static string FolderName = "AuthData";
internal static string FilesExtension = "auth";
}
}

View File

@@ -21,8 +21,9 @@ namespace VDownload.Core.Services.Sources.Twitch
public Channel(string id)
{
ID = id;
Source = PlaylistSource.TwitchChannel;
ID = id;
Url = new Uri($"https://twitch.tv/{ID}");
}
#endregion
@@ -31,25 +32,23 @@ namespace VDownload.Core.Services.Sources.Twitch
#region PROPERTIES
public string ID { get; private set; }
public PlaylistSource Source { get; private set; }
public string ID { get; private set; }
private string UniqueID { get; set; }
public Uri Url { get; private set; }
public string Name { get; private set; }
public IVideo[] Videos { get; private set; }
private string UniqueUserID { get; set; }
#endregion
#region STANDARD METHODS
#region PUBLIC METHODS
// GET CHANNEL METADATA
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
// Get response
JToken response = null;
using (WebClient client = await Client.Helix())
{
@@ -59,52 +58,38 @@ namespace VDownload.Core.Services.Sources.Twitch
else throw new MediaNotFoundException($"Twitch Channel (ID: {ID}) was not found");
}
// Create unified playlist url
Url = new Uri($"https://twitch.tv/{ID}");
// Set parameters
UniqueUserID = (string)response["id"];
UniqueID = (string)response["id"];
Name = (string)response["display_name"];
}
// GET CHANNEL VIDEOS
public async Task GetVideosAsync(CancellationToken cancellationToken = default) => await GetVideosAsync(0, cancellationToken);
public async Task GetVideosAsync(int numberOfVideos, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
// Set page id
string pagination = "";
// Set array of videos
List<Vod> videos = new List<Vod>();
// Get all
bool getAll = numberOfVideos == 0;
// Get videos
int count;
JToken[] videosData;
List<Task> getStreamsTasks = new List<Task>();
do
{
// Set number of videos to get in this iteration
count = numberOfVideos < 100 && !getAll ? numberOfVideos : 100;
// Get response
JToken response = null;
using (WebClient client = await Client.Helix())
{
client.QueryString.Add("user_id", UniqueUserID);
client.QueryString.Add("user_id", UniqueID);
client.QueryString.Add("first", count.ToString());
client.QueryString.Add("after", pagination);
response = JObject.Parse(await client.DownloadStringTaskAsync("https://api.twitch.tv/helix/videos"));
}
// Set page id
pagination = (string)response["pagination"]["cursor"];
// Set videos data
videosData = response["data"].ToArray();
foreach (JToken videoData in videosData)
{
@@ -118,10 +103,8 @@ namespace VDownload.Core.Services.Sources.Twitch
}
while ((getAll || numberOfVideos > 0) && count == videosData.Length);
// Wait for all getStreams tasks
await Task.WhenAll(getStreamsTasks);
// Set videos
Videos = videos.ToArray();
}

View File

@@ -25,8 +25,9 @@ namespace VDownload.Core.Services.Sources.Twitch
public Clip(string id)
{
ID = id;
Source = VideoSource.TwitchClip;
ID = id;
Url = new Uri($"https://clips.twitch.tv/{ID}");
}
#endregion
@@ -38,21 +39,24 @@ namespace VDownload.Core.Services.Sources.Twitch
public VideoSource Source { get; private set; }
public string ID { get; private set; }
public Uri Url { get; private set; }
public Metadata Metadata { get; private set; }
public string Title { get; private set; }
public string Author { get; private set; }
public DateTime Date { get; private set; }
public TimeSpan Duration { get; private set; }
public long Views { get; private set; }
public Uri Thumbnail { get; private set; }
public BaseStream[] BaseStreams { get; private set; }
#endregion
#region STANDARD METHODS
#region PUBLIC METHODS
// GET CLIP METADATA
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
// Get response
JToken response = null;
using (WebClient client = await Client.Helix())
{
@@ -62,39 +66,28 @@ namespace VDownload.Core.Services.Sources.Twitch
else throw new MediaNotFoundException($"Twitch Clip (ID: {ID}) was not found");
}
// Create unified video url
Url = new Uri($"https://clips.twitch.tv/{ID}");
// Set metadata
Metadata = new Metadata()
{
Title = (string)response["title"],
Author = (string)response["broadcaster_name"],
Date = Convert.ToDateTime(response["created_at"]),
Duration = TimeSpan.FromSeconds((double)response["duration"]),
Views = (long)response["view_count"],
Thumbnail = new Uri((string)response["thumbnail_url"]),
};
Title = (string)response["title"];
Author = (string)response["broadcaster_name"];
Date = Convert.ToDateTime(response["created_at"]);
Duration = TimeSpan.FromSeconds((double)response["duration"]);
Views = (long)response["view_count"];
Thumbnail = new Uri((string)response["thumbnail_url"]);
}
public async Task GetStreamsAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
// Get response
JToken[] response;
using (WebClient client = Client.GQL())
{
response = JArray.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "[{\"operationName\":\"VideoAccessToken_Clip\",\"variables\":{\"slug\":\"" + ID + "\"},\"extensions\":{\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11\"}}}]"))[0]["data"]["clip"]["videoQualities"].ToArray();
}
// Init streams list
List<BaseStream> streams = new List<BaseStream>();
// Parse response
foreach (JToken streamData in response)
{
// Create stream
BaseStream stream = new BaseStream()
{
Url = new Uri((string)streamData["sourceURL"]),
@@ -102,21 +95,17 @@ namespace VDownload.Core.Services.Sources.Twitch
FrameRate = (int)streamData["frameRate"],
};
// Add stream
streams.Add(stream);
}
// Set streams
BaseStreams = streams.ToArray();
}
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default)
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TrimData trim, CancellationToken cancellationToken = default)
{
// Invoke DownloadingStarted event
cancellationToken.ThrowIfCancellationRequested();
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
// Get video GQL access token
JToken videoAccessToken = null;
using (WebClient client = Client.GQL())
{
@@ -141,7 +130,7 @@ namespace VDownload.Core.Services.Sources.Twitch
// Processing
StorageFile outputFile = rawFile;
if (extension != MediaFileExtension.MP4 || mediaType != MediaType.AudioVideo || trimStart != null || trimEnd != null)
if (extension != MediaFileExtension.MP4 || mediaType != MediaType.AudioVideo || trim.Start != null || trim.End != null)
{
cancellationToken.ThrowIfCancellationRequested();
outputFile = await downloadingFolder.CreateFileAsync($"transcoded.{extension.ToString().ToLower()}");
@@ -149,15 +138,9 @@ namespace VDownload.Core.Services.Sources.Twitch
MediaProcessor mediaProcessor = new MediaProcessor();
mediaProcessor.ProgressChanged += ProcessingProgressChanged;
Task mediaProcessorTask;
if (trimStart == TimeSpan.Zero && trimEnd == Metadata.Duration) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, cancellationToken: cancellationToken);
else if (trimStart == TimeSpan.Zero) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimStart: trimStart, cancellationToken: cancellationToken);
else if (trimEnd == Metadata.Duration) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimEnd: trimEnd, cancellationToken: cancellationToken);
else mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimStart, trimEnd, cancellationToken);
await mediaProcessorTask;
await mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trim, cancellationToken);
}
// Return output file
return outputFile;
}
@@ -165,7 +148,7 @@ namespace VDownload.Core.Services.Sources.Twitch
#region EVENT HANDLERS
#region EVENTS
public event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
public event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;

View File

@@ -4,11 +4,13 @@ using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using VDownload.Core.Sources;
using VDownload.Core.Structs;
using Windows.Storage;
namespace VDownload.Core.Services.Sources.Twitch.Helpers
{
public static class Auth
public static class Authorization
{
#region CONSTANTS
@@ -16,7 +18,6 @@ namespace VDownload.Core.Services.Sources.Twitch.Helpers
public readonly static string GQLApiClientID = "kimne78kx3ncx6brgo4mv6wki5h1ko";
public readonly static Uri RedirectUrl = new Uri("https://www.vd.com");
// AUTHORIZATION URL
private readonly static string ResponseType = "token";
private readonly static string[] Scopes = new[]
{
@@ -34,8 +35,8 @@ namespace VDownload.Core.Services.Sources.Twitch.Helpers
{
try
{
StorageFolder authDataFolder = await ApplicationData.Current.LocalCacheFolder.GetFolderAsync("AuthData");
StorageFile authDataFile = await authDataFolder.GetFileAsync("Twitch.auth");
StorageFolder authDataFolder = await AuthorizationData.FolderLocation.GetFolderAsync(AuthorizationData.FolderName);
StorageFile authDataFile = await authDataFolder.GetFileAsync($"Twitch.{AuthorizationData.FilesExtension}");
return await FileIO.ReadTextAsync(authDataFile);
}
@@ -47,8 +48,8 @@ namespace VDownload.Core.Services.Sources.Twitch.Helpers
public static async Task SaveAccessTokenAsync(string accessToken)
{
StorageFolder authDataFolder = await ApplicationData.Current.LocalCacheFolder.CreateFolderAsync("AuthData", CreationCollisionOption.OpenIfExists);
StorageFile authDataFile = await authDataFolder.CreateFileAsync("Twitch.auth", CreationCollisionOption.ReplaceExisting);
StorageFolder authDataFolder = await AuthorizationData.FolderLocation.CreateFolderAsync(AuthorizationData.FolderName, CreationCollisionOption.OpenIfExists);
StorageFile authDataFile = await authDataFolder.CreateFileAsync($"Twitch.{AuthorizationData.FilesExtension}", CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(authDataFile, accessToken);
}
@@ -57,15 +58,15 @@ namespace VDownload.Core.Services.Sources.Twitch.Helpers
{
try
{
StorageFolder authDataFolder = await ApplicationData.Current.LocalCacheFolder.GetFolderAsync("AuthData");
StorageFile authDataFile = await authDataFolder.GetFileAsync("Twitch.auth");
StorageFolder authDataFolder = await AuthorizationData.FolderLocation.GetFolderAsync(AuthorizationData.FolderName);
StorageFile authDataFile = await authDataFolder.GetFileAsync($"Twitch.{AuthorizationData.FilesExtension}");
await authDataFile.DeleteAsync();
}
catch (FileNotFoundException) { }
}
public static async Task<(bool IsValid, string Login, DateTime? ExpirationDate)> ValidateAccessTokenAsync(string accessToken)
public static async Task<TwitchAccessTokenValidationData> ValidateAccessTokenAsync(string accessToken)
{
WebClient client = new WebClient { Encoding = Encoding.UTF8 };
client.Headers.Add("Authorization", $"Bearer {accessToken}");
@@ -77,17 +78,20 @@ namespace VDownload.Core.Services.Sources.Twitch.Helpers
string login = response["login"].ToString();
DateTime? expirationDate = DateTime.Now.AddSeconds(long.Parse(response["expires_in"].ToString()));
return (true, login, expirationDate);
return new TwitchAccessTokenValidationData(accessToken, true, login, expirationDate);
}
catch (WebException wex)
catch (WebException ex)
{
if (wex.Response != null)
if (ex.Response is null)
{
JObject wexInfo = JObject.Parse(new StreamReader(wex.Response.GetResponseStream()).ReadToEnd());
if ((int)wexInfo["status"] == 401) return (false, null, null);
throw;
}
else
{
JObject exInfo = JObject.Parse(new StreamReader(ex.Response.GetResponseStream()).ReadToEnd());
if ((int)exInfo["status"] == 401) return new TwitchAccessTokenValidationData(accessToken, false, null, null);
else throw;
}
else throw;
}
}

View File

@@ -8,15 +8,15 @@ namespace VDownload.Core.Services.Sources.Twitch.Helpers
{
internal static async Task<WebClient> Helix()
{
string accessToken = await Auth.ReadAccessTokenAsync();
string accessToken = await Authorization.ReadAccessTokenAsync();
if (accessToken == null) throw new TwitchAccessTokenNotFoundException();
var twitchAccessTokenValidation = await Auth.ValidateAccessTokenAsync(accessToken);
var twitchAccessTokenValidation = await Authorization.ValidateAccessTokenAsync(accessToken);
if (!twitchAccessTokenValidation.IsValid) throw new TwitchAccessTokenNotValidException();
WebClient client = new WebClient();
client.Headers.Add("Authorization", $"Bearer {accessToken}");
client.Headers.Add("Client-Id", Auth.ClientID);
client.Headers.Add("Client-Id", Authorization.ClientID);
return client;
}
@@ -24,7 +24,7 @@ namespace VDownload.Core.Services.Sources.Twitch.Helpers
internal static WebClient GQL()
{
WebClient client = new WebClient();
client.Headers.Add("Client-Id", Auth.GQLApiClientID);
client.Headers.Add("Client-Id", Authorization.GQLApiClientID);
return client;
}

View File

@@ -24,8 +24,9 @@ namespace VDownload.Core.Services.Sources.Twitch
public Vod(string id)
{
ID = id;
Source = VideoSource.TwitchVod;
ID = id;
Url = new Uri($"https://www.twitch.tv/videos/{ID}");
}
#endregion
@@ -37,21 +38,24 @@ namespace VDownload.Core.Services.Sources.Twitch
public VideoSource Source { get; private set; }
public string ID { get; private set; }
public Uri Url { get; private set; }
public Metadata Metadata { get; private set; }
public string Title { get; private set; }
public string Author { get; private set; }
public DateTime Date { get; private set; }
public TimeSpan Duration { get; private set; }
public long Views { get; private set; }
public Uri Thumbnail { get; private set; }
public BaseStream[] BaseStreams { get; private set; }
#endregion
#region STANDARD METHODS
#region PUBLIC METHODS
// GET VOD METADATA
public async Task GetMetadataAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
// Get response
JToken response = null;
using (WebClient client = await Client.Helix())
{
@@ -69,57 +73,40 @@ namespace VDownload.Core.Services.Sources.Twitch
}
}
// Set parameters
GetMetadataAsync(response);
}
internal void GetMetadataAsync(JToken response)
{
// Create unified video url
Url = new Uri($"https://www.twitch.tv/videos/{ID}");
// Set metadata
Metadata = new Metadata()
{
Title = ((string)response["title"]).Replace("\n", ""),
Author = (string)response["user_name"],
Date = Convert.ToDateTime(response["created_at"]),
Duration = ParseDuration((string)response["duration"]),
Views = (long)response["view_count"],
Thumbnail = (string)response["thumbnail_url"] == string.Empty ? null : new Uri(((string)response["thumbnail_url"]).Replace("%{width}", "1920").Replace("%{height}", "1080")),
};
Title = ((string)response["title"]).Replace("\n", "");
Author = (string)response["user_name"];
Date = Convert.ToDateTime(response["created_at"]);
Duration = ParseDuration((string)response["duration"]);
Views = (long)response["view_count"];
Thumbnail = (string)response["thumbnail_url"] == string.Empty ? null : new Uri(((string)response["thumbnail_url"]).Replace("%{width}", "1920").Replace("%{height}", "1080"));
}
// GET VOD STREAMS
public async Task GetStreamsAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
// Get response
string[] response = null;
using (WebClient client = Client.GQL())
{
// Get video GQL access token
cancellationToken.ThrowIfCancellationRequested();
JToken videoAccessToken = JObject.Parse(await client.UploadStringTaskAsync("https://gql.twitch.tv/gql", "{\"operationName\":\"PlaybackAccessToken_Template\",\"query\":\"query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: \\\"web\\\", playerBackend: \\\"mediaplayer\\\", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: \\\"web\\\", playerBackend: \\\"mediaplayer\\\", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}\",\"variables\":{\"isLive\":false,\"login\":\"\",\"isVod\":true,\"vodID\":\"" + ID + "\",\"playerType\":\"embed\"}}"))["data"]["videoPlaybackAccessToken"];
// Get video streams
cancellationToken.ThrowIfCancellationRequested();
response = (await client.DownloadStringTaskAsync($"http://usher.twitch.tv/vod/{ID}?nauth={videoAccessToken["value"]}&nauthsig={videoAccessToken["signature"]}&allow_source=true&player=twitchweb")).Split("\n");
}
// Init streams list
List<BaseStream> streams = new List<BaseStream>();
// Stream data line2 regular expression
Regex streamDataL2Regex = new Regex(@"^#EXT-X-STREAM-INF:BANDWIDTH=\d+,CODECS=""\S+,\S+"",RESOLUTION=\d+x(?<height>\d+),VIDEO=""\w+""(,FRAME-RATE=(?<frame_rate>\d+.\d+))?");
// Parse response
for (int i = 2; i < response.Length; i += 3)
{
// Parse line 2
Match line2 = streamDataL2Regex.Match(response[i + 1]);
// Create stream
BaseStream stream = new BaseStream()
{
Url = new Uri(response[i + 2]),
@@ -127,30 +114,22 @@ namespace VDownload.Core.Services.Sources.Twitch
FrameRate = line2.Groups["frame_rate"].Value != string.Empty ? (int)Math.Round(double.Parse(line2.Groups["frame_rate"].Value)) : 0,
};
// Add stream
streams.Add(stream);
}
// Set streams
BaseStreams = streams.ToArray();
}
// DOWNLOAD AND TRANSCODE VOD
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default)
public async Task<StorageFile> DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TrimData trim, CancellationToken cancellationToken = default)
{
// Invoke DownloadingStarted event
cancellationToken.ThrowIfCancellationRequested();
DownloadingProgressChanged(this, new EventArgs.ProgressChangedEventArgs(0));
// Get video chunks
cancellationToken.ThrowIfCancellationRequested();
DownloadingProgressChanged.Invoke(this, new EventArgs.ProgressChangedEventArgs(0));
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList = await ExtractChunksFromM3U8Async(baseStream.Url, cancellationToken);
// Changeable duration
TimeSpan duration = Metadata.Duration;
TimeSpan duration = Duration;
// Passive trim
if ((bool)Config.GetValue("twitch_vod_passive_trim") && trimStart != TimeSpan.Zero && trimEnd != duration) (trimStart, trimEnd, duration) = PassiveVideoTrim(chunksList, trimStart, trimEnd, Metadata.Duration);
if ((bool)Config.GetValue("twitch_vod_passive_trim") && trim.Start != TimeSpan.Zero && trim.End != duration) (trim, duration) = PassiveVideoTrim(chunksList, trim, Duration);
// Download
cancellationToken.ThrowIfCancellationRequested();
@@ -183,12 +162,7 @@ namespace VDownload.Core.Services.Sources.Twitch
MediaProcessor mediaProcessor = new MediaProcessor();
mediaProcessor.ProgressChanged += ProcessingProgressChanged;
Task mediaProcessorTask;
if (trimStart == TimeSpan.Zero && trimEnd == duration) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, cancellationToken: cancellationToken);
else if (trimStart == TimeSpan.Zero) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimStart: trimStart, cancellationToken: cancellationToken);
else if (trimEnd == duration) mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimEnd: trimEnd, cancellationToken: cancellationToken);
else mediaProcessorTask = mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trimStart, trimEnd, cancellationToken);
await mediaProcessorTask;
await mediaProcessor.Run(rawFile, extension, mediaType, outputFile, trim, cancellationToken);
// Return output file
return outputFile;
@@ -198,30 +172,24 @@ namespace VDownload.Core.Services.Sources.Twitch
#region LOCAL METHODS
#region PRIVATE METHODS
// GET CHUNKS DATA FROM M3U8 PLAYLIST
private static async Task<List<(Uri ChunkUrl, TimeSpan ChunkDuration)>> ExtractChunksFromM3U8Async(Uri streamUrl, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
// Get response
string response = null;
using (WebClient client = Client.GQL())
{
response = await client.DownloadStringTaskAsync(streamUrl);
}
// Create dictionary
List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunks = new List<(Uri ChunkUrl, TimeSpan ChunkDuration)>();
// Chunk data regular expression
Regex chunkDataRegex = new Regex(@"#EXTINF:(?<duration>\d+.\d+),\n(?<filename>\S+.ts)");
// Chunks location
string chunkLocationPath = streamUrl.AbsoluteUri.Replace(System.IO.Path.GetFileName(streamUrl.AbsoluteUri), "");
string chunkLocationPath = streamUrl.AbsoluteUri.Replace(Path.GetFileName(streamUrl.AbsoluteUri), "");
// Pack data into dictionary
foreach (Match chunk in chunkDataRegex.Matches(response))
{
Uri chunkUrl = new Uri($"{chunkLocationPath}{chunk.Groups["filename"].Value}");
@@ -229,37 +197,30 @@ namespace VDownload.Core.Services.Sources.Twitch
chunks.Add((chunkUrl, chunkDuration));
}
// Return chunks data
return chunks;
}
// PASSIVE TRIM
private static (TimeSpan NewTrimStart, TimeSpan NewTrimEnd, TimeSpan NewDuration) PassiveVideoTrim(List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList, TimeSpan trimStart, TimeSpan trimEnd, TimeSpan duration)
private static (TrimData Trim, TimeSpan NewDuration) PassiveVideoTrim(List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunksList, TrimData trim, TimeSpan duration)
{
// Copy duration
TimeSpan newDuration = duration;
// Trim at start
while (chunksList[0].ChunkDuration <= trimStart)
while (chunksList[0].ChunkDuration <= trim.Start)
{
trimStart = trimStart.Subtract(chunksList[0].ChunkDuration);
trimEnd = trimEnd.Subtract(chunksList[0].ChunkDuration);
trim.Start = trim.Start.Subtract(chunksList[0].ChunkDuration);
trim.End = trim.End.Subtract(chunksList[0].ChunkDuration);
newDuration = newDuration.Subtract(chunksList[0].ChunkDuration);
chunksList.RemoveAt(0);
}
// Trim at end
while (chunksList.Last().ChunkDuration <= newDuration.Subtract(trimEnd))
while (chunksList.Last().ChunkDuration <= newDuration.Subtract(trim.End))
{
newDuration = newDuration.Subtract(chunksList.Last().ChunkDuration);
chunksList.RemoveAt(chunksList.Count - 1);
}
// Return data
return (trimStart, trimEnd, newDuration);
return (trim, newDuration);
}
// DOWNLOAD CHUNK
private static async Task<byte[]> DownloadChunkAsync(Uri chunkUrl, CancellationToken cancellationToken = default)
{
int retriesCount = 0;
@@ -285,12 +246,11 @@ namespace VDownload.Core.Services.Sources.Twitch
}
}
// WRITE CHUNK TO FILE
private static Task WriteChunkToFileAsync(StorageFile file, byte[] chunk)
{
return Task.Factory.StartNew(() =>
{
using (var stream = new System.IO.FileStream(file.Path, System.IO.FileMode.Append))
using (var stream = new FileStream(file.Path, FileMode.Append))
{
stream.Write(chunk, 0, chunk.Length);
stream.Close();
@@ -298,7 +258,6 @@ namespace VDownload.Core.Services.Sources.Twitch
});
}
// PARSE DURATION
private static TimeSpan ParseDuration(string duration)
{
char[] separators = { 'h', 'm', 's' };
@@ -316,7 +275,7 @@ namespace VDownload.Core.Services.Sources.Twitch
#region EVENT HANDLERS
#region EVENTS
public event EventHandler<EventArgs.ProgressChangedEventArgs> DownloadingProgressChanged;
public event EventHandler<EventArgs.ProgressChangedEventArgs> ProcessingProgressChanged;

View File

@@ -9,8 +9,20 @@ namespace VDownload.Core.Structs
[Serializable]
public struct BaseStream
{
#region PROPERTIES
public Uri Url { get; set; }
public int Height { get; set; }
public int FrameRate { get; set; }
#endregion
#region METHODS
public override string ToString() => $"{Height}p{(FrameRate > 0 ? FrameRate.ToString() : "N/A")}";
#endregion
}
}

View File

@@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Core.Structs
{
[Serializable]
public struct Metadata
{
public string Title { get; set; }
public string Author { get; set; }
public DateTime Date { get; set; }
public TimeSpan Duration { get; set; }
public long Views { get; set; }
public Uri Thumbnail { get; set; }
}
}

View File

@@ -1,13 +0,0 @@
using System;
using VDownload.Core.Enums;
using VDownload.Core.Interfaces;
using Windows.Storage;
namespace VDownload.Core.Structs
{
public struct TaskData
{
public IVideo VideoService { get; set; }
public TaskOptions TaskOptions { get; set; }
}
}

View File

@@ -1,18 +0,0 @@
using System;
using VDownload.Core.Enums;
using Windows.Storage;
namespace VDownload.Core.Structs
{
public struct TaskOptions
{
public MediaType MediaType { get; set; }
public BaseStream Stream { get; set; }
public TimeSpan TrimStart { get; set; }
public TimeSpan TrimEnd { get; set; }
public string Filename { get; set; }
public MediaFileExtension Extension { get; set; }
public StorageFolder Location { get; set; }
public double Schedule { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Core.Structs
{
public struct TrimData
{
#region PROPERTIES
public TimeSpan Start { get; set; }
public TimeSpan End { get; set; }
#endregion
}
}

View File

@@ -0,0 +1,32 @@
using System;
namespace VDownload.Core.Structs
{
public struct TwitchAccessTokenValidationData
{
#region CONSTRUCTORS
public TwitchAccessTokenValidationData(string accessToken, bool isValid, string login, DateTime? expirationDate)
{
AccessToken = accessToken;
IsValid = isValid;
Login = login;
ExpirationDate = expirationDate;
}
public static TwitchAccessTokenValidationData Null = new TwitchAccessTokenValidationData(string.Empty, false, string.Empty, null);
#endregion
#region PROPERTIES
public string AccessToken { get; private set; }
public bool IsValid { get; private set; }
public string Login { get; private set; }
public DateTime? ExpirationDate { get; private set; }
#endregion
}
}

View File

@@ -124,42 +124,50 @@
<Compile Include="Enums\MediaFileExtension.cs" />
<Compile Include="Enums\MediaType.cs" />
<Compile Include="Enums\PlaylistSource.cs" />
<Compile Include="Enums\SubscriptionStatus.cs" />
<Compile Include="Enums\TaskAddingRequestSource.cs" />
<Compile Include="Enums\DownloadTasksAddingRequestSource.cs" />
<Compile Include="Enums\VideoFileExtension.cs" />
<Compile Include="Enums\VideoSource.cs" />
<Compile Include="Enums\TaskStatus.cs" />
<Compile Include="Enums\DownloadTaskStatus.cs" />
<Compile Include="EventArgs\DownloadTaskEndedUnsuccessfullyEventArgs.cs" />
<Compile Include="EventArgs\DownloadTaskEndedSuccessfullyEventArgs.cs" />
<Compile Include="EventArgs\DownloadTaskScheduledEventArgs.cs" />
<Compile Include="EventArgs\ProgressChangedEventArgs.cs" />
<Compile Include="EventArgs\TasksAddingRequestedEventArgs.cs" />
<Compile Include="EventArgs\DownloadTasksAddingRequestedEventArgs.cs" />
<Compile Include="EventArgs\PlaylistSearchSuccessedEventArgs.cs" />
<Compile Include="EventArgs\SubscriptionLoadSuccessedEventArgs.cs" />
<Compile Include="EventArgs\VideoSearchSuccessedEventArgs.cs" />
<Compile Include="Exceptions\MediaNotFoundException.cs" />
<Compile Include="Exceptions\SubscriptionExistsException.cs" />
<Compile Include="Exceptions\TwitchAccessTokenNotFoundException.cs" />
<Compile Include="Exceptions\TwitchAccessTokenNotValidException.cs" />
<Compile Include="Extensions\TimeSpanExtension.cs" />
<Compile Include="Interfaces\IPlaylist.cs" />
<Compile Include="Interfaces\IVideo.cs" />
<Compile Include="Services\Sources\Twitch\Helpers\Client.cs" />
<Compile Include="Services\DownloadTasksCollectionManagement.cs" />
<Compile Include="Services\OutputFile.cs" />
<Compile Include="Sources\AuthorizationData.cs" />
<Compile Include="Sources\Twitch\Helpers\Client.cs" />
<Compile Include="Services\Subscription.cs" />
<Compile Include="Services\SubscriptionsCollectionManagement.cs" />
<Compile Include="Services\TimeSpanCustomFormat.cs" />
<Compile Include="Structs\BaseStream.cs" />
<Compile Include="Structs\Metadata.cs" />
<Compile Include="Structs\TaskData.cs" />
<Compile Include="Services\DownloadTask.cs" />
<Compile Include="Services\Config.cs" />
<Compile Include="Services\MediaProcessor.cs" />
<Compile Include="Services\Sources\Source.cs" />
<Compile Include="Services\Sources\Twitch\Helpers\Auth.cs" />
<Compile Include="Services\Sources\Twitch\Channel.cs" />
<Compile Include="Services\Sources\Twitch\Clip.cs" />
<Compile Include="Services\Sources\Twitch\Vod.cs" />
<Compile Include="Services\TaskId.cs" />
<Compile Include="Structs\TaskOptions.cs" />
<Compile Include="Sources\Source.cs" />
<Compile Include="Sources\Twitch\Helpers\Authorization.cs" />
<Compile Include="Sources\Twitch\Channel.cs" />
<Compile Include="Sources\Twitch\Clip.cs" />
<Compile Include="Sources\Twitch\Vod.cs" />
<Compile Include="Structs\TrimData.cs" />
<Compile Include="Structs\TwitchAccessTokenValidationData.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.13</Version>
</PackageReference>
<PackageReference Include="Microsoft.Toolkit.Uwp.Connectivity">
<Version>7.1.2</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>13.0.1</Version>
</PackageReference>

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,14 +1,13 @@
<Page
x:Class="VDownload.Views.Settings.SettingsMain"
<UserControl
x:Class="VDownload.Controls.PlaceholderableStackPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.Settings"
xmlns:local="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}">
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
</Grid>
</Page>
<StackPanel x:Name="StackPanel"/>
</UserControl>

View File

@@ -0,0 +1,100 @@
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 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.Controls
{
public sealed partial class PlaceholderableStackPanel : UserControl
{
#region CONSTRUCTORS
public PlaceholderableStackPanel()
{
InitializeComponent();
IsPlaceholderActive = true;
StackPanel.Children.Add(Placeholder);
StackPanel.VerticalAlignment = VerticalAlignment.Center;
}
#endregion
#region PROPERTIES
private UIElement _Placeholder = new Grid();
public UIElement Placeholder
{
get => _Placeholder;
set
{
_Placeholder = value;
if (IsPlaceholderActive)
{
StackPanel.Children.Clear();
StackPanel.Children.Add(_Placeholder);
StackPanel.VerticalAlignment = VerticalAlignment.Center;
}
}
}
public double Spacing
{
get => StackPanel.Spacing;
set => StackPanel.Spacing = value;
}
public bool IsPlaceholderActive { get; private set; }
#endregion
#region PUBLIC METHODS
public void Add(UIElement item)
{
if (IsPlaceholderActive)
{
StackPanel.Children.Clear();
IsPlaceholderActive = false;
StackPanel.VerticalAlignment = VerticalAlignment.Stretch;
}
StackPanel.Children.Add(item);
}
public void Remove(UIElement item)
{
StackPanel.Children.Remove(item);
if (StackPanel.Children.Count == 0)
{
StackPanel.Children.Add(_Placeholder);
StackPanel.VerticalAlignment = VerticalAlignment.Center;
IsPlaceholderActive = true;
}
}
public void Clear()
{
StackPanel.Children.Clear();
StackPanel.Children.Add(_Placeholder);
StackPanel.VerticalAlignment = VerticalAlignment.Center;
IsPlaceholderActive = true;
}
public UIElement[] GetAllItems() => IsPlaceholderActive ? StackPanel.Children.ToArray() : new UIElement[0];
#endregion
}
}

View File

@@ -14,7 +14,7 @@
<SvgImageSource x:Key="LocationIcon" UriSource="ms-appx:///Assets/Icons/LocationLight.svg"/>
<SvgImageSource x:Key="ScheduleIcon" UriSource="ms-appx:///Assets/Icons/ScheduleLight.svg"/>
<SvgImageSource x:Key="StateIdleIcon" UriSource="ms-appx:///Assets/Icons/StateIdleLight.svg"/>
<SvgImageSource x:Key="StateWaitingIcon" UriSource="ms-appx:///Assets/Icons/StateWaitingLight.svg"/>
<SvgImageSource x:Key="StateQueuedIcon" UriSource="ms-appx:///Assets/Icons/StateWaitingLight.svg"/>
<SvgImageSource x:Key="StateCancelledIcon" UriSource="ms-appx:///Assets/Icons/StateCancelledLight.svg"/>
<SvgImageSource x:Key="StateDownloadingIcon" UriSource="ms-appx:///Assets/Icons/StateDownloadingLight.svg"/>
<SvgImageSource x:Key="StateErrorIcon" UriSource="ms-appx:///Assets/Icons/StateErrorLight.svg"/>
@@ -37,7 +37,7 @@
<SvgImageSource x:Key="LocationIcon" UriSource="ms-appx:///Assets/Icons/LocationDark.svg"/>
<SvgImageSource x:Key="ScheduleIcon" UriSource="ms-appx:///Assets/Icons/ScheduleDark.svg"/>
<SvgImageSource x:Key="StateIdleIcon" UriSource="ms-appx:///Assets/Icons/StateIdleDark.svg"/>
<SvgImageSource x:Key="StateWaitingIcon" UriSource="ms-appx:///Assets/Icons/StateWaitingDark.svg"/>
<SvgImageSource x:Key="StateQueuedIcon" UriSource="ms-appx:///Assets/Icons/StateWaitingDark.svg"/>
<SvgImageSource x:Key="StateCancelledIcon" UriSource="ms-appx:///Assets/Icons/StateCancelledDark.svg"/>
<SvgImageSource x:Key="StateDownloadingIcon" UriSource="ms-appx:///Assets/Icons/StateDownloadingDark.svg"/>
<SvgImageSource x:Key="StateErrorIcon" UriSource="ms-appx:///Assets/Icons/StateErrorDark.svg"/>

View File

@@ -117,9 +117,84 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Base_CancelButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="Base_CloseButtonText" xml:space="preserve">
<value>OK</value>
</data>
<data name="Home_DownloadAll_MeteredConnection_Content" xml:space="preserve">
<value>You are on the metered connection now. You can delay tasks until the network changes. Do you want to start the tasks?</value>
</data>
<data name="Home_DownloadAll_MeteredConnection_StartWithDelayButtonText" xml:space="preserve">
<value>Yes (With delay)</value>
</data>
<data name="Home_DownloadAll_MeteredConnection_StartWithoutDelayButtonText" xml:space="preserve">
<value>Yes (Without delay)</value>
</data>
<data name="Home_DownloadAll_MeteredConnection_Title" xml:space="preserve">
<value>Metered connection detected</value>
</data>
<data name="Home_DownloadTaskControl_Start_MeteredConnection_Content" xml:space="preserve">
<value>You are on the metered connection now. You can delay task until the network changes. Do you want to start the task?</value>
</data>
<data name="Home_DownloadTaskControl_Start_MeteredConnection_StartWithDelayButtonText" xml:space="preserve">
<value>Yes (With delay)</value>
</data>
<data name="Home_DownloadTaskControl_Start_MeteredConnection_StartWithoutDelayButtonText1" xml:space="preserve">
<value>Yes (Without delay)</value>
</data>
<data name="Home_DownloadTaskControl_Start_MeteredConnection_Title" xml:space="preserve">
<value>Metered connection detected</value>
</data>
<data name="Home_OptionsBar_PlaylistSearchControl_Base_Title" xml:space="preserve">
<value>Playlist search error</value>
</data>
<data name="Home_OptionsBar_PlaylistSearchControl_InternetNotAvailable_Content" xml:space="preserve">
<value>Unable to connect to servers. Check your internet connection.</value>
</data>
<data name="Home_OptionsBar_PlaylistSearchControl_TwitchAccessTokenNotFound_Content" xml:space="preserve">
<value>To get information about Twitch playlists (Channels), you have to link your Twitch account with VDownload. Go to Sources page to sign in.</value>
</data>
<data name="Home_OptionsBar_PlaylistSearchControl_TwitchAccessTokenNotValid_Content" xml:space="preserve">
<value>There is a problem with linked Twitch account. Check Twitch login status in Sources page.</value>
</data>
<data name="Home_OptionsBar_SubscriptionsLoadControl_Base_Title" xml:space="preserve">
<value>Subscriptions loading error</value>
</data>
<data name="Home_OptionsBar_SubscriptionsLoadControl_InternetNotAvailable_Content" xml:space="preserve">
<value>Unable to connect to servers. Check your internet connection.</value>
</data>
<data name="Home_OptionsBar_SubscriptionsLoadControl_MediaNotFound_Content" xml:space="preserve">
<value>Subscribed playlist not found. Name:</value>
</data>
<data name="Home_OptionsBar_SubscriptionsLoadControl_TwitchAccessTokenNotFound_Content" xml:space="preserve">
<value>To get information about Twitch videos and playlists, you have to link your Twitch account with VDownload. Go to Sources page to sign in.</value>
</data>
<data name="Home_OptionsBar_SubscriptionsLoadControl_TwitchAccessTokenNotValid_Content" xml:space="preserve">
<value>There is a problem with linked Twitch account. Check Twitch login status in Sources page.</value>
</data>
<data name="Home_OptionsBar_VideoSearchControl_Base_Title" xml:space="preserve">
<value>Video search error</value>
</data>
<data name="Home_OptionsBar_VideoSearchControl_InternetNotAvailable_Content" xml:space="preserve">
<value>Unable to connect to servers. Check your internet connection.</value>
</data>
<data name="Home_OptionsBar_VideoSearchControl_TwitchAccessTokenNotFound_Content" xml:space="preserve">
<value>To get information about Twitch videos (VODs and Clips), you have to link your Twitch account with VDownload. Go to Sources page to sign in.</value>
</data>
<data name="Home_OptionsBar_VideoSearchControl_TwitchAccessTokenNotValid_Content" xml:space="preserve">
<value>There is a problem with linked Twitch account. Check Twitch login status in Sources page.</value>
</data>
<data name="Sources_TwitchLogin_Base_Title" xml:space="preserve">
<value>Login to Twitch failed</value>
</data>
<data name="Sources_TwitchLogin_InternetNotAvailable_Content" xml:space="preserve">
<value>Unable to connect to servers. Check your internet connection.</value>
</data>
<data name="Sources_TwitchLogin_Unknown_Content" xml:space="preserve">
<value>Unknown error. </value>
</data>
<data name="Subscription_Adding_Base_Title" xml:space="preserve">
<value>Playlist adding error</value>
</data>

View File

@@ -117,304 +117,211 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CloseErrorDialogButtonText" xml:space="preserve">
<value>OK</value>
<data name="Base_MediaType_AudioVideoText" xml:space="preserve">
<value>Audio &amp; Video</value>
</data>
<data name="HomeAddingDownloadingOptionsHeaderTextBlock.Text" xml:space="preserve">
<data name="Base_MediaType_OnlyAudioText" xml:space="preserve">
<value>Only audio</value>
</data>
<data name="Base_MediaType_OnlyVideoText" xml:space="preserve">
<value>Only video</value>
</data>
<data name="Home_Adding_Base_SerialVideoAddingControl_Filter_AuthorTextBlock.Text" xml:space="preserve">
<value>Author</value>
</data>
<data name="Home_Adding_Base_SerialVideoAddingControl_Filter_AuthorTextBox.PlaceholderText" xml:space="preserve">
<value>Enter regular expression</value>
</data>
<data name="Home_Adding_Base_SerialVideoAddingControl_Filter_DateTextBlock.Text" xml:space="preserve">
<value>Date</value>
</data>
<data name="Home_Adding_Base_SerialVideoAddingControl_Filter_DurationTextBlock.Text" xml:space="preserve">
<value>Duration</value>
</data>
<data name="Home_Adding_Base_SerialVideoAddingControl_Filter_Header_CountTextBlockPrefix" xml:space="preserve">
<value>Hidden</value>
</data>
<data name="Home_Adding_Base_SerialVideoAddingControl_Filter_Header_TextBlock.Text" xml:space="preserve">
<value>Filter</value>
</data>
<data name="Home_Adding_Base_SerialVideoAddingControl_Filter_Removed_RestoreButton.Content" xml:space="preserve">
<value>Restore</value>
</data>
<data name="Home_Adding_Base_SerialVideoAddingControl_Filter_Removed_TextBlock.Text" xml:space="preserve">
<value>Removed</value>
</data>
<data name="Home_Adding_Base_SerialVideoAddingControl_Filter_TitleTextBlock.Text" xml:space="preserve">
<value>Title</value>
</data>
<data name="Home_Adding_Base_SerialVideoAddingControl_Filter_TitleTextBox.PlaceholderText" xml:space="preserve">
<value>Enter regular expression</value>
</data>
<data name="Home_Adding_Base_SerialVideoAddingControl_Filter_ViewsTextBlock.Text" xml:space="preserve">
<value>Views</value>
</data>
<data name="Home_Adding_Base_DownloadingTaskOptionsControl_DownloadingOptions_HeaderTextBlock.Text" xml:space="preserve">
<value>Downloading options</value>
</data>
<data name="HomeAddingFileOptionsHeaderTextBlock.Text" xml:space="preserve">
<value>File options</value>
</data>
<data name="HomeAddingFileSettingControl.Title" xml:space="preserve">
<value>File</value>
</data>
<data name="HomeAddingLocationBrowseButton.Content" xml:space="preserve">
<value>Browse</value>
</data>
<data name="HomeAddingLocationSettingControl.Title" xml:space="preserve">
<value>Location</value>
</data>
<data name="HomeAddingMediaTypeSettingControl.Title" xml:space="preserve">
<data name="Home_Adding_Base_DownloadingTaskOptionsControl_DownloadingOptions_MediaTypeSettingControl.Title" xml:space="preserve">
<value>Media type</value>
</data>
<data name="HomeAddingQualitySettingControl.Title" xml:space="preserve">
<data name="Home_Adding_Base_DownloadingTaskOptionsControl_DownloadingOptions_QualitySettingControl.Title" xml:space="preserve">
<value>Quality</value>
</data>
<data name="HomeAddingScheduleSettingControl.Description" xml:space="preserve">
<value>Number of minutes to start the task (after clicking downloading button).</value>
</data>
<data name="HomeAddingScheduleSettingControl.Title" xml:space="preserve">
<value>Schedule</value>
</data>
<data name="HomeAddingTaskOptionsHeaderTextBlock.Text" xml:space="preserve">
<value>Task options</value>
</data>
<data name="HomeAddingTrimSettingControl.Title" xml:space="preserve">
<data name="Home_Adding_Base_DownloadingTaskOptionsControl_DownloadingOptions_TrimSettingControl.Title" xml:space="preserve">
<value>Trim</value>
</data>
<data name="HomeDownloadAllButtonMeteredConnectionDialogCancel" xml:space="preserve">
<value>No</value>
<data name="Home_Adding_Base_DownloadingTaskOptionsControl_FileOptions_FileLocationSettingControl.Title" xml:space="preserve">
<value>Location</value>
</data>
<data name="HomeDownloadAllButtonMeteredConnectionDialogDescription" xml:space="preserve">
<value>You are on the metered connection now. You can delay tasks until the network changes. Do you want to start the tasks?</value>
<data name="Home_Adding_Base_DownloadingTaskOptionsControl_FileOptions_FileLocationSettingControl_BrowseButton.Content" xml:space="preserve">
<value>Browse</value>
</data>
<data name="HomeDownloadAllButtonMeteredConnectionDialogStartAndDelayText" xml:space="preserve">
<value>Yes (With delay)</value>
<data name="Home_Adding_Base_DownloadingTaskOptionsControl_FileOptions_FileSettingControl.Title" xml:space="preserve">
<value>File</value>
</data>
<data name="HomeDownloadAllButtonMeteredConnectionDialogStartWithoutDelayText" xml:space="preserve">
<value>Yes (Without delay)</value>
<data name="Home_Adding_Base_DownloadingTaskOptionsControl_FileOptions_HeaderTextBlock.Text" xml:space="preserve">
<value>File options</value>
</data>
<data name="HomeDownloadAllButtonMeteredConnectionDialogTitle" xml:space="preserve">
<value>Metered connection detected</value>
<data name="Home_Adding_Base_DownloadingTaskOptionsControl_TaskOptions_HeaderTextBlock.Text" xml:space="preserve">
<value>Task options</value>
</data>
<data name="HomeOptionsBarDownloadAllButton.Label" xml:space="preserve">
<data name="Home_Adding_Base_DownloadingTaskOptionsControl_TaskOptions_ScheduleSettingControl.Description" xml:space="preserve">
<value>Number of minutes to start the task (after clicking downloading button).</value>
</data>
<data name="Home_Adding_Base_DownloadingTaskOptionsControl_TaskOptions_ScheduleSettingControl.Title" xml:space="preserve">
<value>Schedule</value>
</data>
<data name="Home_DownloadTaskControlsStackPanel_Placeholder_TextBlock.Text" xml:space="preserve">
<value>Click "Video/Playlist search" button to start</value>
</data>
<data name="Home_DownloadTaskControl_Error_InternetNotAvailable" xml:space="preserve">
<value>Internet connection error</value>
</data>
<data name="Home_DownloadTaskControl_Notification_EndedSuccessfully_Description" xml:space="preserve">
<value>downloading ended successfully</value>
</data>
<data name="Home_DownloadTaskControl_Notification_EndedSuccessfully_Header" xml:space="preserve">
<value>Video downloading ended successfully</value>
</data>
<data name="Home_DownloadTaskControl_Notification_EndedUnsuccessfully_Description" xml:space="preserve">
<value>an error occured</value>
</data>
<data name="Home_DownloadTaskControl_Notification_EndedUnsuccessfully_Header" xml:space="preserve">
<value>Video downloading ended unsuccessfully</value>
</data>
<data name="Home_DownloadTaskControl_State_Cancelled" xml:space="preserve">
<value>Cancelled</value>
</data>
<data name="Home_DownloadTaskControl_State_Done" xml:space="preserve">
<value>Done</value>
</data>
<data name="Home_DownloadTaskControl_State_Downloading" xml:space="preserve">
<value>Downloading</value>
</data>
<data name="Home_DownloadTaskControl_State_Error" xml:space="preserve">
<value>An error occured</value>
</data>
<data name="Home_DownloadTaskControl_State_Finalizing" xml:space="preserve">
<value>Finalizing</value>
</data>
<data name="Home_DownloadTaskControl_State_Idle" xml:space="preserve">
<value>Idle</value>
</data>
<data name="Home_DownloadTaskControl_State_Processing" xml:space="preserve">
<value>Processing</value>
</data>
<data name="Home_DownloadTaskControl_State_Queued" xml:space="preserve">
<value>Queued</value>
</data>
<data name="Home_DownloadTaskControl_State_Scheduled" xml:space="preserve">
<value>Scheduled</value>
</data>
<data name="Home_OptionsBar_DownloadAllButton.Label" xml:space="preserve">
<value>Download all</value>
</data>
<data name="HomeOptionsBarDownloadAllButton.Width" xml:space="preserve">
<data name="Home_OptionsBar_DownloadAllButton.Width" xml:space="preserve">
<value>90</value>
</data>
<data name="HomeOptionsBarLoadSubscripionsButton.Label" xml:space="preserve">
<data name="Home_OptionsBar_LoadSubscripionsButton.Label" xml:space="preserve">
<value>Load subscriptions</value>
</data>
<data name="HomeOptionsBarLoadSubscripionsButton.Width" xml:space="preserve">
<data name="Home_OptionsBar_LoadSubscripionsButton.Width" xml:space="preserve">
<value>120</value>
</data>
<data name="HomeOptionsBarPlaylistSearchButton.Label" xml:space="preserve">
<data name="Home_OptionsBar_PlaylistSearchButton.Label" xml:space="preserve">
<value>Playlist search</value>
</data>
<data name="HomeOptionsBarPlaylistSearchButton.Width" xml:space="preserve">
<data name="Home_OptionsBar_PlaylistSearchButton.Width" xml:space="preserve">
<value>95</value>
</data>
<data name="HomeOptionsBarPlaylistSearchControlInfoBox.Subtitle" xml:space="preserve">
<data name="Home_OptionsBar_PlaylistSearchControl_InfoBox.Subtitle" xml:space="preserve">
<value>- Twitch (Channel)
Number of videos got from playlist
The number in the numberbox indicades how many videos will be got from playlist. 0 = all.
</value>
</data>
<data name="HomeOptionsBarPlaylistSearchControlInfoBox.Title" xml:space="preserve">
<data name="Home_OptionsBar_PlaylistSearchControl_InfoBox.Title" xml:space="preserve">
<value>Supported sources</value>
</data>
<data name="HomeOptionsBarPlaylistSearchControlSearchButton.Content" xml:space="preserve">
<data name="Home_OptionsBar_PlaylistSearchControl_SearchButton.Content" xml:space="preserve">
<value>Search</value>
</data>
<data name="HomeOptionsBarPlaylistSearchControlUrlTextBox.PlaceholderText" xml:space="preserve">
<data name="Home_OptionsBar_PlaylistSearchControl_UrlTextBox.PlaceholderText" xml:space="preserve">
<value>Playlist URL</value>
</data>
<data name="HomeOptionsBarPlaylistSearchingErrorDialogTitle" xml:space="preserve">
<value>Playlist search error</value>
</data>
<data name="HomeOptionsBarPlaylistSearchingInternetConnectionErrorDialogDescription" xml:space="preserve">
<value>Unable to connect to servers. Check your internet connection.</value>
</data>
<data name="HomeOptionsBarPlaylistSearchingTwitchAccessTokenNotFoundErrorDialogDescription" xml:space="preserve">
<value>To get information about Twitch playlists (Channels), you have to link your Twitch account with VDownload. Go to Sources page to sign in.</value>
</data>
<data name="HomeOptionsBarPlaylistSearchingTwitchAccessTokenNotValidErrorDialogDescription" xml:space="preserve">
<value>There is a problem with linked Twitch account. Check Twitch login status in Sources page.</value>
</data>
<data name="HomeOptionsBarVideoSearchButton.Label" xml:space="preserve">
<data name="Home_OptionsBar_VideoSearchButton.Label" xml:space="preserve">
<value>Video search</value>
</data>
<data name="HomeOptionsBarVideoSearchButton.Width" xml:space="preserve">
<data name="Home_OptionsBar_VideoSearchButton.Width" xml:space="preserve">
<value>90</value>
</data>
<data name="HomeOptionsBarVideoSearchControlInfoBox.Subtitle" xml:space="preserve">
<value>- Twitch (VODs, Clips)</value>
</data>
<data name="HomeOptionsBarVideoSearchControlInfoBox.Title" xml:space="preserve">
<value>Supported sources</value>
</data>
<data name="HomeOptionsBarVideoSearchControlSearchButton.Content" xml:space="preserve">
<value>Search</value>
</data>
<data name="HomeOptionsBarVideoSearchControlUrlTextBox.PlaceholderText" xml:space="preserve">
<value>Video URL</value>
</data>
<data name="HomeOptionsBarVideoSearchingErrorDialogTitle" xml:space="preserve">
<value>Video search error</value>
</data>
<data name="HomeOptionsBarVideoSearchingInternetConnectionErrorDialogDescription" xml:space="preserve">
<value>Unable to connect to servers. Check your internet connection.</value>
</data>
<data name="HomeOptionsBarVideoSearchingTwitchAccessTokenNotFoundErrorDialogDescription" xml:space="preserve">
<value>To get information about Twitch videos (VODs and Clips), you have to link your Twitch account with VDownload. Go to Sources page to sign in.</value>
</data>
<data name="HomeOptionsBarVideoSearchingTwitchAccessTokenNotValidErrorDialogDescription" xml:space="preserve">
<value>There is a problem with linked Twitch account. Check Twitch login status in Sources page.</value>
</data>
<data name="HomePlaylistAddingApplyToAllApplyButton.Content" xml:space="preserve">
<data name="Home_Adding_Base_SerialVideoAddingControl_ApplyToAllOptions_Base_ApplyButton.Content" xml:space="preserve">
<value>Apply</value>
</data>
<data name="HomePlaylistAddingApplyToAllLocationBrowseButton.Content" xml:space="preserve">
<value>Browse</value>
</data>
<data name="HomePlaylistAddingApplyToAllLocationSettingControl.Title" xml:space="preserve">
<value>Location</value>
</data>
<data name="HomePlaylistAddingApplyToAllScheduleSettingControl.Description" xml:space="preserve">
<value>Number of minutes to start the task (after clicking downloading button).</value>
</data>
<data name="HomePlaylistAddingApplyToAllScheduleSettingControl.Title" xml:space="preserve">
<value>Schedule</value>
</data>
<data name="HomePlaylistAddingPanelApplyToAllOptionsHeaderTextBlock.Text" xml:space="preserve">
<data name="Home_Adding_Base_SerialVideoAddingControl_ApplyToAllOptions_HeaderTextBlock.Text" xml:space="preserve">
<value>Apply to all options</value>
</data>
<data name="HomePlaylistAddingPanelFilterAuthorTextBlock.Text" xml:space="preserve">
<value>Author</value>
</data>
<data name="HomePlaylistAddingPanelFilterAuthorTextBoxPlaceholderText" xml:space="preserve">
<value>Enter regular expression</value>
</data>
<data name="HomePlaylistAddingPanelFilterDateTextBlock.Text" xml:space="preserve">
<value>Date</value>
</data>
<data name="HomePlaylistAddingPanelFilterDurationTextBlock.Text" xml:space="preserve">
<value>Duration</value>
</data>
<data name="HomePlaylistAddingPanelFilterHeaderApplyButton.Content" xml:space="preserve">
<value>Apply</value>
</data>
<data name="HomePlaylistAddingPanelFilterHeaderCountTextBlockPrefix" xml:space="preserve">
<value>Hidden</value>
</data>
<data name="HomePlaylistAddingPanelFilterHeaderTextBlock.Text" xml:space="preserve">
<value>Filter</value>
</data>
<data name="HomePlaylistAddingPanelFilterRemovedRestoreButton.Content" xml:space="preserve">
<value>Restore</value>
</data>
<data name="HomePlaylistAddingPanelFilterRemovedTextBlock.Text" xml:space="preserve">
<value>Removed</value>
</data>
<data name="HomePlaylistAddingPanelFilterTitleTextBlock.Text" xml:space="preserve">
<value>Title</value>
</data>
<data name="HomePlaylistAddingPanelFilterTitleTextBoxPlaceholderText" xml:space="preserve">
<value>Enter regular expression</value>
</data>
<data name="HomePlaylistAddingPanelFilterViewsTextBlock.Text" xml:space="preserve">
<value>Views</value>
</data>
<data name="HomePlaylistAddingVideoPanelDownloadingOptionsHeaderTextBlock.Text" xml:space="preserve">
<value>Downloading options</value>
</data>
<data name="HomePlaylistAddingVideoPanelFileOptionsHeaderTextBlock.Text" xml:space="preserve">
<value>File options</value>
</data>
<data name="HomePlaylistAddingVideoPanelFileSettingControl.Title" xml:space="preserve">
<value>File</value>
</data>
<data name="HomePlaylistAddingVideoPanelLocationBrowseButton.Content" xml:space="preserve">
<value>Browse</value>
</data>
<data name="HomePlaylistAddingVideoPanelLocationSettingControl.Title" xml:space="preserve">
<data name="Home_Adding_Base_SerialVideoAddingControl_ApplyToAllOptions_LocationSettingControl.Title" xml:space="preserve">
<value>Location</value>
</data>
<data name="HomePlaylistAddingVideoPanelMediaTypeSettingControl.Title" xml:space="preserve">
<value>Media type</value>
<data name="Home_Adding_Base_SerialVideoAddingControl_ApplyToAllOptions_LocationSettingControl_BrowseButton.Content" xml:space="preserve">
<value>Browse</value>
</data>
<data name="HomePlaylistAddingVideoPanelQualitySettingControl.Title" xml:space="preserve">
<value>Quality</value>
</data>
<data name="HomePlaylistAddingVideoPanelScheduleSettingControl.Description" xml:space="preserve">
<data name="Home_Adding_Base_SerialVideoAddingControl_ApplyToAllOptions_ScheduleSettingControl.Description" xml:space="preserve">
<value>Number of minutes to start the task (after clicking downloading button).</value>
</data>
<data name="HomePlaylistAddingVideoPanelScheduleSettingControl.Title" xml:space="preserve">
<data name="Home_Adding_Base_SerialVideoAddingControl_ApplyToAllOptions_ScheduleSettingControl.Title" xml:space="preserve">
<value>Schedule</value>
</data>
<data name="HomePlaylistAddingVideoPanelTaskOptionsHeaderTextBlock.Text" xml:space="preserve">
<value>Task options</value>
<data name="Home_OptionsBar_SubscriptionsLoadControl_InfoTextBlock_Loading" xml:space="preserve">
<value>Loading new videos</value>
</data>
<data name="HomePlaylistAddingVideoPanelTrimSettingControl.Title" xml:space="preserve">
<value>Trim</value>
<data name="Home_OptionsBar_VideoSearchControl_InfoBox.Subtitle" xml:space="preserve">
<value>- Twitch (VODs, Clips)</value>
</data>
<data name="HomeTaskPanelStateTextCancelled" xml:space="preserve">
<value>Cancelled</value>
<data name="Home_OptionsBar_VideoSearchControl_InfoBox.Title" xml:space="preserve">
<value>Supported sources</value>
</data>
<data name="HomeTaskPanelStateTextDone" xml:space="preserve">
<value>Done</value>
<data name="Home_OptionsBar_VideoSearchControl_SearchButton.Content" xml:space="preserve">
<value>Search</value>
</data>
<data name="HomeTaskPanelStateTextDownloading" xml:space="preserve">
<value>Downloading</value>
<data name="Home_OptionsBar_VideoSearchControl_UrlTextBox.PlaceholderText" xml:space="preserve">
<value>Video URL</value>
</data>
<data name="HomeTaskPanelStateTextError" xml:space="preserve">
<value>An error occured</value>
<data name="Main_NavigationPanel_About.Content" xml:space="preserve">
<value>About</value>
</data>
<data name="HomeTaskPanelStateTextFinalizing" xml:space="preserve">
<value>Finalizing</value>
</data>
<data name="HomeTaskPanelStateTextIdle" xml:space="preserve">
<value>Idle</value>
</data>
<data name="HomeTaskPanelStateTextProcessing" xml:space="preserve">
<value>Processing</value>
</data>
<data name="HomeTaskPanelStateTextScheduled" xml:space="preserve">
<value>Scheduled</value>
</data>
<data name="HomeTaskPanelStateTextWaiting" xml:space="preserve">
<value>Queued</value>
</data>
<data name="HomeTaskPanelTaskStartMeteredConnectionDialogCancel" xml:space="preserve">
<value>No</value>
</data>
<data name="HomeTaskPanelTaskStartMeteredConnectionDialogDescription" xml:space="preserve">
<value>You are on the metered connection now. You can delay task until the network changes. Do you want to start the task?</value>
</data>
<data name="HomeTaskPanelTaskStartMeteredConnectionDialogStartAndDelayText" xml:space="preserve">
<value>Yes (With delay)</value>
</data>
<data name="HomeTaskPanelTaskStartMeteredConnectionDialogStartWithoutDelayText" xml:space="preserve">
<value>Yes (Without delay)</value>
</data>
<data name="HomeTaskPanelTaskStartMeteredConnectionDialogTitle" xml:space="preserve">
<value>Metered connection detected</value>
</data>
<data name="HomeTasksListPlaceholderTextBlock.Text" xml:space="preserve">
<value>Click "Video/Playlist search" button to start</value>
</data>
<data name="MainPageNavigationPanelHomeItem.Content" xml:space="preserve">
<data name="Main_NavigationPanel_Home.Content" xml:space="preserve">
<value>Home</value>
</data>
<data name="MainPageNavigationPanelSourcesItem.Content" xml:space="preserve">
<data name="Main_NavigationPanel_Sources.Content" xml:space="preserve">
<value>Sources</value>
</data>
<data name="MainPageNavigationPanelSubscriptionsItem.Content" xml:space="preserve">
<data name="Main_NavigationPanel_Subscriptions.Content" xml:space="preserve">
<value>Subscriptions</value>
</data>
<data name="MediaTypeAudioVideoText" xml:space="preserve">
<value>Audio &amp; Video</value>
</data>
<data name="MediaTypeOnlyAudioText" xml:space="preserve">
<value>Only audio</value>
</data>
<data name="MediaTypeOnlyVideoText" xml:space="preserve">
<value>Only video</value>
</data>
<data name="NotificationTaskEndedSuccessfullyDescription" xml:space="preserve">
<value>downloading ended successfully</value>
</data>
<data name="NotificationTaskEndedSuccessfullyHeader" xml:space="preserve">
<value>Video downloading ended successfully</value>
</data>
<data name="NotificationTaskEndedUnsuccessfullyDescription" xml:space="preserve">
<value>an error occured</value>
</data>
<data name="NotificationTaskEndedUnsuccessfullyHeader" xml:space="preserve">
<value>Video downloading ended unsuccessfully</value>
</data>
<data name="SourcesTwitchLoginErrorDialogDescriptionInternetConnectionError" xml:space="preserve">
<value>Unable to connect to Twitch servers. Check your internet connection.</value>
</data>
<data name="SourcesTwitchLoginErrorDialogDescriptionUnknown" xml:space="preserve">
<value>Unknown error</value>
</data>
<data name="SourcesTwitchLoginErrorDialogTitle" xml:space="preserve">
<value>Login to Twitch failed</value>
</data>
<data name="Sources_HeaderTextBlock.Text" xml:space="preserve">
<value>Sources</value>
</data>
@@ -424,12 +331,12 @@ The number in the numberbox indicades how many videos will be got from playlist.
<data name="Sources_TwitchSettingControl.Title" xml:space="preserve">
<value>Twitch</value>
</data>
<data name="Sources_TwitchSettingControl_Description_AccessTokenExpired" xml:space="preserve">
<value>Your Twitch access token has expired. Please sign in.</value>
</data>
<data name="Sources_TwitchSettingControl_Description_InternetNotAvailable" xml:space="preserve">
<value>Unable to connect to Twitch servers. Check your internet connection.</value>
</data>
<data name="Sources_TwitchSettingControl_Description_InvalidAccessToken" xml:space="preserve">
<value>Your Twitch access token is invalid (maybe expired?). Please sign in to get new access token.</value>
</data>
<data name="Sources_TwitchSettingControl_Description_LoggedIn" xml:space="preserve">
<value>Logged in as</value>
</data>
@@ -440,7 +347,7 @@ The number in the numberbox indicades how many videos will be got from playlist.
<value>Loading...</value>
</data>
<data name="Sources_TwitchSettingControl_LoginButton_Content_LoggedIn" xml:space="preserve">
<value>Log out</value>
<value>Sign out</value>
</data>
<data name="Sources_TwitchSettingControl_LoginButton_Content_NotLoggedIn" xml:space="preserve">
<value>Sign in</value>
@@ -460,7 +367,16 @@ The number in the numberbox indicades how many videos will be got from playlist.
<data name="Subscriptions_SubscriptionPanel_CountTextBlock_SyncText" xml:space="preserve">
<value>Syncing...</value>
</data>
<data name="TaskErrorInternetConnection" xml:space="preserve">
<value>Internet connection error</value>
<data name="Home_Adding_Subscriptions_HeaderTextBlock.Text" xml:space="preserve">
<value>Subscriptions</value>
</data>
<data name="Home_OptionsBar_SubscriptionsLoadControl_InfoTextBlock_Found" xml:space="preserve">
<value>Found videos</value>
</data>
<data name="Home_OptionsBar_SubscriptionsLoadControl_LoadVideosButton.Content" xml:space="preserve">
<value>Load</value>
</data>
<data name="Settings_HeaderTextBlock.Text" xml:space="preserve">
<value>Settings</value>
</data>
</root>

View File

@@ -119,41 +119,50 @@
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\PlaceholderableStackPanel.xaml.cs">
<DependentUpon>PlaceholderableStackPanel.xaml</DependentUpon>
</Compile>
<Compile Include="Converters\StringToVisibilityConverter.cs" />
<Compile Include="Converters\TimeSpanToTextBoxMaskConverter.cs" />
<Compile Include="Converters\TimeSpanToTextBoxMaskElementsConverter.cs" />
<Compile Include="Views\About\AboutMain.xaml.cs">
<DependentUpon>AboutMain.xaml</DependentUpon>
<Compile Include="Views\About\MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Home\Controls\HomeAddingVideoOptions.xaml.cs">
<DependentUpon>HomeAddingVideoOptions.xaml</DependentUpon>
<Compile Include="Views\Home\Controls\SerialVideoAddingControl.xaml.cs">
<DependentUpon>SerialVideoAddingControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Home\Controls\HomeOptionsBarPlaylistSearch.xaml.cs">
<DependentUpon>HomeOptionsBarPlaylistSearch.xaml</DependentUpon>
<Compile Include="Views\Home\Controls\VideoAddingOptionsControl.xaml.cs">
<DependentUpon>VideoAddingOptionsControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Home\Controls\HomeOptionsBarVideoSearch.xaml.cs">
<DependentUpon>HomeOptionsBarVideoSearch.xaml</DependentUpon>
<Compile Include="Views\Home\Controls\SubscriptionsLoadControl.xaml.cs">
<DependentUpon>SubscriptionsLoadControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Home\Controls\PlaylistSearchControl.xaml.cs">
<DependentUpon>PlaylistSearchControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Home\Controls\VideoSearchControl.xaml.cs">
<DependentUpon>VideoSearchControl.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\SettingControl.xaml.cs">
<DependentUpon>SettingControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Home\HomeMain.xaml.cs">
<DependentUpon>HomeMain.xaml</DependentUpon>
<Compile Include="Views\Home\MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Home\HomePlaylistAddingPanel.xaml.cs">
<DependentUpon>HomePlaylistAddingPanel.xaml</DependentUpon>
<Compile Include="Views\Home\SubscriptionsAddingPanel.xaml.cs">
<DependentUpon>SubscriptionsAddingPanel.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Home\Controls\HomeSerialAddingVideoPanel.xaml.cs">
<DependentUpon>HomeSerialAddingVideoPanel.xaml</DependentUpon>
<Compile Include="Views\Home\PlaylistAddingPanel.xaml.cs">
<DependentUpon>PlaylistAddingPanel.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Home\Controls\HomeTasksListPlaceholder.xaml.cs">
<DependentUpon>HomeTasksListPlaceholder.xaml</DependentUpon>
<Compile Include="Views\Home\Controls\SerialVideoAddingVideoControl.xaml.cs">
<DependentUpon>SerialVideoAddingVideoControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Home\HomeVideoAddingPanel.xaml.cs">
<DependentUpon>HomeVideoAddingPanel.xaml</DependentUpon>
<Compile Include="Views\Home\VideoAddingPanel.xaml.cs">
<DependentUpon>VideoAddingPanel.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Home\Controls\HomeTaskPanel.xaml.cs">
<DependentUpon>HomeTaskPanel.xaml</DependentUpon>
<Compile Include="Views\Home\Controls\DownloadTaskControl.xaml.cs">
<DependentUpon>DownloadTaskControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
@@ -161,8 +170,8 @@
<Compile Include="Views\Sources\MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Settings\SettingsMain.xaml.cs">
<DependentUpon>SettingsMain.xaml</DependentUpon>
<Compile Include="Views\Settings\MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Subscriptions\Controls\SubscriptionPanel.xaml.cs">
<DependentUpon>SubscriptionPanel.xaml</DependentUpon>
@@ -214,8 +223,8 @@
<Content Include="Assets\Icons\StateProcessingLight.svg" />
<Content Include="Assets\Icons\StateScheduledDark.svg" />
<Content Include="Assets\Icons\StateScheduledLight.svg" />
<Content Include="Assets\Icons\StateWaitingDark.svg" />
<Content Include="Assets\Icons\StateWaitingLight.svg" />
<Content Include="Assets\Icons\StateQueuedDark.svg" />
<Content Include="Assets\Icons\StateQueuedLight.svg" />
<Content Include="Assets\Icons\TrimDark.svg" />
<Content Include="Assets\Icons\TrimLight.svg" />
<Content Include="Assets\Icons\Twitch.svg" />
@@ -281,6 +290,10 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Page Include="Controls\PlaceholderableStackPanel.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Resources\Colors.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@@ -297,19 +310,27 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\About\AboutMain.xaml">
<Page Include="Views\About\MainPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Home\Controls\HomeAddingVideoOptions.xaml">
<Page Include="Views\Home\Controls\SerialVideoAddingControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Home\Controls\HomeOptionsBarPlaylistSearch.xaml">
<Page Include="Views\Home\Controls\VideoAddingOptionsControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Home\Controls\SubscriptionsLoadControl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Views\Home\Controls\HomeOptionsBarVideoSearch.xaml">
<Page Include="Views\Home\Controls\PlaylistSearchControl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Views\Home\Controls\VideoSearchControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
@@ -317,27 +338,27 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Home\HomeMain.xaml">
<Page Include="Views\Home\MainPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Home\HomePlaylistAddingPanel.xaml">
<Page Include="Views\Home\SubscriptionsAddingPanel.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Views\Home\PlaylistAddingPanel.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Home\Controls\HomeSerialAddingVideoPanel.xaml">
<Page Include="Views\Home\Controls\SerialVideoAddingVideoControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Home\Controls\HomeTasksListPlaceholder.xaml">
<Page Include="Views\Home\VideoAddingPanel.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Home\HomeVideoAddingPanel.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Home\Controls\HomeTaskPanel.xaml">
<Page Include="Views\Home\Controls\DownloadTaskControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
@@ -349,7 +370,7 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Settings\SettingsMain.xaml">
<Page Include="Views\Settings\MainPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>

View File

@@ -1,5 +1,5 @@
<Page
x:Class="VDownload.Views.About.AboutMain"
x:Class="VDownload.Views.About.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.About"

View File

@@ -20,9 +20,9 @@ namespace VDownload.Views.About
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class AboutMain : Page
public sealed partial class MainPage : Page
{
public AboutMain()
public MainPage()
{
this.InitializeComponent();
}

View File

@@ -1,5 +1,5 @@
<UserControl
x:Class="VDownload.Views.Home.Controls.HomeTaskPanel"
x:Class="VDownload.Views.Home.Controls.DownloadTaskControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.Home"
@@ -39,8 +39,8 @@
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" FontSize="18" VerticalAlignment="Center" FontWeight="SemiBold" Text="{x:Bind Data.VideoService.Metadata.Title}"/>
<TextBlock Grid.Column="1" FontSize="12" VerticalAlignment="Bottom" FontWeight="Light" Text="{x:Bind Data.VideoService.Metadata.Author}" Margin="0,0,0,2"/>
<TextBlock Grid.Column="0" FontSize="18" VerticalAlignment="Center" FontWeight="SemiBold" Text="{x:Bind DownloadTask.Video.Title}"/>
<TextBlock Grid.Column="1" FontSize="12" VerticalAlignment="Bottom" FontWeight="Light" Text="{x:Bind DownloadTask.Video.Author}" Margin="0,0,0,2"/>
</Grid>
<Grid Grid.Row="1" Grid.RowSpan="2" Grid.Column="1" ColumnSpacing="5">
<Grid.RowDefinitions>
@@ -60,12 +60,12 @@
<TextBlock Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" VerticalAlignment="Center" FontSize="{StaticResource MetadataTextSize}" Text="{x:Bind Duration}"/>
<Image Grid.Row="2" Grid.Column="0" Width="{StaticResource MetadataIconSize}" Source="{ThemeResource FileIcon}"/>
<TextBlock Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" VerticalAlignment="Center" FontSize="{StaticResource MetadataTextSize}" Text="{x:Bind File}"/>
<Image x:Name="HomeTaskPanelStateIcon" Grid.Row="3" Grid.Column="0" Width="{StaticResource MetadataIconSize}"/>
<TextBlock x:Name="HomeTaskPanelStateText" Grid.Row="3" Grid.Column="1" VerticalAlignment="Center" FontSize="{StaticResource MetadataTextSize}"/>
<muxc:ProgressBar x:Name="HomeTaskPanelStateProgressBar" Grid.Row="3" Grid.Column="2" Margin="10,0,0,0" VerticalAlignment="Center"/>
<Image x:Name="StateIcon" Grid.Row="3" Grid.Column="0" Width="{StaticResource MetadataIconSize}"/>
<TextBlock x:Name="StateText" Grid.Row="3" Grid.Column="1" VerticalAlignment="Center" FontSize="{StaticResource MetadataTextSize}"/>
<muxc:ProgressBar x:Name="StateProgressBar" Grid.Row="3" Grid.Column="2" Margin="10,0,0,0" VerticalAlignment="Center"/>
</Grid>
<AppBarButton Grid.Row="0" Grid.Column="2" VerticalAlignment="Center" Width="40" Height="48" Margin="-3" Icon="{x:Bind SourceImage}" Click="HomeTaskPanelSourceButton_Click"/>
<AppBarButton x:Name="HomeTaskPanelStartStopButton" Grid.Row="1" Grid.Column="2" VerticalAlignment="Center" Width="40" Height="48" Margin="-3" Icon="Download" Click="HomeTaskPanelStartStopButton_Click"/>
<AppBarButton Grid.Row="2" Grid.Column="2" VerticalAlignment="Center" Width="40" Height="48" Margin="-3" Icon="Cancel" Click="HomeTaskPanelRemoveButton_Click"/>
<AppBarButton Grid.Row="0" Grid.Column="2" VerticalAlignment="Center" Width="40" Height="48" Margin="-3" Icon="{x:Bind SourceImage}" Click="SourceButton_Click"/>
<AppBarButton x:Name="StartStopButton" Grid.Row="1" Grid.Column="2" VerticalAlignment="Center" Width="40" Height="48" Margin="-3" Icon="Download" Click="StartStopButton_Click"/>
<AppBarButton Grid.Row="2" Grid.Column="2" VerticalAlignment="Center" Width="40" Height="48" Margin="-3" Icon="Cancel" Click="RemoveButton_Click"/>
</Grid>
</UserControl>

View File

@@ -0,0 +1,248 @@
using Microsoft.Toolkit.Uwp.Connectivity;
using Microsoft.Toolkit.Uwp.Notifications;
using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.Extensions;
using VDownload.Core.Services;
using VDownload.Core.Structs;
using Windows.ApplicationModel.ExtendedExecution;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
namespace VDownload.Views.Home.Controls
{
public sealed partial class DownloadTaskControl : UserControl
{
#region CONSTANTS
private static readonly ResourceDictionary IconsRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Icons.xaml") };
private static readonly ResourceDictionary ImagesRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Images.xaml") };
#endregion
#region CONSTRUCTORS
public DownloadTaskControl(DownloadTask downloadTask)
{
this.InitializeComponent();
DownloadTask = downloadTask;
DownloadTask.StatusChanged += UpdateStatus;
ThumbnailImage = DownloadTask.Video.Thumbnail != null ? new BitmapImage { UriSource = DownloadTask.Video.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"];
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{DownloadTask.Video.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
TimeSpan newDuration = DownloadTask.Trim.End.Subtract(DownloadTask.Trim.Start);
StringBuilder durationBuilder = new StringBuilder(newDuration.ToStringOptTHBaseMMSS());
if (DownloadTask.Video.Duration > newDuration)
{
durationBuilder.Append($" ({DownloadTask.Trim.Start.ToStringOptTHBaseMMSS(DownloadTask.Trim.End)} - {DownloadTask.Trim.End.ToStringOptTHBaseMMSS(DownloadTask.Trim.Start)})");
}
Duration = durationBuilder.ToString();
StringBuilder mediaTypeQualityBuilder = new StringBuilder(ResourceLoader.GetForCurrentView().GetString($"Base_MediaType_{DownloadTask.MediaType}Text"));
if (DownloadTask.MediaType != MediaType.OnlyAudio)
{
mediaTypeQualityBuilder.Append($" ({DownloadTask.SelectedStream})");
}
MediaTypeQuality = mediaTypeQualityBuilder.ToString();
File = DownloadTask.File.GetPath();
UpdateStatus(this, EventArgs.Empty);
}
#endregion
#region PROPERTIES
private DownloadTask DownloadTask { get; set; }
private ImageSource ThumbnailImage { get; set; }
private IconElement SourceImage { get; set; }
private string Title { get; set; }
private string Author { get; set; }
private string Duration { get; set; }
private string MediaTypeQuality { get; set; }
private string File { get; set; }
#endregion
#region EVENT HANDLERS
private async void StartStopButton_Click(object sender, RoutedEventArgs e)
{
if (DownloadTask.Status != DownloadTaskStatus.Idle)
{
DownloadTask.CancellationTokenSource.Cancel();
}
else
{
bool delay = (bool)Config.GetValue("delay_task_when_queued_task_starts_on_metered_network");
if (NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)
{
ContentDialogResult dialogResult = await new ContentDialog
{
Title = ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_Start_MeteredConnection_Title"),
Content = ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_Start_MeteredConnection_Content"),
PrimaryButtonText = ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_Start_MeteredConnection_StartWithDelayButtonText"),
SecondaryButtonText = ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_Start_MeteredConnection_StartWithoutDelayButtonText1"),
CloseButtonText = ResourceLoader.GetForCurrentView().GetString("Base_CancelButtonText"),
}.ShowAsync();
switch (dialogResult)
{
case ContentDialogResult.Primary: delay = true; break;
case ContentDialogResult.Secondary: delay = false; break;
case ContentDialogResult.None: return;
}
}
await DownloadTask.Run(delay);
}
}
private void RemoveButton_Click(object sender, RoutedEventArgs e)
{
if (DownloadTask.Status != DownloadTaskStatus.Idle)
{
DownloadTask.CancellationTokenSource.Cancel();
}
RemovingRequested.Invoke(this, EventArgs.Empty);
}
private async void SourceButton_Click(object sender, RoutedEventArgs e)
{
await Windows.System.Launcher.LaunchUriAsync(DownloadTask.Video.Url);
}
private void UpdateStatus(object sender, EventArgs e)
{
if (DownloadTask.Status == DownloadTaskStatus.Idle || DownloadTask.Status == DownloadTaskStatus.EndedSuccessfully || DownloadTask.Status == DownloadTaskStatus.EndedUnsuccessfully)
{
StartStopButton.Icon = new SymbolIcon(Symbol.Download);
}
else
{
StartStopButton.Icon = new SymbolIcon(Symbol.Stop);
}
if (DownloadTask.Status == DownloadTaskStatus.Scheduled)
{
StateIcon.Source = (SvgImageSource)IconsRes["StateScheduledIcon"];
StateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_State_Scheduled")} ({DownloadTask.ScheduledFor.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortDatePattern)} {DownloadTask.ScheduledFor.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortTimePattern)})";
StateProgressBar.Visibility = Visibility.Collapsed;
}
else if (DownloadTask.Status == DownloadTaskStatus.Queued)
{
StateIcon.Source = (SvgImageSource)IconsRes["StateQueuedIcon"];
StateText.Text = ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_State_Queued");
StateProgressBar.Visibility = Visibility.Visible;
StateProgressBar.IsIndeterminate = true;
}
else if (DownloadTask.Status == DownloadTaskStatus.Downloading)
{
StateIcon.Source = (SvgImageSource)IconsRes["StateDownloadingIcon"];
StateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_State_Downloading")} ({Math.Round(DownloadTask.DownloadingProgress)}%)";
StateProgressBar.Visibility = Visibility.Visible;
StateProgressBar.IsIndeterminate = false;
StateProgressBar.Value = DownloadTask.DownloadingProgress;
}
else if (DownloadTask.Status == DownloadTaskStatus.Processing)
{
StateIcon.Source = (SvgImageSource)IconsRes["StateProcessingIcon"];
StateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_State_Processing")} ({Math.Round(DownloadTask.ProcessingProgress)}%)";
StateProgressBar.Visibility = Visibility.Visible;
StateProgressBar.IsIndeterminate = false;
StateProgressBar.Value = DownloadTask.ProcessingProgress;
}
else if (DownloadTask.Status == DownloadTaskStatus.Finalizing)
{
StateIcon.Source = (SvgImageSource)IconsRes["StateFinalizingIcon"];
StateText.Text = ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_State_Finalizing");
StateProgressBar.Visibility = Visibility.Visible;
StateProgressBar.IsIndeterminate = true;
}
else if (DownloadTask.Status == DownloadTaskStatus.EndedSuccessfully)
{
StateIcon.Source = (SvgImageSource)IconsRes["StateDoneIcon"];
StateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_State_Done")} ({DownloadTask.ElapsedTime.ToStringOptTHBaseMMSS()})";
StateProgressBar.Visibility = Visibility.Collapsed;
if ((bool)Config.GetValue("show_notification_when_task_ended_successfully"))
{
new ToastContentBuilder()
.AddText(ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_Notification_EndedSuccessfully_Header"))
.AddText($"\"{Title}\" - {ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_Notification_EndedSuccessfully_Description")}")
.Show();
}
}
else if (DownloadTask.Status == DownloadTaskStatus.EndedUnsuccessfully)
{
if (DownloadTask.Exception is OperationCanceledException)
{
StateIcon.Source = (SvgImageSource)IconsRes["StateCancelledIcon"];
StateText.Text = ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_State_Cancelled");
StateProgressBar.Visibility = Visibility.Collapsed;
}
else
{
string errorInfo;
if (DownloadTask.Exception is WebException)
{
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable) errorInfo = ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_Error_InternetNotAvailable");
else throw DownloadTask.Exception;
}
else
{
throw DownloadTask.Exception;
}
StateIcon.Source = (SvgImageSource)IconsRes["StateErrorIcon"];
StateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_State_Error")} ({errorInfo})";
StateProgressBar.Visibility = Visibility.Collapsed;
if ((bool)Config.GetValue("show_notification_when_task_ended_unsuccessfully"))
{
new ToastContentBuilder()
.AddText(ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_Notification_EndedUnsuccessfully_Header"))
.AddText($"\"{Title}\" - {ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_Notification_EndedUnsuccessfully_Description")} ({errorInfo})")
.Show();
}
}
}
else
{
StateIcon.Source = (SvgImageSource)IconsRes["StateIdleIcon"];
StateText.Text = ResourceLoader.GetForCurrentView().GetString("Home_DownloadTaskControl_State_Idle");
StateProgressBar.Visibility = Visibility.Collapsed;
}
}
#endregion
#region EVENT HANDLERS
public event EventHandler RemovingRequested;
#endregion
}
}

View File

@@ -1,76 +0,0 @@
<UserControl
x:Class="VDownload.Views.Home.Controls.HomeAddingVideoOptions"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.Home.Controls"
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"
xmlns:ex="using:Microsoft.Toolkit.Uwp.UI"
xmlns:cc="using:VDownload.Controls"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///Resources/Icons.xaml"/>
<ResourceDictionary Source="ms-appx:///Resources/Converters.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel Spacing="30">
<StackPanel>
<TextBlock x:Uid="HomeAddingDownloadingOptionsHeaderTextBlock" Margin="0,0,0,10" FontWeight="SemiBold"/>
<cc:SettingControl x:Uid="HomeAddingMediaTypeSettingControl" Margin="0,0,0,10" Icon="{ThemeResource MediaTypeIcon}">
<cc:SettingControl.SettingContent>
<ComboBox x:Name="HomeAddingMediaTypeSettingControlComboBox" Width="150" SelectionChanged="HomeAddingMediaTypeSettingControlComboBox_SelectionChanged"/>
</cc:SettingControl.SettingContent>
</cc:SettingControl>
<cc:SettingControl x:Name="HomeAddingQualitySettingControl" Margin="0,0,0,10" x:Uid="HomeAddingQualitySettingControl" Icon="{ThemeResource QualityIcon}">
<cc:SettingControl.SettingContent>
<ComboBox x:Name="HomeAddingQualitySettingControlComboBox" Width="150" SelectionChanged="HomeAddingQualitySettingControlComboBox_SelectionChanged"/>
</cc:SettingControl.SettingContent>
</cc:SettingControl>
<cc:SettingControl x:Uid="HomeAddingTrimSettingControl" Icon="{ThemeResource TrimIcon}">
<cc:SettingControl.SettingContent>
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBox x:Name="HomeAddingTrimStartTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" LostFocus="HomeAddingTrimStartTextBox_LostFocus"/>
<TextBlock VerticalAlignment="Center" Text="-"/>
<TextBox x:Name="HomeAddingTrimEndTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind VideoService.Metadata.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" LostFocus="HomeAddingTrimEndTextBox_LostFocus"/>
</StackPanel>
</cc:SettingControl.SettingContent>
</cc:SettingControl>
</StackPanel>
<StackPanel Spacing="10">
<TextBlock x:Uid="HomeAddingFileOptionsHeaderTextBlock" FontWeight="SemiBold"/>
<cc:SettingControl x:Uid="HomeAddingFileSettingControl" Icon="{ThemeResource FileIcon}">
<cc:SettingControl.SettingContent>
<Grid ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="HomeAddingFilenameTextBox" Grid.Column="0" MaxWidth="300" HorizontalAlignment="Right" IsSpellCheckEnabled="False" LostFocus="HomeAddingFilenameTextBox_LostFocus"/>
<ComboBox x:Name="HomeAddingExtensionComboBox" Grid.Column="1" HorizontalAlignment="Stretch" SelectionChanged="HomeAddingExtensionComboBox_SelectionChanged"/>
</Grid>
</cc:SettingControl.SettingContent>
</cc:SettingControl>
<cc:SettingControl x:Name="HomeAddingLocationSettingControl" x:Uid="HomeAddingLocationSettingControl" Icon="{ThemeResource LocationIcon}">
<cc:SettingControl.SettingContent>
<Button x:Uid="HomeAddingLocationBrowseButton" Click="HomeAddingLocationBrowseButton_Click"/>
</cc:SettingControl.SettingContent>
</cc:SettingControl>
</StackPanel>
<StackPanel Spacing="10">
<TextBlock x:Uid="HomeAddingTaskOptionsHeaderTextBlock" FontWeight="SemiBold"/>
<cc:SettingControl x:Uid="HomeAddingScheduleSettingControl" Icon="{ThemeResource ScheduleIcon}">
<cc:SettingControl.SettingContent>
<muxc:NumberBox x:Name="HomeAddingScheduleNumberBox" MaxWidth="100" Value="0" Minimum="0" SpinButtonPlacementMode="Compact" LostFocus="HomeAddingScheduleNumberBox_LostFocus"/>
</cc:SettingControl.SettingContent>
</cc:SettingControl>
</StackPanel>
</StackPanel>
</UserControl>

View File

@@ -1,279 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Structs;
using Windows.ApplicationModel.Resources;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
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.Home.Controls
{
public sealed partial class HomeAddingVideoOptions : UserControl
{
#region CONSTRUCTORS
public HomeAddingVideoOptions(IVideo videoService)
{
this.InitializeComponent();
// Set video service
VideoService = videoService;
}
// INIT CONTROL
public async Task Init()
{
// Set media type
foreach (string mediaType in Enum.GetNames(typeof(MediaType)))
{
HomeAddingMediaTypeSettingControlComboBox.Items.Add(ResourceLoader.GetForCurrentView().GetString($"MediaType{mediaType}Text"));
}
HomeAddingMediaTypeSettingControlComboBox.SelectedIndex = (int)Config.GetValue("default_media_type");
// Set quality
foreach (BaseStream stream in VideoService.BaseStreams)
{
HomeAddingQualitySettingControlComboBox.Items.Add($"{stream.Height}p{(stream.FrameRate > 0 ? stream.FrameRate.ToString() : "N/A")}");
}
HomeAddingQualitySettingControlComboBox.SelectedIndex = 0;
// Set trim start
TrimStart = TimeSpan.Zero;
HomeAddingTrimStartTextBox.Text = TimeSpanCustomFormat.ToOptTHMMBaseSS(TrimStart, VideoService.Metadata.Duration);
// Set trim end
TrimEnd = VideoService.Metadata.Duration;
HomeAddingTrimEndTextBox.Text = TimeSpanCustomFormat.ToOptTHMMBaseSS(TrimEnd);
// Set filename
string temporaryFilename = (string)Config.GetValue("default_filename");
Dictionary<string, string> filenameStandardTemplates = new Dictionary<string, string>()
{
{ "<title>", VideoService.Metadata.Title },
{ "<author>", VideoService.Metadata.Author },
{ "<views>", VideoService.Metadata.Views.ToString() },
{ "<id>", VideoService.ID },
};
foreach (KeyValuePair<string, string> template in filenameStandardTemplates) temporaryFilename = temporaryFilename.Replace(template.Key, template.Value);
Dictionary<Regex, IFormattable> filenameFormatTemplates = new Dictionary<Regex, IFormattable>()
{
{ new Regex(@"<date_pub:(?<format>.*)>"), VideoService.Metadata.Date },
{ new Regex(@"<date_now:(?<format>.*)>"), DateTime.Now },
{ new Regex(@"<duration:(?<format>.*)>"), VideoService.Metadata.Duration },
};
foreach (KeyValuePair<Regex, IFormattable> template in filenameFormatTemplates) foreach (Match templateMatch in template.Key.Matches(temporaryFilename)) temporaryFilename = temporaryFilename.Replace(templateMatch.Value, template.Value.ToString(templateMatch.Groups["format"].Value, null));
foreach (char c in Path.GetInvalidFileNameChars()) temporaryFilename = temporaryFilename.Replace(c, ' ');
Filename = temporaryFilename;
HomeAddingFilenameTextBox.Text = Filename;
// Set location
if (!(bool)Config.GetValue("custom_media_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("last_media_location"))
{
Location = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("last_media_location");
HomeAddingLocationSettingControl.Description = Location.Path;
}
else if ((bool)Config.GetValue("custom_media_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("custom_media_location"))
{
Location = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("selected_media_location");
HomeAddingLocationSettingControl.Description = Location.Path;
}
else
{
Location = null;
HomeAddingLocationSettingControl.Description = $@"{UserDataPaths.GetDefault().Downloads}\VDownload";
}
// Set schedule
Schedule = 0;
}
#endregion
#region PROPERTIES
// VIDEO SERVICE
private IVideo VideoService { get; set; }
// TASK OPTIONS
public MediaType MediaType { get; private set; }
public BaseStream Stream { get; private set; }
public TimeSpan TrimStart { get; private set; }
public TimeSpan TrimEnd { get; private set; }
public string Filename { get; private set; }
public MediaFileExtension Extension { get; private set; }
public StorageFolder Location { get; private set; }
public double Schedule { get; private set; }
#endregion
#region CHANGE PROPERTIES FROM PARENT VOIDS
// LOCATION
public void ChangeLocation(StorageFolder location)
{
Location = location;
HomeAddingLocationSettingControl.Description = Location.Path ?? $@"{UserDataPaths.GetDefault().Downloads}\VDownload";
}
// SCHEDULE
public void ChangeSchedule(double schedule)
{
Schedule = schedule;
HomeAddingScheduleNumberBox.Value = Schedule;
}
#endregion
#region EVENT HANDLERS VOIDS
// MEDIA TYPE COMBOBOX SELECTION CHANGED
private void HomeAddingMediaTypeSettingControlComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MediaType = (MediaType)HomeAddingMediaTypeSettingControlComboBox.SelectedIndex;
if (HomeAddingMediaTypeSettingControlComboBox.SelectedIndex == (int)MediaType.OnlyAudio)
{
HomeAddingQualitySettingControl.Visibility = Visibility.Collapsed;
HomeAddingQualitySettingControlComboBox.SelectedIndex = VideoService.BaseStreams.Count() - 1;
HomeAddingExtensionComboBox.Items.Clear();
foreach (AudioFileExtension extension in Enum.GetValues(typeof(AudioFileExtension)))
{
HomeAddingExtensionComboBox.Items.Add(extension);
}
HomeAddingExtensionComboBox.SelectedIndex = (int)Config.GetValue("default_audio_extension") - 3;
}
else
{
HomeAddingQualitySettingControl.Visibility = Visibility.Visible;
HomeAddingQualitySettingControlComboBox.SelectedIndex = 0;
HomeAddingExtensionComboBox.Items.Clear();
foreach (VideoFileExtension extension in Enum.GetValues(typeof(VideoFileExtension)))
{
HomeAddingExtensionComboBox.Items.Add(extension);
}
HomeAddingExtensionComboBox.SelectedIndex = (int)Config.GetValue("default_video_extension");
}
}
// QUALITY COMBOBOX SELECTION CHANGED
private void HomeAddingQualitySettingControlComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Stream = VideoService.BaseStreams[HomeAddingQualitySettingControlComboBox.SelectedIndex];
}
// TRIM START TEXTBOX LOST FOCUS
private void HomeAddingTrimStartTextBox_LostFocus(object sender, RoutedEventArgs e)
{
if (!HomeAddingTrimStartTextBox.Text.Contains('_'))
{
string[] segments = HomeAddingTrimStartTextBox.Text.Split(':').Reverse().ToArray();
int.TryParse(segments.ElementAtOrDefault(0), out int seconds);
int.TryParse(segments.ElementAtOrDefault(1), out int minutes);
int.TryParse(segments.ElementAtOrDefault(2), out int hours);
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
if (parsedTimeSpan < VideoService.Metadata.Duration && parsedTimeSpan > TimeSpan.Zero) TrimStart = parsedTimeSpan;
else
{
TrimStart = TimeSpan.Zero;
HomeAddingTrimStartTextBox.Text = TimeSpanCustomFormat.ToOptTHMMBaseSS(TrimStart, VideoService.Metadata.Duration);
}
}
}
// TRIM END TEXTBOX LOST FOCUS
private void HomeAddingTrimEndTextBox_LostFocus(object sender, RoutedEventArgs e)
{
if (!HomeAddingTrimEndTextBox.Text.Contains('_'))
{
string[] segments = HomeAddingTrimEndTextBox.Text.Split(':').Reverse().ToArray();
int.TryParse(segments.ElementAtOrDefault(0), out int seconds);
int.TryParse(segments.ElementAtOrDefault(1), out int minutes);
int.TryParse(segments.ElementAtOrDefault(2), out int hours);
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
if (parsedTimeSpan < VideoService.Metadata.Duration && parsedTimeSpan > TimeSpan.Zero) TrimEnd = parsedTimeSpan;
else
{
TrimEnd = VideoService.Metadata.Duration;
HomeAddingTrimEndTextBox.Text = TimeSpanCustomFormat.ToOptTHMMBaseSS(TrimEnd);
}
}
}
// FILENAME TEXTBOX LOST FOCUS
private void HomeAddingFilenameTextBox_LostFocus(object sender, RoutedEventArgs e)
{
foreach (char c in Path.GetInvalidFileNameChars()) HomeAddingFilenameTextBox.Text = HomeAddingFilenameTextBox.Text.Replace(c, ' ');
Filename = HomeAddingFilenameTextBox.Text;
}
// EXTENSION COMBOBOX SELECTION CHANGED
private void HomeAddingExtensionComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Extension = (MediaFileExtension)HomeAddingExtensionComboBox.SelectedIndex + (MediaType == MediaType.OnlyAudio ? 3 : 0);
}
// SCHEDULE NUMBERBOX LOST FOCUS
private void HomeAddingScheduleNumberBox_LostFocus(object sender, RoutedEventArgs e)
{
if (HomeAddingScheduleNumberBox.Value == double.NaN) HomeAddingScheduleNumberBox.Value = 0;
Schedule = HomeAddingScheduleNumberBox.Value;
}
// LOCATION BROWSE BUTTON CLICKED
private async void HomeAddingLocationBrowseButton_Click(object sender, RoutedEventArgs e)
{
// Create location picker
FolderPicker picker = new FolderPicker
{
SuggestedStartLocation = PickerLocationId.Downloads
};
picker.FileTypeFilter.Add("*");
// Select location
StorageFolder selectedFolder = await picker.PickSingleFolderAsync();
if (selectedFolder != null)
{
try
{
await(await selectedFolder.CreateFileAsync("VDownloadLocationAccessTest")).DeleteAsync();
StorageApplicationPermissions.FutureAccessList.AddOrReplace("last_media_location", selectedFolder);
Location = selectedFolder;
HomeAddingLocationSettingControl.Description = Location.Path;
}
catch (UnauthorizedAccessException) { }
}
}
#endregion
}
}

View File

@@ -1,42 +0,0 @@
<UserControl
x:Class="VDownload.Views.Home.HomeOptionsBarPlaylistSearch"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Controls"
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"
d:DesignHeight="100"
d:DesignWidth="400">
<Grid x:Name="HomeOptionsBarPlaylistSearchControlGrid" Margin="10" ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- URL TEXTBOX -->
<TextBox x:Name="HomeOptionsBarPlaylistSearchControlUrlTextBox" x:Uid="HomeOptionsBarPlaylistSearchControlUrlTextBox" Grid.Column="0" VerticalAlignment="Center"/>
<!-- MAX VIDEOS NUMBERBOX -->
<muxc:NumberBox x:Name="HomeOptionsBarPlaylistSearchControlMaxVideosNumberBox" Grid.Column="1" VerticalAlignment="Center" SpinButtonPlacementMode="Compact" Minimum="0" LostFocus="HomeOptionsBarPlaylistSearchControlMaxVideosNumberBox_LostFocus"/>
<!-- SEARCH BUTTON-->
<Button x:Name="HomeOptionsBarPlaylistSearchControlSearchButton" x:Uid="HomeOptionsBarPlaylistSearchControlSearchButton" Grid.Column="2" Click="HomeOptionsBarPlaylistSearchControlSearchButton_Click"/>
<!-- HELP BUTTON -->
<Button x:Name="HomeOptionsBarPlaylistSearchControlHelpButton" Grid.Column="3" Content="?" Click="HomeOptionsBarPlaylistSearchControlHelpButton_Click">
<Button.Resources>
<muxc:TeachingTip x:Name="HomeOptionsBarPlaylistSearchControlInfoBox" x:Uid="HomeOptionsBarPlaylistSearchControlInfoBox" Target="{x:Bind HomeOptionsBarPlaylistSearchControlGrid}"/>
</Button.Resources>
</Button>
<!-- STATUS CONTROL -->
<ContentPresenter x:Name="HomeOptionsBarPlaylistSearchControlStatusControl" Margin="-10,0,0,0" Grid.Column="4"/>
</Grid>
</UserControl>

View File

@@ -1,167 +0,0 @@
using Microsoft.Toolkit.Uwp.Connectivity;
using System;
using System.Diagnostics;
using System.Net;
using System.Threading;
using VDownload.Core.Enums;
using VDownload.Core.EventArgs;
using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Services.Sources;
using Windows.ApplicationModel.Resources;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
namespace VDownload.Views.Home
{
public sealed partial class HomeOptionsBarPlaylistSearch : UserControl
{
#region CONSTANTS
// RESOURCES
private static readonly ResourceDictionary IconRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Icons.xaml") };
// SEARCHING STATUS CONTROLS
private static readonly Microsoft.UI.Xaml.Controls.ProgressRing HomeOptionsBarPlaylistSearchStatusProgressRing = new Microsoft.UI.Xaml.Controls.ProgressRing { Width = 15, Height = 15, Margin = new Thickness(15, 5, 5, 5), IsActive = true };
private static readonly Image HomeOptionsBarPlaylistSearchStatusErrorImage = new Image { Width = 15, Height = 15, Margin = new Thickness(15, 5, 5, 5), Source = (SvgImageSource)IconRes["ErrorIcon"] };
#endregion
#region CONSTRUCTORS
public HomeOptionsBarPlaylistSearch()
{
this.InitializeComponent();
// Set default max videos
HomeOptionsBarPlaylistSearchControlMaxVideosNumberBox.Value = (int)Config.GetValue("default_max_playlist_videos");
}
#endregion
#region PROPERTIES
public CancellationToken CancellationToken { get; set; }
#endregion
#region EVENT HANDLERS VOIDS
// NUMBERBOX FOCUS LOST
private void HomeOptionsBarPlaylistSearchControlMaxVideosNumberBox_LostFocus(object sender, RoutedEventArgs e)
{
if (double.IsNaN(HomeOptionsBarPlaylistSearchControlMaxVideosNumberBox.Value)) HomeOptionsBarPlaylistSearchControlMaxVideosNumberBox.Value = (int)Config.GetValue("default_max_playlist_videos");
}
// SEARCH BUTTON CLICKED
private async void HomeOptionsBarPlaylistSearchControlSearchButton_Click(object sender, RoutedEventArgs e)
{
// Invoke search button click event
SearchButtonClick?.Invoke(this, EventArgs.Empty);
// Close info box
HomeOptionsBarPlaylistSearchControlInfoBox.IsOpen = false;
// Set SearchingStatusControl
HomeOptionsBarPlaylistSearchControlStatusControl.Content = HomeOptionsBarPlaylistSearchStatusProgressRing;
// Parse url
IPlaylist playlistService = Source.GetPlaylist(HomeOptionsBarPlaylistSearchControlUrlTextBox.Text);
if (playlistService == null)
{
HomeOptionsBarPlaylistSearchControlStatusControl.Content = HomeOptionsBarPlaylistSearchStatusErrorImage;
}
else
{
// Get metadata and streams
try
{
await playlistService.GetMetadataAsync(CancellationToken);
await playlistService.GetVideosAsync((int)Math.Round(HomeOptionsBarPlaylistSearchControlMaxVideosNumberBox.Value), CancellationToken);
}
catch (OperationCanceledException)
{
HomeOptionsBarPlaylistSearchControlStatusControl.Content = null;
return;
}
catch (MediaNotFoundException)
{
HomeOptionsBarPlaylistSearchControlStatusControl.Content = HomeOptionsBarPlaylistSearchStatusErrorImage;
return;
}
catch (TwitchAccessTokenNotFoundException)
{
HomeOptionsBarPlaylistSearchControlStatusControl.Content = HomeOptionsBarPlaylistSearchStatusErrorImage;
ContentDialog twitchAccessTokenNotFoundErrorDialog = new ContentDialog
{
Title = ResourceLoader.GetForCurrentView().GetString("HomeOptionsBarPlaylistSearchingErrorDialogTitle"),
Content = ResourceLoader.GetForCurrentView().GetString("HomeOptionsBarPlaylistSearchingTwitchAccessTokenNotFoundErrorDialogDescription"),
CloseButtonText = ResourceLoader.GetForCurrentView().GetString("CloseErrorDialogButtonText"),
};
await twitchAccessTokenNotFoundErrorDialog.ShowAsync();
return;
}
catch (TwitchAccessTokenNotValidException)
{
HomeOptionsBarPlaylistSearchControlStatusControl.Content = HomeOptionsBarPlaylistSearchStatusErrorImage;
ContentDialog twitchAccessTokenNotValidErrorDialog = new ContentDialog
{
Title = ResourceLoader.GetForCurrentView().GetString("HomeOptionsBarPlaylistSearchingErrorDialogTitle"),
Content = ResourceLoader.GetForCurrentView().GetString("HomeOptionsBarPlaylistSearchingTwitchAccessTokenNotValidErrorDialogDescription"),
CloseButtonText = ResourceLoader.GetForCurrentView().GetString("CloseErrorDialogButtonText"),
};
await twitchAccessTokenNotValidErrorDialog.ShowAsync();
return;
}
catch (WebException ex)
{
HomeOptionsBarPlaylistSearchControlStatusControl.Content = HomeOptionsBarPlaylistSearchStatusErrorImage;
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
{
ContentDialog internetAccessErrorDialog = new ContentDialog
{
Title = ResourceLoader.GetForCurrentView().GetString("HomeOptionsBarPlaylistSearchingErrorDialogTitle"),
Content = ResourceLoader.GetForCurrentView().GetString("HomeOptionsBarPlaylistSearchingInternetConnectionErrorDialogDescription"),
CloseButtonText = ResourceLoader.GetForCurrentView().GetString("CloseErrorDialogButtonText"),
};
await internetAccessErrorDialog.ShowAsync();
return;
}
else throw;
}
// Set searching status control to done (null)
HomeOptionsBarPlaylistSearchControlStatusControl.Content = null;
// Invoke search successed event
PlaylistSearchSuccessed?.Invoke(this, new PlaylistSearchSuccessedEventArgs { PlaylistService = playlistService });
}
}
// HELP BUTTON CLICKED
private void HomeOptionsBarPlaylistSearchControlHelpButton_Click(object sender, RoutedEventArgs e)
{
// Switch info box
HomeOptionsBarPlaylistSearchControlInfoBox.IsOpen = !HomeOptionsBarPlaylistSearchControlInfoBox.IsOpen;
}
#endregion
#region EVENT HANDLERS
public event EventHandler<PlaylistSearchSuccessedEventArgs> PlaylistSearchSuccessed;
public event EventHandler SearchButtonClick;
#endregion
}
}

View File

@@ -1,38 +0,0 @@
<UserControl
x:Class="VDownload.Views.Home.HomeOptionsBarVideoSearch"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Controls"
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"
d:DesignHeight="100"
d:DesignWidth="400">
<Grid x:Name="HomeOptionsBarVideoSearchControlGrid" Margin="10" ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- URL TEXTBOX -->
<TextBox x:Name="HomeOptionsBarVideoSearchControlUrlTextBox" x:Uid="HomeOptionsBarVideoSearchControlUrlTextBox" Grid.Column="0" VerticalAlignment="Center"/>
<!-- SEARCH BUTTON -->
<Button x:Name="HomeOptionsBarVideoSearchControlSearchButton" x:Uid="HomeOptionsBarVideoSearchControlSearchButton" Grid.Column="1" Click="HomeOptionsBarVideoSearchControlSearchButton_Click"/>
<!-- INFO BUTTON -->
<Button x:Name="HomeOptionsBarVideoSearchControlHelpButton" x:Uid="HomeOptionsBarVideoSearchControlHelpButton" Grid.Column="2" Content="?" Click="HomeOptionsBarVideoSearchControlHelpButton_Click">
<Button.Resources>
<muxc:TeachingTip x:Name="HomeOptionsBarVideoSearchControlInfoBox" x:Uid="HomeOptionsBarAddVideoControlInfoBox" Target="{x:Bind HomeOptionsBarVideoSearchControlGrid}"/>
</Button.Resources>
</Button>
<!-- STATUS CONTROL -->
<ContentPresenter x:Name="HomeOptionBarVideoSearchControlStatusControl" Margin="-10,0,0,0" Grid.Column="3"/>
</Grid>
</UserControl>

View File

@@ -1,159 +0,0 @@
using System;
using System.Diagnostics;
using System.Net;
using System.Threading;
using VDownload.Core.Enums;
using VDownload.Core.EventArgs;
using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Services.Sources;
using Windows.ApplicationModel.Resources;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
namespace VDownload.Views.Home
{
public sealed partial class HomeOptionsBarVideoSearch : UserControl
{
#region CONSTANTS
// RESOURCES
private static readonly ResourceDictionary IconRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Icons.xaml") };
// SEARCHING STATUS CONTROLS
private static readonly Microsoft.UI.Xaml.Controls.ProgressRing HomeOptionsBarVideoSearchStatusProgressRing = new Microsoft.UI.Xaml.Controls.ProgressRing { Width = 15, Height = 15, Margin = new Thickness(15, 5, 5, 5), IsActive = true };
private static readonly Image HomeOptionsBarVideoSearchStatusErrorImage = new Image { Width = 15, Height = 15, Margin = new Thickness(15, 5, 5, 5), Source = (SvgImageSource)IconRes["ErrorIcon"] };
#endregion
#region CONSTRUCTORS
public HomeOptionsBarVideoSearch()
{
this.InitializeComponent();
}
#endregion
#region PROPERTIES
public CancellationToken CancellationToken { get; set; }
#endregion
#region EVENT HANDLERS VOIDS
// SEARCH BUTTON CLICKED
private async void HomeOptionsBarVideoSearchControlSearchButton_Click(object sender, RoutedEventArgs e)
{
// Invoke search button click event
SearchButtonClick?.Invoke(this, EventArgs.Empty);
// Close info box
HomeOptionsBarVideoSearchControlInfoBox.IsOpen = false;
// Set SearchingStatusControl
HomeOptionBarVideoSearchControlStatusControl.Content = HomeOptionsBarVideoSearchStatusProgressRing;
// Parse url
IVideo videoService = Source.GetVideo(HomeOptionsBarVideoSearchControlUrlTextBox.Text);
// Check url
if (videoService == null)
{
HomeOptionBarVideoSearchControlStatusControl.Content = HomeOptionsBarVideoSearchStatusErrorImage;
}
else
{
// Get metadata and streams
try
{
await videoService.GetMetadataAsync(CancellationToken);
await videoService.GetStreamsAsync(CancellationToken);
}
catch (OperationCanceledException)
{
HomeOptionBarVideoSearchControlStatusControl.Content = null;
return;
}
catch (MediaNotFoundException)
{
HomeOptionBarVideoSearchControlStatusControl.Content = HomeOptionsBarVideoSearchStatusErrorImage;
return;
}
catch (TwitchAccessTokenNotFoundException)
{
HomeOptionBarVideoSearchControlStatusControl.Content = HomeOptionsBarVideoSearchStatusErrorImage;
ContentDialog twitchAccessTokenNotFoundErrorDialog = new ContentDialog
{
Title = ResourceLoader.GetForCurrentView().GetString("HomeOptionsBarVideoSearchingErrorDialogTitle"),
Content = ResourceLoader.GetForCurrentView().GetString("HomeOptionsBarVideoSearchingTwitchAccessTokenNotFoundErrorDialogDescription"),
CloseButtonText = ResourceLoader.GetForCurrentView().GetString("CloseErrorDialogButtonText"),
};
await twitchAccessTokenNotFoundErrorDialog.ShowAsync();
return;
}
catch (TwitchAccessTokenNotValidException)
{
HomeOptionBarVideoSearchControlStatusControl.Content = HomeOptionsBarVideoSearchStatusErrorImage;
ContentDialog twitchAccessTokenNotValidErrorDialog = new ContentDialog
{
Title = ResourceLoader.GetForCurrentView().GetString("HomeOptionsBarVideoSearchingErrorDialogTitle"),
Content = ResourceLoader.GetForCurrentView().GetString("HomeOptionsBarVideoSearchingTwitchAccessTokenNotValidErrorDialogDescription"),
CloseButtonText = ResourceLoader.GetForCurrentView().GetString("CloseErrorDialogButtonText"),
};
await twitchAccessTokenNotValidErrorDialog.ShowAsync();
return;
}
catch (WebException wex)
{
HomeOptionBarVideoSearchControlStatusControl.Content = HomeOptionsBarVideoSearchStatusErrorImage;
if (wex.Response is null)
{
ContentDialog internetAccessErrorDialog = new ContentDialog
{
Title = ResourceLoader.GetForCurrentView().GetString("HomeOptionsBarVideoSearchingErrorDialogTitle"),
Content = ResourceLoader.GetForCurrentView().GetString("HomeOptionsBarVideoSearchingInternetConnectionErrorDialogDescription"),
CloseButtonText = ResourceLoader.GetForCurrentView().GetString("CloseErrorDialogButtonText"),
};
await internetAccessErrorDialog.ShowAsync();
return;
}
else throw;
}
// Set searching status control to done (null)
HomeOptionBarVideoSearchControlStatusControl.Content = null;
// Invoke search successed event
VideoSearchSuccessed?.Invoke(this, new VideoSearchSuccessedEventArgs { Video = videoService });
}
}
// HELP BUTTON CLICKED
private void HomeOptionsBarVideoSearchControlHelpButton_Click(object sender, RoutedEventArgs e)
{
// Switch info box
HomeOptionsBarVideoSearchControlInfoBox.IsOpen = !HomeOptionsBarVideoSearchControlInfoBox.IsOpen;
}
#endregion
#region EVENT HANDLERS
public event EventHandler<VideoSearchSuccessedEventArgs> VideoSearchSuccessed;
public event EventHandler SearchButtonClick;
#endregion
}
}

View File

@@ -1,103 +0,0 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
namespace VDownload.Views.Home.Controls
{
public sealed partial class HomeSerialAddingVideoPanel : UserControl
{
#region CONSTANTS
private readonly ResourceDictionary ImagesRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Images.xaml") };
#endregion
#region CONSTRUCTORS
public HomeSerialAddingVideoPanel(IVideo videoService)
{
this.InitializeComponent();
// Set video service object
VideoService = videoService;
// Set metadata
ThumbnailImage = VideoService.Metadata.Thumbnail != null ? new BitmapImage { UriSource = VideoService.Metadata.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"];
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{VideoService.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
Title = VideoService.Metadata.Title;
Author = VideoService.Metadata.Author;
Views = VideoService.Metadata.Views.ToString();
Date = VideoService.Metadata.Date.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortDatePattern);
Duration = TimeSpanCustomFormat.ToOptTHBaseMMSS(VideoService.Metadata.Duration);
// Set video options control
HomeVideoAddingOptionsControl = new HomeAddingVideoOptions(VideoService);
}
#endregion
#region PROPERTIES
// BASE VIDEO DATA
public IVideo VideoService { get; set; }
// VIDEO DATA
private ImageSource ThumbnailImage { get; set; }
private IconElement SourceImage { get; set; }
private string Title { get; set; }
private string Author { get; set; }
private string Views { get; set; }
private string Date { get; set; }
private string Duration { get; set; }
// OPTIONS CONTROL
public HomeAddingVideoOptions HomeVideoAddingOptionsControl { get; private set; }
#endregion
#region EVENT HANDLERS VOIDS
// ON CONTROL LOADING
private async void HomeSerialAddingVideoPanel_Loading(FrameworkElement sender, object args)
{
await HomeVideoAddingOptionsControl.Init();
HomeSerialAddingVideoExpander.Content = HomeVideoAddingOptionsControl;
}
// SOURCE BUTTON CLICKED
private async void HomeSerialAddingVideoPanelSourceButton_Click(object sender, RoutedEventArgs e)
{
// Launch the website
await Windows.System.Launcher.LaunchUriAsync(VideoService.Url);
}
// DELETE BUTTON CLICKED
private void HomeSerialAddingVideoPanelDeleteButton_Click(object sender, RoutedEventArgs e)
{
DeleteRequested?.Invoke(this, EventArgs.Empty);
}
#endregion
#region EVENT HANDLERS
public event EventHandler DeleteRequested;
#endregion
}
}

View File

@@ -1,337 +0,0 @@
using Microsoft.Toolkit.Uwp.Connectivity;
using Microsoft.Toolkit.Uwp.Notifications;
using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.Services;
using VDownload.Core.Structs;
using Windows.ApplicationModel.ExtendedExecution;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
namespace VDownload.Views.Home.Controls
{
public sealed partial class HomeTaskPanel : UserControl
{
#region CONSTANTS
private static readonly ResourceDictionary IconsRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Icons.xaml") };
private static readonly ResourceDictionary ImagesRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Images.xaml") };
#endregion
#region CONSTRUCTORS
public HomeTaskPanel(TaskData taskData)
{
this.InitializeComponent();
// Set task data
Data = taskData;
// Set video status
Status = Core.Enums.TaskStatus.Idle;
// Create video cancellation token
CancellationTokenSource = new CancellationTokenSource();
// Set thumbnail image
ThumbnailImage = Data.VideoService.Metadata.Thumbnail != null ? new BitmapImage { UriSource = Data.VideoService.Metadata.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"];
// Set source icon
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{Data.VideoService.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
// Set duration
TimeSpan newDuration = Data.TaskOptions.TrimEnd.Subtract(Data.TaskOptions.TrimStart);
Duration = TimeSpanCustomFormat.ToOptTHBaseMMSS(newDuration);
if (Data.VideoService.Metadata.Duration > newDuration) Duration += $" ({TimeSpanCustomFormat.ToOptTHBaseMMSS(Data.TaskOptions.TrimStart, Data.TaskOptions.TrimEnd)} - {TimeSpanCustomFormat.ToOptTHBaseMMSS(Data.TaskOptions.TrimEnd, Data.TaskOptions.TrimStart)})";
// Set media type
MediaTypeQuality += ResourceLoader.GetForCurrentView().GetString($"MediaType{Data.TaskOptions.MediaType}Text");
if (Data.TaskOptions.MediaType != MediaType.OnlyAudio) MediaTypeQuality += $" ({Data.TaskOptions.Stream.Height}p{(Data.TaskOptions.Stream.FrameRate > 0 ? Data.TaskOptions.Stream.FrameRate.ToString() : "N/A")})";
// Set file
File += $@"{(Data.TaskOptions.Location != null ? Data.TaskOptions.Location.Path : $@"{UserDataPaths.GetDefault().Downloads}\VDownload")}\{Data.TaskOptions.Filename}.{Data.TaskOptions.Extension.ToString().ToLower()}";
// Set state controls
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateIdleIcon"];
HomeTaskPanelStateText.Text = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextIdle");
HomeTaskPanelStateProgressBar.Visibility = Visibility.Collapsed;
}
#endregion
#region PROPERTIES
// TASK STATUS
public Core.Enums.TaskStatus Status { get; set; }
// TASK CANCELLATION TOKEN
public CancellationTokenSource CancellationTokenSource { get; set; }
// TASK DATA
private TaskData Data { get; set; }
// TASK PANEL DATA
private ImageSource ThumbnailImage { get; set; }
private IconElement SourceImage { get; set; }
private string Title { get; set; }
private string Author { get; set; }
private string Duration { get; set; }
private string MediaTypeQuality { get; set; }
private string File { get; set; }
#endregion
#region METHODS
public async Task Start(bool delayWhenOnMeteredConnection)
{
// Change icon
HomeTaskPanelStartStopButton.Icon = new SymbolIcon(Symbol.Stop);
// Create cancellation token
CancellationTokenSource = new CancellationTokenSource();
// Scheduling
if (Data.TaskOptions.Schedule > 0)
{
DateTime ScheduledDateTime = DateTime.Now.AddMinutes(Data.TaskOptions.Schedule);
// Set task status
Status = Core.Enums.TaskStatus.Scheduled;
// Set state controls
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateScheduledIcon"];
HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextScheduled")} ({ScheduledDateTime.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortDatePattern)} {ScheduledDateTime.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortTimePattern)})";
HomeTaskPanelStateProgressBar.Visibility = Visibility.Collapsed;
while (DateTime.Now < ScheduledDateTime && !CancellationTokenSource.IsCancellationRequested) await Task.Delay(100);
}
// Set task status
Status = Core.Enums.TaskStatus.Waiting;
// Set state controls
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateWaitingIcon"];
HomeTaskPanelStateText.Text = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextWaiting");
HomeTaskPanelStateProgressBar.Visibility = Visibility.Visible;
// Wait in queue
await HomeMain.WaitInQueue(delayWhenOnMeteredConnection, CancellationTokenSource.Token);
if (!CancellationTokenSource.IsCancellationRequested)
{
// Set task status
Status = Core.Enums.TaskStatus.InProgress;
// Get task unique ID
string uniqueID = TaskId.Get();
// Get temporary folder
StorageFolder tempFolder;
if ((bool)Config.GetValue("custom_temp_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("custom_temp_location"))
tempFolder = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("custom_temp_location");
else
tempFolder = ApplicationData.Current.TemporaryFolder;
tempFolder = await tempFolder.CreateFolderAsync(uniqueID);
bool endedWithError = false;
try
{
// Throw exception if cancellation requested
CancellationTokenSource.Token.ThrowIfCancellationRequested();
// Start stopwatch
Stopwatch taskStopwatch = Stopwatch.StartNew();
// Set progress event handlers
Data.VideoService.DownloadingProgressChanged += (s, a) =>
{
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateDownloadingIcon"];
HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextDownloading")} ({Math.Round(a.Progress)}%)";
HomeTaskPanelStateProgressBar.Value = a.Progress;
};
Data.VideoService.ProcessingProgressChanged += (s, a) =>
{
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateProcessingIcon"];
HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextProcessing")} ({Math.Round(a.Progress)}%)";
HomeTaskPanelStateProgressBar.Value = a.Progress;
};
// Request extended session
ExtendedExecutionSession session = new ExtendedExecutionSession { Reason = ExtendedExecutionReason.Unspecified };
await session.RequestExtensionAsync();
// Start task
CancellationTokenSource.Token.ThrowIfCancellationRequested();
StorageFile tempOutputFile = await Data.VideoService.DownloadAndTranscodeAsync(tempFolder, Data.TaskOptions.Stream, Data.TaskOptions.Extension, Data.TaskOptions.MediaType, Data.TaskOptions.TrimStart, Data.TaskOptions.TrimEnd, CancellationTokenSource.Token);
// Dispose session
session.Dispose();
// Cancel if requested
CancellationTokenSource.Token.ThrowIfCancellationRequested();
// Set state controls
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateFinalizingIcon"];
HomeTaskPanelStateText.Text = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextFinalizing");
HomeTaskPanelStateProgressBar.IsIndeterminate = true;
// Move to output location
string filename = $"{Data.TaskOptions.Filename}.{Data.TaskOptions.Extension.ToString().ToLower()}";
CreationCollisionOption collisionOption = (bool)Config.GetValue("replace_output_file_if_exists") ? CreationCollisionOption.ReplaceExisting : CreationCollisionOption.GenerateUniqueName;
StorageFile outputFile = await (Data.TaskOptions.Location != null ? Data.TaskOptions.Location.CreateFileAsync(filename, collisionOption): DownloadsFolder.CreateFileAsync(filename, collisionOption));
await tempOutputFile.MoveAndReplaceAsync(outputFile);
// Stop stopwatch
taskStopwatch.Stop();
// Set state controls
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateDoneIcon"];
HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextDone")} ({(Math.Floor(taskStopwatch.Elapsed.TotalHours) > 0 ? $"{ Math.Floor(taskStopwatch.Elapsed.TotalHours):0}:" : "")}{taskStopwatch.Elapsed.Minutes:00}:{taskStopwatch.Elapsed.Seconds:00})";
HomeTaskPanelStateProgressBar.Visibility = Visibility.Collapsed;
// Show notification
if ((bool)Config.GetValue("show_notification_when_task_ended_successfully"))
new ToastContentBuilder()
.AddText(ResourceLoader.GetForCurrentView().GetString("NotificationTaskEndedSuccessfullyHeader"))
.AddText($"\"{Title}\" - {ResourceLoader.GetForCurrentView().GetString("NotificationTaskEndedSuccessfullyDescription")}")
.Show();
}
catch (OperationCanceledException)
{
// Set state controls
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateCancelledIcon"];
HomeTaskPanelStateText.Text = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextCancelled");
HomeTaskPanelStateProgressBar.Visibility = Visibility.Collapsed;
}
catch (WebException ex)
{
string errorInfo;
if (ex.Response is null) errorInfo = ResourceLoader.GetForCurrentView().GetString("TaskErrorInternetConnection");
else throw ex;
// Set state controls
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateErrorIcon"];
HomeTaskPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextError")} ({errorInfo})";
HomeTaskPanelStateProgressBar.Visibility = Visibility.Collapsed;
// Show notification
if ((bool)Config.GetValue("show_notification_when_task_ended_unsuccessfully"))
new ToastContentBuilder()
.AddText(ResourceLoader.GetForCurrentView().GetString("NotificationTaskEndedUnsuccessfullyHeader"))
.AddText($"\"{Title}\" - {ResourceLoader.GetForCurrentView().GetString("NotificationTaskEndedUnsuccessfullyDescription")} ({errorInfo})")
.Show();
}
finally
{
// Set video status
Status = Core.Enums.TaskStatus.Idle;
// Change icon
HomeTaskPanelStartStopButton.Icon = new SymbolIcon(Symbol.Download);
if (!endedWithError || (bool)Config.GetValue("delete_task_temp_when_ended_with_error"))
{
// Delete temporary files
await tempFolder.DeleteAsync();
// Dispose unique id
TaskId.Dispose(uniqueID);
}
if (!endedWithError && !CancellationTokenSource.IsCancellationRequested && (bool)Config.GetValue("remove_task_when_successfully_ended"))
{
// Remove task when successfully ended
TaskRemovingRequested?.Invoke(this, EventArgs.Empty);
}
}
}
else
{
// Set state controls
HomeTaskPanelStateIcon.Source = (SvgImageSource)IconsRes["StateCancelledIcon"];
HomeTaskPanelStateText.Text = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelStateTextCancelled");
HomeTaskPanelStateProgressBar.Visibility = Visibility.Collapsed;
// Change icon
HomeTaskPanelStartStopButton.Icon = new SymbolIcon(Symbol.Download);
}
}
#endregion
#region EVENT HANDLERS VOIDS
// SOURCE BUTTON CLICKED
private async void HomeTaskPanelSourceButton_Click(object sender, RoutedEventArgs e)
{
// Launch the website
await Windows.System.Launcher.LaunchUriAsync(Data.VideoService.Url);
}
// START STOP BUTTON CLICKED
private async void HomeTaskPanelStartStopButton_Click(object sender, RoutedEventArgs e)
{
if (Status == Core.Enums.TaskStatus.InProgress || Status == Core.Enums.TaskStatus.Waiting || Status == Core.Enums.TaskStatus.Scheduled) CancellationTokenSource.Cancel();
else
{
bool delay = (bool)Config.GetValue("delay_task_when_queued_task_starts_on_metered_network");
if (NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)
{
ContentDialogResult dialogResult = await new ContentDialog
{
Title = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelTaskStartMeteredConnectionDialogTitle"),
Content = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelTaskStartMeteredConnectionDialogDescription"),
PrimaryButtonText = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelTaskStartMeteredConnectionDialogStartAndDelayText"),
SecondaryButtonText = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelTaskStartMeteredConnectionDialogStartWithoutDelayText"),
CloseButtonText = ResourceLoader.GetForCurrentView().GetString("HomeTaskPanelTaskStartMeteredConnectionDialogCancel"),
}.ShowAsync();
switch (dialogResult)
{
case ContentDialogResult.Primary: delay = true; break;
case ContentDialogResult.Secondary: delay = false; break;
case ContentDialogResult.None: return;
}
}
await Start(delay);
}
}
// REMOVE BUTTON CLICKED
private void HomeTaskPanelRemoveButton_Click(object sender, RoutedEventArgs e)
{
if (Status == Core.Enums.TaskStatus.InProgress || Status == Core.Enums.TaskStatus.Waiting || Status == Core.Enums.TaskStatus.Scheduled) CancellationTokenSource.Cancel();
TaskRemovingRequested?.Invoke(this, EventArgs.Empty);
}
#endregion
#region EVENT HANDLERS
public event EventHandler TaskRemovingRequested;
#endregion
}
}

View File

@@ -1,33 +0,0 @@
<UserControl
x:Class="VDownload.Views.Home.Controls.HomeTasksListPlaceholder"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.Home"
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">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///Resources/Images.xaml"/>
<ResourceDictionary Source="ms-appx:///Resources/Colors.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Viewbox Width="250">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Source="{StaticResource HomeTasksListPlaceholderImage}" Width="120"/>
<TextBlock Grid.Row="1" x:Uid="HomeTasksListPlaceholderTextBlock" Foreground="{StaticResource HomeTasksListPlaceholderTextColor}"/>
</Grid>
</Viewbox>
</UserControl>

View File

@@ -1,12 +0,0 @@
using Windows.UI.Xaml.Controls;
namespace VDownload.Views.Home.Controls
{
public sealed partial class HomeTasksListPlaceholder : UserControl
{
public HomeTasksListPlaceholder()
{
this.InitializeComponent();
}
}
}

View File

@@ -0,0 +1,32 @@
<UserControl
x:Class="VDownload.Views.Home.Controls.PlaylistSearchControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Controls"
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"
d:DesignHeight="100"
d:DesignWidth="400">
<Grid x:Name="Grid" Margin="10" ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="UrlTextBox" x:Uid="Home_OptionsBar_PlaylistSearchControl_UrlTextBox" Grid.Column="0" VerticalAlignment="Center"/>
<muxc:NumberBox x:Name="MaxVideosNumberBox" Grid.Column="1" VerticalAlignment="Center" SpinButtonPlacementMode="Compact" Minimum="0" LostFocus="MaxVideosNumberBox_LostFocus"/>
<Button x:Name="SearchButton" x:Uid="Home_OptionsBar_PlaylistSearchControl_SearchButton" Grid.Column="2" Click="SearchButton_Click"/>
<Button x:Name="HelpButton" Grid.Column="3" Content="?" Click="HelpButton_Click">
<Button.Resources>
<muxc:TeachingTip x:Name="InfoBox" x:Uid="Home_OptionsBar_PlaylistSearchControl_InfoBox" Target="{x:Bind Grid}"/>
</Button.Resources>
</Button>
<ContentPresenter x:Name="StatusControlPresenter" Margin="-10,0,0,0" Grid.Column="4"/>
</Grid>
</UserControl>

View File

@@ -0,0 +1,147 @@
using Microsoft.Toolkit.Uwp.Connectivity;
using System;
using System.Diagnostics;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.EventArgs;
using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Services.Sources;
using Windows.ApplicationModel.Resources;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
namespace VDownload.Views.Home.Controls
{
public sealed partial class PlaylistSearchControl : UserControl
{
#region CONSTANTS
private static readonly ResourceDictionary IconRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Icons.xaml") };
private static readonly Microsoft.UI.Xaml.Controls.ProgressRing StatusControlProgressRing = new Microsoft.UI.Xaml.Controls.ProgressRing { Width = 15, Height = 15, Margin = new Thickness(15, 5, 5, 5), IsActive = true };
private static readonly Image StatusControlErrorImage = new Image { Width = 15, Height = 15, Margin = new Thickness(15, 5, 5, 5), Source = (SvgImageSource)IconRes["ErrorIcon"] };
#endregion
#region CONSTRUCTORS
public PlaylistSearchControl()
{
this.InitializeComponent();
// Set default max videos
MaxVideosNumberBox.Value = (int)Config.GetValue("default_max_playlist_videos");
}
#endregion
#region PROPERTIES
public CancellationToken CancellationToken { get; set; }
#endregion
#region EVENT HANDLERS
private void MaxVideosNumberBox_LostFocus(object sender, RoutedEventArgs e)
{
if (double.IsNaN(MaxVideosNumberBox.Value)) MaxVideosNumberBox.Value = (int)Config.GetValue("default_max_playlist_videos");
}
private async void SearchButton_Click(object sender, RoutedEventArgs e)
{
async Task ShowDialog(string localErrorKey)
{
ContentDialog errorDialog = new ContentDialog
{
Title = ResourceLoader.GetForCurrentView("DialogResources").GetString("Home_OptionsBar_PlaylistSearchControl_Base_Title"),
Content = ResourceLoader.GetForCurrentView("DialogResources").GetString($"Home_OptionsBar_PlaylistSearchControl_{localErrorKey}_Content"),
CloseButtonText = ResourceLoader.GetForCurrentView("DialogResources").GetString("Base_CloseButtonText"),
};
await errorDialog.ShowAsync();
StatusControlPresenter.Content = StatusControlErrorImage;
}
SearchButtonClicked?.Invoke(this, EventArgs.Empty);
InfoBox.IsOpen = false;
StatusControlPresenter.Content = StatusControlProgressRing;
MaxVideosNumberBox_LostFocus(sender, e);
IPlaylist playlist = Source.GetPlaylist(UrlTextBox.Text);
if (playlist == null)
{
StatusControlPresenter.Content = StatusControlErrorImage;
}
else
{
try
{
await playlist.GetMetadataAsync(CancellationToken);
await playlist.GetVideosAsync((int)Math.Round(MaxVideosNumberBox.Value), CancellationToken);
}
catch (OperationCanceledException)
{
StatusControlPresenter.Content = null;
return;
}
catch (MediaNotFoundException)
{
StatusControlPresenter.Content = StatusControlErrorImage;
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;
}
StatusControlPresenter.Content = null;
SearchingSuccessed?.Invoke(this, new PlaylistSearchSuccessedEventArgs(playlist));
}
}
private void HelpButton_Click(object sender, RoutedEventArgs e)
{
InfoBox.IsOpen = !InfoBox.IsOpen;
}
#endregion
#region EVENT HANDLERS
public event EventHandler<PlaylistSearchSuccessedEventArgs> SearchingSuccessed;
public event EventHandler SearchButtonClicked;
#endregion
}
}

View File

@@ -1,51 +1,36 @@
<UserControl
x:Class="VDownload.Views.Home.HomePlaylistAddingPanel"
x:Class="VDownload.Views.Home.Controls.SerialVideoAddingControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.Home"
xmlns:local="using:VDownload.Views.Home.Controls"
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:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:cc="using:VDownload.Controls"
Loading="HomePlaylistAddingPanel_Loading"
mc:Ignorable="d">
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
Loading="SerialVideoAddingControl_Loading">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///Resources/Colors.xaml"/>
<ResourceDictionary Source="ms-appx:///Resources/Icons.xaml"/>
<ResourceDictionary Source="ms-appx:///Resources/Converters.xaml"/>
<ResourceDictionary Source="ms-appx:///Resources/Colors.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid Padding="10" RowSpacing="10">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- METADATA PANEL -->
<Grid VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Margin="12" VerticalAlignment="Center" FontSize="18" FontWeight="SemiBold" Text="{x:Bind Name}"/>
<AppBarButton Grid.Column="1" Width="40" Height="48" Icon="{x:Bind SourceImage}" Click="HomePlaylistAddingPanelSourceButton_Click"/>
<AppBarButton Grid.Column="2" Width="40" Height="48" Icon="Add" Click="HomePlaylistAddingPanelAddButton_Click"/>
</Grid>
<ScrollViewer Grid.Row="1" Margin="0,0,0,60">
<StackPanel x:Name="HomePlaylistAddingPanelVideosList" Spacing="10"/>
<ScrollViewer Grid.Row="0" Margin="0,0,0,60">
<StackPanel x:Name="VideosStackPanel" x:FieldModifier="public" Spacing="10"/>
</ScrollViewer>
<Grid Grid.Row="1" ColumnSpacing="10">
<Grid Grid.Row="0" ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
@@ -54,24 +39,24 @@
<muxc:Expander.Header>
<StackPanel Margin="-5,0,-10,0" Spacing="10" Orientation="Horizontal">
<Image Height="18" Source="{ThemeResource ApplyToAllOptionsIcon}"/>
<TextBlock x:Uid="HomePlaylistAddingPanelApplyToAllOptionsHeaderTextBlock"/>
<TextBlock x:Uid="Home_Adding_Base_SerialVideoAddingControl_ApplyToAllOptions_HeaderTextBlock"/>
</StackPanel>
</muxc:Expander.Header>
<muxc:Expander.Content>
<StackPanel Spacing="10">
<cc:SettingControl x:Name="HomePlaylistAddingApplyToAllLocationSettingControl" x:Uid="HomePlaylistAddingApplyToAllLocationSettingControl" Icon="{ThemeResource LocationIcon}">
<cc:SettingControl x:Name="ApplyToAllLocationSettingControl" x:Uid="Home_Adding_Base_SerialVideoAddingControl_ApplyToAllOptions_LocationSettingControl" Icon="{ThemeResource LocationIcon}">
<cc:SettingControl.SettingContent>
<StackPanel Spacing="10" Orientation="Horizontal">
<Button x:Uid="HomePlaylistAddingApplyToAllLocationBrowseButton" Click="HomePlaylistAddingApplyToAllLocationBrowseButton_Click"/>
<Button x:Uid="HomePlaylistAddingApplyToAllApplyButton" Click="HomePlaylistAddingApplyToAllApplyLocationButton_Click"/>
<Button x:Uid="Home_Adding_Base_SerialVideoAddingControl_ApplyToAllOptions_LocationSettingControl_BrowseButton" Click="ApplyToAllLocationBrowseButton_Click"/>
<Button x:Uid="Home_Adding_Base_SerialVideoAddingControl_ApplyToAllOptions_Base_ApplyButton" Click="ApplyToAllApplyLocationButton_Click"/>
</StackPanel>
</cc:SettingControl.SettingContent>
</cc:SettingControl>
<cc:SettingControl x:Uid="HomePlaylistAddingApplyToAllScheduleSettingControl" Icon="{ThemeResource ScheduleIcon}">
<cc:SettingControl x:Uid="Home_Adding_Base_SerialVideoAddingControl_ApplyToAllOptions_ScheduleSettingControl" Icon="{ThemeResource ScheduleIcon}">
<cc:SettingControl.SettingContent>
<StackPanel Spacing="10" Orientation="Horizontal">
<muxc:NumberBox x:Name="HomePlaylistAddingApplyToAllScheduleNumberBox" MaxWidth="100" Value="0" Minimum="0" SpinButtonPlacementMode="Compact" LostFocus="HomePlaylistAddingApplyToAllScheduleNumberBox_LostFocus"/>
<Button x:Uid="HomePlaylistAddingApplyToAllApplyButton" Click="HomePlaylistAddingApplyToAllApplyScheduleButton_Click"/>
<muxc:NumberBox x:Name="ApplyToAllScheduleNumberBox" MaxWidth="100" Value="0" Minimum="0" SpinButtonPlacementMode="Compact" LostFocus="ApplyToAllScheduleNumberBox_LostFocus"/>
<Button x:Uid="Home_Adding_Base_SerialVideoAddingControl_ApplyToAllOptions_Base_ApplyButton" Click="ApplyToAllApplyScheduleButton_Click"/>
</StackPanel>
</cc:SettingControl.SettingContent>
</cc:SettingControl>
@@ -87,8 +72,8 @@
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Height="18" Source="{ThemeResource FilterIcon}"/>
<StackPanel Grid.Column="1" Margin="0,0,20,0" Spacing="10" Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock x:Uid="HomePlaylistAddingPanelFilterHeaderTextBlock"/>
<TextBlock x:Name="HomePlaylistAddingPanelFilterHeaderCountTextBlock" FontWeight="Light"/>
<TextBlock x:Uid="Home_Adding_Base_SerialVideoAddingControl_Filter_Header_TextBlock"/>
<TextBlock x:Name="FilterHeaderCountTextBlock" FontWeight="Light"/>
</StackPanel>
</Grid>
</muxc:Expander.Header>
@@ -106,51 +91,51 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" x:Uid="HomePlaylistAddingPanelFilterTitleTextBlock" FontWeight="SemiBold"/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="HomePlaylistAddingPanelFilterTitleTextBox" LostFocus="HomePlaylistAddingPanelFilterTitleAndAuthorTextBox_LostFocus"/>
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" x:Uid="HomePlaylistAddingPanelFilterAuthorTextBlock" FontWeight="SemiBold"/>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="HomePlaylistAddingPanelFilterAuthorTextBox" LostFocus="HomePlaylistAddingPanelFilterTitleAndAuthorTextBox_LostFocus"/>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" x:Uid="HomePlaylistAddingPanelFilterViewsTextBlock" FontWeight="SemiBold"/>
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" x:Uid="Home_Adding_Base_SerialVideoAddingControl_Filter_TitleTextBlock" FontWeight="SemiBold"/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="FilterTitleTextBox" x:Uid="Home_Adding_Base_SerialVideoAddingControl_Filter_TitleTextBox" LostFocus="FilterTitleAndAuthorTextBox_LostFocus"/>
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" x:Uid="Home_Adding_Base_SerialVideoAddingControl_Filter_AuthorTextBlock" FontWeight="SemiBold"/>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="FilterAuthorTextBox" x:Uid="Home_Adding_Base_SerialVideoAddingControl_Filter_AuthorTextBox" LostFocus="FilterTitleAndAuthorTextBox_LostFocus"/>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" x:Uid="Home_Adding_Base_SerialVideoAddingControl_Filter_ViewsTextBlock" FontWeight="SemiBold"/>
<Grid Grid.Row="2" Grid.Column="1" ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="5"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<muxc:NumberBox Grid.Column="0" x:Name="HomePlaylistAddingPanelFilterMinViewsNumberBox" SpinButtonPlacementMode="Compact" LostFocus="HomePlaylistAddingPanelFilterMinViewsNumberBox_LostFocus"/>
<muxc:NumberBox Grid.Column="0" x:Name="FilterMinViewsNumberBox" SpinButtonPlacementMode="Compact" LostFocus="FilterMinViewsNumberBox_LostFocus"/>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Text="-"/>
<muxc:NumberBox Grid.Column="2" x:Name="HomePlaylistAddingPanelFilterMaxViewsNumberBox" SpinButtonPlacementMode="Compact" LostFocus="HomePlaylistAddingPanelFilterMaxViewsNumberBox_LostFocus"/>
<muxc:NumberBox Grid.Column="2" x:Name="FilterMaxViewsNumberBox" SpinButtonPlacementMode="Compact" LostFocus="FilterMaxViewsNumberBox_LostFocus"/>
</Grid>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" x:Uid="HomePlaylistAddingPanelFilterDateTextBlock" FontWeight="SemiBold"/>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" x:Uid="Home_Adding_Base_SerialVideoAddingControl_Filter_DateTextBlock" FontWeight="SemiBold"/>
<Grid Grid.Row="3" Grid.Column="1" ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="5"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<CalendarDatePicker x:Name="HomePlaylistAddingPanelFilterMinDateDatePicker" Grid.Column="0" LostFocus="HomePlaylistAddingPanelFilterMinAndMaxDateDatePicker_LostFocus"/>
<CalendarDatePicker x:Name="FilterMinDateDatePicker" Grid.Column="0" LostFocus="FilterMinAndMaxDateDatePicker_LostFocus"/>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Text="-"/>
<CalendarDatePicker x:Name="HomePlaylistAddingPanelFilterMaxDateDatePicker" Grid.Column="2" LostFocus="HomePlaylistAddingPanelFilterMinAndMaxDateDatePicker_LostFocus"/>
<CalendarDatePicker x:Name="FilterMaxDateDatePicker" Grid.Column="2" LostFocus="FilterMinAndMaxDateDatePicker_LostFocus"/>
</Grid>
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" x:Uid="HomePlaylistAddingPanelFilterDurationTextBlock" FontWeight="SemiBold"/>
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" x:Uid="Home_Adding_Base_SerialVideoAddingControl_Filter_DurationTextBlock" FontWeight="SemiBold"/>
<Grid Grid.Row="4" Grid.Column="1" ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="5"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" x:Name="HomePlaylistAddingPanelFilterMinDurationTextBox" LostFocus="HomePlaylistAddingPanelFilterMinDurationTextBox_LostFocus"/>
<TextBox Grid.Column="0" x:Name="FilterMinDurationTextBox" LostFocus="FilterMinDurationTextBox_LostFocus"/>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Text="-"/>
<TextBox Grid.Column="2" x:Name="HomePlaylistAddingPanelFilterMaxDurationTextBox" LostFocus="HomePlaylistAddingPanelFilterMaxDurationTextBox_LostFocus"/>
<TextBox Grid.Column="2" x:Name="FilterMaxDurationTextBox" LostFocus="FilterMaxDurationTextBox_LostFocus"/>
</Grid>
<TextBlock x:Name="HomePlaylistAddingPanelFilterRemovedTextBlock" Grid.Row="5" Grid.Column="0" VerticalAlignment="Center" x:Uid="HomePlaylistAddingPanelFilterRemovedTextBlock" FontWeight="SemiBold" Visibility="Collapsed"/>
<TextBlock x:Name="FilterRemovedTextBlock" Grid.Row="5" Grid.Column="0" VerticalAlignment="Center" x:Uid="Home_Adding_Base_SerialVideoAddingControl_Filter_Removed_TextBlock" FontWeight="SemiBold" Visibility="Collapsed"/>
<Grid Grid.Row="5" Grid.Column="1" ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" x:Name="HomePlaylistAddingPanelFilterRemovedCountTextBlock" VerticalAlignment="Center" Visibility="Collapsed"/>
<Button Grid.Column="1" x:Uid="HomePlaylistAddingPanelFilterRemovedRestoreButton" x:Name="HomePlaylistAddingPanelFilterRemovedRestoreButton" Visibility="Collapsed" Click="HomePlaylistAddingPanelFilterRemovedRestoreButton_Click"/>
<TextBlock Grid.Column="0" x:Name="FilterRemovedCountTextBlock" VerticalAlignment="Center" Visibility="Collapsed"/>
<Button Grid.Column="1" x:Name="FilterRemovedRestoreButton" x:Uid="Home_Adding_Base_SerialVideoAddingControl_Filter_Removed_RestoreButton" Visibility="Collapsed" Click="FilterRemovedRestoreButton_Click"/>
</Grid>
</Grid>
</muxc:Expander.Content>

View File

@@ -0,0 +1,346 @@
using Microsoft.Toolkit.Uwp.UI;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text.RegularExpressions;
using VDownload.Converters;
using VDownload.Core.Extensions;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using Windows.ApplicationModel.Resources;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
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.Home.Controls
{
public sealed partial class SerialVideoAddingControl : UserControl
{
#region CONSTRUCTORS
public SerialVideoAddingControl(IVideo[] videos)
{
this.InitializeComponent();
Videos = videos;
MinViews = Videos.Min(video => video.Views);
MaxViews = Videos.Max(video => video.Views);
MinDate = Videos.Min(video => video.Date);
MaxDate = Videos.Max(video => video.Date);
MinDuration = Videos.Min(video => video.Duration);
MaxDuration = Videos.Max(video => video.Duration);
FilterMinViewsNumberBox.Minimum = FilterMaxViewsNumberBox.Minimum = FilterMinViewsNumberBox.Value = MinViews;
FilterMinViewsNumberBox.Maximum = FilterMaxViewsNumberBox.Maximum = FilterMaxViewsNumberBox.Value = MaxViews;
FilterMinDateDatePicker.MinDate = FilterMaxDateDatePicker.MinDate = (DateTimeOffset)(FilterMinDateDatePicker.Date = MinDate);
FilterMinDateDatePicker.MaxDate = FilterMaxDateDatePicker.MaxDate = (DateTimeOffset)(FilterMaxDateDatePicker.Date = MaxDate);
TextBoxExtensions.SetMask(FilterMinDurationTextBox, (string)new TimeSpanToTextBoxMaskConverter().Convert(MaxDuration, null, null, null));
TextBoxExtensions.SetMask(FilterMaxDurationTextBox, (string)new TimeSpanToTextBoxMaskConverter().Convert(MaxDuration, null, null, null));
HashSet<int> maskElements = new HashSet<int>();
foreach (TimeSpan ts in new List<TimeSpan> { MinDuration, MaxDuration })
{
if (Math.Floor(ts.TotalHours) > 0) maskElements.Add(int.Parse(Math.Floor(ts.TotalHours).ToString()[0].ToString()));
if (Math.Floor(ts.TotalMinutes) > 0)
{
if (Math.Floor(ts.TotalHours) > 0) maskElements.Add(5);
else maskElements.Add(int.Parse(ts.Minutes.ToString()[0].ToString()));
}
if (Math.Floor(ts.TotalMinutes) > 0) maskElements.Add(5);
else maskElements.Add(int.Parse(ts.Seconds.ToString()[0].ToString()));
}
List<string> maskElementsString = new List<string>();
foreach (int i in maskElements)
{
if (i != 9) maskElementsString.Add($"{i}:[0-{i}]");
}
TextBoxExtensions.SetCustomMask(FilterMinDurationTextBox, string.Join(',', maskElementsString));
TextBoxExtensions.SetCustomMask(FilterMaxDurationTextBox, string.Join(',', maskElementsString));
FilterMinDurationTextBox.Text = MinDuration.ToStringOptTHMMBaseSS(MaxDuration);
FilterMaxDurationTextBox.Text = MaxDuration.ToStringOptTHMMBaseSS();
ApplyToAllSchedule = 0;
foreach (IVideo video in Videos)
{
SerialVideoAddingVideoControl videoControl = new SerialVideoAddingVideoControl(video);
videoControl.DeleteRequested += (s, a) =>
{
DeletedVideos.Add(videoControl);
FilterRemovedTextBlock.Visibility = Visibility.Visible;
FilterRemovedCountTextBlock.Visibility = Visibility.Visible;
FilterRemovedRestoreButton.Visibility = Visibility.Visible;
FilterRemovedCountTextBlock.Text = $"{DeletedVideos.Count}";
FilterHeaderCountTextBlock.Text = HiddenVideos.Count + DeletedVideos.Count > 0 ? $"{ResourceLoader.GetForCurrentView().GetString("Home_Adding_Base_SerialVideoAddingControl_Filter_Header_CountTextBlockPrefix")}: {HiddenVideos.Count + DeletedVideos.Count}" : "";
VideosStackPanel.Children.Remove(videoControl);
};
VideosStackPanel.Children.Add(videoControl);
}
}
#endregion
#region PROPERTIES
private IVideo[] Videos { get; set; }
public StorageFolder ApplyToAllLocation { get; set; }
public double ApplyToAllSchedule { get; set; }
private List<SerialVideoAddingVideoControl> DeletedVideos { get; set; } = new List<SerialVideoAddingVideoControl>();
private List<SerialVideoAddingVideoControl> HiddenVideos { get; set; } = new List<SerialVideoAddingVideoControl>();
private long MinViews { get; set; }
private long MaxViews { get; set; }
private DateTime MinDate { get; set; }
private DateTime MaxDate { get; set; }
private TimeSpan MinDuration { get; set; }
private TimeSpan MaxDuration { get; set; }
#endregion
#region EVENT HANDLERS
private async void SerialVideoAddingControl_Loading(FrameworkElement sender, object args)
{
if (!(bool)Config.GetValue("custom_media_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("last_media_location"))
{
ApplyToAllLocation = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("last_media_location");
ApplyToAllLocationSettingControl.Description = ApplyToAllLocation.Path;
}
else if ((bool)Config.GetValue("custom_media_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("custom_media_location"))
{
ApplyToAllLocation = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("selected_media_location");
ApplyToAllLocationSettingControl.Description = ApplyToAllLocation.Path;
}
else
{
ApplyToAllLocation = null;
ApplyToAllLocationSettingControl.Description = $@"{UserDataPaths.GetDefault().Downloads}\VDownload";
}
}
private async void ApplyToAllLocationBrowseButton_Click(object sender, RoutedEventArgs e)
{
FolderPicker picker = new FolderPicker
{
SuggestedStartLocation = PickerLocationId.Downloads
};
picker.FileTypeFilter.Add("*");
StorageFolder selectedFolder = await picker.PickSingleFolderAsync();
if (selectedFolder != null)
{
try
{
await (await selectedFolder.CreateFileAsync("VDownloadLocationAccessTest")).DeleteAsync();
StorageApplicationPermissions.FutureAccessList.AddOrReplace("last_media_location", selectedFolder);
ApplyToAllLocation = selectedFolder;
ApplyToAllLocationSettingControl.Description = ApplyToAllLocation.Path;
}
catch (UnauthorizedAccessException) { }
}
}
private void ApplyToAllApplyLocationButton_Click(object sender, RoutedEventArgs e)
{
foreach (SerialVideoAddingVideoControl videoPanel in VideosStackPanel.Children)
{
videoPanel.OptionsControl.ChangeLocation(ApplyToAllLocation);
}
foreach (SerialVideoAddingVideoControl videoPanel in DeletedVideos)
{
videoPanel.OptionsControl.ChangeLocation(ApplyToAllLocation);
}
foreach (SerialVideoAddingVideoControl videoPanel in HiddenVideos)
{
videoPanel.OptionsControl.ChangeLocation(ApplyToAllLocation);
}
}
private void ApplyToAllScheduleNumberBox_LostFocus(object sender, RoutedEventArgs e)
{
if (double.IsNaN(ApplyToAllScheduleNumberBox.Value))
{
ApplyToAllScheduleNumberBox.Value = ApplyToAllSchedule;
}
else
{
ApplyToAllSchedule = ApplyToAllScheduleNumberBox.Value;
}
}
private void ApplyToAllApplyScheduleButton_Click(object sender, RoutedEventArgs e)
{
foreach (SerialVideoAddingVideoControl videoPanel in VideosStackPanel.Children)
{
videoPanel.OptionsControl.ChangeSchedule(ApplyToAllSchedule);
}
foreach (SerialVideoAddingVideoControl videoPanel in DeletedVideos)
{
videoPanel.OptionsControl.ChangeSchedule(ApplyToAllSchedule);
}
foreach (SerialVideoAddingVideoControl videoPanel in HiddenVideos)
{
videoPanel.OptionsControl.ChangeSchedule(ApplyToAllSchedule);
}
}
private void FilterTitleAndAuthorTextBox_LostFocus(object sender, RoutedEventArgs e)
{
FilterChanged();
}
private void FilterMinViewsNumberBox_LostFocus(object sender, RoutedEventArgs e)
{
if (double.IsNaN(FilterMinViewsNumberBox.Value))
{
FilterMinViewsNumberBox.Value = MinViews;
}
FilterChanged();
}
private void FilterMaxViewsNumberBox_LostFocus(object sender, RoutedEventArgs e)
{
if (double.IsNaN(FilterMaxViewsNumberBox.Value))
{
FilterMaxViewsNumberBox.Value = MaxViews;
}
FilterChanged();
}
private void FilterMinAndMaxDateDatePicker_LostFocus(object sender, RoutedEventArgs args)
{
FilterChanged();
}
private void FilterMinDurationTextBox_LostFocus(object sender, RoutedEventArgs e)
{
if (!FilterMinDurationTextBox.Text.Contains('_'))
{
string[] segments = FilterMinDurationTextBox.Text.Split(':').Reverse().ToArray();
int.TryParse(segments.ElementAtOrDefault(0), out int seconds);
int.TryParse(segments.ElementAtOrDefault(1), out int minutes);
int.TryParse(segments.ElementAtOrDefault(2), out int hours);
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
if (parsedTimeSpan < MinDuration || parsedTimeSpan > MaxDuration)
{
FilterMinDurationTextBox.Text = MinDuration.ToStringOptTHMMBaseSS(MaxDuration);
}
}
else
{
FilterMinDurationTextBox.Text = MinDuration.ToStringOptTHMMBaseSS(MaxDuration);
}
FilterChanged();
}
private void FilterMaxDurationTextBox_LostFocus(object sender, RoutedEventArgs e)
{
if (!FilterMaxDurationTextBox.Text.Contains('_'))
{
string[] segments = FilterMaxDurationTextBox.Text.Split(':').Reverse().ToArray();
int.TryParse(segments.ElementAtOrDefault(0), out int seconds);
int.TryParse(segments.ElementAtOrDefault(1), out int minutes);
int.TryParse(segments.ElementAtOrDefault(2), out int hours);
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
if (parsedTimeSpan < MinDuration || parsedTimeSpan > MaxDuration)
{
FilterMaxDurationTextBox.Text = MaxDuration.ToStringOptTHMMBaseSS();
}
}
else
{
FilterMaxDurationTextBox.Text = MaxDuration.ToStringOptTHMMBaseSS();
}
FilterChanged();
}
private void FilterRemovedRestoreButton_Click(object sender, RoutedEventArgs e)
{
foreach (SerialVideoAddingVideoControl videoControl in DeletedVideos)
{
VideosStackPanel.Children.Add(videoControl);
}
FilterRemovedTextBlock.Visibility = Visibility.Collapsed;
FilterRemovedCountTextBlock.Visibility = Visibility.Collapsed;
FilterRemovedRestoreButton.Visibility = Visibility.Collapsed;
FilterHeaderCountTextBlock.Text = HiddenVideos.Count + DeletedVideos.Count > 0 ? $"{ResourceLoader.GetForCurrentView().GetString("HomePlaylistAddingPanelFilterHeaderCountTextBlockPrefix")}: {HiddenVideos.Count + DeletedVideos.Count}" : "";
DeletedVideos.Clear();
}
#endregion
#region PRIVATE METHODS
private void FilterChanged()
{
string[] minSegments = FilterMinDurationTextBox.Text.Split(':').Reverse().ToArray();
int.TryParse(minSegments.ElementAtOrDefault(0), out int minSeconds);
int.TryParse(minSegments.ElementAtOrDefault(1), out int minMinutes);
int.TryParse(minSegments.ElementAtOrDefault(2), out int minHours);
TimeSpan minDuration = new TimeSpan(minHours, minMinutes, minSeconds);
string[] maxSegments = FilterMaxDurationTextBox.Text.Split(':').Reverse().ToArray();
int.TryParse(maxSegments.ElementAtOrDefault(0), out int maxSeconds);
int.TryParse(maxSegments.ElementAtOrDefault(1), out int maxMinutes);
int.TryParse(maxSegments.ElementAtOrDefault(2), out int maxHours);
TimeSpan maxDuration = new TimeSpan(maxHours, maxMinutes, maxSeconds);
Regex titleRegex = new Regex("");
Regex authorRegex = new Regex("");
try
{
titleRegex = new Regex(FilterTitleTextBox.Text);
authorRegex = new Regex(FilterAuthorTextBox.Text);
}
catch (ArgumentException) { }
List<SerialVideoAddingVideoControl> allVideos = new List<SerialVideoAddingVideoControl>();
allVideos.AddRange((IEnumerable<SerialVideoAddingVideoControl>)(VideosStackPanel.Children.ToList()));
allVideos.AddRange(HiddenVideos);
VideosStackPanel.Children.Clear();
HiddenVideos.Clear();
foreach (SerialVideoAddingVideoControl videoControl in allVideos)
{
if (
!titleRegex.IsMatch(videoControl.Video.Title) ||
!authorRegex.IsMatch(videoControl.Video.Author) ||
FilterMinViewsNumberBox.Value > videoControl.Video.Views ||
FilterMaxViewsNumberBox.Value < videoControl.Video.Views ||
FilterMinDateDatePicker.Date > videoControl.Video.Date ||
FilterMaxDateDatePicker.Date < videoControl.Video.Date ||
minDuration > videoControl.Video.Duration ||
maxDuration < videoControl.Video.Duration
) HiddenVideos.Add(videoControl);
else VideosStackPanel.Children.Add(videoControl);
}
FilterHeaderCountTextBlock.Text = HiddenVideos.Count + DeletedVideos.Count > 0 ? $"{ResourceLoader.GetForCurrentView().GetString("HomePlaylistAddingPanelFilterHeaderCountTextBlockPrefix")}: {HiddenVideos.Count + DeletedVideos.Count}" : string.Empty;
}
#endregion
}
}

View File

@@ -1,5 +1,5 @@
<UserControl
x:Class="VDownload.Views.Home.Controls.HomeSerialAddingVideoPanel"
x:Class="VDownload.Views.Home.Controls.SerialVideoAddingVideoControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.Home"
@@ -8,10 +8,10 @@
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:cc="using:VDownload.Controls"
xmlns:ex="using:Microsoft.Toolkit.Uwp.UI"
Loading="HomeSerialAddingVideoPanel_Loading"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
d:DesignWidth="400"
Loading="SerialVideoAddingVideoControl_Loading">
<UserControl.Resources>
@@ -27,7 +27,7 @@
</UserControl.Resources>
<muxc:Expander x:Name="HomeSerialAddingVideoExpander" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Background="{ThemeResource HomePlaylistAddingVideoPanelContentBackgroundColor}">
<muxc:Expander x:Name="Expander" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Background="{ThemeResource HomePlaylistAddingVideoPanelContentBackgroundColor}">
<muxc:Expander.Header>
<Grid Margin="-5,10,-15,10" ColumnSpacing="10">
<Grid.RowDefinitions>
@@ -67,8 +67,8 @@
</StackPanel>
</Grid>
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="15" VerticalAlignment="Center" FontWeight="SemiBold" Text="{x:Bind Title}"/>
<AppBarButton Grid.Row="0" Grid.Column="2" Margin="0,-4,0,-4" Width="40" Height="48" Icon="{x:Bind SourceImage}" Click="HomeSerialAddingVideoPanelSourceButton_Click"/>
<AppBarButton Grid.Row="1" Grid.Column="2" Margin="0,-4,0,-4" Width="40" Height="48" Icon="Delete" Click="HomeSerialAddingVideoPanelDeleteButton_Click"/>
<AppBarButton Grid.Row="0" Grid.Column="2" Margin="0,-4,0,-4" Width="40" Height="48" Icon="{x:Bind SourceImage}" Click="SourceButton_Click"/>
<AppBarButton Grid.Row="1" Grid.Column="2" Margin="0,-4,0,-4" Width="40" Height="48" Icon="Delete" Click="HideButton_Click"/>
</Grid>
</muxc:Expander.Header>
</muxc:Expander>

View File

@@ -0,0 +1,94 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using VDownload.Core.Extensions;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
namespace VDownload.Views.Home.Controls
{
public sealed partial class SerialVideoAddingVideoControl : UserControl
{
#region CONSTANTS
private readonly ResourceDictionary ImagesRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Images.xaml") };
#endregion
#region CONSTRUCTORS
public SerialVideoAddingVideoControl(IVideo video)
{
this.InitializeComponent();
Video = video;
ThumbnailImage = Video.Thumbnail != null ? new BitmapImage { UriSource = Video.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"];
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{Video.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
Title = Video.Title;
Author = Video.Author;
Views = Video.Views.ToString();
Date = Video.Date.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortDatePattern);
Duration = Video.Duration.ToStringOptTHBaseMMSS();
OptionsControl = new VideoAddingOptionsControl(Video);
Expander.Content = OptionsControl;
}
#endregion
#region PROPERTIES
public IVideo Video { get; set; }
private ImageSource ThumbnailImage { get; set; }
private IconElement SourceImage { get; set; }
private string Title { get; set; }
private string Author { get; set; }
private string Views { get; set; }
private string Date { get; set; }
private string Duration { get; set; }
public VideoAddingOptionsControl OptionsControl { get; private set; }
#endregion
#region EVENT HANDLERS
private async void SerialVideoAddingVideoControl_Loading(FrameworkElement sender, object args)
{
await OptionsControl.Init();
}
private async void SourceButton_Click(object sender, RoutedEventArgs e)
{
await Windows.System.Launcher.LaunchUriAsync(Video.Url);
}
private void HideButton_Click(object sender, RoutedEventArgs e)
{
DeleteRequested?.Invoke(this, EventArgs.Empty);
}
#endregion
#region EVENTS
public event EventHandler DeleteRequested;
#endregion
}
}

View File

@@ -0,0 +1,23 @@
<UserControl
x:Class="VDownload.Views.Home.Controls.SubscriptionsLoadControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Controls"
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"
d:DesignHeight="100"
d:DesignWidth="400">
<Grid Margin="10" ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<muxc:ProgressRing x:Name="ProgressRing" Grid.Column="0" Width="15" Height="15" Margin="15,5,5,5" IsActive="True"/>
<TextBlock x:Name="InfoTextBlock" Grid.Column="1" VerticalAlignment="Center"/>
<Button x:Name="LoadVideosButton" x:Uid="Home_OptionsBar_SubscriptionsLoadControl_LoadVideosButton" Grid.Column="2" Visibility="Collapsed" Click="LoadVideosButton_Click"/>
</Grid>
</UserControl>

View File

@@ -0,0 +1,183 @@
using Microsoft.Toolkit.Uwp.Connectivity;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.EventArgs;
using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Services.Sources;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
namespace VDownload.Views.Home.Controls
{
public sealed partial class SubscriptionsLoadControl : UserControl
{
#region CONSTRUCTORS
public SubscriptionsLoadControl()
{
this.InitializeComponent();
InfoTextBlock.Text = ResourceLoader.GetForCurrentView().GetString("Home_OptionsBar_SubscriptionsLoadControl_InfoTextBlock_Loading");
}
#endregion
#region PROPERTIES
public CancellationToken CancellationToken { get; set; }
#endregion
#region PUBLIC METHODS
public async Task StartLoading()
{
List<IVideo> loadedVideos = new List<IVideo>();
(Subscription Subscription, StorageFile SubscriptionFile)[] subscriptions = await SubscriptionsCollectionManagement.GetSubscriptionsAsync();
foreach ((Subscription Subscription, StorageFile SubscriptionFile) subscription in subscriptions)
{
try
{
loadedVideos.AddRange(await subscription.Subscription.GetNewVideosAsync(CancellationToken));
}
catch (OperationCanceledException)
{
return;
}
catch (MediaNotFoundException)
{
await ShowDialog("MediaNotFound", subscription.Subscription.Playlist.Name);
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;
}
}
ProgressRing.Visibility = Visibility.Collapsed;
InfoTextBlock.Text = $"{ResourceLoader.GetForCurrentView().GetString("Home_OptionsBar_SubscriptionsLoadControl_InfoTextBlock_Found")}: {loadedVideos.Count}";
if (loadedVideos.Count > 0)
{
LoadVideosButton.Visibility = Visibility.Visible;
}
}
#endregion
#region EVENT HANDLERS
private async void LoadVideosButton_Click(object sender, RoutedEventArgs e)
{
ProgressRing.Visibility = Visibility.Visible;
LoadVideosButton.IsEnabled = false;
List<IVideo> loadedVideos = new List<IVideo>();
(Subscription Subscription, StorageFile SubscriptionFile)[] subscriptions = await SubscriptionsCollectionManagement.GetSubscriptionsAsync();
foreach ((Subscription Subscription, StorageFile SubscriptionFile) subscription in subscriptions)
{
try
{
loadedVideos.AddRange(await subscription.Subscription.GetNewVideosAndUpdateAsync(CancellationToken));
await SubscriptionsCollectionManagement.UpdateSubscriptionFileAsync(subscription.Subscription, subscription.SubscriptionFile);
}
catch (OperationCanceledException)
{
return;
}
catch (MediaNotFoundException)
{
await ShowDialog("MediaNotFound", subscription.Subscription.Playlist.Name);
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;
}
}
LoadingSuccessed.Invoke(this, new SubscriptionLoadSuccessedEventArgs(loadedVideos.ToArray()));
}
#endregion
#region PRIVATE METHODS
private async Task ShowDialog(string localErrorKey, string playlistName = null)
{
StringBuilder content = new StringBuilder(ResourceLoader.GetForCurrentView("DialogResources").GetString($"Home_OptionsBar_SubscriptionsLoadControl_{localErrorKey}_Content"));
if (!string.IsNullOrEmpty(playlistName))
{
content.Append($" {playlistName}");
}
ContentDialog errorDialog = new ContentDialog
{
Title = ResourceLoader.GetForCurrentView("DialogResources").GetString("Home_OptionsBar_SubscriptionsLoadControl_Base_Title"),
Content = content.ToString(),
CloseButtonText = ResourceLoader.GetForCurrentView("DialogResources").GetString("Base_CloseButtonText"),
};
await errorDialog.ShowAsync();
ProgressRing.Visibility = Visibility.Collapsed;
InfoTextBlock.Text = $"{ResourceLoader.GetForCurrentView().GetString("Home_OptionsBar_SubscriptionsLoadControl_InfoTextBlock_Found")}: 0";
}
#endregion
#region EVENTS
public event EventHandler<SubscriptionLoadSuccessedEventArgs> LoadingSuccessed;
#endregion
}
}

View File

@@ -0,0 +1,76 @@
<UserControl
x:Class="VDownload.Views.Home.Controls.VideoAddingOptionsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.Home.Controls"
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"
xmlns:ex="using:Microsoft.Toolkit.Uwp.UI"
xmlns:cc="using:VDownload.Controls"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///Resources/Icons.xaml"/>
<ResourceDictionary Source="ms-appx:///Resources/Converters.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel Spacing="30">
<StackPanel>
<TextBlock x:Uid="Home_Adding_Base_DownloadingTaskOptionsControl_DownloadingOptions_HeaderTextBlock" Margin="0,0,0,10" FontWeight="SemiBold"/>
<cc:SettingControl x:Uid="Home_Adding_Base_DownloadingTaskOptionsControl_DownloadingOptions_MediaTypeSettingControl" Margin="0,0,0,10" Icon="{ThemeResource MediaTypeIcon}">
<cc:SettingControl.SettingContent>
<ComboBox x:Name="MediaTypeSettingControlComboBox" Width="150" SelectionChanged="MediaTypeSettingControlComboBox_SelectionChanged"/>
</cc:SettingControl.SettingContent>
</cc:SettingControl>
<cc:SettingControl x:Name="QualitySettingControl" Margin="0,0,0,10" x:Uid="Home_Adding_Base_DownloadingTaskOptionsControl_DownloadingOptions_QualitySettingControl" Icon="{ThemeResource QualityIcon}">
<cc:SettingControl.SettingContent>
<ComboBox x:Name="QualitySettingControlComboBox" Width="150" SelectionChanged="QualitySettingControlComboBox_SelectionChanged"/>
</cc:SettingControl.SettingContent>
</cc:SettingControl>
<cc:SettingControl x:Uid="Home_Adding_Base_DownloadingTaskOptionsControl_DownloadingOptions_TrimSettingControl" Icon="{ThemeResource TrimIcon}">
<cc:SettingControl.SettingContent>
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBox x:Name="TrimSettingControlStartTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind Video.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind Video.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" LostFocus="TrimSettingControlStartTextBox_LostFocus"/>
<TextBlock VerticalAlignment="Center" Text="-"/>
<TextBox x:Name="TrimSettingControlEndTextBox" ex:TextBoxExtensions.CustomMask="{x:Bind Video.Duration, Converter={StaticResource TimeSpanToTextBoxMaskElementsConverter}}" ex:TextBoxExtensions.Mask="{x:Bind Video.Duration, Converter={StaticResource TimeSpanToTextBoxMaskConverter}}" LostFocus="TrimSettingControlEndTextBox_LostFocus"/>
</StackPanel>
</cc:SettingControl.SettingContent>
</cc:SettingControl>
</StackPanel>
<StackPanel Spacing="10">
<TextBlock x:Uid="Home_Adding_Base_DownloadingTaskOptionsControl_FileOptions_HeaderTextBlock" FontWeight="SemiBold"/>
<cc:SettingControl x:Uid="Home_Adding_Base_DownloadingTaskOptionsControl_FileOptions_FileSettingControl" Icon="{ThemeResource FileIcon}">
<cc:SettingControl.SettingContent>
<Grid ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="FileSettingControlFilenameTextBox" Grid.Column="0" MaxWidth="300" HorizontalAlignment="Right" IsSpellCheckEnabled="False" LostFocus="FileSettingControlFilenameTextBox_LostFocus"/>
<ComboBox x:Name="FileSettingControlFileExtensionComboBox" Grid.Column="1" HorizontalAlignment="Stretch" SelectionChanged="FileSettingControlFileExtensionComboBox_SelectionChanged"/>
</Grid>
</cc:SettingControl.SettingContent>
</cc:SettingControl>
<cc:SettingControl x:Name="FileLocationSettingControl" x:Uid="Home_Adding_Base_DownloadingTaskOptionsControl_FileOptions_FileLocationSettingControl" Icon="{ThemeResource LocationIcon}">
<cc:SettingControl.SettingContent>
<Button x:Uid="Home_Adding_Base_DownloadingTaskOptionsControl_FileOptions_FileLocationSettingControl_BrowseButton" Click="FileSettingControlFileLocationBrowseButton_Click"/>
</cc:SettingControl.SettingContent>
</cc:SettingControl>
</StackPanel>
<StackPanel Spacing="10">
<TextBlock x:Uid="Home_Adding_Base_DownloadingTaskOptionsControl_TaskOptions_HeaderTextBlock" FontWeight="SemiBold"/>
<cc:SettingControl x:Uid="Home_Adding_Base_DownloadingTaskOptionsControl_TaskOptions_ScheduleSettingControl" Icon="{ThemeResource ScheduleIcon}">
<cc:SettingControl.SettingContent>
<muxc:NumberBox x:Name="ScheduleSettingControlNumberBox" MaxWidth="100" Value="0" Minimum="0" SpinButtonPlacementMode="Compact" LostFocus="ScheduleSettingControlNumberBox_LostFocus"/>
</cc:SettingControl.SettingContent>
</cc:SettingControl>
</StackPanel>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,280 @@
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using VDownload.Controls;
using VDownload.Core.Enums;
using VDownload.Core.Extensions;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Structs;
using Windows.ApplicationModel.Resources;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
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.Home.Controls
{
public sealed partial class VideoAddingOptionsControl : UserControl
{
#region CONSTRUCTORS
public VideoAddingOptionsControl(IVideo video)
{
this.InitializeComponent();
Video = video;
foreach (string mediaType in Enum.GetNames(typeof(MediaType)))
{
MediaTypeSettingControlComboBox.Items.Add(ResourceLoader.GetForCurrentView().GetString($"Base_MediaType_{mediaType}Text"));
}
MediaTypeSettingControlComboBox.SelectedIndex = (int)Config.GetValue("default_media_type");
foreach (BaseStream stream in Video.BaseStreams)
{
QualitySettingControlComboBox.Items.Add(stream.ToString());
}
QualitySettingControlComboBox.SelectedIndex = 0;
TrimStart = TimeSpan.Zero;
TrimSettingControlStartTextBox.Text = TrimStart.ToStringOptTHMMBaseSS(Video.Duration);
TrimEnd = Video.Duration;
TrimSettingControlEndTextBox.Text = TrimEnd.ToStringOptTHMMBaseSS();
string temporaryFilename = (string)Config.GetValue("default_filename");
Dictionary<string, string> filenameStandardTemplates = new Dictionary<string, string>()
{
{ "<title>", Video.Title },
{ "<author>", Video.Author },
{ "<views>", Video.Views.ToString() },
{ "<id>", Video.ID },
};
foreach (KeyValuePair<string, string> template in filenameStandardTemplates)
{
temporaryFilename = temporaryFilename.Replace(template.Key, template.Value);
}
Dictionary<Regex, IFormattable> filenameFormatTemplates = new Dictionary<Regex, IFormattable>()
{
{ new Regex(@"<date_pub:(?<format>.*)>"), Video.Date },
{ new Regex(@"<date_now:(?<format>.*)>"), DateTime.Now },
{ new Regex(@"<duration:(?<format>.*)>"), Video.Duration },
};
foreach (KeyValuePair<Regex, IFormattable> template in filenameFormatTemplates)
{
foreach (Match templateMatch in template.Key.Matches(temporaryFilename))
{
temporaryFilename = temporaryFilename.Replace(templateMatch.Value, template.Value.ToString(templateMatch.Groups["format"].Value, null));
}
}
foreach (char c in Path.GetInvalidFileNameChars())
{
temporaryFilename = temporaryFilename.Replace(c, ' ');
}
Filename = temporaryFilename;
FileSettingControlFilenameTextBox.Text = Filename;
Schedule = 0;
}
#endregion
#region PROPERTIES
private IVideo Video { get; set; }
public MediaType MediaType { get; private set; }
public BaseStream Stream { get; private set; }
public TimeSpan TrimStart { get; private set; }
public TimeSpan TrimEnd { get; private set; }
public string Filename { get; private set; }
public MediaFileExtension FileExtension { get; private set; }
public StorageFolder FileLocation { get; private set; }
public double Schedule { get; private set; }
#endregion
#region PUBLIC METHODS
public void ChangeLocation(StorageFolder location)
{
FileLocation = location;
FileLocationSettingControl.Description = FileLocation.Path ?? $@"{UserDataPaths.GetDefault().Downloads}\VDownload";
}
public void ChangeSchedule(double schedule)
{
Schedule = schedule;
ScheduleSettingControlNumberBox.Value = Schedule;
}
#endregion
#region PUBLIC METHODS
public async Task Init()
{
if (!(bool)Config.GetValue("custom_media_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("last_media_location"))
{
FileLocation = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("last_media_location");
FileLocationSettingControl.Description = FileLocation.Path;
}
else if ((bool)Config.GetValue("custom_media_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("custom_media_location"))
{
FileLocation = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("selected_media_location");
FileLocationSettingControl.Description = FileLocation.Path;
}
else
{
FileLocation = null;
FileLocationSettingControl.Description = $@"{UserDataPaths.GetDefault().Downloads}\VDownload";
}
}
#endregion
#region EVENT HANDLERS
private void MediaTypeSettingControlComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MediaType = (MediaType)((ComboBox)sender).SelectedIndex;
if (((ComboBox)sender).SelectedIndex == (int)MediaType.OnlyAudio)
{
QualitySettingControl.Visibility = Visibility.Collapsed;
QualitySettingControlComboBox.SelectedIndex = Video.BaseStreams.Count() - 1;
FileSettingControlFileExtensionComboBox.Items.Clear();
foreach (AudioFileExtension extension in Enum.GetValues(typeof(AudioFileExtension)))
{
FileSettingControlFileExtensionComboBox.Items.Add(extension);
}
FileSettingControlFileExtensionComboBox.SelectedIndex = (int)Config.GetValue("default_audio_extension") - 3;
}
else
{
QualitySettingControl.Visibility = Visibility.Visible;
QualitySettingControlComboBox.SelectedIndex = 0;
FileSettingControlFileExtensionComboBox.Items.Clear();
foreach (VideoFileExtension extension in Enum.GetValues(typeof(VideoFileExtension)))
{
FileSettingControlFileExtensionComboBox.Items.Add(extension);
}
FileSettingControlFileExtensionComboBox.SelectedIndex = (int)Config.GetValue("default_video_extension");
}
}
private void QualitySettingControlComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Stream = Video.BaseStreams[((ComboBox)sender).SelectedIndex];
}
private void TrimSettingControlStartTextBox_LostFocus(object sender, RoutedEventArgs e)
{
if (!((TextBox)sender).Text.Contains('_'))
{
string[] segments = ((TextBox)sender).Text.Split(':').Reverse().ToArray();
int.TryParse(segments.ElementAtOrDefault(0), out int seconds);
int.TryParse(segments.ElementAtOrDefault(1), out int minutes);
int.TryParse(segments.ElementAtOrDefault(2), out int hours);
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
if (parsedTimeSpan < Video.Duration && parsedTimeSpan > TimeSpan.Zero) TrimStart = parsedTimeSpan;
else
{
TrimStart = TimeSpan.Zero;
((TextBox)sender).Text = TrimStart.ToStringOptTHMMBaseSS(Video.Duration);
}
}
}
private void TrimSettingControlEndTextBox_LostFocus(object sender, RoutedEventArgs e)
{
if (!((TextBox)sender).Text.Contains('_'))
{
string[] segments = ((TextBox)sender).Text.Split(':').Reverse().ToArray();
int.TryParse(segments.ElementAtOrDefault(0), out int seconds);
int.TryParse(segments.ElementAtOrDefault(1), out int minutes);
int.TryParse(segments.ElementAtOrDefault(2), out int hours);
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
if (parsedTimeSpan < Video.Duration && parsedTimeSpan > TimeSpan.Zero) TrimEnd = parsedTimeSpan;
else
{
TrimEnd = Video.Duration;
((TextBox)sender).Text = TrimEnd.ToStringOptTHMMBaseSS();
}
}
}
private void FileSettingControlFilenameTextBox_LostFocus(object sender, RoutedEventArgs e)
{
foreach (char c in Path.GetInvalidFileNameChars())
{
((TextBox)sender).Text = ((TextBox)sender).Text.Replace(c, ' ');
}
Filename = ((TextBox)sender).Text;
}
private void FileSettingControlFileExtensionComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
FileExtension = (MediaFileExtension)((ComboBox)sender).SelectedIndex + (MediaType == MediaType.OnlyAudio ? 3 : 0);
}
private async void FileSettingControlFileLocationBrowseButton_Click(object sender, RoutedEventArgs e)
{
FolderPicker picker = new FolderPicker
{
SuggestedStartLocation = PickerLocationId.Downloads
};
picker.FileTypeFilter.Add("*");
StorageFolder selectedFolder = await picker.PickSingleFolderAsync();
if (selectedFolder != null)
{
try
{
await(await selectedFolder.CreateFileAsync("VDownloadLocationAccessTest")).DeleteAsync();
StorageApplicationPermissions.FutureAccessList.AddOrReplace("last_media_location", selectedFolder);
FileLocation = selectedFolder;
FileLocationSettingControl.Description = FileLocation.Path;
}
catch (UnauthorizedAccessException) { }
}
}
private void ScheduleSettingControlNumberBox_LostFocus(object sender, RoutedEventArgs e)
{
if (((NumberBox)sender).Value == double.NaN) ((NumberBox)sender).Value = 0;
Schedule = ((NumberBox)sender).Value;
}
#endregion
}
}

View File

@@ -0,0 +1,30 @@
<UserControl
x:Class="VDownload.Views.Home.Controls.VideoSearchControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Controls"
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"
d:DesignHeight="100"
d:DesignWidth="400">
<Grid x:Name="Grid" Margin="10" ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="UrlTextBox" x:Uid="Home_OptionsBar_VideoSearchControl_UrlTextBox" Grid.Column="0" VerticalAlignment="Center"/>
<Button x:Name="SearchButton" x:Uid="Home_OptionsBar_VideoSearchControl_SearchButton" Grid.Column="1" Click="SearchButton_Click"/>
<Button x:Name="HelpButton" Grid.Column="2" Content="?" Click="HelpButton_Click">
<Button.Resources>
<muxc:TeachingTip x:Name="InfoBox" x:Uid="Home_OptionsBar_VideoSearchControl_InfoBox" Target="{x:Bind Grid}"/>
</Button.Resources>
</Button>
<ContentPresenter x:Name="StatusControlPresenter" Margin="-10,0,0,0" Grid.Column="3"/>
</Grid>
</UserControl>

View File

@@ -0,0 +1,140 @@
using Microsoft.Toolkit.Uwp.Connectivity;
using System;
using System.Diagnostics;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.EventArgs;
using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Services.Sources;
using Windows.ApplicationModel.Resources;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
namespace VDownload.Views.Home.Controls
{
public sealed partial class VideoSearchControl : UserControl
{
#region CONSTANTS
private static readonly ResourceDictionary IconRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Icons.xaml") };
private static readonly Microsoft.UI.Xaml.Controls.ProgressRing StatusControlProgressRing = new Microsoft.UI.Xaml.Controls.ProgressRing { Width = 15, Height = 15, Margin = new Thickness(15, 5, 5, 5), IsActive = true };
private static readonly Image StatusControlErrorImage = new Image { Width = 15, Height = 15, Margin = new Thickness(15, 5, 5, 5), Source = (SvgImageSource)IconRes["ErrorIcon"] };
#endregion
#region CONSTRUCTORS
public VideoSearchControl()
{
InitializeComponent();
}
#endregion
#region PROPERTIES
public CancellationToken CancellationToken { get; set; }
#endregion
#region EVENT HANDLERS
private async void SearchButton_Click(object sender, RoutedEventArgs e)
{
async Task ShowDialog(string localErrorKey)
{
ContentDialog errorDialog = new ContentDialog
{
Title = ResourceLoader.GetForCurrentView("DialogResources").GetString("Home_OptionsBar_VideoSearchControl_Base_Title"),
Content = ResourceLoader.GetForCurrentView("DialogResources").GetString($"Home_OptionsBar_VideoSearchControl_{localErrorKey}_Content"),
CloseButtonText = ResourceLoader.GetForCurrentView("DialogResources").GetString("Base_CloseButtonText"),
};
await errorDialog.ShowAsync();
StatusControlPresenter.Content = StatusControlErrorImage;
}
SearchButtonClicked.Invoke(this, EventArgs.Empty);
InfoBox.IsOpen = false;
StatusControlPresenter.Content = StatusControlProgressRing;
IVideo video = Source.GetVideo(UrlTextBox.Text);
if (video == null)
{
StatusControlPresenter.Content = StatusControlErrorImage;
}
else
{
try
{
await video.GetMetadataAsync(CancellationToken);
await video.GetStreamsAsync(CancellationToken);
}
catch (OperationCanceledException)
{
StatusControlPresenter.Content = null;
return;
}
catch (MediaNotFoundException)
{
StatusControlPresenter.Content = StatusControlErrorImage;
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;
}
StatusControlPresenter.Content = null;
SearchingSuccessed?.Invoke(this, new VideoSearchSuccessedEventArgs(video));
}
}
private void HelpButton_Click(object sender, RoutedEventArgs e)
{
// Switch info box
InfoBox.IsOpen = !InfoBox.IsOpen;
}
#endregion
#region EVENT HANDLERS
public event EventHandler<VideoSearchSuccessedEventArgs> SearchingSuccessed;
public event EventHandler SearchButtonClicked;
#endregion
}
}

View File

@@ -1,97 +0,0 @@
<Page
x:Class="VDownload.Views.Home.HomeMain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.Home"
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"
xmlns:cc="using:VDownload.Controls"
mc:Ignorable="d"
Background="Transparent">
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///Resources/Icons.xaml"/>
<ResourceDictionary Source="ms-appx:///Resources/Colors.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Page.Resources>
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Compact">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="HomeOptionBarButtonsStackPanel.(Grid.ColumnSpan)" Value="4"/>
<Setter Target="HomeOptionBarButtonsStackPanel.(Grid.Column)" Value="0"/>
<Setter Target="HomeOptionsBarAddingControl.(Grid.Row)" Value="0"/>
<Setter Target="HomeOptionsBarAddingControl.(Grid.ColumnSpan)" Value="3"/>
<Setter Target="HomeOptionsBarSearchingStatusControl.(Grid.Row)" Value="0"/>
<Setter Target="HomeOptionsBarSearchingStatusControl.(Grid.Column)" Value="3"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Normal">
<VisualState.StateTriggers>
<AdaptiveTrigger x:Name="HomeNormalStateAdaptiveTrigger" MinWindowWidth="800"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="HomeOptionBarButtonsStackPanel.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="HomeOptionBarButtonsStackPanel.(Grid.Column)" Value="3"/>
<Setter Target="HomeOptionsBarAddingControl.(Grid.Row)" Value="1"/>
<Setter Target="HomeOptionsBarAddingControl.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="HomeOptionsBarSearchingStatusControl.(Grid.Row)" Value="1"/>
<Setter Target="HomeOptionsBarSearchingStatusControl.(Grid.Column)" Value="1"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition x:Name="HomeTasksListRow" Height="1*"/>
<RowDefinition x:Name="HomeOptionBarAndAddingPanelRow" Height="Auto"/>
</Grid.RowDefinitions>
<!-- VIDEOS LIST -->
<ScrollViewer Margin="0,0,0,10" CornerRadius="{ThemeResource ControlCornerRadius}">
<ContentControl x:Name="HomeTasksListParent" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/>
</ScrollViewer>
<!-- OPTIONS BAR AND ADDING PANEL -->
<Grid Grid.Row="1" CornerRadius="{ThemeResource ControlCornerRadius}" Background="{ThemeResource HomeBackgroundColor}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ContentControl x:Name="HomeAddingPanel" Grid.Row="0" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/> <!-- Adding Panel -->
<Grid x:Name="HomeOptionsBar" Grid.Row="1" Background="{ThemeResource HomeOptionBarBackgroundColor}"> <!-- Options Bar -->
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentPresenter x:Name="HomeOptionsBarAddingControl" Grid.Row="1" Grid.Column="0"/>
<ContentPresenter x:Name="HomeOptionsBarSearchingStatusControl" Grid.Row="1" Grid.Column="1"/>
<StackPanel x:Name="HomeOptionBarButtonsStackPanel" Grid.Row="1" Grid.Column="3" Margin="3,0,3,0" HorizontalAlignment="Center" Orientation="Horizontal">
<AppBarButton x:Name="HomeOptionsBarLoadSubscripionsButton" x:Uid="HomeOptionsBarLoadSubscripionsButton" Icon="Favorite" Width="120"/>
<AppBarSeparator VerticalAlignment="Center" Height="50"/>
<AppBarToggleButton x:Name="HomeOptionsBarPlaylistSearchButton" x:Uid="HomeOptionsBarPlaylistSearchButton" Icon="List" Width="85" Checked="HomeOptionsBarPlaylistSearchButton_Checked" Unchecked="HomeOptionsBarAddingButtons_Unchecked"/>
<AppBarToggleButton x:Name="HomeOptionsBarVideoSearchButton" x:Uid="HomeOptionsBarVideoSearchButton" Icon="Video" Width="75" Checked="HomeOptionsBarVideoSearchButton_Checked" Unchecked="HomeOptionsBarAddingButtons_Unchecked"/>
<AppBarSeparator VerticalAlignment="Center" Height="50"/>
<AppBarButton x:Name="HomeOptionsBarDownloadAllButton" x:Uid="HomeOptionsBarDownloadAllButton" Icon="Download" Width="90" Click="HomeOptionsBarDownloadAllButton_Click"/>
</StackPanel>
</Grid>
</Grid>
</Grid>
</Page>

View File

@@ -1,256 +0,0 @@
using Microsoft.Toolkit.Uwp.Connectivity;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.EventArgs;
using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Structs;
using VDownload.Views.Home.Controls;
using Windows.ApplicationModel.Resources;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;
namespace VDownload.Views.Home
{
public sealed partial class HomeMain : Page
{
#region CONSTRUCTORS
public HomeMain()
{
this.InitializeComponent();
// Set cancellation token
SearchingCancellationToken = new CancellationTokenSource();
}
#endregion
#region PROPERTIES
// CANCELLATON TOKEN
private CancellationTokenSource SearchingCancellationToken { get; set; }
// HOME TASKS LIST
private static ContentControl HomeTasksListCurrentParent = null;
private static StackPanel HomeTasksList = null;
public static List<HomeTaskPanel> TasksList = new List<HomeTaskPanel>();
#endregion
#region EVENT HANDLERS VOIDS
// ON NAVIGATED TO
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Set current panel
HomeTasksListCurrentParent = HomeTasksListParent;
// Detach task panels from old task list
if (HomeTasksList != null) HomeTasksList.Children.Clear();
// Create new task list
HomeTasksList = new StackPanel { Spacing = 10 };
// Attach task panels to new task list
if (TasksList.Count > 0)
{
foreach (HomeTaskPanel homeVideoPanel in TasksList) HomeTasksList.Children.Add(homeVideoPanel);
HomeTasksListCurrentParent.Content = HomeTasksList;
}
else
{
HomeTasksListCurrentParent.Content = new HomeTasksListPlaceholder();
}
}
// SEARCH VIDEO BUTTON CHECKED
private void HomeOptionsBarVideoSearchButton_Checked(object sender, RoutedEventArgs e)
{
// Uncheck playlist button
HomeOptionsBarPlaylistSearchButton.IsChecked = false;
// Create video adding control
HomeOptionsBarVideoSearch homeOptionsBarSearchVideoControl = new HomeOptionsBarVideoSearch();
homeOptionsBarSearchVideoControl.VideoSearchSuccessed += HomeOptionsBarVideoSearchControl_VideoSearchSuccessed;
homeOptionsBarSearchVideoControl.SearchButtonClick += (s, a) =>
{
SearchingCancellationToken.Cancel();
SearchingCancellationToken = new CancellationTokenSource();
homeOptionsBarSearchVideoControl.CancellationToken = SearchingCancellationToken.Token;
};
HomeOptionsBarAddingControl.Content = homeOptionsBarSearchVideoControl;
}
// VIDEO SEARCH SUCCESSED
private void HomeOptionsBarVideoSearchControl_VideoSearchSuccessed(object sender, VideoSearchSuccessedEventArgs e)
{
// Set UI
HomeOptionBarAndAddingPanelRow.Height = new GridLength(1, GridUnitType.Star);
HomeTasksListRow.Height = new GridLength(0);
// Open adding panel
HomeVideoAddingPanel addingPanel = new HomeVideoAddingPanel(e.Video);
addingPanel.TasksAddingRequested += HomeTasksAddingRequest;
HomeAddingPanel.Content = addingPanel;
}
// SEARCH PLAYLIST BUTTON CHECKED
private void HomeOptionsBarPlaylistSearchButton_Checked(object sender, RoutedEventArgs e)
{
// Uncheck video button
HomeOptionsBarVideoSearchButton.IsChecked = false;
// Create playlist adding control
HomeOptionsBarPlaylistSearch homeOptionsBarPlaylistSearchControl = new HomeOptionsBarPlaylistSearch();
homeOptionsBarPlaylistSearchControl.PlaylistSearchSuccessed += HomeOptionsBarPlaylistSearchControl_PlaylistSearchSuccessed;
homeOptionsBarPlaylistSearchControl.SearchButtonClick += (s, a) =>
{
SearchingCancellationToken.Cancel();
SearchingCancellationToken = new CancellationTokenSource();
homeOptionsBarPlaylistSearchControl.CancellationToken = SearchingCancellationToken.Token;
};
HomeOptionsBarAddingControl.Content = homeOptionsBarPlaylistSearchControl;
}
// PLAYLIST SEARCH SUCCESSED
private void HomeOptionsBarPlaylistSearchControl_PlaylistSearchSuccessed(object sender, PlaylistSearchSuccessedEventArgs e)
{
// Set UI
HomeOptionBarAndAddingPanelRow.Height = new GridLength(1, GridUnitType.Star);
HomeTasksListRow.Height = new GridLength(0);
// Open adding panel
HomePlaylistAddingPanel addingPanel = new HomePlaylistAddingPanel(e.PlaylistService);
addingPanel.TasksAddingRequested += HomeTasksAddingRequest;
HomeAddingPanel.Content = addingPanel;
}
// TASK ADDING REQUEST
private void HomeTasksAddingRequest(object sender, TasksAddingRequestedEventArgs e)
{
// Replace placeholder
HomeTasksListCurrentParent.Content = HomeTasksList;
// Uncheck button
switch (e.RequestSource)
{
case TaskAddingRequestSource.Video: HomeOptionsBarVideoSearchButton.IsChecked = false; break;
case TaskAddingRequestSource.Playlist: HomeOptionsBarPlaylistSearchButton.IsChecked = false; break;
}
// Create video tasks
foreach (TaskData taskData in e.TaskData)
{
HomeTaskPanel taskPanel = new HomeTaskPanel(taskData);
taskPanel.TaskRemovingRequested += (s, a) =>
{
// Remove task from tasks lists
TasksList.Remove(taskPanel);
HomeTasksList.Children.Remove(taskPanel);
if (TasksList.Count <= 0) HomeTasksListCurrentParent.Content = new HomeTasksListPlaceholder();
};
// Add task to tasks lists
HomeTasksList.Children.Add(taskPanel);
TasksList.Add(taskPanel);
}
}
// TASK ADDING CANCELLED
private void HomeSearchingCancelled()
{
// Cancel searching operations
SearchingCancellationToken.Cancel();
SearchingCancellationToken = new CancellationTokenSource();
// Set grid dimensions
HomeOptionBarAndAddingPanelRow.Height = GridLength.Auto;
HomeTasksListRow.Height = new GridLength(1, GridUnitType.Star);
// Clear panels
HomeAddingPanel.Content = null;
HomeOptionsBarAddingControl.Content = null;
HomeOptionsBarSearchingStatusControl.Content = null;
}
// ADDING BUTTONS UNCHECKED
private void HomeOptionsBarAddingButtons_Unchecked(object sender, RoutedEventArgs e)
{
HomeSearchingCancelled();
}
// DOWNLOAD ALL BUTTON CLICKED
private async void HomeOptionsBarDownloadAllButton_Click(object sender, RoutedEventArgs e)
{
HomeTaskPanel[] idleTasks = TasksList.Where((HomeTaskPanel video) => video.Status == Core.Enums.TaskStatus.Idle).ToArray();
if (idleTasks.Count() > 0)
{
bool delay = (bool)Config.GetValue("delay_task_when_queued_task_starts_on_metered_network");
if (NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)
{
ContentDialogResult dialogResult = await new ContentDialog
{
Title = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogTitle"),
Content = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogDescription"),
PrimaryButtonText = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogStartAndDelayText"),
SecondaryButtonText = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogStartWithoutDelayText"),
CloseButtonText = ResourceLoader.GetForCurrentView().GetString("HomeDownloadAllButtonMeteredConnectionDialogCancel"),
}.ShowAsync();
switch (dialogResult)
{
case ContentDialogResult.Primary: delay = true; break;
case ContentDialogResult.Secondary: delay = false; break;
case ContentDialogResult.None: return;
}
}
foreach (HomeTaskPanel videoPanel in idleTasks)
{
await Task.Delay(1);
#pragma warning disable CS4014
videoPanel.Start(delay);
#pragma warning restore CS4014
}
}
}
#endregion
#region METHODS
// WAIT IN QUEUE
public static async Task WaitInQueue(bool delayWhenOnMeteredConnection, CancellationToken token)
{
while ((TasksList.Where((HomeTaskPanel task) => task.Status == Core.Enums.TaskStatus.InProgress).Count() >= (int)Config.GetValue("max_active_video_task") || (delayWhenOnMeteredConnection && NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)) && !token.IsCancellationRequested)
{
await Task.Delay(100);
}
}
#endregion
}
}

View File

@@ -1,425 +0,0 @@
using Microsoft.Toolkit.Uwp.UI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using VDownload.Converters;
using VDownload.Core.Enums;
using VDownload.Core.EventArgs;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Structs;
using VDownload.Views.Home.Controls;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace VDownload.Views.Home
{
public sealed partial class HomePlaylistAddingPanel : UserControl
{
#region CONSTRUCTORS
public HomePlaylistAddingPanel(IPlaylist playlistService)
{
this.InitializeComponent();
// Set playlist service object
PlaylistService = playlistService;
}
#endregion
#region PROPERTIES
// BASE PLAYLIST DATA
private IPlaylist PlaylistService { get; set; }
// PLAYLIST DATA
private IconElement SourceImage { get; set; }
private string Name { get; set; }
// APPLY TO ALL OPTIONS
public StorageFolder ATLLocation { get; set; }
public double ATLSchedule { get; set; }
// DELETED VIDEOS
public List<HomeSerialAddingVideoPanel> DeletedVideos = new List<HomeSerialAddingVideoPanel>();
public List<HomeSerialAddingVideoPanel> HiddenVideos = new List<HomeSerialAddingVideoPanel>();
// FILTER MIN MAX
private long MinViews { get; set; }
private long MaxViews { get; set; }
private DateTime MinDate { get; set; }
private DateTime MaxDate { get; set; }
private TimeSpan MinDuration { get; set; }
private TimeSpan MaxDuration { get; set; }
#endregion
#region EVENT HANDLERS VOIDS
// ON CONTROL LOADING
private async void HomePlaylistAddingPanel_Loading(FrameworkElement sender, object args)
{
// Set metadata
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{PlaylistService.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
Name = PlaylistService.Name;
// Add videos to list and search mins and maxes
MinViews = PlaylistService.Videos[0].Metadata.Views;
MaxViews = PlaylistService.Videos[0].Metadata.Views;
MinDate = PlaylistService.Videos[0].Metadata.Date;
MaxDate = PlaylistService.Videos[0].Metadata.Date;
MinDuration = PlaylistService.Videos[0].Metadata.Duration;
MaxDuration = PlaylistService.Videos[0].Metadata.Duration;
foreach (IVideo video in PlaylistService.Videos)
{
// Set mins and maxes
if (video.Metadata.Views < MinViews) MinViews = video.Metadata.Views;
if (video.Metadata.Views > MaxViews) MaxViews = video.Metadata.Views;
if (video.Metadata.Date < MinDate) MinDate = video.Metadata.Date;
if (video.Metadata.Date > MaxDate) MaxDate = video.Metadata.Date;
if (video.Metadata.Duration < MinDuration) MinDuration = video.Metadata.Duration;
if (video.Metadata.Duration > MaxDuration) MaxDuration = video.Metadata.Duration;
// Add videos to list
HomeSerialAddingVideoPanel videoPanel = new HomeSerialAddingVideoPanel(video);
videoPanel.DeleteRequested += (s, a) =>
{
DeletedVideos.Add(videoPanel);
HomePlaylistAddingPanelFilterRemovedTextBlock.Visibility = Visibility.Visible;
HomePlaylistAddingPanelFilterRemovedCountTextBlock.Visibility = Visibility.Visible;
HomePlaylistAddingPanelFilterRemovedRestoreButton.Visibility = Visibility.Visible;
HomePlaylistAddingPanelFilterRemovedCountTextBlock.Text = $"{DeletedVideos.Count}";
HomePlaylistAddingPanelFilterHeaderCountTextBlock.Text = HiddenVideos.Count + DeletedVideos.Count > 0 ? $"{ResourceLoader.GetForCurrentView().GetString("HomePlaylistAddingPanelFilterHeaderCountTextBlockPrefix")}: {HiddenVideos.Count + DeletedVideos.Count}" : "";
HomePlaylistAddingPanelVideosList.Children.Remove(videoPanel);
};
HomePlaylistAddingPanelVideosList.Children.Add(videoPanel);
}
// Set apply to all location option
if (!(bool)Config.GetValue("custom_media_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("last_media_location"))
{
ATLLocation = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("last_media_location");
HomePlaylistAddingApplyToAllLocationSettingControl.Description = ATLLocation.Path;
}
else if ((bool)Config.GetValue("custom_media_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("custom_media_location"))
{
ATLLocation = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("selected_media_location");
HomePlaylistAddingApplyToAllLocationSettingControl.Description = ATLLocation.Path;
}
else
{
ATLLocation = null;
HomePlaylistAddingApplyToAllLocationSettingControl.Description = $@"{UserDataPaths.GetDefault().Downloads}\VDownload";
}
// Set apply to all schedule option
ATLSchedule = 0;
// Set title and author filter option
HomePlaylistAddingPanelFilterTitleTextBox.PlaceholderText = ResourceLoader.GetForCurrentView().GetString("HomePlaylistAddingPanelFilterTitleTextBoxPlaceholderText");
HomePlaylistAddingPanelFilterAuthorTextBox.PlaceholderText = ResourceLoader.GetForCurrentView().GetString("HomePlaylistAddingPanelFilterAuthorTextBoxPlaceholderText");
// Set views filter option
HomePlaylistAddingPanelFilterMinViewsNumberBox.Minimum = HomePlaylistAddingPanelFilterMaxViewsNumberBox.Minimum = HomePlaylistAddingPanelFilterMinViewsNumberBox.Value = MinViews;
HomePlaylistAddingPanelFilterMinViewsNumberBox.Maximum = HomePlaylistAddingPanelFilterMaxViewsNumberBox.Maximum = HomePlaylistAddingPanelFilterMaxViewsNumberBox.Value = MaxViews;
// Set date filter option
HomePlaylistAddingPanelFilterMinDateDatePicker.MinDate = HomePlaylistAddingPanelFilterMaxDateDatePicker.MinDate = (DateTimeOffset)(HomePlaylistAddingPanelFilterMinDateDatePicker.Date = MinDate);
HomePlaylistAddingPanelFilterMinDateDatePicker.MaxDate = HomePlaylistAddingPanelFilterMaxDateDatePicker.MaxDate = (DateTimeOffset)(HomePlaylistAddingPanelFilterMaxDateDatePicker.Date = MaxDate);
// Set duration filter option
TextBoxExtensions.SetMask(HomePlaylistAddingPanelFilterMinDurationTextBox, (string)new TimeSpanToTextBoxMaskConverter().Convert(MaxDuration, null, null, null));
TextBoxExtensions.SetMask(HomePlaylistAddingPanelFilterMaxDurationTextBox, (string)new TimeSpanToTextBoxMaskConverter().Convert(MaxDuration, null, null, null));
HashSet<int> maskElements = new HashSet<int>();
foreach (TimeSpan ts in new List<TimeSpan> { MinDuration, MaxDuration })
{
if (Math.Floor(ts.TotalHours) > 0) maskElements.Add(int.Parse(Math.Floor(ts.TotalHours).ToString()[0].ToString()));
if (Math.Floor(ts.TotalMinutes) > 0)
{
if (Math.Floor(ts.TotalHours) > 0) maskElements.Add(5);
else maskElements.Add(int.Parse(ts.Minutes.ToString()[0].ToString()));
}
if (Math.Floor(ts.TotalMinutes) > 0) maskElements.Add(5);
else maskElements.Add(int.Parse(ts.Seconds.ToString()[0].ToString()));
}
List<string> maskElementsString = new List<string>();
foreach (int i in maskElements)
{
if (i != 9) maskElementsString.Add($"{i}:[0-{i}]");
}
TextBoxExtensions.SetCustomMask(HomePlaylistAddingPanelFilterMinDurationTextBox, string.Join(',', maskElementsString));
TextBoxExtensions.SetCustomMask(HomePlaylistAddingPanelFilterMaxDurationTextBox, string.Join(',', maskElementsString));
HomePlaylistAddingPanelFilterMinDurationTextBox.Text = TimeSpanCustomFormat.ToOptTHMMBaseSS(MinDuration, MaxDuration);
HomePlaylistAddingPanelFilterMaxDurationTextBox.Text = TimeSpanCustomFormat.ToOptTHMMBaseSS(MaxDuration);
}
// FILTER CHANGED
private void FilterChanged()
{
// Min duration
string[] minSegments = HomePlaylistAddingPanelFilterMinDurationTextBox.Text.Split(':').Reverse().ToArray();
int.TryParse(minSegments.ElementAtOrDefault(0), out int minSeconds);
int.TryParse(minSegments.ElementAtOrDefault(1), out int minMinutes);
int.TryParse(minSegments.ElementAtOrDefault(2), out int minHours);
TimeSpan minDuration = new TimeSpan(minHours, minMinutes, minSeconds);
// Max duration
string[] maxSegments = HomePlaylistAddingPanelFilterMaxDurationTextBox.Text.Split(':').Reverse().ToArray();
int.TryParse(maxSegments.ElementAtOrDefault(0), out int maxSeconds);
int.TryParse(maxSegments.ElementAtOrDefault(1), out int maxMinutes);
int.TryParse(maxSegments.ElementAtOrDefault(2), out int maxHours);
TimeSpan maxDuration = new TimeSpan(maxHours, maxMinutes, maxSeconds);
// Title and author regex
Regex titleRegex = new Regex("");
Regex authorRegex = new Regex("");
try
{
titleRegex = new Regex(HomePlaylistAddingPanelFilterTitleTextBox.Text);
authorRegex = new Regex(HomePlaylistAddingPanelFilterAuthorTextBox.Text);
}
catch (ArgumentException) { }
List<HomeSerialAddingVideoPanel> allVideos = new List<HomeSerialAddingVideoPanel>();
foreach (HomeSerialAddingVideoPanel videoPanel in HomePlaylistAddingPanelVideosList.Children) allVideos.Add(videoPanel);
foreach (HomeSerialAddingVideoPanel videoPanel in HiddenVideos) allVideos.Add(videoPanel);
HomePlaylistAddingPanelVideosList.Children.Clear();
HiddenVideos.Clear();
foreach (HomeSerialAddingVideoPanel videoPanel in allVideos)
{
if (
!titleRegex.IsMatch(videoPanel.VideoService.Metadata.Title) ||
!authorRegex.IsMatch(videoPanel.VideoService.Metadata.Author) ||
HomePlaylistAddingPanelFilterMinViewsNumberBox.Value > videoPanel.VideoService.Metadata.Views ||
HomePlaylistAddingPanelFilterMaxViewsNumberBox.Value < videoPanel.VideoService.Metadata.Views ||
HomePlaylistAddingPanelFilterMinDateDatePicker.Date > videoPanel.VideoService.Metadata.Date ||
HomePlaylistAddingPanelFilterMaxDateDatePicker.Date < videoPanel.VideoService.Metadata.Date ||
minDuration > videoPanel.VideoService.Metadata.Duration ||
maxDuration < videoPanel.VideoService.Metadata.Duration
) HiddenVideos.Add(videoPanel);
else HomePlaylistAddingPanelVideosList.Children.Add(videoPanel);
}
HomePlaylistAddingPanelFilterHeaderCountTextBlock.Text = HiddenVideos.Count + DeletedVideos.Count > 0 ? $"{ResourceLoader.GetForCurrentView().GetString("HomePlaylistAddingPanelFilterHeaderCountTextBlockPrefix")}: {HiddenVideos.Count + DeletedVideos.Count}" : "";
}
// ATL LOCATION BROWSE BUTTON CLICKED
private async void HomePlaylistAddingApplyToAllLocationBrowseButton_Click(object sender, RoutedEventArgs e)
{
FolderPicker picker = new FolderPicker
{
SuggestedStartLocation = PickerLocationId.Downloads
};
picker.FileTypeFilter.Add("*");
StorageFolder selectedFolder = await picker.PickSingleFolderAsync();
if (selectedFolder != null)
{
try
{
await(await selectedFolder.CreateFileAsync("VDownloadLocationAccessTest")).DeleteAsync();
StorageApplicationPermissions.FutureAccessList.AddOrReplace("last_media_location", selectedFolder);
ATLLocation = selectedFolder;
HomePlaylistAddingApplyToAllLocationSettingControl.Description = ATLLocation.Path;
}
catch (UnauthorizedAccessException) { }
}
}
// APPLY ATL LOCATION BUTTON CLICKED
private void HomePlaylistAddingApplyToAllApplyLocationButton_Click(object sender, RoutedEventArgs e)
{
foreach (HomeSerialAddingVideoPanel videoPanel in HomePlaylistAddingPanelVideosList.Children)
{
videoPanel.HomeVideoAddingOptionsControl.ChangeLocation(ATLLocation);
}
foreach (HomeSerialAddingVideoPanel videoPanel in DeletedVideos)
{
videoPanel.HomeVideoAddingOptionsControl.ChangeLocation(ATLLocation);
}
foreach (HomeSerialAddingVideoPanel videoPanel in HiddenVideos)
{
videoPanel.HomeVideoAddingOptionsControl.ChangeLocation(ATLLocation);
}
}
// ATL SCHEDULE NUMBERBOX VALUE CHANGED
private void HomePlaylistAddingApplyToAllScheduleNumberBox_LostFocus(object sender, RoutedEventArgs e)
{
if (double.IsNaN(HomePlaylistAddingApplyToAllScheduleNumberBox.Value)) HomePlaylistAddingApplyToAllScheduleNumberBox.Value = ATLSchedule;
else ATLSchedule = HomePlaylistAddingApplyToAllScheduleNumberBox.Value;
}
// APPLY ATL SCHEDULE BUTTON CLICKED
private void HomePlaylistAddingApplyToAllApplyScheduleButton_Click(object sender, RoutedEventArgs e)
{
foreach (HomeSerialAddingVideoPanel videoPanel in HomePlaylistAddingPanelVideosList.Children)
{
videoPanel.HomeVideoAddingOptionsControl.ChangeSchedule(ATLSchedule);
}
foreach (HomeSerialAddingVideoPanel videoPanel in DeletedVideos)
{
videoPanel.HomeVideoAddingOptionsControl.ChangeSchedule(ATLSchedule);
}
foreach (HomeSerialAddingVideoPanel videoPanel in HiddenVideos)
{
videoPanel.HomeVideoAddingOptionsControl.ChangeSchedule(ATLSchedule);
}
}
// TITLE AND AUTHOR FILTERS TEXTBOXS TEXT CHANGED
private void HomePlaylistAddingPanelFilterTitleAndAuthorTextBox_LostFocus(object sender, RoutedEventArgs e)
{
FilterChanged();
}
// MIN VIEWS FILTERS NUMBERBOX VALUE CHANGED
private void HomePlaylistAddingPanelFilterMinViewsNumberBox_LostFocus(object sender, RoutedEventArgs e)
{
if (double.IsNaN(HomePlaylistAddingPanelFilterMinViewsNumberBox.Value)) HomePlaylistAddingPanelFilterMinViewsNumberBox.Value = MinViews;
FilterChanged();
}
// MAX VIEWS FILTERS NUMBERBOX VALUE CHANGED
private void HomePlaylistAddingPanelFilterMaxViewsNumberBox_LostFocus(object sender, RoutedEventArgs e)
{
if (double.IsNaN(HomePlaylistAddingPanelFilterMaxViewsNumberBox.Value)) HomePlaylistAddingPanelFilterMaxViewsNumberBox.Value = MaxViews;
FilterChanged();
}
// MIN AND MAX DATE FILTERS DATEPICKER VALUE CHANGED
private void HomePlaylistAddingPanelFilterMinAndMaxDateDatePicker_LostFocus(object sender, RoutedEventArgs args)
{
FilterChanged();
}
// MIN DURATION FILTERS TEXTBOX VALUE CHANGED
private void HomePlaylistAddingPanelFilterMinDurationTextBox_LostFocus(object sender, RoutedEventArgs e)
{
if (!HomePlaylistAddingPanelFilterMinDurationTextBox.Text.Contains('_'))
{
string[] segments = HomePlaylistAddingPanelFilterMinDurationTextBox.Text.Split(':').Reverse().ToArray();
int.TryParse(segments.ElementAtOrDefault(0), out int seconds);
int.TryParse(segments.ElementAtOrDefault(1), out int minutes);
int.TryParse(segments.ElementAtOrDefault(2), out int hours);
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
if (parsedTimeSpan < MinDuration || parsedTimeSpan > MaxDuration)
{
HomePlaylistAddingPanelFilterMinDurationTextBox.Text = TimeSpanCustomFormat.ToOptTHMMBaseSS(MinDuration, MaxDuration);
}
}
else
{
HomePlaylistAddingPanelFilterMinDurationTextBox.Text = TimeSpanCustomFormat.ToOptTHMMBaseSS(MinDuration, MaxDuration);
}
FilterChanged();
}
// MAX DURATION FILTERS TEXTBOX VALUE CHANGED
private void HomePlaylistAddingPanelFilterMaxDurationTextBox_LostFocus(object sender, RoutedEventArgs e)
{
if (!HomePlaylistAddingPanelFilterMaxDurationTextBox.Text.Contains('_'))
{
string[] segments = HomePlaylistAddingPanelFilterMaxDurationTextBox.Text.Split(':').Reverse().ToArray();
int.TryParse(segments.ElementAtOrDefault(0), out int seconds);
int.TryParse(segments.ElementAtOrDefault(1), out int minutes);
int.TryParse(segments.ElementAtOrDefault(2), out int hours);
TimeSpan parsedTimeSpan = new TimeSpan(hours, minutes, seconds);
if (parsedTimeSpan < MinDuration || parsedTimeSpan > MaxDuration)
{
HomePlaylistAddingPanelFilterMaxDurationTextBox.Text = TimeSpanCustomFormat.ToOptTHMMBaseSS(MaxDuration);
}
}
else
{
HomePlaylistAddingPanelFilterMaxDurationTextBox.Text = TimeSpanCustomFormat.ToOptTHMMBaseSS(MaxDuration);
}
FilterChanged();
}
// RESTORE REMOVED VIDEOS BUTTON CLICKED
private void HomePlaylistAddingPanelFilterRemovedRestoreButton_Click(object sender, RoutedEventArgs e)
{
foreach (HomeSerialAddingVideoPanel videoPanel in DeletedVideos)
{
HomePlaylistAddingPanelVideosList.Children.Add(videoPanel);
}
HomePlaylistAddingPanelFilterRemovedTextBlock.Visibility = Visibility.Collapsed;
HomePlaylistAddingPanelFilterRemovedCountTextBlock.Visibility = Visibility.Collapsed;
HomePlaylistAddingPanelFilterRemovedRestoreButton.Visibility = Visibility.Collapsed;
HomePlaylistAddingPanelFilterHeaderCountTextBlock.Text = HiddenVideos.Count + DeletedVideos.Count > 0 ? $"{ResourceLoader.GetForCurrentView().GetString("HomePlaylistAddingPanelFilterHeaderCountTextBlockPrefix")}: {HiddenVideos.Count + DeletedVideos.Count}" : "";
DeletedVideos.Clear();
}
// SOURCE BUTTON CLICKED
private async void HomePlaylistAddingPanelSourceButton_Click(object sender, RoutedEventArgs e)
{
// Launch the website
await Windows.System.Launcher.LaunchUriAsync(PlaylistService.Url);
}
// ADD BUTTON CLICKED
private void HomePlaylistAddingPanelAddButton_Click(object sender, RoutedEventArgs e)
{
// Pack tasks data
List<TaskData> taskDataList = new List<TaskData>();
foreach (HomeSerialAddingVideoPanel videoPanel in HomePlaylistAddingPanelVideosList.Children)
{
TaskData taskData = new TaskData
{
VideoService = videoPanel.VideoService,
TaskOptions = new TaskOptions()
{
MediaType = videoPanel.HomeVideoAddingOptionsControl.MediaType,
Stream = videoPanel.HomeVideoAddingOptionsControl.Stream,
TrimStart = videoPanel.HomeVideoAddingOptionsControl.TrimStart,
TrimEnd = videoPanel.HomeVideoAddingOptionsControl.TrimEnd,
Filename = videoPanel.HomeVideoAddingOptionsControl.Filename,
Extension = videoPanel.HomeVideoAddingOptionsControl.Extension,
Location = videoPanel.HomeVideoAddingOptionsControl.Location,
Schedule = videoPanel.HomeVideoAddingOptionsControl.Schedule,
}
};
taskDataList.Add(taskData);
}
// Request tasks adding
TasksAddingRequestedEventArgs eventArgs = new TasksAddingRequestedEventArgs
{
TaskData = taskDataList.ToArray(),
RequestSource = TaskAddingRequestSource.Playlist
};
TasksAddingRequested?.Invoke(this, eventArgs);
}
#endregion
#region EVENT HANDLERS
public event EventHandler<TasksAddingRequestedEventArgs> TasksAddingRequested;
#endregion
}
}

View File

@@ -1,206 +0,0 @@
<UserControl
x:Class="VDownload.Views.Home.HomeVideoAddingPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.Home"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ex="using:Microsoft.Toolkit.Uwp.UI"
xmlns:cc="using:VDownload.Controls"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
Loading="HomeVideoAddingPanel_Loading"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///Resources/Icons.xaml"/>
<ResourceDictionary Source="ms-appx:///Resources/Converters.xaml"/>
</ResourceDictionary.MergedDictionaries>
<x:String x:Key="MetadataIconSize">16</x:String>
<x:String x:Key="MetadataTextSize">12</x:String>
</ResourceDictionary>
</UserControl.Resources>
<Grid Padding="10" RowSpacing="40" VerticalAlignment="Stretch">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="HomeVideoAddingPanelThumbnailImage.(Grid.Column)" Value="0"/>
<Setter Target="HomeVideoAddingPanelThumbnailImage.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelThumbnailImage.(Grid.Row)" Value="0"/>
<Setter Target="HomeVideoAddingPanelThumbnailImage.(Grid.RowSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelThumbnailImage.Width" Value="NaN"/>
<Setter Target="HomeVideoAddingPanelTitleAndDetailedMetadataGrid.(Grid.Column)" Value="0"/>
<Setter Target="HomeVideoAddingPanelTitleAndDetailedMetadataGrid.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelTitleAndDetailedMetadataGrid.(Grid.Row)" Value="1"/>
<Setter Target="HomeVideoAddingPanelTitleAndDetailedMetadataGrid.(Grid.RowSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelAuthorIcon.(Grid.Row)" Value="0"/>
<Setter Target="HomeVideoAddingPanelAuthorIcon.(Grid.RowSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelAuthorIcon.(Grid.Column)" Value="0"/>
<Setter Target="HomeVideoAddingPanelAuthorIcon.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelAuthorText.(Grid.Row)" Value="2"/>
<Setter Target="HomeVideoAddingPanelAuthorText.(Grid.RowSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelAuthorText.(Grid.Column)" Value="0"/>
<Setter Target="HomeVideoAddingPanelAuthorText.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelAuthorText.HorizontalAlignment" Value="Center"/>
<Setter Target="HomeVideoAddingPanelViewsIcon.(Grid.Row)" Value="0"/>
<Setter Target="HomeVideoAddingPanelViewsIcon.(Grid.RowSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelViewsIcon.(Grid.Column)" Value="1"/>
<Setter Target="HomeVideoAddingPanelViewsIcon.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelViewsText.(Grid.Row)" Value="2"/>
<Setter Target="HomeVideoAddingPanelViewsText.(Grid.RowSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelViewsText.(Grid.Column)" Value="1"/>
<Setter Target="HomeVideoAddingPanelViewsText.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelViewsText.HorizontalAlignment" Value="Center"/>
<Setter Target="HomeVideoAddingPanelDateIcon.(Grid.Row)" Value="0"/>
<Setter Target="HomeVideoAddingPanelDateIcon.(Grid.RowSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelDateIcon.(Grid.Column)" Value="2"/>
<Setter Target="HomeVideoAddingPanelDateIcon.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelDateText.(Grid.Row)" Value="2"/>
<Setter Target="HomeVideoAddingPanelDateText.(Grid.RowSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelDateText.(Grid.Column)" Value="2"/>
<Setter Target="HomeVideoAddingPanelDateText.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelDateText.HorizontalAlignment" Value="Center"/>
<Setter Target="HomeVideoAddingPanelDurationIcon.(Grid.Row)" Value="0"/>
<Setter Target="HomeVideoAddingPanelDurationIcon.(Grid.RowSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelDurationIcon.(Grid.Column)" Value="3"/>
<Setter Target="HomeVideoAddingPanelDurationIcon.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelDurationText.(Grid.Row)" Value="2"/>
<Setter Target="HomeVideoAddingPanelDurationText.(Grid.RowSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelDurationText.(Grid.Column)" Value="3"/>
<Setter Target="HomeVideoAddingPanelDurationText.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelDurationText.HorizontalAlignment" Value="Center"/>
<Setter Target="HomeVideoAddingPanelDMetadataR1.Height" Value="Auto"/>
<Setter Target="HomeVideoAddingPanelDMetadataR2.Height" Value="Auto"/>
<Setter Target="HomeVideoAddingPanelDMetadataC1.Width" Value="1*"/>
<Setter Target="HomeVideoAddingPanelDMetadataC2.Width" Value="1*"/>
</VisualState.Setters>
</VisualState>
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="600"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="HomeVideoAddingPanelThumbnailImage.(Grid.Column)" Value="0"/>
<Setter Target="HomeVideoAddingPanelThumbnailImage.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelThumbnailImage.(Grid.Row)" Value="0"/>
<Setter Target="HomeVideoAddingPanelThumbnailImage.(Grid.RowSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelThumbnailImage.Width" Value="250"/>
<Setter Target="HomeVideoAddingPanelTitleAndDetailedMetadataGrid.(Grid.Column)" Value="1"/>
<Setter Target="HomeVideoAddingPanelTitleAndDetailedMetadataGrid.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelTitleAndDetailedMetadataGrid.(Grid.Row)" Value="0"/>
<Setter Target="HomeVideoAddingPanelTitleAndDetailedMetadataGrid.(Grid.RowSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelAuthorIcon.(Grid.Row)" Value="0"/>
<Setter Target="HomeVideoAddingPanelAuthorIcon.(Grid.RowSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelAuthorIcon.(Grid.Column)" Value="0"/>
<Setter Target="HomeVideoAddingPanelAuthorIcon.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelAuthorText.(Grid.Row)" Value="0"/>
<Setter Target="HomeVideoAddingPanelAuthorText.(Grid.RowSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelAuthorText.(Grid.Column)" Value="2"/>
<Setter Target="HomeVideoAddingPanelAuthorText.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelAuthorText.HorizontalAlignment" Value="Left"/>
<Setter Target="HomeVideoAddingPanelViewsIcon.(Grid.Row)" Value="1"/>
<Setter Target="HomeVideoAddingPanelViewsIcon.(Grid.RowSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelViewsIcon.(Grid.Column)" Value="0"/>
<Setter Target="HomeVideoAddingPanelViewsIcon.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelViewsText.(Grid.Row)" Value="1"/>
<Setter Target="HomeVideoAddingPanelViewsText.(Grid.RowSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelViewsText.(Grid.Column)" Value="2"/>
<Setter Target="HomeVideoAddingPanelViewsText.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelViewsText.HorizontalAlignment" Value="Left"/>
<Setter Target="HomeVideoAddingPanelDateIcon.(Grid.Row)" Value="2"/>
<Setter Target="HomeVideoAddingPanelDateIcon.(Grid.RowSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelDateIcon.(Grid.Column)" Value="0"/>
<Setter Target="HomeVideoAddingPanelDateIcon.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelDateText.(Grid.Row)" Value="2"/>
<Setter Target="HomeVideoAddingPanelDateText.(Grid.RowSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelDateText.(Grid.Column)" Value="2"/>
<Setter Target="HomeVideoAddingPanelDateText.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelDateText.HorizontalAlignment" Value="Left"/>
<Setter Target="HomeVideoAddingPanelDurationIcon.(Grid.Row)" Value="3"/>
<Setter Target="HomeVideoAddingPanelDurationIcon.(Grid.RowSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelDurationIcon.(Grid.Column)" Value="0"/>
<Setter Target="HomeVideoAddingPanelDurationIcon.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelDurationText.(Grid.Row)" Value="3"/>
<Setter Target="HomeVideoAddingPanelDurationText.(Grid.RowSpan)" Value="1"/>
<Setter Target="HomeVideoAddingPanelDurationText.(Grid.Column)" Value="2"/>
<Setter Target="HomeVideoAddingPanelDurationText.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="HomeVideoAddingPanelDurationText.HorizontalAlignment" Value="Left"/>
<Setter Target="HomeVideoAddingPanelDMetadataR1.Height" Value="1*"/>
<Setter Target="HomeVideoAddingPanelDMetadataR2.Height" Value="1*"/>
<Setter Target="HomeVideoAddingPanelDMetadataC1.Width" Value="Auto"/>
<Setter Target="HomeVideoAddingPanelDMetadataC2.Width" Value="Auto"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- METADATA PANEL -->
<Grid Grid.Row="0" ColumnSpacing="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image x:Name="HomeVideoAddingPanelThumbnailImage" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="2" Source="{x:Bind ThumbnailImage}"/> <!-- Thumbnail image -->
<Grid x:Name="HomeVideoAddingPanelTitleAndDetailedMetadataGrid" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" FontSize="18" VerticalAlignment="Center" FontWeight="SemiBold" Text="{x:Bind Title}"/>
<AppBarButton Grid.Column="1" Width="40" Height="48" Icon="{x:Bind SourceImage}" Click="HomeVideoAddingPanelSourceButton_Click"/>
<AppBarButton Grid.Column="2" Width="40" Height="48" Icon="Add" Click="HomeVideoAddingPanelAddButton_Click"/>
</Grid>
<Grid Grid.Row="1" ColumnSpacing="10" RowSpacing="5">
<Grid.RowDefinitions>
<RowDefinition x:Name="HomeVideoAddingPanelDMetadataR1" Height="1*"/>
<RowDefinition x:Name="HomeVideoAddingPanelDMetadataR2" Height="1*"/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="HomeVideoAddingPanelDMetadataC1" Width="Auto"/>
<ColumnDefinition x:Name="HomeVideoAddingPanelDMetadataC2" Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image x:Name="HomeVideoAddingPanelAuthorIcon" Grid.Row="0" Grid.RowSpan="1" Grid.Column="0" Grid.ColumnSpan="2" Width="{StaticResource MetadataIconSize}" Source="{ThemeResource AuthorIcon}"/>
<TextBlock x:Name="HomeVideoAddingPanelAuthorText" Grid.Row="0" Grid.RowSpan="1" Grid.Column="2" Grid.ColumnSpan="2" HorizontalAlignment="Left" FontSize="{StaticResource MetadataTextSize}" Text="{x:Bind Author}"/>
<Image x:Name="HomeVideoAddingPanelViewsIcon" Grid.Row="1" Grid.RowSpan="1" Grid.Column="0" Grid.ColumnSpan="2" Width="{StaticResource MetadataIconSize}" Source="{ThemeResource ViewsIcon}"/>
<TextBlock x:Name="HomeVideoAddingPanelViewsText" Grid.Row="1" Grid.RowSpan="1" Grid.Column="2" Grid.ColumnSpan="2" HorizontalAlignment="Left" FontSize="{StaticResource MetadataTextSize}" Text="{x:Bind Views}"/>
<Image x:Name="HomeVideoAddingPanelDateIcon" Grid.Row="2" Grid.RowSpan="1" Grid.Column="0" Grid.ColumnSpan="2" Width="{StaticResource MetadataIconSize}" Source="{ThemeResource DateIcon}"/>
<TextBlock x:Name="HomeVideoAddingPanelDateText" Grid.Row="2" Grid.RowSpan="1" Grid.Column="2" Grid.ColumnSpan="2" HorizontalAlignment="Left" FontSize="{StaticResource MetadataTextSize}" Text="{x:Bind Date}"/>
<Image x:Name="HomeVideoAddingPanelDurationIcon" Grid.Row="3" Grid.RowSpan="1" Grid.Column="0" Grid.ColumnSpan="2" Width="{StaticResource MetadataIconSize}" Source="{ThemeResource DurationIcon}"/>
<TextBlock x:Name="HomeVideoAddingPanelDurationText" Grid.Row="3" Grid.RowSpan="1" Grid.Column="2" Grid.ColumnSpan="2" HorizontalAlignment="Left" FontSize="{StaticResource MetadataTextSize}" Text="{x:Bind Duration}"/>
</Grid>
</Grid>
</Grid>
<!-- Options PANEL -->
<ScrollViewer Grid.Row="1">
<ContentControl x:Name="HomeVideoAddingOptionsControlParent" HorizontalContentAlignment="Stretch"/>
</ScrollViewer>
</Grid>
</UserControl>

View File

@@ -1,136 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.EventArgs;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Structs;
using VDownload.Views.Home.Controls;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
namespace VDownload.Views.Home
{
public sealed partial class HomeVideoAddingPanel : UserControl
{
#region CONSTANTS
private readonly ResourceDictionary ImagesRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Images.xaml") };
#endregion
#region CONSTRUCTORS
public HomeVideoAddingPanel(IVideo videoService)
{
this.InitializeComponent();
// Set video service object
VideoService = videoService;
// Set metadata
ThumbnailImage = VideoService.Metadata.Thumbnail != null ? new BitmapImage { UriSource = VideoService.Metadata.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"];
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{VideoService.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
Title = VideoService.Metadata.Title;
Author = VideoService.Metadata.Author;
Views = VideoService.Metadata.Views.ToString();
Date = VideoService.Metadata.Date.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortDatePattern);
Duration = TimeSpanCustomFormat.ToOptTHBaseMMSS(VideoService.Metadata.Duration);
// Set video options control
HomeVideoAddingOptionsControl = new HomeAddingVideoOptions(VideoService);
}
#endregion
#region PROPERTIES
// BASE VIDEO DATA
private IVideo VideoService { get; set; }
// VIDEO DATA
private ImageSource ThumbnailImage { get; set; }
private IconElement SourceImage { get; set; }
private string Title { get; set; }
private string Author { get; set; }
private string Views { get; set; }
private string Date { get; set; }
private string Duration { get; set; }
// OPTIONS CONTROL
private HomeAddingVideoOptions HomeVideoAddingOptionsControl { get; set; }
#endregion
#region EVENT HANDLERS VOIDS
private async void HomeVideoAddingPanel_Loading(FrameworkElement sender, object args)
{
await HomeVideoAddingOptionsControl.Init();
HomeVideoAddingOptionsControlParent.Content = HomeVideoAddingOptionsControl;
}
// SOURCE BUTTON CLICKED
public async void HomeVideoAddingPanelSourceButton_Click(object sender, RoutedEventArgs e)
{
// Launch the website
await Windows.System.Launcher.LaunchUriAsync(VideoService.Url);
}
// ADD BUTTON CLICKED
public void HomeVideoAddingPanelAddButton_Click(object sender, RoutedEventArgs e)
{
// Pack task data
TaskData taskData = new TaskData
{
VideoService = VideoService,
TaskOptions = new TaskOptions()
{
MediaType = HomeVideoAddingOptionsControl.MediaType,
Stream = HomeVideoAddingOptionsControl.Stream,
TrimStart = HomeVideoAddingOptionsControl.TrimStart,
TrimEnd = HomeVideoAddingOptionsControl.TrimEnd,
Filename = HomeVideoAddingOptionsControl.Filename,
Extension = HomeVideoAddingOptionsControl.Extension,
Location = HomeVideoAddingOptionsControl.Location,
Schedule = HomeVideoAddingOptionsControl.Schedule,
}
};
// Request task adding
TasksAddingRequestedEventArgs eventArgs = new TasksAddingRequestedEventArgs
{
TaskData = new TaskData[] { taskData },
RequestSource = TaskAddingRequestSource.Video
};
TasksAddingRequested?.Invoke(this, eventArgs);
}
#endregion
#region EVENT HANDLERS
public event EventHandler<TasksAddingRequestedEventArgs> TasksAddingRequested;
#endregion
}
}

View File

@@ -0,0 +1,109 @@
<Page
x:Class="VDownload.Views.Home.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.Home"
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"
xmlns:cc="using:VDownload.Controls"
mc:Ignorable="d"
Background="Transparent">
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///Resources/Icons.xaml"/>
<ResourceDictionary Source="ms-appx:///Resources/Colors.xaml"/>
<ResourceDictionary Source="ms-appx:///Resources/Images.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Page.Resources>
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Compact">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="OptionsBarButtonsStackPanel.(Grid.ColumnSpan)" Value="4"/>
<Setter Target="OptionsBarButtonsStackPanel.(Grid.Column)" Value="0"/>
<Setter Target="SearchControlPresenter.(Grid.Row)" Value="0"/>
<Setter Target="SearchControlPresenter.(Grid.ColumnSpan)" Value="3"/>
<Setter Target="OptionsBarSearchingStatusControl.(Grid.Row)" Value="0"/>
<Setter Target="OptionsBarSearchingStatusControl.(Grid.Column)" Value="3"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Normal">
<VisualState.StateTriggers>
<AdaptiveTrigger x:Name="NormalStateAdaptiveTrigger" MinWindowWidth="800"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="OptionsBarButtonsStackPanel.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="OptionsBarButtonsStackPanel.(Grid.Column)" Value="3"/>
<Setter Target="SearchControlPresenter.(Grid.Row)" Value="1"/>
<Setter Target="SearchControlPresenter.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="SearchingStatusControlPresenter.(Grid.Row)" Value="1"/>
<Setter Target="SearchingStatusControlPresenter.(Grid.Column)" Value="1"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition x:Name="DownloadTaskControlsStackPanelRow" Height="1*"/>
<RowDefinition x:Name="OptionsBarAndAddingPanelRow" Height="Auto"/>
</Grid.RowDefinitions>
<ScrollViewer Margin="0,0,0,10" CornerRadius="{ThemeResource ControlCornerRadius}">
<cc:PlaceholderableStackPanel x:Name="DownloadTaskControlsStackPanel" Spacing="10">
<cc:PlaceholderableStackPanel.Placeholder>
<Viewbox Width="250">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Source="{StaticResource HomeTasksListPlaceholderImage}" Width="120"/>
<TextBlock Grid.Row="1" x:Uid="Home_DownloadTaskControlsStackPanel_Placeholder_TextBlock" Foreground="{StaticResource HomeTasksListPlaceholderTextColor}"/>
</Grid>
</Viewbox>
</cc:PlaceholderableStackPanel.Placeholder>
</cc:PlaceholderableStackPanel>
</ScrollViewer>
<Grid Grid.Row="1" CornerRadius="{ThemeResource ControlCornerRadius}" Background="{ThemeResource HomeBackgroundColor}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ContentPresenter x:Name="AddingPanelPresenter" Grid.Row="0" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/>
<Grid x:Name="OptionsBar" Grid.Row="1" Background="{ThemeResource HomeOptionBarBackgroundColor}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentPresenter x:Name="SearchControlPresenter" Grid.Row="1" Grid.Column="0"/>
<ContentPresenter x:Name="SearchingStatusControlPresenter" Grid.Row="1" Grid.Column="1"/>
<StackPanel x:Name="OptionsBarButtonsStackPanel" Grid.Row="1" Grid.Column="3" Margin="3,0,3,0" HorizontalAlignment="Center" Orientation="Horizontal">
<AppBarToggleButton x:Name="LoadSubscripionsButton" x:Uid="Home_OptionsBar_LoadSubscripionsButton" Icon="Favorite" Width="120" Checked="LoadSubscripionsButton_Checked" Unchecked="AddingButtons_Unchecked"/>
<AppBarSeparator VerticalAlignment="Center" Height="50"/>
<AppBarToggleButton x:Name="PlaylistSearchButton" x:Uid="Home_OptionsBar_PlaylistSearchButton" Icon="List" Width="85" Checked="PlaylistSearchButton_Checked" Unchecked="AddingButtons_Unchecked"/>
<AppBarToggleButton x:Name="VideoSearchButton" x:Uid="Home_OptionsBar_VideoSearchButton" Icon="Video" Width="75" Checked="VideoSearchButton_Checked" Unchecked="AddingButtons_Unchecked"/>
<AppBarSeparator VerticalAlignment="Center" Height="50"/>
<AppBarButton x:Name="DownloadAllButton" x:Uid="Home_OptionsBar_DownloadAllButton" Icon="Download" Width="90" Click="DownloadAllButton_Click"/>
</StackPanel>
</Grid>
</Grid>
</Grid>
</Page>

View File

@@ -0,0 +1,207 @@
using Microsoft.Toolkit.Uwp.Connectivity;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.EventArgs;
using VDownload.Core.Exceptions;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Structs;
using VDownload.Views.Home.Controls;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;
namespace VDownload.Views.Home
{
public sealed partial class MainPage : Page
{
#region CONSTRUCTORS
public MainPage()
{
this.InitializeComponent();
SearchingCancellationToken = new CancellationTokenSource();
}
#endregion
#region PROPERTIES
private CancellationTokenSource SearchingCancellationToken { get; set; }
#endregion
#region EVENT HANDLERS
protected override void OnNavigatedTo(NavigationEventArgs e)
{
foreach (DownloadTask downloadTask in DownloadTasksCollectionManagement.DownloadTasksCollection.Values)
{
DownloadTaskControl downloadTaskControl = new DownloadTaskControl(downloadTask);
downloadTaskControl.RemovingRequested += (s, a) =>
{
DownloadTaskControlsStackPanel.Remove(downloadTaskControl);
DownloadTasksCollectionManagement.Remove(downloadTask.Id);
};
DownloadTaskControlsStackPanel.Add(downloadTaskControl);
}
}
private void VideoSearchButton_Checked(object sender, RoutedEventArgs e)
{
PlaylistSearchButton.IsChecked = false;
LoadSubscripionsButton.IsChecked = false;
VideoSearchControl videoSearchControl = new VideoSearchControl();
videoSearchControl.SearchingSuccessed += (s, a) =>
{
OptionsBarAndAddingPanelRow.Height = new GridLength(1, GridUnitType.Star);
DownloadTaskControlsStackPanelRow.Height = new GridLength(0);
VideoAddingPanel videoAddingPanel = new VideoAddingPanel(a.Video);
videoAddingPanel.TasksAddingRequested += DownloadTasksAddingRequest;
AddingPanelPresenter.Content = videoAddingPanel;
};
videoSearchControl.SearchButtonClicked += (s, a) =>
{
SearchingCancellationToken.Cancel();
SearchingCancellationToken = new CancellationTokenSource();
videoSearchControl.CancellationToken = SearchingCancellationToken.Token;
};
SearchControlPresenter.Content = videoSearchControl;
}
private void PlaylistSearchButton_Checked(object sender, RoutedEventArgs e)
{
VideoSearchButton.IsChecked = false;
LoadSubscripionsButton.IsChecked = false;
PlaylistSearchControl playlistSearchControl = new PlaylistSearchControl();
playlistSearchControl.SearchingSuccessed += (s, a) =>
{
OptionsBarAndAddingPanelRow.Height = new GridLength(1, GridUnitType.Star);
DownloadTaskControlsStackPanelRow.Height = new GridLength(0);
PlaylistAddingPanel playlistAddingPanel = new PlaylistAddingPanel(a.Playlist);
playlistAddingPanel.TasksAddingRequested += DownloadTasksAddingRequest;
AddingPanelPresenter.Content = playlistAddingPanel;
};
playlistSearchControl.SearchButtonClicked += (s, a) =>
{
SearchingCancellationToken.Cancel();
SearchingCancellationToken = new CancellationTokenSource();
playlistSearchControl.CancellationToken = SearchingCancellationToken.Token;
};
SearchControlPresenter.Content = playlistSearchControl;
}
private async void LoadSubscripionsButton_Checked(object sender, RoutedEventArgs e)
{
VideoSearchButton.IsChecked = false;
PlaylistSearchButton.IsChecked = false;
SubscriptionsLoadControl subscriptionsLoadControl = new SubscriptionsLoadControl();
SearchingCancellationToken.Cancel();
SearchingCancellationToken = new CancellationTokenSource();
subscriptionsLoadControl.CancellationToken = SearchingCancellationToken.Token;
subscriptionsLoadControl.LoadingSuccessed += (s, a) =>
{
OptionsBarAndAddingPanelRow.Height = new GridLength(1, GridUnitType.Star);
DownloadTaskControlsStackPanelRow.Height = new GridLength(0);
SubscriptionsAddingPanel subscriptionsAddingPanel = new SubscriptionsAddingPanel(a.Videos);
subscriptionsAddingPanel.TasksAddingRequested += DownloadTasksAddingRequest;
AddingPanelPresenter.Content = subscriptionsAddingPanel;
};
SearchControlPresenter.Content = subscriptionsLoadControl;
await subscriptionsLoadControl.StartLoading();
}
private void AddingButtons_Unchecked(object sender, RoutedEventArgs e)
{
SearchingCancellationToken.Cancel();
SearchingCancellationToken = new CancellationTokenSource();
OptionsBarAndAddingPanelRow.Height = GridLength.Auto;
DownloadTaskControlsStackPanelRow.Height = new GridLength(1, GridUnitType.Star);
AddingPanelPresenter.Content = null;
SearchControlPresenter.Content = null;
SearchingStatusControlPresenter.Content = null;
}
private void DownloadTasksAddingRequest(object sender, DownloadTasksAddingRequestedEventArgs e)
{
switch (e.RequestSource)
{
case DownloadTasksAddingRequestSource.Video: VideoSearchButton.IsChecked = false; break;
case DownloadTasksAddingRequestSource.Playlist: PlaylistSearchButton.IsChecked = false; break;
case DownloadTasksAddingRequestSource.Subscriptions: LoadSubscripionsButton.IsChecked = false; break;
}
foreach (DownloadTask downloadTask in e.DownloadTasks)
{
DownloadTaskControl downloadTaskControl = new DownloadTaskControl(downloadTask);
DownloadTasksCollectionManagement.Add(downloadTask, downloadTask.Id);
downloadTaskControl.RemovingRequested += (s, a) =>
{
DownloadTaskControlsStackPanel.Remove(downloadTaskControl);
DownloadTasksCollectionManagement.Remove(downloadTask.Id);
};
DownloadTaskControlsStackPanel.Add(downloadTaskControl);
}
}
private async void DownloadAllButton_Click(object sender, RoutedEventArgs e)
{
DownloadTask[] idleDownloadTasks = DownloadTasksCollectionManagement.DownloadTasksCollection.Values.Where((task) => task.Status == DownloadTaskStatus.Idle).ToArray();
if (idleDownloadTasks.Count() > 0)
{
bool delay = (bool)Config.GetValue("delay_task_when_queued_task_starts_on_metered_network");
if (NetworkHelper.Instance.ConnectionInformation.IsInternetOnMeteredConnection)
{
ContentDialogResult dialogResult = await new ContentDialog
{
Title = ResourceLoader.GetForCurrentView("DialogResources").GetString("Home_DownloadAll_MeteredConnection_Title"),
Content = ResourceLoader.GetForCurrentView("DialogResources").GetString("Home_DownloadAll_MeteredConnection_Content"),
PrimaryButtonText = ResourceLoader.GetForCurrentView("DialogResources").GetString("Home_DownloadAll_MeteredConnection_StartWithDelayButtonText"),
SecondaryButtonText = ResourceLoader.GetForCurrentView("DialogResources").GetString("Home_DownloadAll_MeteredConnection_StartWithoutDelayButtonText"),
CloseButtonText = ResourceLoader.GetForCurrentView("DialogResources").GetString("Base_CancelButtonText"),
}.ShowAsync();
switch (dialogResult)
{
case ContentDialogResult.Primary: delay = true; break;
case ContentDialogResult.Secondary: delay = false; break;
case ContentDialogResult.None: return;
}
}
foreach (DownloadTask downloadTask in idleDownloadTasks)
{
await Task.Delay(1);
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
downloadTask.Run(delay);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,40 @@
<UserControl
x:Class="VDownload.Views.Home.PlaylistAddingPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.Home"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cc="using:VDownload.Controls"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///Resources/Colors.xaml"/>
<ResourceDictionary Source="ms-appx:///Resources/Icons.xaml"/>
<ResourceDictionary Source="ms-appx:///Resources/Converters.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid x:Name="Base" Padding="10" RowSpacing="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Margin="12" VerticalAlignment="Center" FontSize="18" FontWeight="SemiBold" Text="{x:Bind Name}"/>
<AppBarButton Grid.Column="1" Width="40" Height="48" Icon="{x:Bind SourceImage}" Click="SourceButton_Click"/>
<AppBarButton Grid.Column="2" Width="40" Height="48" Icon="Add" Background="{ThemeResource SystemAccentColor}" Foreground="{ThemeResource SystemAltHighColor}" Click="AddButton_Click"/>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,103 @@
using Microsoft.Toolkit.Uwp.UI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using VDownload.Converters;
using VDownload.Core.Enums;
using VDownload.Core.EventArgs;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Structs;
using VDownload.Views.Home.Controls;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace VDownload.Views.Home
{
public sealed partial class PlaylistAddingPanel : UserControl
{
#region CONSTRUCTORS
public PlaylistAddingPanel(IPlaylist playlist)
{
this.InitializeComponent();
Playlist = playlist;
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{Playlist.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
Name = Playlist.Name;
SerialVideoAddingControl = new SerialVideoAddingControl(playlist.Videos);
Grid.SetRow(SerialVideoAddingControl, 1);
Base.Children.Add(SerialVideoAddingControl);
}
#endregion
#region PROPERTIES
private IPlaylist Playlist { get; set; }
private IconElement SourceImage { get; set; }
private string Name { get; set; }
private SerialVideoAddingControl SerialVideoAddingControl { get; set;}
#endregion
#region EVENT HANDLERS
private async void SourceButton_Click(object sender, RoutedEventArgs e)
{
await Windows.System.Launcher.LaunchUriAsync(Playlist.Url);
}
private void AddButton_Click(object sender, RoutedEventArgs e)
{
List<DownloadTask> tasksList = new List<DownloadTask>();
foreach (SerialVideoAddingVideoControl videoControl in SerialVideoAddingControl.VideosStackPanel.Children)
{
TrimData trim = new TrimData
{
Start = videoControl.OptionsControl.TrimStart,
End = videoControl.OptionsControl.TrimEnd,
};
OutputFile file;
if (videoControl.OptionsControl.FileLocation is null)
{
file = new OutputFile(videoControl.OptionsControl.Filename, videoControl.OptionsControl.FileExtension);
}
else
{
file = new OutputFile(videoControl.OptionsControl.Filename, videoControl.OptionsControl.FileExtension, videoControl.OptionsControl.FileLocation);
}
string id = DownloadTasksCollectionManagement.GenerateID();
tasksList.Add(new DownloadTask(id, videoControl.Video, videoControl.OptionsControl.MediaType, videoControl.OptionsControl.Stream, trim, file, videoControl.OptionsControl.Schedule));
}
TasksAddingRequested?.Invoke(this, new DownloadTasksAddingRequestedEventArgs(tasksList.ToArray(), DownloadTasksAddingRequestSource.Playlist));
}
#endregion
#region EVENTS
public event EventHandler<DownloadTasksAddingRequestedEventArgs> TasksAddingRequested;
#endregion
}
}

View File

@@ -0,0 +1,40 @@
<UserControl
x:Class="VDownload.Views.Home.SubscriptionsAddingPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.Home"
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"
xmlns:cc="using:VDownload.Controls"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///Resources/Colors.xaml"/>
<ResourceDictionary Source="ms-appx:///Resources/Icons.xaml"/>
<ResourceDictionary Source="ms-appx:///Resources/Converters.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid x:Name="Base" Padding="10" RowSpacing="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" x:Uid="Home_Adding_Subscriptions_HeaderTextBlock" Margin="12" VerticalAlignment="Center" FontSize="18" FontWeight="SemiBold"/>
<AppBarButton Grid.Column="2" Width="40" Height="48" Icon="Add" Background="{ThemeResource SystemAccentColor}" Foreground="{ThemeResource SystemAltHighColor}" Click="AddButton_Click"/>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,92 @@
using Microsoft.Toolkit.Uwp.UI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using VDownload.Converters;
using VDownload.Core.Enums;
using VDownload.Core.EventArgs;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Structs;
using VDownload.Views.Home.Controls;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace VDownload.Views.Home
{
public sealed partial class SubscriptionsAddingPanel : UserControl
{
#region CONSTRUCTORS
public SubscriptionsAddingPanel(IVideo[] videos)
{
this.InitializeComponent();
Videos = videos;
SerialVideoAddingControl = new SerialVideoAddingControl(Videos);
Grid.SetRow(SerialVideoAddingControl, 1);
Base.Children.Add(SerialVideoAddingControl);
}
#endregion
#region PROPERTIES
private IVideo[] Videos { get; set; }
private SerialVideoAddingControl SerialVideoAddingControl { get; set; }
#endregion
#region EVENT HANDLERS VOIDS
private void AddButton_Click(object sender, RoutedEventArgs e)
{
List<DownloadTask> tasksList = new List<DownloadTask>();
foreach (SerialVideoAddingVideoControl videoControl in SerialVideoAddingControl.VideosStackPanel.Children)
{
TrimData trim = new TrimData
{
Start = videoControl.OptionsControl.TrimStart,
End = videoControl.OptionsControl.TrimEnd,
};
OutputFile file;
if (videoControl.OptionsControl.FileLocation is null)
{
file = new OutputFile(videoControl.OptionsControl.Filename, videoControl.OptionsControl.FileExtension);
}
else
{
file = new OutputFile(videoControl.OptionsControl.Filename, videoControl.OptionsControl.FileExtension, videoControl.OptionsControl.FileLocation);
}
string id = DownloadTasksCollectionManagement.GenerateID();
tasksList.Add(new DownloadTask(id, videoControl.Video, videoControl.OptionsControl.MediaType, videoControl.OptionsControl.Stream, trim, file, videoControl.OptionsControl.Schedule));
}
TasksAddingRequested?.Invoke(this, new DownloadTasksAddingRequestedEventArgs(tasksList.ToArray(), DownloadTasksAddingRequestSource.Playlist));
}
#endregion
#region EVENT HANDLERS
public event EventHandler<DownloadTasksAddingRequestedEventArgs> TasksAddingRequested;
#endregion
}
}

View File

@@ -0,0 +1,204 @@
<UserControl
x:Class="VDownload.Views.Home.VideoAddingPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.Home"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ex="using:Microsoft.Toolkit.Uwp.UI"
xmlns:cc="using:VDownload.Controls"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
Loading="VideoAddingPanel_Loading"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///Resources/Icons.xaml"/>
<ResourceDictionary Source="ms-appx:///Resources/Converters.xaml"/>
</ResourceDictionary.MergedDictionaries>
<x:String x:Key="MetadataIconSize">16</x:String>
<x:String x:Key="MetadataTextSize">12</x:String>
</ResourceDictionary>
</UserControl.Resources>
<Grid Padding="10" RowSpacing="40" VerticalAlignment="Stretch">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="Thumbnail.(Grid.Column)" Value="0"/>
<Setter Target="Thumbnail.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="Thumbnail.(Grid.Row)" Value="0"/>
<Setter Target="Thumbnail.(Grid.RowSpan)" Value="1"/>
<Setter Target="Thumbnail.Width" Value="NaN"/>
<Setter Target="TitleAndMetadataGrid.(Grid.Column)" Value="0"/>
<Setter Target="TitleAndMetadataGrid.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="TitleAndMetadataGrid.(Grid.Row)" Value="1"/>
<Setter Target="TitleAndMetadataGrid.(Grid.RowSpan)" Value="1"/>
<Setter Target="AuthorIcon.(Grid.Row)" Value="0"/>
<Setter Target="AuthorIcon.(Grid.RowSpan)" Value="2"/>
<Setter Target="AuthorIcon.(Grid.Column)" Value="0"/>
<Setter Target="AuthorIcon.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="AuthorText.(Grid.Row)" Value="2"/>
<Setter Target="AuthorText.(Grid.RowSpan)" Value="2"/>
<Setter Target="AuthorText.(Grid.Column)" Value="0"/>
<Setter Target="AuthorText.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="AuthorText.HorizontalAlignment" Value="Center"/>
<Setter Target="ViewsIcon.(Grid.Row)" Value="0"/>
<Setter Target="ViewsIcon.(Grid.RowSpan)" Value="2"/>
<Setter Target="ViewsIcon.(Grid.Column)" Value="1"/>
<Setter Target="ViewsIcon.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="ViewsText.(Grid.Row)" Value="2"/>
<Setter Target="ViewsText.(Grid.RowSpan)" Value="2"/>
<Setter Target="ViewsText.(Grid.Column)" Value="1"/>
<Setter Target="ViewsText.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="ViewsText.HorizontalAlignment" Value="Center"/>
<Setter Target="DateIcon.(Grid.Row)" Value="0"/>
<Setter Target="DateIcon.(Grid.RowSpan)" Value="2"/>
<Setter Target="DateIcon.(Grid.Column)" Value="2"/>
<Setter Target="DateIcon.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="DateText.(Grid.Row)" Value="2"/>
<Setter Target="DateText.(Grid.RowSpan)" Value="2"/>
<Setter Target="DateText.(Grid.Column)" Value="2"/>
<Setter Target="DateText.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="DateText.HorizontalAlignment" Value="Center"/>
<Setter Target="DurationIcon.(Grid.Row)" Value="0"/>
<Setter Target="DurationIcon.(Grid.RowSpan)" Value="2"/>
<Setter Target="DurationIcon.(Grid.Column)" Value="3"/>
<Setter Target="DurationIcon.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="DurationText.(Grid.Row)" Value="2"/>
<Setter Target="DurationText.(Grid.RowSpan)" Value="2"/>
<Setter Target="DurationText.(Grid.Column)" Value="3"/>
<Setter Target="DurationText.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="DurationText.HorizontalAlignment" Value="Center"/>
<Setter Target="MetadataR1.Height" Value="Auto"/>
<Setter Target="MetadataR2.Height" Value="Auto"/>
<Setter Target="MetadataC1.Width" Value="1*"/>
<Setter Target="MetadataC2.Width" Value="1*"/>
</VisualState.Setters>
</VisualState>
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="600"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="Thumbnail.(Grid.Column)" Value="0"/>
<Setter Target="Thumbnail.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="Thumbnail.(Grid.Row)" Value="0"/>
<Setter Target="Thumbnail.(Grid.RowSpan)" Value="2"/>
<Setter Target="Thumbnail.Width" Value="250"/>
<Setter Target="TitleAndMetadataGrid.(Grid.Column)" Value="1"/>
<Setter Target="TitleAndMetadataGrid.(Grid.ColumnSpan)" Value="1"/>
<Setter Target="TitleAndMetadataGrid.(Grid.Row)" Value="0"/>
<Setter Target="TitleAndMetadataGrid.(Grid.RowSpan)" Value="2"/>
<Setter Target="AuthorIcon.(Grid.Row)" Value="0"/>
<Setter Target="AuthorIcon.(Grid.RowSpan)" Value="1"/>
<Setter Target="AuthorIcon.(Grid.Column)" Value="0"/>
<Setter Target="AuthorIcon.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="AuthorText.(Grid.Row)" Value="0"/>
<Setter Target="AuthorText.(Grid.RowSpan)" Value="1"/>
<Setter Target="AuthorText.(Grid.Column)" Value="2"/>
<Setter Target="AuthorText.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="AuthorText.HorizontalAlignment" Value="Left"/>
<Setter Target="ViewsIcon.(Grid.Row)" Value="1"/>
<Setter Target="ViewsIcon.(Grid.RowSpan)" Value="1"/>
<Setter Target="ViewsIcon.(Grid.Column)" Value="0"/>
<Setter Target="ViewsIcon.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="ViewsText.(Grid.Row)" Value="1"/>
<Setter Target="ViewsText.(Grid.RowSpan)" Value="1"/>
<Setter Target="ViewsText.(Grid.Column)" Value="2"/>
<Setter Target="ViewsText.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="ViewsText.HorizontalAlignment" Value="Left"/>
<Setter Target="DateIcon.(Grid.Row)" Value="2"/>
<Setter Target="DateIcon.(Grid.RowSpan)" Value="1"/>
<Setter Target="DateIcon.(Grid.Column)" Value="0"/>
<Setter Target="DateIcon.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="DateText.(Grid.Row)" Value="2"/>
<Setter Target="DateText.(Grid.RowSpan)" Value="1"/>
<Setter Target="DateText.(Grid.Column)" Value="2"/>
<Setter Target="DateText.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="DateText.HorizontalAlignment" Value="Left"/>
<Setter Target="DurationIcon.(Grid.Row)" Value="3"/>
<Setter Target="DurationIcon.(Grid.RowSpan)" Value="1"/>
<Setter Target="DurationIcon.(Grid.Column)" Value="0"/>
<Setter Target="DurationIcon.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="DurationText.(Grid.Row)" Value="3"/>
<Setter Target="DurationText.(Grid.RowSpan)" Value="1"/>
<Setter Target="DurationText.(Grid.Column)" Value="2"/>
<Setter Target="DurationText.(Grid.ColumnSpan)" Value="2"/>
<Setter Target="DurationText.HorizontalAlignment" Value="Left"/>
<Setter Target="MetadataR1.Height" Value="1*"/>
<Setter Target="MetadataR2.Height" Value="1*"/>
<Setter Target="MetadataC1.Width" Value="Auto"/>
<Setter Target="MetadataC2.Width" Value="Auto"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" ColumnSpacing="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image x:Name="Thumbnail" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="2" Source="{x:Bind ThumbnailImage}"/>
<Grid x:Name="TitleAndMetadataGrid" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" FontSize="18" VerticalAlignment="Center" FontWeight="SemiBold" Text="{x:Bind Title}"/>
<AppBarButton Grid.Column="1" Width="40" Height="48" Icon="{x:Bind SourceImage}" Click="SourceButton_Click"/>
<AppBarButton Grid.Column="2" Width="40" Height="48" Icon="Add" Background="{ThemeResource SystemAccentColor}" Foreground="{ThemeResource SystemAltHighColor}" Click="AddButton_Click"/>
</Grid>
<Grid Grid.Row="1" ColumnSpacing="10" RowSpacing="5">
<Grid.RowDefinitions>
<RowDefinition x:Name="MetadataR1" Height="1*"/>
<RowDefinition x:Name="MetadataR2" Height="1*"/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="MetadataC1" Width="Auto"/>
<ColumnDefinition x:Name="MetadataC2" Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image x:Name="AuthorIcon" Grid.Row="0" Grid.RowSpan="1" Grid.Column="0" Grid.ColumnSpan="2" Width="{StaticResource MetadataIconSize}" Source="{ThemeResource AuthorIcon}"/>
<TextBlock x:Name="AuthorText" Grid.Row="0" Grid.RowSpan="1" Grid.Column="2" Grid.ColumnSpan="2" HorizontalAlignment="Left" FontSize="{StaticResource MetadataTextSize}" Text="{x:Bind Author}"/>
<Image x:Name="ViewsIcon" Grid.Row="1" Grid.RowSpan="1" Grid.Column="0" Grid.ColumnSpan="2" Width="{StaticResource MetadataIconSize}" Source="{ThemeResource ViewsIcon}"/>
<TextBlock x:Name="ViewsText" Grid.Row="1" Grid.RowSpan="1" Grid.Column="2" Grid.ColumnSpan="2" HorizontalAlignment="Left" FontSize="{StaticResource MetadataTextSize}" Text="{x:Bind Views}"/>
<Image x:Name="DateIcon" Grid.Row="2" Grid.RowSpan="1" Grid.Column="0" Grid.ColumnSpan="2" Width="{StaticResource MetadataIconSize}" Source="{ThemeResource DateIcon}"/>
<TextBlock x:Name="DateText" Grid.Row="2" Grid.RowSpan="1" Grid.Column="2" Grid.ColumnSpan="2" HorizontalAlignment="Left" FontSize="{StaticResource MetadataTextSize}" Text="{x:Bind Date}"/>
<Image x:Name="DurationIcon" Grid.Row="3" Grid.RowSpan="1" Grid.Column="0" Grid.ColumnSpan="2" Width="{StaticResource MetadataIconSize}" Source="{ThemeResource DurationIcon}"/>
<TextBlock x:Name="DurationText" Grid.Row="3" Grid.RowSpan="1" Grid.Column="2" Grid.ColumnSpan="2" HorizontalAlignment="Left" FontSize="{StaticResource MetadataTextSize}" Text="{x:Bind Duration}"/>
</Grid>
</Grid>
</Grid>
<ScrollViewer Grid.Row="1">
<ContentPresenter x:Name="OptionsControlPresenter" HorizontalContentAlignment="Stretch"/>
</ScrollViewer>
</Grid>
</UserControl>

View File

@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using VDownload.Core.Enums;
using VDownload.Core.Extensions;
using VDownload.Core.EventArgs;
using VDownload.Core.Interfaces;
using VDownload.Core.Services;
using VDownload.Core.Structs;
using VDownload.Views.Home.Controls;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
namespace VDownload.Views.Home
{
public sealed partial class VideoAddingPanel : UserControl
{
#region CONSTANTS
private readonly ResourceDictionary ImagesRes = new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Images.xaml") };
#endregion
#region CONSTRUCTORS
public VideoAddingPanel(IVideo video)
{
this.InitializeComponent();
Video = video;
ThumbnailImage = Video.Thumbnail != null ? new BitmapImage { UriSource = Video.Thumbnail } : (BitmapImage)ImagesRes["UnknownThumbnailImage"];
SourceImage = new BitmapIcon { UriSource = new Uri($"ms-appx:///Assets/Sources/{Video.GetType().Namespace.Split(".").Last()}.png"), ShowAsMonochrome = false };
Title = Video.Title;
Author = Video.Author;
Views = Video.Views.ToString();
Date = Video.Date.ToString(CultureInfo.InstalledUICulture.DateTimeFormat.ShortDatePattern);
Duration = Video.Duration.ToStringOptTHBaseMMSS();
OptionsControl = new VideoAddingOptionsControl(Video);
OptionsControlPresenter.Content = OptionsControl;
}
#endregion
#region PROPERTIES
private IVideo Video { get; set; }
// VIDEO DATA
private ImageSource ThumbnailImage { get; set; }
private IconElement SourceImage { get; set; }
private string Title { get; set; }
private string Author { get; set; }
private string Views { get; set; }
private string Date { get; set; }
private string Duration { get; set; }
private VideoAddingOptionsControl OptionsControl { get; set; }
#endregion
#region EVENT HANDLERS
private async void VideoAddingPanel_Loading(FrameworkElement sender, object args)
{
await OptionsControl.Init();
}
private async void SourceButton_Click(object sender, RoutedEventArgs e)
{
await Windows.System.Launcher.LaunchUriAsync(Video.Url);
}
private void AddButton_Click(object sender, RoutedEventArgs e)
{
TrimData trim = new TrimData
{
Start = OptionsControl.TrimStart,
End = OptionsControl.TrimEnd,
};
OutputFile file;
if (OptionsControl.FileLocation is null)
{
file = new OutputFile(OptionsControl.Filename, OptionsControl.FileExtension);
}
else
{
file = new OutputFile(OptionsControl.Filename, OptionsControl.FileExtension, OptionsControl.FileLocation);
}
string id = DownloadTasksCollectionManagement.GenerateID();
DownloadTask downloadTask = new DownloadTask(id, Video, OptionsControl.MediaType, OptionsControl.Stream, trim, file, OptionsControl.Schedule);
TasksAddingRequested?.Invoke(this, new DownloadTasksAddingRequestedEventArgs(new DownloadTask[] { downloadTask }, DownloadTasksAddingRequestSource.Video));
}
#endregion
#region EVENTS
public event EventHandler<DownloadTasksAddingRequestedEventArgs> TasksAddingRequested;
#endregion
}
}

View File

@@ -19,10 +19,9 @@
<SolidColorBrush x:Key="NavigationViewContentBackground" Color="Transparent"/>
</ResourceDictionary>
</Page.Resources>
<Grid>
<!-- TITLE BAR -->
<Border x:Name="AppTitleBar"
IsHitTestVisible="True"
VerticalAlignment="Top"
@@ -45,26 +44,25 @@
</StackPanel>
</Border>
<!-- NAVIGATION PANEL AND CONTENT -->
<muxc:NavigationView x:Name="MainPageNavigationPanel" Background="Transparent" IsTitleBarAutoPaddingEnabled="False" IsBackButtonVisible="Collapsed" PaneDisplayMode="LeftCompact" Canvas.ZIndex="0" ItemInvoked="MainPageNavigationPanel_ItemInvoked">
<muxc:NavigationView x:Name="NavigationPanel" Background="Transparent" IsTitleBarAutoPaddingEnabled="False" IsBackButtonVisible="Collapsed" PaneDisplayMode="LeftCompact" Canvas.ZIndex="0" ItemInvoked="NavigationPanel_ItemInvoked">
<muxc:NavigationView.MenuItems>
<muxc:NavigationViewItem x:Uid="MainPageNavigationPanelHomeItem" Tag="home" Icon="Video" Content="Home"/>
<muxc:NavigationViewItem x:Uid="Main_NavigationPanel_Home" Tag="home" Icon="Video"/>
<muxc:NavigationViewItemSeparator/>
<muxc:NavigationViewItem x:Uid="MainPageNavigationPanelSubscriptionsItem" Tag="subscriptions" Icon="Favorite" Content="Subscriptions"/>
<muxc:NavigationViewItem x:Uid="Main_NavigationPanel_Subscriptions" Tag="subscriptions" Icon="Favorite"/>
</muxc:NavigationView.MenuItems>
<muxc:NavigationView.FooterMenuItems>
<muxc:NavigationViewItem x:Uid="MainPageNavigationPanelAboutItem" Tag="about" Content="About">
<muxc:NavigationViewItem x:Uid="Main_NavigationPanel_About" Tag="about">
<muxc:NavigationViewItem.Icon>
<PathIcon Data="M3331.25,1.389c-1833.78,-0 -3325.69,1492.85 -3325.69,3327.78c-0,1834.92 1491.92,3327.78 3325.69,3327.78c1833.78,0 3325.69,-1492.85 3325.69,-3327.78c0,-1834.92 -1491.92,-3327.78 -3325.69,-3327.78Zm0,499.167c1564.18,-0 2826.84,1263.45 2826.84,2828.61c0,1565.15 -1262.66,2828.61 -2826.84,2828.61c-1564.18,-0 -2826.84,-1263.46 -2826.84,-2828.61c-0,-1565.16 1262.66,-2828.61 2826.84,-2828.61Zm0,1164.72c-182.442,-0 -332.569,150.221 -332.569,332.778c-0,182.556 150.127,332.777 332.569,332.777c182.442,0 332.569,-150.221 332.569,-332.777c0,-182.557 -150.127,-332.778 -332.569,-332.778Zm-3.897,1161.15c-136.709,2.137 -247.491,116.362 -245.53,253.158l-0,1830.28c-0.017,1.177 -0.025,2.353 -0.025,3.53c-0,136.931 112.607,249.608 249.452,249.608c136.845,0 249.452,-112.677 249.452,-249.608c0,-1.177 -0.008,-2.353 -0.025,-3.53l0,-1830.28c0.017,-1.193 0.026,-2.386 0.026,-3.58c-0,-136.931 -112.607,-249.608 -249.452,-249.608c-1.3,-0 -2.599,0.01 -3.898,0.03Z"/>
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
<muxc:NavigationViewItem x:Uid="MainPageNavigationPanelSourcesItem" Tag="sources" Content="Sources">
<muxc:NavigationViewItem x:Uid="Main_NavigationPanel_Sources" Tag="sources">
<muxc:NavigationViewItem.Icon>
<PathIcon Data="M959.759,-4.167c-33.26,0 -65.978,1.941 -98.383,5.426c-64.583,6.944 -127.292,20.746 -187.509,40.509c-302.561,99.296 -541.778,351.698 -635.887,670.935c-18.731,63.536 -31.812,129.701 -38.394,197.844c-0.011,0.119 0.012,0.242 0,0.362c-3.279,34.075 -5.142,68.472 -5.142,103.443l0,4629.63c0,559.218 435.309,1018.52 965.315,1018.52l4738.82,0c530.005,0 965.314,-459.301 965.314,-1018.52l-0,-4629.63c-0,-35.074 -1.801,-69.606 -5.142,-103.805c-6.582,-68.143 -19.663,-134.308 -38.393,-197.844c-94.109,-319.237 -333.327,-571.639 -635.887,-670.935c-60.218,-19.763 -122.926,-33.565 -187.51,-40.509c-0.113,-0.012 -0.23,0.012 -0.343,-0c-32.302,-3.501 -64.914,-5.426 -98.039,-5.426l-4738.82,0Zm-0,555.556c145.323,-0 263.267,124.444 263.267,277.778c0,153.333 -117.944,277.777 -263.267,277.777c-145.324,0 -263.268,-124.444 -263.268,-277.777c0,-153.334 117.944,-277.778 263.268,-277.778Zm877.558,-0c145.324,-0 263.268,124.444 263.268,277.778c-0,153.333 -117.944,277.777 -263.268,277.777c-145.323,0 -263.267,-124.444 -263.267,-277.777c-0,-153.334 117.944,-277.778 263.267,-277.778Zm1053.07,-0l2808.19,-0c145.323,-0 263.267,124.444 263.267,277.778c0,153.333 -117.944,277.777 -263.267,277.777l-2808.19,0c-145.323,0 -263.267,-124.444 -263.267,-277.777c-0,-153.334 117.944,-277.778 263.267,-277.778Zm-2369.41,1111.11l5616.37,0l-0,3981.48c-0,258.931 -193.374,462.963 -438.779,462.963l-4738.82,0c-245.405,0 -438.779,-204.032 -438.779,-462.963l-0,-3981.48Zm2247.03,1111.11c-166.006,3.577 -316.401,144.126 -316.401,339.265l0,1543.69c0,260.371 267.48,423.128 482.657,294.054l1285.49,-771.484c216.757,-130.185 216.757,-458.831 0,-588.831l-1285.49,-771.485c-53.794,-32.268 -110.92,-46.403 -166.256,-45.211Z"/>
</muxc:NavigationViewItem.Icon>
</muxc:NavigationViewItem>
</muxc:NavigationView.FooterMenuItems>
<Frame x:Name="MainPageContentFrame" CornerRadius="5" Margin="10"/>
<Frame x:Name="ContentFrame" CornerRadius="5" Margin="10"/>
</muxc:NavigationView>
</Grid>
</Page>

View File

@@ -8,6 +8,21 @@ namespace VDownload.Views
{
public sealed partial class MainPage : Page
{
#region CONSTANTS
private readonly Dictionary<string, Type> Pages = new Dictionary<string, Type>()
{
{"home", typeof(Home.MainPage)},
{"subscriptions", typeof(Subscriptions.MainPage)},
{"about", typeof(About.MainPage)},
{"sources", typeof(Sources.MainPage)},
{"settings", typeof(Settings.MainPage)},
};
#endregion
#region CONSTRUCTORS
public MainPage()
@@ -20,34 +35,28 @@ namespace VDownload.Views
Window.Current.SetTitleBar(AppTitleBar);
// Navigate to home page
MainPageContentFrame.Navigate(Pages["home"]);
MainPageNavigationPanel.SelectedItem = MainPageNavigationPanel.MenuItems[0];
ContentFrame.Navigate(Pages["home"]);
NavigationPanel.SelectedItem = NavigationPanel.MenuItems[0];
}
#endregion
#region NAVIGATION PANEL
#region EVENT HANDLERS
// PAGES DICTIONARY
private readonly Dictionary<string, Type> Pages = new Dictionary<string, Type>()
private void NavigationPanel_ItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args)
{
{"home", typeof(Home.HomeMain)},
{"subscriptions", typeof(Subscriptions.MainPage)},
{"about", typeof(About.AboutMain)},
{"sources", typeof(Sources.MainPage)},
{"settings", typeof(Settings.SettingsMain)},
};
// ON ITEM INVOKED
private void MainPageNavigationPanel_ItemInvoked(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewItemInvokedEventArgs args)
{
if (args.IsSettingsInvoked) MainPageContentFrame.Navigate(Pages["settings"], args.RecommendedNavigationTransitionInfo);
else if (args.InvokedItemContainer != null) MainPageContentFrame.Navigate(Pages[args.InvokedItemContainer.Tag.ToString()], args.RecommendedNavigationTransitionInfo);
if (args.IsSettingsInvoked)
{
ContentFrame.Navigate(Pages["settings"], args.RecommendedNavigationTransitionInfo);
}
else if (args.InvokedItemContainer != null)
{
ContentFrame.Navigate(Pages[args.InvokedItemContainer.Tag.ToString()], args.RecommendedNavigationTransitionInfo);
}
}
#endregion
}
}

View File

@@ -0,0 +1,27 @@
<Page
x:Class="VDownload.Views.Settings.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:VDownload.Views.Settings"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cc="using:VDownload.Controls"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Padding="20" RowSpacing="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock x:Uid="Settings_HeaderTextBlock" Grid.Row="0" FontSize="28" FontWeight="SemiBold"/>
<ScrollViewer Grid.Row="1">
<StackPanel VerticalAlignment="Stretch" Spacing="10">
</StackPanel>
</ScrollViewer>
</Grid>
</Page>

View File

@@ -15,9 +15,9 @@ using Windows.UI.Xaml.Navigation;
namespace VDownload.Views.Settings
{
public sealed partial class SettingsMain : Page
public sealed partial class MainPage : Page
{
public SettingsMain()
public MainPage()
{
this.InitializeComponent();
}

View File

@@ -5,7 +5,9 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using VDownload.Core.Structs;
using Windows.ApplicationModel.Resources;
using Windows.UI.WindowManagement;
using Windows.UI.Xaml;
@@ -39,24 +41,103 @@ namespace VDownload.Views.Sources
await Task.WhenAll(checkingTasks);
}
private async void TwitchSettingControlLoginButton_Click(object sender, RoutedEventArgs e)
{
async Task ShowDialog(string localErrorKey, string unknownErrorInfo = null)
{
StringBuilder content = new StringBuilder(ResourceLoader.GetForCurrentView("DialogResources").GetString($"Sources_TwitchLogin_{localErrorKey}_Content"));
if (!string.IsNullOrEmpty(unknownErrorInfo))
{
content.Append($" {unknownErrorInfo}");
}
ContentDialog errorDialog = new ContentDialog
{
Title = ResourceLoader.GetForCurrentView("DialogResources").GetString("Sources_TwitchLogin_Base_Title"),
Content = content.ToString(),
CloseButtonText = ResourceLoader.GetForCurrentView().GetString("Base_CloseButtonText"),
};
await errorDialog.ShowAsync();
}
TwitchAccessTokenValidationData accessTokenValidation = TwitchAccessTokenValidationData.Null;
try
{
string accessToken = await Core.Services.Sources.Twitch.Helpers.Authorization.ReadAccessTokenAsync();
accessTokenValidation = await Core.Services.Sources.Twitch.Helpers.Authorization.ValidateAccessTokenAsync(accessToken);
}
catch (WebException)
{
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
{
goto Check;
}
else throw;
}
if (accessTokenValidation.IsValid)
{
await Core.Services.Sources.Twitch.Helpers.Authorization.RevokeAccessTokenAsync(accessTokenValidation.AccessToken);
await Core.Services.Sources.Twitch.Helpers.Authorization.DeleteAccessTokenAsync();
}
else
{
string response = await LoginToTwitch();
accessTokenValidation = TwitchAccessTokenValidationData.Null;
try
{
accessTokenValidation = await Core.Services.Sources.Twitch.Helpers.Authorization.ValidateAccessTokenAsync(response);
}
catch (WebException)
{
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
{
await ShowDialog("InternetNotAvailable");
goto Check;
}
else throw;
}
if (accessTokenValidation.IsValid)
{
await Core.Services.Sources.Twitch.Helpers.Authorization.SaveAccessTokenAsync(accessTokenValidation.AccessToken);
}
else
{
Dictionary<string, string> errorCodes = new Dictionary<string, string>
{
{"The user denied you access", string.Empty},
};
if (!string.IsNullOrEmpty(response))
{
if (errorCodes.ContainsKey(response))
{
if (!string.IsNullOrEmpty(errorCodes[response]))
{
await ShowDialog(errorCodes[response]);
}
}
else
{
await ShowDialog("Unknown", response);
}
}
}
}
Check: await CheckTwitch();
}
#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
string twitchAccessToken = await Core.Services.Sources.Twitch.Helpers.Authorization.ReadAccessTokenAsync();
TwitchAccessTokenValidationData twitchAccessTokenValidation = await Core.Services.Sources.Twitch.Helpers.Authorization.ValidateAccessTokenAsync(twitchAccessToken);
if (twitchAccessTokenValidation.IsValid)
{
TwitchSettingControl.Description = $"{ResourceLoader.GetForCurrentView().GetString("Sources_TwitchSettingControl_Description_LoggedIn")} {twitchAccessTokenValidation.Login}";
@@ -66,18 +147,17 @@ namespace VDownload.Views.Sources
{
if (twitchAccessToken != null)
{
TwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("Sources_TwitchSettingControl_Description_AccessTokenExpired");
TwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("Sources_TwitchSettingControl_Description_InvalidAccessToken");
}
else if (twitchAccessTokenValidation.ExpirationDate < DateTime.Now)
else
{
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)
catch (WebException)
{
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
{
@@ -89,122 +169,54 @@ namespace VDownload.Views.Sources
}
}
#region TWITCH
// TWITCH LOGIN BUTTON CLICKED
private async void TwitchSettingControlLoginButton_Click(object sender, RoutedEventArgs e)
private async Task<string> LoginToTwitch()
{
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";
string response = string.Empty;
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();
WebView2 TwitchAuthWebView = new WebView2();
await TwitchAuthWebView.EnsureCoreWebView2Async();
TwitchAuthWebView.Source = Core.Services.Sources.Twitch.Helpers.Authorization.AuthorizationUrl;
// Get response
string response = a.Uri.Replace(Core.Services.Sources.Twitch.Helpers.Auth.RedirectUrl.OriginalString, "");
ElementCompositionPreview.SetAppWindowContent(TwitchAuthWindow, TwitchAuthWebView);
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)
TwitchAuthWebView.NavigationStarting += async (s, a) =>
{
if (!NetworkHelper.Instance.ConnectionInformation.IsInternetAvailable)
if (new Uri(a.Uri).Host == Core.Services.Sources.Twitch.Helpers.Authorization.RedirectUrl.Host)
{
TwitchSettingControl.Description = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchSettingControlDescriptionInternetConnectionError");
TwitchSettingControlLoginButton.Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginButtonTextNotLoggedIn");
TwitchSettingControlLoginButton.IsEnabled = false;
ContentDialog internetAccessErrorDialog = new ContentDialog
string login_response = a.Uri.Replace(Core.Services.Sources.Twitch.Helpers.Authorization.RedirectUrl.OriginalString, "");
if (login_response[1] == '#')
{
Title = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginErrorDialogTitle"),
Content = ResourceLoader.GetForCurrentView().GetString("SourcesTwitchLoginErrorDialogDescriptionInternetConnectionError"),
CloseButtonText = ResourceLoader.GetForCurrentView().GetString("CloseErrorDialogButtonText"),
};
await internetAccessErrorDialog.ShowAsync();
response = login_response.Split('&')[0].Replace("/#access_token=", "");
}
else
{
response = (login_response.Split('&')[1].Replace("error_description=", "")).Replace('+', ' ');
}
await TwitchAuthWindow.CloseAsync();
}
};
await TwitchAuthWindow.TryShowAsync();
try
{
while (TwitchAuthWindow.IsVisible)
{
await Task.Delay(1);
}
else throw;
}
catch (ObjectDisposedException) { }
TwitchAuthWebView.CoreWebView2.CookieManager.DeleteAllCookies();
#pragma warning restore CS8305 // Type is for evaluation purposes only and is subject to change or removal in future updates.
return response;
}
#endregion