diff --git a/FoxTube.Background/BackgroundProcessor.cs b/FoxTube.Background/BackgroundProcessor.cs index 706209d..6e06785 100644 --- a/FoxTube.Background/BackgroundProcessor.cs +++ b/FoxTube.Background/BackgroundProcessor.cs @@ -11,7 +11,6 @@ using System.Xml; using Windows.ApplicationModel.Background; using Windows.Storage; using Windows.UI.Notifications; -using YoutubeExplode.Models; namespace FoxTube.Background { @@ -78,13 +77,13 @@ namespace FoxTube.Background if(i.Snippet.LiveBroadcastContent == "live") ToastNotificationManager.CreateToastNotifier().Show( - Notification.GetStreamToast(i.Id.VideoId, i.Snippet.ChannelId, i.Snippet.Title, i.Snippet.ChannelTitle, i.Snippet.Thumbnails.Medium.Url, i.Snippet.PublishedAt.Value, s.Value)); + Notification.GetStreamToast(i.Id.VideoId, i.Snippet.ChannelId, i.Snippet.Title.ConvertEscapeSymbols(), i.Snippet.ChannelTitle, i.Snippet.Thumbnails.Medium.Url, i.Snippet.PublishedAt.Value, s.Value)); else if(i.Snippet.LiveBroadcastContent == "upcoming") ToastNotificationManager.CreateToastNotifier().Show( - Notification.GetUpcomingToast(i.Id.VideoId, i.Snippet.ChannelId, i.Snippet.Title, i.Snippet.ChannelTitle, i.Snippet.Thumbnails.Medium.Url, i.Snippet.PublishedAt.Value, s.Value)); + Notification.GetUpcomingToast(i.Id.VideoId, i.Snippet.ChannelId, i.Snippet.Title.ConvertEscapeSymbols(), i.Snippet.ChannelTitle, i.Snippet.Thumbnails.Medium.Url, i.Snippet.PublishedAt.Value, s.Value)); else ToastNotificationManager.CreateToastNotifier().Show( - Notification.GetVideoToast(i.Id.VideoId, i.Snippet.ChannelId, i.Snippet.Title, i.Snippet.ChannelTitle, i.Snippet.Thumbnails.Medium.Url, i.Snippet.PublishedAt.Value, s.Value)); + Notification.GetVideoToast(i.Id.VideoId, i.Snippet.ChannelId, i.Snippet.Title.ConvertEscapeSymbols(), i.Snippet.ChannelTitle, i.Snippet.Thumbnails.Medium.Url, i.Snippet.PublishedAt.Value, s.Value)); } } @@ -94,7 +93,7 @@ namespace FoxTube.Background updater.EnableNotificationQueue(true); updater.Clear(); for (int i = 0; i < 5 && i < results.Count; i++) - updater.Update(Tiles.GetTileLayout(System.Security.SecurityElement.Escape(results[i].Snippet.Title), System.Security.SecurityElement.Escape(results[i].Snippet.ChannelTitle), results[i].Snippet.Thumbnails.Medium.Url.Replace("&", "%26"), subscriptions[results[i].Snippet.ChannelId])); + updater.Update(Tiles.GetTileLayout(System.Security.SecurityElement.Escape(results[i].Snippet.Title.ConvertEscapeSymbols()), System.Security.SecurityElement.Escape(results[i].Snippet.ChannelTitle), results[i].Snippet.Thumbnails.Medium.Url.Replace("&", "%26"), subscriptions[results[i].Snippet.ChannelId])); } catch { } } @@ -119,4 +118,12 @@ namespace FoxTube.Background catch { } } } + + public static class Ext + { + public static string ConvertEscapeSymbols(this string str) + { + return str.Replace(""", "\"").Replace("'", "'").Replace("<", "<").Replace(">", ">").Replace("&", "&"); + } + } } diff --git a/FoxTube/App.xaml b/FoxTube/App.xaml index 93d2f98..3f5f37b 100644 --- a/FoxTube/App.xaml +++ b/FoxTube/App.xaml @@ -1,17 +1,14 @@  + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + - Red - - + + + + + + diff --git a/FoxTube/Assets/Data/Patchnotes.xml b/FoxTube/Assets/Data/Patchnotes.xml index b07d623..5bb3172 100644 --- a/FoxTube/Assets/Data/Patchnotes.xml +++ b/FoxTube/Assets/Data/Patchnotes.xml @@ -1,5 +1,43 @@  + + + ### What's new: +- App optimization +- 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 +- Deleted videos are now also displayed +- Added support of April 2018 Update (Windows 10 build 17134) +- Added adverts (not real. Just for debugging) +- Fixed header titles +- Some items were moved from menu to header +- Added "Share" button to video cards +- Added "Delete video from playlist" button to video cards on playlist page\ +- Improved channel cover quality +- If available, shows localized titles and descriptions (based on "Search relevance language" parameter set in settings) +- Updated russian localization + + ### Что нового: +- Оптимизация приложения +- Добавлено уведомление со списком изменений при первом запуске после обновления +- Добавлена возможность добавлять видео в плейлисты на странице просмотра +- Добавлена возможность добавлять видео в плейлисты через контекстное меню карточки +- Добавлена возможность скачивать видео через контекстное меню карточки +- Удаленные видео теперь также отображаются +- Добавлена поддержка Апрельского Обновления 2018 (Windows 10 сборка 17134) +- Добавлена реклама (не настоящие. Только для отладки) +- Исправлено изменение заголовков +- Некоторые пункты меню перемещены в заголовок +- Добавлена кнопка "Поделиться" к видео карточкам +- Добавлена кнопка "Удалить видео из плейлиста" к видео карточкам на страницах плейлистов +- Улучшено качество обложки канала +- Показывает локализированные заголовки и описания если доступны (основан на параметре "Предпочитаемый язык поиска" установленного в настройках) +- Обновлена русская локализация + + + ### What's new: diff --git a/FoxTube/Classes/Methods.cs b/FoxTube/Classes/Methods.cs index 18890fe..f6ebd82 100644 --- a/FoxTube/Classes/Methods.cs +++ b/FoxTube/Classes/Methods.cs @@ -1,27 +1,21 @@ using FoxTube.Pages; -using Google.Apis.YouTube.v3; using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; -using System.Web; using System.Xml; using Windows.ApplicationModel.Core; using Windows.ApplicationModel.DataTransfer; using Windows.ApplicationModel.Resources; -using Windows.ApplicationModel.Resources.Core; using Windows.Storage; using Windows.Storage.Streams; using Windows.System; -using Windows.UI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Documents; -using Windows.UI.Xaml.Media; using YoutubeExplode; using YoutubeExplode.Models.MediaStreams; @@ -48,7 +42,18 @@ namespace FoxTube public static Uri ToUri(this string url) { - return new Uri(url); + if (string.IsNullOrWhiteSpace(url)) + return null; + else + return new Uri(url); + } + + public static string GuardFromNull(string str) + { + if (string.IsNullOrWhiteSpace(str)) + return string.Empty; + else + return str; } public static string GetChars(this string str, int count) diff --git a/FoxTube/Classes/SecretsVault.cs b/FoxTube/Classes/SecretsVault.cs index 990e5a9..cc0c68a 100644 --- a/FoxTube/Classes/SecretsVault.cs +++ b/FoxTube/Classes/SecretsVault.cs @@ -42,8 +42,9 @@ namespace FoxTube }; public static YouTubeService Service => IsAuthorized ? new YouTubeService(Initializer) : NoAuthService; public static HttpClient HttpClient { get; } = new HttpClient(); - public static string AppId => true ? "d25517cb-12d4-4699-8bdc-52040c712cab" : "9ncqqxjtdlfh"; - public static string AdUnitId => true ? "test" : "1100037769"; + private static bool TestAds => true; //Change this bool + public static string AppId => TestAds ? "d25517cb-12d4-4699-8bdc-52040c712cab" : "9ncqqxjtdlfh"; + public static string AdUnitId => TestAds ? "test" : "1100044398"; public static bool AdsDisabled { get; private set; } = true; //User info @@ -109,9 +110,8 @@ namespace FoxTube /// public static void Initialize() { + CheckAddons(); CheckAuthorization(); - // TODO: Reactivate addons initialization - //CheckAddons(); } /// diff --git a/FoxTube/Classes/SettingsStorage.cs b/FoxTube/Classes/SettingsStorage.cs index b5a952b..d564172 100644 --- a/FoxTube/Classes/SettingsStorage.cs +++ b/FoxTube/Classes/SettingsStorage.cs @@ -161,7 +161,7 @@ namespace FoxTube { get { - if (storage.Values["ver"] == null) + if (storage.Values["version"] == null) { PackageVersion ver = Package.Current.Id.Version; storage.Values["version"] = $"{ver.Major}.{ver.Minor}"; diff --git a/FoxTube/Controls/Adverts/CardAdvert.xaml b/FoxTube/Controls/Adverts/CardAdvert.xaml index 4f5a4f6..c947886 100644 --- a/FoxTube/Controls/Adverts/CardAdvert.xaml +++ b/FoxTube/Controls/Adverts/CardAdvert.xaml @@ -9,7 +9,24 @@ VerticalAlignment="Top" d:DesignHeight="290" d:DesignWidth="384" - Visibility="Collapsed"> + Visibility="Collapsed" + Opacity="0" + Name="card"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -52,61 +90,61 @@ - + - + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - + + + - - - - + + + - - + + - - + + - - + + + - - - + + + - + diff --git a/FoxTube/Pages/MainPage.xaml.cs b/FoxTube/Pages/MainPage.xaml.cs index df129ed..14ede21 100644 --- a/FoxTube/Pages/MainPage.xaml.cs +++ b/FoxTube/Pages/MainPage.xaml.cs @@ -12,14 +12,11 @@ using System.Xml; using Google.Apis.YouTube.v3.Data; using Windows.ApplicationModel.Core; using Windows.System; -using Google.Apis.Oauth2.v2; -using Google.Apis.Oauth2.v2.Data; using FoxTube.Pages; using Windows.UI.Popups; using Windows.Networking.Connectivity; using Windows.ApplicationModel.Resources; using Microsoft.Services.Store.Engagement; -using System.Linq; namespace FoxTube { @@ -29,8 +26,12 @@ namespace FoxTube public sealed partial class MainPage : Page { bool wasInvoked = false; + public static event ObjectEventHandler VideoPageSizeChanged; readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Main"); Dictionary headers; + + public Page PageContent => content.Content as Page; + public MainPage() { InitializeComponent(); @@ -43,11 +44,13 @@ namespace FoxTube SecretsVault.SubscriptionsChanged += SecretsVault_SubscriptionsChanged; SecretsVault.Purchased += async (sender, e) => { - //TODO: Localize strings removeAds.Visibility = (e[0] as bool?).Value ? Visibility.Collapsed : Visibility.Visible; - MessageDialog dialog = new MessageDialog("Thanks for purchasing full version of the app (^∇^) In order to complete changes we need to reopen it. But you can do it later"); - dialog.Commands.Add(new UICommand("Close the app", (command) => Methods.CloseApp())); - dialog.Commands.Add(new UICommand("Later")); + if (!(bool)e[0]) + return; + + MessageDialog dialog = new MessageDialog(resources.GetString("/Main/purchaseSuccess")); + dialog.Commands.Add(new UICommand(resources.GetString("/Main/close"), (command) => Methods.CloseApp())); + dialog.Commands.Add(new UICommand(resources.GetString("/Main/delay"))); dialog.CancelCommandIndex = 1; dialog.DefaultCommandIndex = 0; await dialog.ShowAsync(); @@ -80,22 +83,21 @@ namespace FoxTube async void PromptFeedback() { - //TODO: Localize strings if (SettingsStorage.Uptime.TotalHours >= 12 && SettingsStorage.PromptFeedback) { - MessageDialog dialog = new MessageDialog("Have some thoughts to share about the app or any suggestions? Leave feedback!"); - dialog.Commands.Add(new UICommand("Don't ask me anymore", (command) => SettingsStorage.PromptFeedback = false)); - dialog.Commands.Add(new UICommand("Maybe later")); - dialog.Commands.Add(new UICommand("Sure!", async (command) => + MessageDialog dialog = new MessageDialog(resources.GetString("/Main/feedbackMessage")); + dialog.Commands.Add(new UICommand(resources.GetString("/Main/dontAsk"), (command) => SettingsStorage.PromptFeedback = false)); + dialog.Commands.Add(new UICommand(resources.GetString("/Main/promptLater"))); + dialog.Commands.Add(new UICommand(resources.GetString("/Main/sure"), async (command) => { SettingsStorage.PromptFeedback = false; if (StoreServicesFeedbackLauncher.IsSupported()) await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync(); else { - MessageDialog message = new MessageDialog("Oops. Seems like you don't have a Feedback Hub app. But you can stil send your feedback to michael.xfox@outlook.com"); - message.Commands.Add(new UICommand("Send an E-mail", async (c) => await Launcher.LaunchUriAsync("mailto:michael.xfox@outlook.com".ToUri()))); - message.Commands.Add(new UICommand("Nevermind. Get me back")); + MessageDialog message = new MessageDialog(resources.GetString("/Main/feedbackFail")); + message.Commands.Add(new UICommand(resources.GetString("/Main/sendEmail"), async (c) => await Launcher.LaunchUriAsync("mailto:michael.xfox@outlook.com".ToUri()))); + message.Commands.Add(new UICommand(resources.GetString("/Main/goBack"))); message.CancelCommandIndex = 1; message.DefaultCommandIndex = 0; await message.ShowAsync(); @@ -108,10 +110,10 @@ namespace FoxTube if (SettingsStorage.Uptime.TotalHours >= 24 && SettingsStorage.PromptReview) { - MessageDialog dialog = new MessageDialog("Like our app? Review it on Microsoft Store!"); - dialog.Commands.Add(new UICommand("Don't ask me anymore", (command) => SettingsStorage.PromptReview = false)); - dialog.Commands.Add(new UICommand("Maybe later")); - dialog.Commands.Add(new UICommand("Sure!", async (command) => + MessageDialog dialog = new MessageDialog(resources.GetString("/Main/rate")); + dialog.Commands.Add(new UICommand(resources.GetString("/Main/dontAsk"), (command) => SettingsStorage.PromptReview = false)); + dialog.Commands.Add(new UICommand(resources.GetString("/Main/promptLater"))); + dialog.Commands.Add(new UICommand(resources.GetString("/Main/sure"), async (command) => { SettingsStorage.PromptReview = false; await Launcher.LaunchUriAsync("ms-windows-store://review/?ProductId=9NCQQXJTDLFH".ToUri()); @@ -157,7 +159,7 @@ namespace FoxTube break; case "remove": - NavigationViewItem item = nav.MenuItems.Find(i => ((i as NavigationViewItem).Content as StackPanel).Tag.ToString() == (args[1] as Subscription).Snippet.ResourceId.ChannelId) as NavigationViewItem; + Microsoft.UI.Xaml.Controls.NavigationViewItem item = nav.MenuItems.Find(i => ((i as Microsoft.UI.Xaml.Controls.NavigationViewItem).Content as StackPanel).Tag.ToString() == (args[1] as Subscription).Snippet.ResourceId.ChannelId) as Microsoft.UI.Xaml.Controls.NavigationViewItem; if (item == null) break; else @@ -176,10 +178,11 @@ namespace FoxTube case true: account.Visibility = Visibility.Collapsed; - myName.Text = myNameFlyout.Text = SecretsVault.UserInfo.Name; + ToolTipService.SetToolTip(avatar, $"{SecretsVault.UserInfo.Name} ({SecretsVault.UserInfo.Email})"); + myNameFlyout.Text = SecretsVault.UserInfo.Name; myEmail.Text = SecretsVault.UserInfo.Email; - avatarFlyout.ProfilePicture = new BitmapImage(SecretsVault.UserInfo.Picture.ToUri()); - ((avatar.Content as StackPanel).Children[0] as PersonPicture).ProfilePicture = avatarFlyout.ProfilePicture; + avatarFlyout.ProfilePicture = new BitmapImage(SecretsVault.UserInfo.Picture.ToUri()) { DecodePixelHeight = 65, DecodePixelWidth = 65 }; + (avatar.Content as PersonPicture).ProfilePicture = new BitmapImage(SecretsVault.UserInfo.Picture.ToUri()) { DecodePixelHeight = 25, DecodePixelWidth = 25 }; avatar.Visibility = Visibility.Visible; @@ -249,12 +252,12 @@ namespace FoxTube GoToVideo((videoPlaceholder.Content as VideoPage).videoId, (videoPlaceholder.Content as VideoPage).playlistId); } - private async void Feedback_Click(object sender, TappedRoutedEventArgs e) + private async void Feedback_Click(object sender, RoutedEventArgs e) { await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync(); } - private void SignIn_Click(object sender, TappedRoutedEventArgs e) + private void SignIn_Click(object sender, RoutedEventArgs e) { SecretsVault.Authorize(); } @@ -338,7 +341,10 @@ namespace FoxTube nav.ExpandedModeThresholdWidth = short.MaxValue; nav.IsPaneOpen = false; + VideoPageSizeChanged?.Invoke(this, true); videoPlaceholder.Navigate(typeof(VideoPage), new string[2] { id, playlistId }); + + Title.Text = resources.GetString("/Main/video"); } public void GoToDeveloper(string id) @@ -383,6 +389,8 @@ namespace FoxTube else nav.IsBackEnabled = false; + VideoPageSizeChanged?.Invoke(this, false); + SetNavigationMenu(); try { headers[content.SourcePageType](); } @@ -394,7 +402,7 @@ namespace FoxTube if (content.SourcePageType == typeof(Home) || content.SourcePageType == typeof(Settings) || content.SourcePageType == typeof(Subscriptions)) { nav.ExpandedModeThresholdWidth = 1008; - nav.IsPaneOpen = nav.DisplayMode == NavigationViewDisplayMode.Expanded ? true : false; + nav.IsPaneOpen = nav.DisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Expanded ? true : false; } else nav.ExpandedModeThresholdWidth = short.MaxValue; @@ -415,6 +423,8 @@ namespace FoxTube Title.Text = resources.GetString("/Main/video"); nav.ExpandedModeThresholdWidth = short.MaxValue; nav.IsPaneOpen = false; + + VideoPageSizeChanged?.Invoke(this, true); } public void CloseVideo() @@ -431,7 +441,12 @@ namespace FoxTube else nav.IsBackEnabled = false; + VideoPageSizeChanged?.Invoke(this, false); + SetNavigationMenu(); + + try { headers[content.SourcePageType](); } + catch { } } private void Search_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) @@ -538,7 +553,7 @@ namespace FoxTube private void OpenContext(object sender, TappedRoutedEventArgs e) { - ((NavigationViewItem)sender).ContextFlyout.ShowAt((NavigationViewItem)sender); + ((Microsoft.UI.Xaml.Controls.NavigationViewItem)sender).ContextFlyout.ShowAt((Microsoft.UI.Xaml.Controls.NavigationViewItem)sender); } private void RemoveAds_Tapped(object sender, TappedRoutedEventArgs e) @@ -551,7 +566,7 @@ namespace FoxTube content.Navigate(typeof(Home1)); } - private void Nav_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) + private void Nav_SelectionChanged(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewSelectionChangedEventArgs args) { try { @@ -586,7 +601,7 @@ namespace FoxTube catch { } } - private void Nav_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) + private void Nav_BackRequested(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewBackRequestedEventArgs args) { if (videoPlaceholder.Content != null) { @@ -599,16 +614,16 @@ namespace FoxTube content.GoBack(); } - private void Nav_PaneClosing(NavigationView sender, NavigationViewPaneClosingEventArgs args) + private void Nav_PaneClosing(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewPaneClosingEventArgs args) { AppTitle.Visibility = Visibility.Collapsed; } - private void Nav_PaneOpened(NavigationView sender, object args) + private void Nav_PaneOpened(Microsoft.UI.Xaml.Controls.NavigationView sender, object args) { AppTitle.Visibility = Visibility.Visible; - if (sender.DisplayMode == NavigationViewDisplayMode.Expanded && sender.IsPaneOpen) + if (sender.DisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Expanded && sender.IsPaneOpen) AppTitleBar.Margin = new Thickness(40, 0, 0, 0); else AppTitleBar.Margin = new Thickness(); diff --git a/FoxTube/Pages/PlaylistPage.xaml.cs b/FoxTube/Pages/PlaylistPage.xaml.cs index c6f6b84..2e8e04d 100644 --- a/FoxTube/Pages/PlaylistPage.xaml.cs +++ b/FoxTube/Pages/PlaylistPage.xaml.cs @@ -54,12 +54,13 @@ namespace FoxTube.Pages PlaylistsResource.ListRequest infoRequest = SecretsVault.Service.Playlists.List("snippet,contentDetails"); infoRequest.Id = id; + infoRequest.Hl = SettingsStorage.RelevanceLanguage; item = (await infoRequest.ExecuteAsync()).Items[0]; - title.Text = item.Snippet.Title; + title.Text = item.Snippet.Localized.Title; info.Text = $"{item.ContentDetails.ItemCount} {ResourceLoader.GetForCurrentView("Playlist").GetString("/Playlist/videos")}"; - description.Text = item.Snippet.Description; + description.Text = item.Snippet.Localized.Description; channelName.Text = item.Snippet.ChannelTitle; @@ -147,5 +148,10 @@ namespace FoxTube.Pages foreach (PlaylistItem i in response.Items) list.Add(new VideoCard(i.ContentDetails.VideoId, playlistId)); } + + public void DeleteItem(FrameworkElement card) + { + list.DeleteItem(card); + } } } diff --git a/FoxTube/Pages/SettingsPages/General.xaml b/FoxTube/Pages/SettingsPages/General.xaml index 0b44e9d..1bd03f3 100644 --- a/FoxTube/Pages/SettingsPages/General.xaml +++ b/FoxTube/Pages/SettingsPages/General.xaml @@ -18,7 +18,7 @@ @@ -628,6 +634,8 @@ + + diff --git a/FoxTube/Themes/Resources.xaml b/FoxTube/Themes/Resources.xaml new file mode 100644 index 0000000..92cd39a --- /dev/null +++ b/FoxTube/Themes/Resources.xaml @@ -0,0 +1,46 @@ + + + Red + + + + + + + + + + + + 0:0:0.5 + +