diff --git a/FoxTube/Assets/Data/Patchnotes.xml b/FoxTube/Assets/Data/Patchnotes.xml
index 90cfea9..69cf03f 100644
--- a/FoxTube/Assets/Data/Patchnotes.xml
+++ b/FoxTube/Assets/Data/Patchnotes.xml
@@ -5,10 +5,14 @@
### What's new:
- Changelog notification now pops up after update at first launch
- Added ability to add videos to playlists on video page
+- Added ability to add videos to playlists through card's context menu
+- Added ability to download video through card's context menu
### Что нового:
- Добавлено уведомление со списком изменений при первом запуске после обновления
- Добавлена возможность добавлять видео в плейлисты на странице просмотра
+- Добавлена возможность добавлять видео в плейлисты через контекстное меню карточки
+- Добавлена возможность скачивать видео через контекстное меню карточки
diff --git a/FoxTube/Controls/DownloadItem.xaml.cs b/FoxTube/Controls/DownloadItem.xaml.cs
index 2f7f219..6678b2e 100644
--- a/FoxTube/Controls/DownloadItem.xaml.cs
+++ b/FoxTube/Controls/DownloadItem.xaml.cs
@@ -258,21 +258,29 @@ namespace FoxTube.Controls
}
cts.Cancel();
+ DownloadAgent.Remove(this);
status.Text = resources.GetString("/Downloads/cancelling");
progressBar.IsIndeterminate = true;
}
void SetMeta()
{
- thumbnail.Source = new BitmapImage(Container.Thumbnail) { DecodePixelHeight = (int)thumbnail.ActualHeight, DecodePixelWidth = (int)thumbnail.ActualWidth };
+ try
+ {
+ thumbnail.Source = new BitmapImage(Container.Thumbnail) { DecodePixelHeight = (int)thumbnail.ActualHeight, DecodePixelWidth = (int)thumbnail.ActualWidth };
- title.Text = Container.Title;
- path.Text = File.Path;
+ title.Text = Container.Title;
+ path.Text = File.Path;
- meta.Text = $@"{resources.GetString("/Downloads/ext")}: {Container.Extension}
+ meta.Text = $@"{resources.GetString("/Downloads/ext")}: {Container.Extension}
{resources.GetString("/Downloads/quality")}: {Container.Quality}
{resources.GetString("/Downloads/duration")}: {Container.Duration}
{resources.GetString("/Downloads/author")}: {Container.Channel}";
+ }
+ catch
+ {
+ DownloadAgent.Remove(this);
+ }
}
private void Cancel_Click(object sender, RoutedEventArgs e)
diff --git a/FoxTube/Controls/VideoCard.xaml b/FoxTube/Controls/VideoCard.xaml
index de1749c..a54e13f 100644
--- a/FoxTube/Controls/VideoCard.xaml
+++ b/FoxTube/Controls/VideoCard.xaml
@@ -71,12 +71,17 @@
+
+
+
+
+
-
-
+
+
diff --git a/FoxTube/Controls/VideoCard.xaml.cs b/FoxTube/Controls/VideoCard.xaml.cs
index 4ceb0ca..68e0007 100644
--- a/FoxTube/Controls/VideoCard.xaml.cs
+++ b/FoxTube/Controls/VideoCard.xaml.cs
@@ -11,6 +11,7 @@ using Microsoft.AppCenter.Analytics;
using System.Collections.Generic;
using YoutubeExplode;
using Windows.UI.Popups;
+using YoutubeExplode.Models.MediaStreams;
namespace FoxTube.Controls
{
@@ -61,6 +62,7 @@ namespace FoxTube.Controls
else
info.Text = Methods.GetAgo(item.LiveStreamingDetails.ActualStartTime.Value);
liveTag.Visibility = Visibility.Visible;
+ download.Visibility = Visibility.Collapsed;
}
else if (item.Snippet.LiveBroadcastContent == "upcoming")
{
@@ -74,12 +76,15 @@ namespace FoxTube.Controls
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue)
liveContent.Text = resources.GetString("/Cards/goesLive") + (item.LiveStreamingDetails.ScheduledStartTime.Value > DateTime.Now ? " " : " -") + (item.LiveStreamingDetails.ScheduledStartTime.Value - DateTime.Now).ToString(@"hh\:mm\:ss");
else liveContent.Text = resources.GetString("/Cards/upcoming");
+ download.Visibility = Visibility.Collapsed;
}
else
{
views.Text = $"{item.Statistics.ViewCount:0,0} {resources.GetString("/Cards/views")}";
info.Text = $"{item.ContentDetails.Duration.GetDuration()} | {Methods.GetAgo(item.Snippet.PublishedAt.Value)}";
+ LoadDownloads();
}
+ LoadAddTo();
try { thumbnail.Source = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri()); }
catch { }
@@ -103,6 +108,42 @@ namespace FoxTube.Controls
}
}
+ async void LoadDownloads()
+ {
+ try
+ {
+ // TODO: Localize context menu
+ MediaStreamInfoSet infoSet = await new YoutubeClient().GetVideoMediaStreamInfosAsync(videoId);
+ foreach (MuxedStreamInfo i in infoSet.Muxed)
+ {
+ MenuFlyoutItem menuItem = new MenuFlyoutItem()
+ {
+ Text = i.VideoQualityLabel,
+ Tag = new object[] { i, i.VideoQualityLabel }
+ };
+ menuItem.Click += downloadItemSelected;
+ download.Items.Add(menuItem);
+ }
+
+ MenuFlyoutItem audioItem = new MenuFlyoutItem()
+ {
+ Text = resources.GetString("/VideoPage/audio"),
+ Tag = new object[] { infoSet.Audio[0], resources.GetString("/Cards/audioOnly") }
+ };
+ audioItem.Click += downloadItemSelected;
+ download.Items.Add(audioItem);
+ }
+ catch
+ {
+ download.Visibility = Visibility.Collapsed;
+ }
+ }
+
+ private void downloadItemSelected(object sender, RoutedEventArgs e)
+ {
+ DownloadAgent.Add(((sender as MenuFlyoutItem).Tag as object[])[0] as MediaStreamInfo, item, ((sender as MenuFlyoutItem).Tag as object[])[1] as string);
+ }
+
public async void LoadMeta()
{
videoId = item.Id;
@@ -206,5 +247,179 @@ namespace FoxTube.Controls
{
thumbnail.Opacity = 1;
}
+
+ private async void NewPlaylist_Click(object sender, RoutedEventArgs e)
+ {
+ // TODO: Localize strings
+ StackPanel stack = new StackPanel();
+ stack.Children.Add(new TextBox
+ {
+ PlaceholderText = "Enter playlist name"
+ });
+
+ ComboBox comboBox = new ComboBox
+ {
+ Header = "Availablity",
+ SelectedIndex = 0,
+ HorizontalAlignment = HorizontalAlignment.Stretch
+ };
+ comboBox.Items.Add(new ComboBoxItem { Content = "Public" });
+ comboBox.Items.Add(new ComboBoxItem { Content = "Private" });
+ comboBox.Items.Add(new ComboBoxItem { Content = "Direct link" });
+
+ stack.Children.Add(comboBox);
+
+ ContentDialog playlistDialog = new ContentDialog
+ {
+ PrimaryButtonText = "Create and add",
+ CloseButtonText = "Cancel",
+ DefaultButton = ContentDialogButton.Primary,
+ Title = "New playlist",
+ Content = stack
+ };
+ playlistDialog.PrimaryButtonClick += PlaylistDialog_PrimaryButtonClick;
+ await playlistDialog.ShowAsync();
+ }
+
+ private async void PlaylistDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
+ {
+ string privacy = "private";
+ switch (((sender.Content as StackPanel).Children[1] as ComboBox).SelectedIndex)
+ {
+ case 0:
+ privacy = "public";
+ break;
+ case 1:
+ privacy = "private";
+ break;
+ case 2:
+ privacy = "unlisted";
+ break;
+ }
+
+ Playlist newItem = new Playlist
+ {
+ Snippet = new PlaylistSnippet
+ {
+ Title = ((sender.Content as StackPanel).Children[0] as TextBox).Text
+ },
+ Status = new PlaylistStatus
+ {
+ PrivacyStatus = privacy,
+ }
+ };
+
+ Playlist i;
+
+ try { i = await SecretsVault.Service.Playlists.Insert(newItem, "snippet,status").ExecuteAsync(); }
+ catch
+ {
+ return;
+ }
+
+ ToggleMenuFlyoutItem menuItem = new ToggleMenuFlyoutItem
+ {
+ Text = i.Snippet.Title,
+ IsChecked = true,
+ Tag = i,
+ Icon = new FontIcon
+ {
+ Glyph = "\xE728"
+ }
+ };
+ menuItem.Click += Item_Click;
+ addTo.Items.Add(menuItem);
+
+ Item_Click(menuItem, null);
+ }
+
+ private void Wl_Click(object sender, RoutedEventArgs e)
+ {
+
+ }
+
+ async void LoadAddTo()
+ {
+ if (SecretsVault.UserChannel == null)
+ {
+ addTo.Visibility = Visibility.Collapsed;
+ return;
+ }
+
+ if (SecretsVault.WatchLater.Contains(item.Id))
+ (addTo.Items[1] as ToggleMenuFlyoutItem).IsChecked = true;
+
+ PlaylistsResource.ListRequest request = SecretsVault.Service.Playlists.List("snippet");
+ request.Mine = true;
+ request.MaxResults = 50;
+
+ PlaylistListResponse response = await request.ExecuteAsync();
+
+ PlaylistItemsResource.ListRequest itemRequest = SecretsVault.Service.PlaylistItems.List("snippet");
+ itemRequest.VideoId = item.Id;
+
+ foreach (Playlist i in response.Items)
+ {
+ itemRequest.PlaylistId = i.Id;
+ ToggleMenuFlyoutItem menuItem = new ToggleMenuFlyoutItem
+ {
+ Text = i.Snippet.Title,
+ IsChecked = (await itemRequest.ExecuteAsync()).Items.Count > 0,
+ Tag = i,
+ Icon = new FontIcon
+ {
+ Glyph = "\xE728"
+ }
+ };
+ menuItem.Click += Item_Click;
+ addTo.Items.Add(menuItem);
+ }
+ }
+
+ private async void Item_Click(object sender, RoutedEventArgs e)
+ {
+ if (((ToggleMenuFlyoutItem)sender).IsChecked)
+ {
+ try
+ {
+ PlaylistItem playlist = new PlaylistItem
+ {
+ Snippet = new PlaylistItemSnippet
+ {
+ PlaylistId = (((ToggleMenuFlyoutItem)sender).Tag as Playlist).Id,
+ ResourceId = new ResourceId
+ {
+ VideoId = item.Id,
+ Kind = "youtube#video"
+ }
+ }
+ };
+ PlaylistItemsResource.InsertRequest request = SecretsVault.Service.PlaylistItems.Insert(playlist, "snippet");
+
+ await request.ExecuteAsync();
+ }
+ catch
+ {
+ ((ToggleMenuFlyoutItem)sender).IsChecked = false;
+ }
+ }
+ else
+ {
+ try
+ {
+ PlaylistItemsResource.ListRequest itemRequest = SecretsVault.Service.PlaylistItems.List("snippet");
+ itemRequest.VideoId = item.Id;
+ itemRequest.PlaylistId = ((Playlist)((ToggleMenuFlyoutItem)sender).Tag).Id;
+
+ PlaylistItemsResource.DeleteRequest request = SecretsVault.Service.PlaylistItems.Delete((await itemRequest.ExecuteAsync()).Items[0].Id);
+
+ await request.ExecuteAsync();
+ }
+ catch
+ {
+ ((ToggleMenuFlyoutItem)sender).IsChecked = true;
+ }
+ }
+ }
}
}
diff --git a/FoxTube/Pages/VideoPage.xaml b/FoxTube/Pages/VideoPage.xaml
index 846761f..7c0fda3 100644
--- a/FoxTube/Pages/VideoPage.xaml
+++ b/FoxTube/Pages/VideoPage.xaml
@@ -117,43 +117,6 @@
-
@@ -201,7 +164,7 @@
-
+