From 71cc12106c61be7d65cab19cb0d1efca5309058d Mon Sep 17 00:00:00 2001 From: Michael Gordeev Date: Wed, 11 Jul 2018 18:58:05 +0300 Subject: [PATCH] DownloadAgent --- FoxTube.Background/BackgroundProcessor.cs | 67 +++++++ FoxTube.Background/FoxTube.Background.csproj | 136 ++++++++++++++ FoxTube.Background/Notification.cs | 175 ++++++++++++++++++ FoxTube.Background/Properties/AssemblyInfo.cs | 29 +++ FoxTube.Background/ToastTemplates.cs | 45 +++++ FoxTube.sln | 22 +++ FoxTube/Assets/Icons/Profile.png | Bin 0 -> 22217 bytes FoxTube/Assets/Icons/Send.png | Bin 0 -> 11584 bytes FoxTube/Classes/Methods.cs | 39 +++- FoxTube/Classes/Notification.cs | 159 ++++++++++++++-- FoxTube/Classes/ObjectEventArgs.cs | 18 ++ FoxTube/Classes/SecretsVault.cs | 104 ++++++++--- FoxTube/Controls/DownloadAgent.cs | 63 +++++++ FoxTube/Controls/DownloadItem.xaml | 52 ++++++ FoxTube/Controls/DownloadItem.xaml.cs | 162 ++++++++++++++++ FoxTube/Controls/VideoCard.xaml | 5 + FoxTube/Controls/VideoCard.xaml.cs | 5 +- FoxTube/Controls/VideoCardWide.xaml | 9 +- FoxTube/Controls/VideoCardWide.xaml.cs | 3 + FoxTube/FoxTube.csproj | 45 +++++ FoxTube/Notifications/Comment.xml | 22 +++ FoxTube/Notifications/Download.xml | 1 + FoxTube/Notifications/DownloadComplete.xml | 1 + FoxTube/Notifications/Internal.xml | 1 + FoxTube/Notifications/Post.xml | 1 + FoxTube/Notifications/ServerLogSamlpe.xml | 7 + FoxTube/Notifications/Video.xml | 1 + .../Notifications/downloadHistorySample.xml | 16 ++ FoxTube/Pages/Downloads.xaml | 33 ++++ FoxTube/Pages/Downloads.xaml.cs | 58 ++++++ FoxTube/Pages/MainPage.xaml.cs | 4 + FoxTube/Pages/Translate.xaml.cs | 2 +- FoxTube/Pages/Video.xaml | 12 +- FoxTube/Pages/Video.xaml.cs | 117 +++++++++++- ...NewCommentNotification.xml => Comment.xml} | 0 ...dingVideoNotification.xml => Download.xml} | 0 Src/{UpdateNotification.xml => Internal.xml} | 0 Src/{NewPostNotification.xml => Post.xml} | 0 Src/{NewVideoNotification.xml => Video.xml} | 0 39 files changed, 1359 insertions(+), 55 deletions(-) create mode 100644 FoxTube.Background/BackgroundProcessor.cs create mode 100644 FoxTube.Background/FoxTube.Background.csproj create mode 100644 FoxTube.Background/Notification.cs create mode 100644 FoxTube.Background/Properties/AssemblyInfo.cs create mode 100644 FoxTube.Background/ToastTemplates.cs create mode 100644 FoxTube/Assets/Icons/Profile.png create mode 100644 FoxTube/Assets/Icons/Send.png create mode 100644 FoxTube/Classes/ObjectEventArgs.cs create mode 100644 FoxTube/Controls/DownloadAgent.cs create mode 100644 FoxTube/Controls/DownloadItem.xaml create mode 100644 FoxTube/Controls/DownloadItem.xaml.cs create mode 100644 FoxTube/Notifications/Comment.xml create mode 100644 FoxTube/Notifications/Download.xml create mode 100644 FoxTube/Notifications/DownloadComplete.xml create mode 100644 FoxTube/Notifications/Internal.xml create mode 100644 FoxTube/Notifications/Post.xml create mode 100644 FoxTube/Notifications/ServerLogSamlpe.xml create mode 100644 FoxTube/Notifications/Video.xml create mode 100644 FoxTube/Notifications/downloadHistorySample.xml create mode 100644 FoxTube/Pages/Downloads.xaml create mode 100644 FoxTube/Pages/Downloads.xaml.cs rename Src/{NewCommentNotification.xml => Comment.xml} (100%) rename Src/{DownloadingVideoNotification.xml => Download.xml} (100%) rename Src/{UpdateNotification.xml => Internal.xml} (100%) rename Src/{NewPostNotification.xml => Post.xml} (100%) rename Src/{NewVideoNotification.xml => Video.xml} (100%) diff --git a/FoxTube.Background/BackgroundProcessor.cs b/FoxTube.Background/BackgroundProcessor.cs new file mode 100644 index 0000000..152477a --- /dev/null +++ b/FoxTube.Background/BackgroundProcessor.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using Windows.ApplicationModel.Background; +using Windows.Storage; +using Windows.UI.Notifications; + +namespace FoxTube.Background +{ + public sealed class BackgroundProcessor : IBackgroundTask + { + public List Notifications = new List(); + + private DateTime lastCheck; + private ApplicationDataContainer settings = ApplicationData.Current.LocalSettings; + + BackgroundTaskDeferral def; + public async void Run(IBackgroundTaskInstance taskInstance) + { + XmlDocument doc = new XmlDocument(); + def = taskInstance.GetDeferral(); + + if (settings.Values["notificationsHistory"] != null) + doc.LoadXml(settings.Values["notificationsHistory"] as string); + else + { + doc.AppendChild(doc.CreateXmlDeclaration("1.0", "utf-8", null)); + doc.AppendChild(doc.CreateElement("notifications")); + settings.Values.Add("notificationsHistory", doc.InnerXml); + } + + CheckAccount(); + + CheckAnnouncements(); + + def.Complete(); + } + + void CheckAccount() + { + + } + + void CheckAnnouncements() + { + XmlDocument doc = new XmlDocument(); + doc.Load(XmlReader.Create("http://foxgame.hol.es/ftp.xml")); + if ((XmlConvert.ToDateTime((doc["posts"].FirstChild as XmlElement).GetAttribute("time"), XmlDateTimeSerializationMode.Utc) - DateTime.UtcNow).TotalSeconds > 0) + Notifications.Add(new Notification(NotificationType.Internal, + doc["posts"].FirstChild["notificationHeader"].InnerText, + doc["posts"].FirstChild["content"].InnerText, + XmlConvert.ToDateTime((doc["posts"].FirstChild as XmlElement).GetAttribute("time"), + XmlDateTimeSerializationMode.Local), (doc["posts"].FirstChild as XmlElement).GetAttribute("image"))); + if ((bool)settings.Values["newmessagesNotification"] == true) + SendNotification(Notifications.Last()); + } + + void SendNotification(Notification notification) + { + ToastNotificationManager.CreateToastNotifier().Show(notification.GetToast(0)); + } + } +} diff --git a/FoxTube.Background/FoxTube.Background.csproj b/FoxTube.Background/FoxTube.Background.csproj new file mode 100644 index 0000000..1793119 --- /dev/null +++ b/FoxTube.Background/FoxTube.Background.csproj @@ -0,0 +1,136 @@ + + + + + Debug + AnyCPU + {FC9128D7-E3AA-48ED-8641-629794B88B28} + winmdobj + Properties + FoxTube.Background + FoxTube.Background + en-US + UAP + 10.0.17134.0 + 10.0.15063.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + false + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + prompt + 4 + + + x86 + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x86 + false + prompt + + + x86 + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x86 + false + prompt + + + ARM + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM + false + prompt + + + ARM + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM + false + prompt + + + x64 + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x64 + false + prompt + + + x64 + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x64 + false + prompt + + + PackageReference + + + + + + + + + + 6.1.5 + + + + + ..\..\..\..\..\..\Program Files (x86)\Microsoft SDKs\UWPNuGetPackages\microsoft.netcore.universalwindowsplatform\6.1.5\ref\uap10.0.15138\System.Net.WebClient.dll + + + + 14.0 + + + + \ No newline at end of file diff --git a/FoxTube.Background/Notification.cs b/FoxTube.Background/Notification.cs new file mode 100644 index 0000000..eccc230 --- /dev/null +++ b/FoxTube.Background/Notification.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Windows.Data.Xml.Dom; +using Windows.UI; +using Windows.UI.Notifications; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Media.Imaging; + +namespace FoxTube.Background +{ + public enum NotificationType + { + Video, Comment, Post, Internal + } + + public class Notification + { + public string Channel { get; set; } + public string Content { get; set; } + public DateTime TimeStamp { get; set; } + public NotificationType Type { get; set; } + public string Avatar { get; set; } + public string Thumbnail { get; set; } + + public Notification(NotificationType type, + string channelName, string content, DateTime date, + string thumbnailUrl, string avatarUrl = "ms-appx:///Assets/Icons/Profile.png") + { + Channel = channelName; + Content = content; + TimeStamp = date; + Type = type; + Avatar = avatarUrl; + Thumbnail = thumbnailUrl; + } + + public UIElement GetNotification() + { + StackPanel stackPanel = new StackPanel() { Margin = new Thickness(10, 0, 0, 0) }; + + //Channel + switch (Type) + { + case NotificationType.Comment: + stackPanel.Children.Add(new TextBlock() + { + FontSize = 14, + FontStyle = Windows.UI.Text.FontStyle.Italic, + Foreground = new SolidColorBrush(Colors.Gray), + Text = string.Format("{0} replied to your comment", Channel) + }); + break; + + case NotificationType.Internal: + stackPanel.Children.Add(new TextBlock() + { + FontSize = 14, + FontStyle = Windows.UI.Text.FontStyle.Italic, + Foreground = new SolidColorBrush(Colors.Gray), + Text = Channel + }); + break; + + case NotificationType.Post: + stackPanel.Children.Add(new TextBlock() + { + FontSize = 14, + FontStyle = Windows.UI.Text.FontStyle.Italic, + Foreground = new SolidColorBrush(Colors.Gray), + Text = string.Format("{0} published new post", Channel) + }); + break; + + case NotificationType.Video: + stackPanel.Children.Add(new TextBlock() + { + FontSize = 14, + FontStyle = Windows.UI.Text.FontStyle.Italic, + Foreground = new SolidColorBrush(Colors.Gray), + Text = string.Format("{0} uploaded new video", Channel) + }); + break; + } + + //Content + stackPanel.Children.Add(new TextBlock() + { + TextWrapping = TextWrapping.WrapWholeWords, + Text = Content, + }); + //Time + stackPanel.Children.Add(new TextBlock() + { + FontSize = 13, + Foreground = new SolidColorBrush(Colors.Gray), + Text = TimeStamp.ToString() + }); + PersonPicture avatar = new PersonPicture() + { + Height = 50, + VerticalAlignment = VerticalAlignment.Top, + ProfilePicture = string.IsNullOrWhiteSpace(Avatar) ? null : new BitmapImage(new Uri(Avatar)) + }; + + Grid grid = new Grid(); + grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(50) }); + grid.ColumnDefinitions.Add(new ColumnDefinition()); + + grid.Children.Add(avatar); + Grid.SetColumn(stackPanel, 1); + grid.Children.Add(stackPanel); + + Button item = new Button() + { + HorizontalAlignment = HorizontalAlignment.Stretch, + HorizontalContentAlignment = HorizontalAlignment.Left, + Background = new SolidColorBrush(Colors.Transparent), + Content = grid + }; + + return item; + } + + public ToastNotification GetToast(int assignedId) + { + XmlDocument template = new XmlDocument(); + switch (Type) + { + case NotificationType.Comment: + template.LoadXml($@" + + + + {Channel} posted a new comment + {Content} + + + + + + + + + + + "); + break; + + case NotificationType.Video: + template.LoadXml($@" {Channel} uploaded a new video {Content} "); + break; + + case NotificationType.Internal: + string thumb1 = string.IsNullOrWhiteSpace(Thumbnail) ? "Assets/AnnouncementThumb.png" : Thumbnail; + template.LoadXml($@" {Channel} {Content} "); + break; + + case NotificationType.Post: + string thumb2 = string.IsNullOrWhiteSpace(Thumbnail) ? "" : $""; + template.LoadXml($@" {thumb2} {Channel} {Content} "); + break; + } + + return new ToastNotification(template); + } + } +} diff --git a/FoxTube.Background/Properties/AssemblyInfo.cs b/FoxTube.Background/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b4bdea3 --- /dev/null +++ b/FoxTube.Background/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("FoxTube.Background")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("FoxTube.Background")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/FoxTube.Background/ToastTemplates.cs b/FoxTube.Background/ToastTemplates.cs new file mode 100644 index 0000000..cf6483a --- /dev/null +++ b/FoxTube.Background/ToastTemplates.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FoxTube.Background +{ + public static class ToastTemplates + { + public static string Comment = + @" + + + + [ChannelName] posted a new comment + [VideoName] + + + + + + + + + + + + "; + public static string Video = + @" [ChannelName] uploaded a new video [VideoName] "; + public static string Post = + @" [ChannelName] [PostContent] "; + public static string Internal = + @" [Header] "; + public static string Download = + @" Downloading a video... [VideoName] "; + public static string DownloadComplete = + @" Download complete Subnautica - SAY GOODBYE TO SUBNAUTICA! We're Back Home! - Subnautica Ending (Full Release Gameplay) "; + } +} diff --git a/FoxTube.sln b/FoxTube.sln index a73f0db..7357c1c 100644 --- a/FoxTube.sln +++ b/FoxTube.sln @@ -5,16 +5,21 @@ VisualStudioVersion = 15.0.27428.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FoxTube", "FoxTube\FoxTube.csproj", "{2597B816-7316-4D20-BA6C-D78001E89C1A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FoxTube.Background", "FoxTube.Background\FoxTube.Background.csproj", "{FC9128D7-E3AA-48ED-8641-629794B88B28}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU Debug|ARM = Debug|ARM Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU Release|ARM = Release|ARM Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|Any CPU.ActiveCfg = Debug|x86 {2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|ARM.ActiveCfg = Debug|ARM {2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|ARM.Build.0 = Debug|ARM {2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|ARM.Deploy.0 = Debug|ARM @@ -24,6 +29,7 @@ Global {2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|x86.ActiveCfg = Debug|x86 {2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|x86.Build.0 = Debug|x86 {2597B816-7316-4D20-BA6C-D78001E89C1A}.Debug|x86.Deploy.0 = Debug|x86 + {2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|Any CPU.ActiveCfg = Release|x86 {2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|ARM.ActiveCfg = Release|ARM {2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|ARM.Build.0 = Release|ARM {2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|ARM.Deploy.0 = Release|ARM @@ -33,6 +39,22 @@ Global {2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|x86.ActiveCfg = Release|x86 {2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|x86.Build.0 = Release|x86 {2597B816-7316-4D20-BA6C-D78001E89C1A}.Release|x86.Deploy.0 = Release|x86 + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|ARM.ActiveCfg = Debug|ARM + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|ARM.Build.0 = Debug|ARM + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|x64.ActiveCfg = Debug|x64 + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|x64.Build.0 = Debug|x64 + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|x86.ActiveCfg = Debug|x86 + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Debug|x86.Build.0 = Debug|x86 + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|Any CPU.Build.0 = Release|Any CPU + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|ARM.ActiveCfg = Release|ARM + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|ARM.Build.0 = Release|ARM + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|x64.ActiveCfg = Release|x64 + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|x64.Build.0 = Release|x64 + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|x86.ActiveCfg = Release|x86 + {FC9128D7-E3AA-48ED-8641-629794B88B28}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/FoxTube/Assets/Icons/Profile.png b/FoxTube/Assets/Icons/Profile.png new file mode 100644 index 0000000000000000000000000000000000000000..71ac04e9abb701b9e26ddd0fc172dc3bf171623b GIT binary patch literal 22217 zcmZ6zcT`hL7dLzossRL)X6T|w5kVoS^dg`l1W4#zEF^${RH*@_2m*R7V1UpGRZyCW z6yZ|57>c1IT|;lu`#aqGyx&^y_Xmr`Idf+A?Af#HZ}Zg1P>&6PL_iS4cH{ar69|G6 zj((UK!4p=>H!<)F=4GO%1-)w*m;ry#-`6zIgrN5cEPIX&;O}D|*Dbvu=y>DN53I#K z?+yf++1uSrXkVc{2n%-R>+tIm^O{EKcBfC|7 zHH%9-iy5oJ5XZHlN*0Opc~1XasTuN`NwRGbpgzTnD5B^5nO|bX%@twf*bj=<2U^LU2A_lz8D>X{3Ah45qQFyH!2CWG!VhNA3s5 z?A61#_(Ht{7}QK)+TXjLh zuiTDW62og_d1rfhug`)8R2$_>%;`u4g2vhIwc?_C?(#Bw$+66%}Y9B zkL&sJIQOa>e(5g%d7%k4rNihE84Q7jffEeJ#L|qHL|u~_`a)QS17{P+g0uo#M;*sU?4Q2Kw(#5;(*vhIG*qfu+R2aG@5@D z(GGAM$@54;esw2vLRZ1UmwGD~Z17vYeG~Y^0Nzzc5$k|rHHXAV?z;SYog?zhBHDmO zk>&pFxlXdfKQXa0;W(~n?UGSFuG-wbB~7J+x4N)j;fx%u(L5C07SiWWi9D1Dssu-( zT?aeZfHTD*Sr^92&nEKxX{DQbu7h$&Y%XWI0G?hLyJbaHut$qQkxv;p_%5MBs--O^ ztNf9$e{yt}Ll|7>>)z@@taNN5a%b>+^W?z|%w!c737gH(UA_>}ki_UUy*feRAwVtRQQynh1k}d0^ETE96qc|t19}q%bxEaQOMA1a)`+P(tEEf3R>d? z^FnSzgXW+6CpI#{Xe&JY@SQ_-7*j4Mw68_Sy!HZFN8u`3v6hU3lDJXf8-=HL^Ep*i zkfBE{j4=*;l<|Xq{?Gi>soL>e0s*LzUH17OR*20F8EdcpA2! zj3a7OnF)44AOfz%jp7l#TQw*!8JK!X+Ct3V0F2~}(}PZ?H+>tK@)RkBi8`0J(nK1Mfx)aP%5=`NzJPef6bVP^O1HJ7SU{{$QSIadV9e@Po5F?NwQc z1zb)Su!j$@2QzMj3U$fhgc>u&mk|-RJmK|*u8&Vs%i*rjeQW;F#lOKN+-uR@QhTNc2@}HuBIT%t&``3 zS!U|&wN{#xo}b!h3oR~vjQl$EgPHIR95ya*JFqGo6}p#8 zz^&b6nxoJKDL-0F?OiZM33{VXsevMrPjSX{9uw#xJGRu`_AOID7!-0a?e{S zc7?*dbF0kgu-Q&1)5^+bK~*ztoog~XV(oaui%4__Sx_wPosRp=qe`?mdVsYy;Fqx6 zg8()467lDodzm*C2zh@I>$1uTi_?G}`-sl)|=bkrM^R-FUeL=VHX4ZG{(g-#Ekns zT(FmUFj2H5S@)u90IAaar1QCJ8ZVq7TZNuA|Fbn9fY!l!kGu{&%;;^4`+^AtJ!s?IO7u+kcEH zuiXO>T#p$!n!3U_JZ0`N`6IecZ@g+4S8{DxT704DfFNGhmzpnf;-#d0(q+7;wR&He zl3W7531j5Y0Ygv8*rjcj2UvZaXIo&J-74TI)zf7Rd)p~sW#r}TUgJG~Ee6#vv%OVB z>*Q5oXmEO%#b@=gHgA0vLo!3N^ok;w@Ld5SaZna{yF963goqKoA=a5PXumo`CBZYg zpiE|~qT}6K6^Ue1>S>=>;R{;>?rZRqnV;^~U3IMv6TU9yL)}xl?&ac+4l8wrIE9o$ zpYr6MO=%9^o^wH&Ndl}+|;0p?ktxuJtV2i=q5_v`Vszj->38? zg{k>DCvn_JajNcY_M?_SI46=#q~V7acAXqt0jU-_#Em-Q==lUFb+%kZoHi11-FjZn z(M)3Jks433o5-_E+tdcHQlLOW4aDlla6;{c4hCaFxNJ3IY!F&R3y8=SEAN8+2zD98 zDXIBFhAE-~oHdR13y-kx{qhq`r?dK(J*(yw1p>1Ui^1!hW#)*Y%^wHS(4MCI5BaJ~-IrBe z%7A??r-;1*p_Rn*R}Od4(LH#Do@+f8@MS}qHFQl>M4%>~x69FB%uEfdqB_FFU36^E z776=vjV`(vRmHpz)EGlPla%L`b>L>&M#{`{nT}_rfRDe{&oMf z>yppYn#G=wA+u2#~ttRy(+ z;`nnkijWD0*{yC2Zf4xhk6duB_>K{g{=&pDVEnacB9>GE36Wbq)g0gbC1S)~bD*pg z-!&I%EiCm$=$t)Q+f(@m!I`dIW=SS^E&h>zxP?J)7)-6jUEM0tA5K+m$^+Ul#h_XLAvr?Qhc~KZ1 z8M6tU=9hY|$2+pOua)2~NK|*gE1q+G&@Zr(F=63WhMba=^9E-Tj+i{d-AYlFi5D6L zw|=Ck#Xo@^mK085OBPc!56Zh@Hd)mB2*JvoNVw?jHdJdJv4TbGmzM~Qn(rwRER9~S z9gC197pVjUEg6^XYNNw4(qVraMFnC%uZ1|V1k0F_))L5GEK{}^&pr|ioX|9G72Br6AR>*m6c6|(Xc7VIAYH9JMT@gyGEkwm;{`> zU@<#vnv+tZ+j5F%f4HEn)9Dmo;dmEypHeHqxT0V zD8h0Aikq&-1FR4i7@QmcZ#a~!)vw>5b9sa$T@FqA!&T4GFK|BpSUD0`@dJ}y0tl|f zXbu@gEZCqMso{-x?M1|LY>%784O~ajRM26y>9A{~`B&_-CcYGqGxFtznAFt9Bnh|j z+68iy%Cf+tcAG+_G9p>S-s2cwaE_K@embqAb~b{>-uj$kkeo$|yoaLV$>4n{U36{N zscpQ}9rHjWF=pkhm4o66mqsquFQn-6>IJkmX4ul!X9|f9Bi&}=*!L#1bYh>#c88mE z`|-kaK77J^^-`x}@-kXavWe_y-5;+m;Hc}NmX<&7Pv~^dZ)uPXf?!)V!wpjpoXxb0 z-vl7mxDp9nUQ%C5N+${@P*v;^*)7XS4U7iVpU-unYZ=YBvk|e_C>mPqV*YW}{Fc(9 z_}w7p>0`bS_{f6kn)|^|9)(biTWWq)w=8YU^ow)Blt3y1Zr&eG&UbJd$5}{y9Kyq{ zwqPLGfI#J^d7OnArnnM}q6Z6Oq)aa7Srkv>u)DJtgPPpwm<ZfE6|sk_o9BiP z7gQOHM3N^J`&8Ll@I{MlWPJ@dWf8SL}0Z4@7cT6QD@_`&8?~q{y7z&?0p%i2MDg zrMn_$3TqCG2%h67IhdJ` z8(gh>Qda_XT@QXfPzZ%`vxy$n4G$z^oAB>@?x4erotQYBGPUHBz{XKmAnLv{8$k5lyC3 zq?=;(-+>4jV~op}_GA0^QVmy`92APoEGWwgtc4V_ELDbkPX#re5!;`+ZMt7XYa5A% z>N;NKURS0vL*G;IsrwDhG6jBRsbM9mL{A*?&Y+Og*i^BS2Pba`vSOk6!0s91JKE`E2-y&kd%Mv6`FP54q=kR2T>(=b?=c zKs+nS#zh&sK^3Enx!_jZyH4qj50U@Nlg6p4gB^7d z>AQiqsS4vEt%4e?ZbPJW;#LT{j7;2mwYUUVIvCfk_xkS(*OU3%UQGV?!DC{jw=Iyl`%@c-k((I*4O)X@-Eeoq8*%)=)hf#+CnU*)I< zF{Q>}W8}AFgs1s9x70uE*__a{Y|pnQu0>2L>-)%pTL-p|nmymhRAvyZlicSdC}~~6 z`%)BPg60RJQ$-7P{*l3bUJi^S69=a2?ZR0pp1DUvRBDe}PU**(6)_w=#Hd~IP%~oJ zR8~94I(+tbiK(>xk|dbyz<|N+H<(WO8K6zhf8px+yW3@h4^Sf*kZn86g};A0`fS1@ z0g_|Qz#G*g6Vx(ce|vo%UZjP{2YxPTnPJ^~fQC7Y6){@M{80#efmhi{VB`=Rth6<~ zA0-`tgjI(^4>@vqEaR%ImL#S0=%T+h*dRMC=Pi|1Yky!MK4hVe*-Mo;LR&pZxJ7?E z&CTF@2>m)^VgS}*u4QDb%&I(+hPLFzId|{oeI+zZgvD*hZg9rCcU{5HWxCdGp~_g& z7jiT(&6aP-QiW?6HgxAYpoD{gh|GUqzn98(VG37?Ef=Eh+gxv{3t|Qt(VaE8AbX)2 zWlDSC$NrZb9PziRPEJ3pC;Icx>t;1urZ7r`~lBIzHhq0 z@=T%`J9P^7r|FRO|ID11yT;kMuTq60@&ieo&(j@tuK0&qq?5ltEsU&>$%npEXS_$| zaf|)?bR3$|R(C5#dm*&3D0ruHm9Be#uco~Sy}<>2xLffL{tfw4V4)s1nz8hOvH0N) zA(%)w2TJ-MacJl9e~)lSk0emBjHS1O*d1~Jn=7d^KPPaN-jO>)xwtp$T#%MgT#YHp z_Y}G;2U(qhpD!k5o}Pz#V~5qDX<=X33ly?GAR=i^>2WTMp2IMGP>0S!@&e4fz-_25 zU*qmRH#GcUGdCA!wg#p$POUNa@nt}lWh?DInVk;}h>0nsvk*1#-`%ca9zFbfQJB#B zp~SJ0r=K_`DW!GvYQty$-l8)EyT+3l$zXM|6Xf`OtXiBZvCkKsiL?N4d;*1zW^F2z zQ)~_q&d?l}kO;YQF1mCyKFD?aLuM z#3-$0;KWpVDTvcs2Ph=%`&oy}3f5qw6~Mt*l!Tt-4(=j*Mut=V$gF&ZV7sShv9poqcLFbh?OhniE<7`P!E4 zus)E$8>hP!Ljr3~gRMXH@YMsk@@t%Ueq{ppxV8v4ZfJu&)$l$ix{SKuD~u=7a)8n> z#y&pGMt&(J4;Zr@WZ{?FRCLCHJU$WMIDPeUXgoN(vu&T72at{~5o8GgHV4%$h#6w# z6{gozbcUoe%9PX~IMnz$IF99mSvnJ7>x+F$TQc=ivpZbFGraqXFC-0^mr!)BqFu?| z-1GZ^`Ax{-OHp%v@9%?%P6fj2bXZZ#2_Y|^SUpH$fFk7ede2B8*9U*!T^a7Dpq#BjwhmIko|e1C&yQzk$KoY}Oyb-oU_ zouRhGUCW=7QjzStZjc12?@J5jjf^kEqDN;@TXfE|YUu7JyM_t)DLZ;tx-f9>GcsZ4g#lU5g)hLIK(y3$IkCm&J&+&WPkpsN`;On@U~|~M zx0wXO2w1no=3wIPVAwJ6&8UEo*Lt=dly_sXLUyx)?&P&2syil>QroE^C&4b$Hvr47 zAnX794&m8+_TRt+!FoW{8`-(Cj}NN!!6Hg9JXkyeQ2!qdz_-92Z7kq~pOPR_1L`?u zvg>^Pzk*S=BH5?ypGp{Al=xCtRuecl3ULERNG#Vhw=;w zgNf^dVNbyd_PsAZQfEBg!v5tc@?T041T~;mR^U@99ufKGOguO9YdSh}x$(y`zCkiuzWwp*qeP z0Y=&{zVz2jfQ(-Lbg^GdW=azIZ!O;C|3>|fO-H-g2D>?0TMcZz6}T*XQeY^Ea-ly! zkRy-8D8c_bH#n^$A!rL;sHX?qTyjkKlnKbpI-1^v=}7p4KA|TfRNxv04@-g-{$=lf zn?FB(8J3O&JO`rkulSt_ITABo4h_^7TFsTa1Atl}M%y3K{+5cIhr{!9pA!M^!M+-R zCmtO`DI?KlUmx8P%RUurdDn$yF0jF(#$h2Z;P(A{D<0a*chUhZ^s&Y19k?3ZXEuq6 zyTmXdaQ*0x#EcyrF~}bHwC=k;kOWM5T;yVR#O8nR(g)IaDaAIQ>S5jh1l07w>OQK2 zdVWUj;jxeKqfZW84H@n~syhZXAx!4}cl{RsB$X9)M8necPIrH1c40i)KHPx1XL&#M zQ)vFVBVIlCDK4vM=w(G8sfvJ~ix7lMPH!kt{yW^#?FNfD^O}knYR@PN$3AQl|k$QCmNhghUC1>UKS}n zA}aeH%7N;u81~C)?g(3;z>fQF@WGZ28;{)bjxNjjU_3qVrh?1x1Ha;29LR1#ky<`p z20avx(d_ikkBVn$cv9v=V2p3RXlE5m^`w(9&$e0Ao+GFj zCG{Q?66?3TKgP1-2J!KgsJ<}NO4ww622z(;?@0eI?OfLAvfN#z#D*3OBSg3ES)w_B zF{mrS5vi&Sx}DH%DJka!>yy(s`oOtcK^N=Tpx`Og{iRnC^^3x!C`OKstgGWU3M}jpN$&2O(;-nN$CMBWo9Q`<@mzd+OUd`}@=G ztKjoyd~6RNTDx;U2lh8BFjew7nSNCDO|#UJ4@lo+9Z!%)_sx?J3IX2O;>J zG8sKE5!8RTWRLAmq^{@*!wLC$hUYUUd3v612TQOHFZC6`z6@jjmsye{WpK>8a6aSq z=DvP`fi6k3kW(|cZW4DOX9lul3D1}0jU7ipSjO&;fI8j}I-nY;&i+fn&Cfu(nU}k5 z4tw5wLxD9_5@g0s+;4-#F{**Z4eJFsuk06Bu+(@{)u9oLLpKn$k9kImOP94*;D^Pp zVD-`NI<5yctya&*vyNU_kP75GR#n(^Hiv6Pb}%G(UQ!Rd=LwrCn+|GA8iYsS9K=C- z=VQLdgY`>F1+JcwD4)^9?v*F(FeeaqgZ$vjVA-*! zBwg?5MfG!<8WBN@!hd~uzCqq@1ZbbznYF(z3UGIrmNN@XN)lrFe^w#Ka3IBad5(UM zp9G%-$9v3}^RrHqgsbQec&Qu6!Gk0rTye^6F%ZcqyjTCkbDQO4E~AI2;AUQWZEI9R z*M2COIFR-Tk#2-P7|F0&$DkXHi$5Dx%*ufIK%Q0nR@0he#Pt2_mb}sn<5e)O)+ztB zs^HSyD3SvchiaDmTy;0=g&EuxD}j&<`MJwrJ-Gs7-U8j4Z&VL#mZ4c(KpIE_A7xk( z!T0bik9n9wOpZxBr!2Hm(wsKgRe>i;>UulVrKDZ+llr_|V?MKG`4}3;RZlCYxS_Rw zIBgZ+=P4ke+kDnx?N$2+?a-@hccdzZ3i?C(Fpy@xxLX#{JB2KoFEm^bP@Y28K!deN zCNDn7Mya(@OI^QqHoSjNhTtEr3e;?S32!p50V_0p6-5wKb}}6gF8()V&Kq&LAiqX5 z4vN;IjNU}>79OW0>fSaHR5C$LYm?wdMAjOu1ScYv5>(mRkd_f_Si&JFtNb!tzwZb# z?2q3VfDHS=-dCZ=@h!p3)2A-LvShpEQu8O)1<{Rv7=Cf7^3V_*gpQt5(t4EHON9UV?F=$ysL=LY2n%qTs@N8up;1TSCmeYB%2fswp?(uD*<%-XnyFnBrx( z_yFW^%euwl%Furm4074P**%kG8#OMXP}}96{*Z1Aq@7D!;xCY`24#!cw(SC0evsk^ zk>0ej`b!zw!$=tgE03)k;v#g4y!trc41~a5=|t=Zqz>~u7U$r*ko;IpFgCqqRWj6B zV-zS*x~z$N_N>7b?Y;J|bSE$r+Cw=x`hs&!HXEpdvq4mdG3MXePZcPy8(N;)dnhN& z3S(@g**H>0g}Hrio|5AOS6KoS^fB+1e^O67iw+vIoUrGlV1co*zg;zma1^n16$(}%Wb*fRj(aKrM(ua-*`u#6hn_BUc2B!C7o%CA??Fq*&1 zVO@`%S04v8#L}x6k5xZOits~FZ_mJ7{Gy@r{ulad@F%w+?R`bU+vQJxe!qIH~pM(V+HU7Vg2bVx~r20YFGi)+G6j9z)W(z z5inaRU+fwFSk>7ISog1OUBG1R9v6m-9Mw*_M)rdLqOo7ifL&9>3IJr5!B`_MTg7V3 zu3(aEwY;$*nvPfGbv5k&xtuYPmm6%oUfkL?7#*u$-x|;JB(9tc$W1md{OeJn zxg>qNT4#J;lI{;scA&_&7aub_Zg07Tfc4IWuXn8E+A;kGEX|-WA`wOVXVn~3r}blL?U zS&4D-4kPPOZ#$|ZLTh;bnUD?5x^O_O&3HpYqg?!m>c4*n~6PWdyHXb8J4J~cg`a4Yf%~>cYfW=Tlg%nB#S;pDRq%8->)3Ss6Msj1Qd6);6W=8|5)^db#s>KfMLS;6zOWG<;MDlDV4FTI9m0cD9>^-E~TRS1m+83@t* zF`O^u+*X`=4hE%H{zZ+up4%;qEhM_WMdh1i7s=n3pyUN0FEpXSgsHe+IVwMwC zivQxtY~vQki7F|_LHk)(Hx(mp5dB=pFLs>URWlrfw>~PwK`Q_}pdxd97k82P3s+Gt zk(%d0;K|{qGIuHy?@LR~XMHkTcOSx&eAWf}tuMB3ii(^;YH(@BQWLSyH`mMYR%!7j zTLplRKbYl0h}I=$TOVn>bw*-D`()<_sWXxsM@XNHh*=RClfVEgRT=Dt3q2G5{r`ig z3EF>7O&K&BomSgYMknNIFKzU_eD}^y;_x z?puhZkP-w@QCp@$T#vuSO*q4pv2#3W@u}fFC1kGSRL_q*@2m9~!z7(Y&gYkw#oH=` z(vYYAW~QXMq13IIgolMt2yLT*>ZC`^9|*SxIk5 zV!IFT4{cWi^Z6U-hLvvmTHA3Zt7X~^u_PgxfCNc_!pSQ>1l|M#XTG47hkK>xtu(vX zHtGJkuK4veqRn7)mvVpsiR_@hu;Bn8yw~WMqj@5D=SsypM=%j;U0!?7!%>d`x{L2D8*=O@LTipbOXhr-^(?UGf9$+4_x>Aq!+bs=yHS<{qg?EgF)Un>y)7LIlK5c;mB*D zyn!|SvgtOzw4o=A97)GfVH*|5Uix=$b)Ua&adR{;*Ij-g8Lvlvik@}fDxZ+wSz4fe z>yY>l&&u}y;#m#hJl*dJEjV}C?86ac4ey-By|v3K)}g{K2M5#IkO08T(pjk}DHRxL-zm*kWUO1=5ACv zT#o`^T1Vqazl;XGp1t9u|BDj7&O#~iF3OLXYEz?kU{+vK&-t{)1AtER0P`!J?lyE; zQsC!|x0#_Bv||uAdKXbS=l2LKX$Ek_C({|Gd6mq%!o8mZsE-(>Vjnfto3l+P9x14i zt~XdYd>!DJw|_IGw{&j}r#n=K)Lj>Y#KVtXmdm9U(%czjuuMm8R%gmiCav!ufFW5b z1sH2hkld2&H*a5BvSP$VclJ#274FAlh&U}%GgLi(pqBqWO zKut;g-;w~Q{vf~fF;Ok>P#91v){iF%E(?|gQ2f>*Pu9AX7Gqeq>P9B3S zNLPP#>QV&8+d_V;vsHvZI>MZTgj|($|AWy(P_|ZGwnaT@Dd7Cl?**OI0IVeNuo(o*d6`2u~F{hz=KqHxCC}_)C?k@ z-n?Xrc*$SK3rJwrp^SAQq6#^`i2sM)R_NX;E+{P|)-HfUp6+HQjVDHSYywA7fj|Jw z17YE)kR>3XH?wq?6A`|+EhZd&9yrnha&t!r$;}-V!Vj7vaxh|B{1GLfzrYx1D&Y6w z|7$N$-@VBTahr(wKgHMgd8dqPYj6XzAjrMf;}c}Va9E>85AhaW=#L+>B+=<)!LFeO z*9rxDx7+|P9PoI!EcVLvxUmk~kFpj?tXG%pe=r6>uh9Thsbe%*l>&GW^nq8S&bIhoC-SRs;PEFQ z-0bY$qU7Yds}KJ?7YEflKsQy)EtD3%>677qVUl(8JmUQ&P*f>dxhA z6$ap1QKGKVXVBP`)R4CC?;kUS-dd@`P5c@yK=<~}Rpo0nJp}2o| zACqExsJO6@-#lN}b1^M`+vj|GsI|snW&N+83EvJk(qi(S*fIG{yFV}(%U7d6LcM0> zZ4Zsc6p@(?V{=jWH)lSMCP=y;+O?#uZ&o%QR2vmN>ITSceV;E43Fd~6tP|#b$4EpU z(A15#?obu{fHRH;fLu)-ABlD4wrTTo;cA?5p>&8b0deYfj(x$T=|cwcH94hJOIK30K@onXFeO&7GtW zZsY72s4*@j*ntYduenCC)5#e)IoEmCqo)zD|kHTsvXoP^bLlF3JlC{!@-WdpCbaap&BE+v@K;q@x# z+8WP09CSHX{R+bu7)i;xgs&iZ)rr#_#IST}l5RugkZplSe0*#<^MIkSRCeE26{Y!X zmtUPK<(pi$HP(I#=WTC#qQk-fsH8J*rR;La(7RCp)8EgGXjXgb!j`shC8ba&3Qe1M zq;M_~oAg06=xJP5sKVtE(|&EQAHL&ZVHqHbb|_`W24>un|~sB)6& z*;CrgAmXst2~+cC{s~)f=A#`33Kf{aH9>$&y{Yrym|&F4#Yffb8BuFL2Y0vHe|YBd zoUDDrpqh2h0~KN5g=*&vyGo6lG^mS)QWVMa~0rgz`@(mrHm>fdiOv8C}(B` z&HuG{Ln$O(XDsH0rU+EyJL*J9ryW)YF;AvXEuUhjJ`>~$5cj~N49=h}8F3%jsxvv#cx?`6Fpe<}JR#^8$9`dA}Knt@C#$c0(iMg?~^hJ+xT z(x(=LK%d(cy9g3`IrB14{Wo>SFCkk?3{GRwJu6qGzSts`G#HS4#$UqQ^DD(v&n z-&YBqCY0?^L}1?Ha+#NW2C*yw-);^-h#)1Nl{;}MIsPQ}$-?E}i-G|m@tD%)CD4(O zZr;s2xJy^eSzeXqpoD#V+;o3{f^mpEO0`Q#fjrEO2tQll2ClWA{Y;L>vE#pYKM~Yc ztI{pRTHV>vQL|mRfCrV`j&-Nuw|D`#?nYCuTIzkusPqqJaZHA)F)%K3z`SZ2yA7Ir zMQYaihad|K>WuqMDrQC$;J1Ljmd}B^BnG0*!rXo^KZ}`T=Pp{A+tEk4VTj zEPk!)fF;lOMF8IxjiAGTTg#fCH~4nFPc1H<;3e6;bG|5oJ(oT%MTLmcyBM zZ{V(j3mgriF<_EsOhosIG1Dz7#iW>N(*U)NzONm!u(z1~LH#3=^g2O%K!NzO92y3I zt_C`pU5-neW%^P*RIe36!+}(a9`J!U^N!gVfe>(*%8c!(&9>T*L>pg4YN+TjtlmYa z8+{E5M&>6Xe%Gz31Q}nh%iD5AGH}Gr4*mn;-vcxFb8!Q~V&?wVC$)E7 z=i#=3i7fZ^-3xUcdDjO4@Cu&Sd}NNlR#30k03c883V*}vEZ7rn-CYEO%K;Bs%*Vv| zi>0GIcXJtVyMN@31YZZE6Ui^|sRdI6+4T}oeEp4O9nEyGM_R2X=k)9aYXZyT&Vf!AYg!O}U$>f9)lG@(N=gs@3hpfW|+=p1E#)u$*UpfruW$C*x* zX{yrAdfl3U2apv}gS7;Se)m4J8%+nwQvPkzSAIl6*jwkq)68uxbXu;eVflw(s^$ry zZ(n6Hf{N-hqI%%!>Xd46Oic2=T}g23

mRa&wW}ju9n!hGv_!&OO(6XFmEB=#KoQ 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 literal 0 HcmV?d00001 diff --git a/FoxTube/Assets/Icons/Send.png b/FoxTube/Assets/Icons/Send.png new file mode 100644 index 0000000000000000000000000000000000000000..2c73623783b73f4cd1fa53cc01a40fc4968575c0 GIT binary patch literal 11584 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 F{}*HOIDY^D literal 0 HcmV?d00001 diff --git a/FoxTube/Classes/Methods.cs b/FoxTube/Classes/Methods.cs index 35759d7..182fbb7 100644 --- a/FoxTube/Classes/Methods.cs +++ b/FoxTube/Classes/Methods.cs @@ -1,5 +1,7 @@ -using System; +using MyToolkit.Multimedia; +using System; using System.Text.RegularExpressions; +using System.Threading.Tasks; using Windows.UI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -70,5 +72,40 @@ namespace FoxTube else block.Inlines.Add(new Run { Text = item }); } } + + public static string QualityToString(YouTubeQuality quality) + { + switch(quality) + { + case YouTubeQuality.NotAvailable: + return "N/A"; + case YouTubeQuality.Quality1080P: + return "1080p"; + case YouTubeQuality.Quality144P: + return "144p"; + case YouTubeQuality.Quality2160P: + return "2160p"; + case YouTubeQuality.Quality240P: + return "240p"; + case YouTubeQuality.Quality270P: + return "270p"; + case YouTubeQuality.Quality360P: + return "360p"; + case YouTubeQuality.Quality480P: + return "480p"; + case YouTubeQuality.Quality520P: + return "520p"; + case YouTubeQuality.Quality720P: + return "720p"; + case YouTubeQuality.QualityHigh: + return "[Audio only] High quality"; + case YouTubeQuality.QualityLow: + return "[Audio only] Low quality"; + case YouTubeQuality.QualityMedium: + return "[Audio only] Medium quality"; + default: + return "Unknown"; + } + } } } diff --git a/FoxTube/Classes/Notification.cs b/FoxTube/Classes/Notification.cs index 52204d3..cdb02bb 100644 --- a/FoxTube/Classes/Notification.cs +++ b/FoxTube/Classes/Notification.cs @@ -3,6 +3,11 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Windows.Data.Xml.Dom; +using Windows.UI; +using Windows.UI.Notifications; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Imaging; @@ -10,35 +15,157 @@ namespace FoxTube { public enum NotificationType { - NewVideo, NewComment, NewPost, Update + Video, Comment, Post, Internal } public class Notification { - public string Header { get; set; } - public string message { get; set; } - public DateTime time { get; set; } + public string Channel { get; set; } + public string Content { get; set; } + public DateTime TimeStamp { get; set; } public NotificationType Type { get; set; } public string Avatar { get; set; } public string Thumbnail { get; set; } - public Notification(string header, string content, DateTime date, NotificationType type, string avatar = "ms-appx:///Assets/NewsAvatar.png", string thumbnail = "ms-appx:///Assets/AnnouncementThumb.png") + public Notification(NotificationType type, + string channelName, string content, DateTime date, + string thumbnailUrl, string avatarUrl = "ms-appx:///Assets/Icons/Profile.png") { - Header = header; - message = content; - time = date; + Channel = channelName; + Content = content; + TimeStamp = date; Type = type; - Avatar = avatar; - Thumbnail = thumbnail; + Avatar = avatarUrl; + Thumbnail = thumbnailUrl; } - public string returnTimecode(bool twelveFormat = true) + public UIElement GetNotification() { - TimeSpan diff = DateTime.Now - time; - if (diff.TotalDays == 0) - return string.Format("{0}:{1}", time.Hour, time.Minute); - else - return string.Format("{0}/{1} {2}:{3}", time.Month, time.Day, time.Hour, time.Minute); + StackPanel stackPanel = new StackPanel() { Margin = new Thickness(10, 0, 0, 0) }; + + //Channel + switch (Type) + { + case NotificationType.Comment: + stackPanel.Children.Add(new TextBlock() + { + FontSize = 14, + FontStyle = Windows.UI.Text.FontStyle.Italic, + Foreground = new SolidColorBrush(Colors.Gray), + Text = string.Format("{0} replied to your comment", Channel) + }); + break; + + case NotificationType.Internal: + stackPanel.Children.Add(new TextBlock() + { + FontSize = 14, + FontStyle = Windows.UI.Text.FontStyle.Italic, + Foreground = new SolidColorBrush(Colors.Gray), + Text = Channel + }); + break; + + case NotificationType.Post: + stackPanel.Children.Add(new TextBlock() + { + FontSize = 14, + FontStyle = Windows.UI.Text.FontStyle.Italic, + Foreground = new SolidColorBrush(Colors.Gray), + Text = string.Format("{0} published new post", Channel) + }); + break; + + case NotificationType.Video: + stackPanel.Children.Add(new TextBlock() + { + FontSize = 14, + FontStyle = Windows.UI.Text.FontStyle.Italic, + Foreground = new SolidColorBrush(Colors.Gray), + Text = string.Format("{0} uploaded new video", Channel) + }); + break; + } + + //Content + stackPanel.Children.Add(new TextBlock() + { + TextWrapping = TextWrapping.WrapWholeWords, + Text = Content, + }); + //Time + stackPanel.Children.Add(new TextBlock() + { + FontSize = 13, + Foreground = new SolidColorBrush(Colors.Gray), + Text = TimeStamp.ToString() + }); + PersonPicture avatar = new PersonPicture() + { + Height = 50, + VerticalAlignment = VerticalAlignment.Top, + ProfilePicture = string.IsNullOrWhiteSpace(Avatar) ? null : new BitmapImage(new Uri(Avatar)) + }; + + Grid grid = new Grid(); + grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(50) }); + grid.ColumnDefinitions.Add(new ColumnDefinition()); + + grid.Children.Add(avatar); + Grid.SetColumn(stackPanel, 1); + grid.Children.Add(stackPanel); + + Button item = new Button() + { + HorizontalAlignment = HorizontalAlignment.Stretch, + HorizontalContentAlignment = HorizontalAlignment.Left, + Background = new SolidColorBrush(Colors.Transparent), + Content = grid + }; + + return item; + } + + public ToastNotification GetToast(int assignedId) + { + System.Xml.XmlDocument template = new System.Xml.XmlDocument(); + switch(Type) + { + case NotificationType.Comment: + template.Load("ms-appx:///Assets/Notifications/Comment.xml"); + template["toast"]["visual"]["binding"]["image"].SetAttribute("src", Avatar); + template["toast"]["visual"]["binding"].ChildNodes[1].InnerText = string.Format("{0} posted a new comment", Channel); + template["toast"]["visual"]["binding"].LastChild.InnerText = Content; + break; + + case NotificationType.Video: + template.Load("ms-appx:///Assets/Notifications/Video.xml"); + (template["toast"]["visual"]["binding"].FirstChild as System.Xml.XmlElement).SetAttribute("src", Avatar); + template["toast"]["visual"]["binding"].ChildNodes[1].InnerText = string.Format("{0} uploaded a new video", Channel); + template["toast"]["visual"]["binding"].ChildNodes[2].InnerText = Content; + (template["toast"]["visual"]["binding"].LastChild as System.Xml.XmlElement).SetAttribute("src", Thumbnail); + break; + + case NotificationType.Internal: + template.Load("ms-appx:///Assets/Notifications/Internal.xml"); + if(!string.IsNullOrWhiteSpace(Thumbnail)) + (template["toast"]["visual"]["binding"].FirstChild as System.Xml.XmlElement).SetAttribute("src", Avatar); + template["toast"]["visual"]["binding"].LastChild.InnerText = Channel; + break; + + case NotificationType.Post: + template.Load("ms-appx:///Assets/Notifications/Video.xml"); + (template["toast"]["visual"]["binding"].FirstChild as System.Xml.XmlElement).SetAttribute("src", Thumbnail); + (template["toast"]["visual"]["binding"].ChildNodes[1] as System.Xml.XmlElement).SetAttribute("src", Avatar); + template["toast"]["visual"]["binding"].ChildNodes[2].InnerText = string.Format("{0} published a new post", Channel); + template["toast"]["visual"]["binding"].ChildNodes[3].InnerText = Content; + break; + } + + XmlDocument toastXml = new XmlDocument(); + toastXml.LoadXml(template.InnerXml); + + return new ToastNotification(toastXml); } } } diff --git a/FoxTube/Classes/ObjectEventArgs.cs b/FoxTube/Classes/ObjectEventArgs.cs new file mode 100644 index 0000000..d5716c0 --- /dev/null +++ b/FoxTube/Classes/ObjectEventArgs.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FoxTube +{ + public class ObjectEventArgs : EventArgs + { + public List Parameters = new List(); + public ObjectEventArgs(params object[] args) + { + foreach (object a in args) + Parameters.Add(a); + } + } +} diff --git a/FoxTube/Classes/SecretsVault.cs b/FoxTube/Classes/SecretsVault.cs index 588957d..332c55f 100644 --- a/FoxTube/Classes/SecretsVault.cs +++ b/FoxTube/Classes/SecretsVault.cs @@ -24,47 +24,50 @@ using Google.Apis.Util.Store; using Google.Apis.YouTube.v3; using Google.Apis.Auth.OAuth2.Responses; using Windows.Storage; +using Google.Apis.YouTube.v3.Data; namespace FoxTube { public class SecretsVault { #region Static Information - public static NetworkCredential EmailCredential { get => new NetworkCredential("youwillneverknowthisadress@gmail.com", "thisisthepassword12345"); } - public static SecretsVault Vault { get => Methods.MainPage.Vault; } + public static NetworkCredential EmailCredential => new NetworkCredential("youwillneverknowthisadress@gmail.com", "thisisthepassword12345"); + private static ClientSecrets Secrets => new ClientSecrets() + { + ClientId = "349735264870-2ekqlm0a4mkg3mmrfcv90s3qp3o15dq0.apps.googleusercontent.com", + ClientSecret = "BkVZOAaCU2Zclf0Zlicg6y2_" + }; + + public static SecretsVault Vault => Methods.MainPage.Vault; + public static string AccountId => Methods.MainPage.Vault.userId; - private static ClientSecrets Secrets - { - get => new ClientSecrets() - { - ClientId = "349735264870-2ekqlm0a4mkg3mmrfcv90s3qp3o15dq0.apps.googleusercontent.com", - ClientSecret = "BkVZOAaCU2Zclf0Zlicg6y2_" - }; - } - public static bool IsAuthorized { get => Vault.IsLoged; } + public static bool IsAuthorized => Vault.IsLoged; - public static YouTubeService NoAuthService + public static Google.Apis.YouTube.v3.Data.Channel UserChannel => Methods.MainPage.Vault.channel; + public static List> WatchLater => Methods.MainPage.Vault.later; + public static List UserHistory => Methods.MainPage.Vault.history; + public static List Subscriptions => Methods.MainPage.Vault.subs; + + public static YouTubeService NoAuthService => new YouTubeService(new BaseClientService.Initializer() { - get => new YouTubeService(new BaseClientService.Initializer() - { - ApiKey = "AIzaSyBgHrCnrlzlVmk0cJKL8RqP9Y8x6XSuk_0", - ApplicationName = "FoxTube" - }); - } + ApiKey = "AIzaSyBgHrCnrlzlVmk0cJKL8RqP9Y8x6XSuk_0", + ApplicationName = "FoxTube" + }); - public static YouTubeService Service + public static YouTubeService Service => new YouTubeService(new BaseClientService.Initializer() { - get => new YouTubeService(new BaseClientService.Initializer() - { - HttpClientInitializer = Vault.Credential, - ApplicationName = "FoxTube" - }); - } + HttpClientInitializer = Vault.Credential, + ApplicationName = "FoxTube" + }); #endregion #region Object containers public bool IsLoged = false; public string userId; + public List history = new List(); + public List subs = new List(); + public List> later = new List>(); + public Google.Apis.YouTube.v3.Data.Channel channel; public UserCredential Credential; public event EventHandler AuthorizationStateChanged; @@ -78,9 +81,56 @@ namespace FoxTube AuthorizationStateChanged.Invoke(this, null); } - var request = Service.Channels.List("snippet"); + var request = Service.Channels.List("snippet,contentDetails"); request.Mine = true; - userId = (await request.ExecuteAsync()).Items[0].Id; + channel = (await request.ExecuteAsync()).Items[0]; + userId = channel.Id; + + var historyRequest = Service.PlaylistItems.List("snippet"); + historyRequest.PlaylistId = channel.ContentDetails.RelatedPlaylists.WatchHistory; + historyRequest.MaxResults = 50; + var response = await historyRequest.ExecuteAsync(); + history.Clear(); + foreach (PlaylistItem i in response.Items) + history.Add(i.Snippet.ResourceId.VideoId); + + var laterRequest = Service.PlaylistItems.List("snippet"); + laterRequest.PlaylistId = channel.ContentDetails.RelatedPlaylists.WatchLater; + laterRequest.MaxResults = 50; + var laterResponse = await laterRequest.ExecuteAsync(); + later.Clear(); + + foreach (PlaylistItem i in laterResponse.Items) + later.Add(new KeyValuePair(i.Id, i.Snippet.ResourceId.VideoId)); + + string nextToken = laterResponse.NextPageToken; + while (nextToken != null) + { + laterRequest.PageToken = nextToken; + laterResponse = await laterRequest.ExecuteAsync(); + foreach (PlaylistItem i in laterResponse.Items) + later.Add(new KeyValuePair(i.Id, i.Snippet.ResourceId.VideoId)); + + nextToken = laterResponse.NextPageToken; + } + + SubscriptionsResource.ListRequest subRequest = SecretsVault.Service.Subscriptions.List("snippet"); + subRequest.Mine = true; + subRequest.MaxResults = 50; + SubscriptionListResponse subResponse = await subRequest.ExecuteAsync(); + subs.Clear(); + + foreach (Subscription s in subResponse.Items) + subs.Add(s.Snippet.ResourceId.ChannelId); + + nextToken = subResponse.NextPageToken; + while(nextToken != null) + { + subRequest.PageToken = nextToken; + subResponse = await subRequest.ExecuteAsync(); + foreach (Subscription s in subResponse.Items) + subs.Add(s.Snippet.ResourceId.ChannelId); + } } public async void Deauthenticate() diff --git a/FoxTube/Controls/DownloadAgent.cs b/FoxTube/Controls/DownloadAgent.cs new file mode 100644 index 0000000..511fdb3 --- /dev/null +++ b/FoxTube/Controls/DownloadAgent.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.IO; +using MyToolkit.Multimedia; + +namespace FoxTube.Controls +{ + public class DownloadAgent + { + public List Items = new List(); + + XmlDocument doc = new XmlDocument(); + string path = $@"{Directory.GetCurrentDirectory()}\DownloadHistory.xml"; + public DownloadAgent() + { + if (File.Exists(path)) + doc.Load(path); + else + { + doc.AppendChild(doc.CreateXmlDeclaration("1.0", "utf-8", null)); + doc.AppendChild(doc.CreateElement("downloads")); + doc.Save(path); + } + + foreach(XmlElement e in doc["downloads"].ChildNodes) + try + { + Items.Add(new DownloadItem( + e["details"]["id"].InnerText, + e["title"].InnerText, + e["snippet"]["author"].InnerText, + e["snippet"]["image"].InnerText, + e["snippet"]["duration"].InnerText, + e["snippet"]["quality"].InnerText, + e["details"]["path"].InnerText)); + } + catch { } + } + + public void Add(string id, YouTubeQuality quality) + { + DownloadItem item = new DownloadItem(id, quality); + item.DownloadCanceled += Item_DownloadCanceled; + item.DownloadComplete += Item_DownloadComplete; + + Items.Add(item); + } + + private void Item_DownloadComplete(object sender, EventArgs e) + { + doc["downloads"].AppendChild((e as ObjectEventArgs).Parameters[0] as XmlElement); + } + + private void Item_DownloadCanceled(object sender, EventArgs e) + { + Items.Remove(sender as DownloadItem); + } + } +} diff --git a/FoxTube/Controls/DownloadItem.xaml b/FoxTube/Controls/DownloadItem.xaml new file mode 100644 index 0000000..8c5e2fe --- /dev/null +++ b/FoxTube/Controls/DownloadItem.xaml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +