diff --git a/FoxTube.Background/BackgroundProcessor.cs b/FoxTube.Background/BackgroundProcessor.cs index a05d2e3..706209d 100644 --- a/FoxTube.Background/BackgroundProcessor.cs +++ b/FoxTube.Background/BackgroundProcessor.cs @@ -1,24 +1,26 @@ using Google.Apis.Services; using Google.Apis.YouTube.v3; using Google.Apis.YouTube.v3.Data; -using Microsoft.AppCenter.Analytics; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; +using System.Net.Http; using System.Threading.Tasks; using System.Xml; using Windows.ApplicationModel.Background; using Windows.Storage; using Windows.UI.Notifications; +using YoutubeExplode.Models; namespace FoxTube.Background { public sealed class BackgroundProcessor : IBackgroundTask { - private DateTime lastCheck = DateTime.Now; - private readonly ApplicationDataContainer settings = ApplicationData.Current.LocalSettings; - private YouTubeService Service => new YouTubeService(new BaseClientService.Initializer() + private DateTime lastCheck; + private readonly ApplicationDataContainer settings = ApplicationData.Current.RoamingSettings; + dynamic prefs; + private readonly YouTubeService Service = new YouTubeService(new BaseClientService.Initializer() { ApiKey = "AIzaSyBgHrCnrlzlVmk0cJKL8RqP9Y8x6XSuk_0", ApplicationName = "FoxTube" @@ -30,21 +32,20 @@ namespace FoxTube.Background try { def = taskInstance.GetDeferral(); - taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled); if (settings.Values["lastCheck"] == null) { - settings.Values.Add("lastCheck", DateTime.Now.ToString()); + settings.Values["lastCheck"] = DateTime.Now.ToString(); def.Complete(); return; } else lastCheck = DateTime.Parse(settings.Values["lastCheck"] as string); - bool[] notificationsSettings = JsonConvert.DeserializeObject(await FileIO.ReadTextAsync(await ApplicationData.Current.RoamingFolder.GetFileAsync("notifications.json"))); - if (notificationsSettings[0]) + prefs = JsonConvert.DeserializeObject(settings.Values["settings"] as string); + if ((bool)prefs.devNotifications) CheckAnnouncements(); - if (notificationsSettings[1]) + if ((bool)prefs.videoNotifications) await CheckAccount(); } finally @@ -54,20 +55,11 @@ namespace FoxTube.Background } } - private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) - { - Analytics.TrackEvent("Background task caneled", new Dictionary() - { - { "Reason", reason.ToString() } - }); - settings.Values["lastCheck"] = DateTime.Now.ToString(); - } - async Task CheckAccount() { try { - Dictionary subscriptions = JsonConvert.DeserializeObject>(await FileIO.ReadTextAsync(await ApplicationData.Current.RoamingFolder.GetFileAsync("background.json"))); + Dictionary subscriptions = JsonConvert.DeserializeObject>(settings.Values["subscriptions"] as string); List results = new List(); @@ -84,8 +76,15 @@ namespace FoxTube.Background { results.Add(i); - ToastNotificationManager.CreateToastNotifier().Show( - Notification.GetVideoToast(i.Id.VideoId, i.Snippet.ChannelId, i.Snippet.Title, i.Snippet.ChannelTitle, i.Snippet.Thumbnails.Medium.Url, s.Value)); + 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)); + 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)); + 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)); } } @@ -94,25 +93,28 @@ namespace FoxTube.Background TileUpdater updater = TileUpdateManager.CreateTileUpdaterForApplication(); updater.EnableNotificationQueue(true); updater.Clear(); - for (int i = 0; i < 5; i++) - updater.Update(Tiles.GetTileLayout(results[i].Snippet.Title, results[i].Snippet.ChannelTitle, results[i].Snippet.Thumbnails.Medium.Url, subscriptions[results[i].Snippet.ChannelId])); + 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])); } catch { } } - void CheckAnnouncements() + async void CheckAnnouncements() { try { XmlDocument doc = new XmlDocument(); - doc.Load(XmlReader.Create("http://foxgame-studio.000webhostapp.com/foxtube-messages.xml")); - if ((DateTime.Parse((doc["posts"].FirstChild as XmlElement).GetAttribute("time")) - lastCheck).TotalSeconds > 0) + doc.LoadXml(await new HttpClient().GetStringAsync("http://foxgame-studio.000webhostapp.com/foxtube-messages.xml")); + XmlElement item = doc["posts"].FirstChild as XmlElement; + + DateTime date = DateTime.Parse(item.GetAttribute("time")); + if (date > lastCheck && date < DateTime.Now) ToastNotificationManager.CreateToastNotifier().Show( - Notification.GetInternalToast(doc["posts"].FirstChild["id"].InnerText, - doc["posts"].FirstChild["header"].InnerText, - doc["posts"].FirstChild["content"].InnerText, - doc["posts"].FirstChild["thumbnail"].InnerText, - doc["posts"].FirstChild["avatar"].InnerText)); + Notification.GetInternalToast(item["id"].InnerText, + item["header"][(string)prefs.language].InnerText, + item["content"][(string)prefs.language].InnerText, + item["thumbnail"].InnerText, + item["avatar"].InnerText)); } catch { } } diff --git a/FoxTube.Background/FoxTube.Background.csproj b/FoxTube.Background/FoxTube.Background.csproj index 11a5b78..e8b0cea 100644 --- a/FoxTube.Background/FoxTube.Background.csproj +++ b/FoxTube.Background/FoxTube.Background.csproj @@ -128,10 +128,16 @@ 1.29.2.1006 - 1.13.0 + 1.13.2 - 6.1.5 + 6.2.8 + + + 5.1.1 + + + 4.6.7 diff --git a/FoxTube.Background/ResourceCreators.cs b/FoxTube.Background/ResourceCreators.cs index 3df0305..254f1ff 100644 --- a/FoxTube.Background/ResourceCreators.cs +++ b/FoxTube.Background/ResourceCreators.cs @@ -1,4 +1,7 @@ -using Newtonsoft.Json; +using Google.Apis.YouTube.v3.Data; +using Microsoft.Toolkit.Uwp.Notifications; +using Newtonsoft.Json; +using System; using System.Collections.Generic; using Windows.Data.Xml.Dom; using Windows.Storage; @@ -8,18 +11,22 @@ namespace FoxTube.Background { public static class Notification { - private static Dictionary languagePack = LoadPack(); + private static readonly Dictionary languagePack = LoadPack(); private static Dictionary LoadPack() { - object[] saved = JsonConvert.DeserializeObject(ApplicationData.Current.RoamingSettings.Values["settings"] as string); - if (saved[7] as string == "ru-RU") + dynamic saved = JsonConvert.DeserializeObject(ApplicationData.Current.RoamingSettings.Values["settings"] as string); + if (saved.language as string == "ru-RU") return new Dictionary() { { "addLater", "Посмотреть позже" }, { "changelog", "Список изменений" }, { "changelogHeader", "Что нового в версии" }, { "videoContent", "загрузил новое видео" }, + { "live", "ПРЯМОЙ ЭФИР" }, + { "upcoming", "Запланирован" }, + { "liveContent", "начал прямой эфир" }, + { "upcomingContent", "запланировал прямой эфир" }, { "goChannel", "Открыть канал" } }; else @@ -29,6 +36,10 @@ namespace FoxTube.Background { "changelog", "Changelog" }, { "changelogHeader", "What's new in version" }, { "videoContent", "uploaded a new video" }, + { "live", "LIVE" }, + { "upcoming", "Upcoming" }, + { "liveContent", "started live broadcast" }, + { "upcomingContent", "planned live broadcast" }, { "goChannel", "Go to channel" } }; } @@ -51,17 +62,17 @@ namespace FoxTube.Background return new ToastNotification(template); } - public static ToastNotification GetVideoToast(string id, string channelId, string title, string channel, string thumbnail, string avatar) + public static ToastNotification GetVideoToast(string id, string channelId, string title, string channel, string thumbnail, DateTimeOffset timeStamp, string avatar) { XmlDocument template = new XmlDocument(); - - template.LoadXml($@" + string ts = $"{timeStamp.Year}-{timeStamp.Month:00}-{timeStamp.Day:00}T{timeStamp.Hour:00}:{timeStamp.Minute:00}:{timeStamp.Second:00}Z"; + template.LoadXml($@" - {title} - {channel} {languagePack["videoContent"]} + {System.Security.SecurityElement.Escape(title)} + {System.Security.SecurityElement.Escape(channel)} {languagePack["videoContent"]} @@ -73,6 +84,48 @@ namespace FoxTube.Background return new ToastNotification(template); } + public static ToastNotification GetStreamToast(string id, string channelId, string title, string channel, string thumbnail, DateTimeOffset timeStamp, string avatar) + { + XmlDocument template = new XmlDocument(); + string ts = $"{timeStamp.Year}-{timeStamp.Month:00}-{timeStamp.Day:00}T{timeStamp.Hour:00}:{timeStamp.Minute:00}:{timeStamp.Second:00}Z"; + template.LoadXml($@" + + + + + 🔴 [{languagePack["live"]}] {System.Security.SecurityElement.Escape(title)} + {System.Security.SecurityElement.Escape(channel)} {languagePack["liveContent"]} + + + + + + + "); + + return new ToastNotification(template); + } + public static ToastNotification GetUpcomingToast(string id, string channelId, string title, string channel, string thumbnail, DateTimeOffset timeStamp, string avatar) + { + XmlDocument template = new XmlDocument(); + string ts = $"{timeStamp.Year}-{timeStamp.Month:00}-{timeStamp.Day:00}T{timeStamp.Hour:00}:{timeStamp.Minute:00}:{timeStamp.Second:00}Z"; + template.LoadXml($@" + + + + + 🔴 [{languagePack["upcoming"]}] {System.Security.SecurityElement.Escape(title)} + {System.Security.SecurityElement.Escape(channel)} {languagePack["upcomingContent"]} + + + + + + + "); + + return new ToastNotification(template); + } public static ToastNotification GetInternalToast(string id, string header, string content, string thumbnail, string avatar) { diff --git a/FoxTube/App.xaml b/FoxTube/App.xaml index 41a4760..93d2f98 100644 --- a/FoxTube/App.xaml +++ b/FoxTube/App.xaml @@ -5,5 +5,13 @@ xmlns:local="using:FoxTube"> Red + + diff --git a/FoxTube/App.xaml.cs b/FoxTube/App.xaml.cs index 5198496..771f189 100644 --- a/FoxTube/App.xaml.cs +++ b/FoxTube/App.xaml.cs @@ -4,13 +4,15 @@ using Microsoft.AppCenter.Analytics; using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; +using System.Xml; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; using Windows.ApplicationModel.Background; +using Windows.ApplicationModel.Core; using Windows.Globalization; using Windows.Storage; -using Windows.System; using Windows.System.Power; using Windows.UI.Notifications; using Windows.UI.Xaml; @@ -19,19 +21,13 @@ using Windows.UI.Xaml.Navigation; namespace FoxTube { - /// - /// Provides application-specific behavior to supplement the default Application class. - /// sealed partial class App : Application { - /// - /// Initializes the singleton application object. This is the first line of authored code - /// executed, and as such is the logical equivalent of main() or WinMain(). - /// + Stopwatch sw = new Stopwatch(); public App() { SettingsStorage.LoadData(); - + switch (SettingsStorage.Theme) { case 0: @@ -41,20 +37,46 @@ namespace FoxTube RequestedTheme = ApplicationTheme.Dark; break; } - ApplicationLanguages.PrimaryLanguageOverride = SettingsStorage.Language; + + CheckVersion(); + InitializeComponent(); Suspending += OnSuspending; UnhandledException += UnhandledError; + AppCenter.Start("45774462-9ea7-438a-96fc-03982666f39e", typeof(Analytics)); AppCenter.SetCountryCode(SettingsStorage.Region); + + sw.Start(); } /// - /// Invoked when the application is launched normally by the end user. Other entry points - /// will be used such as when the application is launched to open a specific file. + /// Comparing current version with last recorded version. If doesn't match, poping up changelog notification /// - /// Details about the launch request and process. + public async void CheckVersion() + { + PackageVersion ver = Package.Current.Id.Version; + if (SettingsStorage.Version != $"{ver.Major}.{ver.Minor}") + { + try + { + XmlDocument changelog = new XmlDocument(); + StorageFile file = await (await Package.Current.InstalledLocation.GetFolderAsync(@"Assets\Data")).GetFileAsync("Patchnotes.xml"); + changelog.Load(await file.OpenStreamForReadAsync()); + XmlElement e = changelog["items"].ChildNodes[0] as XmlElement; + + ToastNotificationManager.CreateToastNotifier().Show(Background.Notification.GetChangelogToast(e.GetAttribute("version"))); + + SettingsStorage.Version = $"{ver.Major}.{ver.Minor}"; + } + catch + { + Debug.WriteLine("Unable to retrieve changelog"); + } + } + } + protected override void OnLaunched(LaunchActivatedEventArgs e) { // Do not repeat app initialization when the Window already has content, @@ -97,7 +119,6 @@ namespace FoxTube return; var backgroundRequest = await BackgroundExecutionManager.RequestAccessAsync(); - var saverRequest = PowerManager.EnergySaverStatus; if (backgroundRequest == BackgroundAccessStatus.DeniedBySystemPolicy || backgroundRequest == BackgroundAccessStatus.DeniedByUser) return; @@ -171,9 +192,6 @@ namespace FoxTube Debug.WriteLine(e.Message); } break; - case "download": - await Launcher.LaunchFileAsync(await StorageFile.GetFileFromPathAsync(arguments[1])); - break; } } @@ -199,57 +217,60 @@ namespace FoxTube Window.Current.Activate(); - if (e is ToastNotificationActivatedEventArgs) + switch (e.Kind) { - string[] args = (e as ToastNotificationActivatedEventArgs).Argument.Split('|'); - switch (args[0]) - { - case "changelog": - case "inbox": - Methods.MainPage.GoToDeveloper(args[1]); - break; + case ActivationKind.Protocol: + break; + case ActivationKind.ToastNotification: + string[] args = (e as ToastNotificationActivatedEventArgs).Argument.Split('|'); + switch (args[0]) + { + case "changelog": + case "inbox": + Methods.MainPage.GoToDeveloper(args[1]); + break; - case "video": - Methods.MainPage.GoToVideo(args[1]); - break; + case "video": + Methods.MainPage.GoToVideo(args[1]); + break; - case "channel": - Methods.MainPage.GoToChannel(args[1]); - break; - case "download": - Methods.MainPage.GoToDownloads(); - break; - case "dcancel": - DownloadAgent.Remove(args[1]); - break; - } + case "channel": + Methods.MainPage.GoToChannel(args[1]); + break; + case "download": + Methods.MainPage.GoToDownloads(); + break; + case "dcancel": + DownloadAgent.Cancel(args[1]); + break; + } + break; } } - /// - /// Invoked when Navigation to a certain page fails - /// - /// The Frame which failed navigation - /// Details about the navigation failure + void Launch(string e = null) + { + + } + void OnNavigationFailed(object sender, NavigationFailedEventArgs e) { throw new Exception("Failed to load Page " + e.SourcePageType.FullName); } - - /// - /// Invoked when application execution is being suspended. Application state is saved - /// without knowing whether the application will be terminated or resumed with the contents - /// of memory still intact. - /// - /// The source of the suspend request. - /// Details about the suspend request. + private void OnSuspending(object sender, SuspendingEventArgs e) { var deferral = e.SuspendingOperation.GetDeferral(); - SettingsStorage.ExportSettings(); + + sw.Stop(); + SettingsStorage.Uptime += sw.Elapsed; + + SettingsStorage.SaveData(); DownloadAgent.QuitPrompt(); deferral.Complete(); + Analytics.TrackEvent("Session terminated"); } + private void UnhandledError(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e) { Analytics.TrackEvent("The app crashed", new Dictionary() diff --git a/FoxTube/Assets/BadgeLogo.scale-100.png b/FoxTube/Assets/BadgeLogo.scale-100.png index d7bd4fb..986ba33 100644 Binary files a/FoxTube/Assets/BadgeLogo.scale-100.png and b/FoxTube/Assets/BadgeLogo.scale-100.png differ diff --git a/FoxTube/Assets/BadgeLogo.scale-125.png b/FoxTube/Assets/BadgeLogo.scale-125.png index 079bb98..3e9cb8e 100644 Binary files a/FoxTube/Assets/BadgeLogo.scale-125.png and b/FoxTube/Assets/BadgeLogo.scale-125.png differ diff --git a/FoxTube/Assets/BadgeLogo.scale-150.png b/FoxTube/Assets/BadgeLogo.scale-150.png index 1166617..28bbb0c 100644 Binary files a/FoxTube/Assets/BadgeLogo.scale-150.png and b/FoxTube/Assets/BadgeLogo.scale-150.png differ diff --git a/FoxTube/Assets/BadgeLogo.scale-200.png b/FoxTube/Assets/BadgeLogo.scale-200.png index aa41fb1..f45a820 100644 Binary files a/FoxTube/Assets/BadgeLogo.scale-200.png and b/FoxTube/Assets/BadgeLogo.scale-200.png differ diff --git a/FoxTube/Assets/BadgeLogo.scale-400.png b/FoxTube/Assets/BadgeLogo.scale-400.png index bcddc3a..42b17d6 100644 Binary files a/FoxTube/Assets/BadgeLogo.scale-400.png and b/FoxTube/Assets/BadgeLogo.scale-400.png differ diff --git a/FoxTube/Assets/Data/Patchnotes.xml b/FoxTube/Assets/Data/Patchnotes.xml index 5d5cf2f..b07d623 100644 --- a/FoxTube/Assets/Data/Patchnotes.xml +++ b/FoxTube/Assets/Data/Patchnotes.xml @@ -1,5 +1,65 @@  + + + ### What's new: +- Improved stability and speed of the app +- Fixed a lot of bugs +- Rebuilt player +- Added animations and acrylic +- Added accout information +- Added history +- Added "Watch later" playlist +- Added "Recommended" and "Subscriptions" tabs on home page +- Added tiny icon near the channel search +- Added ability to delete comments +- Rebuilt videos downloading +- Added transparent title bar +- Added prompts to rate the app and leave feedback (dialogs shows up after 12 and 24 hours of active using) +- Rebuilt cards grid +- Added publish date on the video page +- Stream countdown was rebuilt and moved to the top of video page +- Current playlist focuses on current video +- Text selection color is now red instead of system accent color +- Added toast notifications for livestreams + +### Known issues: +- 'History' and 'Watch later' playlists sometimes are missing. Solution: restart the app +- The same problem with 'Recommended' and 'Subscriptions' tabs on home page +- Somewhere there is no russian locale. Will be finished in 1.0 version +- Playlists management will be introduced in the next version but it may miss some playlists +- Even though I've done ads delivering system it's not introduced because of some problems with ads' lookup. I just want to make your ads experience to be the best one. So it won't be implemented until I make sure that they are ready + + ### Что нового: +- Улучшена стабильность и скорость приложения +- Исправлена куча багов +- Переработан плеер +- Добавлены анимации и акрил +- Добавлена информация об аккаунте +- Добавлена история +- Добавлен плейлист "Посмотреть позже" +- Добавлены вкладки "Рекоммендованные" и "Подписки" на домашней странице +- Добавлена маленькая иконка канала при прокрутке вниз на странице канала +- Добавлена возможность удалять комментарии +- Переработано скачивание видео +- Добавлен прозрачный заголовок окна +- Добавлены всплывающие уведомления с просьбой оценить приложение и оставить отзыв (появляются после 12 и 24 часов активного использования) +- Переработана сетка карточек +- Добавлена информация о дате публикации видео на странице просмотра +- Обратный отсчет для стримов переработан и перенесен вверх страницы +- Список видео текущего плейлиста сразу перематывается на текущее +- Текст выделяется красным, а не текущим цветом системы +- Добавлены уведомления для прямых эфиров + +### Известные проблемы: +- История и плейлист 'Посмотреть позже' иногда могут не отображаться. Решение: перезапустить приложение +- Аналогично и со вкладками 'Рекомендованные' и 'Подписки' на домашней странице +- В некоторых местах отсутствует русская локализация. Будет дополнена в версии 1.0 +- Управление плейлистами будет добавлено в следующей версии, но некоторые плейлисты могут отсутствовать +- Хотя я сделал систему доставки рекламы, она не введена из-за некоторых проблем с видом банеров. Я хочу сделать ваш опыт взаимодействия с рекламой в приложении лучше, так что она не будет введена до тех пор, пока я не буду в этом уверен + + + ### What's new: diff --git a/FoxTube/Assets/Data/RevEn.xml b/FoxTube/Assets/Data/RevEn.xml new file mode 100644 index 0000000..5d5cf2f --- /dev/null +++ b/FoxTube/Assets/Data/RevEn.xml @@ -0,0 +1,55 @@ + + + + + ### What's new: + +- Small fixes +- First public pre-release version +- Some content was cut out due to its incompleteness + + ### Что нового: + +- Мелкие исправления багов +- Эта версия является первой пред-релизной публичной версией +- Некотроые функции были вырезаны из-за их незавершенности + + + + + + ### What's new: + +- 'Live' button fixed in the player +- Long channel names on crads fixed +- Fixed video description disappearing on window resizing +- Player seek is fixed +- Added video buffering progress indicatior +- Small fixes + +### Known issues: + +- Recommended and subscriptions pages aren't implemented +- History isn't implemented +- Playlists management isn't implemented +- Ads aren't implemented + + ### Что нового: + +- Кнопка перехода к прямому эфиру на стримах теперь работает +- Исправлен баг с длинными именами каналов на карточках +- Исправлено исчезание описания видео при изменении размеров окна +- Исправлен ползунок перемотки видео +- Добавлен индикатор буферизации видео +- Мелкие исправления + +### Что по-прежнему не работает: + +- Страница рекомендованных видео и страница видео с подписок +- История +- Работа с плейлистами +- Нет карточек рекламы + + + + diff --git a/FoxTube/Assets/videoPlaceholder.png b/FoxTube/Assets/videoPlaceholder.png new file mode 100644 index 0000000..54f5e6a Binary files /dev/null and b/FoxTube/Assets/videoPlaceholder.png differ diff --git a/FoxTube/Classes/DownloadAgent.cs b/FoxTube/Classes/DownloadAgent.cs index 5b21ec6..c46fdb6 100644 --- a/FoxTube/Classes/DownloadAgent.cs +++ b/FoxTube/Classes/DownloadAgent.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using Windows.Storage; -using FoxTube.Classes; using Newtonsoft.Json; using YoutubeExplode.Models.MediaStreams; using Google.Apis.YouTube.v3.Data; using FoxTube.Controls; +using FoxTube.Pages; namespace FoxTube { @@ -13,6 +13,7 @@ namespace FoxTube { public static List items = new List(); private static ApplicationDataContainer settings = ApplicationData.Current.LocalSettings; + public static Downloads Page { get; set; } public static StorageFolder Downloads { get; set; } public static async void Initialize() @@ -31,45 +32,34 @@ namespace FoxTube items.Insert(0, new DownloadItem(info, meta, qualty)); } - public static void CancelItem(string id) + public static void Remove(DownloadItem item) { - DownloadItem item = items.Find(x => x.Container.Id == id); - if (item == null || !item.InProgress) - return; - - item.CancelPrompt(); + try { Page.Remove(item); } + catch { } + items.Remove(item); } - public static void Remove(string id) + public static void Cancel(string id) { - DownloadItem item = items.Find(x => x.Container.Id == id); - if (item == null) - return; - - if (item.InProgress) + DownloadItem item = items.Find(i => i.Container.Id == id); + if (item != null) item.Cancel(); - else - items.Remove(item); } public static void QuitPrompt() { - foreach (DownloadItem i in items.FindAll(i => i.InProgress)) + foreach (DownloadItem i in items.FindAll(i => !i.Container.IsDownloaded)) + { i.Cancel(); + items.Remove(i); + } List containers = new List(); items.ForEach(i => containers.Add(i.Container)); string data = JsonConvert.SerializeObject(containers); - try - { - settings.Values["downloads"] = data; - } - catch - { - settings.Values.Add("downloads", data); - } + settings.Values["downloads"] = data; } } } diff --git a/FoxTube/Classes/DownloadItemContainer.cs b/FoxTube/Classes/DownloadItemContainer.cs deleted file mode 100644 index 0a99e6d..0000000 --- a/FoxTube/Classes/DownloadItemContainer.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace FoxTube.Classes -{ - public class DownloadItemContainer - { - public string Title { get; set; } - public string Channel { get; set; } - public string Id { get; set; } - public string Name { get; set; } - public string Extension { get; set; } - public Uri Thumbnail { get; set; } - public string Quality { get; set; } - public TimeSpan Duration { get; set; } - } -} diff --git a/FoxTube/Classes/InboxItem.cs b/FoxTube/Classes/InboxItem.cs index 5342d2c..21ff0cc 100644 --- a/FoxTube/Classes/InboxItem.cs +++ b/FoxTube/Classes/InboxItem.cs @@ -9,6 +9,16 @@ namespace FoxTube.Classes { public InboxItemType Type { get; set; } = InboxItemType.Default; public DateTime TimeStamp { get; set; } + public string TimeStampString + { + get + { + if (Type == InboxItemType.PatchNote) + return TimeStamp.ToShortDateString(); + else + return TimeStamp.ToString(); + } + } public string Subject { get; set; } public string Content { get; set; } diff --git a/FoxTube/Classes/Methods.cs b/FoxTube/Classes/Methods.cs index be01184..18890fe 100644 --- a/FoxTube/Classes/Methods.cs +++ b/FoxTube/Classes/Methods.cs @@ -1,16 +1,19 @@ 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; @@ -19,16 +22,20 @@ 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; namespace FoxTube { + public interface NavigationPage + { + object Parameter { get; set; } + } public static class Methods { private static ResourceLoader resources = ResourceLoader.GetForCurrentView("Methods"); public static CommentsPage CommentsPage { get; set; } - - public static bool NeedToResponse { get; set; } = false; + public static MainPage MainPage { get { return (Window.Current.Content as Frame).Content as MainPage; } @@ -150,41 +157,25 @@ namespace FoxTube public static void FormatText(ref TextBlock block, string text) { block.Inlines.Clear(); + Regex filter = new Regex(@"\b((?:https?://|www\.)\S+)|(\S+@\S+)\b", RegexOptions.IgnoreCase); + Regex link = new Regex(@"\b(?:https?://|www\.)\S+\b", RegexOptions.IgnoreCase); + Regex mail = new Regex(@"\b\S+@\S+\b", RegexOptions.IgnoreCase); - Regex regx = new Regex(@"(http(s)?://[\S]+|www.[\S]+|[\S]+@[\S]+)", RegexOptions.IgnoreCase); - Regex isWWW = new Regex(@"(http[s]?://[\S]+|www.[\S]+)"); - Regex isEmail = new Regex(@"[\S]+@[\S]+"); - foreach (string item in regx.Split(text)) + foreach (string item in filter.Split(text)) { - if (isWWW.IsMatch(item)) + if (link.IsMatch(item)) { - try - { - Hyperlink link = new Hyperlink(); - link.Click += (s, arg) => { ProcessLink(item); }; - link.Inlines.Add(new Run { Text = item }); - block.Inlines.Add(link); - } - catch - { - block.Inlines.Add(new Run { Text = item }); - } + Hyperlink hl = new Hyperlink(); + hl.Click += (s, arg) => ProcessLink(item); + hl.Inlines.Add(new Run { Text = item }); + block.Inlines.Add(hl); } - else if (isEmail.IsMatch(item)) + else if (mail.IsMatch(item)) { - try - { - Hyperlink link = new Hyperlink { NavigateUri = new Uri($"mailto:{item}"), Foreground = new SolidColorBrush(Colors.Red) }; - link.Inlines.Add(new Run { Text = item }); - block.Inlines.Add(link); - } - catch - { - block.Inlines.Add(new Run { Text = item }); - } + Hyperlink hl = new Hyperlink { NavigateUri = $"mailto:{item}".ToUri() }; + hl.Inlines.Add(new Run { Text = item }); + block.Inlines.Add(hl); } - else if (item == "s") - continue; else block.Inlines.Add(new Run { Text = item }); } @@ -220,68 +211,51 @@ namespace FoxTube return "Unknown"; } } - + public async static void ProcessLink(string url) { - try - { - Debug.WriteLine($"Processing link: {url}"); - if (url.Contains("youtube.com/") || url.Contains("youtu.be/")) - { - Debug.WriteLine("This is an internal youtube link"); - url = url.Replace("https://", "").Replace("http://", "").Replace("wwww.", "").Replace("//", ""); - Debug.WriteLine($"Prepared link: {url}"); + string output; + string type; - if (url.Contains("/playlist")) - { - Debug.WriteLine($"This is a playlist link. ID: {HttpUtility.ParseQueryString(url).Get("list")}"); - MainPage.GoToPlaylist(HttpUtility.ParseQueryString(url).Get("list")); - } - else if (url.Contains("youtu.be/")) - { - Debug.WriteLine($"This is obfuscated video link. Video ID: {url.Split('/')[1]}"); - MainPage.GoToVideo(url.Split('/')[1]); - } - else if (url.Contains("/watch")) - { - Debug.WriteLine($"This is regular video link. Video ID: {HttpUtility.ParseQueryString(url).Get("v")}"); - MainPage.GoToVideo(HttpUtility.ParseQueryString(url).Get(0), HttpUtility.ParseQueryString(url).Get("list")); - } - else if (url.Contains("/v/")) - { - Debug.WriteLine($"This is video link. ID: {url.Split('/')[2].Split('?')[0]}"); - MainPage.GoToVideo(url.Split('/')[2].Split('?')[0]); - } - else if (url.Contains("/channel/")) - { - Debug.WriteLine($"This is channel link. ID: {url.Split('/')[2]}"); - MainPage.GoToChannel(url.Split('/')[2]); - } - else if (url.Contains("/user/")) - { - Debug.WriteLine($"This is channel link with username. Username: {url.Split('/')[2]}"); - ChannelsResource.ListRequest request = SecretsVault.Service.Channels.List("id"); - Debug.WriteLine(request.ForUsername = url.Split('/')[2]); - request.MaxResults = 1; - MainPage.GoToChannel((await request.ExecuteAsync()).Items[0].Id); - } - else if (url.Contains("/c/")) - { - Debug.WriteLine($"This is channel link with custom url. Custom name: {url.Split('/')[2]}"); - SearchResource.ListRequest request = SecretsVault.Service.Search.List("id"); - Debug.WriteLine(request.Q = url.Split('/')[2]); - request.MaxResults = 1; - MainPage.GoToChannel((await request.ExecuteAsync()).Items[0].Id.ChannelId); - } - else - throw new Exception(); - } - else - throw new Exception(); - } - catch + if (YoutubeClient.TryParseChannelId(url, out output)) { - await Launcher.LaunchUriAsync(new Uri(url)); + type = "channel"; + goto LinkFound; + } + else if (YoutubeClient.TryParsePlaylistId(url, out output)) + { + type = "playlist"; + goto LinkFound; + } + else if (YoutubeClient.TryParseUsername(url, out output)) + { + type = "user"; + goto LinkFound; + } + else if (YoutubeClient.TryParseVideoId(url, out output)) + { + type = "video"; + goto LinkFound; + } + + await Launcher.LaunchUriAsync(new Uri(url)); + return; + + LinkFound: + switch (type) + { + case "channel": + MainPage.GoToChannel(output); + break; + case "video": + MainPage.GoToVideo(output); + break; + case "playlist": + MainPage.GoToPlaylist(output); + break; + case "user": + MainPage.GoToChannel(await new YoutubeClient().GetChannelIdAsync(output)); + break; } } @@ -308,5 +282,31 @@ namespace FoxTube deferral.Complete(); } } + + public static async Task> GetHistory() + { + List list = new List(); + + string output = await SecretsVault.HttpClient.GetStringAsync($"https://www.youtube.com/list_ajax?style=json&action_get_list=1&list=HL&hl={SettingsStorage.RelevanceLanguage}"); + + dynamic raw = JsonConvert.DeserializeObject(output); + foreach (dynamic i in raw.video) + list.Add(i.encrypted_id.ToString()); + + return list; + } + + public static async Task> GetLater() + { + List list = new List(); + + string output = await SecretsVault.HttpClient.GetStringAsync($"https://www.youtube.com/list_ajax?style=json&action_get_list=1&list=WL&hl={SettingsStorage.RelevanceLanguage}"); + + dynamic raw = JsonConvert.DeserializeObject(output); + foreach (dynamic i in raw.video) + list.Add(i.encrypted_id.ToString()); + + return list; + } } } diff --git a/FoxTube/Classes/SearchPaameters.cs b/FoxTube/Classes/SearchPaameters.cs index 8b6e86c..323cb70 100644 --- a/FoxTube/Classes/SearchPaameters.cs +++ b/FoxTube/Classes/SearchPaameters.cs @@ -129,5 +129,21 @@ namespace FoxTube Channel = channelId; Filter = filters; } + + public override string ToString() + { + return $@"Term: {Term} +Channel id: {Channel} +Filters: + Order: {Filter.Order} + Type: {Filter.Type} + Date: {Filter.Date} + Duration: {Filter.Duration} + HD: {Filter.HD} + 3D: {Filter.Is3D} + Event type: {Filter.LiveEvent} + CC: {Filter.Captions} + License: {Filter.CreativeCommons}"; + } } } diff --git a/FoxTube/Classes/SecretsVault.cs b/FoxTube/Classes/SecretsVault.cs index 7c7024d..990e5a9 100644 --- a/FoxTube/Classes/SecretsVault.cs +++ b/FoxTube/Classes/SecretsVault.cs @@ -9,6 +9,10 @@ using Google.Apis.YouTube.v3.Data; using Newtonsoft.Json; using Windows.Storage; using Windows.Services.Store; +using System.Net.Http; +using Google.Apis.Oauth2.v2.Data; +using Google.Apis.Oauth2.v2; +using static Google.Apis.Auth.OAuth2.UwpCodeReceiver; namespace FoxTube { @@ -37,18 +41,22 @@ namespace FoxTube ApplicationName = "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"; public static bool AdsDisabled { get; private set; } = true; //User info public static bool IsAuthorized => Credential != null; - public static UserCredential Credential { get; set; } + private static UserCredential Credential { get; set; } public static string AccountId => UserChannel?.Id; public static Channel UserChannel { get; private set; } + public static Userinfoplus UserInfo { get; private set; } public static List Subscriptions { get; } = new List(); + public static List History { get; set; } = new List(); + public static List WatchLater { get; set; } = new List(); #endregion #region Methods @@ -69,7 +77,7 @@ namespace FoxTube try { await Service.Subscriptions.Delete(s.Id).ExecuteAsync(); } catch { return true; } - SubscriptionsChanged?.Invoke(null, "remove", Subscriptions.IndexOf(s)); + SubscriptionsChanged?.Invoke(null, "remove", s); Subscriptions.Remove(s); return false; } @@ -96,35 +104,67 @@ namespace FoxTube } } + /// + /// Sets up **SecretsVault** + /// + public static void Initialize() + { + CheckAuthorization(); + // TODO: Reactivate addons initialization + //CheckAddons(); + } + /// /// Prompts to add an Youtube account and retrieves its info when successful /// /// Loads user's subscriptions if true public static async void Authorize(bool retrieveSubs = true) { + #region Retrieving user's credential try { - #region Retrieving user's credential Credential = await GoogleWebAuthorizationBroker.AuthorizeAsync( - Secrets, - new[] - { - Google.Apis.Oauth2.v2.Oauth2Service.Scope.UserinfoProfile, - YouTubeService.Scope.YoutubeForceSsl - }, - "user", - CancellationToken.None); - - if (Credential == null || !retrieveSubs) - goto InvokeEvent; + Secrets, + new[] + { + Oauth2Service.Scope.UserinfoProfile, + Oauth2Service.Scope.UserinfoEmail, + YouTubeService.Scope.YoutubeForceSsl, + YouTubeService.Scope.Youtube, + YouTubeService.Scope.YoutubeUpload, + YouTubeService.Scope.YoutubeReadonly, + YouTubeService.Scope.Youtubepartner + }, + "user", + CancellationToken.None); + } + catch (AuthenticateException e) + { + if (e.Message.Contains("UserCancel")) + return; + else + throw e; + } - SettingsStorage.HasAccount = true; - #endregion + if (Credential == null || !retrieveSubs) + return; + SettingsStorage.HasAccount = true; + + HttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Credential.Token.AccessToken); + #endregion + + try + { #region Retrieving user's data - var request = Service.Channels.List("snippet,contentDetails"); - request.Mine = true; - UserChannel = (await request.ExecuteAsync()).Items[0]; + UserInfo = await new Oauth2Service(Initializer).Userinfo.Get().ExecuteAsync(); + + try + { + WatchLater = await Methods.GetLater(); + History = await Methods.GetHistory(); + } + catch { } SubscriptionsResource.ListRequest subRequest = Service.Subscriptions.List("snippet"); subRequest.Mine = true; @@ -143,13 +183,16 @@ namespace FoxTube nextToken = subResponse.NextPageToken; } while (!string.IsNullOrWhiteSpace(nextToken)); + + var request = Service.Channels.List("snippet,contentDetails"); + request.Mine = true; + UserChannel = (await request.ExecuteAsync()).Items[0]; #endregion //Saving user's subscriptions for background task SaveSubscriptions(); - InvokeEvent: - AuthorizationStateChanged?.Invoke(args: IsAuthorized); + AuthorizationStateChanged?.Invoke(args: true); } catch { @@ -160,13 +203,11 @@ namespace FoxTube /// /// Saves user's subscriptions keypairs (channel ID: avatar URL) into "background.json" file for concurrent background processing /// - public static async void SaveSubscriptions() + public static void SaveSubscriptions() { Dictionary subs = new Dictionary(); - Subscriptions.ForEach(x => subs.Add(x.Snippet.ResourceId.ChannelId, x.Snippet.Thumbnails.Medium.Url)); - await FileIO.WriteTextAsync( - await ApplicationData.Current.RoamingFolder.CreateFileAsync("background.json", CreationCollisionOption.ReplaceExisting), - JsonConvert.SerializeObject(subs)); + Subscriptions.ForEach(x => subs.Add(x.Snippet.ResourceId.ChannelId, x.Snippet.Thumbnails.Default__.Url)); + ApplicationData.Current.RoamingSettings.Values["subscriptions"] = JsonConvert.SerializeObject(subs); } /// @@ -179,6 +220,8 @@ namespace FoxTube Credential = null; AuthorizationStateChanged?.Invoke(args: false); SettingsStorage.HasAccount = false; + + ApplicationData.Current.RoamingSettings.Values["subscriptions"] = ""; } } @@ -190,7 +233,11 @@ namespace FoxTube { if (SettingsStorage.HasAccount) Authorize(retrieveSubs); - else AuthorizationStateChanged.Invoke(args: false); + else + { + AuthorizationStateChanged.Invoke(args: false); + ApplicationData.Current.RoamingSettings.Values["subscriptions"] = ""; + } } /// @@ -201,9 +248,11 @@ namespace FoxTube try { StoreContext store = StoreContext.GetDefault(); - StoreProductQueryResult requset = await store.GetAssociatedStoreProductsAsync(new[] { "Consumable", "Durable", "UnmanagedConsumable" }); + StoreProductQueryResult requset = await store.GetAssociatedStoreProductsAsync(new[] { "Durable" }); + Dictionary l = new Dictionary(); + requset.Products.ForEach(i => l.Add(i.Key, i.Value)); - if (!requset.Products["foxtube-adsremove"].IsInUserCollection) + if (!requset.Products["9NP1QK556625"].IsInUserCollection) { AdsDisabled = false; Purchased?.Invoke(args:false); @@ -215,7 +264,7 @@ namespace FoxTube public static async void GetAdblock() { StoreContext store = StoreContext.GetDefault(); - StorePurchaseResult request = await store.RequestPurchaseAsync("foxtube-adsremove"); + StorePurchaseResult request = await store.RequestPurchaseAsync("9NP1QK556625"); switch (request.Status) { diff --git a/FoxTube/Classes/SettingsStorage.cs b/FoxTube/Classes/SettingsStorage.cs index 9a58a04..b5a952b 100644 --- a/FoxTube/Classes/SettingsStorage.cs +++ b/FoxTube/Classes/SettingsStorage.cs @@ -7,128 +7,152 @@ using Windows.Storage; namespace FoxTube { - public enum MatureState { Blocked, Allowed, AllowedOnce } + public enum MatureState { Blocked, Allowed } + + public class SettingsContainer + { + public string videoQuality = "remember"; + public string rememberedQuality = "1080p"; + + public bool videoNotifications = true; + public bool devNotifications = true; + + public bool checkConnection = true; + public bool autoplay = true; + public double volume = 100; + + public string language = (new[] { "ua", "ru", "by", "kz", "kg", "md", "lv", "ee" }).Contains(CultureInfo.InstalledUICulture.TwoLetterISOLanguageName) ? "ru-RU" : "en-US"; + public string relevanceLanguage = (new[] { "ua", "ru", "by", "kz", "kg", "md", "lv", "ee" }).Contains(CultureInfo.InstalledUICulture.TwoLetterISOLanguageName) ? "ru" : "en"; + public string region = (new[] { "ua", "ru", "by", "kz", "kg", "md", "lv", "ee" }).Contains(CultureInfo.InstalledUICulture.TwoLetterISOLanguageName) ? "ru" : "us"; + public int safeSearch = 0; + + public bool hasAccount = false; + public int theme = 2; + + public TimeSpan uptime = TimeSpan.FromSeconds(0); + public bool promptReview = true; + public bool promptFeedback = true; + } public static class SettingsStorage { public static string VideoQuality { - get { return (string)settings[0]; } + get { return Container.videoQuality; } set { - settings[0] = value; + Container.videoQuality = value; SaveData(); } } public static string RememberedQuality { - get { return (string)settings[1]; } + get { return Container.rememberedQuality; } set { - settings[1] = value; + Container.rememberedQuality = value; SaveData(); } } public static bool VideoNotifications { - get { return (bool)settings[2]; } + get { return Container.videoNotifications; } set { - settings[2] = value; + Container.videoNotifications = value; SaveData(); } } public static bool DevNotifications { - get { return (bool)settings[3]; } + get { return Container.devNotifications; } set { - settings[3] = value; + Container.devNotifications = value; SaveData(); } } public static bool CheckConnection { - get { return (bool)settings[4]; } + get { return Container.checkConnection; } set { - settings[4] = value; + Container.checkConnection = value; SaveData(); } } public static bool Autoplay { - get { return (bool)settings[5]; } + get { return Container.autoplay; } set { - settings[5] = value; + Container.autoplay = value; SaveData(); } } - public static int Volume + public static double Volume { - get { return Convert.ToInt32(settings[6]); } + get { return Container.volume; } set { - settings[6] = value; + Container.volume = value; SaveData(); } } public static string Language { - get { return (string)settings[7]; } + get { return Container.language; } set { - settings[7] = value; + Container.language = value; SaveData(); } } public static string RelevanceLanguage { - get { return (string)settings[8]; } + get { return Container.relevanceLanguage; } set { - settings[8] = value; + Container.relevanceLanguage = value; SaveData(); } } public static string Region { - get { return (string)settings[9]; } + get { return Container.region; } set { - settings[9] = value; + Container.region = value; SaveData(); } } public static int SafeSearch { - get { return Convert.ToInt32(settings[10]); } + get { return Container.safeSearch; } set { - settings[10] = value; - SaveData(); - } - } - - public static int Theme - { - get { return Convert.ToInt32(settings[11]); } - set - { - settings[11] = value; + Container.safeSearch = value; SaveData(); } } public static bool HasAccount { - get { return (bool)settings[12]; } + get { return Container.hasAccount; } set { - settings[12] = value; + Container.hasAccount = value; + SaveData(); + } + } + public static int Theme + { + get { return Container.theme; } + set + { + Container.theme = value; SaveData(); } } @@ -167,95 +191,54 @@ namespace FoxTube } } + public static TimeSpan Uptime + { + get { return Container.uptime; } + set + { + Container.uptime = value; + SaveData(); + } + } + public static bool PromptReview + { + get { return Container.promptReview; } + set + { + Container.promptReview = value; + SaveData(); + } + } + public static bool PromptFeedback + { + get { return Container.promptFeedback; } + set + { + Container.promptFeedback = value; + SaveData(); + } + } + //Settings storage private static readonly ApplicationDataContainer storage = ApplicationData.Current.RoamingSettings; - - //Predefined preferences - private static object[] settings = new object[] - { - "remember", - "1080p", - - true, - true, - - true, - true, - 100, - - (new[] { "ua", "ru", "by", "kz", "kg", "md", "lv", "ee" }).Contains(CultureInfo.InstalledUICulture.TwoLetterISOLanguageName) ? "ru-RU" : "en-US", - (new[] { "ua", "ru", "by", "kz", "kg", "md", "lv", "ee" }).Contains(CultureInfo.InstalledUICulture.TwoLetterISOLanguageName) ? "ru" : "en", - (new[] { "ua", "ru", "by", "kz", "kg", "md", "lv", "ee" }).Contains(CultureInfo.InstalledUICulture.TwoLetterISOLanguageName) ? "ru" : "US", - 0, - - 2, - false - }; + private static SettingsContainer Container; public static void LoadData() { - /*if(true && (storage.Values["forceUpdate"] == null || storage.Values["forceUpdate"].ToString() != Version)) - { - SaveData(); - storage.Values["forceUpdate"] = Version; - return; - }*/ - try { - object[] saved = JsonConvert.DeserializeObject(storage.Values["settings"] as string); - if (settings.Length > saved.Length) - { - if (saved[0] is string) - settings[0] = saved; - if (saved[1] is string) - settings[1] = saved; - if (saved[2] is bool) - settings[2] = saved; - if (saved[3] is bool) - settings[3] = saved; - if (saved[4] is bool) - settings[4] = saved; - if (saved[5] is bool) - settings[5] = saved; - if (saved[6] is int) - settings[6] = saved; - if (saved[7] is string) - settings[7] = saved; - if (saved[8] is string) - settings[8] = saved; - if (saved[9] is string) - settings[9] = saved; - if (saved[10] is int) - settings[10] = saved; - if (saved[11] is int) - settings[11] = saved; - if (saved[12] is bool) - settings[12] = saved; - - SaveData(); - } - else settings = saved; + Container = JsonConvert.DeserializeObject(storage.Values["settings"] as string); + } + catch + { + Container = new SettingsContainer(); + SaveData(); } - catch (ArgumentNullException) { } } public static void SaveData() { - storage.Values["settings"] = JsonConvert.SerializeObject(settings); - ExportSettings(); - } - - public static async void ExportSettings() - { - try - { - bool[] notificationsSettings = new[] { VideoNotifications, DevNotifications }; - await FileIO.WriteTextAsync( - await ApplicationData.Current.RoamingFolder.CreateFileAsync("notifications.json", CreationCollisionOption.ReplaceExisting), - JsonConvert.SerializeObject(notificationsSettings)); - } - catch { } + storage.Values["settings"] = JsonConvert.SerializeObject(Container); } } } diff --git a/FoxTube/Controls/Adverts/CardAdvert.xaml b/FoxTube/Controls/Adverts/CardAdvert.xaml index 8582208..4f5a4f6 100644 --- a/FoxTube/Controls/Adverts/CardAdvert.xaml +++ b/FoxTube/Controls/Adverts/CardAdvert.xaml @@ -7,38 +7,38 @@ mc:Ignorable="d" HorizontalAlignment="Stretch" VerticalAlignment="Top" - SizeChanged="UserControl_SizeChanged" d:DesignHeight="290" - d:DesignWidth="384"> + d:DesignWidth="384" + Visibility="Collapsed"> - diff --git a/FoxTube/Controls/Adverts/CardAdvert.xaml.cs b/FoxTube/Controls/Adverts/CardAdvert.xaml.cs index 64fd8e8..cb567f1 100644 --- a/FoxTube/Controls/Adverts/CardAdvert.xaml.cs +++ b/FoxTube/Controls/Adverts/CardAdvert.xaml.cs @@ -31,10 +31,8 @@ namespace FoxTube.Controls.Adverts { title.Text = advert.Title; image.Source = new BitmapImage(advert.MainImages.First().Url.ToUri()); - if (advert.AdIcon == null) - contentGrid.ColumnDefinitions[0].Width = new GridLength(0); - else - icon.ProfilePicture = advert.AdIcon.Source; + + icon.ProfilePicture = advert.AdIcon.Source; if (string.IsNullOrWhiteSpace(advert.SponsoredBy)) sponsor.Visibility = Visibility.Collapsed; @@ -53,10 +51,5 @@ namespace FoxTube.Controls.Adverts Visibility = Visibility.Visible; } - - private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e) - { - Height = e.NewSize.Width * 0.75; - } } } diff --git a/FoxTube/Controls/ChannelCard.xaml b/FoxTube/Controls/ChannelCard.xaml index 79ccc3d..8abf26d 100644 --- a/FoxTube/Controls/ChannelCard.xaml +++ b/FoxTube/Controls/ChannelCard.xaml @@ -4,51 +4,65 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)" mc:Ignorable="d" HorizontalAlignment="Stretch" VerticalAlignment="Top" - SizeChanged="UserControl_SizeChanged" d:DesignHeight="290" - d:DesignWidth="384"> + d:DesignWidth="384" + Opacity="0"> - diff --git a/FoxTube/Controls/ChannelCard.xaml.cs b/FoxTube/Controls/ChannelCard.xaml.cs index 5a9bcec..48bcfd4 100644 --- a/FoxTube/Controls/ChannelCard.xaml.cs +++ b/FoxTube/Controls/ChannelCard.xaml.cs @@ -22,60 +22,70 @@ namespace FoxTube.Controls string channelId; Channel item; - public ChannelCard(string id, string live = "null") + public ChannelCard(string id, string live = null) { InitializeComponent(); Initialize(id, live); } - private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e) - { - Height = e.NewSize.Width * 0.75; - } - public async void Initialize(string id, string live) { - ChannelsResource.ListRequest request = SecretsVault.Service.Channels.List("snippet,statistics,brandingSettings"); - request.Id = id; - ChannelListResponse response = await request.ExecuteAsync(); - - item = response.Items[0]; - channelId = id; - - title.Text = item.Snippet.Title; - - subs.Text = $"{item.Statistics.SubscriberCount:0,0} {resources.GetString("/Cards/subscribers")}"; - uploads.Text = $"{item.Statistics.VideoCount:0,0} {resources.GetString("/Cards/videos")}"; - - if (live == "live") - liveTag.Visibility = Visibility.Visible; - - if(SecretsVault.IsAuthorized) + try { - foreach(Subscription s in SecretsVault.Subscriptions) + if (id == SecretsVault.AccountId) + grid.RowDefinitions[3].Height = new GridLength(0); + + ChannelsResource.ListRequest request = SecretsVault.Service.Channels.List("snippet,statistics,brandingSettings"); + request.Id = id; + ChannelListResponse response = await request.ExecuteAsync(); + + item = response.Items[0]; + channelId = id; + + title.Text = item.Snippet.Title; + description.Text = item.Snippet.Description; + + subs.Text = $"{item.Statistics.SubscriberCount:0,0} {resources.GetString("/Cards/subscribers")}"; + uploads.Text = $"{item.Statistics.VideoCount:0,0} {resources.GetString("/Cards/videos")}"; + + if (live == "live") + liveTag.Visibility = Visibility.Visible; + + if (SecretsVault.IsAuthorized) { - if(s.Snippet.ResourceId.ChannelId == id) + if (SecretsVault.Subscriptions.Exists(i => i.Snippet.ResourceId.ChannelId == id)) { subscribe.Background = new SolidColorBrush(Colors.Transparent); subscribe.Foreground = new SolidColorBrush(Colors.Gray); subscribe.Content = resources.GetString("/Cards/unsubscribe"); } + subscriptionPane.Visibility = Visibility.Visible; } - subscriptionPane.Visibility = Visibility.Visible; + + try { avatar.ProfilePicture = new BitmapImage(new Uri(item.Snippet.Thumbnails.Medium.Url)) { DecodePixelWidth = 74, DecodePixelHeight = 74 }; } + catch { } + try + { + if (!item.BrandingSettings.Image.BannerImageUrl.Contains("default")) + cover.Source = new BitmapImage(item.BrandingSettings.Image.BannerImageUrl.ToUri()); + } + catch { } + } + catch (Exception e) + { + Visibility = Visibility.Collapsed; + Microsoft.AppCenter.Analytics.Analytics.TrackEvent("VideoCard loading failed", new System.Collections.Generic.Dictionary() + { + { "Exception", e.GetType().ToString() }, + { "Message", e.Message }, + { "Video ID", id } + }); } - try { avatar.ProfilePicture = new BitmapImage(new Uri(item.Snippet.Thumbnails.Medium.Url)); } - catch { } - try - { - if (item.BrandingSettings.Image.BannerImageUrl.Contains("default")) - throw new Exception("Default channel cover detected"); - cover.Source = new BitmapImage((item.BrandingSettings.Image.BannerTvHighImageUrl ?? item.BrandingSettings.Image.BannerTvImageUrl).ToUri()); - } - catch { } + Opacity = 1; } - private void Button_Click(object sender, RoutedEventArgs e) + public void Button_Click(object sender, RoutedEventArgs e) { Methods.MainPage.GoToChannel(channelId); } @@ -104,13 +114,18 @@ namespace FoxTube.Controls private void GetLink_Click(object sender, RoutedEventArgs e) { DataPackage data = new DataPackage(); - data.SetText(string.IsNullOrWhiteSpace(item.Snippet.CustomUrl) ? $"https://www.youtube.com/channel/{item.Id}" : $"https://www.youtube.com/user/{item.Snippet.CustomUrl}"); + data.SetText($"https://www.youtube.com/channel/{item.Id}"); Clipboard.SetContent(data); } private async void InBrowser_Click(object sender, RoutedEventArgs e) { - await Launcher.LaunchUriAsync((string.IsNullOrWhiteSpace(item.Snippet.CustomUrl) ? $"https://www.youtube.com/channel/{item.Id}" : $"https://www.youtube.com/user/{item.Snippet.CustomUrl}").ToUri()); + await Launcher.LaunchUriAsync($"https://www.youtube.com/channel/{item.Id}".ToUri()); + } + + private void Cover_ImageOpened(object sender, RoutedEventArgs e) + { + cover.Opacity = 1; } } } diff --git a/FoxTube/Controls/Chat.xaml b/FoxTube/Controls/Chat.xaml index 0968ad0..7082a0e 100644 --- a/FoxTube/Controls/Chat.xaml +++ b/FoxTube/Controls/Chat.xaml @@ -24,53 +24,55 @@ Content="" FontSize="30"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FoxTube/Controls/Chat.xaml.cs b/FoxTube/Controls/Chat.xaml.cs index 4235fd9..1f95fda 100644 --- a/FoxTube/Controls/Chat.xaml.cs +++ b/FoxTube/Controls/Chat.xaml.cs @@ -5,6 +5,10 @@ using Windows.UI.Xaml.Media; using Google.Apis.YouTube.v3.Data; using Google.Apis.YouTube.v3; using Windows.UI; +using Microsoft.AppCenter.Analytics; +using System.Collections.Generic; +using Windows.UI.Popups; +using Windows.ApplicationModel.Resources; namespace FoxTube.Controls { @@ -66,6 +70,10 @@ namespace FoxTube.Controls { string chatId; DateTime lastInsert; + + LiveChatMessagesResource.ListRequest request; + LiveChatMessageListResponse response; + DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) @@ -76,19 +84,20 @@ namespace FoxTube.Controls InitializeComponent(); if (!SecretsVault.IsAuthorized) inputField.Visibility = Visibility.Collapsed; - chatId = id; + + request = SecretsVault.Service.LiveChatMessages.List(chatId, "snippet,authorDetails"); + timer.Tick += Update; timer.Start(); } public async void Update(object sender, object e) { - LiveChatMessagesResource.ListRequest request = SecretsVault.Service.LiveChatMessages.List(chatId, "snippet,authorDetails"); - LiveChatMessageListResponse response = await request.ExecuteAsync(); - foreach (LiveChatMessage i in response.Items) - if(i.Snippet.PublishedAt >= lastInsert) - list.Items.Insert(0, new ChatMessage(i)); + response = await request.ExecuteAsync(); + foreach (LiveChatMessage i in response.Items.FindAll(i => i.Snippet.PublishedAt >= lastInsert)) + list.Items.Insert(0, new ChatMessage(i)); + lastInsert = DateTime.Now; timer.Interval = TimeSpan.FromMilliseconds(response.PollingIntervalMillis.Value); timer.Start(); @@ -99,16 +108,16 @@ namespace FoxTube.Controls Methods.MainPage.GoToChannel(((HyperlinkButton)sender).Tag as string); } - private async void send_Click(object sender, RoutedEventArgs e) + private async void send_Click(object sender, RoutedEventArgs args) { try { - newMessage.IsEnabled = false; - send.IsEnabled = false; - if (string.IsNullOrWhiteSpace(newMessage.Text)) return; + newMessage.IsEnabled = false; + send.IsEnabled = false; + LiveChatMessage message = new LiveChatMessage() { Snippet = new LiveChatMessageSnippet() @@ -131,11 +140,18 @@ namespace FoxTube.Controls list.Items.Add(new ChatMessage(response)); } } - finally + catch(Exception e) { - newMessage.IsEnabled = true; - send.IsEnabled = true; + await new MessageDialog(ResourceLoader.GetForCurrentView("Chat").GetString("/Chat/failed")).ShowAsync(); + Analytics.TrackEvent("Failed to send a chat message", new Dictionary() + { + { "Exception", e.GetType().ToString() }, + { "Message", e.Message } + }); } + + newMessage.IsEnabled = true; + send.IsEnabled = true; } } } diff --git a/FoxTube/Controls/CommentCard.xaml b/FoxTube/Controls/CommentCard.xaml index 93a22c0..13e4747 100644 --- a/FoxTube/Controls/CommentCard.xaml +++ b/FoxTube/Controls/CommentCard.xaml @@ -6,96 +6,84 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="400"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - - - + + + + + + - + - - - - - diff --git a/FoxTube/Controls/PlaylistCard.xaml.cs b/FoxTube/Controls/PlaylistCard.xaml.cs index ddcbc0f..8632a39 100644 --- a/FoxTube/Controls/PlaylistCard.xaml.cs +++ b/FoxTube/Controls/PlaylistCard.xaml.cs @@ -1,6 +1,8 @@ using Google.Apis.YouTube.v3; using Google.Apis.YouTube.v3.Data; +using Microsoft.AppCenter.Analytics; using System; +using System.Collections.Generic; using Windows.ApplicationModel.DataTransfer; using Windows.System; using Windows.UI.Xaml; @@ -17,6 +19,8 @@ namespace FoxTube.Controls Playlist item; public string playlistId; + public bool NeedInitialize { get; set; } = true; + public PlaylistCard(string id) { InitializeComponent(); @@ -25,34 +29,41 @@ namespace FoxTube.Controls public async void Initialize(string id) { - PlaylistsResource.ListRequest request = SecretsVault.Service.Playlists.List("snippet,contentDetails"); - request.Id = id; - PlaylistListResponse response = await request.ExecuteAsync(); - - item = response.Items[0]; - playlistId = id; - - title.Text = item.Snippet.Title; - channelName.Text = item.Snippet.ChannelTitle; - counter.Text = item.ContentDetails.ItemCount.ToString(); - date.Text = Methods.GetAgo(item.Snippet.PublishedAt.Value); - - ChannelsResource.ListRequest r = SecretsVault.Service.Channels.List("snippet"); - r.Id = item.Snippet.ChannelId; - try { - thumbnail.Source = new BitmapImage((item.Snippet.Thumbnails.Maxres ?? item.Snippet.Thumbnails.Medium).Url.ToUri()); - avatar.ProfilePicture = new BitmapImage(new Uri((await r.ExecuteAsync()).Items[0].Snippet.Thumbnails.Medium.Url)); - } catch { } + playlistId = id; + PlaylistsResource.ListRequest request = SecretsVault.Service.Playlists.List("snippet,contentDetails"); + request.Id = playlistId; + item = (await request.ExecuteAsync()).Items[0]; + + title.Text = item.Snippet.Title; + channelName.Text = item.Snippet.ChannelTitle; + counter.Text = item.ContentDetails.ItemCount.ToString(); + date.Text = Methods.GetAgo(item.Snippet.PublishedAt.Value); + + ChannelsResource.ListRequest r = SecretsVault.Service.Channels.List("snippet"); + r.Id = item.Snippet.ChannelId; + + try { thumbnail.Source = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri()); } + catch { } + try { avatar.ProfilePicture = new BitmapImage(new Uri((await r.ExecuteAsync()).Items[0].Snippet.Thumbnails.Medium.Url)) { DecodePixelWidth = 46, DecodePixelHeight = 46 }; } + catch { } + + Opacity = 1; + } + catch (Exception e) + { + Visibility = Visibility.Collapsed; + Analytics.TrackEvent("PlaylistCard loading failed", new Dictionary() + { + { "Exception", e.GetType().ToString() }, + { "Message", e.Message }, + { "Playlist ID", playlistId } + }); + } } - private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e) - { - Height = e.NewSize.Width * 0.75; - } - - private void Button_Click(object sender, RoutedEventArgs e) + public void Button_Click(object sender, RoutedEventArgs e) { Methods.MainPage.GoToPlaylist(item.Id); } @@ -73,5 +84,10 @@ namespace FoxTube.Controls { await Launcher.LaunchUriAsync($"https://www.youtube.com/playlist?list={playlistId}".ToUri()); } + + private void Thumbnail_ImageOpened(object sender, RoutedEventArgs e) + { + thumbnail.Opacity = 1; + } } } diff --git a/FoxTube/Controls/ShowMore.xaml b/FoxTube/Controls/ShowMore.xaml index ea507f5..ba69b19 100644 --- a/FoxTube/Controls/ShowMore.xaml +++ b/FoxTube/Controls/ShowMore.xaml @@ -2,7 +2,6 @@ x:Class="FoxTube.Controls.ShowMore" 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"> diff --git a/FoxTube/Controls/ShowMore.xaml.cs b/FoxTube/Controls/ShowMore.xaml.cs index 2fad8d0..eccffb7 100644 --- a/FoxTube/Controls/ShowMore.xaml.cs +++ b/FoxTube/Controls/ShowMore.xaml.cs @@ -1,19 +1,5 @@ -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; 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 User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 namespace FoxTube.Controls { @@ -22,7 +8,7 @@ namespace FoxTube.Controls public event Event Clicked; public ShowMore() { - this.InitializeComponent(); + InitializeComponent(); } private void btn_Click(object sender, RoutedEventArgs e) @@ -32,11 +18,21 @@ namespace FoxTube.Controls Clicked.Invoke(); } - public void Complete(bool close = false) + public void Invoke() + { + btn_Click(this, null); + } + + public void Show() + { + btn.Visibility = Visibility.Collapsed; + bar.Visibility = Visibility.Visible; + } + + public void Complete() { bar.Visibility = Visibility.Collapsed; - if (!close) - btn.Visibility = Visibility.Visible; + btn.Visibility = Visibility.Visible; } } } diff --git a/FoxTube/Controls/VideoCard.xaml b/FoxTube/Controls/VideoCard.xaml index 804e5cb..de1749c 100644 --- a/FoxTube/Controls/VideoCard.xaml +++ b/FoxTube/Controls/VideoCard.xaml @@ -4,51 +4,62 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)" mc:Ignorable="d" HorizontalAlignment="Stretch" VerticalAlignment="Top" - SizeChanged="UserControl_SizeChanged" d:DesignHeight="290" - d:DesignWidth="384"> + d:DesignWidth="384" + Opacity="0"> - diff --git a/FoxTube/Controls/VideoCard.xaml.cs b/FoxTube/Controls/VideoCard.xaml.cs index f02673d..4ceb0ca 100644 --- a/FoxTube/Controls/VideoCard.xaml.cs +++ b/FoxTube/Controls/VideoCard.xaml.cs @@ -7,6 +7,10 @@ using Windows.UI.Xaml.Media.Imaging; using Windows.System; using Windows.ApplicationModel.DataTransfer; using Windows.ApplicationModel.Resources; +using Microsoft.AppCenter.Analytics; +using System.Collections.Generic; +using YoutubeExplode; +using Windows.UI.Popups; namespace FoxTube.Controls { @@ -20,40 +24,101 @@ namespace FoxTube.Controls public string playlistId; public string videoId; Video item; - + public VideoCard(string id, string playlist = null) { InitializeComponent(); Initialize(id, playlist); } - private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e) + public VideoCard(Video meta, string playlist = null) { - Height = e.NewSize.Width * 0.75; + InitializeComponent(); + item = meta; + playlistId = playlist; + LoadMeta(); } public async void Initialize(string id, string playlist = null) { - VideosResource.ListRequest request = SecretsVault.Service.Videos.List("snippet,contentDetails,statistics,liveStreamingDetails"); - request.Id = id; - VideoListResponse response = await request.ExecuteAsync(); + try + { + videoId = id; + playlistId = playlist; - item = response.Items[0]; - videoId = id; - playlistId = playlist; + VideosResource.ListRequest request = SecretsVault.Service.Videos.List("snippet,contentDetails,statistics,liveStreamingDetails"); + request.Id = id; + item = (await request.ExecuteAsync()).Items[0]; + + title.Text = item.Snippet.Title; + channelName.Text = item.Snippet.ChannelTitle; + if (item.Snippet.LiveBroadcastContent == "live") + { + views.Text = $"{item.LiveStreamingDetails.ConcurrentViewers:0,0} {resources.GetString("/Cards/viewers")}"; + if (item.LiveStreamingDetails.ScheduledStartTime.HasValue && item.LiveStreamingDetails.ScheduledEndTime.HasValue) + info.Text = $"{item.LiveStreamingDetails.ScheduledEndTime - item.LiveStreamingDetails.ActualStartTime} | {Methods.GetAgo(item.LiveStreamingDetails.ActualStartTime.Value)}"; + else + info.Text = Methods.GetAgo(item.LiveStreamingDetails.ActualStartTime.Value); + liveTag.Visibility = Visibility.Visible; + } + else if (item.Snippet.LiveBroadcastContent == "upcoming") + { + views.Text = ""; + if (item.LiveStreamingDetails.ScheduledStartTime.HasValue && item.LiveStreamingDetails.ScheduledEndTime.HasValue) + info.Text = $"{item.LiveStreamingDetails.ScheduledEndTime - item.LiveStreamingDetails.ScheduledStartTime} | {item.LiveStreamingDetails.ScheduledStartTime}"; + else + info.Text = $"{Methods.GetAgo(item.Snippet.PublishedAt.Value)}"; + liveTag.Visibility = Visibility.Visible; + + 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"); + } + else + { + views.Text = $"{item.Statistics.ViewCount:0,0} {resources.GetString("/Cards/views")}"; + info.Text = $"{item.ContentDetails.Duration.GetDuration()} | {Methods.GetAgo(item.Snippet.PublishedAt.Value)}"; + } + + try { thumbnail.Source = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri()); } + catch { } + try { avatar.ProfilePicture = new BitmapImage((await new YoutubeClient().GetChannelAsync(item.Snippet.ChannelId)).LogoUrl.ToUri()) { DecodePixelWidth = 46, DecodePixelHeight = 46 }; } + catch { } + + if(SecretsVault.History.Contains(videoId)) + watched.Visibility = Visibility.Visible; + + Opacity = 1; + } + catch (Exception e) + { + Visibility = Visibility.Collapsed; + Analytics.TrackEvent("VideoCard loading failed", new Dictionary() + { + { "Exception", e.GetType().ToString() }, + { "Message", e.Message }, + { "Video ID", videoId } + }); + } + } + + public async void LoadMeta() + { + videoId = item.Id; title.Text = item.Snippet.Title; channelName.Text = item.Snippet.ChannelTitle; + if (item.Snippet.LiveBroadcastContent == "live") { views.Text = $"{item.LiveStreamingDetails.ConcurrentViewers:0,0} {resources.GetString("/Cards/viewers")}"; if (item.LiveStreamingDetails.ScheduledStartTime.HasValue && item.LiveStreamingDetails.ScheduledEndTime.HasValue) - info.Text = $"{item.LiveStreamingDetails.ScheduledEndTime - item.LiveStreamingDetails.ScheduledStartTime} | {Methods.GetAgo(item.LiveStreamingDetails.ActualStartTime.Value)}"; + info.Text = $"{item.LiveStreamingDetails.ScheduledEndTime - item.LiveStreamingDetails.ActualStartTime} | {Methods.GetAgo(item.LiveStreamingDetails.ActualStartTime.Value)}"; else - info.Text = item.LiveStreamingDetails.ActualStartTime.Value.ToString(); + info.Text = Methods.GetAgo(item.LiveStreamingDetails.ActualStartTime.Value); liveTag.Visibility = Visibility.Visible; } - else if(item.Snippet.LiveBroadcastContent == "upcoming") + else if (item.Snippet.LiveBroadcastContent == "upcoming") { views.Text = ""; if (item.LiveStreamingDetails.ScheduledStartTime.HasValue && item.LiveStreamingDetails.ScheduledEndTime.HasValue) @@ -62,8 +127,8 @@ namespace FoxTube.Controls info.Text = $"{Methods.GetAgo(item.Snippet.PublishedAt.Value)}"; liveTag.Visibility = Visibility.Visible; - if (item.LiveStreamingDetails.ScheduledStartTime.HasValue && (item.LiveStreamingDetails.ScheduledStartTime - DateTime.Now).Value.TotalMilliseconds > 0) - liveContent.Text = $"{resources.GetString("/Cards/goesLive")} {item.LiveStreamingDetails.ScheduledStartTime}"; + if (item.LiveStreamingDetails.ScheduledStartTime.HasValue) + liveContent.Text = $"{resources.GetString("/Cards/goesLive")} {item.LiveStreamingDetails.ScheduledStartTime - DateTime.Now}"; else liveContent.Text = resources.GetString("/Cards/upcoming"); } else @@ -72,26 +137,51 @@ namespace FoxTube.Controls info.Text = $"{item.ContentDetails.Duration.GetDuration()} | {Methods.GetAgo(item.Snippet.PublishedAt.Value)}"; } - var request1 = SecretsVault.Service.Channels.List("snippet"); - request1.Id = item.Snippet.ChannelId; - ChannelListResponse response1 = await request1.ExecuteAsync(); - - try - { - avatar.ProfilePicture = new BitmapImage(new Uri(response1.Items[0].Snippet.Thumbnails.Medium.Url)); - thumbnail.Source = new BitmapImage(new Uri((item.Snippet.Thumbnails.Maxres ?? item.Snippet.Thumbnails.Medium).Url)); - } + try { thumbnail.Source = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri()); } + catch { } + try { avatar.ProfilePicture = new BitmapImage((await new YoutubeClient().GetChannelAsync(item.Snippet.ChannelId)).LogoUrl.ToUri()) { DecodePixelHeight = 50, DecodePixelWidth = 50 }; } catch { } - /*if(SecretsVault.UserHistory.Exists(x => x.Id == videoId)) - { + if (SecretsVault.History.Contains(videoId)) watched.Visibility = Visibility.Visible; - leftOn.Value = SecretsVault.UserHistory.Find(x => x.Id == videoId).LeftOn; - }*/ + + Opacity = 1; } - public void Button_Click(object sender, RoutedEventArgs e) + public async void Button_Click(object sender, RoutedEventArgs e) { + if (item.ContentDetails.ContentRating != null) + { + if (SecretsVault.IsAuthorized) + { + if (SettingsStorage.Mature == MatureState.Blocked) + { + MessageDialog dialog = new MessageDialog(resources.GetString("/VideoPage/matureText"), resources.GetString("/VideoPage/wantContinue")); + dialog.Commands.Add(new UICommand(resources.GetString("/VideoPage/yes"), null, true)); + dialog.Commands.Add(new UICommand(resources.GetString("/VideoPage/always"), (command) => SettingsStorage.Mature = MatureState.Allowed, true)); + dialog.Commands.Add(new UICommand(resources.GetString("/VideoPage/no"), null, false)); + + dialog.CancelCommandIndex = 2; + dialog.DefaultCommandIndex = 0; + + if (!(bool)(await dialog.ShowAsync()).Id) + return; + } + } + else + { + MessageDialog dialog = new MessageDialog(resources.GetString("/VideoPage/matureText"), resources.GetString("/VideoPage/signRequired")); + dialog.Commands.Add(new UICommand(resources.GetString("/VideoPage/signin"), (command) => SecretsVault.Authorize())); + dialog.Commands.Add(new UICommand(resources.GetString("/VideoPage/cancel"))); + + dialog.CancelCommandIndex = 1; + dialog.DefaultCommandIndex = 0; + + await dialog.ShowAsync(); + return; + } + } + Methods.MainPage.GoToVideo(videoId, playlistId); } @@ -111,5 +201,10 @@ namespace FoxTube.Controls { await Launcher.LaunchUriAsync($"https://www.youtube.com/watch?v={videoId}".ToUri()); } + + private void Thumbnail_ImageOpened(object sender, RoutedEventArgs e) + { + thumbnail.Opacity = 1; + } } } diff --git a/FoxTube/Controls/VideoPlayer.xaml b/FoxTube/Controls/VideoPlayer.xaml deleted file mode 100644 index a8bd086..0000000 --- a/FoxTube/Controls/VideoPlayer.xaml +++ /dev/null @@ -1,250 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - + + - + @@ -74,6 +77,6 @@ - + diff --git a/FoxTube/Pages/PlaylistPage.xaml.cs b/FoxTube/Pages/PlaylistPage.xaml.cs index 0bd0d5c..c6f6b84 100644 --- a/FoxTube/Pages/PlaylistPage.xaml.cs +++ b/FoxTube/Pages/PlaylistPage.xaml.cs @@ -18,26 +18,26 @@ namespace FoxTube.Pages /// /// Playlist page /// - public sealed partial class PlaylistPage : Page + public sealed partial class PlaylistPage : Page, NavigationPage { + public object Parameter { get; set; } = null; public string playlistId; Playlist item; - LoadingPage loading; - VideoGrid list; + PlaylistItemsResource.ListRequest request; + string token; public PlaylistPage() { InitializeComponent(); - loading = grid.Children[2] as LoadingPage; - list = ((grid.Children[0] as ScrollViewer).Content as Grid).Children[1] as VideoGrid; - loading.RefreshPage += refresh_Click; + DataTransferManager.GetForCurrentView().DataRequested += new TypedEventHandler(Share); } protected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); + Parameter = e.Parameter; if (e.Parameter == null) loading.Error("NullReferenceException", "Unable to initialize page. Playlist ID is not stated."); else @@ -51,49 +51,39 @@ namespace FoxTube.Pages try { playlistId = id; - if (Methods.NeedToResponse) - Methods.MainPage.Content_Navigated(this, null); - PlaylistsResource.ListRequest request = SecretsVault.Service.Playlists.List("snippet,contentDetails"); - request.Id = id; + PlaylistsResource.ListRequest infoRequest = SecretsVault.Service.Playlists.List("snippet,contentDetails"); + infoRequest.Id = id; - item = (await request.ExecuteAsync()).Items[0]; + item = (await infoRequest.ExecuteAsync()).Items[0]; title.Text = item.Snippet.Title; info.Text = $"{item.ContentDetails.ItemCount} {ResourceLoader.GetForCurrentView("Playlist").GetString("/Playlist/videos")}"; description.Text = item.Snippet.Description; channelName.Text = item.Snippet.ChannelTitle; + + ChannelsResource.ListRequest channelRequest = SecretsVault.Service.Channels.List("snippet"); + channelRequest.Id = item.Snippet.ChannelId; + Channel channel = (await channelRequest.ExecuteAsync()).Items[0]; - try - { - thumbnail.Source = new BitmapImage(new Uri(item.Snippet.Thumbnails.Medium.Url)); - ChannelsResource.ListRequest channelRequest = SecretsVault.Service.Channels.List("snippet"); - channelRequest.Id = item.Snippet.ChannelId; - Channel channel = (await channelRequest.ExecuteAsync()).Items[0]; - avatar.ProfilePicture = new BitmapImage(new Uri(channel.Snippet.Thumbnails.Medium.Url)); - } + try { avatar.ProfilePicture = new BitmapImage(channel.Snippet.Thumbnails.Medium.Url.ToUri()) { DecodePixelWidth = 50, DecodePixelHeight = 50 }; } + catch { } + try { thumbnail.Source = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri()); } catch { } - PlaylistItemsResource.ListRequest listRequest = SecretsVault.Service.PlaylistItems.List("contentDetails"); - listRequest.PlaylistId = id; - listRequest.MaxResults = 50; + request = SecretsVault.Service.PlaylistItems.List("contentDetails"); + request.PlaylistId = id; + request.MaxResults = 25; - PlaylistItemListResponse response = await listRequest.ExecuteAsync(); - - list.Clear(); + PlaylistItemListResponse response = await request.ExecuteAsync(); + token = response.NextPageToken; + if (string.IsNullOrWhiteSpace(token)) + more.Visibility = Visibility.Collapsed; + foreach (PlaylistItem i in response.Items) list.Add(new VideoCard(i.ContentDetails.VideoId, playlistId)); - while (response.NextPageToken != null) - { - listRequest.PageToken = response.NextPageToken; - response = await listRequest.ExecuteAsync(); - - foreach (PlaylistItem i in response.Items) - list.Add(new VideoCard(i.ContentDetails.VideoId, playlistId)); - } - loading.Close(); } catch (System.Net.Http.HttpRequestException) @@ -140,5 +130,22 @@ namespace FoxTube.Pages $"https://www.youtube.com/playlist?list={item.Id}", ResourceLoader.GetForCurrentView("Cards").GetString("/Cards/playlistShare")); } + + private async void ShowMore_Clicked() + { + request.PageToken = token; + PlaylistItemListResponse response = await request.ExecuteAsync(); + + if (string.IsNullOrWhiteSpace(request.PageToken)) + more.Visibility = Visibility.Collapsed; + else + { + token = response.NextPageToken; + more.Complete(); + } + + foreach (PlaylistItem i in response.Items) + list.Add(new VideoCard(i.ContentDetails.VideoId, playlistId)); + } } } diff --git a/FoxTube/Pages/Search.xaml b/FoxTube/Pages/Search.xaml index 9533e50..ff7ff98 100644 --- a/FoxTube/Pages/Search.xaml +++ b/FoxTube/Pages/Search.xaml @@ -67,16 +67,16 @@ public sealed partial class About : Page { - PackageVersion ver = Package.Current.Id.Version; public About() { - this.InitializeComponent(); - version.Text = string.Format("{0}.{1}.{2}", ver.Major, ver.Minor, ver.Build); + InitializeComponent(); + PackageVersion ver = Package.Current.Id.Version; + version.Text = $"{ver.Major}.{ver.Minor}.{ver.Build}"; + + if (StoreServicesFeedbackLauncher.IsSupported()) + feedback.Visibility = Visibility.Visible; } private async void Button_Click(object sender, RoutedEventArgs e) { - await Windows.System.Launcher.LaunchUriAsync(new Uri("feedback-hub:")); + await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync(); } } } diff --git a/FoxTube/Pages/SettingsPages/General.xaml b/FoxTube/Pages/SettingsPages/General.xaml index fa41c91..0b44e9d 100644 --- a/FoxTube/Pages/SettingsPages/General.xaml +++ b/FoxTube/Pages/SettingsPages/General.xaml @@ -9,7 +9,7 @@ - + @@ -26,18 +26,18 @@ - + - + - + diff --git a/FoxTube/Pages/SettingsPages/General.xaml.cs b/FoxTube/Pages/SettingsPages/General.xaml.cs index 1f6b0d4..6b6c0af 100644 --- a/FoxTube/Pages/SettingsPages/General.xaml.cs +++ b/FoxTube/Pages/SettingsPages/General.xaml.cs @@ -7,6 +7,7 @@ using Google.Apis.YouTube.v3; using Google.Apis.YouTube.v3.Data; using YoutubeExplode.Models.MediaStreams; using System; +using Windows.Globalization; namespace FoxTube.Pages.SettingsPages { @@ -50,19 +51,17 @@ namespace FoxTube.Pages.SettingsPages async void InitializeRegions() { I18nRegionsResource.ListRequest regRequest = SecretsVault.Service.I18nRegions.List("snippet"); + regRequest.Hl = SettingsStorage.Language; I18nRegionListResponse regResponse = await regRequest.ExecuteAsync(); - foreach(I18nRegion i in regResponse.Items) + regResponse.Items.ForEach(i => region.Items.Add(new ComboBoxItem { - region.Items.Add(new ComboBoxItem - { - Content = i.Snippet.Name, - Tag = i.Snippet.Gl - }); - if (SettingsStorage.Region == i.Snippet.Gl) - region.SelectedItem = region.Items.Last(); - } + Content = i.Snippet.Name, + Tag = i.Snippet.Gl + })); + region.SelectedItem = region.Items.Find(i => ((ComboBoxItem)i).Tag as string == SettingsStorage.Region) ?? region.Items.Find(i => ((ComboBoxItem)i).Tag as string == SettingsStorage.Language.Remove(0, 3)); I18nLanguagesResource.ListRequest langRequest = SecretsVault.Service.I18nLanguages.List("snippet"); + langRequest.Hl = SettingsStorage.Language; I18nLanguageListResponse langResponse = await langRequest.ExecuteAsync(); foreach(I18nLanguage i in langResponse.Items) { @@ -80,7 +79,7 @@ namespace FoxTube.Pages.SettingsPages { if (SettingsStorage.Language == (language.SelectedItem as ComboBoxItem).Tag.ToString()) return; - + ApplicationLanguages.PrimaryLanguageOverride = (language.SelectedItem as ComboBoxItem).Tag.ToString(); SettingsStorage.Language = (language.SelectedItem as ComboBoxItem).Tag.ToString(); restartNote.Visibility = Visibility.Visible; } @@ -135,8 +134,7 @@ namespace FoxTube.Pages.SettingsPages else if (sender == system && SettingsStorage.Theme != 2) { SettingsStorage.Theme = 2; - Color uiTheme = new Windows.UI.ViewManagement.UISettings().GetColorValue(Windows.UI.ViewManagement.UIColorType.Background); - if (uiTheme == Colors.Black) + if (new Windows.UI.ViewManagement.UISettings().GetColorValue(Windows.UI.ViewManagement.UIColorType.Background) == Colors.Black) Methods.MainPage.RequestedTheme = ElementTheme.Dark; else Methods.MainPage.RequestedTheme = ElementTheme.Light; diff --git a/FoxTube/Pages/SettingsPages/Inbox.xaml b/FoxTube/Pages/SettingsPages/Inbox.xaml index dd9cc27..70713e5 100644 --- a/FoxTube/Pages/SettingsPages/Inbox.xaml +++ b/FoxTube/Pages/SettingsPages/Inbox.xaml @@ -52,43 +52,41 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + @@ -99,8 +97,8 @@ diff --git a/FoxTube/Pages/Subscriptions.xaml.cs b/FoxTube/Pages/Subscriptions.xaml.cs index 0533baa..503f1ba 100644 --- a/FoxTube/Pages/Subscriptions.xaml.cs +++ b/FoxTube/Pages/Subscriptions.xaml.cs @@ -8,8 +8,9 @@ namespace FoxTube.Pages /// /// User's subscriptions page /// - public sealed partial class Subscriptions : Page + public sealed partial class Subscriptions : Page, NavigationPage { + public object Parameter { get; set; } = null; readonly List list = SecretsVault.Subscriptions; public Subscriptions() { diff --git a/FoxTube/Pages/VideoGrid.xaml b/FoxTube/Pages/VideoGrid.xaml index 0234a59..fc7e54e 100644 --- a/FoxTube/Pages/VideoGrid.xaml +++ b/FoxTube/Pages/VideoGrid.xaml @@ -4,18 +4,22 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:ui="using:Microsoft.Toolkit.Uwp.UI.Controls" - xmlns:controls="using:FoxTube.Controls" - x:Name="root" mc:Ignorable="d"> - - - - - - - - + + + + + + + + + + + + + + + diff --git a/FoxTube/Pages/VideoGrid.xaml.cs b/FoxTube/Pages/VideoGrid.xaml.cs index 52b1ee5..d1b6acb 100644 --- a/FoxTube/Pages/VideoGrid.xaml.cs +++ b/FoxTube/Pages/VideoGrid.xaml.cs @@ -1,4 +1,5 @@ -using Windows.UI.Xaml; +using System.Collections.Generic; +using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace FoxTube.Pages @@ -8,6 +9,19 @@ namespace FoxTube.Pages /// public sealed partial class VideoGrid : Page { + public int Columns + { + get { return cols; } + set + { + cols = value; + UpdateGrid(); + } + } + private int cols = 1; + public int Count => Children.Count; + public List Children { get; } = new List(); + public VideoGrid() { InitializeComponent(); @@ -15,7 +29,8 @@ namespace FoxTube.Pages public void Add(UIElement card) { - list.Items.Add(card); + (grid.Children[Count % cols + 1] as StackPanel).Children.Add(card); + Children.Add(card); /*if (list.Items.Count % 10 == 0) list.Items.Add(new CardAdvert());*/ empty.Visibility = Visibility.Collapsed; @@ -23,8 +38,39 @@ namespace FoxTube.Pages public void Clear() { - list.Items.Clear(); + for (int k = 1; k <= 5; k++) + (grid.Children[k] as StackPanel).Children.Clear(); + empty.Visibility = Visibility.Visible; } + + void UpdateGrid() + { + for (int k = 1; k <= 5; k++) + (grid.Children[k] as StackPanel).Children.Clear(); + + for (int k = 0; k < Count; k++) + (grid.Children[k % cols + 1] as StackPanel).Children.Add(Children[k]); + + for (int k = 0; k < cols; k++) + grid.ColumnDefinitions[k].Width = new GridLength(1, GridUnitType.Star); + + for (int k = cols; k < 5; k++) + grid.ColumnDefinitions[k].Width = new GridLength(0); + } + + private void Grid_SizeChanged(object sender, SizeChangedEventArgs e) + { + if (e.NewSize.Width >= 1600 && Columns != 5) + Columns = 5; + else if (e.NewSize.Width >= 1200 && e.NewSize.Width < 1600 && Columns != 4) + Columns = 4; + else if (e.NewSize.Width >= 900 && e.NewSize.Width < 1200 && Columns != 3) + Columns = 3; + else if (e.NewSize.Width >= 550 && e.NewSize.Width < 900 && Columns != 2) + Columns = 2; + else if (e.NewSize.Width < 550 && Columns != 1) + Columns = 1; + } } } diff --git a/FoxTube/Pages/VideoPage.xaml b/FoxTube/Pages/VideoPage.xaml index fc2d669..f595f0d 100644 --- a/FoxTube/Pages/VideoPage.xaml +++ b/FoxTube/Pages/VideoPage.xaml @@ -9,7 +9,6 @@ xmlns:controls1="using:FoxTube.Controls" mc:Ignorable="d"> - @@ -30,12 +29,38 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -55,6 +80,10 @@ + + + + - - - - - @@ -88,7 +112,35 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -136,22 +188,19 @@ - - - - + - + - + - - - + + + @@ -175,6 +224,13 @@ - + + + + + + + + diff --git a/FoxTube/Pages/VideoPage.xaml.cs b/FoxTube/Pages/VideoPage.xaml.cs index dc6559b..f9e5cbb 100644 --- a/FoxTube/Pages/VideoPage.xaml.cs +++ b/FoxTube/Pages/VideoPage.xaml.cs @@ -7,6 +7,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Windows.ApplicationModel.DataTransfer; using Windows.ApplicationModel.Resources; using Windows.Foundation; @@ -50,26 +52,19 @@ namespace FoxTube.Pages public string playlistId = null; public Video item; - bool wide; bool isExtended = false; Rating userRating = Rating.None; - - public VideoPlayer player; - public CommentsPage comments; - public LoadingPage loading; - + DispatcherTimer liveTimer; + DispatcherTimer countdownTimer; + + public LoadingPage LoadingPage => loading; + public VideoPlayer Player => player; public VideoPage() { InitializeComponent(); - loading = grid.Children[3] as LoadingPage; - loading.RefreshPage += refresh_Click; - player = mainContent.Children[0] as VideoPlayer; - player.SetFullSize += Player_SetFullSize; - player.NextClicked += Player_NextClicked; - comments = commentsPlaceholder.Content as CommentsPage; DataTransferManager.GetForCurrentView().DataRequested += new TypedEventHandler(Share); @@ -127,6 +122,9 @@ namespace FoxTube.Pages else LoadStream(); + if (item.Snippet.LiveBroadcastContent == "upcoming") + SetSchedule(); + LoadInfo(); loading.Close(); @@ -147,6 +145,36 @@ namespace FoxTube.Pages } } + void SetSchedule() + { + views.Visibility = Visibility.Collapsed; + upcoming.Visibility = Visibility.Visible; + if (item.LiveStreamingDetails.ScheduledEndTime.HasValue || item.LiveStreamingDetails.ScheduledStartTime.HasValue) + schedule.Visibility = Visibility.Visible; + + if (item.LiveStreamingDetails.ScheduledEndTime.HasValue) + { + end.Text = $"{resources.GetString("/VideoPage/end")} {item.LiveStreamingDetails.ScheduledEndTime.Value}"; + end.Visibility = Visibility.Visible; + } + if (item.LiveStreamingDetails.ScheduledStartTime.HasValue) + { + start.Text = $"{resources.GetString("/VideoPage/start")} {item.LiveStreamingDetails.ScheduledStartTime.Value}"; + start.Visibility = Visibility.Visible; + + countdownPanel.Visibility = Visibility.Visible; + + countdownTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; + countdownTimer.Tick += (s, e) => + { + countdown.Text = item.LiveStreamingDetails.ScheduledStartTime.Value > DateTime.Now ? "" : "-" + (item.LiveStreamingDetails.ScheduledStartTime.Value - DateTime.Now).ToString(@"hh\:mm\:ss"); + if (countdown.Text == "00:00:00") + refresh_Click(this, null); + }; + countdownTimer.Start(); + } + } + async void LoadPlaylist(string id) { playlistId = id; @@ -161,30 +189,19 @@ namespace FoxTube.Pages PlaylistItemsResource.ListRequest listRequest = SecretsVault.Service.PlaylistItems.List("snippet"); listRequest.MaxResults = 50; listRequest.PlaylistId = id; - PlaylistItemListResponse listResponse = await listRequest.ExecuteAsync(); - foreach (PlaylistItem i in listResponse.Items) + PlaylistItemListResponse listResponse; + do { - items.Add(new VideoPlaylistItem(i.Snippet.Thumbnails.Medium.Url, i.Snippet.Title, i.Snippet.ResourceId.VideoId)); - if (items.Last().Id == videoId) - selection = items.Last(); - } - - string token = listResponse.NextPageToken; - while (!string.IsNullOrWhiteSpace(token)) - { - listRequest.PageToken = token; listResponse = await listRequest.ExecuteAsync(); + listRequest.PageToken = listResponse.NextPageToken; - foreach (PlaylistItem i in listResponse.Items) - { + foreach(PlaylistItem i in listResponse.Items) items.Add(new VideoPlaylistItem(i.Snippet.Thumbnails.Medium.Url, i.Snippet.Title, i.Snippet.ResourceId.VideoId)); - if (items.Last().Id == videoId) - selection = items.Last(); - } - - token = listResponse.NextPageToken; } + while (!string.IsNullOrWhiteSpace(listRequest.PageToken)); + + selection = items.Find(i => i.Id == item.Id); for (int k = 0; k < items.Count; k++) items[k].Number = k + 1; @@ -199,14 +216,19 @@ namespace FoxTube.Pages playlistList.SelectedItem = selection; pivot.SelectedItem = playlist; + await Task.Delay(500); + + playlistScroll.ChangeView(null, playlistList.SelectedIndex * 86 + 89, null, true); + if (playlistList.SelectedIndex == playlistList.Items.Count - 1) - player.Next.Visibility = Visibility.Collapsed; + player.Controls.IsNextTrackButtonVisible = false; } async void LoadInfo() { //Setting meta title.Text = item.Snippet.Title; + date.Text = $"{resources.GetString("/VideoPage/publishedAt")}: {item.Snippet.PublishedAt} ({Methods.GetAgo(item.Snippet.PublishedAt.Value)})"; Methods.FormatText(ref description, item.Snippet.Description); //Setting channel button @@ -214,7 +236,7 @@ namespace FoxTube.Pages channelRequest.Id = item.Snippet.ChannelId; var item1 = (await channelRequest.ExecuteAsync()).Items[0]; - channelAvatar.ProfilePicture = new BitmapImage(new Uri(item1.Snippet.Thumbnails.Medium.Url)); + channelAvatar.ProfilePicture = new BitmapImage(item1.Snippet.Thumbnails.Medium.Url.ToUri()) { DecodePixelHeight = 90, DecodePixelWidth = 90 }; channelName.Text = item.Snippet.ChannelTitle; subscribers.Text = $"{item1.Statistics.SubscriberCount:0,0} {resources.GetString("/Cards/subscribers")}"; @@ -223,11 +245,6 @@ namespace FoxTube.Pages likes.Text = $"{item.Statistics.LikeCount:0,0}"; rating.Value = (double)item.Statistics.DislikeCount / (double)(item.Statistics.DislikeCount + item.Statistics.LikeCount) * 100; - //Setting category - VideoCategoriesResource.ListRequest categoryRequest = SecretsVault.Service.VideoCategories.List("snippet"); - categoryRequest.Id = item.Snippet.CategoryId; - category.Text = (await categoryRequest.ExecuteAsync()).Items[0].Snippet.Title; - //Setting User's rate if (SecretsVault.IsAuthorized) { @@ -289,9 +306,8 @@ namespace FoxTube.Pages { VideosResource.ListRequest request = SecretsVault.Service.Videos.List("liveStreamingDetails"); request.Id = videoId; - Video video = (await request.ExecuteAsync()).Items[0]; - views.Text = $"{video.LiveStreamingDetails.ConcurrentViewers} {resources.GetString("/Cards/viewers")}"; + views.Text = $"{(await request.ExecuteAsync()).Items[0].LiveStreamingDetails.ConcurrentViewers} {resources.GetString("/Cards/viewers")}"; } void LoadStats() @@ -326,9 +342,9 @@ namespace FoxTube.Pages audioItem.Click += downloadItemSelected; downloadSelector.Items.Add(audioItem); } - catch (Exception e) + catch { - loading.Error(e.GetType().ToString(), e.Message); + download.Visibility = Visibility.Collapsed; } } @@ -350,32 +366,36 @@ namespace FoxTube.Pages SearchListResponse response = await request.ExecuteAsync(); foreach (SearchResult video in response.Items) - relatedVideos.Children.Add(new VideoCard(video.Id.VideoId)); + relatedVideos.Add(new VideoCard(video.Id.VideoId)); } - private void Player_SetFullSize(object sender, params object[] e) + private void Player_Minimize(object sender, params object[] e) { + if (isExtended == (bool)e[0]) + return; + isExtended = (bool)e[0]; if(isExtended) { grid.ColumnDefinitions[1].Width = new GridLength(0); commandbar.Visibility = Visibility.Collapsed; - - mainScroll.Margin = new Thickness(0); + mainScroll.ChangeView(0, 0, null); - mainScroll.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden; + + mainScroll.Margin = new Thickness(0); mainScroll.VerticalScrollMode = ScrollMode.Disabled; + + Methods.MainPage.MinimizeVideo(); } else { + grid.ColumnDefinitions[1].Width = new GridLength(400); commandbar.Visibility = Visibility.Visible; mainScroll.Margin = new Thickness(0,0,0,50); - mainScroll.VerticalScrollBarVisibility = ScrollBarVisibility.Auto; mainScroll.VerticalScrollMode = ScrollMode.Auto; - - if (wide) - grid.ColumnDefinitions[1].Width = new GridLength(400); + + Methods.MainPage.MaximizeVideo(); } } @@ -387,8 +407,8 @@ namespace FoxTube.Pages private async void openBrowser_Click(object sender, RoutedEventArgs e) { player.Pause(); - string timecode = player.Elapsed.TotalSeconds > 10 ? - "&t=" + (int)player.Elapsed.TotalSeconds + "s" : string.Empty; + string timecode = player.Position.TotalSeconds > 10 ? + "&t=" + (int)player.Position.TotalSeconds + "s" : string.Empty; await Launcher.LaunchUriAsync($"https://www.youtube.com/watch?v={videoId}{timecode}".ToUri()); } @@ -398,15 +418,14 @@ namespace FoxTube.Pages Methods.MainPage.GoToVideo(videoId, playlistId); } + public void CloseVideo() + { + player.Controls_CloseRequested(this, null); + } + private void grid_SizeChanged(object sender, SizeChangedEventArgs e) { - Debug.WriteLine(e.NewSize.Width); - if (e.NewSize.Width > 1000) - wide = true; - else - wide = false; - - if (e.NewSize.Width > 1000 && mainContent.Children.Contains(pivot) && !isExtended) + if (e.NewSize.Width > 1000 && mainContent.Children.Contains(pivot)) { mainContent.Children.Remove(pivot); tabsPlaceholder.Children.Add(pivot); @@ -547,5 +566,21 @@ namespace FoxTube.Pages subscribe.Content = resources.GetString("/Cards/subscribe/Content"); } } + + private async void NewPlaylist_Click(object sender, RoutedEventArgs e) + { + //TODO: Localize strings + await playlistDialog.ShowAsync(); + } + + private void Wl_Click(object sender, RoutedEventArgs e) + { + + } + + private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) + { + + } } } diff --git a/FoxTube/Strings/en-US/Channel.resw b/FoxTube/Strings/en-US/Channel.resw index 8bf1e13..9938142 100644 --- a/FoxTube/Strings/en-US/Channel.resw +++ b/FoxTube/Strings/en-US/Channel.resw @@ -156,4 +156,7 @@ Views: + + Search results + \ No newline at end of file diff --git a/FoxTube/Strings/en-US/Chat.resw b/FoxTube/Strings/en-US/Chat.resw index c617ede..956e3a0 100644 --- a/FoxTube/Strings/en-US/Chat.resw +++ b/FoxTube/Strings/en-US/Chat.resw @@ -120,6 +120,9 @@ Send a message + + Failed to send your message. It may be caused by turned on chat slow mode. Please, try again later. P.S. Report has been sent to developers + Moderator diff --git a/FoxTube/Strings/en-US/CommentsPage.resw b/FoxTube/Strings/en-US/CommentsPage.resw index e7e6243..8c91f2a 100644 --- a/FoxTube/Strings/en-US/CommentsPage.resw +++ b/FoxTube/Strings/en-US/CommentsPage.resw @@ -138,12 +138,15 @@ Cancel - + Delete comment Submit + + Failed to delete your commentary. Please, try again later. + Failed to edit your commentary. Please, try again later. diff --git a/FoxTube/Strings/en-US/Downloads.resw b/FoxTube/Strings/en-US/Downloads.resw index 375962e..930816e 100644 --- a/FoxTube/Strings/en-US/Downloads.resw +++ b/FoxTube/Strings/en-US/Downloads.resw @@ -138,6 +138,12 @@ Extension + + Bug report has been sent + + + Error occured while dowloading a video + Go to original diff --git a/FoxTube/Strings/en-US/Main.resw b/FoxTube/Strings/en-US/Main.resw index a53def0..f82db5d 100644 --- a/FoxTube/Strings/en-US/Main.resw +++ b/FoxTube/Strings/en-US/Main.resw @@ -180,13 +180,13 @@ Sign in with existing account - + Add account Create new Google account - + Log out diff --git a/FoxTube/Strings/en-US/VideoPage.resw b/FoxTube/Strings/en-US/VideoPage.resw index bf046a2..fc4cf51 100644 --- a/FoxTube/Strings/en-US/VideoPage.resw +++ b/FoxTube/Strings/en-US/VideoPage.resw @@ -120,6 +120,9 @@ Add to + + Don't ask me again + Audio track @@ -129,6 +132,9 @@ Go back for 10 seconds + + Cancel + Cast to device @@ -141,12 +147,18 @@ Comments + + Stream starts in: + Description Download video + + Ends at: + Exit compact view mode @@ -156,9 +168,15 @@ Skip forward for 30 seconds + + (Auto-generated) + Go to live broadcast + + This content may be not apropriate for children under 18 + Maximize @@ -174,6 +192,9 @@ Next video + + No + Open in browser @@ -195,15 +216,30 @@ Suggestons + + Stream schedule: + Share + + Sign in + + + Please, sign into your account + + + Starts at: + Elapsed time since stream start Captions + + Language + No captions are available @@ -213,7 +249,19 @@ Captions + + Stream hasn't started yet + Volume + + Do you want to continue + + + Yes + + + Published at + \ No newline at end of file diff --git a/FoxTube/Strings/ru-RU/Channel.resw b/FoxTube/Strings/ru-RU/Channel.resw index 0eabc5a..6474c13 100644 --- a/FoxTube/Strings/ru-RU/Channel.resw +++ b/FoxTube/Strings/ru-RU/Channel.resw @@ -156,4 +156,7 @@ Просмотры: + + Результаты поиска + \ No newline at end of file diff --git a/FoxTube/Strings/ru-RU/Chat.resw b/FoxTube/Strings/ru-RU/Chat.resw index b2d460f..5f558c7 100644 --- a/FoxTube/Strings/ru-RU/Chat.resw +++ b/FoxTube/Strings/ru-RU/Chat.resw @@ -120,6 +120,9 @@ Отправить сообщение + + Не удалось отправить ваше сообщение. Это могло произойти из-за включенного в этом чате замедленного режима. Пожалуйста, попробуйте позже. P.S. Отчет об ошибке был отправлен разработчикам + Модератор diff --git a/FoxTube/Strings/ru-RU/CommentsPage.resw b/FoxTube/Strings/ru-RU/CommentsPage.resw index 51dc3ef..1e4b8f2 100644 --- a/FoxTube/Strings/ru-RU/CommentsPage.resw +++ b/FoxTube/Strings/ru-RU/CommentsPage.resw @@ -138,12 +138,15 @@ Отмена - + Удалить комментарий Отправить + + Не удалось удалить комментарий. Пожалуйста, попробуйте позже + Не удалось изменить комментарий. Пожалуйста, попробуйте позже diff --git a/FoxTube/Strings/ru-RU/Downloads.resw b/FoxTube/Strings/ru-RU/Downloads.resw index 9efd987..c9b0fcb 100644 --- a/FoxTube/Strings/ru-RU/Downloads.resw +++ b/FoxTube/Strings/ru-RU/Downloads.resw @@ -138,6 +138,12 @@ Расширение + + Отчет об ошибке был отправлен + + + При загрузке видео произошла ошибка + Открыть оригинал diff --git a/FoxTube/Strings/ru-RU/Main.resw b/FoxTube/Strings/ru-RU/Main.resw index a7638c5..34aad54 100644 --- a/FoxTube/Strings/ru-RU/Main.resw +++ b/FoxTube/Strings/ru-RU/Main.resw @@ -180,13 +180,13 @@ Войти с помощью существующего аккаунта Google - + Войти в аккаунт Создать новый аккаунт Google - + Выйти diff --git a/FoxTube/Strings/ru-RU/VideoPage.resw b/FoxTube/Strings/ru-RU/VideoPage.resw index b862e69..228d6e3 100644 --- a/FoxTube/Strings/ru-RU/VideoPage.resw +++ b/FoxTube/Strings/ru-RU/VideoPage.resw @@ -120,6 +120,9 @@ Добавить в + + Больше не спрашивать + Аудио дорожка @@ -129,6 +132,9 @@ Назад на 10 секунд + + Отмена + Отправить на устройство @@ -141,12 +147,18 @@ Комментарии + + Стрим начнется через: + Описание Скачать видео + + Конец: + Развернуть @@ -156,9 +168,15 @@ Вперед на 30 секунд + + (Авто-перевод) + Перейти к прямому эфиру + + Этот контент может быть неподходящим для лиц младше 18 лет + Развернуть @@ -174,6 +192,9 @@ Следующее видео + + Нет + Открыть в браузере @@ -195,15 +216,30 @@ Похожее + + Расписание эфира + Поделиться + + Войти + + + Пожалуйста, войдите в аккаунт + + + Начало: + Время, прошедшее с начала стрима Субтитры + + Язык субтитров + Нет доступных субтитров @@ -213,7 +249,19 @@ Субтитры + + Прямой эфир еще не начался + Громкость + + Хотите продолжить? + + + Да + + + Опубликовано + \ No newline at end of file diff --git a/FoxTube/Themes/Generic.xaml b/FoxTube/Themes/Generic.xaml new file mode 100644 index 0000000..7e2613e --- /dev/null +++ b/FoxTube/Themes/Generic.xaml @@ -0,0 +1,636 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Visible + + + + + + + + + + + Visible + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Src/Concepts/YouTubeConcept1 (2).png b/Src/Concepts/YouTubeConcept1 (2).png new file mode 100644 index 0000000..d0e8282 Binary files /dev/null and b/Src/Concepts/YouTubeConcept1 (2).png differ diff --git a/Src/Concepts/YouTubeConcept1.png b/Src/Concepts/YouTubeConcept1.png new file mode 100644 index 0000000..ecb865e Binary files /dev/null and b/Src/Concepts/YouTubeConcept1.png differ diff --git a/Src/Concepts/YouTubeConcept1.psd b/Src/Concepts/YouTubeConcept1.psd new file mode 100644 index 0000000..551b519 Binary files /dev/null and b/Src/Concepts/YouTubeConcept1.psd differ diff --git a/Src/Concepts/YouTubeConcept2.png b/Src/Concepts/YouTubeConcept2.png new file mode 100644 index 0000000..c9962dc Binary files /dev/null and b/Src/Concepts/YouTubeConcept2.png differ diff --git a/Src/Concepts/YouTubeConcept2.psd b/Src/Concepts/YouTubeConcept2.psd new file mode 100644 index 0000000..ddd4977 Binary files /dev/null and b/Src/Concepts/YouTubeConcept2.psd differ diff --git a/Src/Concepts/YouTubeConcept4.png b/Src/Concepts/YouTubeConcept4.png new file mode 100644 index 0000000..c5b3127 Binary files /dev/null and b/Src/Concepts/YouTubeConcept4.png differ diff --git a/Src/Concepts/YouTubeConcept4.psd b/Src/Concepts/YouTubeConcept4.psd new file mode 100644 index 0000000..3769167 Binary files /dev/null and b/Src/Concepts/YouTubeConcept4.psd differ diff --git a/Src/Concepts/YouTubeConcept5 (2).png b/Src/Concepts/YouTubeConcept5 (2).png new file mode 100644 index 0000000..3a06737 Binary files /dev/null and b/Src/Concepts/YouTubeConcept5 (2).png differ diff --git a/Src/Concepts/YouTubeConcept5.png b/Src/Concepts/YouTubeConcept5.png new file mode 100644 index 0000000..d1bd39b Binary files /dev/null and b/Src/Concepts/YouTubeConcept5.png differ diff --git a/Src/Concepts/YouTubeConcept5.psd b/Src/Concepts/YouTubeConcept5.psd new file mode 100644 index 0000000..15bda60 Binary files /dev/null and b/Src/Concepts/YouTubeConcept5.psd differ diff --git a/Src/Concepts/YouTubeConcept6 (2).png b/Src/Concepts/YouTubeConcept6 (2).png new file mode 100644 index 0000000..fad09f8 Binary files /dev/null and b/Src/Concepts/YouTubeConcept6 (2).png differ diff --git a/Src/Concepts/YouTubeConcept6.png b/Src/Concepts/YouTubeConcept6.png new file mode 100644 index 0000000..b686864 Binary files /dev/null and b/Src/Concepts/YouTubeConcept6.png differ diff --git a/Src/Concepts/YouTubeConcept6.psd b/Src/Concepts/YouTubeConcept6.psd new file mode 100644 index 0000000..ffaa4cc Binary files /dev/null and b/Src/Concepts/YouTubeConcept6.psd differ diff --git a/Src/Concepts/YouTubeConceptIcon.png b/Src/Concepts/YouTubeConceptIcon.png new file mode 100644 index 0000000..c90d9fb Binary files /dev/null and b/Src/Concepts/YouTubeConceptIcon.png differ diff --git a/Src/Concepts/YouTubeConceptIcon.psd b/Src/Concepts/YouTubeConceptIcon.psd new file mode 100644 index 0000000..cfec99b Binary files /dev/null and b/Src/Concepts/YouTubeConceptIcon.psd differ diff --git a/Src/Concepts/YouTubeConceptIcon1.png b/Src/Concepts/YouTubeConceptIcon1.png new file mode 100644 index 0000000..1ceb564 Binary files /dev/null and b/Src/Concepts/YouTubeConceptIcon1.png differ diff --git a/Src/Concepts/YouTubeConceptTemp.psd b/Src/Concepts/YouTubeConceptTemp.psd new file mode 100644 index 0000000..ca6b9ce Binary files /dev/null and b/Src/Concepts/YouTubeConceptTemp.psd differ diff --git a/Src/Concepts/YouYubeConcept3 (2).png b/Src/Concepts/YouYubeConcept3 (2).png new file mode 100644 index 0000000..766d345 Binary files /dev/null and b/Src/Concepts/YouYubeConcept3 (2).png differ diff --git a/Src/Concepts/YouYubeConcept3.png b/Src/Concepts/YouYubeConcept3.png new file mode 100644 index 0000000..7d39c46 Binary files /dev/null and b/Src/Concepts/YouYubeConcept3.png differ diff --git a/Src/Concepts/YouYubeConcept3.psd b/Src/Concepts/YouYubeConcept3.psd new file mode 100644 index 0000000..dceab58 Binary files /dev/null and b/Src/Concepts/YouYubeConcept3.psd differ diff --git a/Src/Concepts/YouYubeConcept7 (2).png b/Src/Concepts/YouYubeConcept7 (2).png new file mode 100644 index 0000000..6f64bef Binary files /dev/null and b/Src/Concepts/YouYubeConcept7 (2).png differ diff --git a/Src/Concepts/YouYubeConcept7.png b/Src/Concepts/YouYubeConcept7.png new file mode 100644 index 0000000..4e42488 Binary files /dev/null and b/Src/Concepts/YouYubeConcept7.png differ diff --git a/Src/FoxTube screenshots/Eng/Dark/Channel_dark_eng.png b/Src/FoxTube screenshots/Eng/Dark/Channel_dark_eng.png new file mode 100644 index 0000000..2ec6082 Binary files /dev/null and b/Src/FoxTube screenshots/Eng/Dark/Channel_dark_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Dark/Main_dark_eng.png b/Src/FoxTube screenshots/Eng/Dark/Main_dark_eng.png new file mode 100644 index 0000000..7b5d6b5 Binary files /dev/null and b/Src/FoxTube screenshots/Eng/Dark/Main_dark_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Dark/Playlist_dark_eng.png b/Src/FoxTube screenshots/Eng/Dark/Playlist_dark_eng.png new file mode 100644 index 0000000..61f73ee Binary files /dev/null and b/Src/FoxTube screenshots/Eng/Dark/Playlist_dark_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Dark/Stream_dark_eng.png b/Src/FoxTube screenshots/Eng/Dark/Stream_dark_eng.png new file mode 100644 index 0000000..8264569 Binary files /dev/null and b/Src/FoxTube screenshots/Eng/Dark/Stream_dark_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Dark/Video_dark_eng.png b/Src/FoxTube screenshots/Eng/Dark/Video_dark_eng.png new file mode 100644 index 0000000..ddcc2f0 Binary files /dev/null and b/Src/FoxTube screenshots/Eng/Dark/Video_dark_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Light/Channel_light_eng.png b/Src/FoxTube screenshots/Eng/Light/Channel_light_eng.png new file mode 100644 index 0000000..3f08c2e Binary files /dev/null and b/Src/FoxTube screenshots/Eng/Light/Channel_light_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Light/Home_light_eng.png b/Src/FoxTube screenshots/Eng/Light/Home_light_eng.png new file mode 100644 index 0000000..7fb22ed Binary files /dev/null and b/Src/FoxTube screenshots/Eng/Light/Home_light_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Light/Playlist_light_eng.png b/Src/FoxTube screenshots/Eng/Light/Playlist_light_eng.png new file mode 100644 index 0000000..18090f0 Binary files /dev/null and b/Src/FoxTube screenshots/Eng/Light/Playlist_light_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Light/Stream_light_eng.png b/Src/FoxTube screenshots/Eng/Light/Stream_light_eng.png new file mode 100644 index 0000000..8e29581 Binary files /dev/null and b/Src/FoxTube screenshots/Eng/Light/Stream_light_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Light/Video_light_eng.png b/Src/FoxTube screenshots/Eng/Light/Video_light_eng.png new file mode 100644 index 0000000..c2347df Binary files /dev/null and b/Src/FoxTube screenshots/Eng/Light/Video_light_eng.png differ diff --git a/Src/FoxTube screenshots/Eng/Pip_eng.png b/Src/FoxTube screenshots/Eng/Pip_eng.png new file mode 100644 index 0000000..c0fabd4 Binary files /dev/null and b/Src/FoxTube screenshots/Eng/Pip_eng.png differ diff --git a/Src/FoxTube screenshots/Rus/Dark/Channel_dark_rus.png b/Src/FoxTube screenshots/Rus/Dark/Channel_dark_rus.png new file mode 100644 index 0000000..0fb15b4 Binary files /dev/null and b/Src/FoxTube screenshots/Rus/Dark/Channel_dark_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Dark/Home_dark_rus.png b/Src/FoxTube screenshots/Rus/Dark/Home_dark_rus.png new file mode 100644 index 0000000..2c13e2c Binary files /dev/null and b/Src/FoxTube screenshots/Rus/Dark/Home_dark_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Dark/Playlist_dark_rus.png b/Src/FoxTube screenshots/Rus/Dark/Playlist_dark_rus.png new file mode 100644 index 0000000..1f2d332 Binary files /dev/null and b/Src/FoxTube screenshots/Rus/Dark/Playlist_dark_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Dark/Stream_dark_rus.png b/Src/FoxTube screenshots/Rus/Dark/Stream_dark_rus.png new file mode 100644 index 0000000..346b370 Binary files /dev/null and b/Src/FoxTube screenshots/Rus/Dark/Stream_dark_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Dark/Video_dark_rus.png b/Src/FoxTube screenshots/Rus/Dark/Video_dark_rus.png new file mode 100644 index 0000000..eab7011 Binary files /dev/null and b/Src/FoxTube screenshots/Rus/Dark/Video_dark_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Light/Channel_light_rus.png b/Src/FoxTube screenshots/Rus/Light/Channel_light_rus.png new file mode 100644 index 0000000..6293762 Binary files /dev/null and b/Src/FoxTube screenshots/Rus/Light/Channel_light_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Light/Home_light_rus.png b/Src/FoxTube screenshots/Rus/Light/Home_light_rus.png new file mode 100644 index 0000000..eb97fb5 Binary files /dev/null and b/Src/FoxTube screenshots/Rus/Light/Home_light_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Light/Playlist_light_rus.png b/Src/FoxTube screenshots/Rus/Light/Playlist_light_rus.png new file mode 100644 index 0000000..96d3de3 Binary files /dev/null and b/Src/FoxTube screenshots/Rus/Light/Playlist_light_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Light/Stream_light_rus.png b/Src/FoxTube screenshots/Rus/Light/Stream_light_rus.png new file mode 100644 index 0000000..6f63caf Binary files /dev/null and b/Src/FoxTube screenshots/Rus/Light/Stream_light_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Light/Video_light_rus.png b/Src/FoxTube screenshots/Rus/Light/Video_light_rus.png new file mode 100644 index 0000000..f0ce71e Binary files /dev/null and b/Src/FoxTube screenshots/Rus/Light/Video_light_rus.png differ diff --git a/Src/FoxTube screenshots/Rus/Pip_rus.png b/Src/FoxTube screenshots/Rus/Pip_rus.png new file mode 100644 index 0000000..efd2c4a Binary files /dev/null and b/Src/FoxTube screenshots/Rus/Pip_rus.png differ diff --git a/Src/PosterLogo.png b/Src/PosterLogo.png new file mode 100644 index 0000000..75042ae Binary files /dev/null and b/Src/PosterLogo.png differ diff --git a/Src/PosterLogo.psd b/Src/PosterLogo.psd new file mode 100644 index 0000000..edb2ddb Binary files /dev/null and b/Src/PosterLogo.psd differ diff --git a/Src/SquareLogo.png b/Src/SquareLogo.png new file mode 100644 index 0000000..14b66f2 Binary files /dev/null and b/Src/SquareLogo.png differ diff --git a/Src/SquareLogo300.png b/Src/SquareLogo300.png new file mode 100644 index 0000000..6aaa210 Binary files /dev/null and b/Src/SquareLogo300.png differ