Archived
1
0

DownloadAgent

This commit is contained in:
Michael Gordeev
2018-07-11 18:58:05 +03:00
parent 839be10ed2
commit 71cc12106c
39 changed files with 1359 additions and 55 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+38 -1
View File
@@ -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";
}
}
}
}
+143 -16
View File
@@ -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);
}
}
}
+18
View File
@@ -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<object> Parameters = new List<object>();
public ObjectEventArgs(params object[] args)
{
foreach (object a in args)
Parameters.Add(a);
}
}
}
+77 -27
View File
@@ -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<KeyValuePair<string, string>> WatchLater => Methods.MainPage.Vault.later;
public static List<string> UserHistory => Methods.MainPage.Vault.history;
public static List<string> 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<string> history = new List<string>();
public List<string> subs = new List<string>();
public List<KeyValuePair<string, string>> later = new List<KeyValuePair<string, string>>();
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<string, string>(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<string, string>(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()
+63
View File
@@ -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<DownloadItem> Items = new List<DownloadItem>();
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);
}
}
}
+52
View File
@@ -0,0 +1,52 @@
<UserControl
x:Class="FoxTube.Controls.DownloadItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FoxTube.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="100"
d:DesignWidth="1500">
<!--<Button HorizontalAlignment="Stretch" Background="WhiteSmoke" Height="100" Padding="0" HorizontalContentAlignment="Stretch"/>-->
<Grid Background="WhiteSmoke" Height="100">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Image Name="thumbnail" Source="/Assets/videoThumbSample.png"/>
<TextBlock Name="title" Grid.Column="1" Margin="5" TextWrapping="WrapWholeWords" Text="[Title]" FontSize="20" MaxLines="3"/>
<StackPanel Grid.Column="2" Margin="5">
<TextBlock Text="Extension:" Foreground="Gray"/>
<TextBlock Text="Quality:" Foreground="Gray" Name="quality"/>
<TextBlock Text="Duration:" Foreground="Gray" Name="duration"/>
<TextBlock Text="Author:" Foreground="Gray" Name="channel"/>
</StackPanel>
<StackPanel Name="donePanel" Grid.Column="3" Orientation="Horizontal" Visibility="Collapsed">
<Button Name="open" Click="open_Click" Width="80" Height="80" Padding="0" Background="Transparent">
<StackPanel>
<TextBlock Text="&#xED25;" FontFamily="Segoe MDL2 Assets" FontSize="30" HorizontalAlignment="Center"/>
<TextBlock Text="Open" TextWrapping="WrapWholeWords" HorizontalTextAlignment="Center"/>
</StackPanel>
</Button>
<Button Name="gotoOriginal" Click="gotoOriginal_Click" Width="80" Height="80" Padding="0" Background="Transparent">
<StackPanel>
<TextBlock Text="&#xE2B4;" FontFamily="Segoe MDL2 Assets" FontSize="30" HorizontalAlignment="Center"/>
<TextBlock Text="Go to original" TextWrapping="WrapWholeWords" HorizontalTextAlignment="Center" HorizontalAlignment="Center"/>
</StackPanel>
</Button>
</StackPanel>
<StackPanel Name="progressPanel" Grid.Column="4" Margin="10">
<TextBlock Name="status" Text="Initializing..." HorizontalAlignment="Left"/>
<ProgressBar Name="progressBar" Width="200" IsIndeterminate="True" Foreground="Red"/>
<TextBlock Name="perc" Text="--%"/>
<Button Content="Cancel" Name="cancel" Click="cancel_Click" HorizontalAlignment="Right"/>
</StackPanel>
</Grid>
</UserControl>
+162
View File
@@ -0,0 +1,162 @@
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;
using System.Net;
using Windows.UI.Xaml.Media.Imaging;
using Windows.System;
using MyToolkit.Multimedia;
using Windows.Storage;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using Windows.UI.Popups;
using System.Xml;
// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
namespace FoxTube.Controls
{
public sealed partial class DownloadItem : UserControl
{
string uri;
string Id;
public event EventHandler DownloadCanceled;
public event EventHandler DownloadComplete;
WebClient client = new WebClient();
ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
public DownloadItem(string id, YouTubeQuality q)
{
this.InitializeComponent();
client.DownloadFileCompleted += DownloadCompleted;
client.DownloadProgressChanged += UpdateInfo;
try
{
Download(id, q);
}
catch
{
if (client.IsBusy)
client.CancelAsync();
new MessageDialog("We were unable to download video due to connection problems or internal YouTube server error. Please, try again later.", "Failed to download").ShowAsync();
throw new FileNotFoundException();
}
}
async void Download(string id, YouTubeQuality q)
{
Id = id;
VideosResource.ListRequest request = SecretsVault.NoAuthService.Videos.List("snippet");
request.Id = id;
var response = (await request.ExecuteAsync()).Items[0];
Uri url = (await YouTube.GetVideoUriAsync(id, q)).Uri;
client.DownloadFileAsync(url, settings.Values["defaultDownload"] as string + $@"\{response.Snippet.Title} - {response.Snippet.ChannelTitle}.mp4");
thumbnail.Source = new BitmapImage(new Uri(response.Snippet.Thumbnails.Standard.Url));
title.Text = response.Snippet.Title;
channel.Text = $"Author: {response.Snippet.ChannelTitle}";
quality.Text = $"Quality: {Methods.QualityToString(q)}"; TimeSpan ts = XmlConvert.ToTimeSpan(response.ContentDetails.Duration);
duration.Text = string.Format("Duration: {0}{1:00}:{2:00} | ", ts.Hours == 0 ? "" : ts.Hours + ":", ts.Minutes, ts.Seconds);
uri = settings.Values["defaultDownload"] as string + $@"\{response.Snippet.Title} - {response.Snippet.ChannelTitle}.mp4";
status.Text = "Downloading...";
perc.Text = "0%";
}
private void UpdateInfo(object sender, DownloadProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
perc.Text = $"{e.ProgressPercentage}%";
}
private void DownloadCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
progressPanel.Visibility = Visibility.Collapsed;
donePanel.Visibility = Visibility.Visible;
XmlElement node = new XmlDocument().CreateElement("item");
node.InnerXml = $@"<item>
<title{title.Text}></title>
<snippet>
<quality>{quality.Text.Split(' ')[1]}</quality>
<duration>{duration.Text.Split(' ')[1]}</duration>
<author>{channel.Text.Split(' ')[1]}</author>
</snippet>
<details>
<path>{uri}</path>
<id>{Id}</id>
</details>
</item>";
DownloadComplete.Invoke(this, new ObjectEventArgs(node));
}
public DownloadItem(string videoId, string videoName, string channelName, string thumbUrl, string length, string videoQuality, string path)
{
if (!File.Exists(path))
throw new FileNotFoundException();
title.Text = videoName;
thumbnail.Source = new BitmapImage(new Uri(thumbUrl));
quality.Text = $"Quality: {videoQuality}";
duration.Text = $"Duration: {length}";
channel.Text = $"Author: {channelName}";
progressPanel.Visibility = Visibility.Collapsed;
donePanel.Visibility = Visibility.Visible;
uri = path;
Id = videoId;
}
private async void open_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(uri))
await Launcher.LaunchUriAsync(new Uri(uri));
}
private void gotoOriginal_Click(object sender, RoutedEventArgs e)
{
Methods.MainPage.GoToVideo(Id);
}
private async void cancel_Click(object sender, RoutedEventArgs e)
{
if(client.IsBusy)
{
MessageDialog dialog = new MessageDialog("Are you sure?", "Cancelling download");
dialog.Commands.Add(new UICommand("Yes", (command) =>
{
status.Text = "Cancelling...";
progressBar.IsIndeterminate = true;
cancel.IsEnabled = false;
client.CancelAsync();
DownloadCanceled.Invoke(this, null);
}));
dialog.Commands.Add(new UICommand("No"));
dialog.DefaultCommandIndex = 1;
await dialog.ShowAsync();
}
}
}
}
+5
View File
@@ -20,6 +20,11 @@
</Grid.RowDefinitions>
<Grid Grid.Row="0" Background="Black">
<Image Name="thumbnail" Source="Assets/videoThumbSample.png" Stretch="Fill"/>
<Grid Background="#7FFFFFFF" Name="watched" Visibility="Collapsed">
<StackPanel Margin="5" Background="WhiteSmoke" VerticalAlignment="Top" HorizontalAlignment="Left" Padding="5,2,5,2" BorderBrush="Gray" BorderThickness="1">
<TextBlock Text="Watched" Foreground="Gray" FontSize="12"/>
</StackPanel>
</Grid>
<StackPanel Margin="0,0,5,5" Background="WhiteSmoke" VerticalAlignment="Bottom" HorizontalAlignment="Right" Padding="5,2,5,0" Height="20">
<TextBlock Name="info" Text="[Duration] | [Published at]" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Gray" FontSize="12"/>
</StackPanel>
+4 -1
View File
@@ -58,7 +58,7 @@ namespace FoxTube
views.Text = string.Format("{0} views", item.Statistics.ViewCount);
string duration;
if (item.ContentDetails.Duration != null || item.ContentDetails.Duration != "")
if (!string.IsNullOrWhiteSpace(item.ContentDetails.Duration))
{
TimeSpan ts = XmlConvert.ToTimeSpan(item.ContentDetails.Duration);
duration = string.Format("{0}{1:00}:{2:00} | ", ts.Hours == 0 ? "" : ts.Hours + ":", ts.Minutes, ts.Seconds);
@@ -94,6 +94,9 @@ namespace FoxTube
avatar.ProfilePicture = new BitmapImage(new Uri(item1.Snippet.Thumbnails.Medium.Url));
channelName.Text = item1.Snippet.Title;
if (SecretsVault.UserHistory.Contains(item.Id))
watched.Visibility = Visibility.Visible;
}
private async void Button_Click(object sender, RoutedEventArgs e)
+8 -1
View File
@@ -11,7 +11,14 @@
<Button Padding="0" Background="WhiteSmoke" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" Margin="2" Click="Button_Click">
<StackPanel Orientation="Horizontal">
<Image Name="thumbnail" Source="Assets/videoThumbSample.png"/>
<Grid>
<Image Name="thumbnail" Source="Assets/videoThumbSample.png"/>
<Grid Background="#7FFFFFFF" Name="watched" Visibility="Collapsed">
<StackPanel Margin="5" Background="WhiteSmoke" VerticalAlignment="Top" HorizontalAlignment="Left" Padding="5,2,5,2" BorderBrush="Gray" BorderThickness="1">
<TextBlock Text="Watched" Foreground="Gray" FontSize="12"/>
</StackPanel>
</Grid>
</Grid>
<Grid Margin="10" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
+3
View File
@@ -91,6 +91,9 @@ namespace FoxTube
avatar.ProfilePicture = new BitmapImage(new Uri(item1.Snippet.Thumbnails.Medium.Url));
channelName.Text = item1.Snippet.Title;
channelSubs.Text = string.Format("{0} subscribers", item1.Statistics.SubscriberCount);
if (SecretsVault.UserHistory.Contains(item.Id))
watched.Visibility = Visibility.Visible;
}
private void channelLink_Click(object sender, RoutedEventArgs e)
+45
View File
@@ -97,9 +97,14 @@
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Classes\Methods.cs" />
<Compile Include="Classes\ObjectEventArgs.cs" />
<Compile Include="Controls\CommentCard.xaml.cs">
<DependentUpon>CommentCard.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\DownloadAgent.cs" />
<Compile Include="Controls\DownloadItem.xaml.cs">
<DependentUpon>DownloadItem.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\SuggestionsQueries.xaml.cs">
<DependentUpon>SuggestionsQueries.xaml</DependentUpon>
</Compile>
@@ -112,6 +117,9 @@
<Compile Include="Pages\CommentsPage.xaml.cs">
<DependentUpon>CommentsPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\Downloads.xaml.cs">
<DependentUpon>Downloads.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\Feedback.xaml.cs">
<DependentUpon>Feedback.xaml</DependentUpon>
</Compile>
@@ -173,6 +181,8 @@
<Content Include="Assets\AnnouncementThumb.png" />
<Content Include="Assets\ChannelCoverTemplate.png" />
<Content Include="Assets\FoxGame.png" />
<Content Include="Assets\Icons\Profile.png" />
<Content Include="Assets\Icons\Send.png" />
<Content Include="Assets\LargeTile.scale-100.png" />
<Content Include="Assets\LargeTile.scale-125.png" />
<Content Include="Assets\LargeTile.scale-150.png" />
@@ -217,6 +227,24 @@
<Content Include="Assets\Wide310x150Logo.scale-125.png" />
<Content Include="Assets\Wide310x150Logo.scale-150.png" />
<Content Include="Assets\Wide310x150Logo.scale-400.png" />
<Content Include="Notifications\Comment.xml">
<SubType>Designer</SubType>
</Content>
<Content Include="Notifications\Download.xml">
<SubType>Designer</SubType>
</Content>
<Content Include="Notifications\DownloadComplete.xml" />
<Content Include="Notifications\downloadHistorySample.xml" />
<Content Include="Notifications\Internal.xml">
<SubType>Designer</SubType>
</Content>
<Content Include="Notifications\Post.xml">
<SubType>Designer</SubType>
</Content>
<Content Include="Notifications\ServerLogSamlpe.xml" />
<Content Include="Notifications\Video.xml">
<SubType>Designer</SubType>
</Content>
<Content Include="Properties\Default.rd.xml" />
<Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\SplashScreen.scale-200.png" />
@@ -234,6 +262,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\DownloadItem.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\SuggestionsQueries.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@@ -250,6 +282,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\Downloads.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\Feedback.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@@ -343,6 +379,9 @@
<PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls">
<Version>3.0.0</Version>
</PackageReference>
<PackageReference Include="MyToolkit">
<Version>2.5.16</Version>
</PackageReference>
<PackageReference Include="MyToolkit.Extended">
<Version>2.5.16</Version>
</PackageReference>
@@ -353,6 +392,12 @@
<ItemGroup>
<None Include="FoxTube_TemporaryKey.pfx" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FoxTube.Background\FoxTube.Background.csproj">
<Project>{fc9128d7-e3aa-48ed-8641-629794b88b28}</Project>
<Name>FoxTube.Background</Name>
</ProjectReference>
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
+22
View File
@@ -0,0 +1,22 @@
<toast launch="action=openThread&amp;threadId=92187">
<visual>
<binding template="ToastGeneric">
<image placement="appLogoOverride" hint-crop="circle" src="Assets/Icons/Profile.png"/>
<text>[ChannelName] posted a new comment</text>
<text>[VideoName]</text>
</binding>
</visual>
<actions>
<input id="textBox" type="text" placeHolderContent="Send a reply"/>
<action content="Send" imageUri="Assets/Icons/Send.png"
hint-inputId="textBox" activationType="background"
arguments="action=reply&amp;threadId=92187"/>
<action content="Like"
arguments="action=commentPhoto&amp;photoId=92187"/>
<action content="Go to comment"
arguments="action=commentPhoto&amp;photoId=92187"/>
</actions>
</toast>
+1
View File
@@ -0,0 +1 @@
<toast launch="action=viewPhoto&amp;photoId=92187">
@@ -0,0 +1 @@
<toast launch="action=viewPhoto&amp;photoId=92187">
+1
View File
@@ -0,0 +1 @@
<toast launch="action=openThread&amp;threadId=92187">
+1
View File
@@ -0,0 +1 @@
<toast launch="action=openThread&amp;threadId=92187">
@@ -0,0 +1,7 @@
<posts>
<post time="YYYY-MM-DDThh:mm:ss-03" image="http://foxtube.hol.es/foxtube/FILE_NAME.png Hero image (not implemented yet)">
<notificationHeader>Short headline for toast notifications</notificationHeader>
<header>Main headline for full post</header>
<content>Announcement body (beware of special characters)</content>
</post>
</posts>
+1
View File
@@ -0,0 +1 @@
<toast launch="action=viewPhoto&amp;photoId=92187">
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<downloads>
<item>
<title></title>
<snippet>
<quality></quality>
<duration></duration>
<author></author>
<image></image>
</snippet>
<details>
<path></path>
<id></id>
</details>
</item>
</downloads>
+33
View File
@@ -0,0 +1,33 @@
<Page
x:Class="FoxTube.Pages.Downloads"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FoxTube.Pages"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Padding="5">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel>
<TextBlock FontSize="28" Text="Downloads"/>
<Grid VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Button Name="openFolder" Click="openFolder_Click" Content="Open folder" Margin="5"/>
<TextBlock Name="path" IsTextSelectionEnabled="True" Grid.Column="1" Text="C://Users/Admin/Downloads" VerticalAlignment="Center"/>
<Button Name="changePath" Click="changePath_Click" Grid.Column="2" Content="Change path" Margin="5"/>
</Grid>
</StackPanel>
<ScrollViewer Grid.Row="1">
<StackPanel Margin="5" Name="stack"/>
</ScrollViewer>
</Grid>
</Page>
+58
View File
@@ -0,0 +1,58 @@
using FoxTube.Controls;
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.Storage;
using Windows.Storage.Pickers;
using Windows.System;
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;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
namespace FoxTube.Pages
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class Downloads : Page
{
ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
public Downloads()
{
this.InitializeComponent();
path.Text = settings.Values["defaultDownload"] as string;
foreach (DownloadItem item in Methods.MainPage.Agent.Items)
stack.Children.Add(item);
}
private async void changePath_Click(object sender, RoutedEventArgs e)
{
FolderPicker picker = new FolderPicker()
{
SuggestedStartLocation = PickerLocationId.Desktop,
ViewMode = PickerViewMode.Thumbnail
};
StorageFolder p = await picker.PickSingleFolderAsync();
if (p != null)
settings.Values["defaultDownload"] = p.Path;
path.Text = settings.Values["defaultDownload"] as string;
}
private async void openFolder_Click(object sender, RoutedEventArgs e)
{
await Launcher.LaunchUriAsync(new Uri(settings.Values["defaultDownload"] as string));
}
}
}
+4
View File
@@ -55,6 +55,7 @@ namespace FoxTube
public sealed partial class MainPage : Page
{
public SecretsVault Vault = new SecretsVault();
public DownloadAgent Agent = new DownloadAgent();
RightPaneState paneState = RightPaneState.Full;
bool isForcedCollapsed = false;
@@ -95,6 +96,9 @@ namespace FoxTube
if (settings.Values["safeSearch"] == null)
settings.Values.Add("safeSearch", 0);
if (settings.Values["defaultDownload"] == null)
settings.Values.Add("defaultDownload", Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\DownloadedVideos");
content.Navigate(typeof(Home));
Vault.AuthorizationStateChanged += Vault_AuthorizationStateChanged;
+1 -1
View File
@@ -108,7 +108,7 @@ namespace FoxTube
}
catch
{
MessageDialog message = new MessageDialog("We was unable to send your feedback due to connection problems or internal server error. Please, try again later.", "Failed to send your feedback");
MessageDialog message = new MessageDialog("We were unable to send your feedback due to connection problems or internal server error. Please, try again later.", "Failed to send your feedback");
await message.ShowAsync();
export.IsEnabled = true;
upload.IsEnabled = true;
+9 -3
View File
@@ -87,20 +87,26 @@
<CommandBar VerticalAlignment="Bottom" Name="commandbar">
<AppBarButton Name="download" Icon="Download" Label="Download video">
<AppBarButton.Flyout>
<MenuFlyout>
<MenuFlyout x:Name="downloadSelector">
<MenuFlyoutItem Text="Select quality:" IsEnabled="False"/>
<MenuFlyoutSeparator/>
<MenuFlyoutItem Text="No items are available" IsEnabled="False"/>
</MenuFlyout>
</AppBarButton.Flyout>
</AppBarButton>
<AppBarToggleButton Name="addLater" Icon="Clock" Label="Watch later" Visibility="Collapsed"/>
<AppBarToggleButton Name="addLater" Click="addLater_Click" Icon="Clock" Label="Watch later" Visibility="Collapsed"/>
<AppBarButton Name="refresh" Click="refresh_Click" Icon="Refresh" Label="Refresh page"/>
<AppBarButton Name="share" Click="share_Click" Icon="Share" Label="Share"/>
<AppBarButton Name="openBrowser" Click="openBrowser_Click" Icon="Globe" Label="Open in browser"/>
<CommandBar.SecondaryCommands>
<AppBarButton Name="addToPlaylist" Icon="Add" Label="Add to playlist" Visibility="Collapsed"/>
<AppBarButton Name="addToPlaylist" Click="addToPlaylist_Click" Icon="Add" Label="Add to playlist" Visibility="Collapsed">
<AppBarButton.Flyout>
<MenuFlyout>
</MenuFlyout>
</AppBarButton.Flyout>
</AppBarButton>
<AppBarButton Icon="Flag" Label="Report this video" Visibility="Collapsed"/>
</CommandBar.SecondaryCommands>
</CommandBar>
+112 -5
View File
@@ -30,6 +30,8 @@ using Windows.UI.Text;
using Windows.UI;
using FoxTube.Pages;
using MyToolkit.Multimedia;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
namespace FoxTube
@@ -41,11 +43,16 @@ namespace FoxTube
/// </summary>
public sealed partial class Video : Page
{
ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
string videoId;
Google.Apis.YouTube.v3.Data.Video item;
List<string> downloads = new List<string>();
bool wide;
bool isExtended = false;
bool isAddedToLater = false;
Rating userRating = Rating.None;
@@ -82,6 +89,7 @@ namespace FoxTube
grid.ColumnDefinitions[1].Width = new GridLength(0);
pivot.SelectedIndex = 0;
pivot_SelectionChanged(this, null);
}
}
@@ -142,7 +150,8 @@ namespace FoxTube
channelAvatar.ProfilePicture = new BitmapImage(new Uri(item1.Snippet.Thumbnails.Medium.Url));
channelName.Text = item.Snippet.ChannelTitle;
subscribers.Text = item1.Statistics.SubscriberCount + " subscribers";
subscribers.Text = string.Format("{0:0,0} subscribers", item1.Statistics.SubscriberCount);
if(SecretsVault.IsAuthorized)
{
@@ -157,6 +166,15 @@ namespace FoxTube
userRating = Rating.Dislike;
dislike.Foreground = new SolidColorBrush(Colors.Red);
}
/*addLater.Visibility = Visibility.Visible;
addToPlaylist.Visibility = Visibility.Visible;
if (SecretsVault.WatchLater.Contains(item.Id))
{
isAddedToLater = true;
addLater.IsChecked = true;
}*/
}
player = new VideoPlayer(item, item1.Snippet.Thumbnails.Medium.Url);
@@ -164,11 +182,30 @@ namespace FoxTube
playerPlaceholder.Children.Add(player);
LoadDownloads();
Thread.Sleep(1000);
grid.Visibility = Visibility.Visible;
LoadingScreen.Visibility = Visibility.Collapsed;
LoadRelatedVideos();
if (SecretsVault.IsAuthorized && SecretsVault.UserHistory[0] != item.Id)
{
await SecretsVault.Service.PlaylistItems.Insert(new PlaylistItem()
{
Snippet = new PlaylistItemSnippet()
{
ResourceId = new ResourceId()
{
Kind = "youtube#video",
VideoId = item.Id
},
PlaylistId = SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.WatchHistory
}
}, "snippet").ExecuteAsync();
SecretsVault.UserHistory.Insert(0, item.Id);
}
}
catch
{
@@ -176,6 +213,46 @@ namespace FoxTube
}
}
async void LoadDownloads()
{
Debug.WriteLine("Loading download options...");
downloadSelector.Items.Clear();
YouTubeUri[] uris = await YouTube.GetUrisAsync(item.Id);
if (uris.Length > 0)
foreach (YouTubeUri u in uris)
{
if (u.HasAudio && u.HasVideo)
{
downloads.Add(u.Uri.AbsoluteUri);
MenuFlyoutItem menuItem = new MenuFlyoutItem()
{
Text = Methods.QualityToString(u.VideoQuality)
};
menuItem.Click += downloadItemSelected;
downloadSelector.Items.Add(menuItem);
}
else if (u.HasAudio)
{
downloads.Add(u.Uri.AbsoluteUri);
MenuFlyoutItem menuItem = new MenuFlyoutItem()
{
Text = Methods.QualityToString(u.AudioQuality)
};
menuItem.Click += downloadItemSelected;
downloadSelector.Items.Add(menuItem);
}
}
else
download.Visibility = Visibility.Collapsed;
Debug.WriteLine("Done");
}
private void downloadItemSelected(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
async void LoadRelatedVideos()
{
loadingRelated.Refresh();
@@ -183,6 +260,7 @@ namespace FoxTube
SearchResource.ListRequest request = SecretsVault.IsAuthorized? SecretsVault.Service.Search.List("snippet") : SecretsVault.NoAuthService.Search.List("snippet");
request.RelatedToVideoId = videoId;
request.SafeSearch = (SearchResource.ListRequest.SafeSearchEnum)(int)settings.Values["safeSearch"];
request.MaxResults = 20;
request.Type = "video";
@@ -297,8 +375,6 @@ namespace FoxTube
toDescription.Visibility = Visibility.Collapsed;
grid.ColumnDefinitions[1].Width = new GridLength(400);
pivot.SelectedIndex = 0;
}
else if (e.NewSize.Width <= 1000 & e.PreviousSize.Width > 1000)
{
@@ -317,9 +393,10 @@ namespace FoxTube
toDescription.Visibility = Visibility.Visible;
grid.ColumnDefinitions[1].Width = new GridLength(0);
pivot.SelectedIndex = 0;
}
pivot.SelectedIndex = 0;
pivot_SelectionChanged(this, null);
}
private void toDescription_Click(object sender, RoutedEventArgs e)
@@ -500,5 +577,35 @@ namespace FoxTube
break;
}
}
private async void addLater_Click(object sender, RoutedEventArgs e)
{
isAddedToLater = !isAddedToLater;
if(isAddedToLater)
{
PlaylistItem pi = await SecretsVault.Service.PlaylistItems.Insert(new PlaylistItem()
{
Snippet = new PlaylistItemSnippet()
{
ResourceId = new ResourceId()
{
Kind = "youtube#video",
VideoId = item.Id
},
PlaylistId = SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.WatchLater
}
}, "snippet").ExecuteAsync();
SecretsVault.WatchLater.Insert(0, new KeyValuePair<string, string>(pi.Id, item.Id));
}
else
{
//await SecretsVault.Service.PlaylistItems.Delete(SecretsVault.WatchLater[])
}
}
private void addToPlaylist_Click(object sender, RoutedEventArgs e)
{
}
}
}