Archived
1
0

Merged PR 23: [0.4] Release candidate

- Submission #1 (ver. 0.3) fixes
- History and watch later pages
- Optimizing, debugging, decorating
- Optimizing and refactoring pt.2
- Optimization, refactoring, debugging

Related Work Items: #251, #252, #261
- Bug fixes, improvements, #283, #282: Fixed, #194: Done
- System transport controls next button fixed
- Well, I'm depressing so no message. Just accept your faith
- Background fix
- Region settings fixed
Channel page cover fixed
Livestream toasts added
- Patchnote update
- Livestreams support on new player
This commit is contained in:
Michael Gordeev
2019-04-05 18:15:46 +00:00
140 changed files with 4221 additions and 3347 deletions
+33 -31
View File
@@ -1,24 +1,26 @@
using Google.Apis.Services; using Google.Apis.Services;
using Google.Apis.YouTube.v3; using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data; using Google.Apis.YouTube.v3.Data;
using Microsoft.AppCenter.Analytics;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
using Windows.ApplicationModel.Background; using Windows.ApplicationModel.Background;
using Windows.Storage; using Windows.Storage;
using Windows.UI.Notifications; using Windows.UI.Notifications;
using YoutubeExplode.Models;
namespace FoxTube.Background namespace FoxTube.Background
{ {
public sealed class BackgroundProcessor : IBackgroundTask public sealed class BackgroundProcessor : IBackgroundTask
{ {
private DateTime lastCheck = DateTime.Now; private DateTime lastCheck;
private readonly ApplicationDataContainer settings = ApplicationData.Current.LocalSettings; private readonly ApplicationDataContainer settings = ApplicationData.Current.RoamingSettings;
private YouTubeService Service => new YouTubeService(new BaseClientService.Initializer() dynamic prefs;
private readonly YouTubeService Service = new YouTubeService(new BaseClientService.Initializer()
{ {
ApiKey = "AIzaSyBgHrCnrlzlVmk0cJKL8RqP9Y8x6XSuk_0", ApiKey = "AIzaSyBgHrCnrlzlVmk0cJKL8RqP9Y8x6XSuk_0",
ApplicationName = "FoxTube" ApplicationName = "FoxTube"
@@ -30,21 +32,20 @@ namespace FoxTube.Background
try try
{ {
def = taskInstance.GetDeferral(); def = taskInstance.GetDeferral();
taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);
if (settings.Values["lastCheck"] == null) if (settings.Values["lastCheck"] == null)
{ {
settings.Values.Add("lastCheck", DateTime.Now.ToString()); settings.Values["lastCheck"] = DateTime.Now.ToString();
def.Complete(); def.Complete();
return; return;
} }
else else
lastCheck = DateTime.Parse(settings.Values["lastCheck"] as string); lastCheck = DateTime.Parse(settings.Values["lastCheck"] as string);
bool[] notificationsSettings = JsonConvert.DeserializeObject<bool[]>(await FileIO.ReadTextAsync(await ApplicationData.Current.RoamingFolder.GetFileAsync("notifications.json"))); prefs = JsonConvert.DeserializeObject<dynamic>(settings.Values["settings"] as string);
if (notificationsSettings[0]) if ((bool)prefs.devNotifications)
CheckAnnouncements(); CheckAnnouncements();
if (notificationsSettings[1]) if ((bool)prefs.videoNotifications)
await CheckAccount(); await CheckAccount();
} }
finally finally
@@ -54,20 +55,11 @@ namespace FoxTube.Background
} }
} }
private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
Analytics.TrackEvent("Background task caneled", new Dictionary<string, string>()
{
{ "Reason", reason.ToString() }
});
settings.Values["lastCheck"] = DateTime.Now.ToString();
}
async Task CheckAccount() async Task CheckAccount()
{ {
try try
{ {
Dictionary<string, string> subscriptions = JsonConvert.DeserializeObject<Dictionary<string, string>>(await FileIO.ReadTextAsync(await ApplicationData.Current.RoamingFolder.GetFileAsync("background.json"))); Dictionary<string, string> subscriptions = JsonConvert.DeserializeObject<Dictionary<string, string>>(settings.Values["subscriptions"] as string);
List<SearchResult> results = new List<SearchResult>(); List<SearchResult> results = new List<SearchResult>();
@@ -84,8 +76,15 @@ namespace FoxTube.Background
{ {
results.Add(i); results.Add(i);
ToastNotificationManager.CreateToastNotifier().Show( if(i.Snippet.LiveBroadcastContent == "live")
Notification.GetVideoToast(i.Id.VideoId, i.Snippet.ChannelId, i.Snippet.Title, i.Snippet.ChannelTitle, i.Snippet.Thumbnails.Medium.Url, s.Value)); 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(); TileUpdater updater = TileUpdateManager.CreateTileUpdaterForApplication();
updater.EnableNotificationQueue(true); updater.EnableNotificationQueue(true);
updater.Clear(); updater.Clear();
for (int i = 0; i < 5; i++) for (int i = 0; i < 5 && i < results.Count; i++)
updater.Update(Tiles.GetTileLayout(results[i].Snippet.Title, results[i].Snippet.ChannelTitle, results[i].Snippet.Thumbnails.Medium.Url, subscriptions[results[i].Snippet.ChannelId])); 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 { } catch { }
} }
void CheckAnnouncements() async void CheckAnnouncements()
{ {
try try
{ {
XmlDocument doc = new XmlDocument(); XmlDocument doc = new XmlDocument();
doc.Load(XmlReader.Create("http://foxgame-studio.000webhostapp.com/foxtube-messages.xml")); doc.LoadXml(await new HttpClient().GetStringAsync("http://foxgame-studio.000webhostapp.com/foxtube-messages.xml"));
if ((DateTime.Parse((doc["posts"].FirstChild as XmlElement).GetAttribute("time")) - lastCheck).TotalSeconds > 0) XmlElement item = doc["posts"].FirstChild as XmlElement;
DateTime date = DateTime.Parse(item.GetAttribute("time"));
if (date > lastCheck && date < DateTime.Now)
ToastNotificationManager.CreateToastNotifier().Show( ToastNotificationManager.CreateToastNotifier().Show(
Notification.GetInternalToast(doc["posts"].FirstChild["id"].InnerText, Notification.GetInternalToast(item["id"].InnerText,
doc["posts"].FirstChild["header"].InnerText, item["header"][(string)prefs.language].InnerText,
doc["posts"].FirstChild["content"].InnerText, item["content"][(string)prefs.language].InnerText,
doc["posts"].FirstChild["thumbnail"].InnerText, item["thumbnail"].InnerText,
doc["posts"].FirstChild["avatar"].InnerText)); item["avatar"].InnerText));
} }
catch { } catch { }
} }
+8 -2
View File
@@ -128,10 +128,16 @@
<Version>1.29.2.1006</Version> <Version>1.29.2.1006</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.AppCenter.Analytics"> <PackageReference Include="Microsoft.AppCenter.Analytics">
<Version>1.13.0</Version> <Version>1.13.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.1.5</Version> <Version>6.2.8</Version>
</PackageReference>
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications">
<Version>5.1.1</Version>
</PackageReference>
<PackageReference Include="YoutubeExplode">
<Version>4.6.7</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup />
+62 -9
View File
@@ -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 System.Collections.Generic;
using Windows.Data.Xml.Dom; using Windows.Data.Xml.Dom;
using Windows.Storage; using Windows.Storage;
@@ -8,18 +11,22 @@ namespace FoxTube.Background
{ {
public static class Notification public static class Notification
{ {
private static Dictionary<string, string> languagePack = LoadPack(); private static readonly Dictionary<string, string> languagePack = LoadPack();
private static Dictionary<string, string> LoadPack() private static Dictionary<string, string> LoadPack()
{ {
object[] saved = JsonConvert.DeserializeObject<object[]>(ApplicationData.Current.RoamingSettings.Values["settings"] as string); dynamic saved = JsonConvert.DeserializeObject<dynamic>(ApplicationData.Current.RoamingSettings.Values["settings"] as string);
if (saved[7] as string == "ru-RU") if (saved.language as string == "ru-RU")
return new Dictionary<string, string>() return new Dictionary<string, string>()
{ {
{ "addLater", "Посмотреть позже" }, { "addLater", "Посмотреть позже" },
{ "changelog", "Список изменений" }, { "changelog", "Список изменений" },
{ "changelogHeader", "Что нового в версии" }, { "changelogHeader", "Что нового в версии" },
{ "videoContent", "загрузил новое видео" }, { "videoContent", "загрузил новое видео" },
{ "live", "ПРЯМОЙ ЭФИР" },
{ "upcoming", "Запланирован" },
{ "liveContent", "начал прямой эфир" },
{ "upcomingContent", "запланировал прямой эфир" },
{ "goChannel", "Открыть канал" } { "goChannel", "Открыть канал" }
}; };
else else
@@ -29,6 +36,10 @@ namespace FoxTube.Background
{ "changelog", "Changelog" }, { "changelog", "Changelog" },
{ "changelogHeader", "What's new in version" }, { "changelogHeader", "What's new in version" },
{ "videoContent", "uploaded a new video" }, { "videoContent", "uploaded a new video" },
{ "live", "LIVE" },
{ "upcoming", "Upcoming" },
{ "liveContent", "started live broadcast" },
{ "upcomingContent", "planned live broadcast" },
{ "goChannel", "Go to channel" } { "goChannel", "Go to channel" }
}; };
} }
@@ -51,17 +62,17 @@ namespace FoxTube.Background
return new ToastNotification(template); 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(); 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($@"<toast activationType='foreground' launch='video|{id}'> template.LoadXml($@"<toast activationType='foreground' launch='video|{id}' displayTimestamp='{ts}'>
<visual> <visual>
<binding template='ToastGeneric'> <binding template='ToastGeneric'>
<image placement='hero' src='{thumbnail.Replace("&", "%26")}'/> <image placement='hero' src='{thumbnail.Replace("&", "%26")}'/>
<image placement='appLogoOverride' hint-crop='circle' src='{avatar.Replace("&", "%26") ?? "http://foxgame-studio.000webhostapp.com/FoxTubeAssets/LogoAvatar.png"}'/> <image placement='appLogoOverride' hint-crop='circle' src='{avatar.Replace("&", "%26") ?? "http://foxgame-studio.000webhostapp.com/FoxTubeAssets/LogoAvatar.png"}'/>
<text>{title}</text> <text>{System.Security.SecurityElement.Escape(title)}</text>
<text>{channel} {languagePack["videoContent"]}</text> <text>{System.Security.SecurityElement.Escape(channel)} {languagePack["videoContent"]}</text>
</binding> </binding>
</visual> </visual>
@@ -73,6 +84,48 @@ namespace FoxTube.Background
return new ToastNotification(template); 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($@"<toast activationType='foreground' launch='video|{id}' displayTimestamp='{ts}'>
<visual>
<binding template='ToastGeneric'>
<image placement='hero' src='{thumbnail.Replace("&", "%26")}'/>
<image placement='appLogoOverride' hint-crop='circle' src='{avatar.Replace("&", "%26") ?? "http://foxgame-studio.000webhostapp.com/FoxTubeAssets/LogoAvatar.png"}'/>
<text>🔴 [{languagePack["live"]}] {System.Security.SecurityElement.Escape(title)}</text>
<text>{System.Security.SecurityElement.Escape(channel)} {languagePack["liveContent"]}</text>
</binding>
</visual>
<actions>
<action content='{languagePack["goChannel"]}' activationType='foreground' arguments='channel|{channelId}'/>
</actions>
</toast>");
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($@"<toast activationType='foreground' launch='video|{id}' displayTimestamp='{ts}'>
<visual>
<binding template='ToastGeneric'>
<image placement='hero' src='{thumbnail.Replace("&", "%26")}'/>
<image placement='appLogoOverride' hint-crop='circle' src='{avatar.Replace("&", "%26") ?? "http://foxgame-studio.000webhostapp.com/FoxTubeAssets/LogoAvatar.png"}'/>
<text>🔴 [{languagePack["upcoming"]}] {System.Security.SecurityElement.Escape(title)}</text>
<text>{System.Security.SecurityElement.Escape(channel)} {languagePack["upcomingContent"]}</text>
</binding>
</visual>
<actions>
<action content='{languagePack["goChannel"]}' activationType='foreground' arguments='channel|{channelId}'/>
</actions>
</toast>");
return new ToastNotification(template);
}
public static ToastNotification GetInternalToast(string id, string header, string content, string thumbnail, string avatar) public static ToastNotification GetInternalToast(string id, string header, string content, string thumbnail, string avatar)
{ {
+8
View File
@@ -5,5 +5,13 @@
xmlns:local="using:FoxTube"> xmlns:local="using:FoxTube">
<Application.Resources> <Application.Resources>
<Color x:Key="SystemAccentColor">Red</Color> <Color x:Key="SystemAccentColor">Red</Color>
<Style TargetType="Button" BasedOn="{StaticResource ButtonRevealStyle}"/>
<Style TargetType="ListViewItem" BasedOn="{StaticResource ListViewItemRevealStyle}"/>
<Style TargetType="TextBlock">
<Setter Property="SelectionHighlightColor" Value="Red"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="SelectionHighlightColor" Value="Red"/>
</Style>
</Application.Resources> </Application.Resources>
</Application> </Application>
+71 -50
View File
@@ -4,13 +4,15 @@ using Microsoft.AppCenter.Analytics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Xml;
using Windows.ApplicationModel; using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation; using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel.Background; using Windows.ApplicationModel.Background;
using Windows.ApplicationModel.Core;
using Windows.Globalization; using Windows.Globalization;
using Windows.Storage; using Windows.Storage;
using Windows.System;
using Windows.System.Power; using Windows.System.Power;
using Windows.UI.Notifications; using Windows.UI.Notifications;
using Windows.UI.Xaml; using Windows.UI.Xaml;
@@ -19,15 +21,9 @@ using Windows.UI.Xaml.Navigation;
namespace FoxTube namespace FoxTube
{ {
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
sealed partial class App : Application sealed partial class App : Application
{ {
/// <summary> Stopwatch sw = new Stopwatch();
/// 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().
/// </summary>
public App() public App()
{ {
SettingsStorage.LoadData(); SettingsStorage.LoadData();
@@ -41,20 +37,46 @@ namespace FoxTube
RequestedTheme = ApplicationTheme.Dark; RequestedTheme = ApplicationTheme.Dark;
break; break;
} }
ApplicationLanguages.PrimaryLanguageOverride = SettingsStorage.Language; ApplicationLanguages.PrimaryLanguageOverride = SettingsStorage.Language;
CheckVersion();
InitializeComponent(); InitializeComponent();
Suspending += OnSuspending; Suspending += OnSuspending;
UnhandledException += UnhandledError; UnhandledException += UnhandledError;
AppCenter.Start("45774462-9ea7-438a-96fc-03982666f39e", typeof(Analytics)); AppCenter.Start("45774462-9ea7-438a-96fc-03982666f39e", typeof(Analytics));
AppCenter.SetCountryCode(SettingsStorage.Region); AppCenter.SetCountryCode(SettingsStorage.Region);
sw.Start();
} }
/// <summary> /// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points /// Comparing current version with last recorded version. If doesn't match, poping up changelog notification
/// will be used such as when the application is launched to open a specific file.
/// </summary> /// </summary>
/// <param name="e">Details about the launch request and process.</param> 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) protected override void OnLaunched(LaunchActivatedEventArgs e)
{ {
// Do not repeat app initialization when the Window already has content, // Do not repeat app initialization when the Window already has content,
@@ -97,7 +119,6 @@ namespace FoxTube
return; return;
var backgroundRequest = await BackgroundExecutionManager.RequestAccessAsync(); var backgroundRequest = await BackgroundExecutionManager.RequestAccessAsync();
var saverRequest = PowerManager.EnergySaverStatus;
if (backgroundRequest == BackgroundAccessStatus.DeniedBySystemPolicy || backgroundRequest == BackgroundAccessStatus.DeniedByUser) if (backgroundRequest == BackgroundAccessStatus.DeniedBySystemPolicy || backgroundRequest == BackgroundAccessStatus.DeniedByUser)
return; return;
@@ -171,9 +192,6 @@ namespace FoxTube
Debug.WriteLine(e.Message); Debug.WriteLine(e.Message);
} }
break; break;
case "download":
await Launcher.LaunchFileAsync(await StorageFile.GetFileFromPathAsync(arguments[1]));
break;
} }
} }
@@ -199,57 +217,60 @@ namespace FoxTube
Window.Current.Activate(); Window.Current.Activate();
if (e is ToastNotificationActivatedEventArgs) switch (e.Kind)
{ {
string[] args = (e as ToastNotificationActivatedEventArgs).Argument.Split('|'); case ActivationKind.Protocol:
switch (args[0]) break;
{ case ActivationKind.ToastNotification:
case "changelog": string[] args = (e as ToastNotificationActivatedEventArgs).Argument.Split('|');
case "inbox": switch (args[0])
Methods.MainPage.GoToDeveloper(args[1]); {
break; case "changelog":
case "inbox":
Methods.MainPage.GoToDeveloper(args[1]);
break;
case "video": case "video":
Methods.MainPage.GoToVideo(args[1]); Methods.MainPage.GoToVideo(args[1]);
break; break;
case "channel": case "channel":
Methods.MainPage.GoToChannel(args[1]); Methods.MainPage.GoToChannel(args[1]);
break; break;
case "download": case "download":
Methods.MainPage.GoToDownloads(); Methods.MainPage.GoToDownloads();
break; break;
case "dcancel": case "dcancel":
DownloadAgent.Remove(args[1]); DownloadAgent.Cancel(args[1]);
break; break;
} }
break;
} }
} }
/// <summary> void Launch(string e = null)
/// Invoked when Navigation to a certain page fails {
/// </summary>
/// <param name="sender">The Frame which failed navigation</param> }
/// <param name="e">Details about the navigation failure</param>
void OnNavigationFailed(object sender, NavigationFailedEventArgs e) void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{ {
throw new Exception("Failed to load Page " + e.SourcePageType.FullName); throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
} }
/// <summary>
/// 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.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private void OnSuspending(object sender, SuspendingEventArgs e) private void OnSuspending(object sender, SuspendingEventArgs e)
{ {
var deferral = e.SuspendingOperation.GetDeferral(); var deferral = e.SuspendingOperation.GetDeferral();
SettingsStorage.ExportSettings();
sw.Stop();
SettingsStorage.Uptime += sw.Elapsed;
SettingsStorage.SaveData();
DownloadAgent.QuitPrompt(); DownloadAgent.QuitPrompt();
deferral.Complete(); deferral.Complete();
Analytics.TrackEvent("Session terminated");
} }
private void UnhandledError(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e) private void UnhandledError(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
{ {
Analytics.TrackEvent("The app crashed", new Dictionary<string, string>() Analytics.TrackEvent("The app crashed", new Dictionary<string, string>()
Binary file not shown.

Before

Width:  |  Height:  |  Size: 562 B

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 667 B

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 B

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 804 B

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 843 B

+60
View File
@@ -1,5 +1,65 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<items> <items>
<item time="2019-04-05" version="0.4">
<content>
<en-US>### 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
</en-US>
<ru-RU>### Что нового:
- Улучшена стабильность и скорость приложения
- Исправлена куча багов
- Переработан плеер
- Добавлены анимации и акрил
- Добавлена информация об аккаунте
- Добавлена история
- Добавлен плейлист "Посмотреть позже"
- Добавлены вкладки "Рекоммендованные" и "Подписки" на домашней странице
- Добавлена маленькая иконка канала при прокрутке вниз на странице канала
- Добавлена возможность удалять комментарии
- Переработано скачивание видео
- Добавлен прозрачный заголовок окна
- Добавлены всплывающие уведомления с просьбой оценить приложение и оставить отзыв (появляются после 12 и 24 часов активного использования)
- Переработана сетка карточек
- Добавлена информация о дате публикации видео на странице просмотра
- Обратный отсчет для стримов переработан и перенесен вверх страницы
- Список видео текущего плейлиста сразу перематывается на текущее
- Текст выделяется красным, а не текущим цветом системы
- Добавлены уведомления для прямых эфиров
### Известные проблемы:
- История и плейлист 'Посмотреть позже' иногда могут не отображаться. Решение: перезапустить приложение
- Аналогично и со вкладками 'Рекомендованные' и 'Подписки' на домашней странице
- В некоторых местах отсутствует русская локализация. Будет дополнена в версии 1.0
- Управление плейлистами будет добавлено в следующей версии, но некоторые плейлисты могут отсутствовать
- Хотя я сделал систему доставки рекламы, она не введена из-за некоторых проблем с видом банеров. Я хочу сделать ваш опыт взаимодействия с рекламой в приложении лучше, так что она не будет введена до тех пор, пока я не буду в этом уверен
</ru-RU>
</content>
</item>
<item time="2019-02-02" version="0.3"> <item time="2019-02-02" version="0.3">
<content> <content>
<en-US>### What's new: <en-US>### What's new:
+55
View File
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8" ?>
<items>
<item time="2019-02-02" version="0.3">
<content>
<en-US>### What's new:
- Small fixes
- First public pre-release version
- Some content was cut out due to its incompleteness
</en-US>
<ru-RU>### Что нового:
- Мелкие исправления багов
- Эта версия является первой пред-релизной публичной версией
- Некотроые функции были вырезаны из-за их незавершенности
</ru-RU>
</content>
</item>
<item time="2019-01-05" version="0.2.19012">
<content>
<en-US>### 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
</en-US>
<ru-RU>### Что нового:
- Кнопка перехода к прямому эфиру на стримах теперь работает
- Исправлен баг с длинными именами каналов на карточках
- Исправлено исчезание описания видео при изменении размеров окна
- Исправлен ползунок перемотки видео
- Добавлен индикатор буферизации видео
- Мелкие исправления
### Что по-прежнему не работает:
- Страница рекомендованных видео и страница видео с подписок
- История
- Работа с плейлистами
- Нет карточек рекламы
</ru-RU>
</content>
</item>
</items>
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

+14 -24
View File
@@ -1,11 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Windows.Storage; using Windows.Storage;
using FoxTube.Classes;
using Newtonsoft.Json; using Newtonsoft.Json;
using YoutubeExplode.Models.MediaStreams; using YoutubeExplode.Models.MediaStreams;
using Google.Apis.YouTube.v3.Data; using Google.Apis.YouTube.v3.Data;
using FoxTube.Controls; using FoxTube.Controls;
using FoxTube.Pages;
namespace FoxTube namespace FoxTube
{ {
@@ -13,6 +13,7 @@ namespace FoxTube
{ {
public static List<DownloadItem> items = new List<DownloadItem>(); public static List<DownloadItem> items = new List<DownloadItem>();
private static ApplicationDataContainer settings = ApplicationData.Current.LocalSettings; private static ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
public static Downloads Page { get; set; }
public static StorageFolder Downloads { get; set; } public static StorageFolder Downloads { get; set; }
public static async void Initialize() public static async void Initialize()
@@ -31,45 +32,34 @@ namespace FoxTube
items.Insert(0, new DownloadItem(info, meta, qualty)); 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); try { Page.Remove(item); }
if (item == null || !item.InProgress) catch { }
return; items.Remove(item);
item.CancelPrompt();
} }
public static void Remove(string id) public static void Cancel(string id)
{ {
DownloadItem item = items.Find(x => x.Container.Id == id); DownloadItem item = items.Find(i => i.Container.Id == id);
if (item == null) if (item != null)
return;
if (item.InProgress)
item.Cancel(); item.Cancel();
else
items.Remove(item);
} }
public static void QuitPrompt() 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(); i.Cancel();
items.Remove(i);
}
List<DownloadItemContainer> containers = new List<DownloadItemContainer>(); List<DownloadItemContainer> containers = new List<DownloadItemContainer>();
items.ForEach(i => containers.Add(i.Container)); items.ForEach(i => containers.Add(i.Container));
string data = JsonConvert.SerializeObject(containers); string data = JsonConvert.SerializeObject(containers);
try settings.Values["downloads"] = data;
{
settings.Values["downloads"] = data;
}
catch
{
settings.Values.Add("downloads", data);
}
} }
} }
} }
-16
View File
@@ -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; }
}
}
+10
View File
@@ -9,6 +9,16 @@ namespace FoxTube.Classes
{ {
public InboxItemType Type { get; set; } = InboxItemType.Default; public InboxItemType Type { get; set; } = InboxItemType.Default;
public DateTime TimeStamp { get; set; } 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 Subject { get; set; }
public string Content { get; set; } public string Content { get; set; }
+87 -87
View File
@@ -1,16 +1,19 @@
using FoxTube.Pages; using FoxTube.Pages;
using Google.Apis.YouTube.v3; using Google.Apis.YouTube.v3;
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web; using System.Web;
using System.Xml; using System.Xml;
using Windows.ApplicationModel.Core; using Windows.ApplicationModel.Core;
using Windows.ApplicationModel.DataTransfer; using Windows.ApplicationModel.DataTransfer;
using Windows.ApplicationModel.Resources; using Windows.ApplicationModel.Resources;
using Windows.ApplicationModel.Resources.Core;
using Windows.Storage; using Windows.Storage;
using Windows.Storage.Streams; using Windows.Storage.Streams;
using Windows.System; using Windows.System;
@@ -19,16 +22,20 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Documents; using Windows.UI.Xaml.Documents;
using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media;
using YoutubeExplode;
using YoutubeExplode.Models.MediaStreams; using YoutubeExplode.Models.MediaStreams;
namespace FoxTube namespace FoxTube
{ {
public interface NavigationPage
{
object Parameter { get; set; }
}
public static class Methods public static class Methods
{ {
private static ResourceLoader resources = ResourceLoader.GetForCurrentView("Methods"); private static ResourceLoader resources = ResourceLoader.GetForCurrentView("Methods");
public static CommentsPage CommentsPage { get; set; } public static CommentsPage CommentsPage { get; set; }
public static bool NeedToResponse { get; set; } = false;
public static MainPage MainPage public static MainPage MainPage
{ {
get { return (Window.Current.Content as Frame).Content as 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) public static void FormatText(ref TextBlock block, string text)
{ {
block.Inlines.Clear(); 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); foreach (string item in filter.Split(text))
Regex isWWW = new Regex(@"(http[s]?://[\S]+|www.[\S]+)");
Regex isEmail = new Regex(@"[\S]+@[\S]+");
foreach (string item in regx.Split(text))
{ {
if (isWWW.IsMatch(item)) if (link.IsMatch(item))
{ {
try Hyperlink hl = new Hyperlink();
{ hl.Click += (s, arg) => ProcessLink(item);
Hyperlink link = new Hyperlink(); hl.Inlines.Add(new Run { Text = item });
link.Click += (s, arg) => { ProcessLink(item); }; block.Inlines.Add(hl);
link.Inlines.Add(new Run { Text = item });
block.Inlines.Add(link);
}
catch
{
block.Inlines.Add(new Run { Text = item });
}
} }
else if (isEmail.IsMatch(item)) else if (mail.IsMatch(item))
{ {
try Hyperlink hl = new Hyperlink { NavigateUri = $"mailto:{item}".ToUri() };
{ hl.Inlines.Add(new Run { Text = item });
Hyperlink link = new Hyperlink { NavigateUri = new Uri($"mailto:{item}"), Foreground = new SolidColorBrush(Colors.Red) }; block.Inlines.Add(hl);
link.Inlines.Add(new Run { Text = item });
block.Inlines.Add(link);
}
catch
{
block.Inlines.Add(new Run { Text = item });
}
} }
else if (item == "s")
continue;
else else
block.Inlines.Add(new Run { Text = item }); block.Inlines.Add(new Run { Text = item });
} }
@@ -223,65 +214,48 @@ namespace FoxTube
public async static void ProcessLink(string url) public async static void ProcessLink(string url)
{ {
try string output;
{ string type;
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}");
if (url.Contains("/playlist")) if (YoutubeClient.TryParseChannelId(url, out output))
{
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
{ {
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(); deferral.Complete();
} }
} }
public static async Task<List<string>> GetHistory()
{
List<string> list = new List<string>();
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<List<string>> GetLater()
{
List<string> list = new List<string>();
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;
}
} }
} }
+16
View File
@@ -129,5 +129,21 @@ namespace FoxTube
Channel = channelId; Channel = channelId;
Filter = filters; 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}";
}
} }
} }
+78 -29
View File
@@ -9,6 +9,10 @@ using Google.Apis.YouTube.v3.Data;
using Newtonsoft.Json; using Newtonsoft.Json;
using Windows.Storage; using Windows.Storage;
using Windows.Services.Store; 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 namespace FoxTube
{ {
@@ -37,18 +41,22 @@ namespace FoxTube
ApplicationName = "FoxTube" ApplicationName = "FoxTube"
}; };
public static YouTubeService Service => IsAuthorized ? new YouTubeService(Initializer) : NoAuthService; 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 AppId => true ? "d25517cb-12d4-4699-8bdc-52040c712cab" : "9ncqqxjtdlfh";
public static string AdUnitId => true ? "test" : "1100037769"; public static string AdUnitId => true ? "test" : "1100037769";
public static bool AdsDisabled { get; private set; } = true; public static bool AdsDisabled { get; private set; } = true;
//User info //User info
public static bool IsAuthorized => Credential != null; 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 string AccountId => UserChannel?.Id;
public static Channel UserChannel { get; private set; } public static Channel UserChannel { get; private set; }
public static Userinfoplus UserInfo { get; private set; }
public static List<Subscription> Subscriptions { get; } = new List<Subscription>(); public static List<Subscription> Subscriptions { get; } = new List<Subscription>();
public static List<string> History { get; set; } = new List<string>();
public static List<string> WatchLater { get; set; } = new List<string>();
#endregion #endregion
#region Methods #region Methods
@@ -69,7 +77,7 @@ namespace FoxTube
try { await Service.Subscriptions.Delete(s.Id).ExecuteAsync(); } try { await Service.Subscriptions.Delete(s.Id).ExecuteAsync(); }
catch { return true; } catch { return true; }
SubscriptionsChanged?.Invoke(null, "remove", Subscriptions.IndexOf(s)); SubscriptionsChanged?.Invoke(null, "remove", s);
Subscriptions.Remove(s); Subscriptions.Remove(s);
return false; return false;
} }
@@ -96,35 +104,67 @@ namespace FoxTube
} }
} }
/// <summary>
/// Sets up **SecretsVault**
/// </summary>
public static void Initialize()
{
CheckAuthorization();
// TODO: Reactivate addons initialization
//CheckAddons();
}
/// <summary> /// <summary>
/// Prompts to add an Youtube account and retrieves its info when successful /// Prompts to add an Youtube account and retrieves its info when successful
/// </summary> /// </summary>
/// <param name="retrieveSubs">Loads user's subscriptions if true</param> /// <param name="retrieveSubs">Loads user's subscriptions if true</param>
public static async void Authorize(bool retrieveSubs = true) public static async void Authorize(bool retrieveSubs = true)
{ {
#region Retrieving user's credential
try try
{ {
#region Retrieving user's credential
Credential = await GoogleWebAuthorizationBroker.AuthorizeAsync( Credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
Secrets, Secrets,
new[] new[]
{ {
Google.Apis.Oauth2.v2.Oauth2Service.Scope.UserinfoProfile, Oauth2Service.Scope.UserinfoProfile,
YouTubeService.Scope.YoutubeForceSsl Oauth2Service.Scope.UserinfoEmail,
}, YouTubeService.Scope.YoutubeForceSsl,
"user", YouTubeService.Scope.Youtube,
CancellationToken.None); YouTubeService.Scope.YoutubeUpload,
YouTubeService.Scope.YoutubeReadonly,
YouTubeService.Scope.Youtubepartner
},
"user",
CancellationToken.None);
}
catch (AuthenticateException e)
{
if (e.Message.Contains("UserCancel"))
return;
else
throw e;
}
if (Credential == null || !retrieveSubs) if (Credential == null || !retrieveSubs)
goto InvokeEvent; return;
SettingsStorage.HasAccount = true; SettingsStorage.HasAccount = true;
#endregion
HttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Credential.Token.AccessToken);
#endregion
try
{
#region Retrieving user's data #region Retrieving user's data
var request = Service.Channels.List("snippet,contentDetails"); UserInfo = await new Oauth2Service(Initializer).Userinfo.Get().ExecuteAsync();
request.Mine = true;
UserChannel = (await request.ExecuteAsync()).Items[0]; try
{
WatchLater = await Methods.GetLater();
History = await Methods.GetHistory();
}
catch { }
SubscriptionsResource.ListRequest subRequest = Service.Subscriptions.List("snippet"); SubscriptionsResource.ListRequest subRequest = Service.Subscriptions.List("snippet");
subRequest.Mine = true; subRequest.Mine = true;
@@ -143,13 +183,16 @@ namespace FoxTube
nextToken = subResponse.NextPageToken; nextToken = subResponse.NextPageToken;
} while (!string.IsNullOrWhiteSpace(nextToken)); } while (!string.IsNullOrWhiteSpace(nextToken));
var request = Service.Channels.List("snippet,contentDetails");
request.Mine = true;
UserChannel = (await request.ExecuteAsync()).Items[0];
#endregion #endregion
//Saving user's subscriptions for background task //Saving user's subscriptions for background task
SaveSubscriptions(); SaveSubscriptions();
InvokeEvent: AuthorizationStateChanged?.Invoke(args: true);
AuthorizationStateChanged?.Invoke(args: IsAuthorized);
} }
catch catch
{ {
@@ -160,13 +203,11 @@ namespace FoxTube
/// <summary> /// <summary>
/// Saves user's subscriptions keypairs (channel ID: avatar URL) into "background.json" file for concurrent background processing /// Saves user's subscriptions keypairs (channel ID: avatar URL) into "background.json" file for concurrent background processing
/// </summary> /// </summary>
public static async void SaveSubscriptions() public static void SaveSubscriptions()
{ {
Dictionary<string, string> subs = new Dictionary<string, string>(); Dictionary<string, string> subs = new Dictionary<string, string>();
Subscriptions.ForEach(x => subs.Add(x.Snippet.ResourceId.ChannelId, x.Snippet.Thumbnails.Medium.Url)); Subscriptions.ForEach(x => subs.Add(x.Snippet.ResourceId.ChannelId, x.Snippet.Thumbnails.Default__.Url));
await FileIO.WriteTextAsync( ApplicationData.Current.RoamingSettings.Values["subscriptions"] = JsonConvert.SerializeObject(subs);
await ApplicationData.Current.RoamingFolder.CreateFileAsync("background.json", CreationCollisionOption.ReplaceExisting),
JsonConvert.SerializeObject(subs));
} }
/// <summary> /// <summary>
@@ -179,6 +220,8 @@ namespace FoxTube
Credential = null; Credential = null;
AuthorizationStateChanged?.Invoke(args: false); AuthorizationStateChanged?.Invoke(args: false);
SettingsStorage.HasAccount = false; SettingsStorage.HasAccount = false;
ApplicationData.Current.RoamingSettings.Values["subscriptions"] = "";
} }
} }
@@ -190,7 +233,11 @@ namespace FoxTube
{ {
if (SettingsStorage.HasAccount) if (SettingsStorage.HasAccount)
Authorize(retrieveSubs); Authorize(retrieveSubs);
else AuthorizationStateChanged.Invoke(args: false); else
{
AuthorizationStateChanged.Invoke(args: false);
ApplicationData.Current.RoamingSettings.Values["subscriptions"] = "";
}
} }
/// <summary> /// <summary>
@@ -201,9 +248,11 @@ namespace FoxTube
try try
{ {
StoreContext store = StoreContext.GetDefault(); StoreContext store = StoreContext.GetDefault();
StoreProductQueryResult requset = await store.GetAssociatedStoreProductsAsync(new[] { "Consumable", "Durable", "UnmanagedConsumable" }); StoreProductQueryResult requset = await store.GetAssociatedStoreProductsAsync(new[] { "Durable" });
Dictionary<string, StoreProduct> l = new Dictionary<string, StoreProduct>();
requset.Products.ForEach(i => l.Add(i.Key, i.Value));
if (!requset.Products["foxtube-adsremove"].IsInUserCollection) if (!requset.Products["9NP1QK556625"].IsInUserCollection)
{ {
AdsDisabled = false; AdsDisabled = false;
Purchased?.Invoke(args:false); Purchased?.Invoke(args:false);
@@ -215,7 +264,7 @@ namespace FoxTube
public static async void GetAdblock() public static async void GetAdblock()
{ {
StoreContext store = StoreContext.GetDefault(); StoreContext store = StoreContext.GetDefault();
StorePurchaseResult request = await store.RequestPurchaseAsync("foxtube-adsremove"); StorePurchaseResult request = await store.RequestPurchaseAsync("9NP1QK556625");
switch (request.Status) switch (request.Status)
{ {
+96 -113
View File
@@ -7,128 +7,152 @@ using Windows.Storage;
namespace FoxTube 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 class SettingsStorage
{ {
public static string VideoQuality public static string VideoQuality
{ {
get { return (string)settings[0]; } get { return Container.videoQuality; }
set set
{ {
settings[0] = value; Container.videoQuality = value;
SaveData(); SaveData();
} }
} }
public static string RememberedQuality public static string RememberedQuality
{ {
get { return (string)settings[1]; } get { return Container.rememberedQuality; }
set set
{ {
settings[1] = value; Container.rememberedQuality = value;
SaveData(); SaveData();
} }
} }
public static bool VideoNotifications public static bool VideoNotifications
{ {
get { return (bool)settings[2]; } get { return Container.videoNotifications; }
set set
{ {
settings[2] = value; Container.videoNotifications = value;
SaveData(); SaveData();
} }
} }
public static bool DevNotifications public static bool DevNotifications
{ {
get { return (bool)settings[3]; } get { return Container.devNotifications; }
set set
{ {
settings[3] = value; Container.devNotifications = value;
SaveData(); SaveData();
} }
} }
public static bool CheckConnection public static bool CheckConnection
{ {
get { return (bool)settings[4]; } get { return Container.checkConnection; }
set set
{ {
settings[4] = value; Container.checkConnection = value;
SaveData(); SaveData();
} }
} }
public static bool Autoplay public static bool Autoplay
{ {
get { return (bool)settings[5]; } get { return Container.autoplay; }
set set
{ {
settings[5] = value; Container.autoplay = value;
SaveData(); SaveData();
} }
} }
public static int Volume public static double Volume
{ {
get { return Convert.ToInt32(settings[6]); } get { return Container.volume; }
set set
{ {
settings[6] = value; Container.volume = value;
SaveData(); SaveData();
} }
} }
public static string Language public static string Language
{ {
get { return (string)settings[7]; } get { return Container.language; }
set set
{ {
settings[7] = value; Container.language = value;
SaveData(); SaveData();
} }
} }
public static string RelevanceLanguage public static string RelevanceLanguage
{ {
get { return (string)settings[8]; } get { return Container.relevanceLanguage; }
set set
{ {
settings[8] = value; Container.relevanceLanguage = value;
SaveData(); SaveData();
} }
} }
public static string Region public static string Region
{ {
get { return (string)settings[9]; } get { return Container.region; }
set set
{ {
settings[9] = value; Container.region = value;
SaveData(); SaveData();
} }
} }
public static int SafeSearch public static int SafeSearch
{ {
get { return Convert.ToInt32(settings[10]); } get { return Container.safeSearch; }
set set
{ {
settings[10] = value; Container.safeSearch = value;
SaveData();
}
}
public static int Theme
{
get { return Convert.ToInt32(settings[11]); }
set
{
settings[11] = value;
SaveData(); SaveData();
} }
} }
public static bool HasAccount public static bool HasAccount
{ {
get { return (bool)settings[12]; } get { return Container.hasAccount; }
set set
{ {
settings[12] = value; Container.hasAccount = value;
SaveData();
}
}
public static int Theme
{
get { return Container.theme; }
set
{
Container.theme = value;
SaveData(); 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 //Settings storage
private static readonly ApplicationDataContainer storage = ApplicationData.Current.RoamingSettings; private static readonly ApplicationDataContainer storage = ApplicationData.Current.RoamingSettings;
private static SettingsContainer Container;
//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
};
public static void LoadData() public static void LoadData()
{ {
/*if(true && (storage.Values["forceUpdate"] == null || storage.Values["forceUpdate"].ToString() != Version))
{
SaveData();
storage.Values["forceUpdate"] = Version;
return;
}*/
try try
{ {
object[] saved = JsonConvert.DeserializeObject<object[]>(storage.Values["settings"] as string); Container = JsonConvert.DeserializeObject<SettingsContainer>(storage.Values["settings"] as string);
if (settings.Length > saved.Length) }
{ catch
if (saved[0] is string) {
settings[0] = saved; Container = new SettingsContainer();
if (saved[1] is string) SaveData();
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;
} }
catch (ArgumentNullException) { }
} }
public static void SaveData() public static void SaveData()
{ {
storage.Values["settings"] = JsonConvert.SerializeObject(settings); storage.Values["settings"] = JsonConvert.SerializeObject(Container);
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 { }
} }
} }
} }
+22 -22
View File
@@ -7,38 +7,38 @@
mc:Ignorable="d" mc:Ignorable="d"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Top" VerticalAlignment="Top"
SizeChanged="UserControl_SizeChanged"
d:DesignHeight="290" d:DesignHeight="290"
d:DesignWidth="384"> d:DesignWidth="384"
Visibility="Collapsed">
<Button Padding="0" Background="Transparent" Name="btn"> <Button Padding="0" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<Grid Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}"> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*"/> <RowDefinition/>
<RowDefinition Height="75"/> <RowDefinition Height="20"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Source="/Assets/videoPlaceholder.png" Opacity=".0001" Stretch="Fill"/>
<Image Name="image" Source="/Assets/videoThumbSample.png" Stretch="Fill"/> <Image Name="image" Source="/Assets/videoThumbSample.png" Stretch="Fill"/>
<StackPanel Margin="0,0,5,5" Background="Orange" VerticalAlignment="Bottom" BorderBrush="OrangeRed" BorderThickness="1" HorizontalAlignment="Right" Padding="5,2,5,3"> <StackPanel Margin="0,0,5,5" Background="Orange" VerticalAlignment="Bottom" BorderBrush="OrangeRed" BorderThickness="1" HorizontalAlignment="Right" Padding="5,2,5,3">
<TextBlock Name="info" Text="SPONSORED CONTENT" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Gray" FontSize="12"/> <TextBlock Name="info" Text="SPONSORED CONTENT" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Gray" FontSize="12"/>
</StackPanel> </StackPanel>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions>
<Grid Name="contentGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Height="50" Width="50" Margin="5,-30,5,10" Fill="{ThemeResource SystemControlBackgroundChromeMediumBrush}"/>
<PersonPicture Name="icon" Grid.Column="0" Height="46" Margin="5,-30,5,0" BorderBrush="White" BorderThickness="10"/>
<TextBlock Name="sponsor" HorizontalAlignment="Left" Grid.Column="1" Text="[Sponsored by]" TextTrimming="CharacterEllipsis" Foreground="Gray" Margin="0,2,0,0" FontSize="12"/> <Grid Grid.Row="1" Name="contentGrid">
<TextBlock Grid.Column="1" Name="desc" Text="[Description]" HorizontalAlignment="Right" Foreground="Gray" Margin="0,2,2,0" FontSize="12"/> <Grid.ColumnDefinitions>
</Grid> <ColumnDefinition Width="60"/>
<TextBlock Grid.Row="1" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" Margin="5" MaxLines="2" TextTrimming="CharacterEllipsis"/> <ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Height="50" Width="50" Margin="5,-30,5,10" Fill="{ThemeResource SystemControlBackgroundChromeMediumBrush}"/>
<PersonPicture FontFamily="Segoe MDL2 Assets" Initials="&#xEC24;" Name="icon" Grid.Column="0" Height="46" Margin="5,-30,5,0" BorderBrush="White" BorderThickness="10"/>
<TextBlock Name="sponsor" HorizontalAlignment="Left" Grid.Column="1" Text="[Sponsored by]" TextTrimming="CharacterEllipsis" Foreground="Gray" Margin="0,2,0,0" FontSize="12"/>
<TextBlock Grid.Column="1" Name="desc" Text="[Description]" HorizontalAlignment="Right" Foreground="Gray" Margin="0,2,2,0" FontSize="12"/>
</Grid> </Grid>
<TextBlock Grid.Row="2" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" Margin="5" MaxLines="2" TextTrimming="CharacterEllipsis"/>
</Grid> </Grid>
</Button> </Button>
</UserControl> </UserControl>
+2 -9
View File
@@ -31,10 +31,8 @@ namespace FoxTube.Controls.Adverts
{ {
title.Text = advert.Title; title.Text = advert.Title;
image.Source = new BitmapImage(advert.MainImages.First().Url.ToUri()); image.Source = new BitmapImage(advert.MainImages.First().Url.ToUri());
if (advert.AdIcon == null)
contentGrid.ColumnDefinitions[0].Width = new GridLength(0); icon.ProfilePicture = advert.AdIcon.Source;
else
icon.ProfilePicture = advert.AdIcon.Source;
if (string.IsNullOrWhiteSpace(advert.SponsoredBy)) if (string.IsNullOrWhiteSpace(advert.SponsoredBy))
sponsor.Visibility = Visibility.Collapsed; sponsor.Visibility = Visibility.Collapsed;
@@ -53,10 +51,5 @@ namespace FoxTube.Controls.Adverts
Visibility = Visibility.Visible; Visibility = Visibility.Visible;
} }
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
Height = e.NewSize.Width * 0.75;
}
} }
} }
+45 -31
View File
@@ -4,51 +4,65 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 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" mc:Ignorable="d"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Top" VerticalAlignment="Top"
SizeChanged="UserControl_SizeChanged"
d:DesignHeight="290" d:DesignHeight="290"
d:DesignWidth="384"> d:DesignWidth="384"
Opacity="0">
<Button Padding="0" Background="Transparent" Click="Button_Click" VerticalContentAlignment="Stretch" VerticalAlignment="Stretch"> <Windows10version1809:UserControl.OpacityTransition>
<Grid Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}"> <ScalarTransition Duration="0:0:0.5"/>
</Windows10version1809:UserControl.OpacityTransition>
<Button Padding="0" Margin="1" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" Click="Button_Click">
<Grid Name="grid">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="50"/>
<RowDefinition/> <RowDefinition/>
<RowDefinition Height="auto"/> <RowDefinition Height="auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Name="cover" Source="/Assets/ChannelCoverTemplate.png" Stretch="UniformToFill" VerticalAlignment="Center"/>
<Image Source="/Assets/ChannelCoverTemplate.png" Stretch="UniformToFill" Opacity=".0001"/>
<Image Name="cover" Source="/Assets/ChannelCoverTemplate.png" Stretch="UniformToFill" ImageOpened="Cover_ImageOpened" Opacity="0">
<Windows10version1809:Image.OpacityTransition>
<ScalarTransition Duration="0:0:0.5"/>
</Windows10version1809:Image.OpacityTransition>
</Image>
<StackPanel Name="liveTag" Margin="5" Background="Red" BorderBrush="White" BorderThickness="1" VerticalAlignment="Bottom" HorizontalAlignment="Right" Padding="5,2,5,3" Orientation="Horizontal" Visibility="Collapsed"> <StackPanel Name="liveTag" Margin="5" Background="Red" BorderBrush="White" BorderThickness="1" VerticalAlignment="Bottom" HorizontalAlignment="Right" Padding="5,2,5,3" Orientation="Horizontal" Visibility="Collapsed">
<TextBlock Text="&#xEC44; " VerticalAlignment="Center" Foreground="White" FontSize="12" FontFamily="Segoe MDL2 Assets" FontWeight="Black"/> <TextBlock Text="&#xEC44; " VerticalAlignment="Center" Foreground="White" FontSize="12" FontFamily="Segoe MDL2 Assets" FontWeight="Black"/>
<TextBlock x:Uid="/Cards/live" Text="LIVE" VerticalAlignment="Center" Foreground="White" FontSize="12" FontWeight="Bold"/> <TextBlock x:Uid="/Cards/live" Text="LIVE" VerticalAlignment="Center" Foreground="White" FontSize="12" FontWeight="Bold"/>
</StackPanel> </StackPanel>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Ellipse Height="80" Width="80" Margin="0,-45,0,0" VerticalAlignment="Bottom" Fill="{ThemeResource SystemControlBackgroundChromeMediumBrush}"/>
<PersonPicture Name="avatar" Grid.Column="0" Height="74" Margin="3,-45,3,3" VerticalAlignment="Bottom" BorderBrush="White" BorderThickness="10"/>
<TextBlock Name="title" Grid.Column="1" Text="[Channel name]" Margin="5" TextWrapping="WrapWholeWords" MaxLines="2" TextTrimming="CharacterEllipsis"/> <Grid Grid.Row="1">
<StackPanel Grid.Column="2" Margin="5"> <Grid.ColumnDefinitions>
<TextBlock Name="subs" Text="[Subscribers counter]" Foreground="Gray"/> <ColumnDefinition Width="auto"/>
<TextBlock Name="uploads" Text="[Uploads counter]" Foreground="Gray"/> <ColumnDefinition/>
</StackPanel> <ColumnDefinition Width="auto"/>
</Grid> </Grid.ColumnDefinitions>
<TextBlock Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Height="50" Margin="10" TextAlignment="Center" Padding="0,16,0,0" Foreground="Gray"> <Ellipse Height="80" Width="80" Margin="0,-45,0,0" VerticalAlignment="Bottom" Fill="{ThemeResource SystemControlBackgroundChromeMediumBrush}"/>
<Hyperlink Click="Hyperlink_Click"><Run x:Uid="/Cards/login">Log in</Run></Hyperlink> <Run x:Uid="/Cards/tomanage">to manage your subscriptions</Run> <PersonPicture Name="avatar" Grid.Column="0" Height="74" Margin="3,-45,3,3" VerticalAlignment="Bottom" BorderBrush="White" BorderThickness="10"/>
</TextBlock>
<Grid Visibility="Collapsed" Grid.Row="1" VerticalAlignment="Bottom" Margin="10" Name="subscriptionPane" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}"> <TextBlock Name="title" Grid.Column="1" Text="[Channel name]" Margin="5" TextWrapping="WrapWholeWords" MaxLines="2" TextTrimming="CharacterEllipsis"/>
<Button x:Uid="/Cards/subscribe" Click="subscribe_Click" Name="subscribe" HorizontalAlignment="Stretch" Height="50" Background="Red" Foreground="White" FontSize="18" FontWeight="SemiBold" Content="Subscirbe" Margin="0,0,0,0"/>
<ToggleButton Name="notify" Height="50" Width="50" Visibility="Collapsed" FontFamily="Segoe MDL2 Assets" FontSize="18" FontWeight="SemiBold" Content="&#xE7ED;" Foreground="White" Background="Red" HorizontalAlignment="Right"/> <StackPanel Grid.Column="2" Margin="5">
</Grid> <TextBlock Name="subs" Text="[Subscribers counter]" Foreground="Gray"/>
<TextBlock Name="uploads" Text="[Uploads counter]" Foreground="Gray"/>
</StackPanel>
</Grid>
<TextBlock Height="75" Name="description" Grid.Row="2" Margin="10" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin molestie vulputate leo, sed faucibus ex rutrum nec. Donec quis diam nisi. Suspendisse sollicitudin sapien quis eros vulputate, sed scelerisque enim ullamcorper. Donec vulputate commodo mi, vel vestibulum quam posuere ac. Curabitur ac nunc augue. Phasellus aliquam neque ac condimentum bibendum." TextWrapping="WrapWholeWords" TextTrimming="CharacterEllipsis"/>
<TextBlock Grid.Row="3" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Height="50" Margin="10" TextAlignment="Center" Padding="0,16,0,0" Foreground="Gray">
<Hyperlink Click="Hyperlink_Click"><Run x:Uid="/Cards/login">Log in</Run></Hyperlink> <Run x:Uid="/Cards/tomanage">to manage your subscriptions</Run>
</TextBlock>
<Grid Visibility="Collapsed" Grid.Row="3" VerticalAlignment="Stretch" Margin="10" Name="subscriptionPane" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<Button x:Uid="/Cards/subscribe" VerticalAlignment="Stretch" Click="subscribe_Click" Name="subscribe" HorizontalAlignment="Stretch" Height="50" Background="Red" Foreground="White" FontSize="18" FontWeight="SemiBold" Content="Subscirbe" Margin="0,0,0,0"/>
<ToggleButton Name="notify" Height="50" Width="50" Visibility="Collapsed" FontFamily="Segoe MDL2 Assets" FontSize="18" FontWeight="SemiBold" Content="&#xE7ED;" Foreground="White" Background="Red" HorizontalAlignment="Right"/>
</Grid> </Grid>
</Grid> </Grid>
</Button> </Button>
+52 -37
View File
@@ -22,60 +22,70 @@ namespace FoxTube.Controls
string channelId; string channelId;
Channel item; Channel item;
public ChannelCard(string id, string live = "null") public ChannelCard(string id, string live = null)
{ {
InitializeComponent(); InitializeComponent();
Initialize(id, live); 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) public async void Initialize(string id, string live)
{ {
ChannelsResource.ListRequest request = SecretsVault.Service.Channels.List("snippet,statistics,brandingSettings"); try
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)
{ {
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.Background = new SolidColorBrush(Colors.Transparent);
subscribe.Foreground = new SolidColorBrush(Colors.Gray); subscribe.Foreground = new SolidColorBrush(Colors.Gray);
subscribe.Content = resources.GetString("/Cards/unsubscribe"); 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<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Video ID", id }
});
} }
try { avatar.ProfilePicture = new BitmapImage(new Uri(item.Snippet.Thumbnails.Medium.Url)); } Opacity = 1;
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 { }
} }
private void Button_Click(object sender, RoutedEventArgs e) public void Button_Click(object sender, RoutedEventArgs e)
{ {
Methods.MainPage.GoToChannel(channelId); Methods.MainPage.GoToChannel(channelId);
} }
@@ -104,13 +114,18 @@ namespace FoxTube.Controls
private void GetLink_Click(object sender, RoutedEventArgs e) private void GetLink_Click(object sender, RoutedEventArgs e)
{ {
DataPackage data = new DataPackage(); 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); Clipboard.SetContent(data);
} }
private async void InBrowser_Click(object sender, RoutedEventArgs e) 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;
} }
} }
} }
+50 -48
View File
@@ -24,53 +24,55 @@
Content="&#xE122;" FontSize="30"/> Content="&#xE122;" FontSize="30"/>
</Grid> </Grid>
<ScrollViewer Grid.Row="1"> <ListView Name="list" Grid.Row="1" SelectionMode="None">
<ListView Name="list" SelectionMode="None"> <ListView.ItemTemplate>
<ListView.ItemTemplate> <DataTemplate>
<DataTemplate> <Border BorderBrush="Red" BorderThickness="{Binding Path=BorderThickness}" CornerRadius="5" HorizontalAlignment="Stretch" Background="{Binding Path=Background}" Margin="0,2">
<Border BorderBrush="Red" BorderThickness="{Binding Path=BorderThickness}" CornerRadius="5" HorizontalAlignment="Stretch" Background="{Binding Path=Background}" Margin="0,2"> <Grid Margin="0,5,5,0">
<Grid Margin="0,5,5,0"> <Grid.ColumnDefinitions>
<Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition/>
<ColumnDefinition/> </Grid.ColumnDefinitions>
</Grid.ColumnDefinitions> <StackPanel Orientation="Horizontal" VerticalAlignment="Top" Margin="5,0">
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Margin="5,0"> <PersonPicture Height="20">
<PersonPicture Height="20" ProfilePicture="{Binding Path=Avatar}"/> <PersonPicture.ProfilePicture>
<FontIcon Glyph="&#xEC61;" Margin="2,0" Visibility="{Binding Path=IsVerified}"> <BitmapImage UriSource="{Binding Path=Avatar}" DecodePixelHeight="20" DecodePixelWidth="20"/>
<ToolTipService.ToolTip> </PersonPicture.ProfilePicture>
<TextBlock x:Uid="/Chat/verified"/> </PersonPicture>
</ToolTipService.ToolTip> <FontIcon Glyph="&#xEC61;" Margin="2,0" Visibility="{Binding Path=IsVerified}">
</FontIcon> <ToolTipService.ToolTip>
<FontIcon Glyph="&#xEC1B;" Margin="2,0" Visibility="{Binding Path=IsModerator}"> <TextBlock x:Uid="/Chat/verified"/>
<ToolTipService.ToolTip> </ToolTipService.ToolTip>
<TextBlock x:Uid="/Chat/moder"/> </FontIcon>
</ToolTipService.ToolTip> <FontIcon Glyph="&#xEC1B;" Margin="2,0" Visibility="{Binding Path=IsModerator}">
</FontIcon> <ToolTipService.ToolTip>
<FontIcon Glyph="&#xECA7;" Margin="2,0" Visibility="{Binding Path=IsOwner}"> <TextBlock x:Uid="/Chat/moder"/>
<ToolTipService.ToolTip> </ToolTipService.ToolTip>
<TextBlock x:Uid="/Chat/owner"/> </FontIcon>
</ToolTipService.ToolTip> <FontIcon Glyph="&#xECA7;" Margin="2,0" Visibility="{Binding Path=IsOwner}">
</FontIcon> <ToolTipService.ToolTip>
<FontIcon Glyph="&#xE735;" Margin="2,0" Visibility="{Binding Path=IsSponsor}"> <TextBlock x:Uid="/Chat/owner"/>
<ToolTipService.ToolTip> </ToolTipService.ToolTip>
<TextBlock x:Uid="/Chat/sponsor"/> </FontIcon>
</ToolTipService.ToolTip> <FontIcon Glyph="&#xE735;" Margin="2,0" Visibility="{Binding Path=IsSponsor}">
</FontIcon> <ToolTipService.ToolTip>
</StackPanel> <TextBlock x:Uid="/Chat/sponsor"/>
<StackPanel Orientation="Horizontal" Grid.Column="1" VerticalAlignment="Top" Margin="0,0,5,0"> </ToolTipService.ToolTip>
<HyperlinkButton Content="{Binding Path=Author}" Tag="{Binding Path=ChannelId}" Grid.Column="1" Margin="0,-6,0,0" FontWeight="Bold" Click="HyperlinkButton_Click"/> </FontIcon>
<TextBlock Text=":"/> </StackPanel>
</StackPanel> <StackPanel Orientation="Horizontal" Grid.Column="1" VerticalAlignment="Top" Margin="0,0,5,0">
<GroupItem Content="{Binding Path=Message}" Grid.Column="2"/> <HyperlinkButton Content="{Binding Path=Author}" Tag="{Binding Path=ChannelId}" Grid.Column="1" Margin="0,-6,0,0" FontWeight="Bold" Click="HyperlinkButton_Click"/>
</Grid> <TextBlock Text=":"/>
</Border> </StackPanel>
</DataTemplate> <GroupItem Content="{Binding Path=Message}" Grid.Column="2"/>
</ListView.ItemTemplate> </Grid>
<ListViewItem> </Border>
<TextBlock x:Uid="/Chat/welcome" Text="Welcome to the chat room" Foreground="Gray"/> </DataTemplate>
</ListViewItem> </ListView.ItemTemplate>
</ListView> <ListViewItem>
</ScrollViewer> <TextBlock x:Uid="/Chat/welcome" Text="Welcome to the chat room" Foreground="Gray"/>
</ListViewItem>
</ListView>
</Grid> </Grid>
</UserControl> </UserControl>
+29 -13
View File
@@ -5,6 +5,10 @@ using Windows.UI.Xaml.Media;
using Google.Apis.YouTube.v3.Data; using Google.Apis.YouTube.v3.Data;
using Google.Apis.YouTube.v3; using Google.Apis.YouTube.v3;
using Windows.UI; using Windows.UI;
using Microsoft.AppCenter.Analytics;
using System.Collections.Generic;
using Windows.UI.Popups;
using Windows.ApplicationModel.Resources;
namespace FoxTube.Controls namespace FoxTube.Controls
{ {
@@ -66,6 +70,10 @@ namespace FoxTube.Controls
{ {
string chatId; string chatId;
DateTime lastInsert; DateTime lastInsert;
LiveChatMessagesResource.ListRequest request;
LiveChatMessageListResponse response;
DispatcherTimer timer = new DispatcherTimer() DispatcherTimer timer = new DispatcherTimer()
{ {
Interval = TimeSpan.FromSeconds(1) Interval = TimeSpan.FromSeconds(1)
@@ -76,19 +84,20 @@ namespace FoxTube.Controls
InitializeComponent(); InitializeComponent();
if (!SecretsVault.IsAuthorized) if (!SecretsVault.IsAuthorized)
inputField.Visibility = Visibility.Collapsed; inputField.Visibility = Visibility.Collapsed;
chatId = id; chatId = id;
request = SecretsVault.Service.LiveChatMessages.List(chatId, "snippet,authorDetails");
timer.Tick += Update; timer.Tick += Update;
timer.Start(); timer.Start();
} }
public async void Update(object sender, object e) public async void Update(object sender, object e)
{ {
LiveChatMessagesResource.ListRequest request = SecretsVault.Service.LiveChatMessages.List(chatId, "snippet,authorDetails"); response = await request.ExecuteAsync();
LiveChatMessageListResponse response = await request.ExecuteAsync(); foreach (LiveChatMessage i in response.Items.FindAll(i => i.Snippet.PublishedAt >= lastInsert))
foreach (LiveChatMessage i in response.Items) list.Items.Insert(0, new ChatMessage(i));
if(i.Snippet.PublishedAt >= lastInsert)
list.Items.Insert(0, new ChatMessage(i));
lastInsert = DateTime.Now; lastInsert = DateTime.Now;
timer.Interval = TimeSpan.FromMilliseconds(response.PollingIntervalMillis.Value); timer.Interval = TimeSpan.FromMilliseconds(response.PollingIntervalMillis.Value);
timer.Start(); timer.Start();
@@ -99,16 +108,16 @@ namespace FoxTube.Controls
Methods.MainPage.GoToChannel(((HyperlinkButton)sender).Tag as string); 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 try
{ {
newMessage.IsEnabled = false;
send.IsEnabled = false;
if (string.IsNullOrWhiteSpace(newMessage.Text)) if (string.IsNullOrWhiteSpace(newMessage.Text))
return; return;
newMessage.IsEnabled = false;
send.IsEnabled = false;
LiveChatMessage message = new LiveChatMessage() LiveChatMessage message = new LiveChatMessage()
{ {
Snippet = new LiveChatMessageSnippet() Snippet = new LiveChatMessageSnippet()
@@ -131,11 +140,18 @@ namespace FoxTube.Controls
list.Items.Add(new ChatMessage(response)); list.Items.Add(new ChatMessage(response));
} }
} }
finally catch(Exception e)
{ {
newMessage.IsEnabled = true; await new MessageDialog(ResourceLoader.GetForCurrentView("Chat").GetString("/Chat/failed")).ShowAsync();
send.IsEnabled = true; Analytics.TrackEvent("Failed to send a chat message", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message }
});
} }
newMessage.IsEnabled = true;
send.IsEnabled = true;
} }
} }
} }
+65 -77
View File
@@ -7,95 +7,83 @@
mc:Ignorable="d" mc:Ignorable="d"
d:DesignWidth="400"> d:DesignWidth="400">
<Grid Background="{ThemeResource AppBarBackgroundThemeBrush}" Margin="2" Name="grid"> <Grid Margin="2">
<Grid.RowDefinitions> <Grid.ColumnDefinitions>
<RowDefinition Height="auto"/> <ColumnDefinition Width="60"/>
<RowDefinition Height="0"/> <ColumnDefinition/>
<RowDefinition Height="0"/> </Grid.ColumnDefinitions>
</Grid.RowDefinitions> <PersonPicture Tapped="avatar_Tapped" Name="avatar" Height="50" Margin="5" VerticalAlignment="Top"/>
<Grid> <StackPanel Grid.Column="1" Margin="5">
<Grid.ColumnDefinitions> <TextBlock Text="Channel name" Name="author" TextWrapping="WrapWholeWords" Foreground="Gray" FontSize="13"/>
<ColumnDefinition Width="60"/> <Border Visibility="Collapsed" Name="specialAuthor" HorizontalAlignment="Left" Background="Red" CornerRadius="5">
<ColumnDefinition/> <TextBlock Text="Channelname" Foreground="White" TextWrapping="WrapWholeWords" FontSize="13" Padding="2,0,2,2"/>
</Grid.ColumnDefinitions> </Border>
<PersonPicture Tapped="avatar_Tapped" Name="avatar" Height="50" Margin="5" VerticalAlignment="Top"/> <TextBlock Text="[Publish date] (edited)" Name="meta" TextWrapping="WrapWholeWords" Foreground="Gray" FontSize="13"/>
<Grid Grid.Column="1" Margin="5"> <TextBlock Name="text" IsTextSelectionEnabled="True" TextWrapping="WrapWholeWords" Text="Content"/>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock Name="author" TextWrapping="WrapWholeWords" Foreground="Gray" FontSize="13" Text="[Author's name]"/>
<Border Name="authorBorder" HorizontalAlignment="Left" Background="Red" CornerRadius="5" Visibility="Collapsed">
<TextBlock Name="specialAuthor" TextWrapping="WrapWholeWords" Foreground="White" FontSize="13" Text="[Author's name]" Padding="2,0,2,2"/>
</Border>
<TextBlock Name="meta" Grid.Row="1" TextWrapping="WrapWholeWords" Foreground="Gray" FontSize="13" Text="[Published time span] [?](edited)"/>
<TextBlock Name="text" IsTextSelectionEnabled="True" TextWrapping="WrapWholeWords" Grid.Row="2" Text="[Content]"/>
<StackPanel Grid.Row="2" Name="editor" Visibility="Collapsed"> <StackPanel Name="editor" Visibility="Collapsed">
<TextBox Name="editorText" Text="[Content]" AcceptsReturn="True" TextWrapping="Wrap" TextChanged="editorText_TextChanged"/> <TextBox Name="editorText" Text="[Content]" AcceptsReturn="True" TextWrapping="Wrap" TextChanged="editorText_TextChanged"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,5,0,5"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,5,0,5">
<Button x:Uid="/CommentsPage/editorDelete" Content="Delete comment" Background="Red" Name="deleteComment" Click="DeleteComment_Click"/> <Button x:Uid="/CommentsPage/editorDelete" FontFamily="Segoe MDL2 Assets" Content="&#xE107;" Background="Transparent" FontSize="20" Foreground="Red" Name="deleteComment" Click="DeleteComment_Click"/>
<Button x:Uid="/CommentsPage/editorCancel" Name="editorClose" Content="Cancel" Click="editorClose_Click" Margin="5, 0"/> <Button x:Uid="/CommentsPage/editorCancel" Name="editorClose" Content="Cancel" Click="editorClose_Click" Margin="5, 0"/>
<Button x:Uid="/CommentsPage/editorSubmit" Name="editorSend" Content="Submit" Click="editorSend_Click"/> <Button x:Uid="/CommentsPage/editorSubmit" Name="editorSend" Content="Submit" Click="editorSend_Click"/>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Horizontal" Name="toolbar">
<TextBlock Name="upvote" Foreground="Gray" Padding="0"
VerticalAlignment="Center" Margin="0,0,5,0"
FontFamily="Segoe MDL2 Assets" Text="&#xE19F;" FontSize="20"/>
<TextBlock Name="rating" Foreground="Gray" VerticalAlignment="Center" Text="123"/>
<Button Click="showReplies_Click" Name="showReplies" Background="Transparent" Foreground="Gray" Padding="0" Margin="10,0,0,0"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
Height="35">
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xEC42;" Margin="0,0,5,0" FontSize="20"/>
<TextBlock Text="123"/>
</StackPanel> </StackPanel>
<ProgressBar Name="editorSending" Foreground="Red" IsIndeterminate="True" Visibility="Collapsed"/> </Button>
</StackPanel>
<StackPanel Grid.Row="3" Orientation="Horizontal"> <Button Click="replyBtn_Click" Name="replyBtn" Background="Transparent" Foreground="Gray" Padding="0" Margin="10,0,0,0"
<TextBlock Name="upvote" Foreground="Gray" Padding="0"
VerticalAlignment="Center" Margin="0,0,5,0"
FontFamily="Segoe MDL2 Assets" Text="&#xE19F;" FontSize="20"/>
<TextBlock Name="rating" Foreground="Gray" VerticalAlignment="Center" Text="123"/>
<Button Visibility="Collapsed" Name="downvote" Background="Transparent" Foreground="Gray" Padding="0"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
Height="35" Width="35"
FontFamily="Segoe MDL2 Assets" Content="&#xE19E;" FontSize="20"/>
<Button Click="showReplies_Click" Name="showReplies" Background="Transparent" Foreground="Gray" Padding="0" Margin="10,0,0,0"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
Height="35"> Height="35">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xEC42;" Margin="0,0,5,0" FontSize="20"/> <TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xEE35;" FontSize="20"/>
<TextBlock Text="123"/> <TextBlock x:Uid="/CommentsPage/reply" Text="Reply"/>
</StackPanel> </StackPanel>
</Button> </Button>
<Button Click="replyBtn_Click" Name="replyBtn" Background="Transparent" Foreground="Gray" Padding="0" Margin="10,0,0,0" <Button Click="editBtn_Click" Visibility="Collapsed" Name="editBtn" Background="Transparent" Foreground="Gray" Padding="0" Margin="10,0,0,0"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
Height="35"> Height="35">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xEE35;" FontSize="20"/> <TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xE104;" FontSize="20"/>
<TextBlock x:Uid="/CommentsPage/reply" Text="Reply"/> <TextBlock x:Uid="/CommentsPage/edit" Text="Edit"/>
</StackPanel> </StackPanel>
</Button> </Button>
</StackPanel>
<Button Click="editBtn_Click" Visibility="Collapsed" Name="editBtn" Background="Transparent" Foreground="Gray" Padding="0" Margin="10,0,0,0" <Grid Name="replyEditor" Visibility="Collapsed">
VerticalContentAlignment="Center" HorizontalContentAlignment="Center" <TextBox x:Uid="/CommentsPage/replyBox" Name="reply" TextChanged="reply_TextChanged" TextWrapping="Wrap" AcceptsReturn="True" MaxLength="500"
Height="35"> Margin="2,0,34,0"
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xE104;" FontSize="20"/>
<TextBlock x:Uid="/CommentsPage/edit" Text="Edit"/>
</StackPanel>
</Button>
</StackPanel>
<ProgressBar Grid.Row="3" VerticalAlignment="Bottom" IsIndeterminate="True" Visibility="Collapsed" Name="commentsLoading"/>
</Grid>
</Grid>
<TextBox x:Uid="/CommentsPage/replyBox" Grid.Row="1" Name="reply" TextChanged="reply_TextChanged" TextWrapping="Wrap" BorderThickness="0" Background="{ThemeResource SystemControlBackgroundChromeMediumLowBrush}" AcceptsReturn="True" MaxLength="500"
Padding="5" Margin="0,0,32,0"
PlaceholderText="Enter your reply..."/> PlaceholderText="Enter your reply..."/>
<Button Grid.Row="1" Name="send" Click="send_Click" IsEnabled="False" HorizontalAlignment="Right" VerticalAlignment="Top" <Button Name="send" Click="send_Click" IsEnabled="True" HorizontalAlignment="Right" VerticalAlignment="Top"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
Width="32" Height="32" Padding="0" Width="32" Height="32" Padding="0"
Background="Red" Foreground="White" Background="Transparent"
FontFamily="Segoe MDL2 Assets" FontFamily="Segoe MDL2 Assets"
Content="&#xE122;" /> FontSize="30"
<ProgressBar Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" IsIndeterminate="True" Foreground="Red" Name="sending" Visibility="Collapsed"/> Content="&#xE122;"/>
</Grid>
<ProgressBar Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" IsIndeterminate="True" Name="processing" Visibility="Collapsed"/>
<StackPanel Grid.Row="2" Margin="60,0,0,0" Name="replies"/> <StackPanel Name="replies" Visibility="Collapsed">
<StackPanel.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition IsStaggeringEnabled="True"/>
</TransitionCollection>
</StackPanel.ChildrenTransitions>
</StackPanel>
</StackPanel>
</Grid> </Grid>
</UserControl> </UserControl>
+126 -94
View File
@@ -4,12 +4,12 @@ using System.Linq;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Input;
using Google.Apis.YouTube.v3; using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data; using Google.Apis.YouTube.v3.Data;
using Windows.UI.Xaml.Media.Imaging; using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Popups; using Windows.UI.Popups;
using Windows.ApplicationModel.Resources; using Windows.ApplicationModel.Resources;
using Microsoft.AppCenter.Analytics;
namespace FoxTube.Controls namespace FoxTube.Controls
{ {
@@ -21,10 +21,11 @@ namespace FoxTube.Controls
{ {
ResourceLoader resources = ResourceLoader.GetForCurrentView("CommentsPage"); ResourceLoader resources = ResourceLoader.GetForCurrentView("CommentsPage");
Comment item; public Comment item;
CommentThread thread; public CommentThread thread;
CommentType type;
bool repliesLoaded = false; bool repliesLoaded = false;
CommentType type = CommentType.TopLevel;
public CommentCard(CommentThread comment) public CommentCard(CommentThread comment)
{ {
@@ -37,15 +38,15 @@ namespace FoxTube.Controls
item = comment.Snippet.TopLevelComment; item = comment.Snippet.TopLevelComment;
thread = comment; thread = comment;
replyBtn.Visibility = !comment.Snippet.CanReply.Value || !SecretsVault.IsAuthorized ? Visibility.Collapsed : Visibility.Visible; replyBtn.Visibility = comment.Snippet.CanReply.Value && SecretsVault.IsAuthorized ? Visibility.Visible : Visibility.Collapsed;
if (!comment.Snippet.TotalReplyCount.HasValue || comment.Snippet.TotalReplyCount.Value == 0) if (!comment.Snippet.TotalReplyCount.HasValue || comment.Snippet.TotalReplyCount.Value == 0)
showReplies.Visibility = Visibility.Collapsed; showReplies.Visibility = Visibility.Collapsed;
else else
((showReplies.Content as StackPanel).Children[1] as TextBlock).Text = comment.Snippet.TotalReplyCount.ToString(); ((showReplies.Content as StackPanel).Children[1] as TextBlock).Text = comment.Snippet.TotalReplyCount.ToString();
if (comment.Snippet.TopLevelComment.Snippet.CanRate == false) if (comment.Snippet.TopLevelComment.Snippet.CanRate == false)
{ {
upvote.Visibility = Visibility.Collapsed; upvote.Visibility = Visibility.Collapsed;
downvote.Visibility = Visibility.Collapsed;
rating.Visibility = Visibility.Collapsed; rating.Visibility = Visibility.Collapsed;
} }
else else
@@ -53,15 +54,15 @@ namespace FoxTube.Controls
if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == SecretsVault.AccountId) if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == SecretsVault.AccountId)
{ {
specialAuthor.Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName; (specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
authorBorder.Visibility = Visibility.Visible; specialAuthor.Visibility = Visibility.Visible;
author.Visibility = Visibility.Collapsed; author.Visibility = Visibility.Collapsed;
editBtn.Visibility = Visibility.Visible; editBtn.Visibility = Visibility.Visible;
} }
else if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == Methods.MainPage.GetCurrentItem().Snippet.ChannelId) else if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == item.Snippet.ChannelId)
{ {
specialAuthor.Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName; (specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
authorBorder.Visibility = Visibility.Visible; specialAuthor.Visibility = Visibility.Visible;
author.Visibility = Visibility.Collapsed; author.Visibility = Visibility.Collapsed;
} }
else else
@@ -70,13 +71,24 @@ namespace FoxTube.Controls
meta.Text = string.Format("{0} {1}", Methods.GetAgo(comment.Snippet.TopLevelComment.Snippet.PublishedAt.Value), comment.Snippet.TopLevelComment.Snippet.UpdatedAt != comment.Snippet.TopLevelComment.Snippet.PublishedAt ? resources.GetString("/CommentsPage/edited") : ""); meta.Text = string.Format("{0} {1}", Methods.GetAgo(comment.Snippet.TopLevelComment.Snippet.PublishedAt.Value), comment.Snippet.TopLevelComment.Snippet.UpdatedAt != comment.Snippet.TopLevelComment.Snippet.PublishedAt ? resources.GetString("/CommentsPage/edited") : "");
Methods.FormatText(ref text, comment.Snippet.TopLevelComment.Snippet.TextDisplay); Methods.FormatText(ref text, comment.Snippet.TopLevelComment.Snippet.TextDisplay);
try { avatar.ProfilePicture = new BitmapImage(new Uri(comment.Snippet.TopLevelComment.Snippet.AuthorProfileImageUrl)); } try { avatar.ProfilePicture = new BitmapImage(new Uri(comment.Snippet.TopLevelComment.Snippet.AuthorProfileImageUrl)) { DecodePixelWidth = 50, DecodePixelHeight = 50 }; }
catch { } catch { }
} }
public void DeleteComment(CommentCard comment)
{
replies.Children.Remove(comment);
((showReplies.Content as StackPanel).Children[1] as TextBlock).Text = (--thread.Snippet.TotalReplyCount).ToString();
if(thread.Snippet.TotalReplyCount == 0)
showReplies.Visibility = Visibility.Collapsed;
}
public CommentCard(Comment comment) public CommentCard(Comment comment)
{ {
this.InitializeComponent(); InitializeComponent();
Initialize(comment); Initialize(comment);
} }
@@ -87,7 +99,6 @@ namespace FoxTube.Controls
replyBtn.Visibility = Visibility.Collapsed; replyBtn.Visibility = Visibility.Collapsed;
showReplies.Visibility = Visibility.Collapsed; showReplies.Visibility = Visibility.Collapsed;
downvote.Visibility = Visibility.Collapsed;
if (comment.Snippet.CanRate == false) if (comment.Snippet.CanRate == false)
{ {
@@ -99,15 +110,15 @@ namespace FoxTube.Controls
if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == SecretsVault.AccountId) if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == SecretsVault.AccountId)
{ {
specialAuthor.Text = comment.Snippet.AuthorDisplayName; (specialAuthor.Child as TextBlock).Text = comment.Snippet.AuthorDisplayName;
authorBorder.Visibility = Visibility.Visible; specialAuthor.Visibility = Visibility.Visible;
author.Visibility = Visibility.Collapsed; author.Visibility = Visibility.Collapsed;
editBtn.Visibility = Visibility.Visible; editBtn.Visibility = Visibility.Visible;
} }
else if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == Methods.MainPage.GetCurrentItem().Snippet.ChannelId) else if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == item.Snippet.ChannelId)
{ {
specialAuthor.Text = comment.Snippet.AuthorDisplayName; (specialAuthor.Child as TextBlock).Text = comment.Snippet.AuthorDisplayName;
authorBorder.Visibility = Visibility.Visible; specialAuthor.Visibility = Visibility.Visible;
author.Visibility = Visibility.Collapsed; author.Visibility = Visibility.Collapsed;
} }
else else
@@ -116,59 +127,42 @@ namespace FoxTube.Controls
meta.Text = string.Format("{0} {1}", Methods.GetAgo(comment.Snippet.PublishedAt.Value), comment.Snippet.UpdatedAt != comment.Snippet.PublishedAt ? resources.GetString("/CommentsPage/edited") : ""); meta.Text = string.Format("{0} {1}", Methods.GetAgo(comment.Snippet.PublishedAt.Value), comment.Snippet.UpdatedAt != comment.Snippet.PublishedAt ? resources.GetString("/CommentsPage/edited") : "");
Methods.FormatText(ref text, comment.Snippet.TextDisplay); Methods.FormatText(ref text, comment.Snippet.TextDisplay);
try { avatar.ProfilePicture = new BitmapImage(new Uri(comment.Snippet.AuthorProfileImageUrl)); } try { avatar.ProfilePicture = new BitmapImage(new Uri(comment.Snippet.AuthorProfileImageUrl)) { DecodePixelWidth = 50, DecodePixelHeight = 50 }; }
catch { } catch { }
} }
private void replyBtn_Click(object sender, RoutedEventArgs e) private void replyBtn_Click(object sender, RoutedEventArgs e)
{ {
if (grid.RowDefinitions[1].Height == new GridLength(0)) replyEditor.Visibility = replyEditor.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
grid.RowDefinitions[1].Height = GridLength.Auto;
else
grid.RowDefinitions[1].Height = new GridLength(0);
} }
private async void showReplies_Click(object sender, RoutedEventArgs e) private async void showReplies_Click(object sender, RoutedEventArgs e)
{ {
if (grid.RowDefinitions[2].Height == new GridLength(0)) replies.Visibility = replies.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
if (repliesLoaded)
return;
processing.Visibility = Visibility.Visible;
CommentsResource.ListRequest request = SecretsVault.Service.Comments.List("snippet");
request.ParentId = item.Id;
request.TextFormat = CommentsResource.ListRequest.TextFormatEnum.PlainText;
request.MaxResults = 50;
CommentListResponse response;
do
{ {
if(type == CommentType.TopLevel && !repliesLoaded) response = await request.ExecuteAsync();
{ request.PageToken = response.NextPageToken;
commentsLoading.Visibility = Visibility.Visible;
var request = SecretsVault.Service.Comments.List("snippet");
request.ParentId = item.Id;
request.TextFormat = CommentsResource.ListRequest.TextFormatEnum.PlainText;
request.MaxResults = 50;
string token; response.Items.ForEach(i => replies.Children.Add(new CommentCard(i)));
IList<Comment> list = new List<Comment>();
var response = await request.ExecuteAsync();
token = response.NextPageToken;
foreach (Comment i in response.Items)
list.Add(i);
while(token != null)
{
request.PageToken = token;
response = await request.ExecuteAsync();
token = response.NextPageToken;
foreach (Comment i in response.Items)
list.Add(i);
}
foreach (Comment c in list.Reverse())
replies.Children.Add(new CommentCard(c));
repliesLoaded = true;
commentsLoading.Visibility = Visibility.Collapsed;
}
grid.RowDefinitions[2].Height = GridLength.Auto;
} }
else while (!string.IsNullOrWhiteSpace(request.PageToken));
grid.RowDefinitions[2].Height = new GridLength(0);
repliesLoaded = true;
processing.Visibility = Visibility.Collapsed;
} }
private void avatar_Tapped(object sender, TappedRoutedEventArgs e) private void avatar_Tapped(object sender, TappedRoutedEventArgs e)
@@ -178,99 +172,126 @@ namespace FoxTube.Controls
private void reply_TextChanged(object sender, TextChangedEventArgs e) private void reply_TextChanged(object sender, TextChangedEventArgs e)
{ {
if (reply.Text.Length == 0) send.IsEnabled = reply.Text.Length == 0 ? false : true;
send.IsEnabled = false;
else
send.IsEnabled = true;
} }
private async void send_Click(object sender, RoutedEventArgs e) private async void send_Click(object sender, RoutedEventArgs args)
{ {
if (string.IsNullOrWhiteSpace(reply.Text))
return;
send.IsEnabled = false; send.IsEnabled = false;
reply.IsEnabled = false; reply.IsEnabled = false;
sending.Visibility = Visibility.Visible; processing.Visibility = Visibility.Visible;
Comment comment = new Comment(); Comment comment = new Comment()
comment.Snippet = new CommentSnippet(); {
comment.Snippet.TextOriginal = reply.Text; Snippet = new CommentSnippet()
comment.Snippet.ParentId = item.Id; {
TextOriginal = reply.Text,
ParentId = item.Id
}
};
try try
{ {
Comment response = await SecretsVault.Service.Comments.Insert(comment, "snippet").ExecuteAsync(); Comment response = await SecretsVault.Service.Comments.Insert(comment, "snippet").ExecuteAsync();
reply.Text = ""; reply.Text = "";
grid.RowDefinitions[1].Height = new GridLength(0); replyEditor.Visibility = Visibility.Collapsed;
replies.Children.Add(new CommentCard(response)); if (repliesLoaded)
replies.Children.Insert(0, new CommentCard(response));
else
showReplies_Click(this, null);
showReplies.Visibility = Visibility.Visible;
((showReplies.Content as StackPanel).Children[1] as TextBlock).Text = (++thread.Snippet.TotalReplyCount).ToString();
} }
catch catch (Exception e)
{ {
await new MessageDialog(resources.GetString("/CommentsPage/failedReply")).ShowAsync(); await new MessageDialog(resources.GetString("/CommentsPage/failedReply")).ShowAsync();
Analytics.TrackEvent("Failed to send comment reply", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Top comment ID", item.Id }
});
} }
send.IsEnabled = true; send.IsEnabled = true;
reply.IsEnabled = true; reply.IsEnabled = true;
sending.Visibility = Visibility.Collapsed; processing.Visibility = Visibility.Collapsed;
} }
private void editorClose_Click(object sender, RoutedEventArgs e) private void editorClose_Click(object sender, RoutedEventArgs e)
{ {
((grid.Children[0] as Grid).Children[1] as Grid).RowDefinitions[3].Height = GridLength.Auto;
text.Visibility = Visibility.Visible;
editor.Visibility = Visibility.Collapsed; editor.Visibility = Visibility.Collapsed;
text.Visibility = Visibility.Visible;
toolbar.Visibility = Visibility.Visible;
} }
private async void editorSend_Click(object sender, RoutedEventArgs e) private async void editorSend_Click(object sender, RoutedEventArgs args)
{ {
if (string.IsNullOrWhiteSpace(editorText.Text))
return;
editorText.IsEnabled = false; editorText.IsEnabled = false;
editorSend.IsEnabled = false; editorSend.IsEnabled = false;
editorClose.IsEnabled = false; editorClose.IsEnabled = false;
editorSending.Visibility = Visibility.Visible; deleteComment.IsEnabled = false;
processing.Visibility = Visibility.Visible;
try try
{ {
item.Snippet.TextDisplay = editorText.Text; item.Snippet.TextOriginal = editorText.Text;
item.Snippet.UpdatedAt = DateTime.Now; item.Snippet.UpdatedAt = DateTime.Now;
if (type == CommentType.Reply) if (type == CommentType.Reply)
{
await SecretsVault.Service.Comments.Update(item, "snippet").ExecuteAsync(); await SecretsVault.Service.Comments.Update(item, "snippet").ExecuteAsync();
Initialize(item);
}
else else
{ {
thread.Snippet.TopLevelComment = item; thread.Snippet.TopLevelComment = item;
await SecretsVault.Service.CommentThreads.Update(thread, "snippet").ExecuteAsync(); await SecretsVault.Service.CommentThreads.Update(thread, "snippet").ExecuteAsync();
Initialize(thread);
} }
editorClose_Click(this, null); text.Text = editorText.Text;
meta.Text = $"{Methods.GetAgo(item.Snippet.PublishedAt.Value)} {resources.GetString("/CommentsPage/edited")}";
editor.Visibility = Visibility.Collapsed;
text.Visibility = Visibility.Visible;
toolbar.Visibility = Visibility.Visible;
} }
catch catch (Exception e)
{ {
await new MessageDialog(resources.GetString("/CommentsPage/failedEdit")).ShowAsync(); await new MessageDialog(resources.GetString("/CommentsPage/failedEdit")).ShowAsync();
Analytics.TrackEvent("Failed to edit comment", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message }
});
} }
editorText.IsEnabled = true; editorText.IsEnabled = true;
editorSend.IsEnabled = true; editorSend.IsEnabled = true;
editorClose.IsEnabled = true; editorClose.IsEnabled = true;
editorSending.Visibility = Visibility.Collapsed; deleteComment.IsEnabled = true;
processing.Visibility = Visibility.Collapsed;
} }
private void editorText_TextChanged(object sender, TextChangedEventArgs e) private void editorText_TextChanged(object sender, TextChangedEventArgs e)
{ {
if (editorText.Text.Length == 0) editorSend.IsEnabled = editorText.Text.Length == 0 ? false : true;
editorSend.IsEnabled = false;
else editorSend.IsEnabled = true;
} }
private void editBtn_Click(object sender, RoutedEventArgs e) private void editBtn_Click(object sender, RoutedEventArgs e)
{ {
((grid.Children[0] as Grid).Children[1] as Grid).RowDefinitions[3].Height = new GridLength(0); editor.Visibility = Visibility.Visible;
toolbar.Visibility = Visibility.Collapsed;
text.Visibility = Visibility.Collapsed; text.Visibility = Visibility.Collapsed;
editorText.Text = text.Text; editorText.Text = text.Text;
editor.Visibility = Visibility.Visible;
} }
private async void DeleteComment_Click(object sender, RoutedEventArgs e) private async void DeleteComment_Click(object sender, RoutedEventArgs args)
{ {
MessageDialog dialog = new MessageDialog(resources.GetString("/CommentsPage/deleteContent"), resources.GetString("/CommentsPage/deleteHeader")); MessageDialog dialog = new MessageDialog(resources.GetString("/CommentsPage/deleteContent"), resources.GetString("/CommentsPage/deleteHeader"));
@@ -279,9 +300,20 @@ namespace FoxTube.Controls
try try
{ {
await SecretsVault.Service.Comments.Delete(item.Id).ExecuteAsync(); await SecretsVault.Service.Comments.Delete(item.Id).ExecuteAsync();
Methods.CommentsPage.RemoveComment(this); if (type == CommentType.Reply)
Methods.CommentsPage.RemoveComment(this, item.Snippet.ParentId);
else
Methods.CommentsPage.RemoveComment(this);
}
catch (Exception e)
{
await new MessageDialog(resources.GetString("/CommentsPage/failedDelete")).ShowAsync();
Analytics.TrackEvent("Failed delete comment", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message }
});
} }
catch { }
})); }));
dialog.Commands.Add(new UICommand(resources.GetString("/CommentsPage/no"))); dialog.Commands.Add(new UICommand(resources.GetString("/CommentsPage/no")));
+5 -10
View File
@@ -22,12 +22,7 @@
<TextBlock Name="title" Grid.Column="1" Margin="5" TextWrapping="WrapWholeWords" Text="[Title]" FontSize="20" MaxLines="2"/> <TextBlock Name="title" Grid.Column="1" Margin="5" TextWrapping="WrapWholeWords" Text="[Title]" FontSize="20" MaxLines="2"/>
<TextBlock Margin="5" Name="path" Grid.Column="1" VerticalAlignment="Bottom" Text="C://Users/Michael Gordeev/Downloads/[Title].mp4" Foreground="LightGray" MaxLines="1" TextWrapping="WrapWholeWords"/> <TextBlock Margin="5" Name="path" Grid.Column="1" VerticalAlignment="Bottom" Text="C://Users/Michael Gordeev/Downloads/[Title].mp4" Foreground="LightGray" MaxLines="1" TextWrapping="WrapWholeWords"/>
<StackPanel Grid.Column="2" Margin="5"> <TextBlock Grid.Column="2" Margin="5" Name="meta"/>
<TextBlock Text="Extension:" Foreground="Gray" Name="ext"/>
<TextBlock Text="Quality:" Foreground="Gray" Name="quality"/>
<TextBlock Text="Duration:" Foreground="Gray" Name="duration"/>
<TextBlock Text="Author:" Foreground="Gray" Name="channel"/>
</StackPanel>
<StackPanel Name="donePanel" Grid.Column="3" Orientation="Horizontal" Visibility="Visible"> <StackPanel Name="donePanel" Grid.Column="3" Orientation="Horizontal" Visibility="Visible">
<Button Name="open" Click="open_Click" Width="80" Height="80" Padding="0" Background="Transparent"> <Button Name="open" Click="open_Click" Width="80" Height="80" Padding="0" Background="Transparent">
@@ -44,10 +39,10 @@
</Button> </Button>
</StackPanel> </StackPanel>
<StackPanel Name="progressPanel" Grid.Column="4" Margin="10"> <StackPanel Name="progressPanel" Grid.Column="4" Margin="10">
<TextBlock Name="status" Text="Downloading..." HorizontalAlignment="Left"/> <TextBlock x:Uid="/Downloads/downloading" Name="status" Text="Downloading..." HorizontalAlignment="Left"/>
<ProgressBar Name="progressBar" Width="200" Maximum="1" IsIndeterminate="True" Foreground="Red"/> <ProgressBar Name="progressBar" Width="200" Maximum="1"/>
<TextBlock Name="perc" Text="--%"/> <TextBlock Name="progressText" Text="--%"/>
<Button x:Uid="/Downloads/cancel" Content="Cancel" Name="cancel" Click="CancelPrompt" HorizontalAlignment="Right"/> <Button x:Uid="/Downloads/cancel" Content="Cancel" Name="cancel" Click="Cancel_Click" HorizontalAlignment="Right"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
</UserControl> </UserControl>
+169 -126
View File
@@ -4,7 +4,6 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging; using Windows.UI.Xaml.Media.Imaging;
using Windows.System; using Windows.System;
using FoxTube.Classes;
using YoutubeExplode.Models.MediaStreams; using YoutubeExplode.Models.MediaStreams;
using YoutubeExplode; using YoutubeExplode;
using Windows.Storage; using Windows.Storage;
@@ -16,30 +15,55 @@ using Windows.UI.Notifications;
using Microsoft.Toolkit.Uwp.Notifications; using Microsoft.Toolkit.Uwp.Notifications;
using System.Threading.Tasks; using System.Threading.Tasks;
using Windows.ApplicationModel.Resources; using Windows.ApplicationModel.Resources;
using Microsoft.AppCenter.Analytics;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace FoxTube.Controls namespace FoxTube.Controls
{ {
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; }
public bool IsDownloaded { get; set; }
}
public sealed partial class DownloadItem : UserControl public sealed partial class DownloadItem : UserControl
{ {
ResourceLoader resources = ResourceLoader.GetForCurrentView("Downloads"); ResourceLoader resources = ResourceLoader.GetForCurrentView("Downloads");
public DownloadItemContainer Container { get; private set; } public DownloadItemContainer Container { get; private set; }
public StorageFile file; public StorageFile File { get; private set; }
public bool InProgress { get; set; } = false;
readonly YoutubeClient client = new YoutubeClient(); CancellationTokenSource cts;
readonly CancellationTokenSource cts = new CancellationTokenSource(); Progress<double> progress;
CancellationToken token;
readonly Progress<double> progress = new Progress<double>();
private readonly NotificationData data = new NotificationData();
DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
double percentage; double percentage;
DispatcherTimer timer;
NotificationData data;
public DownloadItem(MediaStreamInfo info, Video meta, string q) public DownloadItem(MediaStreamInfo info, Video meta, string q)
{ {
InitializeComponent(); InitializeComponent();
Download(info, meta, q);
Container = new DownloadItemContainer()
{
Channel = meta.Snippet.ChannelTitle,
Duration = Methods.GetDuration(meta.ContentDetails.Duration),
Extension = info.Container.GetFileExtension(),
Id = meta.Id,
IsDownloaded = false,
Quality = q,
Thumbnail = meta.Snippet.Thumbnails.Medium.Url.ToUri(),
Title = meta.Snippet.Title
};
Download(info);
} }
public DownloadItem(DownloadItemContainer container) public DownloadItem(DownloadItemContainer container)
@@ -47,130 +71,145 @@ namespace FoxTube.Controls
InitializeComponent(); InitializeComponent();
Container = container; Container = container;
Initialize(); Load();
} }
public async void Initialize() async void Load()
{ {
try File = await DownloadAgent.Downloads.TryGetItemAsync(Container.Name) as StorageFile;
{ if (File == null)
file = await DownloadAgent.Downloads.GetFileAsync(Container.Name); DownloadAgent.Remove(this);
}
catch
{
DownloadAgent.Remove(Container.Id);
return;
}
title.Text = Container.Title;
path.Text = file.Name;
thumbnail.Source = new BitmapImage(Container.Thumbnail);
quality.Text = $"{resources.GetString("/Downloads/quality")}: {Container.Quality}";
duration.Text = $"{resources.GetString("/Downloads/duration")}: {Container.Duration}";
channel.Text = $"{resources.GetString("/Downloads/author")}: {Container.Channel}";
ext.Text = $"{resources.GetString("/Downloads/ext")}: {Container.Extension}";
progressPanel.Visibility = Visibility.Collapsed;
donePanel.Visibility = Visibility.Visible; donePanel.Visibility = Visibility.Visible;
progressPanel.Visibility = Visibility.Collapsed;
SetMeta();
} }
async void Download(MediaStreamInfo info, Video meta, string q) async void Download(MediaStreamInfo info)
{ {
try File = await DownloadAgent.Downloads.CreateFileAsync($"{Container.Title.ReplaceInvalidChars('_')}.{Container.Extension}", CreationCollisionOption.GenerateUniqueName);
Container.Name = File.Name;
donePanel.Visibility = Visibility.Collapsed;
progressPanel.Visibility = Visibility.Visible;
SetMeta();
cts = new CancellationTokenSource();
progress = new Progress<double>();
progress.ProgressChanged += (s, e) => percentage = e;
timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
timer.Tick += (s, e) => UpdateInfo();
#region Polling notification
ToastContent toastContent = new ToastContent()
{ {
InProgress = true; Visual = new ToastVisual()
Container = new DownloadItemContainer();
token = cts.Token;
progress.ProgressChanged += (s, e) => percentage = e;
timer.Tick += (s, e) => UpdateInfo(percentage);
file = await DownloadAgent.Downloads.CreateFileAsync($"{meta.Snippet.Title.ReplaceInvalidChars('_')}.{info.Container.GetFileExtension()}", CreationCollisionOption.GenerateUniqueName);
Container.Name = file.Name;
ToastContent toastContent = new ToastContent()
{ {
Visual = new ToastVisual() BindingGeneric = new ToastBindingGeneric()
{ {
BindingGeneric = new ToastBindingGeneric() Children =
{
Children =
{ {
new AdaptiveText() { Text = resources.GetString("/Downloads/toastStartHeader") }, new AdaptiveText() { Text = resources.GetString("/Downloads/toastStartHeader") },
new AdaptiveProgressBar() new AdaptiveProgressBar()
{ {
Title = meta.Snippet.Title, Title = Container.Title,
Status = resources.GetString("/Downloads/downloading/Text"), Status = resources.GetString("/Downloads/downloading/Text"),
Value = new BindableProgressBarValue("value") Value = new BindableProgressBarValue("value")
} }
} }
} }
}, },
Actions = new ToastActionsCustom() Actions = new ToastActionsCustom()
{ {
Buttons = Buttons =
{ {
new ToastButton(resources.GetString("/Downloads/cancel/Content"), $"dcancel|{Container.Id}") new ToastButton(resources.GetString("/Downloads/cancel/Content"), $"dcancel|{Container.Id}")
{
ActivationType = ToastActivationType.Background
}
} }
}, },
Launch = "download", Launch = "download",
ActivationType = ToastActivationType.Foreground ActivationType = ToastActivationType.Foreground
}; };
data.Values["value"] = "0"; data = new NotificationData();
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(toastContent.GetXml()) { Tag = $"download|{meta.Id}", Data = data }); data.Values["value"] = "0";
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(toastContent.GetXml()) { Tag = $"download|{Container.Id}", Data = data });
#endregion
Container.Channel = meta.Snippet.ChannelTitle; timer.Start();
Container.Duration = XmlConvert.ToTimeSpan(meta.ContentDetails.Duration);
Container.Id = meta.Id;
Container.Quality = q;
Container.Thumbnail = meta.Snippet.Thumbnails.Medium.Url.ToUri();
Container.Title = meta.Snippet.Title;
Container.Extension = info.Container.GetFileExtension();
thumbnail.Source = new BitmapImage(new Uri(meta.Snippet.Thumbnails.Medium.Url)); try
title.Text = meta.Snippet.Title; {
ext.Text = $"{resources.GetString("/Downloads/ext")}: {info.Container.GetFileExtension()}"; await new YoutubeClient().DownloadMediaStreamAsync(info, await File.OpenStreamForWriteAsync(), progress, cts.Token);
quality.Text = $"{resources.GetString("/Downloads/quality")}: {q}";
duration.Text = $"{resources.GetString("/Downloads/duration")}: {XmlConvert.ToTimeSpan(meta.ContentDetails.Duration)}";
channel.Text = $"{resources.GetString("/Downloads/author")}: {meta.Snippet.ChannelTitle}";
path.Text = file.Name;
progressPanel.Visibility = Visibility.Visible; Container.IsDownloaded = true;
donePanel.Visibility = Visibility.Collapsed; timer.Stop();
timer.Start(); if (cts.IsCancellationRequested)
throw new TaskCanceledException();
await client.DownloadMediaStreamAsync(info, await file.OpenStreamForWriteAsync(), progress, token); else
progressPanel.Visibility = Visibility.Collapsed;
donePanel.Visibility = Visibility.Visible;
InProgress = false;
if (!cts.IsCancellationRequested)
DownloadCompleted(); DownloadCompleted();
} }
catch (TaskCanceledException) { } catch (TaskCanceledException)
{
Windows.Data.Xml.Dom.XmlDocument template = new Windows.Data.Xml.Dom.XmlDocument();
template.LoadXml($@"<toast>
<visual>
<binding template='ToastGeneric'>
<text>{resources.GetString("/Downloads/toastCanceledHeader")}</text>
<text>{Container.Title}</text>
</binding>
</visual>
</toast>");
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(template) { Tag = $"download|{Container.Id}" });
try { await File.DeleteAsync(StorageDeleteOption.PermanentDelete); }
catch { }
DownloadAgent.Remove(this);
}
catch (Exception e)
{
Windows.Data.Xml.Dom.XmlDocument template = new Windows.Data.Xml.Dom.XmlDocument();
template.LoadXml($@"<toast>
<visual>
<binding template='ToastGeneric'>
<text>{resources.GetString("/Downloads/failedHead")}</text>
<text>{resources.GetString("/Downloads/failedBody")}</text>
</binding>
</visual>
</toast>");
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(template) { Tag = $"download|{Container.Id}" });
Analytics.TrackEvent("Failed to download video", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Container", JsonConvert.SerializeObject(Container) }
});
try { await File.DeleteAsync(StorageDeleteOption.PermanentDelete); }
catch { }
DownloadAgent.Remove(this);
}
} }
private void UpdateInfo(double e) private void UpdateInfo()
{ {
progressBar.Value = e; progressBar.Value = percentage;
perc.Text = $"{(int)e * 100}%"; progressText.Text = Math.Round(percentage * 100, 1) + "%";
data.Values["value"] = e.ToString(); data.Values["value"] = percentage.ToString();
ToastNotificationManager.CreateToastNotifier().Update(data, $"download|{Container.Id}"); ToastNotificationManager.CreateToastNotifier().Update(data, $"download|{Container.Id}");
} }
private void DownloadCompleted() private void DownloadCompleted()
{ {
Windows.Data.Xml.Dom.XmlDocument template = new Windows.Data.Xml.Dom.XmlDocument(); Windows.Data.Xml.Dom.XmlDocument template = new Windows.Data.Xml.Dom.XmlDocument();
template.LoadXml($@"<toast activationType='background' launch='download|{file.Path}'> template.LoadXml($@"<toast activationType='foreground' launch='download'>
<visual> <visual>
<binding template='ToastGeneric'> <binding template='ToastGeneric'>
<text>{resources.GetString("/Downloads/toastCompleteHeader")}</text> <text>{resources.GetString("/Downloads/toastCompleteHeader")}</text>
@@ -185,11 +224,14 @@ namespace FoxTube.Controls
</actions> </actions>
</toast>"); </toast>");
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(template) { Tag = $"download|{Container.Id}" }); ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(template) { Tag = $"download|{Container.Id}" });
donePanel.Visibility = Visibility.Visible;
progressPanel.Visibility = Visibility.Collapsed;
} }
private async void open_Click(object sender, RoutedEventArgs e) private async void open_Click(object sender, RoutedEventArgs e)
{ {
await Launcher.LaunchFileAsync(file); await Launcher.LaunchFileAsync(File);
} }
private void gotoOriginal_Click(object sender, RoutedEventArgs e) private void gotoOriginal_Click(object sender, RoutedEventArgs e)
@@ -197,44 +239,45 @@ namespace FoxTube.Controls
Methods.MainPage.GoToVideo(Container.Id); Methods.MainPage.GoToVideo(Container.Id);
} }
public async void Cancel() public async void Cancel(bool prompt = true)
{ {
status.Text = resources.GetString("/Downloads/cancelling");
progressBar.IsIndeterminate = true;
cancel.IsEnabled = false; cancel.IsEnabled = false;
cts.Cancel(); if(prompt)
try
{
await file.DeleteAsync();
}
catch { }
Windows.Data.Xml.Dom.XmlDocument template = new Windows.Data.Xml.Dom.XmlDocument();
template.LoadXml($@"<toast activationType='foreground' launch='download'>
<visual>
<binding template='ToastGeneric'>
<text>{resources.GetString("/Downloads/toastCanceledHeader")}</text>
<text>{Container.Title}</text>
</binding>
</visual>
</toast>");
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(template) { Tag = $"download|{Container.Id}" });
DownloadAgent.Remove(Container.Id);
}
public async void CancelPrompt(object sender = null, RoutedEventArgs e = null)
{
if(InProgress)
{ {
MessageDialog dialog = new MessageDialog(resources.GetString("/Downloads/prompt"), resources.GetString("/Downloads/cancellingHeader")); MessageDialog dialog = new MessageDialog(resources.GetString("/Downloads/prompt"), resources.GetString("/Downloads/cancellingHeader"));
dialog.Commands.Add(new UICommand(resources.GetString("/Downloads/yes"), (command) => Cancel())); dialog.Commands.Add(new UICommand(resources.GetString("/Downloads/yes"), null, false));
dialog.Commands.Add(new UICommand(resources.GetString("/Downloads/no"))); dialog.Commands.Add(new UICommand(resources.GetString("/Downloads/no"), null, true));
dialog.DefaultCommandIndex = 1; dialog.DefaultCommandIndex = 1;
await dialog.ShowAsync(); if ((bool)(await dialog.ShowAsync()).Id)
{
cancel.IsEnabled = true;
return;
}
} }
cts.Cancel();
status.Text = resources.GetString("/Downloads/cancelling");
progressBar.IsIndeterminate = true;
}
void SetMeta()
{
thumbnail.Source = new BitmapImage(Container.Thumbnail) { DecodePixelHeight = (int)thumbnail.ActualHeight, DecodePixelWidth = (int)thumbnail.ActualWidth };
title.Text = Container.Title;
path.Text = File.Path;
meta.Text = $@"{resources.GetString("/Downloads/ext")}: {Container.Extension}
{resources.GetString("/Downloads/quality")}: {Container.Quality}
{resources.GetString("/Downloads/duration")}: {Container.Duration}
{resources.GetString("/Downloads/author")}: {Container.Channel}";
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
Cancel();
} }
} }
} }
+4 -2
View File
@@ -2,7 +2,6 @@
x:Class="FoxTube.Controls.LiveCaptions" x:Class="FoxTube.Controls.LiveCaptions"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FoxTube.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" mc:Ignorable="d"
@@ -10,7 +9,10 @@
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
Margin="0,55"> Margin="0,55">
<Grid Background="#99000000"> <Grid>
<Grid.Background>
<AcrylicBrush TintColor="#BF000000" TintOpacity="0.75"/>
</Grid.Background>
<TextBlock Text="Hello, World!" Name="text" Margin="5" FontSize="24"/> <TextBlock Text="Hello, World!" Name="text" Margin="5" FontSize="24"/>
</Grid> </Grid>
</UserControl> </UserControl>
+7 -40
View File
@@ -1,7 +1,6 @@
using System; using System;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Windows.Media;
using YoutubeExplode.Models.ClosedCaptions; using YoutubeExplode.Models.ClosedCaptions;
namespace FoxTube.Controls namespace FoxTube.Controls
@@ -17,8 +16,8 @@ namespace FoxTube.Controls
set => text.FontSize = value; set => text.FontSize = value;
} }
public MediaTimelineController Player { get; set; } public MediaElement Player { get; set; }
private bool isClosed = false;
DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(10) }; DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(10) };
ClosedCaption currentCaption = null; ClosedCaption currentCaption = null;
@@ -32,41 +31,19 @@ namespace FoxTube.Controls
private void UpdateCaption(object sender, object e) private void UpdateCaption(object sender, object e)
{ {
TimeSpan currentPosition = Player.Position; currentCaption = track.Captions.Find(i => i.Offset <= Player.Position && i.Offset + i.Duration > Player.Position);
bool found = false; if (currentCaption != null)
if(!isClosed) text.Text = currentCaption.Text;
track.Captions.ForEach(i =>
{
if (Player.Position >= i.Offset && Player.Position <= i.Offset + i.Duration)
{
currentCaption = i;
text.Text = currentCaption.Text;
Visibility = Visibility.Visible;
found = true;
}
});
if (!found) Visibility = currentCaption == null ? Visibility.Collapsed : Visibility.Visible;
{
currentCaption = null;
Visibility = Visibility.Collapsed;
}
} }
public async void Initialize(ClosedCaptionTrackInfo info) public async void Initialize(ClosedCaptionTrackInfo info)
{ {
track = await new YoutubeExplode.YoutubeClient().GetClosedCaptionTrackAsync(info); track = await new YoutubeExplode.YoutubeClient().GetClosedCaptionTrackAsync(info);
track.Captions.ForEach(i => UpdateCaption(this, null);
{
if (Player.Position > i.Offset && Player.Position < i.Offset + i.Duration)
{
currentCaption = i;
text.Text = currentCaption.Text;
Visibility = Visibility.Visible;
}
});
timer.Start(); timer.Start();
} }
@@ -78,15 +55,5 @@ namespace FoxTube.Controls
Visibility = Visibility.Collapsed; Visibility = Visibility.Collapsed;
timer.Stop(); timer.Stop();
} }
public void Hide()
{
isClosed = true;
}
public void Show()
{
isClosed = false;
}
} }
} }
+340
View File
@@ -0,0 +1,340 @@
using FoxTube.Controls;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Windows.ApplicationModel.Resources;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using YoutubeExplode.Models.ClosedCaptions;
using YoutubeExplode.Models.MediaStreams;
namespace FoxTube
{
public delegate void QualityChangedEventHandler(object sender, MediaStreamInfo requestedQuality, MediaStreamInfoSet list);
public delegate void MinimodeChangedEventHandler(object sender, bool isOn);
/// <summary>
/// Custom controls for media player. MARKUP IS IN **Themes/Generic.xaml**!!!
/// </summary>
public sealed class PlayerControls : MediaTransportControls
{
public event RoutedEventHandler CloseRequested;
public event RoutedEventHandler LiveRequested;
public event MinimodeChangedEventHandler MiniModeChanged;
public event RoutedEventHandler NextRequested;
public event QualityChangedEventHandler QualityChanged;
public MediaElement Player;
public IReadOnlyList<ClosedCaptionTrackInfo> ClosedCaptions { get; set; }
public MediaStreamInfoSet MediaStreams { get; set; }
Queue<Action> queue = new Queue<Action>();
bool isReady = false;
public PlayerControls()
{
DefaultStyleKey = typeof(PlayerControls);
}
protected override void OnApplyTemplate()
{
isReady = true;
(GetTemplateChild("close") as Button).Click += Close_Click;
(GetTemplateChild("compactClose") as Button).Click += Close_Click;
(GetTemplateChild("minimize") as Button).Click += Minimize_Click;
(GetTemplateChild("maximize") as Button).Click += Minimize_Click;
(GetTemplateChild("CompactOverlayButton") as Button).Click += CompactOverlay_Click;
(GetTemplateChild("qualitySelector") as ComboBox).SelectionChanged += QualitySelector_SelectionChanged;
(GetTemplateChild("ccSwitch") as ToggleSwitch).Toggled += CcSwitch_Toggled;
(GetTemplateChild("ccSelector") as ComboBox).SelectionChanged += CcSelector_SelectionChanged;
(GetTemplateChild("next") as Button).Click += (s, e) => NextRequested.Invoke(s, e);
(GetTemplateChild("AudioMuteButton") as Button).Click += Mute_Click;
(GetTemplateChild("VolumeSlider") as Slider).ValueChanged += Volume_ValueChanged;
(GetTemplateChild("ProgressSlider") as Slider).ValueChanged += ProgressSlider_ValueChanged;
(GetTemplateChild("goLive") as Button).Click += (s, e) => LiveRequested.Invoke(s, e);
if (queue.Count > 0)
foreach (Action i in queue)
i();
base.OnApplyTemplate();
}
private void Minimize_Click(object sender, RoutedEventArgs e)
{
if (sender == (GetTemplateChild("minimize") as Button))
{
MiniModeChanged.Invoke(this, true);
SetMinimized();
}
else
{
MiniModeChanged.Invoke(this, false);
SetNormal();
}
}
public void Minimize()
{
Minimize_Click(GetTemplateChild("minimize"), null);
}
private void ProgressSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
(GetTemplateChild("compactSeek") as ProgressBar).Value = e.NewValue;
}
private void Mute_Click(object sender, RoutedEventArgs e)
{
if (((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph == "\xE74F")
Volume_ValueChanged(this, null);
else
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE74F";
}
private void Volume_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
double v = (GetTemplateChild("VolumeSlider") as Slider).Value;
if (v == 0)
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE74F";
else if (v <= 25 && v > 0)
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE992";
else if (v <= 50 && v > 25)
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE993";
else if (v <= 75 && v > 50)
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE994";
else if (v > 75)
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE995";
}
private void CcSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
CcSwitch_Toggled((GetTemplateChild("ccSwitch") as ToggleSwitch), null);
}
private void CcSwitch_Toggled(object sender, RoutedEventArgs e)
{
if((GetTemplateChild("captions") as LiveCaptions).Player == null)
(GetTemplateChild("captions") as LiveCaptions).Player = Player;
if ((sender as ToggleSwitch).IsOn)
(GetTemplateChild("captions") as LiveCaptions).Initialize(((GetTemplateChild("ccSelector") as ComboBox).SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
else
(GetTemplateChild("captions") as LiveCaptions).Close();
}
private void QualitySelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SettingsStorage.RememberedQuality = e.AddedItems[0] as string;
MediaStreamInfo item = MediaStreams.Muxed.Find(i => i.VideoQualityLabel == e.AddedItems[0] as string);
if (item == null)
item = MediaStreams.Video.Find(i => i.VideoQualityLabel == e.AddedItems[0] as string);
QualityChanged?.Invoke(sender, item, MediaStreams);
}
private void CompactOverlay_Click(object sender, RoutedEventArgs e)
{
if((sender as Button).Margin.Top > 0)
{
Button btnCompact = sender as Button;
(GetTemplateChild("center") as Grid).Children.Remove(btnCompact);
(GetTemplateChild("headerToolbar") as StackPanel).Children.Add(btnCompact);
btnCompact.Margin = new Thickness(0);
(btnCompact.Content as FontIcon).Glyph = "\xE2B3";
SetNormal();
}
else
SetCompactView();
}
private void Close_Click(object sender, RoutedEventArgs e)
{
CloseRequested?.Invoke(sender, e);
}
public void SetCompactView()
{
Button btn = GetTemplateChild("CompactOverlayButton") as Button;
(GetTemplateChild("headerToolbar") as StackPanel).Children.Remove(btn);
(GetTemplateChild("center") as Grid).Children.Add(btn);
btn.Margin = new Thickness(0, 32, 0, 0);
(btn.Content as FontIcon).Glyph = "\xE2B4";
Button btnPlay = GetTemplateChild("PlayPauseButton") as Button;
(GetTemplateChild("leftStack") as StackPanel).Children.Remove(btnPlay);
(GetTemplateChild("centralStack") as StackPanel).Children.Add(btnPlay);
Button btnFwd = GetTemplateChild("SkipForwardButton") as Button;
(GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnFwd);
(GetTemplateChild("centralStack") as StackPanel).Children.Insert(1, btnFwd);
Button btnBwd = GetTemplateChild("SkipBackwardButton") as Button;
(GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnBwd);
(GetTemplateChild("centralStack") as StackPanel).Children.Insert(0, btnBwd);
(GetTemplateChild("header") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("centerBgr") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("center") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("footer") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("maximize") as Button).Visibility = Visibility.Collapsed;
(GetTemplateChild("compactClose") as Button).Visibility = Visibility.Collapsed;
(GetTemplateChild("dragholder") as Button).Visibility = Visibility.Visible;
(GetTemplateChild("captions") as LiveCaptions).Size = 15;
}
public void SetMinimized()
{
Button btnPlay = GetTemplateChild("PlayPauseButton") as Button;
(GetTemplateChild("leftStack") as StackPanel).Children.Remove(btnPlay);
(GetTemplateChild("centralStack") as StackPanel).Children.Add(btnPlay);
Button btnFwd = GetTemplateChild("SkipForwardButton") as Button;
(GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnFwd);
(GetTemplateChild("centralStack") as StackPanel).Children.Insert(1, btnFwd);
Button btnBwd = GetTemplateChild("SkipBackwardButton") as Button;
(GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnBwd);
(GetTemplateChild("centralStack") as StackPanel).Children.Insert(0, btnBwd);
(GetTemplateChild("header") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("centerBgr") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("center") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("footer") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("compactClose") as Button).Visibility = Visibility.Visible;
(GetTemplateChild("maximize") as Button).Visibility = Visibility.Visible;
(GetTemplateChild("captions") as LiveCaptions).Size = 15;
}
public void SetNormal()
{
Button btnPlay = GetTemplateChild("PlayPauseButton") as Button;
(GetTemplateChild("centralStack") as StackPanel).Children.Remove(btnPlay);
(GetTemplateChild("leftStack") as StackPanel).Children.Insert(0, btnPlay);
Button btnFwd = GetTemplateChild("SkipForwardButton") as Button;
(GetTemplateChild("centralStack") as StackPanel).Children.Remove(btnFwd);
(GetTemplateChild("rightStack") as StackPanel).Children.Insert(0, btnFwd);
Button btnBwd = GetTemplateChild("SkipBackwardButton") as Button;
(GetTemplateChild("centralStack") as StackPanel).Children.Remove(btnBwd);
(GetTemplateChild("rightStack") as StackPanel).Children.Insert(0, btnBwd);
(GetTemplateChild("header") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("centerBgr") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("center") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("footer") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("captions") as LiveCaptions).Size = 24;
(GetTemplateChild("dragholder") as Button).Visibility = Visibility.Collapsed;
}
public void SetMeta(string title, string channel)
{
if (!isReady)
{
queue.Enqueue(() => SetMeta(title, channel));
return;
}
(GetTemplateChild("title") as TextBlock).Text = title;
(GetTemplateChild("author") as TextBlock).Text = channel;
}
public void SetCaptions(IReadOnlyList<ClosedCaptionTrackInfo> list)
{
if (!isReady)
{
queue.Enqueue(() => SetCaptions(list));
return;
}
ClosedCaptions = list;
if (list.Count > 0)
{
foreach(ClosedCaptionTrackInfo i in list)
(GetTemplateChild("ccSelector") as ComboBox).Items.Add(new ComboBoxItem()
{
Content = string.Format("{0} {1}", CultureInfo.GetCultureInfo(i.Language.Code).DisplayName, i.IsAutoGenerated ? ResourceLoader.GetForCurrentView("VideoPage").GetString("/VideoPage/generatedCaption") : ""),
Tag = i
});
ClosedCaptionTrackInfo item = list.Find(i => SettingsStorage.RelevanceLanguage.Contains(i.Language.Code)) ?? list.Find(i => "en-US".Contains(i.Language.Code));
if (item == null)
item = list.First();
(GetTemplateChild("ccSelector") as ComboBox).SelectedItem = (GetTemplateChild("ccSelector") as ComboBox).Items.Find(i => (i as ComboBoxItem).Tag == item);
}
else
(GetTemplateChild("cc") as Button).Visibility = Visibility.Collapsed;
}
public void SetQualities(MediaStreamInfoSet list)
{
if (!isReady)
{
queue.Enqueue(() => SetQualities(list));
return;
}
MediaStreams = list;
List<VideoQuality> q = list.GetAllVideoQualities().ToList();
q.Sort();
q.Reverse();
List<string> labels = new List<string>();
q.ForEach(i => labels.Add(i.GetVideoQualityLabel()));
(GetTemplateChild("qualitySelector") as ComboBox).ItemsSource = labels;
string s = SettingsStorage.VideoQuality == "remember" ? SettingsStorage.RememberedQuality : SettingsStorage.VideoQuality;
if (labels.Contains(s))
(GetTemplateChild("qualitySelector") as ComboBox).SelectedItem = s;
else
(GetTemplateChild("qualitySelector") as ComboBox).SelectedIndex = 0;
}
public void SetStream(string url)
{
if (!isReady)
{
queue.Enqueue(() => SetStream(url));
return;
}
(GetTemplateChild("goLive") as Button).Visibility = Visibility.Visible;
(GetTemplateChild("sliderPan") as Grid).Children.Remove(GetTemplateChild("TimeElapsedElement") as TextBlock);
(GetTemplateChild("rightStack") as StackPanel).Children.Insert(0, GetTemplateChild("TimeElapsedElement") as TextBlock);
(GetTemplateChild("TimeElapsedElement") as TextBlock).VerticalAlignment = VerticalAlignment.Center;
(GetTemplateChild("TimeElapsedElement") as TextBlock).FontSize = 18;
(GetTemplateChild("TimeElapsedElement") as TextBlock).Margin = new Thickness(10, 0, 10, 0);
(GetTemplateChild("sliderPan") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("cc") as Button).Visibility = Visibility.Collapsed;
(GetTemplateChild("quality") as Button).Visibility = Visibility.Collapsed;
Player.Source = url.ToUri();
}
}
}
+26
View File
@@ -0,0 +1,26 @@
<UserControl
x:Class="FoxTube.VideoPlayer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:controls1="using:FoxTube.Controls"
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
xmlns:foxtube="using:FoxTube"
mc:Ignorable="d"
d:DesignHeight="1080"
d:DesignWidth="1920"
RequestedTheme="Dark">
<Grid Background="White" Name="grid">
<MediaElement Name="videoSource" AreTransportControlsEnabled="True" PosterSource="ms-appx:///Assets/videoThumbSample.png" CurrentStateChanged="VideoSource_CurrentStateChanged" MediaOpened="VideoSource_MediaOpened" VolumeChanged="VideoSource_VolumeChanged">
<MediaElement.TransportControls>
<foxtube:PlayerControls IsCompactOverlayButtonVisible="True" IsCompactOverlayEnabled="True"
IsFullWindowButtonVisible="True" IsFullWindowEnabled="True"
IsSkipBackwardButtonVisible="True" IsSkipBackwardEnabled="True"
IsSkipForwardButtonVisible="True" IsSkipForwardEnabled="True"/>
</MediaElement.TransportControls>
</MediaElement>
<MediaElement Name="audioSource" Width="0" AreTransportControlsEnabled="False" Visibility="Collapsed" Height="0" VerticalAlignment="Top" HorizontalAlignment="Left" CurrentStateChanged="AudioSource_CurrentStateChanged" MediaOpened="AudioSource_MediaOpened"/>
</Grid>
</UserControl>
+234
View File
@@ -0,0 +1,234 @@
using System;
using System.Linq;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Google.Apis.YouTube.v3.Data;
using Windows.UI.Xaml.Media.Imaging;
using Windows.Media;
using Windows.Storage.Streams;
using YoutubeExplode.Models.MediaStreams;
using YoutubeExplode;
using System.Diagnostics;
namespace FoxTube
{
public sealed partial class VideoPlayer : UserControl
{
public Video item;
public string avatar;
public event Event NextClicked;
public event ObjectEventHandler MiniMode;
public PlayerControls Controls => videoSource.TransportControls as PlayerControls;
public TimeSpan Position => videoSource.Position;
bool audioLoaded = false;
bool videoLoaded = false;
bool isMuxed = false;
DispatcherTimer muxedTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
TimeSpan timecodeBackup;
bool needUpdateTimecode = false;
SystemMediaTransportControls systemControls;
public VideoPlayer()
{
InitializeComponent();
}
public async void Initialize(Video meta, string channelAvatar, bool privateMode = false)
{
item = meta;
avatar = channelAvatar;
videoSource.PosterSource = new BitmapImage((meta.Snippet.Thumbnails.Maxres ?? meta.Snippet.Thumbnails.Medium).Url.ToUri());
Controls.SetMeta(meta.Snippet.Title, meta.Snippet.ChannelTitle);
if (item.Snippet.LiveBroadcastContent == "none")
{
InitializeContols();
Controls.SetQualities(await new YoutubeClient().GetVideoMediaStreamInfosAsync(item.Id));
Controls.SetCaptions(await new YoutubeClient().GetVideoClosedCaptionTrackInfosAsync(item.Id));
}
else if (item.Snippet.LiveBroadcastContent == "live")
{
InitializeContols();
Controls.IsSkipBackwardButtonVisible = false;
Controls.IsSkipForwardButtonVisible = false;
Controls.LiveRequested += Controls_LiveRequested;
Controls.SetStream((await new YoutubeClient().GetVideoMediaStreamInfosAsync(item.Id)).HlsLiveStreamUrl);
}
else
videoSource.AreTransportControlsEnabled = false;
if (!privateMode)
Debug.WriteLine("TODO: history entry creation");
// TODO: Create history entry
Visibility = Visibility.Visible;
}
private void Controls_LiveRequested(object sender, RoutedEventArgs e)
{
videoSource.Position = videoSource.NaturalDuration.TimeSpan;
}
public void InitializeContols()
{
videoSource.Volume = SettingsStorage.Volume;
muxedTimer.Tick += (s, e) =>
{
if (!Enumerable.Range(-100, 100).Contains((int)(videoSource.Position - audioSource.Position).TotalMilliseconds))
audioSource.Position = videoSource.Position;
};
Controls.CloseRequested += Controls_CloseRequested;
Controls.NextRequested += (s, e) => NextClicked?.Invoke();
Controls.QualityChanged += Controls_QualityChanged;
Controls.MiniModeChanged += Controls_MiniModeChanged;
Controls.Player = videoSource;
#region System Media Transport Controls
systemControls = SystemMediaTransportControls.GetForCurrentView();
systemControls.IsNextEnabled = true;
systemControls.IsPauseEnabled = true;
systemControls.IsPlayEnabled = true;
systemControls.DisplayUpdater.Type = MediaPlaybackType.Video;
systemControls.DisplayUpdater.VideoProperties.Title = item.Snippet.Title;
systemControls.DisplayUpdater.VideoProperties.Subtitle = item.Snippet.ChannelTitle;
systemControls.DisplayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromUri(avatar.ToUri());
systemControls.DisplayUpdater.Update();
systemControls.ButtonPressed += SystemControls_Engaged;
systemControls.IsEnabled = true;
#endregion
}
public void Controls_MiniModeChanged(object sender, bool e)
{
videoSource.IsFullWindow = false;
MiniMode?.Invoke(this, e);
}
public void Minimize()
{
Controls.Minimize();
}
private void Controls_QualityChanged(object sender, MediaStreamInfo requestedQuality, MediaStreamInfoSet list)
{
videoSource.Pause();
timecodeBackup = videoSource.Position;
needUpdateTimecode = true;
audioLoaded = false;
videoLoaded = false;
muxedTimer.Stop();
videoSource.Source = requestedQuality.Url.ToUri();
if(requestedQuality is MuxedStreamInfo)
{
isMuxed = true;
audioSource.Source = null;
}
else
audioSource.Source = list.Audio.First().Url.ToUri();
}
public void Controls_CloseRequested(object sender, RoutedEventArgs e)
{
if(systemControls != null)
systemControls.IsEnabled = false;
videoSource.Stop();
audioSource.Stop();
Methods.MainPage.CloseVideo();
}
private async void SystemControls_Engaged(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
if (args.Button == SystemMediaTransportControlsButton.Next)
NextClicked?.Invoke();
switch (args.Button)
{
case SystemMediaTransportControlsButton.Play:
videoSource.Play();
break;
case SystemMediaTransportControlsButton.Pause:
videoSource.Pause();
break;
case SystemMediaTransportControlsButton.Next:
NextClicked.Invoke();
break;
}
});
}
public void Pause()
{
videoSource.Pause();
}
private void VideoSource_CurrentStateChanged(object sender, RoutedEventArgs e)
{
switch(videoSource.CurrentState)
{
case Windows.UI.Xaml.Media.MediaElementState.Buffering:
case Windows.UI.Xaml.Media.MediaElementState.Paused:
systemControls.PlaybackStatus = MediaPlaybackStatus.Paused;
// TODO: Create history entry
audioSource?.Pause();
break;
case Windows.UI.Xaml.Media.MediaElementState.Playing:
systemControls.PlaybackStatus = MediaPlaybackStatus.Playing;
audioSource?.Play();
break;
}
}
private void AudioSource_CurrentStateChanged(object sender, RoutedEventArgs e)
{
if(audioSource.CurrentState == Windows.UI.Xaml.Media.MediaElementState.Playing)
muxedTimer.Start();
else
muxedTimer.Stop();
}
private void VideoSource_MediaOpened(object sender, RoutedEventArgs e)
{
videoLoaded = true;
if (needUpdateTimecode)
{
videoSource.Position = timecodeBackup;
needUpdateTimecode = false;
}
if (audioLoaded || isMuxed)
videoSource.Play();
}
private void AudioSource_MediaOpened(object sender, RoutedEventArgs e)
{
audioLoaded = true;
if (needUpdateTimecode)
audioSource.Position = timecodeBackup;
if (videoLoaded)
videoSource.Play();
}
private void VideoSource_VolumeChanged(object sender, RoutedEventArgs e)
{
audioSource.Volume = videoSource.Volume;
SettingsStorage.Volume = videoSource.Volume;
}
}
}
+32 -24
View File
@@ -2,23 +2,35 @@
x:Class="FoxTube.Controls.PlaylistCard" x:Class="FoxTube.Controls.PlaylistCard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FoxTube.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 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" mc:Ignorable="d"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Top" VerticalAlignment="Top"
SizeChanged="UserControl_SizeChanged"
d:DesignHeight="290" d:DesignHeight="290"
d:DesignWidth="384"> d:DesignWidth="384"
Opacity="0">
<Button Padding="0" Background="Transparent" Click="Button_Click"> <Windows10version1809:Page.OpacityTransition>
<Grid Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}"> <ScalarTransition Duration="0:0:0.5"/>
</Windows10version1809:Page.OpacityTransition>
<Button Padding="0" Margin="1" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" Click="Button_Click">
<Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*"/> <RowDefinition/>
<RowDefinition Height="75"/> <RowDefinition Height="20"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Name="thumbnail" Source="/Assets/videoThumbSample.png" Stretch="Fill"/>
<Image Source="/Assets/videoPlaceholder.png" Opacity=".0001" Stretch="Fill"/>
<Image Name="thumbnail" Source="/Assets/videoThumbSample.png" Stretch="Fill" Opacity="0" ImageOpened="Thumbnail_ImageOpened">
<Windows10version1809:Image.OpacityTransition>
<ScalarTransition Duration="0:0:0.5"/>
</Windows10version1809:Image.OpacityTransition>
</Image>
<Grid HorizontalAlignment="Right" Width="100"> <Grid HorizontalAlignment="Right" Width="100">
<Grid.Background> <Grid.Background>
<AcrylicBrush TintColor="#7F000000" BackgroundSource="Backdrop" AlwaysUseFallback="False" TintOpacity="1" Opacity="0.97"/> <AcrylicBrush TintColor="#7F000000" BackgroundSource="Backdrop" AlwaysUseFallback="False" TintOpacity="1" Opacity="0.97"/>
@@ -28,24 +40,20 @@
<TextBlock Foreground="White" Text="[N/A]" HorizontalAlignment="Center" FontSize="20" Name="counter"/> <TextBlock Foreground="White" Text="[N/A]" HorizontalAlignment="Center" FontSize="20" Name="counter"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Height="50" Width="50" Margin="5,-30,5,10" Fill="{ThemeResource SystemControlBackgroundChromeMediumBrush}"/>
<PersonPicture Name="avatar" Grid.Column="0" Height="46" Margin="5,-30,5,0" BorderBrush="White" BorderThickness="10"/>
<TextBlock HorizontalAlignment="Left" MaxWidth="200" TextTrimming="CharacterEllipsis" Name="channelName" Grid.Column="1" Text="[Channel name]" Foreground="Gray" Margin="0,2,0,0" FontSize="12"/> <Grid Grid.Row="1">
<TextBlock Grid.Column="1" Name="date" Text="[Published at]" HorizontalAlignment="Right" Foreground="Gray" Margin="0,2,2,0" FontSize="12"/> <Grid.ColumnDefinitions>
</Grid> <ColumnDefinition Width="60"/>
<TextBlock Grid.Row="1" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" TextTrimming="CharacterEllipsis" Margin="5" MaxLines="2"/> <ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Height="50" Width="50" Margin="5,-30,5,10" Fill="{ThemeResource SystemControlBackgroundChromeMediumBrush}"/>
<PersonPicture Name="avatar" Grid.Column="0" Height="46" Margin="5,-30,5,0" BorderBrush="White" BorderThickness="10"/>
<TextBlock HorizontalAlignment="Left" MaxWidth="200" TextTrimming="CharacterEllipsis" Name="channelName" Grid.Column="1" Text="[Channel name]" Foreground="Gray" Margin="0,2,0,0" FontSize="12"/>
<TextBlock Grid.Column="1" Name="date" Text="[Published at]" HorizontalAlignment="Right" Foreground="Gray" Margin="0,2,2,0" FontSize="12"/>
</Grid> </Grid>
<TextBlock Grid.Row="2" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" TextTrimming="CharacterEllipsis" Margin="5" MaxLines="2"/>
</Grid> </Grid>
</Button> </Button>
<UserControl.ContextFlyout> <UserControl.ContextFlyout>
+40 -24
View File
@@ -1,6 +1,8 @@
using Google.Apis.YouTube.v3; using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data; using Google.Apis.YouTube.v3.Data;
using Microsoft.AppCenter.Analytics;
using System; using System;
using System.Collections.Generic;
using Windows.ApplicationModel.DataTransfer; using Windows.ApplicationModel.DataTransfer;
using Windows.System; using Windows.System;
using Windows.UI.Xaml; using Windows.UI.Xaml;
@@ -17,6 +19,8 @@ namespace FoxTube.Controls
Playlist item; Playlist item;
public string playlistId; public string playlistId;
public bool NeedInitialize { get; set; } = true;
public PlaylistCard(string id) public PlaylistCard(string id)
{ {
InitializeComponent(); InitializeComponent();
@@ -25,34 +29,41 @@ namespace FoxTube.Controls
public async void Initialize(string id) 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 try
{ {
thumbnail.Source = new BitmapImage((item.Snippet.Thumbnails.Maxres ?? item.Snippet.Thumbnails.Medium).Url.ToUri()); playlistId = id;
avatar.ProfilePicture = new BitmapImage(new Uri((await r.ExecuteAsync()).Items[0].Snippet.Thumbnails.Medium.Url)); PlaylistsResource.ListRequest request = SecretsVault.Service.Playlists.List("snippet,contentDetails");
} catch { } 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<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Playlist ID", playlistId }
});
}
} }
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e) public void Button_Click(object sender, RoutedEventArgs e)
{
Height = e.NewSize.Width * 0.75;
}
private void Button_Click(object sender, RoutedEventArgs e)
{ {
Methods.MainPage.GoToPlaylist(item.Id); Methods.MainPage.GoToPlaylist(item.Id);
} }
@@ -73,5 +84,10 @@ namespace FoxTube.Controls
{ {
await Launcher.LaunchUriAsync($"https://www.youtube.com/playlist?list={playlistId}".ToUri()); await Launcher.LaunchUriAsync($"https://www.youtube.com/playlist?list={playlistId}".ToUri());
} }
private void Thumbnail_ImageOpened(object sender, RoutedEventArgs e)
{
thumbnail.Opacity = 1;
}
} }
} }
-1
View File
@@ -2,7 +2,6 @@
x:Class="FoxTube.Controls.ShowMore" x:Class="FoxTube.Controls.ShowMore"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FoxTube.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"> mc:Ignorable="d">
+15 -19
View File
@@ -1,19 +1,5 @@
using System; using Windows.UI.Xaml;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
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 namespace FoxTube.Controls
{ {
@@ -22,7 +8,7 @@ namespace FoxTube.Controls
public event Event Clicked; public event Event Clicked;
public ShowMore() public ShowMore()
{ {
this.InitializeComponent(); InitializeComponent();
} }
private void btn_Click(object sender, RoutedEventArgs e) private void btn_Click(object sender, RoutedEventArgs e)
@@ -32,11 +18,21 @@ namespace FoxTube.Controls
Clicked.Invoke(); 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; bar.Visibility = Visibility.Collapsed;
if (!close) btn.Visibility = Visibility.Visible;
btn.Visibility = Visibility.Visible;
} }
} }
} }
+34 -23
View File
@@ -4,51 +4,62 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 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" mc:Ignorable="d"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Top" VerticalAlignment="Top"
SizeChanged="UserControl_SizeChanged"
d:DesignHeight="290" d:DesignHeight="290"
d:DesignWidth="384"> d:DesignWidth="384"
Opacity="0">
<Button Padding="0" Background="Transparent" Click="Button_Click"> <Windows10version1809:UserControl.OpacityTransition>
<Grid Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}"> <ScalarTransition Duration="0:0:0.5"/>
</Windows10version1809:UserControl.OpacityTransition>
<Button Padding="0" Margin="1" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" Click="Button_Click">
<Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*"/> <RowDefinition/>
<RowDefinition Height="75"/> <RowDefinition Height="20"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Image Name="thumbnail" Source="/Assets/videoThumbSample.png" Stretch="Fill"/>
<Image Source="/Assets/videoPlaceholder.png" Opacity=".0001" Stretch="Fill"/>
<Image Name="thumbnail" Source="/Assets/videoThumbSample.png" Stretch="Fill" ImageOpened="Thumbnail_ImageOpened" Opacity="0">
<Windows10version1809:Image.OpacityTransition>
<ScalarTransition Duration="0:0:0.5"/>
</Windows10version1809:Image.OpacityTransition>
</Image>
<Grid Background="#7F000000" Name="watched" Visibility="Collapsed"> <Grid Background="#7F000000" Name="watched" Visibility="Collapsed">
<StackPanel Margin="5" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" VerticalAlignment="Top" HorizontalAlignment="Left" Padding="5,2,5,2" BorderBrush="Gray" BorderThickness="1"> <StackPanel Margin="5" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" VerticalAlignment="Top" HorizontalAlignment="Left" Padding="5,2,5,2" BorderBrush="Gray" BorderThickness="1">
<TextBlock x:Uid="/Cards/watched" Text="Watched" Foreground="Gray" FontSize="12"/> <TextBlock x:Uid="/Cards/watched" Text="Watched" Foreground="Gray" FontSize="12"/>
</StackPanel> </StackPanel>
<ProgressBar VerticalAlignment="Bottom" Margin="54,0,0,0" Foreground="Red" Name="leftOn"/> <ProgressBar VerticalAlignment="Bottom" Margin="54,0,0,0" Foreground="Red" Name="leftOn"/>
</Grid> </Grid>
<StackPanel Margin="0,0,5,5" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" VerticalAlignment="Bottom" HorizontalAlignment="Right" Padding="5,2,5,3"> <StackPanel Margin="0,0,5,5" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" VerticalAlignment="Bottom" HorizontalAlignment="Right" Padding="5,2,5,3">
<TextBlock Name="info" Text="[Duration] | [Published at]" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Gray" FontSize="12"/> <TextBlock Name="info" Text="[Duration] | [Published at]" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Gray" FontSize="12"/>
</StackPanel> </StackPanel>
<StackPanel Name="liveTag" Margin="0,0,5,30" Background="Red" BorderBrush="White" BorderThickness="1" VerticalAlignment="Bottom" HorizontalAlignment="Right" Padding="5,2,5,3" Orientation="Horizontal" Visibility="Collapsed"> <StackPanel Name="liveTag" Margin="0,0,5,30" Background="Red" BorderBrush="White" BorderThickness="1" VerticalAlignment="Bottom" HorizontalAlignment="Right" Padding="5,2,5,3" Orientation="Horizontal" Visibility="Collapsed">
<TextBlock Text="&#xEC44; " VerticalAlignment="Center" Foreground="White" FontSize="12" FontFamily="Segoe MDL2 Assets" FontWeight="Black"/> <TextBlock Text="&#xEC44; " VerticalAlignment="Center" Foreground="White" FontSize="12" FontFamily="Segoe MDL2 Assets" FontWeight="Black"/>
<TextBlock x:Uid="/Cards/live" Name="liveContent" Text="LIVE" VerticalAlignment="Center" Foreground="White" FontSize="12" FontWeight="Bold"/> <TextBlock x:Uid="/Cards/live" Name="liveContent" Text="LIVE" VerticalAlignment="Center" Foreground="White" FontSize="12" FontWeight="Bold"/>
</StackPanel> </StackPanel>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Height="50" Width="50" Margin="5,-30,5,10" Fill="{ThemeResource SystemControlBackgroundChromeMediumBrush}"/>
<PersonPicture Name="avatar" Grid.Column="0" Height="46" Margin="5,-30,5,0" BorderBrush="White" BorderThickness="10"/>
<TextBlock MaxWidth="200" Name="channelName" HorizontalAlignment="Left" Grid.Column="1" Text="[Channel name]" TextTrimming="CharacterEllipsis" Foreground="Gray" Margin="0,2,0,0" FontSize="12"/> <Grid Grid.Row="1">
<TextBlock Grid.Column="1" Name="views" Text="[Views]" HorizontalAlignment="Right" Foreground="Gray" Margin="0,2,2,0" FontSize="12"/> <Grid.ColumnDefinitions>
</Grid> <ColumnDefinition Width="60"/>
<TextBlock Grid.Row="1" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" Margin="5" MaxLines="2" TextTrimming="CharacterEllipsis"/> <ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Height="50" Width="50" Margin="5,-30,5,10" Fill="{ThemeResource SystemControlBackgroundChromeMediumBrush}"/>
<PersonPicture Name="avatar" Height="46" Margin="5,-30,5,0"/>
<TextBlock MaxWidth="200" Name="channelName" HorizontalAlignment="Left" Grid.Column="1" Text="[Channel name]" TextTrimming="CharacterEllipsis" Foreground="Gray" Margin="0,2,0,0" FontSize="12"/>
<TextBlock Grid.Column="1" Name="views" Text="[Views]" HorizontalAlignment="Right" Foreground="Gray" Margin="0,2,2,0" FontSize="12"/>
</Grid> </Grid>
<TextBlock Grid.Row="2" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" Margin="5" MaxLines="2" TextTrimming="CharacterEllipsis"/>
</Grid> </Grid>
</Button> </Button>
<UserControl.ContextFlyout> <UserControl.ContextFlyout>
+122 -27
View File
@@ -7,6 +7,10 @@ using Windows.UI.Xaml.Media.Imaging;
using Windows.System; using Windows.System;
using Windows.ApplicationModel.DataTransfer; using Windows.ApplicationModel.DataTransfer;
using Windows.ApplicationModel.Resources; using Windows.ApplicationModel.Resources;
using Microsoft.AppCenter.Analytics;
using System.Collections.Generic;
using YoutubeExplode;
using Windows.UI.Popups;
namespace FoxTube.Controls namespace FoxTube.Controls
{ {
@@ -27,33 +31,94 @@ namespace FoxTube.Controls
Initialize(id, playlist); 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) public async void Initialize(string id, string playlist = null)
{ {
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("snippet,contentDetails,statistics,liveStreamingDetails"); try
request.Id = id; {
VideoListResponse response = await request.ExecuteAsync(); videoId = id;
playlistId = playlist;
item = response.Items[0]; VideosResource.ListRequest request = SecretsVault.Service.Videos.List("snippet,contentDetails,statistics,liveStreamingDetails");
videoId = id; request.Id = id;
playlistId = playlist; 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<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Video ID", videoId }
});
}
}
public async void LoadMeta()
{
videoId = item.Id;
title.Text = item.Snippet.Title; title.Text = item.Snippet.Title;
channelName.Text = item.Snippet.ChannelTitle; channelName.Text = item.Snippet.ChannelTitle;
if (item.Snippet.LiveBroadcastContent == "live") if (item.Snippet.LiveBroadcastContent == "live")
{ {
views.Text = $"{item.LiveStreamingDetails.ConcurrentViewers:0,0} {resources.GetString("/Cards/viewers")}"; views.Text = $"{item.LiveStreamingDetails.ConcurrentViewers:0,0} {resources.GetString("/Cards/viewers")}";
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue && item.LiveStreamingDetails.ScheduledEndTime.HasValue) 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 else
info.Text = item.LiveStreamingDetails.ActualStartTime.Value.ToString(); info.Text = Methods.GetAgo(item.LiveStreamingDetails.ActualStartTime.Value);
liveTag.Visibility = Visibility.Visible; liveTag.Visibility = Visibility.Visible;
} }
else if(item.Snippet.LiveBroadcastContent == "upcoming") else if (item.Snippet.LiveBroadcastContent == "upcoming")
{ {
views.Text = ""; views.Text = "";
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue && item.LiveStreamingDetails.ScheduledEndTime.HasValue) if (item.LiveStreamingDetails.ScheduledStartTime.HasValue && item.LiveStreamingDetails.ScheduledEndTime.HasValue)
@@ -62,8 +127,8 @@ namespace FoxTube.Controls
info.Text = $"{Methods.GetAgo(item.Snippet.PublishedAt.Value)}"; info.Text = $"{Methods.GetAgo(item.Snippet.PublishedAt.Value)}";
liveTag.Visibility = Visibility.Visible; liveTag.Visibility = Visibility.Visible;
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue && (item.LiveStreamingDetails.ScheduledStartTime - DateTime.Now).Value.TotalMilliseconds > 0) if (item.LiveStreamingDetails.ScheduledStartTime.HasValue)
liveContent.Text = $"{resources.GetString("/Cards/goesLive")} {item.LiveStreamingDetails.ScheduledStartTime}"; liveContent.Text = $"{resources.GetString("/Cards/goesLive")} {item.LiveStreamingDetails.ScheduledStartTime - DateTime.Now}";
else liveContent.Text = resources.GetString("/Cards/upcoming"); else liveContent.Text = resources.GetString("/Cards/upcoming");
} }
else else
@@ -72,26 +137,51 @@ namespace FoxTube.Controls
info.Text = $"{item.ContentDetails.Duration.GetDuration()} | {Methods.GetAgo(item.Snippet.PublishedAt.Value)}"; info.Text = $"{item.ContentDetails.Duration.GetDuration()} | {Methods.GetAgo(item.Snippet.PublishedAt.Value)}";
} }
var request1 = SecretsVault.Service.Channels.List("snippet"); try { thumbnail.Source = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri()); }
request1.Id = item.Snippet.ChannelId; catch { }
ChannelListResponse response1 = await request1.ExecuteAsync(); try { avatar.ProfilePicture = new BitmapImage((await new YoutubeClient().GetChannelAsync(item.Snippet.ChannelId)).LogoUrl.ToUri()) { DecodePixelHeight = 50, DecodePixelWidth = 50 }; }
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));
}
catch { } catch { }
/*if(SecretsVault.UserHistory.Exists(x => x.Id == videoId)) if (SecretsVault.History.Contains(videoId))
{
watched.Visibility = Visibility.Visible; 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); Methods.MainPage.GoToVideo(videoId, playlistId);
} }
@@ -111,5 +201,10 @@ namespace FoxTube.Controls
{ {
await Launcher.LaunchUriAsync($"https://www.youtube.com/watch?v={videoId}".ToUri()); await Launcher.LaunchUriAsync($"https://www.youtube.com/watch?v={videoId}".ToUri());
} }
private void Thumbnail_ImageOpened(object sender, RoutedEventArgs e)
{
thumbnail.Opacity = 1;
}
} }
} }
-250
View File
@@ -1,250 +0,0 @@
<UserControl
x:Class="FoxTube.VideoPlayer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:controls1="using:FoxTube.Controls"
mc:Ignorable="d"
d:DesignHeight="1080"
d:DesignWidth="1920"
RequestedTheme="Dark"
PointerMoved="UserControl_PointerMoved"
PointerExited="UserControl_PointerExited"
PointerEntered="UserControl_PointerEntered">
<Grid Background="White" Name="grid" Tapped="UserControl_Tapped">
<MediaPlayerElement IsDoubleTapEnabled="False" Name="videoSource" AreTransportControlsEnabled="False" PosterSource="ms-appx:///Assets/videoThumbSample.png"/>
<MediaPlayerElement Name="audioSource" Width="0" Height="0" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<controls1:LiveCaptions Player="{x:Bind controller}" Visibility="Collapsed"/>
<Grid Name="controls" Visibility="Visible">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid Name="header" Height="50" Background="#7F000000">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Button Click="minimize_Click" Name="minimize" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE011;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/minimize"/>
</ToolTipService.ToolTip>
</Button>
<StackPanel Grid.Column="1" Margin="10,0,10,0" VerticalAlignment="Center">
<TextBlock Name="title" Text="[Title]" Foreground="White" VerticalAlignment="Center" TextWrapping="WrapWholeWords" FontSize="20" MaxLines="1"/>
<TextBlock Foreground="LightGray" Text="[Channel name]" Name="channelName" FontStyle="Italic"/>
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Name="closeHeader" Click="close_Click" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE10A;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/close"/>
</ToolTipService.ToolTip>
</Button>
<Button Name="cast" Click="cast_Click" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xEC15;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/cast"/>
</ToolTipService.ToolTip>
</Button>
<Button Name="miniViewBtn" Click="miniView_Click" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE2B3;" Foreground="White" Width="50" Height="50" FontSize="25" HorizontalAlignment="Center" Margin="0,0,0,0" VerticalAlignment="Center">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/miniview"/>
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</Grid>
<Grid Grid.Row="1" Name="playPauseArea" Tapped="playPauseArea_Tapped" DoubleTapped="playPauseArea_DoubleTapped" Background="Black" Opacity=".0001"/>
<Grid Name="touchCentral" Background="#7F000000" Visibility="Collapsed" Grid.Row="1">
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button VerticalAlignment="Center" Content="&#xED3C;" FontFamily="Segoe MDL2 Assets" Background="Transparent" FontSize="40" Foreground="WhiteSmoke" Name="touchBack10" Click="back10_Click"/>
<Button VerticalAlignment="Center" Content="&#xE102;" FontFamily="Segoe MDL2 Assets" Background="Transparent" FontSize="100" Foreground="WhiteSmoke" Name="touchPlay" Click="play_Click"/>
<Button VerticalAlignment="Center" Content="&#xED3D;" FontFamily="Segoe MDL2 Assets" Background="Transparent" FontSize="40" Foreground="WhiteSmoke" Name="touchFwd30" Click="fwd30_Click"/>
</StackPanel>
<Button Visibility="Collapsed" Margin="0,32,0,0" VerticalAlignment="Top" HorizontalAlignment="Right" Name="miniViewExit" Grid.Row="1" Click="miniView_Click" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE2B4;" Foreground="White" Width="45" Height="45" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/exitminiview"/>
</ToolTipService.ToolTip>
</Button>
<Button Visibility="Collapsed" VerticalAlignment="Top" HorizontalAlignment="Right" Name="close" Click="close_Click" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE10A;" Foreground="White" Width="45" Height="45" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/close"/>
</ToolTipService.ToolTip>
</Button>
<Button Visibility="Collapsed"
VerticalAlignment="Top" HorizontalAlignment="Left"
Name="maximize" Click="maximize_Click"
Background="Transparent" FontFamily="Segoe MDL2 Assets" Foreground="White" FontSize="25"
Content="&#xE010;"
Width="45" Height="45">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/maximize"/>
</ToolTipService.ToolTip>
</Button>
<ProgressBar VerticalAlignment="Bottom" Foreground="Red" Name="seekIndicator" Visibility="Collapsed"/>
</Grid>
<StackPanel Name="schedulePanel" Visibility="Collapsed" VerticalAlignment="Bottom" HorizontalAlignment="Right" Grid.Row="1" BorderBrush="Black" BorderThickness="2" Background="#7E000000" Padding="10" Margin="0,25" Orientation="Horizontal">
<FontIcon Glyph="&#xE704;" FontSize="30" Margin="0,0,10,0" VerticalAlignment="Top"/>
<StackPanel>
<TextBlock FontWeight="Bold" Text="Stream hasn't started yet"/>
<TextBlock Name="scheduleHeader" Visibility="Collapsed" Text="Stream schedule:"/>
<TextBlock Name="scheduleStart" Visibility="Collapsed" Text="Start time: "/>
<TextBlock Name="scheduleEnd" Visibility="Collapsed" Text="End time:"/>
<TextBlock Name="countdownHeader" Visibility="Collapsed" FontWeight="Bold" Text="Stream will be started in:" Margin="0,10,0,0"/>
<TextBlock Name="countdown" Visibility="Collapsed" FontWeight="SemiBold" FontSize="20" Text="1:00:00:00"/>
</StackPanel>
</StackPanel>
<Grid Grid.Row="2" Height="50" Name="mainControls" Background="#7F000000">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<Button Click="play_Click" Name="play" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE768;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/play"/>
</ToolTipService.ToolTip>
</Button>
<Button Name="next" Click="next_Click" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE101;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/next"/>
</ToolTipService.ToolTip>
</Button>
<Button Name="openVolume" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE995;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/volume"/>
</ToolTipService.ToolTip>
<Button.Flyout>
<Flyout>
<StackPanel Orientation="Horizontal" Margin="-10">
<Button Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE995;" Foreground="White" Width="50" Height="50" FontSize="25" Name="muteBtn" Click="muteBtn_Click">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/mute"/>
</ToolTipService.ToolTip>
</Button>
<Slider Orientation="Horizontal" Width="150" Margin="10,5,10,0" VerticalAlignment="Center" Name="volume" ValueChanged="volume_ValueChanged"/>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
<Button Name="gotoLive" Visibility="Collapsed" Click="GotoLive_Click" Background="Transparent" Foreground="White" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/goLive"/>
</ToolTipService.ToolTip>
<StackPanel Orientation="Horizontal">
<FontIcon Glyph="&#xE91F;" Foreground="Red"/>
<TextBlock Text="Live" Margin="5,0,0,0" FontSize="15" VerticalAlignment="Center"/>
</StackPanel>
</Button>
</StackPanel>
<Grid Grid.Column="1" Name="seekPanel">
<TextBlock Name="elapsedTime" Foreground="White" Text="[Elapsed]" VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<TextBlock Name="remainingTime" Foreground="White" Text="[Remaining]" VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
<Grid VerticalAlignment="Top" Margin="0,15,0,0" Height="2">
<ProgressBar Background="#66FFFFFF" Foreground="LightGray" Name="bufferingLevel"/>
</Grid>
<Slider PointerCaptureLost="seek_PointerCaptureLost" ManipulationStarted="Seek_PointerCaptured" ManipulationMode="TranslateRailsX" ValueChanged="seek_ValueChanged" Name="seek" VerticalAlignment="Top" IsThumbToolTipEnabled="False" Background="Transparent" HorizontalAlignment="Stretch"/>
</Grid>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<TextBlock Text="1:12:32" Name="liveElapsed" Visibility="Collapsed" Margin="10,0" FontSize="20" VerticalAlignment="Center">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/streamElapsed"/>
</ToolTipService.ToolTip>
</TextBlock>
<StackPanel Orientation="Horizontal" Name="rewindPanel">
<Button Click="back10_Click" Name="back10" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xED3C;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/back"/>
</ToolTipService.ToolTip>
</Button>
<Button Click="fwd30_Click" Name="fwd30" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xED3D;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/fwd"/>
</ToolTipService.ToolTip>
</Button>
</StackPanel>
<Line Stroke="White" StrokeThickness="2" Y1="5" Y2="45"/>
<Button Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE190;" Foreground="White" Width="50" Height="50" FontSize="25" Name="captionsBtn">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/subs"/>
</ToolTipService.ToolTip>
<Button.Flyout>
<Flyout>
<StackPanel Width="225">
<ToggleSwitch x:Uid="/VideoPage/subsSwitch" Name="subsSwitch" Toggled="subsSwitch_Toggled" OnContent="Subtitles" OffContent="Subtitles"/>
<ComboBox x:Uid="/VideoPage/subsSelector" Name="subsLang" Header="Language" PlaceholderText="No subtitles are available" Visibility="Collapsed" HorizontalAlignment="Stretch" SelectionChanged="subsLang_SelectionChanged"/>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
<Button Name="qualityBtn" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE713;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/quality"/>
</ToolTipService.ToolTip>
<Button.Flyout>
<Flyout>
<ComboBox x:Uid="/VideoPage/qualitySelector" Width="225" Header="Quality" Name="quality" SelectionChanged="quality_SelectionChanged"/>
</Flyout>
</Button.Flyout>
</Button>
<Button Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE740;" Foreground="White" Width="50" Height="50" FontSize="25" Name="fullscreen" Click="fullscreen_Click">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/fullscreen"/>
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</Grid>
</Grid>
</Grid>
<Grid Name="matureBlock" Visibility="Collapsed" Background="#FF333333" Padding="25">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel>
<TextBlock Text="Warning! Mature content!" Foreground="White" FontSize="30"/>
<Line Stroke="White" StrokeThickness="2" X1="0" X2="350"/>
</StackPanel>
<Grid Name="proceedMature" Visibility="Collapsed" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock TextWrapping="WrapWholeWords" Foreground="White" Text="This content isn't advised for children. It can represent violance, blood or sexual scenes." FontSize="20"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="1" VerticalAlignment="Bottom">
<CheckBox Foreground="White" Name="matureDisable">
<TextBlock Foreground="White" Text="Don't show me it again"/>
</CheckBox>
<Button Content="Continue" Name="matureDismiss" Click="matureDismiss_Click" Margin="5,0,0,0" Foreground="White" Background="Gray"/>
</StackPanel>
</Grid>
<Grid BorderBrush="OrangeRed" BorderThickness="5" Margin="0,10,0,0" Visibility="Collapsed" Name="signReq" Grid.Row="1" VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xE192;" FontSize="40" Foreground="OrangeRed" Margin="5"/>
<StackPanel HorizontalAlignment="Stretch" Grid.Column="1">
<TextBlock Text="Sign in into your account to continue" Foreground="OrangeRed" FontWeight="Bold" FontSize="20"/>
<TextBlock Text="To watch this video you have to confirm your age by sign in into your account which contains your age" Foreground="OrangeRed"/>
</StackPanel>
<Button Name="signin" Click="signin_Click" Content="Sign in now" Foreground="White" Background="Gray" HorizontalAlignment="Right" Grid.Column="1" Margin="0,0,10,0"/>
</Grid>
</Grid>
</Grid>
</UserControl>
-810
View File
@@ -1,810 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Foundation;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;
using Google.Apis.YouTube.v3.Data;
using Windows.UI.Xaml.Media.Imaging;
using Windows.Media;
using Windows.Storage.Streams;
using Windows.UI.ViewManagement;
using System.Xml;
using Windows.ApplicationModel.Core;
using Windows.UI;
using Windows.Media.Casting;
using YoutubeExplode.Models.MediaStreams;
using YoutubeExplode;
using YoutubeExplode.Models.ClosedCaptions;
using System.Globalization;
using FoxTube.Controls;
using Windows.System;
using Windows.Media.Core;
using Windows.Media.Playback;
namespace FoxTube
{
public enum PlayerLayout { Normal, Fullscreen, Minimized }
public sealed partial class VideoPlayer : UserControl
{
public string videoId;
public Video item;
public string avatar;
public PlayerLayout layout = PlayerLayout.Normal;
public bool pointerCaptured = false;
bool seekCaptured = false;
Point cursorBackup;
public event ObjectEventHandler SetFullSize;
public event Event NextClicked;
public Button Next => next;
public TimeSpan Elapsed { get; set; } = TimeSpan.FromMilliseconds(0);
public TimeSpan Remaining => Total.Subtract(Elapsed);
public TimeSpan Total { get; set; }
double timecodeBackup = 0;
bool needUpdateTimecode = false;
SystemMediaTransportControls systemControls;
IReadOnlyList<ClosedCaptionTrackInfo> ccInfo;
MediaStreamInfoSet streamInfo;
DispatcherTimer timer = new DispatcherTimer()
{
Interval = TimeSpan.FromSeconds(1)
};
DispatcherTimer ctrlsFadeTimer = null;
LiveCaptions captions;
MediaPlayer videoPlayer;
MediaPlayer audioPlayer;
MediaTimelineController controller;
public VideoPlayer()
{
InitializeComponent();
}
public void Initialize(Video meta, string channelAvatar)
{
Visibility = Visibility.Collapsed;
item = meta;
avatar = channelAvatar;
videoId = item.Id;
if (item.ContentDetails.ContentRating != null)
{
if (SecretsVault.IsAuthorized)
{
if (SettingsStorage.Mature == MatureState.AllowedOnce)
SettingsStorage.Mature = MatureState.Blocked;
else if (SettingsStorage.Mature == MatureState.Blocked)
{
Visibility = Visibility.Visible;
proceedMature.Visibility = Visibility.Visible;
matureBlock.Visibility = Visibility.Visible;
return;
}
}
else
{
Visibility = Visibility.Visible;
signReq.Visibility = Visibility.Visible;
matureBlock.Visibility = Visibility.Visible;
return;
}
}
if (item.Snippet.LiveBroadcastContent == "none")
LoadVideo();
else if (item.Snippet.LiveBroadcastContent == "live")
LoadStream();
else
LoadUpcoming();
Visibility = Visibility.Visible;
}
public void InitializeContols()
{
videoPlayer = new MediaPlayer();
controller = new MediaTimelineController();
videoPlayer.TimelineController = controller;
videoSource.SetMediaPlayer(videoPlayer);
videoPlayer.MediaOpened += async (s, e) =>
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
if (controller.State == MediaTimelineControllerState.Running && needUpdateTimecode)
{
controller.Position = TimeSpan.FromSeconds(timecodeBackup);
needUpdateTimecode = false;
controller.Resume();
return;
}
seek.IsEnabled = true;
play.IsEnabled = true;
touchPlay.IsEnabled = true;
if (SettingsStorage.Autoplay)
controller.Resume();
});
};
volume.Value = SettingsStorage.Volume;
if (!ApplicationView.GetForCurrentView().IsViewModeSupported(ApplicationViewMode.CompactOverlay))
miniViewBtn.Visibility = Visibility.Collapsed;
controller.StateChanged += CurrentStateChanged;
controller.Ended += (s, arg) =>
{
seek.Value = seek.Maximum;
seekIndicator.Value = seekIndicator.Maximum;
};
#region System Media Transport Controls
systemControls = SystemMediaTransportControls.GetForCurrentView();
systemControls.IsNextEnabled = true;
systemControls.IsPauseEnabled = true;
systemControls.IsPlayEnabled = true;
systemControls.DisplayUpdater.Type = MediaPlaybackType.Video;
systemControls.DisplayUpdater.VideoProperties.Title = item.Snippet.Title;
systemControls.DisplayUpdater.VideoProperties.Subtitle = item.Snippet.ChannelTitle;
systemControls.DisplayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromUri(avatar.ToUri());
systemControls.DisplayUpdater.Update();
systemControls.ButtonPressed += SystemControls_Engaged;
systemControls.IsEnabled = true;
#endregion
videoSource.PosterSource = new BitmapImage((item.Snippet.Thumbnails.Maxres ?? item.Snippet.Thumbnails.Medium).Url.ToUri());
title.Text = item.Snippet.Title;
channelName.Text = item.Snippet.ChannelTitle;
ctrlsFadeTimer = new DispatcherTimer();
ctrlsFadeTimer.Interval = TimeSpan.FromSeconds(5);
ctrlsFadeTimer.Tick += ControlsFade;
}
public void LoadUpcoming()
{
schedulePanel.Visibility = Visibility.Visible;
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue)
{
scheduleHeader.Visibility = Visibility.Visible;
scheduleStart.Visibility = Visibility.Visible;
countdownHeader.Visibility = Visibility.Visible;
countdown.Visibility = Visibility.Visible;
scheduleStart.Text = $"Start time: {item.LiveStreamingDetails.ScheduledStartTime.Value}";
timer.Tick += UpdateCountdown;
timer.Start();
}
if (item.LiveStreamingDetails.ScheduledEndTime.HasValue)
{
scheduleHeader.Visibility = Visibility.Visible;
scheduleEnd.Visibility = Visibility.Visible;
scheduleEnd.Text = $"End time: {item.LiveStreamingDetails.ScheduledEndTime.Value}";
}
}
public async void LoadStream()
{
InitializeContols();
seekPanel.Visibility = Visibility.Collapsed;
rewindPanel.Visibility = Visibility.Collapsed;
captionsBtn.Visibility = Visibility.Collapsed;
qualityBtn.Visibility = Visibility.Collapsed;
gotoLive.Visibility = Visibility.Visible;
liveElapsed.Visibility = Visibility.Visible;
timer.Tick += UpdateLive;
timer.Start();
streamInfo = await new YoutubeClient().GetVideoMediaStreamInfosAsync(videoId);
videoPlayer.Source = MediaSource.CreateFromUri(streamInfo.HlsLiveStreamUrl.ToUri());
}
public async void LoadVideo()
{
InitializeContols();
captions = grid.Children[2] as LiveCaptions;
captions.Player = controller;
controller.PositionChanged += UpdateSeek;
Total = XmlConvert.ToTimeSpan(item.ContentDetails.Duration);
seek.Maximum = Total.TotalMilliseconds;
seekIndicator.Maximum = Total.TotalMilliseconds;
elapsedTime.Text = Elapsed.Hours > 0 ? $"{Elapsed:hh\\:mm\\:ss}" : $"{Elapsed:mm\\:ss}";
remainingTime.Text = Remaining.Hours > 0 ? $"{Remaining:hh\\:mm\\:ss}" : $"{Remaining:mm\\:ss}";
#region Retrieving info for CC and Media streams
//Loading streams
streamInfo = await new YoutubeClient().GetVideoMediaStreamInfosAsync(videoId);
List<VideoQuality> q = streamInfo.GetAllVideoQualities().ToList();
q.Sort();
q.Reverse();
foreach (VideoQuality i in q)
quality.Items.Add(new ComboBoxItem() { Content = i.GetVideoQualityLabel() });
string s = SettingsStorage.VideoQuality == "remember" ? SettingsStorage.RememberedQuality : SettingsStorage.VideoQuality;
quality.SelectedItem = quality.Items.ToList().Exists(x => (x as ComboBoxItem).Content as string == s) ? quality.Items.Find(x => (x as ComboBoxItem).Content as string == s) : quality.Items.First();
//Loading captions
ccInfo = await new YoutubeClient().GetVideoClosedCaptionTrackInfosAsync(videoId);
if (ccInfo.Count > 0)
{
foreach (ClosedCaptionTrackInfo cc in ccInfo)
{
subsLang.Items.Add(new ComboBoxItem()
{
Content = string.Format("{0}{1}", CultureInfo.GetCultureInfo(cc.Language.Code).DisplayName, cc.IsAutoGenerated ? " (Auto-generated)" : ""),
Tag = cc
});
if (SettingsStorage.RelevanceLanguage.Contains(cc.Language.Code))
subsLang.SelectedItem = subsLang.Items.Last();
}
if (subsLang.SelectedItem == null)
if(ccInfo.ToList().Exists(i => i.Language.Code == "en"))
subsLang.SelectedItem = subsLang.Items.Find(i => (((ComboBoxItem)i).Tag as ClosedCaptionTrackInfo).Language.Code == "en");
else
subsLang.SelectedIndex = 0;
}
else
captionsBtn.Visibility = Visibility.Collapsed;
#endregion
}
public void UpdateCountdown(object sender, object e)
{
countdown.Text = $"{item.LiveStreamingDetails.ScheduledStartTime.Value - DateTime.Now:hh\\:mm\\:ss}";
}
public void UpdateLive(object sender, object e)
{
liveElapsed.Text = $"{DateTime.Now - item.LiveStreamingDetails.ActualStartTime:hh\\:mm\\:ss}";
}
public async void UpdateSeek(MediaTimelineController sender, object e)
{
try
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
bufferingLevel.Value = videoPlayer.PlaybackSession.DownloadProgress * 100;
if (seekCaptured)
return;
if(needUpdateTimecode)
if (controller.State == MediaTimelineControllerState.Running)
needUpdateTimecode = false;
else
return;
seek.Value = controller.Position.TotalMilliseconds;
seekIndicator.Value = seek.Value;
});
}
catch { }
}
private async void SystemControls_Engaged(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
switch (args.Button)
{
case SystemMediaTransportControlsButton.Pause:
controller.Pause();
break;
case SystemMediaTransportControlsButton.Play:
controller.Resume();
break;
case SystemMediaTransportControlsButton.Next:
NextClicked?.Invoke();
break;
}
});
}
void ControlsFade(object sender, object e)
{
if (seekCaptured)
return;
controls.Visibility = Visibility.Collapsed;
if (layout != PlayerLayout.Minimized)
touchCentral.Visibility = Visibility.Collapsed;
if (pointerCaptured)
Window.Current.CoreWindow.PointerCursor = null;
seekIndicator.Visibility = Visibility.Collapsed;
ctrlsFadeTimer.Stop();
}
public void UpdateSize()
{
if(layout != PlayerLayout.Normal)
Height = Window.Current.Bounds.Height;
}
void ShowControls()
{
if (ctrlsFadeTimer == null)
return;
controls.Visibility = Visibility.Visible;
Window.Current.CoreWindow.PointerCursor = new CoreCursor(CoreCursorType.Arrow, 0);
ctrlsFadeTimer.Start();
}
private void volume_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
double v = volume.Value;
if (v == 0)
muteBtn.Content = openVolume.Content = "\xE74F";
else if (v <= 25 && v > 0)
muteBtn.Content = openVolume.Content = "\xE992";
else if (v <= 50 && v > 25)
muteBtn.Content = openVolume.Content = "\xE993";
else if (v <= 75 && v > 50)
muteBtn.Content = openVolume.Content = "\xE994";
else if (v > 75)
muteBtn.Content = openVolume.Content = "\xE995";
SettingsStorage.Volume = (int) volume.Value;
if(audioPlayer != null)
audioPlayer.Volume = volume.Value * .01;
videoPlayer.Volume = volume.Value * .01;
}
private void muteBtn_Click(object sender, RoutedEventArgs e)
{
if (volume.Value != 0)
{
int v = SettingsStorage.Volume;
volume.Value = 0;
SettingsStorage.Volume = v;
}
else volume.Value = SettingsStorage.Volume;
}
private void UserControl_PointerMoved(object sender, PointerRoutedEventArgs e)
{
if (ctrlsFadeTimer == null)
return;
else if (cursorBackup != Window.Current.CoreWindow.PointerPosition)
ShowControls();
cursorBackup = Window.Current.CoreWindow.PointerPosition;
}
private void UserControl_PointerExited(object sender, PointerRoutedEventArgs e)
{
if (e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse && ctrlsFadeTimer != null)
{
pointerCaptured = false;
ControlsFade(this, null);
}
}
private void UserControl_PointerEntered(object sender, PointerRoutedEventArgs e)
{
if (e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse && ctrlsFadeTimer != null)
pointerCaptured = true;
}
private void playPauseArea_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
switch (layout)
{
case PlayerLayout.Minimized:
if (ApplicationView.GetForCurrentView().ViewMode == ApplicationViewMode.CompactOverlay)
miniView_Click(this, null);
else if (ApplicationView.GetForCurrentView().ViewMode == ApplicationViewMode.Default)
maximize_Click(this, null);
break;
case PlayerLayout.Fullscreen:
case PlayerLayout.Normal:
fullscreen_Click(this, null);
break;
}
}
private void playPauseArea_Tapped(object sender, TappedRoutedEventArgs e)
{
if (e.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse && layout != PlayerLayout.Minimized)
play_Click(this, null);
UserControl_Tapped(sender, e);
}
private void UserControl_Tapped(object sender, TappedRoutedEventArgs e)
{
if (e.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Touch && ctrlsFadeTimer != null)
{
touchCentral.Visibility = Visibility.Visible;
if (ctrlsFadeTimer.IsEnabled)
ControlsFade(this, null);
else
ShowControls();
}
}
private void quality_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
SettingsStorage.RememberedQuality = ((ComboBoxItem)quality.SelectedItem).Content.ToString();
controller.Pause();
timecodeBackup = controller.Position.TotalSeconds;
needUpdateTimecode = true;
if (streamInfo.Muxed.ToList().Exists(x => x.VideoQualityLabel == (quality.SelectedItem as ComboBoxItem).Content.ToString()))
{
videoPlayer.Source = MediaSource.CreateFromUri(streamInfo.Muxed.Find(x => x.VideoQualityLabel == (quality.SelectedItem as ComboBoxItem).Content as string).Url.ToUri());
audioPlayer = null;
}
else
{
if (audioPlayer == null)
{
audioPlayer = new MediaPlayer();
audioPlayer.TimelineController = controller;
audioPlayer.Volume = volume.Value * .01;
audioSource.SetMediaPlayer(audioPlayer);
}
VideoStreamInfo videoInfo = streamInfo.Video.Find(i => i.VideoQualityLabel == (quality.SelectedItem as ComboBoxItem).Content.ToString());
AudioStreamInfo audioInfo = streamInfo.Audio.First();
videoPlayer.Source = MediaSource.CreateFromUri(videoInfo.Url.ToUri());
audioPlayer.Source = MediaSource.CreateFromUri(audioInfo.Url.ToUri());
}
controller.Resume();
}
catch { }
}
private void subsSwitch_Toggled(object sender, RoutedEventArgs e)
{
if (subsSwitch.IsOn)
subsLang.Visibility = Visibility.Visible;
else
subsLang.Visibility = Visibility.Collapsed;
LoadTrack();
}
void LoadTrack()
{
if (subsSwitch.IsOn)
captions.Initialize((subsLang.SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
else
captions?.Close();
}
private void fullscreen_Click(object sender, RoutedEventArgs e)
{
bool fullScreen = layout == PlayerLayout.Fullscreen ? false : true;
SetFullSize.Invoke(this, fullScreen);
Methods.MainPage.Fullscreen(fullScreen);
if(fullScreen)
{
ApplicationView.GetForCurrentView().TryEnterFullScreenMode();
fullscreen.Content = "\xE1D8";
layout = PlayerLayout.Fullscreen;
}
else
{
ApplicationView.GetForCurrentView().ExitFullScreenMode();
fullscreen.Content = "\xE1D9";
layout = PlayerLayout.Normal;
Height = double.NaN;
}
}
private void play_Click(object sender, RoutedEventArgs e)
{
if (controller.State == MediaTimelineControllerState.Running)
controller.Pause();
else if (controller.State == MediaTimelineControllerState.Paused)
controller.Resume();
}
private async void CurrentStateChanged(MediaTimelineController sender, object e)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
needUpdateTimecode = false;
switch (sender.State)
{
case MediaTimelineControllerState.Paused:
play.Content = "\xE102";
touchPlay.Content = "\xE102";
systemControls.PlaybackStatus = MediaPlaybackStatus.Paused;
break;
case MediaTimelineControllerState.Running:
play.Content = "\xE103";
touchPlay.Content = "\xE103";
systemControls.PlaybackStatus = MediaPlaybackStatus.Playing;
break;
default:
systemControls.PlaybackStatus = MediaPlaybackStatus.Closed;
break;
}
//SecretsVault.HistoryAdd(videoId, elapsed, total);
});
}
private async void miniView_Click(object sender, RoutedEventArgs e)
{
ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar;
bool MiniView = layout == PlayerLayout.Minimized ? false : true;
SetFullSize(this, MiniView);
if (MiniView)
{
if (layout == PlayerLayout.Fullscreen)
fullscreen.Content = "\xE740";
await ApplicationView.GetForCurrentView().TryEnterViewModeAsync(ApplicationViewMode.CompactOverlay);
pointerCaptured = false;
titleBar.ButtonBackgroundColor = Colors.Transparent;
titleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true;
mainControls.Visibility = Visibility.Collapsed;
header.Visibility = Visibility.Collapsed;
touchCentral.Visibility = Visibility.Visible;
miniViewExit.Visibility = Visibility.Visible;
touchBack10.FontSize = touchFwd30.FontSize = 20;
touchPlay.FontSize = 50;
Methods.MainPage.Fullscreen(true);
layout = PlayerLayout.Minimized;
if(captions != null)
captions.Size = 15;
}
else
{
await ApplicationView.GetForCurrentView().TryEnterViewModeAsync(ApplicationViewMode.Default);
mainControls.Visibility = Visibility.Visible;
header.Visibility = Visibility.Visible;
touchCentral.Visibility = Visibility.Collapsed;
miniViewExit.Visibility = Visibility.Collapsed;
touchBack10.FontSize = touchFwd30.FontSize = 40;
touchPlay.FontSize = 100;
Methods.MainPage.Fullscreen(false);
Height = double.NaN;
layout = PlayerLayout.Normal;
if(captions != null)
captions.Size = 24;
}
}
public void minimize_Click(object sender, RoutedEventArgs e)
{
if (layout == PlayerLayout.Fullscreen)
{
ApplicationView.GetForCurrentView().ExitFullScreenMode();
fullscreen.Content = "\xE740";
layout = PlayerLayout.Normal;
Methods.MainPage.Fullscreen(false);
}
else
SetFullSize?.Invoke(this, true);
Width = 432;
Height = 243;
layout = PlayerLayout.Minimized;
Methods.MainPage.MinimizeVideo();
mainControls.Visibility = Visibility.Collapsed;
header.Visibility = Visibility.Collapsed;
touchCentral.Visibility = Visibility.Visible;
maximize.Visibility = Visibility.Visible;
close.Visibility = Visibility.Visible;
touchBack10.FontSize = touchFwd30.FontSize = 20;
touchPlay.FontSize = 50;
if (captions != null)
captions.Size = 15;
}
private void maximize_Click(object sender, RoutedEventArgs e)
{
SetFullSize?.Invoke(this, false);
Width = double.NaN;
Height = double.NaN;
layout = PlayerLayout.Normal;
Methods.MainPage.MaximizeVideo();
mainControls.Visibility = Visibility.Visible;
header.Visibility = Visibility.Visible;
touchCentral.Visibility = Visibility.Collapsed;
maximize.Visibility = Visibility.Collapsed;
close.Visibility = Visibility.Collapsed;
touchBack10.FontSize = touchFwd30.FontSize = 40;
touchPlay.FontSize = 100;
if (captions != null)
captions.Size = 24;
}
private void seek_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
Elapsed = TimeSpan.FromMilliseconds(seek.Value);
elapsedTime.Text = Elapsed.Hours > 0 ? $"{Elapsed:hh\\:mm\\:ss}" : $"{Elapsed:mm\\:ss}";
remainingTime.Text = Remaining.Hours > 0 ? $"{Remaining:hh\\:mm\\:ss}" : $"{Remaining:mm\\:ss}";
}
private void seek_PointerCaptureLost(object sender, PointerRoutedEventArgs e)
{
seekCaptured = false;
needUpdateTimecode = true;
controller.Position = Elapsed;
}
private void fwd30_Click(object sender, RoutedEventArgs e)
{
if(Remaining.TotalSeconds >= 30)
controller.Position = Elapsed.Add(TimeSpan.FromSeconds(30));
}
private void back10_Click(object sender, RoutedEventArgs e)
{
if (Elapsed.TotalSeconds >= 10)
controller.Position = Elapsed.Subtract(TimeSpan.FromSeconds(10));
}
private void next_Click(object sender, RoutedEventArgs e)
{
NextClicked?.Invoke();
}
private void matureDismiss_Click(object sender, RoutedEventArgs e)
{
if ((bool)matureDisable.IsChecked)
SettingsStorage.Mature = MatureState.Allowed;
else
SettingsStorage.Mature = MatureState.AllowedOnce;
Methods.MainPage.GoToVideo(videoId, Methods.MainPage.GetPlaylist());
}
private void signin_Click(object sender, RoutedEventArgs e)
{
SecretsVault.Authorize();
}
public void Pause()
{
controller.Pause();
}
public void close_Click(object sender, RoutedEventArgs e)
{
systemControls.IsEnabled = false;
pointerCaptured = false;
controller.Pause();
ctrlsFadeTimer?.Stop();
timer?.Stop();
Methods.MainPage.CloseVideo();
}
private void cast_Click(object sender, RoutedEventArgs e)
{
if (videoPlayer.Source == null)
return;
controller.Pause();
CastingDevicePicker picker = new CastingDevicePicker();
picker.Filter.SupportsVideo = true;
picker.CastingDeviceSelected += async (s, args) =>
{
CastingConnection connection = args.SelectedCastingDevice.CreateCastingConnection();
await connection.RequestStartCastingAsync(videoPlayer.GetAsCastingSource());
};
Point positinon = cast.TransformToVisual(Window.Current.Content).TransformPoint(new Point(0, 0));
picker.Show(new Rect(positinon.X, positinon.Y, cast.ActualWidth, cast.ActualHeight), Windows.UI.Popups.Placement.Below);
}
public void KeyUpPressed(object sender, KeyRoutedEventArgs e)
{
switch(e.Key)
{
case VirtualKey.Escape:
if (layout == PlayerLayout.Fullscreen)
fullscreen_Click(this, null);
break;
case VirtualKey.F11:
fullscreen_Click(this, null);
break;
case VirtualKey.Space:
play_Click(this, null);
break;
case VirtualKey.Left:
back10_Click(this, null);
break;
case VirtualKey.Right:
fwd30_Click(this, null);
break;
}
}
private void subsLang_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
LoadTrack();
}
private void GotoLive_Click(object sender, RoutedEventArgs e) //TODO: Refactor
{
try
{
MediaTimeRange range = videoPlayer.PlaybackSession.GetSeekableRanges().Last();
controller.Position = range.End.Subtract(TimeSpan.FromMilliseconds(100));
}
catch { }
}
private void Seek_PointerCaptured(object sender, ManipulationStartedRoutedEventArgs e)
{
seekCaptured = true;
}
}
}
+26 -12
View File
@@ -12,7 +12,7 @@
<DefaultLanguage>en-US</DefaultLanguage> <DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier> <TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.17763.0</TargetPlatformVersion> <TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.17763.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.16299.0</TargetPlatformMinVersion> <TargetPlatformMinVersion>10.0.17134.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion> <MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
@@ -21,7 +21,7 @@
<PackageCertificateThumbprint>50B93E6A246058D555BA65CD203D7A02064A7409</PackageCertificateThumbprint> <PackageCertificateThumbprint>50B93E6A246058D555BA65CD203D7A02064A7409</PackageCertificateThumbprint>
<GenerateAppInstallerFile>False</GenerateAppInstallerFile> <GenerateAppInstallerFile>False</GenerateAppInstallerFile>
<AppxAutoIncrementPackageRevision>True</AppxAutoIncrementPackageRevision> <AppxAutoIncrementPackageRevision>True</AppxAutoIncrementPackageRevision>
<AppxPackageDir>D:\XFox\Documents\FoxTube builds\0.3\</AppxPackageDir> <AppxPackageDir>E:\XFox\Documents\FoxTube builds\0.4\</AppxPackageDir>
<AppxBundle>Always</AppxBundle> <AppxBundle>Always</AppxBundle>
<AppxBundlePlatforms>x86|x64|arm</AppxBundlePlatforms> <AppxBundlePlatforms>x86|x64|arm</AppxBundlePlatforms>
<AppInstallerUpdateFrequency>1</AppInstallerUpdateFrequency> <AppInstallerUpdateFrequency>1</AppInstallerUpdateFrequency>
@@ -103,7 +103,6 @@
<Compile Include="App.xaml.cs"> <Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon> <DependentUpon>App.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Classes\DownloadItemContainer.cs" />
<Compile Include="Classes\InboxItem.cs" /> <Compile Include="Classes\InboxItem.cs" />
<Compile Include="Classes\Methods.cs" /> <Compile Include="Classes\Methods.cs" />
<Compile Include="Classes\SearchPaameters.cs" /> <Compile Include="Classes\SearchPaameters.cs" />
@@ -130,6 +129,7 @@
<Compile Include="Controls\LiveCaptions.xaml.cs"> <Compile Include="Controls\LiveCaptions.xaml.cs">
<DependentUpon>LiveCaptions.xaml</DependentUpon> <DependentUpon>LiveCaptions.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Controls\Player\PlayerControls.cs" />
<Compile Include="Controls\PlaylistCard.xaml.cs"> <Compile Include="Controls\PlaylistCard.xaml.cs">
<DependentUpon>PlaylistCard.xaml</DependentUpon> <DependentUpon>PlaylistCard.xaml</DependentUpon>
</Compile> </Compile>
@@ -192,7 +192,7 @@
<Compile Include="Pages\VideoGrid.xaml.cs"> <Compile Include="Pages\VideoGrid.xaml.cs">
<DependentUpon>VideoGrid.xaml</DependentUpon> <DependentUpon>VideoGrid.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Controls\VideoPlayer.xaml.cs"> <Compile Include="Controls\Player\VideoPlayer.xaml.cs">
<DependentUpon>VideoPlayer.xaml</DependentUpon> <DependentUpon>VideoPlayer.xaml</DependentUpon>
</Compile> </Compile>
</ItemGroup> </ItemGroup>
@@ -209,6 +209,7 @@
<Content Include="Assets\BadgeLogo.scale-200.png" /> <Content Include="Assets\BadgeLogo.scale-200.png" />
<Content Include="Assets\BadgeLogo.scale-400.png" /> <Content Include="Assets\BadgeLogo.scale-400.png" />
<Content Include="Assets\ChannelCoverTemplate.png" /> <Content Include="Assets\ChannelCoverTemplate.png" />
<Content Include="Assets\Data\RevEn.xml" />
<Content Include="Assets\Data\Patchnotes.xml" /> <Content Include="Assets\Data\Patchnotes.xml" />
<Content Include="Assets\FoxGame.png" /> <Content Include="Assets\FoxGame.png" />
<Content Include="Assets\Icons\Profile.png" /> <Content Include="Assets\Icons\Profile.png" />
@@ -251,6 +252,7 @@
<Content Include="Assets\StoreLogo.scale-150.png" /> <Content Include="Assets\StoreLogo.scale-150.png" />
<Content Include="Assets\StoreLogo.scale-200.png" /> <Content Include="Assets\StoreLogo.scale-200.png" />
<Content Include="Assets\StoreLogo.scale-400.png" /> <Content Include="Assets\StoreLogo.scale-400.png" />
<Content Include="Assets\videoPlaceholder.png" />
<Content Include="Assets\videoThumbSample.png" /> <Content Include="Assets\videoThumbSample.png" />
<Content Include="Assets\WhatsNewThumb.png" /> <Content Include="Assets\WhatsNewThumb.png" />
<Content Include="Assets\Wide310x150Logo.scale-100.png" /> <Content Include="Assets\Wide310x150Logo.scale-100.png" />
@@ -380,14 +382,18 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Controls\VideoPlayer.xaml"> <Page Include="Controls\Player\VideoPlayer.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Themes\Generic.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AdaptiveCards.Rendering.Uwp"> <PackageReference Include="AdaptiveCards.Rendering.Uwp">
<Version>1.1.0</Version> <Version>1.1.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Google.Apis"> <PackageReference Include="Google.Apis">
<Version>1.30.0-beta02</Version> <Version>1.30.0-beta02</Version>
@@ -408,29 +414,34 @@
<Version>10.1811.22001</Version> <Version>10.1811.22001</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.AppCenter.Analytics"> <PackageReference Include="Microsoft.AppCenter.Analytics">
<Version>1.13.0</Version> <Version>1.13.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.1.9</Version> <Version>6.2.8</Version>
</PackageReference>
<PackageReference Include="Microsoft.Services.Store.Engagement">
<Version>10.1901.28001</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications"> <PackageReference Include="Microsoft.Toolkit.Uwp.Notifications">
<Version>5.0.0</Version> <Version>5.1.1</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls"> <PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls">
<Version>5.0.0</Version> <Version>5.1.1</Version>
</PackageReference>
<PackageReference Include="Microsoft.UI.Xaml">
<Version>2.0.181018004</Version>
</PackageReference> </PackageReference>
<PackageReference Include="runtime.win10-arm64.runtime.native.System.IO.Compression"> <PackageReference Include="runtime.win10-arm64.runtime.native.System.IO.Compression">
<Version>4.3.2</Version> <Version>4.3.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="YoutubeExplode"> <PackageReference Include="YoutubeExplode">
<Version>4.6.4</Version> <Version>4.6.7</Version>
</PackageReference> </PackageReference>
<PackageReference Include="YoutubeExtractor"> <PackageReference Include="YoutubeExtractor">
<Version>0.10.11</Version> <Version>0.10.11</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="FoxTube_TemporaryKey.pfx" />
<PRIResource Include="Strings\ru-RU\VideoPage.resw" /> <PRIResource Include="Strings\ru-RU\VideoPage.resw" />
<PRIResource Include="Strings\en-US\VideoPage.resw" /> <PRIResource Include="Strings\en-US\VideoPage.resw" />
<PRIResource Include="Strings\ru-RU\Channel.resw" /> <PRIResource Include="Strings\ru-RU\Channel.resw" />
@@ -476,6 +487,9 @@
<SDKReference Include="Microsoft.Advertising.Xaml, Version=10.0"> <SDKReference Include="Microsoft.Advertising.Xaml, Version=10.0">
<Name>Microsoft Advertising SDK for XAML</Name> <Name>Microsoft Advertising SDK for XAML</Name>
</SDKReference> </SDKReference>
<SDKReference Include="Microsoft.Services.Store.Engagement, Version=10.0">
<Name>Microsoft Engagement Framework</Name>
</SDKReference>
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup />
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' "> <PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
Binary file not shown.
Binary file not shown.
+5 -2
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" IgnorableNamespaces="uap mp uap3"> <Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" IgnorableNamespaces="uap mp uap3">
<Identity Name="53949MichaelXFoxGordeev.FoxTube" Publisher="CN=FD7A34DD-FE4D-4D7D-9D33-2DA9EBBE7725" Version="0.3.4.0" /> <Identity Name="53949MichaelXFoxGordeev.FoxTube" Publisher="CN=FD7A34DD-FE4D-4D7D-9D33-2DA9EBBE7725" Version="0.4.7.0" />
<mp:PhoneIdentity PhoneProductId="04fd81c1-6473-4174-afd7-4ac71dd85721" PhonePublisherId="00000000-0000-0000-0000-000000000000" /> <mp:PhoneIdentity PhoneProductId="04fd81c1-6473-4174-afd7-4ac71dd85721" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
<Properties> <Properties>
<DisplayName>FoxTube</DisplayName> <DisplayName>FoxTube</DisplayName>
@@ -15,7 +15,7 @@
</Resources> </Resources>
<Applications> <Applications>
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="FoxTube.App" ResourceGroup="foxtube"> <Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="FoxTube.App" ResourceGroup="foxtube">
<uap:VisualElements DisplayName="FoxTube" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" Description="YouTube Client for Windows 10" BackgroundColor="skyBlue"> <uap:VisualElements DisplayName="FoxTube" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" Description="YouTube Client for Windows 10" BackgroundColor="#282828">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" ShortName="FoxTube" Square310x310Logo="Assets\LargeTile.png" Square71x71Logo="Assets\SmallTile.png"> <uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" ShortName="FoxTube" Square310x310Logo="Assets\LargeTile.png" Square71x71Logo="Assets\SmallTile.png">
<uap:ShowNameOnTiles> <uap:ShowNameOnTiles>
<uap:ShowOn Tile="square150x150Logo" /> <uap:ShowOn Tile="square150x150Logo" />
@@ -27,6 +27,9 @@
<uap:LockScreen Notification="badgeAndTileText" BadgeLogo="Assets\BadgeLogo.png"/> <uap:LockScreen Notification="badgeAndTileText" BadgeLogo="Assets\BadgeLogo.png"/>
</uap:VisualElements> </uap:VisualElements>
<Extensions> <Extensions>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="foxtube"/>
</uap:Extension>
<Extension Category="windows.backgroundTasks" EntryPoint="FoxTube.Background.BackgroundProcessor"> <Extension Category="windows.backgroundTasks" EntryPoint="FoxTube.Background.BackgroundProcessor">
<BackgroundTasks> <BackgroundTasks>
<Task Type="general" /> <Task Type="general" />
+1 -3
View File
@@ -1,11 +1,9 @@
<Page <Page
x:Class="FoxTube.Pages.Browser" x:Class="FoxTube.Pages.Home1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FoxTube.Pages"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 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" mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
+7 -42
View File
@@ -1,55 +1,20 @@
using Google.Apis.Http; using Windows.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
namespace FoxTube.Pages namespace FoxTube.Pages
{ {
/// <summary> public sealed partial class Home1 : Page
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class Browser : Page
{ {
public Browser() public Home1()
{ {
InitializeComponent(); InitializeComponent();
Initialize();
} }
public async void Initialize() public async void Initialize()
{ {
/*Debug.WriteLine(SecretsVault.Credential.Token.AccessToken); SecretsVault.HttpClient.DefaultRequestHeaders.Referrer = "https://youtube.com/".ToUri();
WebClient client = new WebClient(); string response = await SecretsVault.Service.HttpClient.GetStringAsync(adress.Text);
client.Headers.Add(HttpRequestHeader.Cookie, "SID=9wYUCqAm2D7AmC_Vi8uNGjYZAf6Js2hasI1gCEhznMjJbYqnt0J6m1sthArcXG_pMMadnQ.; HSID=AhyajPo6nPBx7VB-0; SSID=AaaOvEW6jZVcc4Asp; APISID=tXeMRBKErzlt6KOo/Aapw7Rv4U_HG1A0CQ; SAPISID=FGp4Ff7MMF8Yq0X4/AOdNjGueWyCkkK7C5; LOGIN_INFO=AFmmF2swRAIgZln6SD5aFUlABb9pBEq9uAwLBISe7sYR1NWVXyaDTY4CIBLo_KAFcoo4wtlW0ZPmJnHaa-xVhsA7MzdGm7-vvgX-:QUQ3MjNmekJTZ3M2dXJNaFh3M3NfTFVDS0RIaUM3WlJNWlRJbk5sZUE1eHR3bHkwckhQeEppazkyekhDb0ljcXpacDdwQXlIanhSbnpSWkUyZVFpdWtiT243Rzhad0N4aGZwUXJDZ1Mxd0tFTS0wVDdudk9xaFJDdTNYUWtnQlE3VXhQdVl5MjB2MGdEdl9keElDaS1yX0tmQWowS041ZWF1VU9tV0c3bTRVbWNGSHFjWHRDVTIw;");*/ code.Text = response;
HttpClient client = new HttpClient(); //view.NavigateToString(response);
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "BQcUCusfz2zKN_ejHc3Xu15ahz8eEEaKouKJydqBAVKxWxcqfhht1zVux-G9bRf0KSTFBw");
/*SecretsVault.Credential.ToString();
string url = $"https://www.youtube.com/list_ajax?style=json&action_get_list=1&list=HL&hl=en";
string response = client.DownloadString(url.ToUri());
HttpClient c = new HttpClient();*/
string response = await SecretsVault.Service.HttpClient.GetStringAsync("https://www.youtube.com/list_ajax?style=xml&action_get_list=1&list=HL");
//HttpResponseMessage res = await c.GetAsync("");
Debug.WriteLine(response);
/*new Google.Apis.Oauth2.v2.Oauth2Service.Initializer();
code.Text = response;*/
} }
private void Adress_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) private void Adress_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
+55 -68
View File
@@ -8,9 +8,10 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:pages="using:FoxTube.Pages" xmlns:pages="using:FoxTube.Pages"
xmlns:controls="using:FoxTube.Controls" xmlns:controls="using:FoxTube.Controls"
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid Name="grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" SizeChanged="Grid_SizeChanged">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition/> <RowDefinition/>
<RowDefinition Height="auto"/> <RowDefinition Height="auto"/>
@@ -18,88 +19,74 @@
<Pivot SelectedIndex="0" Name="content" IsHeaderItemsCarouselEnabled="False" SelectionChanged="Content_SelectionChanged"> <Pivot SelectedIndex="0" Name="content" IsHeaderItemsCarouselEnabled="False" SelectionChanged="Content_SelectionChanged">
<PivotItem x:Uid="/Channel/videos" Header="Videos"> <PivotItem x:Uid="/Channel/videos" Header="Videos">
<ScrollViewer> <Grid>
<StackPanel Name="videos"> <ParallaxView Source="{x:Bind videoScroll}" VerticalShift="100">
<Image Name="channelCover" Stretch="Uniform" Source="/Assets/ChannelCoverTemplate.png"/>
<Grid> <Grid>
<Grid.ColumnDefinitions> <Image Source="/Assets/ChannelCoverTemplate.png" Name="channelCover" Stretch="Uniform" HorizontalAlignment="Left" VerticalAlignment="Top" Opacity="0" ImageOpened="ChannelCover_ImageOpened">
<ColumnDefinition Width="120"/> <Windows10version1809:Image.OpacityTransition>
<ColumnDefinition Width="*"/> <ScalarTransition Duration="0:0:0.5"/>
<ColumnDefinition Width="Auto"/> </Windows10version1809:Image.OpacityTransition>
</Grid.ColumnDefinitions> </Image>
<Ellipse HorizontalAlignment="Left" Margin="10,-40,0,0" Fill="Black" Width="100" Height="100"/>
<PersonPicture Name="avatar" HorizontalAlignment="Left" Margin="10,-40,0,0"/>
<StackPanel Grid.Column="1" Orientation="Vertical" Margin="10,0,0,5">
<TextBlock Name="title" FontWeight="SemiBold" FontSize="22" Text="Channel name"/>
<TextBlock Name="subscribers" Foreground="Gray" Text="1,000,000 subscribers"/>
<TextBlock Name="videosCount" Foreground="Gray" Text="563,000 videos"/>
</StackPanel>
<TextBlock Grid.Column="2" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Height="50" Margin="10" TextAlignment="Center" Padding="0,16,0,0" Foreground="Gray">
<Hyperlink Click="Hyperlink_Click"><Run x:Uid="/Cards/login">Log in</Run></Hyperlink> <Run x:Uid="/Cards/tomanage">to manage your subscriptions</Run>
</TextBlock>
<Grid Visibility="Collapsed" Grid.Column="2" VerticalAlignment="Bottom" Margin="10" Name="subscriptionPane" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button x:Uid="/Cards/subscribe" Click="Subscribe_Click" Name="subscribe" Width="250" Height="50" Background="Red" Foreground="White" FontSize="18" FontWeight="SemiBold" Content="Subscirbe"/>
</Grid>
</Grid> </Grid>
<pages:VideoGrid/> </ParallaxView>
<controls:ShowMore Clicked="VideoMore_Clicked"/> <ScrollViewer ViewChanged="ScrollViewer_ViewChanged" Name="videoScroll">
</StackPanel> <StackPanel Background="{ThemeResource AppBarBackgroundThemeBrush}" Margin="0,300,0,0" Name="infoStack" Visibility="Visible">
</ScrollViewer> <Grid Name="infoPanel">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Ellipse HorizontalAlignment="Left" Margin="10,-40,0,0" Fill="Black" Width="100" Height="1"/>
<PersonPicture Name="avatar" HorizontalAlignment="Left" Margin="10,-40,0,0"/>
<StackPanel Grid.Column="1" Orientation="Vertical" Margin="10,0,0,5">
<TextBlock Name="title" FontWeight="SemiBold" FontSize="22" Text="Channel name"/>
<TextBlock Name="subscribers" Foreground="Gray" Text="1,000,000 subscribers"/>
<TextBlock Name="videosCount" Foreground="Gray" Text="563,000 videos"/>
</StackPanel>
<TextBlock Grid.Column="2" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Height="50" Margin="10" TextAlignment="Center" Padding="0,16,0,0" Foreground="Gray">
<Hyperlink Click="Hyperlink_Click"><Run x:Uid="/Cards/login">Log in</Run></Hyperlink> <Run x:Uid="/Cards/tomanage">to manage your subscriptions</Run>
</TextBlock>
<Grid Visibility="Collapsed" Grid.Column="2" VerticalAlignment="Bottom" Margin="10" Name="subscriptionPane" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button x:Uid="/Cards/subscribe" Click="Subscribe_Click" Name="subscribe" Width="250" Height="50" Background="Red" Foreground="White" FontSize="18" FontWeight="SemiBold" Content="Subscirbe"/>
</Grid>
</Grid>
<pages:VideoGrid x:Name="videoList"/>
<controls:ShowMore Clicked="VideoMore_Clicked" x:Name="videoMore"/>
</StackPanel>
</ScrollViewer>
</Grid>
</PivotItem> </PivotItem>
<PivotItem x:Uid="/Channel/playlists" Header="Playlists"> <PivotItem x:Uid="/Channel/playlists" Header="Playlists">
<ScrollViewer> <ScrollViewer>
<Grid Name="playlists"> <Grid>
<StackPanel Margin="10" Visibility="Visible"> <StackPanel Margin="10" Visibility="Visible">
<TextBlock x:Uid="/Channel/playlistTitle" FontSize="28" Text="Playlists"/> <pages:VideoGrid x:Name="playlistList"/>
<pages:VideoGrid/> <controls:ShowMore Clicked="ShowMorePlaylists_Click" x:Name="playlistMore"/>
<controls:ShowMore Clicked="ShowMorePlaylists_Click"/>
</StackPanel> </StackPanel>
<local:LoadingPage Visibility="Collapsed"/> <local:LoadingPage Visibility="Collapsed" x:Name="playlistLoading"/>
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>
</PivotItem> </PivotItem>
<PivotItem x:Uid="/Channel/about" Header="About channel"> <PivotItem x:Uid="/Channel/about" Header="About channel">
<ScrollViewer> <ScrollViewer>
<StackPanel Margin="10"> <TextBlock Name="description" TextWrapping="WrapWholeWords" Margin="10" IsTextSelectionEnabled="True" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum justo erat, dapibus sit amet maximus eget, volutpat non turpis. Suspendisse. "/>
<TextBlock x:Uid="/Channel/aboutTitle" FontSize="28" Text="About this channel"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock x:Uid="/Channel/desc" FontSize="24" Text="Description"/>
<TextBlock Name="description" Margin="0,10,0,0" TextWrapping="WrapWholeWords" IsTextSelectionEnabled="True"/>
</StackPanel>
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock x:Uid="/Channel/stats" FontSize="24" Text="Statistics"/>
<TextBlock x:Uid="/Channel/regDate" Text="Registration date:" Grid.Row="1"/>
<TextBlock Grid.Row="1" Grid.Column="2" Name="registration" Text="13-May-18" HorizontalAlignment="Right"/>
<TextBlock x:Uid="/Channel/views" Grid.Row="2" Text="Views"/>
<TextBlock Grid.Row="2" Grid.Column="2" Name="views" Text="1885510485" HorizontalAlignment="Right"/>
</Grid>
</Grid>
</StackPanel>
</ScrollViewer> </ScrollViewer>
</PivotItem> </PivotItem>
<Pivot.RightHeader> <Pivot.RightHeader>
<AutoSuggestBox x:Uid="/Channel/search" VerticalAlignment="Center" Width="250" Margin="8" PlaceholderText="Search on channel" QueryIcon="Find" Name="search" QuerySubmitted="AutoSuggestBox_QuerySubmitted"/> <StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal" Name="ColapsedHeader" Opacity="0">
<Windows10version1809:StackPanel.OpacityTransition>
<ScalarTransition/>
</Windows10version1809:StackPanel.OpacityTransition>
<PersonPicture Height="32" Name="collapsedAvatar"/>
<TextBlock Text="Channel name" VerticalAlignment="Center" Margin="10,0" Name="collapsedTitle"/>
<Button x:Uid="/Cards/subscribe" Background="Red" Foreground="White" FontWeight="SemiBold" Content="Subscribe" Width="150" Name="collapsedBtn" Click="Subscribe_Click" Padding="2"/>
</StackPanel>
<AutoSuggestBox x:Uid="/Channel/search" VerticalAlignment="Center" Width="250" Margin="8" PlaceholderText="Search on channel" QueryIcon="Find" Name="search" QuerySubmitted="AutoSuggestBox_QuerySubmitted"/>
</StackPanel>
</Pivot.RightHeader> </Pivot.RightHeader>
</Pivot> </Pivot>
@@ -109,6 +96,6 @@
<AppBarButton x:Uid="/Channel/share" Icon="Share" Label="Share" Name="share" Click="Share_Click"/> <AppBarButton x:Uid="/Channel/share" Icon="Share" Label="Share" Name="share" Click="Share_Click"/>
</CommandBar> </CommandBar>
<local:LoadingPage Grid.RowSpan="2" Visibility="Collapsed"/> <local:LoadingPage Grid.RowSpan="2" Visibility="Collapsed" x:Name="loading" RefreshPage="Refresh_Click"/>
</Grid> </Grid>
</Page> </Page>
+89 -91
View File
@@ -21,42 +21,30 @@ namespace FoxTube.Pages
/// <summary> /// <summary>
/// Channel page /// Channel page
/// </summary> /// </summary>
public sealed partial class ChannelPage : Page public sealed partial class ChannelPage : Page, NavigationPage
{ {
public object Parameter { get; set; } = null;
readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Cards"); readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Cards");
public string channelId; public string channelId;
public Channel item; public Channel item;
readonly LoadingPage loading, playlistLoading; SearchResource.ListRequest request;
readonly VideoGrid videoList, playlistList;
readonly ShowMore videoMore, playlistMore;
SearchResource.ListRequest videoRequest, playlistRequest; private string videoToken, playlistToken;
private string videoToken;
private string playlistToken;
private bool playlistLoaded = false; private bool playlistLoaded = false;
public ChannelPage() public ChannelPage()
{ {
InitializeComponent(); InitializeComponent();
loading = grid.Children[2] as LoadingPage;
playlistLoading = playlists.Children[1] as LoadingPage;
videoList = videos.Children[2] as VideoGrid;
playlistList = (playlists.Children[0] as StackPanel).Children[1] as VideoGrid;
videoMore = videos.Children[3] as ShowMore;
playlistMore = (playlists.Children[0] as StackPanel).Children[2] as ShowMore;
loading.RefreshPage += Refresh_Click;
DataTransferManager.GetForCurrentView().DataRequested += new TypedEventHandler<DataTransferManager, DataRequestedEventArgs>(Share); DataTransferManager.GetForCurrentView().DataRequested += new TypedEventHandler<DataTransferManager, DataRequestedEventArgs>(Share);
} }
protected override void OnNavigatedTo(NavigationEventArgs e) protected override void OnNavigatedTo(NavigationEventArgs e)
{ {
base.OnNavigatedTo(e); base.OnNavigatedTo(e);
Parameter = e.Parameter;
if ((string)e.Parameter == null) if ((string)e.Parameter == null)
loading.Error("NullReferenceException", "Unable to initialize search. Search term is not stated."); loading.Error("NullReferenceException", "Unable to initialize search. Search term is not stated.");
else else
@@ -65,60 +53,50 @@ namespace FoxTube.Pages
public async void Initialize(string id) public async void Initialize(string id)
{ {
content.SelectedIndex = 0;
loading.Refresh(); loading.Refresh();
playlistLoading.Refresh(); playlistLoading.Refresh();
try try
{ {
channelId = id; if (id == SecretsVault.AccountId)
if (Methods.NeedToResponse) {
Methods.MainPage.Content_Navigated(this, null); infoPanel.ColumnDefinitions[2].Width = new GridLength(0);
collapsedBtn.Visibility = Visibility.Collapsed;
}
ChannelsResource.ListRequest request = SecretsVault.Service.Channels.List("snippet,statistics,brandingSettings"); ChannelsResource.ListRequest infoRequest = SecretsVault.Service.Channels.List("snippet,statistics,brandingSettings");
request.Id = id; infoRequest.Id = channelId = id;
if (content.Items.Count == 4)
content.Items.RemoveAt(3);
item = (await request.ExecuteAsync()).Items[0]; item = (await infoRequest.ExecuteAsync()).Items[0];
title.Text = item.Snippet.Title; title.Text = collapsedTitle.Text = item.Snippet.Title;
subscribers.Text = $"{item.Statistics.SubscriberCount:0,0} {resources.GetString("/Cards/subscribers")}"; subscribers.Text = $"{item.Statistics.SubscriberCount:0,0} {resources.GetString("/Cards/subscribers")}";
videosCount.Text = $"{item.Statistics.VideoCount:0,0} {resources.GetString("/Cards/videos")}"; videosCount.Text = $"{item.Statistics.VideoCount:0,0} {resources.GetString("/Cards/videos")}";
try if (!item.BrandingSettings.Image.BannerImageUrl.Contains("default"))
{ try { channelCover.Source = new BitmapImage(item.BrandingSettings.Image.BannerImageUrl.ToUri()); }
if (item.BrandingSettings.Image.BannerImageUrl.Contains("default")) catch { }
throw new Exception("Default channel cover detected");
channelCover.Source = new BitmapImage(new Uri(item.BrandingSettings.Image.BannerImageUrl)); try { avatar.ProfilePicture = collapsedAvatar.ProfilePicture = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri()) { DecodePixelHeight = 100, DecodePixelWidth = 100 }; }
}
catch { }
try { avatar.ProfilePicture = new BitmapImage(new Uri(item.Snippet.Thumbnails.Medium.Url)); }
catch { } catch { }
Methods.FormatText(ref description, item.Snippet.Description); Methods.FormatText(ref description, item.Snippet.Description);
views.Text = $"{item.Statistics.ViewCount:0,0}";
registration.Text = item.Snippet.PublishedAt.ToString();
videoRequest = SecretsVault.Service.Search.List("id"); request = SecretsVault.Service.Search.List("id");
videoRequest.ChannelId = id; request.ChannelId = id;
videoRequest.Type = "video"; request.Type = "video";
videoRequest.Order = SearchResource.ListRequest.OrderEnum.Date; request.Order = SearchResource.ListRequest.OrderEnum.Date;
videoRequest.MaxResults = 48; request.MaxResults = 25;
SearchListResponse response = await videoRequest.ExecuteAsync(); SearchListResponse response = await request.ExecuteAsync();
videoList.Clear();
foreach (SearchResult i in response.Items) foreach (SearchResult i in response.Items)
{ videoList.Add(new VideoCard(i.Id.VideoId));
VideoCard card = new VideoCard(i.Id.VideoId);
videoList.Add(card);
}
if (!string.IsNullOrWhiteSpace(response.NextPageToken)) if (!string.IsNullOrWhiteSpace(response.NextPageToken))
videoToken = response.NextPageToken; videoToken = response.NextPageToken;
else else
videoMore.Complete(true); videoMore.Visibility = Visibility.Collapsed;
if (SecretsVault.IsAuthorized) if (SecretsVault.IsAuthorized)
{ {
@@ -127,6 +105,7 @@ namespace FoxTube.Pages
subscribe.Background = new SolidColorBrush(Colors.Transparent); subscribe.Background = new SolidColorBrush(Colors.Transparent);
subscribe.Foreground = new SolidColorBrush(Colors.Gray); subscribe.Foreground = new SolidColorBrush(Colors.Gray);
subscribe.Content = resources.GetString("/Cards/unsubscribe"); subscribe.Content = resources.GetString("/Cards/unsubscribe");
collapsedBtn.Visibility = Visibility.Collapsed;
} }
subscriptionPane.Visibility = Visibility.Visible; subscriptionPane.Visibility = Visibility.Visible;
} }
@@ -147,6 +126,8 @@ namespace FoxTube.Pages
{ "Channel ID", channelId } { "Channel ID", channelId }
}); });
} }
ScrollViewer_ViewChanged(this, null);
} }
async void LoadPlaylist() async void LoadPlaylist()
@@ -155,20 +136,12 @@ namespace FoxTube.Pages
{ {
playlistLoading.Refresh(); playlistLoading.Refresh();
playlistRequest = SecretsVault.Service.Search.List("id"); request.Type = "playlist";
playlistRequest.ChannelId = channelId;
playlistRequest.Order = SearchResource.ListRequest.OrderEnum.Date;
playlistRequest.Type = "playlist";
playlistRequest.MaxResults = 48;
SearchListResponse response = await playlistRequest.ExecuteAsync(); SearchListResponse response = await request.ExecuteAsync();
playlistList.Clear();
foreach (SearchResult i in response.Items) foreach (SearchResult i in response.Items)
{ playlistList.Add(new PlaylistCard(i.Id.PlaylistId));
PlaylistCard card = new PlaylistCard(i.Id.PlaylistId);
playlistList.Add(card);
}
if (!string.IsNullOrWhiteSpace(response.NextPageToken)) if (!string.IsNullOrWhiteSpace(response.NextPageToken))
playlistToken = response.NextPageToken; playlistToken = response.NextPageToken;
@@ -182,9 +155,15 @@ namespace FoxTube.Pages
{ {
playlistLoading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true); playlistLoading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
} }
catch catch (Exception e)
{ {
playlistLoading.Error(); playlistLoading.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("Channel playlists list loading error", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Channel ID", channelId }
});
} }
} }
@@ -192,46 +171,47 @@ namespace FoxTube.Pages
{ {
if (content.SelectedIndex == 1 && !playlistLoaded) if (content.SelectedIndex == 1 && !playlistLoaded)
LoadPlaylist(); LoadPlaylist();
if (content.SelectedIndex == 0)
ScrollViewer_ViewChanged(this, null);
else
ColapsedHeader.Opacity = 1;
} }
private async void ShowMorePlaylists_Click() private async void ShowMorePlaylists_Click()
{ {
playlistRequest.PageToken = playlistToken; request.Type = "playlist";
SearchListResponse response = await playlistRequest.ExecuteAsync(); request.PageToken = playlistToken;
SearchListResponse response = await request.ExecuteAsync();
foreach (SearchResult i in response.Items) foreach (SearchResult i in response.Items)
{ playlistList.Add(new PlaylistCard(i.Id.PlaylistId));
PlaylistCard card = new PlaylistCard(i.Id.PlaylistId);
playlistList.Add(card);
}
if (!string.IsNullOrWhiteSpace(response.NextPageToken)) if (string.IsNullOrWhiteSpace(response.NextPageToken))
playlistMore.Visibility = Visibility.Collapsed;
else
{ {
playlistToken = response.NextPageToken; playlistToken = response.NextPageToken;
playlistMore.Complete(); playlistMore.Complete();
} }
else
playlistMore.Complete(true);
} }
private async void VideoMore_Clicked() private async void VideoMore_Clicked()
{ {
videoRequest.PageToken = videoToken; request.Type = "video";
SearchListResponse response = await videoRequest.ExecuteAsync(); request.PageToken = videoToken;
SearchListResponse response = await request.ExecuteAsync();
foreach (SearchResult i in response.Items) foreach (SearchResult i in response.Items)
{ videoList.Add(new VideoCard(i.Id.VideoId));
VideoCard card = new VideoCard(i.Id.VideoId);
videoList.Add(card);
}
if (!string.IsNullOrWhiteSpace(response.NextPageToken)) if (string.IsNullOrWhiteSpace(response.NextPageToken))
videoMore.Visibility = Visibility.Collapsed;
else
{ {
videoToken = response.NextPageToken; videoToken = response.NextPageToken;
videoMore.Complete(); videoMore.Complete();
} }
else
videoMore.Complete(true);
} }
private async void Subscribe_Click(object sender, RoutedEventArgs e) private async void Subscribe_Click(object sender, RoutedEventArgs e)
@@ -241,12 +221,14 @@ namespace FoxTube.Pages
subscribe.Background = new SolidColorBrush(Colors.Transparent); subscribe.Background = new SolidColorBrush(Colors.Transparent);
subscribe.Foreground = new SolidColorBrush(Colors.Gray); subscribe.Foreground = new SolidColorBrush(Colors.Gray);
subscribe.Content = resources.GetString("/Cards/unsubscribe"); subscribe.Content = resources.GetString("/Cards/unsubscribe");
collapsedBtn.Visibility = Visibility.Collapsed;
} }
else else
{ {
subscribe.Background = new SolidColorBrush(Colors.Red); subscribe.Background = new SolidColorBrush(Colors.Red);
subscribe.Foreground = new SolidColorBrush(Colors.White); subscribe.Foreground = new SolidColorBrush(Colors.White);
subscribe.Content = resources.GetString("/Cards/subscribe/Content"); subscribe.Content = resources.GetString("/Cards/subscribe/Content");
collapsedBtn.Visibility = Visibility.Visible;
} }
} }
@@ -259,21 +241,29 @@ namespace FoxTube.Pages
{ {
if(search.Text.Length > 2) if(search.Text.Length > 2)
{ {
if (content.Items.Count == 4) if(content.Items.Count < 4)
((content.Items[3] as PivotItem).Content as Frame).Navigate(typeof(Search), new SearchParameters(search.Text, item.Id));
else
{
content.Items.Add(new PivotItem() content.Items.Add(new PivotItem()
{ {
Content = new Frame() Header = resources.GetString("/Channel/searchHeader"),
Content = new Search()
}); });
((content.Items[3] as PivotItem).Content as Frame).Navigate(typeof(Search), new SearchParameters(search.Text, item.Id));
}
((content.Items[3] as PivotItem).Content as Search).Initialize(new SearchParameters(search.Text, item.Id));
content.SelectedIndex = 3; content.SelectedIndex = 3;
} }
} }
private void ScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
Rect panel = infoPanel.TransformToVisual(videoScroll).TransformBounds(new Rect(0.0, 0.0, infoPanel.ActualWidth, infoPanel.ActualHeight));
Rect view = new Rect(0.0, 0.0, videoScroll.ActualWidth, videoScroll.ActualHeight);
if (view.Contains(new Point(panel.Left, panel.Bottom)))
ColapsedHeader.Opacity = 0;
else
ColapsedHeader.Opacity = 1;
}
private void Refresh_Click(object sender, RoutedEventArgs e) private void Refresh_Click(object sender, RoutedEventArgs e)
{ {
Methods.MainPage.GoToChannel(channelId); Methods.MainPage.GoToChannel(channelId);
@@ -281,10 +271,7 @@ namespace FoxTube.Pages
private async void InBrowser_Click(object sender, RoutedEventArgs e) private async void InBrowser_Click(object sender, RoutedEventArgs e)
{ {
if (!string.IsNullOrWhiteSpace(item.Snippet.CustomUrl)) await Launcher.LaunchUriAsync(new Uri($"https://www.youtube.com/channel/{item.Id}"));
await Launcher.LaunchUriAsync(new Uri($"https://www.youtube.com/user/{item.Snippet.CustomUrl}"));
else
await Launcher.LaunchUriAsync(new Uri($"https://www.youtube.com/channel/{item.Id}"));
} }
private void Share_Click(object sender, RoutedEventArgs e) private void Share_Click(object sender, RoutedEventArgs e)
@@ -292,6 +279,17 @@ namespace FoxTube.Pages
DataTransferManager.ShowShareUI(); DataTransferManager.ShowShareUI();
} }
private void ChannelCover_ImageOpened(object sender, RoutedEventArgs e)
{
channelCover.Opacity = 1;
infoStack.Margin = new Thickness(0, channelCover.ActualHeight, 0, 0);
}
private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
{
infoStack.Margin = new Thickness(0, channelCover.ActualHeight, 0, 0);
}
private void Share(DataTransferManager sender, DataRequestedEventArgs args) private void Share(DataTransferManager sender, DataRequestedEventArgs args)
{ {
Methods.Share(args, Methods.Share(args,
+9 -3
View File
@@ -4,6 +4,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:FoxTube.Controls"
mc:Ignorable="d" mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
@@ -43,9 +44,14 @@
<ScrollViewer Grid.Row="1" Name="scroll"> <ScrollViewer Grid.Row="1" Name="scroll">
<StackPanel> <StackPanel>
<StackPanel Name="placeholder"/> <StackPanel Name="placeholder">
<HyperlinkButton x:Uid="/CommentsPage/more" Visibility="Collapsed" Name="more" Click="more_Click" HorizontalAlignment="Center" Foreground="Red" Content="Show more"/> <StackPanel.ChildrenTransitions>
<ProgressBar Name="moreLoading" Visibility="Collapsed" IsIndeterminate="True" Foreground="Red"/> <TransitionCollection>
<EntranceThemeTransition IsStaggeringEnabled="True"/>
</TransitionCollection>
</StackPanel.ChildrenTransitions>
</StackPanel>
<controls:ShowMore x:Name="more" Clicked="ShowMore_Clicked"/>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>
+92 -101
View File
@@ -17,9 +17,10 @@ namespace FoxTube.Pages
ResourceLoader resources = ResourceLoader.GetForCurrentView("CommentsPage"); ResourceLoader resources = ResourceLoader.GetForCurrentView("CommentsPage");
string threadId; string threadId;
string nextPageToken; string token;
CommentThreadsResource.ListRequest.OrderEnum order = CommentThreadsResource.ListRequest.OrderEnum.Relevance; CommentThreadsResource.ListRequest.OrderEnum order = CommentThreadsResource.ListRequest.OrderEnum.Relevance;
CommentThreadsResource.ListRequest request;
public CommentsPage() public CommentsPage()
{ {
@@ -33,144 +34,134 @@ namespace FoxTube.Pages
if (!SecretsVault.IsAuthorized) if (!SecretsVault.IsAuthorized)
grid.RowDefinitions[0].Height = new GridLength(0); grid.RowDefinitions[0].Height = new GridLength(0);
else
grid.RowDefinitions[0].Height = GridLength.Auto;
counter.Text = $"{video.Statistics.CommentCount:0,0} {resources.GetString("/CommentsPage/comments")}"; counter.Text = $"{video.Statistics.CommentCount:0,0} {resources.GetString("/CommentsPage/comments")}";
orderBtn.Content = resources.GetString("/CommentsPage/relevance/Text"); orderBtn.Content = resources.GetString("/CommentsPage/relevance/Text");
var request = SecretsVault.Service.CommentThreads.List("snippet,replies"); request = SecretsVault.Service.CommentThreads.List("snippet,replies");
request.MaxResults = 25;
request.Order = order; request.Order = order;
request.VideoId = video.Id; request.VideoId = video.Id;
request.TextFormat = CommentThreadsResource.ListRequest.TextFormatEnum.PlainText; request.TextFormat = CommentThreadsResource.ListRequest.TextFormatEnum.PlainText;
var response = await request.ExecuteAsync(); var response = await request.ExecuteAsync();
if(response.NextPageToken != null) token = response.NextPageToken;
{ if (string.IsNullOrWhiteSpace(token))
nextPageToken = response.NextPageToken; more.Visibility = Visibility.Collapsed;
more.Visibility = Visibility.Visible;
}
foreach (CommentThread comment in response.Items) foreach (CommentThread comment in response.Items)
placeholder.Children.Add(new CommentCard(comment)); placeholder.Children.Add(new CommentCard(comment));
} }
public void RemoveComment(CommentCard commentCard) public void RemoveComment(CommentCard commentCard, string topCommentId = null)
{ {
placeholder.Children.Remove(commentCard); if (string.IsNullOrWhiteSpace(topCommentId))
} placeholder.Children.Remove(commentCard);
else
private async void more_Click(object sender, RoutedEventArgs e) (placeholder.Children.Find(i => (i as CommentCard).thread.Id == topCommentId) as CommentCard).DeleteComment(commentCard);
{
more.Visibility = Visibility.Collapsed;
moreLoading.Visibility = Visibility.Visible;
var request = SecretsVault.Service.CommentThreads.List("snippet,replies");
request.Order = order;
request.VideoId = threadId;
request.TextFormat = CommentThreadsResource.ListRequest.TextFormatEnum.PlainText;
request.PageToken = nextPageToken;
var response = await request.ExecuteAsync();
foreach (CommentThread comment in response.Items)
placeholder.Children.Add(new CommentCard(comment));
if(response.NextPageToken != null)
{
nextPageToken = response.NextPageToken;
more.Visibility = Visibility.Visible;
}
moreLoading.Visibility = Visibility.Collapsed;
} }
private async void toRelevance_Click(object sender, RoutedEventArgs e) private async void toRelevance_Click(object sender, RoutedEventArgs e)
{ {
if(order != CommentThreadsResource.ListRequest.OrderEnum.Relevance) if (order == CommentThreadsResource.ListRequest.OrderEnum.Relevance)
{ return;
more.Show();
order = CommentThreadsResource.ListRequest.OrderEnum.Relevance;
orderBtn.Content = resources.GetString("/CommentsPage/relevance/Text");
placeholder.Children.Clear();
request.Order = order;
var response = await request.ExecuteAsync();
token = response.NextPageToken;
if (string.IsNullOrWhiteSpace(token))
more.Visibility = Visibility.Collapsed; more.Visibility = Visibility.Collapsed;
moreLoading.Visibility = Visibility.Visible;
order = CommentThreadsResource.ListRequest.OrderEnum.Relevance; foreach (CommentThread comment in response.Items)
orderBtn.Content = resources.GetString("/CommentsPage/relevance/Text"); placeholder.Children.Add(new CommentCard(comment));
placeholder.Children.Clear(); more.Complete();
var request = SecretsVault.Service.CommentThreads.List("snippet,replies");
request.Order = order;
request.VideoId = threadId;
request.TextFormat = CommentThreadsResource.ListRequest.TextFormatEnum.PlainText;
var response = await request.ExecuteAsync();
nextPageToken = response.NextPageToken;
foreach (CommentThread comment in response.Items)
placeholder.Children.Add(new CommentCard(comment));
more.Visibility = Visibility.Visible;
moreLoading.Visibility = Visibility.Collapsed;
}
} }
private async void toDate_Click(object sender, RoutedEventArgs e) private async void toDate_Click(object sender, RoutedEventArgs e)
{ {
if (order != CommentThreadsResource.ListRequest.OrderEnum.Time) if (order == CommentThreadsResource.ListRequest.OrderEnum.Time)
{ return;
more.Show();
order = CommentThreadsResource.ListRequest.OrderEnum.Time;
orderBtn.Content = resources.GetString("/CommentsPage/publish");
placeholder.Children.Clear();
request.Order = order;
var response = await request.ExecuteAsync();
token = response.NextPageToken;
if (string.IsNullOrWhiteSpace(token))
more.Visibility = Visibility.Collapsed; more.Visibility = Visibility.Collapsed;
moreLoading.Visibility = Visibility.Visible;
order = CommentThreadsResource.ListRequest.OrderEnum.Time; foreach (CommentThread comment in response.Items)
orderBtn.Content = resources.GetString("/CommentsPage/publish"); placeholder.Children.Add(new CommentCard(comment));
placeholder.Children.Clear(); more.Complete();
var request = SecretsVault.Service.CommentThreads.List("snippet,replies");
request.Order = order;
request.VideoId = threadId;
request.TextFormat = CommentThreadsResource.ListRequest.TextFormatEnum.PlainText;
var response = await request.ExecuteAsync();
nextPageToken = response.NextPageToken;
foreach (CommentThread comment in response.Items)
placeholder.Children.Add(new CommentCard(comment));
more.Visibility = Visibility.Visible;
moreLoading.Visibility = Visibility.Collapsed;
}
} }
private async void send_Click(object sender, RoutedEventArgs e) private async void send_Click(object sender, RoutedEventArgs e)
{ {
if(newComment.Text.Length > 0) if (string.IsNullOrWhiteSpace(newComment.Text))
return;
newComment.IsEnabled = false;
send.IsEnabled = false;
sending.Visibility = Visibility.Visible;
CommentThread thread = new CommentThread()
{ {
newComment.IsEnabled = false; Snippet = new CommentThreadSnippet()
send.IsEnabled = false;
sending.Visibility = Visibility.Visible;
CommentThread thread = new CommentThread();
thread.Snippet = new CommentThreadSnippet();
Comment comment = new Comment();
comment.Snippet = new CommentSnippet();
comment.Snippet.TextOriginal = newComment.Text;
thread.Snippet.VideoId = threadId;
thread.Snippet.TopLevelComment = comment;
try
{ {
CommentThread response = await SecretsVault.Service.CommentThreads.Insert(thread, "snippet").ExecuteAsync(); TopLevelComment = new Comment()
newComment.Text = ""; {
placeholder.Children.Insert(0, new CommentCard(response)); Snippet = new CommentSnippet()
scroll.ChangeView(null, 0, null); {
} TextOriginal = newComment.Text
catch }
{ },
await new MessageDialog("Failed to publish your comment. Please, try again later.", "Failed to publish your comment").ShowAsync(); VideoId = threadId
} }
};
newComment.IsEnabled = true; try
send.IsEnabled = true; {
sending.Visibility = Visibility.Collapsed; CommentThread response = await SecretsVault.Service.CommentThreads.Insert(thread, "snippet").ExecuteAsync();
placeholder.Children.Insert(0, new CommentCard(response));
newComment.Text = "";
scroll.ChangeView(null, 0, null);
} }
catch { await new MessageDialog("Failed to publish your comment. Please, try again later.").ShowAsync(); }
newComment.IsEnabled = true;
send.IsEnabled = true;
sending.Visibility = Visibility.Collapsed;
}
private async void ShowMore_Clicked()
{
request.PageToken = token;
var response = await request.ExecuteAsync();
foreach (CommentThread comment in response.Items)
placeholder.Children.Add(new CommentCard(comment));
token = response.NextPageToken;
more.Complete();
if (string.IsNullOrWhiteSpace(token))
more.Visibility = Visibility.Collapsed;
} }
} }
} }
+11 -4
View File
@@ -22,13 +22,20 @@
<Button Grid.Column="1" x:Uid="/Downloads/openFolder" Content="Open folder" Name="open" Click="Open_Click" VerticalAlignment="Center"/> <Button Grid.Column="1" x:Uid="/Downloads/openFolder" Content="Open folder" Name="open" Click="Open_Click" VerticalAlignment="Center"/>
</Grid> </Grid>
<ScrollViewer Grid.Row="1">
<StackPanel Name="stack"/>
</ScrollViewer>
<TextBlock x:Uid="/Downloads/noItems" Grid.Row="1" Name="empty" HorizontalAlignment="Center" VerticalAlignment="Top" FontSize="28" Text="You haven't downloaded anything yet" Margin="10" TextWrapping="WrapWholeWords" Foreground="Gray" FontWeight="SemiBold"/> <TextBlock x:Uid="/Downloads/noItems" Grid.Row="1" Name="empty" HorizontalAlignment="Center" VerticalAlignment="Top" FontSize="28" Text="You haven't downloaded anything yet" Margin="10" TextWrapping="WrapWholeWords" Foreground="Gray" FontWeight="SemiBold"/>
<CommandBar DefaultLabelPosition="Right" Grid.Row="2"> <ScrollViewer Grid.Row="1">
<StackPanel Name="list">
<StackPanel.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition IsStaggeringEnabled="True"/>
</TransitionCollection>
</StackPanel.ChildrenTransitions>
</StackPanel>
</ScrollViewer>
<CommandBar Grid.Row="2">
<AppBarButton x:Uid="/Downloads/refresh" Label="Refresh" Icon="Refresh" Click="Refresh"/> <AppBarButton x:Uid="/Downloads/refresh" Label="Refresh" Icon="Refresh" Click="Refresh"/>
</CommandBar> </CommandBar>
</Grid> </Grid>
+21 -38
View File
@@ -1,59 +1,42 @@
using System; using FoxTube.Controls;
using System;
using Windows.System; using Windows.System;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace FoxTube.Pages namespace FoxTube.Pages
{ {
/// <summary> /// <summary>
/// Downloads page /// Downloads page
/// </summary> /// </summary>
public sealed partial class Downloads : Page public sealed partial class Downloads : Page, NavigationPage
{ {
public object Parameter { get; set; } = null;
public Downloads() public Downloads()
{ {
this.InitializeComponent(); InitializeComponent();
SetPath(); DownloadAgent.Page = this;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
stack.Children.Clear();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
DownloadAgent.items.ForEach(i =>
{
stack.Children.Add(i);
i.Initialize();
});
empty.Visibility = stack.Children.Count > 0 ? Visibility.Collapsed : Visibility.Visible;
}
void SetPath()
{
path.Text = DownloadAgent.Downloads.Path; path.Text = DownloadAgent.Downloads.Path;
} Refresh(this, null);
void Refresh(object sender, RoutedEventArgs e)
{
stack.Children.Clear();
DownloadAgent.items.ForEach(i =>
{
stack.Children.Add(i);
i.Initialize();
});
empty.Visibility = stack.Children.Count > 0 ? Visibility.Collapsed : Visibility.Visible;
} }
private async void Open_Click(object sender, RoutedEventArgs e) private async void Open_Click(object sender, RoutedEventArgs e)
{ {
await Launcher.LaunchFolderAsync(DownloadAgent.Downloads); await Launcher.LaunchFolderAsync(DownloadAgent.Downloads);
} }
private void Refresh(object sender, RoutedEventArgs e)
{
list.Children.Clear();
DownloadAgent.items.ForEach(i => list.Children.Add(i));
empty.Visibility = list.Children.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
}
public void Remove(DownloadItem item)
{
list.Children.Remove(item);
empty.Visibility = list.Children.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
}
} }
} }
+8 -5
View File
@@ -1,5 +1,4 @@
<Page <Page
NavigationCacheMode="Enabled"
x:Class="FoxTube.Pages.History" x:Class="FoxTube.Pages.History"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -7,21 +6,25 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:foxtube="using:FoxTube" xmlns:foxtube="using:FoxTube"
xmlns:controls="using:FoxTube.Controls"
mc:Ignorable="d" mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Name="grid"> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition/> <RowDefinition/>
<RowDefinition Height="auto"/> <RowDefinition Height="auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<ScrollViewer Name="scroll"> <ScrollViewer>
<local:VideoGrid/> <StackPanel>
<local:VideoGrid x:Name="list"/>
<controls:ShowMore Clicked="ShowMore_Clicked" x:Name="more"/>
</StackPanel>
</ScrollViewer> </ScrollViewer>
<CommandBar Grid.Row="1" DefaultLabelPosition="Right"> <CommandBar Grid.Row="1" DefaultLabelPosition="Right">
<AppBarButton Icon="Refresh" Label="Refresh" Name="refresh" Click="Refresh_Click"/> <AppBarButton Icon="Refresh" Label="Refresh" Name="refresh" Click="Refresh_Click"/>
<AppBarButton Label="Open in browser" Icon="Globe" Name="toBrowser" Click="toBrowser_Click"/> <AppBarButton Label="Open in browser" Icon="Globe" Name="toBrowser" Click="toBrowser_Click"/>
</CommandBar> </CommandBar>
<foxtube:LoadingPage Visibility="Collapsed" Grid.RowSpan="2"/> <foxtube:LoadingPage Visibility="Collapsed" Grid.RowSpan="2" x:Name="loading"/>
</Grid> </Grid>
</Page> </Page>
+56 -10
View File
@@ -1,4 +1,7 @@
using System; using FoxTube.Controls;
using Microsoft.AppCenter.Analytics;
using System;
using System.Collections.Generic;
using Windows.System; using Windows.System;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
@@ -9,10 +12,12 @@ namespace FoxTube.Pages
/// <summary> /// <summary>
/// YouTube history page /// YouTube history page
/// </summary> /// </summary>
public sealed partial class History : Page public sealed partial class History : Page, NavigationPage
{ {
LoadingPage loading; public object Parameter { get; set; } = null;
VideoGrid list; List<string> entries;
int page = 1;
public string id = "HL";
public History() public History()
{ {
@@ -22,27 +27,68 @@ namespace FoxTube.Pages
protected override void OnNavigatedTo(NavigationEventArgs e) protected override void OnNavigatedTo(NavigationEventArgs e)
{ {
base.OnNavigatedTo(e); base.OnNavigatedTo(e);
Parameter = e.Parameter;
loading.RefreshPage += Refresh_Click;
if (!string.IsNullOrWhiteSpace(e.Parameter.ToString()))
id = e.Parameter.ToString();
loading = grid.Children[2] as LoadingPage;
list = scroll.Content as VideoGrid;
Initialize(); Initialize();
} }
public void Initialize() public async void Initialize()
{ {
loading.Refresh(); try
{
loading.Refresh();
loading.Close(); entries = id == "HL" ? SecretsVault.History = await Methods.GetHistory() : SecretsVault.WatchLater = await Methods.GetLater();
for (int k = 0; k < 25 && k < entries.Count; k++)
list.Add(new VideoCard(entries[k]));
if (list.Count >= entries.Count)
more.Visibility = Visibility.Collapsed;
loading.Close();
}
catch (System.Net.Http.HttpRequestException)
{
loading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch (Exception e)
{
loading.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("History loading error", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "ID", id }
});
}
} }
private async void toBrowser_Click(object sender, RoutedEventArgs e) private async void toBrowser_Click(object sender, RoutedEventArgs e)
{ {
await Launcher.LaunchUriAsync(new Uri("youtube.com/feed/history")); await Launcher.LaunchUriAsync(new Uri(id == "HL" ? "https://www.youtube.com/feed/history" : "https://www.youtube.com/playlist?list=WL"));
} }
private void Refresh_Click(object sender, RoutedEventArgs e) private void Refresh_Click(object sender, RoutedEventArgs e)
{ {
list.Clear();
Initialize();
}
private void ShowMore_Clicked()
{
for (int k = 25 * page++; k < 25 * page; k++)
list.Add(new VideoCard(entries[k]));
if (list.Count >= entries.Count)
more.Visibility = Visibility.Collapsed;
else
more.Complete();
} }
} }
} }
+59 -26
View File
@@ -9,36 +9,69 @@
xmlns:controls="using:FoxTube.Controls" xmlns:controls="using:FoxTube.Controls"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Name="grid"> <Pivot SelectionChanged="pivot_SelectionChanged" Name="pivot">
<Grid.RowDefinitions> <PivotItem Name="recommended" Header="Recommended" x:Uid="/Home/recommended">
<RowDefinition/> <Grid>
<RowDefinition Height="auto"/> <Grid.RowDefinitions>
</Grid.RowDefinitions> <RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Pivot Name="pivot" SelectionChanged="pivot_SelectionChanged">
<PivotItem x:Uid="/Home/recommended" Header="Recommended" Name="recommended">
<ScrollViewer>
<StackPanel/>
</ScrollViewer>
</PivotItem>
<PivotItem x:Uid="/Home/trending" Header="Trending" Name="trending">
<ScrollViewer> <ScrollViewer>
<StackPanel> <StackPanel>
<pages:VideoGrid/> <pages:VideoGrid x:Name="recGrid"/>
<controls:ShowMore Clicked="TrendMore_Clicked"/> <controls:ShowMore Clicked="Recommended_More" x:Name="recommendedMore"/>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>
</PivotItem>
<PivotItem x:Uid="/Home/subs" Header="Subscriptions" Name="subscriptions">
<ScrollViewer>
<StackPanel/>
</ScrollViewer>
</PivotItem>
</Pivot>
<CommandBar Grid.Row="1"> <CommandBar Grid.Row="1">
<AppBarButton x:Uid="/Home/refresh" Icon="Refresh" Label="Refresh page" Name="refresh" Click="refreshPage"/> <AppBarButton Icon="Refresh" Label="Refresh" x:Uid="/Home/refresh" Click="Recommended_Refresh"/>
</CommandBar> </CommandBar>
<local:LoadingPage Grid.RowSpan="2" Margin="0,50,0,0" Visibility="Visible" RefreshPage="refreshPage"/>
</Grid> <local:LoadingPage x:Name="recsLoading" Grid.RowSpan="2" RefreshPage="Recommended_Refresh"/>
</Grid>
</PivotItem>
<PivotItem Name="trending" Header="Trending" x:Uid="/Home/trending">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<ScrollViewer>
<StackPanel>
<pages:VideoGrid x:Name="trendGrid"/>
<controls:ShowMore Clicked="Trending_More" x:Name="trendingMore"/>
</StackPanel>
</ScrollViewer>
<CommandBar Grid.Row="1">
<AppBarButton Icon="Refresh" Label="Refresh" x:Uid="/Home/refresh" Click="Trends_Refresh"/>
</CommandBar>
<local:LoadingPage x:Name="trendsLoading" Grid.RowSpan="2" RefreshPage="Trends_Refresh"/>
</Grid>
</PivotItem>
<PivotItem Name="subscriptions" Header="Subscriptions" x:Uid="/Home/subs">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<ScrollViewer>
<StackPanel>
<pages:VideoGrid x:Name="subsGrid"/>
<controls:ShowMore Clicked="Subscriptions_More" x:Name="subscriptionsMore"/>
</StackPanel>
</ScrollViewer>
<CommandBar Grid.Row="1">
<AppBarButton Icon="Refresh" Label="Refresh" x:Uid="/Home/refresh" Click="Subscriptions_Refresh"/>
</CommandBar>
<local:LoadingPage x:Name="subsLoading" Grid.RowSpan="2" RefreshPage="Subscriptions_Refresh"/>
</Grid>
</PivotItem>
</Pivot>
</Page> </Page>
+153 -100
View File
@@ -5,166 +5,219 @@ using Windows.UI.Xaml.Controls;
using Google.Apis.YouTube.v3; using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data; using Google.Apis.YouTube.v3.Data;
using FoxTube.Controls; using FoxTube.Controls;
using FoxTube.Pages; using System.Net.Http;
using System.Text.RegularExpressions;
using Microsoft.AppCenter.Analytics;
namespace FoxTube namespace FoxTube
{ {
/// <summary> /// <summary>
/// Home page /// Home page
/// </summary> /// </summary>
public sealed partial class Home : Page public sealed partial class Home : Page, NavigationPage
{ {
// TODO: Refactor home page public object Parameter { get; set; } = null;
private bool trendLoaded = false; private bool trendLoaded = false, recLoaded = false, subsLoaded = false;
VideoGrid trendGrid; List<string> homeList = new List<string>();
ShowMore trendMore; List<string> subsList = new List<string>();
LoadingPage loading;
string trendToken; VideosResource.ListRequest trendsRequest;
Dictionary<string, string> subsTokens = new Dictionary<string, string>();
public Home() public Home()
{ {
InitializeComponent(); InitializeComponent();
trendGrid = ((trending.Content as ScrollViewer).Content as StackPanel).Children[0] as VideoGrid;
trendMore = ((trending.Content as ScrollViewer).Content as StackPanel).Children[1] as ShowMore;
loading = grid.Children[2] as LoadingPage;
Initialize(); Initialize();
} }
private void refreshPage(object sender, RoutedEventArgs e)
{
Methods.MainPage.GoToHome();
}
private async void TrendMore_Clicked()
{
try
{
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("id");
request.MaxResults = 48;
request.PageToken = trendToken;
request.Chart = VideosResource.ListRequest.ChartEnum.MostPopular;
request.RegionCode = SettingsStorage.Region;
VideoListResponse response = await request.ExecuteAsync();
if (!string.IsNullOrWhiteSpace(response.NextPageToken))
trendToken = response.NextPageToken;
else
trendMore.Complete(true);
foreach (Video vid in response.Items)
{
VideoCard vCard = new VideoCard(vid.Id);
trendGrid.Add(vCard);
}
trendMore.Complete();
}
catch
{
trendMore.Complete(true);
}
}
public void Initialize() public void Initialize()
{ {
/*if(SecretsVault.IsAuthorized) if(!SecretsVault.IsAuthorized)
LoadRecommendations();
else*/
{ {
pivot.Items.Remove(recommended); pivot.Items.Remove(recommended);
pivot.Items.Remove(subscriptions); pivot.Items.Remove(subscriptions);
LoadTrending();
} }
} }
private void pivot_SelectionChanged(object sender, SelectionChangedEventArgs e) private void pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{ {
loading.Close(); if (pivot.SelectedItem == recommended && !recLoaded)
if (pivot.SelectedItem == recommended)
LoadRecommendations(); LoadRecommendations();
else if (pivot.SelectedItem == trending && !trendLoaded) else if (pivot.SelectedItem == trending && !trendLoaded)
LoadTrending(); LoadTrending();
else if (pivot.SelectedItem == subscriptions) else if (pivot.SelectedItem == subscriptions && !subsLoaded)
LoadSubscriptions(); LoadSubscriptions();
} }
#region Initializing tabs
async void LoadRecommendations()
{
try
{
recsLoading.Refresh();
string response = await SecretsVault.HttpClient.GetStringAsync("https://www.youtube.com/");
foreach (Match match in Regex.Matches(response, @"\bdata-context-item-id=(\S*)\b", RegexOptions.IgnoreCase))
homeList.Add(match.Value.Split('"')[1]);
for (int k = 0; k < 25 && k < homeList.Count; k++)
recGrid.Add(new VideoCard(homeList[k]));
recommendedMore.Visibility = recGrid.Count >= homeList.Count ? Visibility.Collapsed : Visibility.Visible;
recsLoading.Close();
recLoaded = true;
}
catch (HttpRequestException)
{
recLoaded = false;
recsLoading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch (Exception e)
{
recLoaded = false;
recsLoading.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("Failed to load recommendations", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message }
});
}
}
async void LoadTrending() async void LoadTrending()
{ {
try try
{ {
loading.Refresh(); trendsLoading.Refresh();
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("id");
request.MaxResults = 48;
request.Chart = VideosResource.ListRequest.ChartEnum.MostPopular; trendsRequest = SecretsVault.Service.Videos.List("snippet,contentDetails,statistics,liveStreamingDetails");
request.RegionCode = SettingsStorage.Region; trendsRequest.MaxResults = 25;
VideoListResponse response = await request.ExecuteAsync();
if (!string.IsNullOrWhiteSpace(response.NextPageToken)) trendsRequest.Chart = VideosResource.ListRequest.ChartEnum.MostPopular;
trendToken = response.NextPageToken; trendsRequest.RegionCode = SettingsStorage.Region;
else
trendMore.Complete(true);
foreach (Video vid in response.Items) VideoListResponse response = await trendsRequest.ExecuteAsync();
{
VideoCard vCard = new VideoCard(vid.Id);
trendGrid.Add(vCard);
}
loading.Close(); trendsRequest.PageToken = response.NextPageToken;
trendingMore.Visibility = string.IsNullOrWhiteSpace(response.NextPageToken) ? Visibility.Collapsed : Visibility.Visible;
foreach (Video i in response.Items)
trendGrid.Add(new VideoCard(i));
trendsLoading.Close();
trendLoaded = true; trendLoaded = true;
} }
catch (System.Net.Http.HttpRequestException) catch (HttpRequestException)
{ {
trendLoaded = false; trendLoaded = false;
loading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true); trendsLoading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
} }
catch(Exception e) catch(Exception e)
{ {
trendLoaded = false; trendLoaded = false;
loading.Error(e.GetType().ToString(), e.Message); trendsLoading.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("Failed to load trendings", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message }
});
} }
} }
async void LoadSubscriptions()
void LoadRecommendations()
{ {
try try
{ {
loading.Refresh(); subsLoading.Refresh();
throw new NotImplementedException("This page has not implemented yet.");
}
catch (System.Net.Http.HttpRequestException)
{
loading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch (Exception e)
{
loading.Error(e.GetType().ToString(), e.Message);
}
}
void LoadSubscriptions() await SecretsVault.HttpClient.GetStringAsync("https://www.youtube.com/list_ajax?style=json&action_get_list=1&list=WL");
{ string response = await SecretsVault.HttpClient.GetStringAsync("https://www.youtube.com/feed/subscriptions");
try
{ foreach (Match match in Regex.Matches(response, @"\bdata-context-item-id=(\S*)\b", RegexOptions.IgnoreCase))
loading.Refresh(); subsList.Add(match.Value.Split('"')[1]);
throw new NotImplementedException("This page has not implemented yet.");
for (int k = 0; k < 25 && k < subsList.Count; k++)
subsGrid.Add(new VideoCard(subsList[k]));
subscriptionsMore.Visibility = subsGrid.Count >= subsList.Count ? Visibility.Collapsed : Visibility.Visible;
subsLoading.Close();
subsLoaded = true;
} }
catch (System.Net.Http.HttpRequestException) catch (HttpRequestException)
{ {
loading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true); subsLoaded = false;
subsLoading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
} }
catch (Exception e) catch (Exception e)
{ {
loading.Error(e.GetType().ToString(), e.Message); subsLoaded = false;
subsLoading.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("Failed to load subscriptions", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message }
});
} }
} }
#endregion
#region More requests
private void Recommended_More()
{
int l = 25 + recGrid.Count;
for (int k = recGrid.Count; k < l && k < homeList.Count; k++)
recGrid.Add(new VideoCard(homeList[k]));
recommendedMore.Visibility = recGrid.Count >= homeList.Count ? Visibility.Collapsed : Visibility.Visible;
recommendedMore.Complete();
}
private async void Trending_More()
{
VideoListResponse response = await trendsRequest.ExecuteAsync();
trendsRequest.PageToken = response.NextPageToken;
trendingMore.Visibility = string.IsNullOrWhiteSpace(response.NextPageToken) ? Visibility.Collapsed : Visibility.Visible;
foreach (Video i in response.Items)
trendGrid.Add(new VideoCard(i));
trendingMore.Complete();
}
private void Subscriptions_More()
{
int l = 25 + subsGrid.Count;
for (int k = subsGrid.Count; k < l && k < subsList.Count; k++)
subsGrid.Add(new VideoCard(subsList[k]));
subscriptionsMore.Visibility = subsGrid.Count >= subsList.Count ? Visibility.Collapsed : Visibility.Visible;
subscriptionsMore.Complete();
}
#endregion
#region Refreshing tabs
private void Recommended_Refresh(object sender, RoutedEventArgs e)
{
recGrid.Clear();
LoadRecommendations();
}
private void Trends_Refresh(object sender, RoutedEventArgs e)
{
trendsRequest = null;
trendGrid.Clear();
LoadTrending();
}
private void Subscriptions_Refresh(object sender, RoutedEventArgs e)
{
subsGrid.Clear();
LoadSubscriptions();
}
#endregion
} }
} }
+1
View File
@@ -4,6 +4,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 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"> mc:Ignorable="d">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
+74 -22
View File
@@ -4,13 +4,58 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
xmlns:Windows10version1803="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 6)" xmlns:Windows10version1803="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 6)"
mc:Ignorable="d" mc:Ignorable="d">
SizeChanged="Page_SizeChanged"
PreviewKeyUp="Page_PreviewKeyUp">
<Grid Name="grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid Name="grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<NavigationView Header="Home" SelectedItem="toHome" Windows10version1803:BackRequested="Nav_BackRequested" Windows10version1803:PaneTitle="FoxTube" OpenPaneLength="300" Name="nav" SelectionChanged="Nav_SelectionChanged"> <Windows10version1809:Grid.BackgroundTransition>
<BrushTransition/>
</Windows10version1809:Grid.BackgroundTransition>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="{x:Bind nav.CompactModeThresholdWidth}"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="Title.Margin" Value="0"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="AppTitleBar"
IsHitTestVisible="True"
VerticalAlignment="Top"
Background="Transparent"
Canvas.ZIndex="1">
<TextBlock x:Name="AppTitle"
Text="FoxTube"
VerticalAlignment="Center"
Margin="12, 8, 0, 0"
Style="{StaticResource CaptionTextBlockStyle}" />
</Border>
<NavigationView SelectedItem="toHome" Windows10version1803:BackRequested="Nav_BackRequested" Windows10version1803:PaneClosing="Nav_PaneClosing" Windows10version1803:PaneOpened="Nav_PaneOpened" OpenPaneLength="300" Name="nav" SelectionChanged="Nav_SelectionChanged">
<NavigationView.Header>
<TextBlock Name="Title" Margin="0,30,0,10" Style="{StaticResource TitleTextBlockStyle}"/>
</NavigationView.Header>
<NavigationView.MenuItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Padding="5" Margin="-5,0,0,0" Tag="{Binding Snippet.ResourceId.ChannelId}">
<PersonPicture Height="20" Margin="-5,0,15,0">
<PersonPicture.ProfilePicture>
<BitmapImage UriSource="{Binding Snippet.Thumbnails.Medium.Url}" DecodePixelHeight="20" DecodePixelWidth="20"/>
</PersonPicture.ProfilePicture>
</PersonPicture>
<TextBlock Text="{Binding Snippet.Title}"/>
</StackPanel>
</DataTemplate>
</NavigationView.MenuItemTemplate>
<NavigationView.MenuItems> <NavigationView.MenuItems>
<NavigationViewItem x:Uid="/Main/home" Icon="Home" Content="Home" Name="toHome"/> <NavigationViewItem x:Uid="/Main/home" Icon="Home" Content="Home" Name="toHome"/>
@@ -30,8 +75,14 @@
<NavigationView.PaneFooter> <NavigationView.PaneFooter>
<NavigationViewList> <NavigationViewList>
<NavigationViewList.ItemContainerTransitions>
<TransitionCollection>
<EntranceThemeTransition IsStaggeringEnabled="True"/>
<ReorderThemeTransition/>
</TransitionCollection>
</NavigationViewList.ItemContainerTransitions>
<NavigationViewItem Name="openWeb" Tapped="Web_Tapped" Icon="Globe" Content="Browser" Visibility="Collapsed"/> <NavigationViewItem Name="openWeb" Tapped="Web_Tapped" Icon="Globe" Content="Browser" Visibility="Collapsed"/>
<NavigationViewItem x:Uid="/Main/feedback" Name="feedback" Content="Give a feedback" Tapped="Feedback_Click"> <NavigationViewItem x:Uid="/Main/feedback" Name="feedback" Content="Give a feedback" Tapped="Feedback_Click" Visibility="Collapsed">
<NavigationViewItem.Icon> <NavigationViewItem.Icon>
<FontIcon Glyph="&#xED15;"/> <FontIcon Glyph="&#xED15;"/>
</NavigationViewItem.Icon> </NavigationViewItem.Icon>
@@ -43,18 +94,10 @@
</NavigationViewItem.Icon> </NavigationViewItem.Icon>
</NavigationViewItem> </NavigationViewItem>
<NavigationViewItem Name="account" Tapped="OpenContext"> <NavigationViewItem Name="account" x:Uid="/Main/signIn" Content="Add account" Tapped="SignIn_Click" Visibility="Collapsed">
<StackPanel Orientation="Horizontal"> <NavigationViewItem.Icon>
<FontIcon Glyph="&#xE8FA;" FontSize="16" Margin="0,0,16,0"/> <FontIcon Glyph="&#xE8FA;"/>
<TextBlock x:Uid="/Main/signIn" Text="Add account"/> </NavigationViewItem.Icon>
</StackPanel>
<NavigationViewItem.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="/Main/signEx" Text="Sign in with existing account" Name="signIn" Click="SignIn_Click"/>
<MenuFlyoutItem x:Uid="/Main/signNew" Text="Create new Google account" Name="createAccount" Click="CreateAccount_Click"/>
</MenuFlyout>
</NavigationViewItem.ContextFlyout>
</NavigationViewItem> </NavigationViewItem>
<NavigationViewItem Visibility="Collapsed" Name="avatar" Tapped="OpenContext" Padding="-5"> <NavigationViewItem Visibility="Collapsed" Name="avatar" Tapped="OpenContext" Padding="-5">
@@ -64,11 +107,20 @@
</StackPanel> </StackPanel>
<NavigationViewItem.ContextFlyout> <NavigationViewItem.ContextFlyout>
<MenuFlyout> <Flyout>
<MenuFlyoutItem x:Uid="/Main/myChannelContext" Text="My channel" Name="myChannel" Click="MyChannel_Click"/> <Grid>
<MenuFlyoutSeparator/> <Grid.ColumnDefinitions>
<MenuFlyoutItem x:Uid="/Main/signOut" Text="Log out" Name="logout" Click="Logout_Click"/> <ColumnDefinition Width="auto"/>
</MenuFlyout> <ColumnDefinition/>
</Grid.ColumnDefinitions>
<PersonPicture Width="50" Name="avatarFlyout" VerticalAlignment="Top"/>
<StackPanel Grid.Column="1" Margin="5">
<TextBlock Name="myNameFlyout"/>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Name="myEmail"/>
<HyperlinkButton x:Uid="/Main/signOut" Content="Log out" Click="Logout_Click"/>
</StackPanel>
</Grid>
</Flyout>
</NavigationViewItem.ContextFlyout> </NavigationViewItem.ContextFlyout>
</NavigationViewItem> </NavigationViewItem>
</NavigationViewList> </NavigationViewList>
File diff suppressed because it is too large Load Diff
+8 -5
View File
@@ -1,5 +1,4 @@
<Page <Page
NavigationCacheMode="Enabled"
x:Class="FoxTube.Pages.PlaylistPage" x:Class="FoxTube.Pages.PlaylistPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -7,10 +6,11 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:foxtube="using:FoxTube" xmlns:foxtube="using:FoxTube"
xmlns:controls="using:FoxTube.Controls"
mc:Ignorable="d" mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Name="grid"> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition/> <RowDefinition/>
<RowDefinition Height="auto"/> <RowDefinition Height="auto"/>
@@ -57,11 +57,14 @@
</Button> </Button>
</StackPanel> </StackPanel>
<local:VideoGrid Grid.Column="1" Grid.Row="1"/> <StackPanel Grid.Column="1" Grid.Row="1">
<local:VideoGrid x:Name="list"/>
<controls:ShowMore x:Name="more" Clicked="ShowMore_Clicked"/>
</StackPanel>
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>
<CommandBar Grid.Row="2" Grid.ColumnSpan="2"> <CommandBar Grid.Row="2">
<AppBarButton x:Uid="/Playlist/openWeb" Icon="Globe" Label="Open in browser" Name="inBrowser" Click="inBrowser_Click"/> <AppBarButton x:Uid="/Playlist/openWeb" Icon="Globe" Label="Open in browser" Name="inBrowser" Click="inBrowser_Click"/>
<AppBarButton x:Uid="/Playlist/addTo" Icon="Add" Label="Add to" IsEnabled="False" Visibility="Collapsed"> <AppBarButton x:Uid="/Playlist/addTo" Icon="Add" Label="Add to" IsEnabled="False" Visibility="Collapsed">
<AppBarButton.Flyout> <AppBarButton.Flyout>
@@ -74,6 +77,6 @@
<AppBarButton x:Uid="/Playlist/share" Icon="Share" Label="Share" Name="share" Click="share_Click"/> <AppBarButton x:Uid="/Playlist/share" Icon="Share" Label="Share" Name="share" Click="share_Click"/>
</CommandBar> </CommandBar>
<foxtube:LoadingPage Grid.RowSpan="2" Visibility="Collapsed"/> <foxtube:LoadingPage Grid.RowSpan="2" Visibility="Collapsed" RefreshPage="refresh_Click" x:Name="loading"/>
</Grid> </Grid>
</Page> </Page>
+40 -33
View File
@@ -18,26 +18,26 @@ namespace FoxTube.Pages
/// <summary> /// <summary>
/// Playlist page /// Playlist page
/// </summary> /// </summary>
public sealed partial class PlaylistPage : Page public sealed partial class PlaylistPage : Page, NavigationPage
{ {
public object Parameter { get; set; } = null;
public string playlistId; public string playlistId;
Playlist item; Playlist item;
LoadingPage loading; PlaylistItemsResource.ListRequest request;
VideoGrid list; string token;
public PlaylistPage() public PlaylistPage()
{ {
InitializeComponent(); 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<DataTransferManager, DataRequestedEventArgs>(Share); DataTransferManager.GetForCurrentView().DataRequested += new TypedEventHandler<DataTransferManager, DataRequestedEventArgs>(Share);
} }
protected override void OnNavigatedTo(NavigationEventArgs e) protected override void OnNavigatedTo(NavigationEventArgs e)
{ {
base.OnNavigatedTo(e); base.OnNavigatedTo(e);
Parameter = e.Parameter;
if (e.Parameter == null) if (e.Parameter == null)
loading.Error("NullReferenceException", "Unable to initialize page. Playlist ID is not stated."); loading.Error("NullReferenceException", "Unable to initialize page. Playlist ID is not stated.");
else else
@@ -51,13 +51,11 @@ namespace FoxTube.Pages
try try
{ {
playlistId = id; playlistId = id;
if (Methods.NeedToResponse)
Methods.MainPage.Content_Navigated(this, null);
PlaylistsResource.ListRequest request = SecretsVault.Service.Playlists.List("snippet,contentDetails"); PlaylistsResource.ListRequest infoRequest = SecretsVault.Service.Playlists.List("snippet,contentDetails");
request.Id = id; infoRequest.Id = id;
item = (await request.ExecuteAsync()).Items[0]; item = (await infoRequest.ExecuteAsync()).Items[0];
title.Text = item.Snippet.Title; title.Text = item.Snippet.Title;
info.Text = $"{item.ContentDetails.ItemCount} {ResourceLoader.GetForCurrentView("Playlist").GetString("/Playlist/videos")}"; info.Text = $"{item.ContentDetails.ItemCount} {ResourceLoader.GetForCurrentView("Playlist").GetString("/Playlist/videos")}";
@@ -65,35 +63,27 @@ namespace FoxTube.Pages
channelName.Text = item.Snippet.ChannelTitle; channelName.Text = item.Snippet.ChannelTitle;
try ChannelsResource.ListRequest channelRequest = SecretsVault.Service.Channels.List("snippet");
{ channelRequest.Id = item.Snippet.ChannelId;
thumbnail.Source = new BitmapImage(new Uri(item.Snippet.Thumbnails.Medium.Url)); Channel channel = (await channelRequest.ExecuteAsync()).Items[0];
ChannelsResource.ListRequest channelRequest = SecretsVault.Service.Channels.List("snippet");
channelRequest.Id = item.Snippet.ChannelId; try { avatar.ProfilePicture = new BitmapImage(channel.Snippet.Thumbnails.Medium.Url.ToUri()) { DecodePixelWidth = 50, DecodePixelHeight = 50 }; }
Channel channel = (await channelRequest.ExecuteAsync()).Items[0]; catch { }
avatar.ProfilePicture = new BitmapImage(new Uri(channel.Snippet.Thumbnails.Medium.Url)); try { thumbnail.Source = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri()); }
}
catch { } catch { }
PlaylistItemsResource.ListRequest listRequest = SecretsVault.Service.PlaylistItems.List("contentDetails"); request = SecretsVault.Service.PlaylistItems.List("contentDetails");
listRequest.PlaylistId = id; request.PlaylistId = id;
listRequest.MaxResults = 50; request.MaxResults = 25;
PlaylistItemListResponse response = await listRequest.ExecuteAsync(); PlaylistItemListResponse response = await request.ExecuteAsync();
token = response.NextPageToken;
if (string.IsNullOrWhiteSpace(token))
more.Visibility = Visibility.Collapsed;
list.Clear();
foreach (PlaylistItem i in response.Items) foreach (PlaylistItem i in response.Items)
list.Add(new VideoCard(i.ContentDetails.VideoId, playlistId)); 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(); loading.Close();
} }
catch (System.Net.Http.HttpRequestException) catch (System.Net.Http.HttpRequestException)
@@ -140,5 +130,22 @@ namespace FoxTube.Pages
$"https://www.youtube.com/playlist?list={item.Id}", $"https://www.youtube.com/playlist?list={item.Id}",
ResourceLoader.GetForCurrentView("Cards").GetString("/Cards/playlistShare")); 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));
}
} }
} }
+4 -4
View File
@@ -67,16 +67,16 @@
<Button x:Uid="/Search/apply" Content="Apply" Margin="10,0,0,10" Name="apply" Click="Apply_Click"/> <Button x:Uid="/Search/apply" Content="Apply" Margin="10,0,0,10" Name="apply" Click="Apply_Click"/>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
<pages:VideoGrid/> <pages:VideoGrid x:Name="list"/>
<controls:ShowMore Clicked="More_Clicked"/> <controls:ShowMore x:Name="more" Clicked="More_Clicked"/>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>
<CommandBar Grid.Row="1" DefaultLabelPosition="Right"> <CommandBar Grid.Row="1">
<AppBarButton Label="Open in browser" Icon="Globe" Name="inBrowser" Click="InBrowser_Click"/> <AppBarButton Label="Open in browser" Icon="Globe" Name="inBrowser" Click="InBrowser_Click"/>
<AppBarButton Label="Refresh" Icon="Refresh" Click="AppBarButton_Click"/> <AppBarButton Label="Refresh" Icon="Refresh" Click="AppBarButton_Click"/>
</CommandBar> </CommandBar>
<local:LoadingPage Grid.RowSpan="2" Visibility="Collapsed" RefreshPage="AppBarButton_Click"/> <local:LoadingPage x:Name="loading" Grid.RowSpan="2" Visibility="Collapsed" RefreshPage="AppBarButton_Click"/>
</Grid> </Grid>
</Page> </Page>
+21 -18
View File
@@ -8,30 +8,26 @@ using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation; using Windows.UI.Xaml.Navigation;
using Windows.System; using Windows.System;
using Windows.ApplicationModel.Resources; using Windows.ApplicationModel.Resources;
using Microsoft.AppCenter.Analytics;
using System.Collections.Generic;
namespace FoxTube namespace FoxTube
{ {
/// <summary> /// <summary>
/// Search page /// Search page
/// </summary> /// </summary>
public sealed partial class Search : Page public sealed partial class Search : Page, NavigationPage
{ {
public object Parameter { get; set; } = null;
readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Search"); readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Search");
public SearchParameters Parameters; public SearchParameters Parameters;
SearchResource.ListRequest request; SearchResource.ListRequest request;
string nextToken; string nextToken;
readonly VideoGrid list;
readonly LoadingPage loading;
readonly ShowMore more;
public Search() public Search()
{ {
InitializeComponent(); InitializeComponent();
loading = grid.Children[2] as LoadingPage;
list = ((grid.Children[0] as ScrollViewer).Content as StackPanel).Children[4] as VideoGrid;
more = ((grid.Children[0] as ScrollViewer).Content as StackPanel).Children[5] as ShowMore;
} }
public string SetResults(int? count) public string SetResults(int? count)
@@ -68,6 +64,7 @@ namespace FoxTube
protected override void OnNavigatedTo(NavigationEventArgs e) protected override void OnNavigatedTo(NavigationEventArgs e)
{ {
base.OnNavigatedTo(e); base.OnNavigatedTo(e);
Parameter = e.Parameter;
if (e.Parameter == null) if (e.Parameter == null)
loading.Error("NullReferenceException", "Unable to initialize search. Search term is not stated."); loading.Error("NullReferenceException", "Unable to initialize search. Search term is not stated.");
else else
@@ -77,7 +74,6 @@ namespace FoxTube
else else
loading.Error("ArgumentException", "Wrong parameter"); loading.Error("ArgumentException", "Wrong parameter");
} }
} }
public async void Initialize(SearchParameters arg) public async void Initialize(SearchParameters arg)
@@ -100,20 +96,21 @@ namespace FoxTube
request.RegionCode = SettingsStorage.Region; request.RegionCode = SettingsStorage.Region;
request.RelevanceLanguage = SettingsStorage.RelevanceLanguage; request.RelevanceLanguage = SettingsStorage.RelevanceLanguage;
request.MaxResults = 48; request.MaxResults = 25;
try
{
request.Order = (SearchResource.ListRequest.OrderEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.Order);
request.Type = (string)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.Type);
request.PublishedAfter = (DateTime)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.Date);
request.Order = (SearchResource.ListRequest.OrderEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.Order);
request.Type = (string)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.Type);
request.PublishedAfter = (DateTime?)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.Date);
if (Parameters.Filter.Type == SearchParameters.Filters.Enumerations.Type.Video)
{
request.VideoDuration = (SearchResource.ListRequest.VideoDurationEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.Duration);
request.VideoDefinition = (SearchResource.ListRequest.VideoDefinitionEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.HD); request.VideoDefinition = (SearchResource.ListRequest.VideoDefinitionEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.HD);
request.VideoDimension = (SearchResource.ListRequest.VideoDimensionEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.ThreeD); request.VideoDimension = (SearchResource.ListRequest.VideoDimensionEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.ThreeD);
request.VideoCaption = (SearchResource.ListRequest.VideoCaptionEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.Captions); request.VideoCaption = (SearchResource.ListRequest.VideoCaptionEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.Captions);
request.EventType = (SearchResource.ListRequest.EventTypeEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.LiveEvent); request.EventType = (SearchResource.ListRequest.EventTypeEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.LiveEvent);
request.VideoLicense = (SearchResource.ListRequest.VideoLicenseEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.CreativeCommons); request.VideoLicense = (SearchResource.ListRequest.VideoLicenseEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.CreativeCommons);
} }
catch { }
order.SelectedIndex = (int)Parameters.Filter.Order; order.SelectedIndex = (int)Parameters.Filter.Order;
type.SelectedIndex = (int)Parameters.Filter.Type; type.SelectedIndex = (int)Parameters.Filter.Type;
@@ -135,7 +132,7 @@ namespace FoxTube
if (!string.IsNullOrWhiteSpace(response.NextPageToken)) if (!string.IsNullOrWhiteSpace(response.NextPageToken))
nextToken = response.NextPageToken; nextToken = response.NextPageToken;
else else
more.Complete(true); more.Visibility = Visibility.Collapsed;
list.Clear(); list.Clear();
foreach (SearchResult item in response.Items) foreach (SearchResult item in response.Items)
@@ -150,6 +147,12 @@ namespace FoxTube
catch (Exception e) catch (Exception e)
{ {
loading.Error(e.GetType().ToString(), e.Message); loading.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("Search loading error", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Parameters", Parameters.ToString() }
});
} }
} }
@@ -185,7 +188,7 @@ namespace FoxTube
more.Complete(); more.Complete();
} }
else else
more.Complete(true); more.Visibility = Visibility.Collapsed;
} }
private void Type_SelectionChanged(object sender, SelectionChangedEventArgs e) private void Type_SelectionChanged(object sender, SelectionChangedEventArgs e)
+6 -10
View File
@@ -7,8 +7,9 @@ namespace FoxTube
/// <summary> /// <summary>
/// Settings tabs placeholder /// Settings tabs placeholder
/// </summary> /// </summary>
public sealed partial class Settings : Page public sealed partial class Settings : Page, NavigationPage
{ {
public object Parameter { get; set; } = null;
bool inboxLoaded = false; bool inboxLoaded = false;
string inboxId = null; string inboxId = null;
public Settings() public Settings()
@@ -19,16 +20,11 @@ namespace FoxTube
protected override void OnNavigatedTo(NavigationEventArgs e) protected override void OnNavigatedTo(NavigationEventArgs e)
{ {
base.OnNavigatedTo(e); base.OnNavigatedTo(e);
if(!string.IsNullOrWhiteSpace(e.Parameter as string)) Parameter = e.Parameter;
if (!string.IsNullOrWhiteSpace(e.Parameter as string))
{ {
if ((e.Parameter as string).Contains("inbox") || (e.Parameter as string).Contains("changelog")) inboxId = e.Parameter as string;
{ pivot.SelectedIndex = 2;
if ((string)e.Parameter != "inbox" && (string)e.Parameter != "changelog")
inboxId = e.Parameter as string;
pivot.SelectedIndex = 2;
}
else if ((e.Parameter as string) == "about")
pivot.SelectedIndex = 1;
} }
} }
+2 -4
View File
@@ -27,9 +27,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel> <StackPanel>
<TextBlock x:Uid="/About/aboutHeader" Text="About us and this app" FontSize="28"/> <TextBlock Text="FoxTube" FontSize="28"/>
<TextBlock Text="FoxTube" FontSize="24"/>
<TextBlock Name="version" Text="[currentVersion]" FontSize="14" Foreground="Gray" Margin="0,-5,0,10"/> <TextBlock Name="version" Text="[currentVersion]" FontSize="14" Foreground="Gray" Margin="0,-5,0,10"/>
<TextBlock x:Uid="/About/developed" TextWrapping="WrapWholeWords" Text="Developed by Michael Gordeev (also known as XFox)" Margin="0,0,0,10"/> <TextBlock x:Uid="/About/developed" TextWrapping="WrapWholeWords" Text="Developed by Michael Gordeev (also known as XFox)" Margin="0,0,0,10"/>
@@ -49,7 +47,7 @@
<HyperlinkButton x:Uid="/About/guides" Content="YouTube Community Guidelines" NavigateUri="https://youtube.com/t/community_guidelines" Padding="0,0,0,10"/> <HyperlinkButton x:Uid="/About/guides" Content="YouTube Community Guidelines" NavigateUri="https://youtube.com/t/community_guidelines" Padding="0,0,0,10"/>
<TextBlock x:Uid="/About/crMe" Text="© 2018 Michael Gordeev"/> <TextBlock x:Uid="/About/crMe" Text="© 2018 Michael Gordeev"/>
<TextBlock x:Uid="/About/crYt" Text="© 2018 YouTube, LLC"/> <TextBlock x:Uid="/About/crYt" Text="© 2018 YouTube, LLC"/>
<Button x:Uid="/About/feedback" Content="Leave feedback" Margin="0,5" Click="Button_Click"/> <Button Name="feedback" x:Uid="/About/feedback" Content="Leave feedback" Margin="0,5" Click="Button_Click" Visibility="Collapsed"/>
</StackPanel> </StackPanel>
<Image Grid.Column="1" Source="/Assets/LogoAvatar.png" VerticalAlignment="Top" Width="128"/> <Image Grid.Column="1" Source="/Assets/LogoAvatar.png" VerticalAlignment="Top" Width="128"/>
</Grid> </Grid>
+9 -5
View File
@@ -1,4 +1,5 @@
using System; using Microsoft.Services.Store.Engagement;
using System;
using Windows.ApplicationModel; using Windows.ApplicationModel;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
@@ -10,16 +11,19 @@ namespace FoxTube.Pages.SettingsPages
/// </summary> /// </summary>
public sealed partial class About : Page public sealed partial class About : Page
{ {
PackageVersion ver = Package.Current.Id.Version;
public About() public About()
{ {
this.InitializeComponent(); InitializeComponent();
version.Text = string.Format("{0}.{1}.{2}", ver.Major, ver.Minor, ver.Build); 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) private async void Button_Click(object sender, RoutedEventArgs e)
{ {
await Windows.System.Launcher.LaunchUriAsync(new Uri("feedback-hub:")); await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync();
} }
} }
} }
+4 -4
View File
@@ -9,7 +9,7 @@
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<TextBlock x:Uid="/General/preferences" Text="Preferences" FontSize="28"/> <TextBlock x:Uid="/General/preferences" Text="Preferences" FontSize="28"/>
<TextBlock x:Uid="/General/regNsearch" Text="Region &#x26; search" FontSize="22"/> <TextBlock x:Uid="/General/regNsearch" Text="Region &#x26; search" FontSize="22" Margin="0,10,0,0"/>
<ComboBox x:Uid="/General/interfaceLang" Header="App interface language" MinWidth="250" Name="language" SelectionChanged="language_SelectionChanged"> <ComboBox x:Uid="/General/interfaceLang" Header="App interface language" MinWidth="250" Name="language" SelectionChanged="language_SelectionChanged">
<ComboBoxItem x:Uid="/General/en" Content="English (United States)" Tag="en-US"/> <ComboBoxItem x:Uid="/General/en" Content="English (United States)" Tag="en-US"/>
<ComboBoxItem x:Uid="/General/ru" Content="Russian (Russia)" Tag="ru-RU"/> <ComboBoxItem x:Uid="/General/ru" Content="Russian (Russia)" Tag="ru-RU"/>
@@ -26,18 +26,18 @@
<ComboBoxItem x:Uid="/General/strict" Content="Strict"/> <ComboBoxItem x:Uid="/General/strict" Content="Strict"/>
</ComboBox> </ComboBox>
<TextBlock x:Uid="/General/playback" Text="Playback" FontSize="22"/> <TextBlock x:Uid="/General/playback" Text="Playback" FontSize="22" Margin="0,10,0,0"/>
<ComboBox x:Uid="/General/quality" Width="250" Header="Default video playback quality" Name="quality" SelectionChanged="quality_SelectionChanged"> <ComboBox x:Uid="/General/quality" Width="250" Header="Default video playback quality" Name="quality" SelectionChanged="quality_SelectionChanged">
<ComboBoxItem Tag="remember" x:Uid="/General/remember" Content="Remember my choice"/> <ComboBoxItem Tag="remember" x:Uid="/General/remember" Content="Remember my choice"/>
</ComboBox> </ComboBox>
<ToggleSwitch x:Uid="/General/metered" OnContent="Notify when playing on metered connection" OffContent="Notify when playing on metered connection" Name="mobileWarning" Toggled="mobileWarning_Toggled"/> <ToggleSwitch x:Uid="/General/metered" OnContent="Notify when playing on metered connection" OffContent="Notify when playing on metered connection" Name="mobileWarning" Toggled="mobileWarning_Toggled"/>
<ToggleSwitch x:Uid="/General/autoplay" OnContent="Play videos automatically" OffContent="Play videos automatically" Name="autoplay" Toggled="autoplay_Toggled"/> <ToggleSwitch x:Uid="/General/autoplay" OnContent="Play videos automatically" OffContent="Play videos automatically" Name="autoplay" Toggled="autoplay_Toggled"/>
<TextBlock x:Uid="/General/notifications" Text="Notifications" FontSize="22"/> <TextBlock x:Uid="/General/notifications" Text="Notifications" FontSize="22" Margin="0,10,0,0"/>
<ToggleSwitch x:Uid="/General/newVideo" Name="newVideo" OnContent="Notify when someone of your subscriptions uploaded new video" OffContent="Notify when someone of your subscriptions uploaded new video" Toggled="notification_IsEnabledChanged"/> <ToggleSwitch x:Uid="/General/newVideo" Name="newVideo" OnContent="Notify when someone of your subscriptions uploaded new video" OffContent="Notify when someone of your subscriptions uploaded new video" Toggled="notification_IsEnabledChanged"/>
<ToggleSwitch x:Uid="/General/devNotifications" Name="devNews" OnContent="Recieve messages from developers" OffContent="Recieve messages from developers" Toggled="devNews_Toggled"/> <ToggleSwitch x:Uid="/General/devNotifications" Name="devNews" OnContent="Recieve messages from developers" OffContent="Recieve messages from developers" Toggled="devNews_Toggled"/>
<TextBlock x:Uid="/General/color" Text="Color mode" FontSize="22"/> <TextBlock x:Uid="/General/color" Text="Color mode" FontSize="22" Margin="0,10,0,0"/>
<RadioButton x:Uid="/General/colorLight" Content="Light" Name="light" GroupName="color" Checked="RadioButton_Checked"/> <RadioButton x:Uid="/General/colorLight" Content="Light" Name="light" GroupName="color" Checked="RadioButton_Checked"/>
<RadioButton x:Uid="/General/colorDark" Content="Dark" Name="dark" GroupName="color" Checked="RadioButton_Checked"/> <RadioButton x:Uid="/General/colorDark" Content="Dark" Name="dark" GroupName="color" Checked="RadioButton_Checked"/>
<RadioButton x:Uid="/General/colorDefault" Content="Windows default" Name="system" GroupName="color" Checked="RadioButton_Checked"/> <RadioButton x:Uid="/General/colorDefault" Content="Windows default" Name="system" GroupName="color" Checked="RadioButton_Checked"/>
+10 -12
View File
@@ -7,6 +7,7 @@ using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data; using Google.Apis.YouTube.v3.Data;
using YoutubeExplode.Models.MediaStreams; using YoutubeExplode.Models.MediaStreams;
using System; using System;
using Windows.Globalization;
namespace FoxTube.Pages.SettingsPages namespace FoxTube.Pages.SettingsPages
{ {
@@ -50,19 +51,17 @@ namespace FoxTube.Pages.SettingsPages
async void InitializeRegions() async void InitializeRegions()
{ {
I18nRegionsResource.ListRequest regRequest = SecretsVault.Service.I18nRegions.List("snippet"); I18nRegionsResource.ListRequest regRequest = SecretsVault.Service.I18nRegions.List("snippet");
regRequest.Hl = SettingsStorage.Language;
I18nRegionListResponse regResponse = await regRequest.ExecuteAsync(); 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
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));
});
if (SettingsStorage.Region == i.Snippet.Gl)
region.SelectedItem = region.Items.Last();
}
I18nLanguagesResource.ListRequest langRequest = SecretsVault.Service.I18nLanguages.List("snippet"); I18nLanguagesResource.ListRequest langRequest = SecretsVault.Service.I18nLanguages.List("snippet");
langRequest.Hl = SettingsStorage.Language;
I18nLanguageListResponse langResponse = await langRequest.ExecuteAsync(); I18nLanguageListResponse langResponse = await langRequest.ExecuteAsync();
foreach(I18nLanguage i in langResponse.Items) foreach(I18nLanguage i in langResponse.Items)
{ {
@@ -80,7 +79,7 @@ namespace FoxTube.Pages.SettingsPages
{ {
if (SettingsStorage.Language == (language.SelectedItem as ComboBoxItem).Tag.ToString()) if (SettingsStorage.Language == (language.SelectedItem as ComboBoxItem).Tag.ToString())
return; return;
ApplicationLanguages.PrimaryLanguageOverride = (language.SelectedItem as ComboBoxItem).Tag.ToString();
SettingsStorage.Language = (language.SelectedItem as ComboBoxItem).Tag.ToString(); SettingsStorage.Language = (language.SelectedItem as ComboBoxItem).Tag.ToString();
restartNote.Visibility = Visibility.Visible; restartNote.Visibility = Visibility.Visible;
} }
@@ -135,8 +134,7 @@ namespace FoxTube.Pages.SettingsPages
else if (sender == system && SettingsStorage.Theme != 2) else if (sender == system && SettingsStorage.Theme != 2)
{ {
SettingsStorage.Theme = 2; SettingsStorage.Theme = 2;
Color uiTheme = new Windows.UI.ViewManagement.UISettings().GetColorValue(Windows.UI.ViewManagement.UIColorType.Background); if (new Windows.UI.ViewManagement.UISettings().GetColorValue(Windows.UI.ViewManagement.UIColorType.Background) == Colors.Black)
if (uiTheme == Colors.Black)
Methods.MainPage.RequestedTheme = ElementTheme.Dark; Methods.MainPage.RequestedTheme = ElementTheme.Dark;
else else
Methods.MainPage.RequestedTheme = ElementTheme.Light; Methods.MainPage.RequestedTheme = ElementTheme.Light;
+36 -38
View File
@@ -52,43 +52,41 @@
<ColumnDefinition/> <ColumnDefinition/>
<ColumnDefinition Width="0"/> <ColumnDefinition Width="0"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<ScrollViewer Background="{ThemeResource SystemControlBackgroundChromeMediumLowBrush}">
<StackPanel VerticalAlignment="Stretch"> <StackPanel VerticalAlignment="Stretch" Background="{StaticResource SystemControlBackgroundChromeMediumLowBrush}">
<ComboBox x:Uid="/Inbox/filter" Header="Filter" Margin="10" HorizontalAlignment="Stretch" SelectedIndex="0" Name="filter" SelectionChanged="filter_SelectionChanged"> <ComboBox x:Uid="/Inbox/filter" Header="Filter" Margin="10" HorizontalAlignment="Stretch" SelectedIndex="0" Name="filter" SelectionChanged="filter_SelectionChanged">
<ComboBoxItem x:Uid="/Inbox/all" Content="All"/> <ComboBoxItem x:Uid="/Inbox/all" Content="All"/>
<ComboBoxItem x:Uid="/Inbox/messages" Content="Messages"/> <ComboBoxItem x:Uid="/Inbox/messages" Content="Messages"/>
<ComboBoxItem x:Uid="/Inbox/changelogs" Content="Patch notes"/> <ComboBoxItem x:Uid="/Inbox/changelogs" Content="Patch notes"/>
</ComboBox> </ComboBox>
<ListBox Name="list" SelectionChanged="list_SelectionChanged" Background="Transparent"> <ListView Name="list" SelectionChanged="list_SelectionChanged" Background="Transparent">
<ListBox.ItemContainerStyle> <ListView.ItemContainerStyle>
<Style TargetType="ListBoxItem"> <Style TargetType="ListViewItem">
<Setter Property="Padding" Value="10"/> <Setter Property="Padding" Value="10"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/> <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style> </Style>
</ListBox.ItemContainerStyle> </ListView.ItemContainerStyle>
<ListBox.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid> <Grid Margin="0, 10">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/> <ColumnDefinition Width="auto"/>
<ColumnDefinition Width="2*"/> <ColumnDefinition Width="2*"/>
<ColumnDefinition/> </Grid.ColumnDefinitions>
</Grid.ColumnDefinitions> <Grid Margin="0,0,10,0">
<Grid Margin="0,0,10,0"> <Ellipse Fill="Red" Height="40" Width="40"/>
<Ellipse Fill="Red" Height="40" Width="40"/> <TextBlock Foreground="White" FontFamily="Segoe MDL2 Assets" Text="{Binding Path=Icon}" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Light" FontSize="17"/>
<TextBlock Foreground="White" FontFamily="Segoe MDL2 Assets" Text="{Binding Path=Icon}" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Light" FontSize="17"/>
</Grid>
<StackPanel Grid.Column="1">
<TextBlock FontWeight="Bold" Text="{Binding Path=Title}" MaxLines="1" TextWrapping="Wrap"/>
<TextBlock Opacity=".5" Text="{Binding Path=Subtitle}"/>
</StackPanel>
<TextBlock Opacity=".5" FontSize="13" Text="{Binding Path=TimeStamp}" Grid.Column="2" TextWrapping="WrapWholeWords"/>
</Grid> </Grid>
</DataTemplate> <StackPanel Grid.Column="1">
</ListBox.ItemTemplate> <TextBlock FontWeight="Bold" Text="{Binding Path=Title}" MaxLines="1" TextWrapping="Wrap"/>
</ListBox> <TextBlock Opacity=".5" Text="{Binding Path=Subtitle}"/>
</StackPanel> <TextBlock Opacity=".5" FontSize="13" Text="{Binding Path=TimeStampString}" TextWrapping="WrapWholeWords"/>
</ScrollViewer> </StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<ScrollViewer Grid.Column="1"> <ScrollViewer Grid.Column="1">
<StackPanel Margin="10"> <StackPanel Margin="10">
@@ -99,8 +97,8 @@
<Button Grid.Column="1" VerticalAlignment="Top" HorizontalAlignment="Right" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE106;" Width="50" Height="50" Name="close" Click="close_Click"/> <Button Grid.Column="1" VerticalAlignment="Top" HorizontalAlignment="Right" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE106;" Width="50" Height="50" Name="close" Click="close_Click"/>
<Grid Grid.Column="1" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Name="block"> <Grid Grid.Column="1" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Name="block">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"> <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xE7EA;" FontSize="70" Foreground="Gray" Margin="10"/> <TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xE7EA;" FontSize="50" Foreground="Gray" Margin="10"/>
<TextBlock x:Uid="/Inbox/select" Text="Select item from list" FontSize="30" VerticalAlignment="Center" TextWrapping="WrapWholeWords" Foreground="Gray"/> <TextBlock x:Uid="/Inbox/select" Text="Select item from list" FontSize="24" VerticalAlignment="Center" TextWrapping="WrapWholeWords" Foreground="Gray"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Grid> </Grid>
+15 -6
View File
@@ -6,7 +6,6 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using FoxTube.Classes; using FoxTube.Classes;
using Windows.Storage; using Windows.Storage;
using System.Diagnostics;
using System.Xml; using System.Xml;
namespace FoxTube.Pages.SettingsPages namespace FoxTube.Pages.SettingsPages
@@ -17,6 +16,8 @@ namespace FoxTube.Pages.SettingsPages
public sealed partial class Inbox : Page public sealed partial class Inbox : Page
{ {
List<InboxItem> items = new List<InboxItem>(); List<InboxItem> items = new List<InboxItem>();
string open;
public Inbox() public Inbox()
{ {
InitializeComponent(); InitializeComponent();
@@ -39,7 +40,7 @@ namespace FoxTube.Pages.SettingsPages
doc.Load("http://foxgame-studio.000webhostapp.com/foxtube-messages.xml"); doc.Load("http://foxgame-studio.000webhostapp.com/foxtube-messages.xml");
foreach (XmlElement e in doc["posts"].ChildNodes) foreach (XmlElement e in doc["posts"].ChildNodes)
items.Add(new InboxItem( items.Add(new InboxItem(
e["header"].InnerText, e["header"][SettingsStorage.Language].InnerText,
e["content"][SettingsStorage.Language].InnerText, e["content"][SettingsStorage.Language].InnerText,
DateTime.Parse(e.GetAttribute("time")), DateTime.Parse(e.GetAttribute("time")),
e["id"].InnerText e["id"].InnerText
@@ -50,6 +51,11 @@ namespace FoxTube.Pages.SettingsPages
catch { } catch { }
items.ForEach(i => list.Items.Add(i)); items.ForEach(i => list.Items.Add(i));
if (!string.IsNullOrWhiteSpace(open))
Open(open);
open = null;
} }
private void filter_SelectionChanged(object sender, SelectionChangedEventArgs e) private void filter_SelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -88,7 +94,6 @@ namespace FoxTube.Pages.SettingsPages
{ {
grid.ColumnDefinitions[0].Width = new GridLength(0); grid.ColumnDefinitions[0].Width = new GridLength(0);
grid.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star); grid.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);
Debug.WriteLine("Opened");
} }
} }
} }
@@ -115,14 +120,18 @@ namespace FoxTube.Pages.SettingsPages
{ {
grid.ColumnDefinitions[0].Width = new GridLength(1, GridUnitType.Star); grid.ColumnDefinitions[0].Width = new GridLength(1, GridUnitType.Star);
grid.ColumnDefinitions[1].Width = new GridLength(0); grid.ColumnDefinitions[1].Width = new GridLength(0);
Debug.WriteLine("Closed");
} }
} }
public void Open(string arg) public void Open(string arg)
{ {
string id = arg.Split('&')[1]; if(items.Count == 0)
InboxItem item = items.Find(x => x.Id == id); {
open = arg;
return;
}
InboxItem item = items.Find(i => i.Id == arg);
if(item != null) if(item != null)
list.SelectedItem = item; list.SelectedItem = item;
} }
+6 -2
View File
@@ -20,8 +20,12 @@
<ColumnDefinition Width="55"/> <ColumnDefinition Width="55"/>
<ColumnDefinition/> <ColumnDefinition/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<PersonPicture Height="50" HorizontalAlignment="Left" ProfilePicture="{Binding Path=Snippet.Thumbnails.Medium.Url}"/> <PersonPicture Height="50" HorizontalAlignment="Left">
<TextBlock Grid.Column="1" TextWrapping="Wrap" VerticalAlignment="Center" Text="{Binding Path=Snippet.Title}"/> <PersonPicture.ProfilePicture>
<BitmapImage UriSource="{Binding Path=Snippet.Thumbnails.Medium.Url}" DecodePixelHeight="50" DecodePixelWidth="50"/>
</PersonPicture.ProfilePicture>
</PersonPicture>
<TextBlock Grid.Column="1" TextWrapping="Wrap" VerticalAlignment="Center" TextTrimming="CharacterEllipsis" Text="{Binding Path=Snippet.Title}"/>
</Grid> </Grid>
</Button> </Button>
</DataTemplate> </DataTemplate>
+2 -1
View File
@@ -8,8 +8,9 @@ namespace FoxTube.Pages
/// <summary> /// <summary>
/// User's subscriptions page /// User's subscriptions page
/// </summary> /// </summary>
public sealed partial class Subscriptions : Page public sealed partial class Subscriptions : Page, NavigationPage
{ {
public object Parameter { get; set; } = null;
readonly List<Subscription> list = SecretsVault.Subscriptions; readonly List<Subscription> list = SecretsVault.Subscriptions;
public Subscriptions() public Subscriptions()
{ {
+15 -11
View File
@@ -4,18 +4,22 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 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"> mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Name="grid" SizeChanged="Grid_SizeChanged">
<Grid.RowDefinitions> <Grid.ColumnDefinitions>
<RowDefinition Height="auto"/> <ColumnDefinition/>
<RowDefinition/> <ColumnDefinition Width="0"/>
</Grid.RowDefinitions> <ColumnDefinition Width="0"/>
<controls:Advert/> <ColumnDefinition Width="0"/>
<ui:AdaptiveGridView Name="list" OneRowModeEnabled="False" DesiredWidth="384" SelectionMode="None" Grid.Row="1"/> <ColumnDefinition Width="0"/>
<TextBlock Name="empty" Text="&#xD8;" FontSize="200" Foreground="Gray" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.RowSpan="2"/> </Grid.ColumnDefinitions>
<TextBlock Name="empty" Grid.ColumnSpan="5" Text="&#xD8;" FontSize="200" Foreground="Gray" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.RowSpan="2"/>
<StackPanel Grid.Column="0" Name="col0"/>
<StackPanel Grid.Column="1" Name="col1"/>
<StackPanel Grid.Column="2" Name="col2"/>
<StackPanel Grid.Column="3" Name="col3"/>
<StackPanel Grid.Column="4" Name="col4"/>
</Grid> </Grid>
</Page> </Page>
+49 -3
View File
@@ -1,4 +1,5 @@
using Windows.UI.Xaml; using System.Collections.Generic;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
namespace FoxTube.Pages namespace FoxTube.Pages
@@ -8,6 +9,19 @@ namespace FoxTube.Pages
/// </summary> /// </summary>
public sealed partial class VideoGrid : Page 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<UIElement> Children { get; } = new List<UIElement>();
public VideoGrid() public VideoGrid()
{ {
InitializeComponent(); InitializeComponent();
@@ -15,7 +29,8 @@ namespace FoxTube.Pages
public void Add(UIElement card) 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) /*if (list.Items.Count % 10 == 0)
list.Items.Add(new CardAdvert());*/ list.Items.Add(new CardAdvert());*/
empty.Visibility = Visibility.Collapsed; empty.Visibility = Visibility.Collapsed;
@@ -23,8 +38,39 @@ namespace FoxTube.Pages
public void Clear() public void Clear()
{ {
list.Items.Clear(); for (int k = 1; k <= 5; k++)
(grid.Children[k] as StackPanel).Children.Clear();
empty.Visibility = Visibility.Visible; 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;
}
} }
} }
+76 -20
View File
@@ -9,7 +9,6 @@
xmlns:controls1="using:FoxTube.Controls" xmlns:controls1="using:FoxTube.Controls"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid Name="grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" SizeChanged="grid_SizeChanged"> <Grid Name="grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" SizeChanged="grid_SizeChanged">
<VisualStateManager.VisualStateGroups> <VisualStateManager.VisualStateGroups>
<VisualStateGroup> <VisualStateGroup>
@@ -30,12 +29,38 @@
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="400"/> <ColumnDefinition Width="400"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<ScrollViewer Margin="0,0,0,50" Name="mainScroll"> <ScrollViewer Margin="0,0,0,50" Name="mainScroll" VerticalScrollBarVisibility="Hidden">
<StackPanel Orientation="Vertical" Name="mainContent"> <StackPanel Orientation="Vertical" Name="mainContent">
<local:VideoPlayer/> <Border BorderBrush="Red" BorderThickness="5" CornerRadius="10" Margin="0,10" Name="upcoming" Visibility="Collapsed">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<FontIcon Glyph="&#xE704;" FontSize="50" VerticalAlignment="Center"/>
<TextBlock x:Uid="/VideoPage/upcomingHeader" FontWeight="Bold" FontSize="25" Text="Stream hasn't started yet" Grid.Column="1" Margin="10,0" VerticalAlignment="Center"/>
<StackPanel Grid.Column="2" Margin="10,0" Name="schedule" Visibility="Collapsed">
<TextBlock x:Uid="/VideoPage/scheduleHeader" FontSize="20" FontWeight="SemiBold" Text="Stream schedule:"/>
<TextBlock Name="start" Text="Starts at: 2/15/2019 21:00:00" Visibility="Collapsed"/>
<TextBlock Name="end" Text="Ends at: 2/15/2019 23:00:00" Visibility="Collapsed"/>
</StackPanel>
<StackPanel Grid.Column="3" Margin="10,0" Name="countdownPanel" Visibility="Collapsed">
<TextBlock x:Uid="/VideoPage/countdown" FontSize="20" FontWeight="SemiBold" Text="Stream starts in:"/>
<TextBlock Name="countdown" FontWeight="Bold" FontSize="35" Text="00:12:12"/>
</StackPanel>
</Grid>
</Border>
<local:VideoPlayer x:Name="player" NextClicked="Player_NextClicked" MiniMode="Player_Minimize"/>
<PivotItem Header="Description" Name="descriptionPanel"> <PivotItem Header="Description" Name="descriptionPanel">
<StackPanel Margin="0,10"> <StackPanel Margin="0,10">
<TextBlock IsTextSelectionEnabled="True" Name="title" Text="[Video title]" FontSize="25" TextWrapping="WrapWholeWords" HorizontalTextAlignment="Start"/> <TextBlock IsTextSelectionEnabled="True" Name="title" Text="[Video title]" FontSize="25" TextWrapping="WrapWholeWords" HorizontalTextAlignment="Start"/>
<TextBlock Text="Published at: " Name="date"/>
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
@@ -55,6 +80,10 @@
<TextBlock Name="views" Text="[views]" FontSize="24" Foreground="Gray"/> <TextBlock Name="views" Text="[views]" FontSize="24" Foreground="Gray"/>
<ProgressBar Name="rating" Background="Green" Foreground="Red"/> <ProgressBar Name="rating" Background="Green" Foreground="Red"/>
<Grid> <Grid>
<TextBlock Foreground="Gray" Text="[dislikes]" Name="dislikes"/>
<TextBlock HorizontalAlignment="Right" Foreground="Gray" Text="[likes]" Name="likes"/>
</Grid>
<Grid BorderBrush="{StaticResource SystemControlBackgroundListMediumRevealBorderBrush}">
<FontIcon Foreground="Gray" <FontIcon Foreground="Gray"
HorizontalAlignment="Left" HorizontalAlignment="Left"
FontSize="40" FontSize="40"
@@ -67,14 +96,9 @@
Name="like" Tapped="like_Click" Name="like" Tapped="like_Click"
Glyph="&#xE19F;"/> Glyph="&#xE19F;"/>
</Grid> </Grid>
<Grid>
<TextBlock Foreground="Gray" Text="[dislikes]" Name="dislikes"/>
<TextBlock HorizontalAlignment="Right" Foreground="Gray" Text="[likes]" Name="likes"/>
</Grid>
</StackPanel> </StackPanel>
</Grid> </Grid>
<TextBlock Name="description" Text="[Description]" IsTextSelectionEnabled="True" TextWrapping="WrapWholeWords"/> <TextBlock Name="description" Text="[Description]" IsTextSelectionEnabled="True" TextWrapping="WrapWholeWords"/>
<TextBlock Margin="0,20" Name="category" Text="Category: "/>
</StackPanel> </StackPanel>
</PivotItem> </PivotItem>
</StackPanel> </StackPanel>
@@ -88,7 +112,35 @@
</AppBarButton> </AppBarButton>
<AppBarButton x:Uid="/VideoPage/addTo" Name="addTo" Label="Add to" Icon="Add" Visibility="Collapsed"> <AppBarButton x:Uid="/VideoPage/addTo" Name="addTo" Label="Add to" Icon="Add" Visibility="Collapsed">
<AppBarButton.Flyout> <AppBarButton.Flyout>
<Flyout> <MenuFlyout x:Name="addList">
<MenuFlyoutItem Text="New playlist" Name="newPlaylist" Click="NewPlaylist_Click">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE109;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<ToggleMenuFlyoutItem Text="Watch later" Background="Red" Name="wl" Click="Wl_Click">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE728;"/>
</MenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
<MenuFlyoutSeparator/>
<ToggleMenuFlyoutItem Text="Cats">
<ToggleMenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE292;"/>
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
<ToggleMenuFlyoutItem Text="Dogs">
<ToggleMenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE292;"/>
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
<ToggleMenuFlyoutItem Text="Porn">
<ToggleMenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE292;"/>
</ToggleMenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
</MenuFlyout>
<!--<Flyout>
<ScrollViewer Margin="-12" MaxHeight="300"> <ScrollViewer Margin="-12" MaxHeight="300">
<NavigationViewList Width="200" IsMultiSelectCheckBoxEnabled="True" SelectionMode="Multiple"> <NavigationViewList Width="200" IsMultiSelectCheckBoxEnabled="True" SelectionMode="Multiple">
<NavigationViewItem Content="Watch later"> <NavigationViewItem Content="Watch later">
@@ -124,7 +176,7 @@
</NavigationViewItem> </NavigationViewItem>
</NavigationViewList> </NavigationViewList>
</ScrollViewer> </ScrollViewer>
</Flyout> </Flyout>-->
</AppBarButton.Flyout> </AppBarButton.Flyout>
</AppBarButton> </AppBarButton>
<AppBarButton x:Uid="/VideoPage/refresh" Name="refresh" Click="refresh_Click" Icon="Refresh" Label="Refresh page"/> <AppBarButton x:Uid="/VideoPage/refresh" Name="refresh" Click="refresh_Click" Icon="Refresh" Label="Refresh page"/>
@@ -136,22 +188,19 @@
<Pivot Grid.Row="1" Name="pivot" SelectedIndex="0" IsHeaderItemsCarouselEnabled="False"> <Pivot Grid.Row="1" Name="pivot" SelectedIndex="0" IsHeaderItemsCarouselEnabled="False">
<PivotItem x:Uid="/VideoPage/related" Header="Suggestions"> <PivotItem x:Uid="/VideoPage/related" Header="Suggestions">
<ScrollViewer> <ScrollViewer>
<StackPanel> <pages:VideoGrid x:Name="relatedVideos"/>
<controls1:Advert/>
<StackPanel Name="relatedVideos"/>
</StackPanel>
</ScrollViewer> </ScrollViewer>
</PivotItem> </PivotItem>
<PivotItem x:Uid="/VideoPage/comments" Header="Comments" Name="commentsPlaceholder"> <PivotItem x:Uid="/VideoPage/comments" Header="Comments" Name="commentsPlaceholder">
<pages:CommentsPage/> <pages:CommentsPage x:Name="comments"/>
</PivotItem> </PivotItem>
<PivotItem x:Uid="/VideoPage/playlist" Header="Playlist" Name="playlist"> <PivotItem x:Uid="/VideoPage/playlist" Header="Playlist" Name="playlist">
<ScrollViewer> <ScrollViewer Name="playlistScroll">
<StackPanel> <StackPanel>
<StackPanel Padding="8" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}"> <StackPanel Padding="8" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<TextBlock Text="Music" FontSize="26" TextWrapping="WrapWholeWords" Name="playlistName"/> <TextBlock Text="[Playlsit name]" FontSize="26" TextWrapping="WrapWholeWords" Name="playlistName"/>
<TextBlock Text="DAGames" Name="playlistChannel"/> <TextBlock Text="[Channel name]" Name="playlistChannel"/>
<TextBlock Text="15/155" Name="playlistCounter"/> <TextBlock Text="[Counter]" Name="playlistCounter"/>
</StackPanel> </StackPanel>
<ListBox Background="Transparent" SelectionChanged="ListBox_SelectionChanged" Name="playlistList"> <ListBox Background="Transparent" SelectionChanged="ListBox_SelectionChanged" Name="playlistList">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
@@ -175,6 +224,13 @@
</Pivot> </Pivot>
</Grid> </Grid>
<local:LoadingPage Grid.ColumnSpan="2" Visibility="Collapsed"/> <ContentDialog PrimaryButtonText="Create and add" Title="New playlist" DefaultButton="Primary" PrimaryButtonClick="ContentDialog_PrimaryButtonClick" Name="playlistDialog">
<StackPanel>
<TextBox PlaceholderText="Enter playlist name" Name="newListName"/>
<TextBlock Text="Invalid name: playlist with the name already exists" TextWrapping="WrapWholeWords" Foreground="Red" Visibility="Collapsed" Name="newListErr"/>
</StackPanel>
</ContentDialog>
<local:LoadingPage Grid.ColumnSpan="2" Visibility="Collapsed" x:Name="loading" RefreshPage="refresh_Click"/>
</Grid> </Grid>
</Page> </Page>
+91 -56
View File
@@ -7,6 +7,8 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Windows.ApplicationModel.DataTransfer; using Windows.ApplicationModel.DataTransfer;
using Windows.ApplicationModel.Resources; using Windows.ApplicationModel.Resources;
using Windows.Foundation; using Windows.Foundation;
@@ -50,26 +52,19 @@ namespace FoxTube.Pages
public string playlistId = null; public string playlistId = null;
public Video item; public Video item;
bool wide;
bool isExtended = false; bool isExtended = false;
Rating userRating = Rating.None; Rating userRating = Rating.None;
public VideoPlayer player;
public CommentsPage comments;
public LoadingPage loading;
DispatcherTimer liveTimer; DispatcherTimer liveTimer;
DispatcherTimer countdownTimer;
public LoadingPage LoadingPage => loading;
public VideoPlayer Player => player;
public VideoPage() public VideoPage()
{ {
InitializeComponent(); 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<DataTransferManager, DataRequestedEventArgs>(Share); DataTransferManager.GetForCurrentView().DataRequested += new TypedEventHandler<DataTransferManager, DataRequestedEventArgs>(Share);
@@ -127,6 +122,9 @@ namespace FoxTube.Pages
else else
LoadStream(); LoadStream();
if (item.Snippet.LiveBroadcastContent == "upcoming")
SetSchedule();
LoadInfo(); LoadInfo();
loading.Close(); 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) async void LoadPlaylist(string id)
{ {
playlistId = id; playlistId = id;
@@ -161,30 +189,19 @@ namespace FoxTube.Pages
PlaylistItemsResource.ListRequest listRequest = SecretsVault.Service.PlaylistItems.List("snippet"); PlaylistItemsResource.ListRequest listRequest = SecretsVault.Service.PlaylistItems.List("snippet");
listRequest.MaxResults = 50; listRequest.MaxResults = 50;
listRequest.PlaylistId = id; 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(); 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)); 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++) for (int k = 0; k < items.Count; k++)
items[k].Number = k + 1; items[k].Number = k + 1;
@@ -199,14 +216,19 @@ namespace FoxTube.Pages
playlistList.SelectedItem = selection; playlistList.SelectedItem = selection;
pivot.SelectedItem = playlist; pivot.SelectedItem = playlist;
await Task.Delay(500);
playlistScroll.ChangeView(null, playlistList.SelectedIndex * 86 + 89, null, true);
if (playlistList.SelectedIndex == playlistList.Items.Count - 1) if (playlistList.SelectedIndex == playlistList.Items.Count - 1)
player.Next.Visibility = Visibility.Collapsed; player.Controls.IsNextTrackButtonVisible = false;
} }
async void LoadInfo() async void LoadInfo()
{ {
//Setting meta //Setting meta
title.Text = item.Snippet.Title; 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); Methods.FormatText(ref description, item.Snippet.Description);
//Setting channel button //Setting channel button
@@ -214,7 +236,7 @@ namespace FoxTube.Pages
channelRequest.Id = item.Snippet.ChannelId; channelRequest.Id = item.Snippet.ChannelId;
var item1 = (await channelRequest.ExecuteAsync()).Items[0]; 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; channelName.Text = item.Snippet.ChannelTitle;
subscribers.Text = $"{item1.Statistics.SubscriberCount:0,0} {resources.GetString("/Cards/subscribers")}"; 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}"; likes.Text = $"{item.Statistics.LikeCount:0,0}";
rating.Value = (double)item.Statistics.DislikeCount / (double)(item.Statistics.DislikeCount + item.Statistics.LikeCount) * 100; 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 //Setting User's rate
if (SecretsVault.IsAuthorized) if (SecretsVault.IsAuthorized)
{ {
@@ -289,9 +306,8 @@ namespace FoxTube.Pages
{ {
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("liveStreamingDetails"); VideosResource.ListRequest request = SecretsVault.Service.Videos.List("liveStreamingDetails");
request.Id = videoId; 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() void LoadStats()
@@ -326,9 +342,9 @@ namespace FoxTube.Pages
audioItem.Click += downloadItemSelected; audioItem.Click += downloadItemSelected;
downloadSelector.Items.Add(audioItem); 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(); SearchListResponse response = await request.ExecuteAsync();
foreach (SearchResult video in response.Items) 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]; isExtended = (bool)e[0];
if(isExtended) if(isExtended)
{ {
grid.ColumnDefinitions[1].Width = new GridLength(0); grid.ColumnDefinitions[1].Width = new GridLength(0);
commandbar.Visibility = Visibility.Collapsed; commandbar.Visibility = Visibility.Collapsed;
mainScroll.Margin = new Thickness(0);
mainScroll.ChangeView(0, 0, null); mainScroll.ChangeView(0, 0, null);
mainScroll.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden;
mainScroll.Margin = new Thickness(0);
mainScroll.VerticalScrollMode = ScrollMode.Disabled; mainScroll.VerticalScrollMode = ScrollMode.Disabled;
Methods.MainPage.MinimizeVideo();
} }
else else
{ {
grid.ColumnDefinitions[1].Width = new GridLength(400);
commandbar.Visibility = Visibility.Visible; commandbar.Visibility = Visibility.Visible;
mainScroll.Margin = new Thickness(0,0,0,50); mainScroll.Margin = new Thickness(0,0,0,50);
mainScroll.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
mainScroll.VerticalScrollMode = ScrollMode.Auto; mainScroll.VerticalScrollMode = ScrollMode.Auto;
if (wide) Methods.MainPage.MaximizeVideo();
grid.ColumnDefinitions[1].Width = new GridLength(400);
} }
} }
@@ -387,8 +407,8 @@ namespace FoxTube.Pages
private async void openBrowser_Click(object sender, RoutedEventArgs e) private async void openBrowser_Click(object sender, RoutedEventArgs e)
{ {
player.Pause(); player.Pause();
string timecode = player.Elapsed.TotalSeconds > 10 ? string timecode = player.Position.TotalSeconds > 10 ?
"&t=" + (int)player.Elapsed.TotalSeconds + "s" : string.Empty; "&t=" + (int)player.Position.TotalSeconds + "s" : string.Empty;
await Launcher.LaunchUriAsync($"https://www.youtube.com/watch?v={videoId}{timecode}".ToUri()); await Launcher.LaunchUriAsync($"https://www.youtube.com/watch?v={videoId}{timecode}".ToUri());
} }
@@ -398,15 +418,14 @@ namespace FoxTube.Pages
Methods.MainPage.GoToVideo(videoId, playlistId); Methods.MainPage.GoToVideo(videoId, playlistId);
} }
public void CloseVideo()
{
player.Controls_CloseRequested(this, null);
}
private void grid_SizeChanged(object sender, SizeChangedEventArgs e) private void grid_SizeChanged(object sender, SizeChangedEventArgs e)
{ {
Debug.WriteLine(e.NewSize.Width); if (e.NewSize.Width > 1000 && mainContent.Children.Contains(pivot))
if (e.NewSize.Width > 1000)
wide = true;
else
wide = false;
if (e.NewSize.Width > 1000 && mainContent.Children.Contains(pivot) && !isExtended)
{ {
mainContent.Children.Remove(pivot); mainContent.Children.Remove(pivot);
tabsPlaceholder.Children.Add(pivot); tabsPlaceholder.Children.Add(pivot);
@@ -547,5 +566,21 @@ namespace FoxTube.Pages
subscribe.Content = resources.GetString("/Cards/subscribe/Content"); 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)
{
}
} }
} }
+3
View File
@@ -156,4 +156,7 @@
<data name="views.Text" xml:space="preserve"> <data name="views.Text" xml:space="preserve">
<value>Views:</value> <value>Views:</value>
</data> </data>
<data name="searchHeader" xml:space="preserve">
<value>Search results</value>
</data>
</root> </root>
+3
View File
@@ -120,6 +120,9 @@
<data name="box.PlaceholderText" xml:space="preserve"> <data name="box.PlaceholderText" xml:space="preserve">
<value>Send a message</value> <value>Send a message</value>
</data> </data>
<data name="failed" xml:space="preserve">
<value>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</value>
</data>
<data name="moder.Text" xml:space="preserve"> <data name="moder.Text" xml:space="preserve">
<value>Moderator</value> <value>Moderator</value>
</data> </data>
+4 -1
View File
@@ -138,12 +138,15 @@
<data name="editorCancel.Content" xml:space="preserve"> <data name="editorCancel.Content" xml:space="preserve">
<value>Cancel</value> <value>Cancel</value>
</data> </data>
<data name="editorDelete.Content" xml:space="preserve"> <data name="editorDelete.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Delete comment</value> <value>Delete comment</value>
</data> </data>
<data name="editorSubmin.Content" xml:space="preserve"> <data name="editorSubmin.Content" xml:space="preserve">
<value>Submit</value> <value>Submit</value>
</data> </data>
<data name="failedDelete" xml:space="preserve">
<value>Failed to delete your commentary. Please, try again later.</value>
</data>
<data name="failedEdit" xml:space="preserve"> <data name="failedEdit" xml:space="preserve">
<value>Failed to edit your commentary. Please, try again later.</value> <value>Failed to edit your commentary. Please, try again later.</value>
</data> </data>
+6
View File
@@ -138,6 +138,12 @@
<data name="ext" xml:space="preserve"> <data name="ext" xml:space="preserve">
<value>Extension</value> <value>Extension</value>
</data> </data>
<data name="failedBody" xml:space="preserve">
<value>Bug report has been sent</value>
</data>
<data name="failedHead" xml:space="preserve">
<value>Error occured while dowloading a video</value>
</data>
<data name="gotoOrign.Text" xml:space="preserve"> <data name="gotoOrign.Text" xml:space="preserve">
<value>Go to original</value> <value>Go to original</value>
</data> </data>
+2 -2
View File
@@ -180,13 +180,13 @@
<data name="signEx.Text" xml:space="preserve"> <data name="signEx.Text" xml:space="preserve">
<value>Sign in with existing account</value> <value>Sign in with existing account</value>
</data> </data>
<data name="signIn.Text" xml:space="preserve"> <data name="signIn.Content" xml:space="preserve">
<value>Add account</value> <value>Add account</value>
</data> </data>
<data name="signNew.Text" xml:space="preserve"> <data name="signNew.Text" xml:space="preserve">
<value>Create new Google account</value> <value>Create new Google account</value>
</data> </data>
<data name="signOut.Text" xml:space="preserve"> <data name="signOut.Content" xml:space="preserve">
<value>Log out</value> <value>Log out</value>
</data> </data>
<data name="subscriptions.Content" xml:space="preserve"> <data name="subscriptions.Content" xml:space="preserve">
+48
View File
@@ -120,6 +120,9 @@
<data name="addTo.Label" xml:space="preserve"> <data name="addTo.Label" xml:space="preserve">
<value>Add to</value> <value>Add to</value>
</data> </data>
<data name="always" xml:space="preserve">
<value>Don't ask me again</value>
</data>
<data name="audio" xml:space="preserve"> <data name="audio" xml:space="preserve">
<value>Audio track</value> <value>Audio track</value>
</data> </data>
@@ -129,6 +132,9 @@
<data name="back.Text" xml:space="preserve"> <data name="back.Text" xml:space="preserve">
<value>Go back for 10 seconds</value> <value>Go back for 10 seconds</value>
</data> </data>
<data name="cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="cast.Text" xml:space="preserve"> <data name="cast.Text" xml:space="preserve">
<value>Cast to device</value> <value>Cast to device</value>
</data> </data>
@@ -141,12 +147,18 @@
<data name="comments.Header" xml:space="preserve"> <data name="comments.Header" xml:space="preserve">
<value>Comments</value> <value>Comments</value>
</data> </data>
<data name="countdown.Text" xml:space="preserve">
<value>Stream starts in:</value>
</data>
<data name="desc" xml:space="preserve"> <data name="desc" xml:space="preserve">
<value>Description</value> <value>Description</value>
</data> </data>
<data name="download.Label" xml:space="preserve"> <data name="download.Label" xml:space="preserve">
<value>Download video</value> <value>Download video</value>
</data> </data>
<data name="end" xml:space="preserve">
<value>Ends at:</value>
</data>
<data name="exitminiview.Text" xml:space="preserve"> <data name="exitminiview.Text" xml:space="preserve">
<value>Exit compact view mode</value> <value>Exit compact view mode</value>
</data> </data>
@@ -156,9 +168,15 @@
<data name="fwd.Text" xml:space="preserve"> <data name="fwd.Text" xml:space="preserve">
<value>Skip forward for 30 seconds</value> <value>Skip forward for 30 seconds</value>
</data> </data>
<data name="generatedCaption" xml:space="preserve">
<value>(Auto-generated)</value>
</data>
<data name="goLive.Text" xml:space="preserve"> <data name="goLive.Text" xml:space="preserve">
<value>Go to live broadcast</value> <value>Go to live broadcast</value>
</data> </data>
<data name="matureText" xml:space="preserve">
<value>This content may be not apropriate for children under 18</value>
</data>
<data name="maximize.Text" xml:space="preserve"> <data name="maximize.Text" xml:space="preserve">
<value>Maximize</value> <value>Maximize</value>
</data> </data>
@@ -174,6 +192,9 @@
<data name="next.Text" xml:space="preserve"> <data name="next.Text" xml:space="preserve">
<value>Next video</value> <value>Next video</value>
</data> </data>
<data name="no" xml:space="preserve">
<value>No</value>
</data>
<data name="openWeb.Label" xml:space="preserve"> <data name="openWeb.Label" xml:space="preserve">
<value>Open in browser</value> <value>Open in browser</value>
</data> </data>
@@ -195,15 +216,30 @@
<data name="related.Header" xml:space="preserve"> <data name="related.Header" xml:space="preserve">
<value>Suggestons</value> <value>Suggestons</value>
</data> </data>
<data name="schedule.Text" xml:space="preserve">
<value>Stream schedule:</value>
</data>
<data name="share.Label" xml:space="preserve"> <data name="share.Label" xml:space="preserve">
<value>Share</value> <value>Share</value>
</data> </data>
<data name="signin" xml:space="preserve">
<value>Sign in</value>
</data>
<data name="signRequired" xml:space="preserve">
<value>Please, sign into your account</value>
</data>
<data name="start" xml:space="preserve">
<value>Starts at:</value>
</data>
<data name="streamElapsed.Text" xml:space="preserve"> <data name="streamElapsed.Text" xml:space="preserve">
<value>Elapsed time since stream start</value> <value>Elapsed time since stream start</value>
</data> </data>
<data name="subs.Text" xml:space="preserve"> <data name="subs.Text" xml:space="preserve">
<value>Captions</value> <value>Captions</value>
</data> </data>
<data name="subsSelector.Header" xml:space="preserve">
<value>Language</value>
</data>
<data name="subsSelector.PlaceholderText" xml:space="preserve"> <data name="subsSelector.PlaceholderText" xml:space="preserve">
<value>No captions are available</value> <value>No captions are available</value>
</data> </data>
@@ -213,7 +249,19 @@
<data name="subsSwitch.OnContent" xml:space="preserve"> <data name="subsSwitch.OnContent" xml:space="preserve">
<value>Captions</value> <value>Captions</value>
</data> </data>
<data name="upcomingHeader.Text" xml:space="preserve">
<value>Stream hasn't started yet</value>
</data>
<data name="volume.Text" xml:space="preserve"> <data name="volume.Text" xml:space="preserve">
<value>Volume</value> <value>Volume</value>
</data> </data>
<data name="wantContinue" xml:space="preserve">
<value>Do you want to continue</value>
</data>
<data name="yes" xml:space="preserve">
<value>Yes</value>
</data>
<data name="publishedAt" xml:space="preserve">
<value>Published at</value>
</data>
</root> </root>
+3
View File
@@ -156,4 +156,7 @@
<data name="views.Text" xml:space="preserve"> <data name="views.Text" xml:space="preserve">
<value>Просмотры: </value> <value>Просмотры: </value>
</data> </data>
<data name="searchHeader" xml:space="preserve">
<value>Результаты поиска</value>
</data>
</root> </root>
+3
View File
@@ -120,6 +120,9 @@
<data name="box.PlaceholderText" xml:space="preserve"> <data name="box.PlaceholderText" xml:space="preserve">
<value>Отправить сообщение</value> <value>Отправить сообщение</value>
</data> </data>
<data name="failed" xml:space="preserve">
<value>Не удалось отправить ваше сообщение. Это могло произойти из-за включенного в этом чате замедленного режима. Пожалуйста, попробуйте позже. P.S. Отчет об ошибке был отправлен разработчикам</value>
</data>
<data name="moder.Text" xml:space="preserve"> <data name="moder.Text" xml:space="preserve">
<value>Модератор</value> <value>Модератор</value>
</data> </data>
+4 -1
View File
@@ -138,12 +138,15 @@
<data name="editorCancel.Content" xml:space="preserve"> <data name="editorCancel.Content" xml:space="preserve">
<value>Отмена</value> <value>Отмена</value>
</data> </data>
<data name="editorDelete.Content" xml:space="preserve"> <data name="editorDelete.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Удалить комментарий</value> <value>Удалить комментарий</value>
</data> </data>
<data name="editorSubmin.Content" xml:space="preserve"> <data name="editorSubmin.Content" xml:space="preserve">
<value>Отправить</value> <value>Отправить</value>
</data> </data>
<data name="failedDelete" xml:space="preserve">
<value>Не удалось удалить комментарий. Пожалуйста, попробуйте позже</value>
</data>
<data name="failedEdit" xml:space="preserve"> <data name="failedEdit" xml:space="preserve">
<value>Не удалось изменить комментарий. Пожалуйста, попробуйте позже</value> <value>Не удалось изменить комментарий. Пожалуйста, попробуйте позже</value>
</data> </data>
+6
View File
@@ -138,6 +138,12 @@
<data name="ext" xml:space="preserve"> <data name="ext" xml:space="preserve">
<value>Расширение</value> <value>Расширение</value>
</data> </data>
<data name="failedBody" xml:space="preserve">
<value>Отчет об ошибке был отправлен</value>
</data>
<data name="failedHead" xml:space="preserve">
<value>При загрузке видео произошла ошибка</value>
</data>
<data name="gotoOrign.Text" xml:space="preserve"> <data name="gotoOrign.Text" xml:space="preserve">
<value>Открыть оригинал</value> <value>Открыть оригинал</value>
</data> </data>
+2 -2
View File
@@ -180,13 +180,13 @@
<data name="signEx.Text" xml:space="preserve"> <data name="signEx.Text" xml:space="preserve">
<value>Войти с помощью существующего аккаунта Google</value> <value>Войти с помощью существующего аккаунта Google</value>
</data> </data>
<data name="signIn.Text" xml:space="preserve"> <data name="signIn.Content" xml:space="preserve">
<value>Войти в аккаунт</value> <value>Войти в аккаунт</value>
</data> </data>
<data name="signNew.Text" xml:space="preserve"> <data name="signNew.Text" xml:space="preserve">
<value>Создать новый аккаунт Google</value> <value>Создать новый аккаунт Google</value>
</data> </data>
<data name="signOut.Text" xml:space="preserve"> <data name="signOut.Content" xml:space="preserve">
<value>Выйти</value> <value>Выйти</value>
</data> </data>
<data name="subscriptions.Content" xml:space="preserve"> <data name="subscriptions.Content" xml:space="preserve">
+48
View File
@@ -120,6 +120,9 @@
<data name="addTo.Label" xml:space="preserve"> <data name="addTo.Label" xml:space="preserve">
<value>Добавить в</value> <value>Добавить в</value>
</data> </data>
<data name="always" xml:space="preserve">
<value>Больше не спрашивать</value>
</data>
<data name="audio" xml:space="preserve"> <data name="audio" xml:space="preserve">
<value>Аудио дорожка</value> <value>Аудио дорожка</value>
</data> </data>
@@ -129,6 +132,9 @@
<data name="back.Text" xml:space="preserve"> <data name="back.Text" xml:space="preserve">
<value>Назад на 10 секунд</value> <value>Назад на 10 секунд</value>
</data> </data>
<data name="cancel" xml:space="preserve">
<value>Отмена</value>
</data>
<data name="cast.Text" xml:space="preserve"> <data name="cast.Text" xml:space="preserve">
<value>Отправить на устройство</value> <value>Отправить на устройство</value>
</data> </data>
@@ -141,12 +147,18 @@
<data name="comments.Header" xml:space="preserve"> <data name="comments.Header" xml:space="preserve">
<value>Комментарии</value> <value>Комментарии</value>
</data> </data>
<data name="countdown.Text" xml:space="preserve">
<value>Стрим начнется через:</value>
</data>
<data name="desc" xml:space="preserve"> <data name="desc" xml:space="preserve">
<value>Описание</value> <value>Описание</value>
</data> </data>
<data name="download.Label" xml:space="preserve"> <data name="download.Label" xml:space="preserve">
<value>Скачать видео</value> <value>Скачать видео</value>
</data> </data>
<data name="end" xml:space="preserve">
<value>Конец:</value>
</data>
<data name="exitminiview.Text" xml:space="preserve"> <data name="exitminiview.Text" xml:space="preserve">
<value>Развернуть</value> <value>Развернуть</value>
</data> </data>
@@ -156,9 +168,15 @@
<data name="fwd.Text" xml:space="preserve"> <data name="fwd.Text" xml:space="preserve">
<value>Вперед на 30 секунд</value> <value>Вперед на 30 секунд</value>
</data> </data>
<data name="generatedCaption" xml:space="preserve">
<value>(Авто-перевод)</value>
</data>
<data name="goLive.Text" xml:space="preserve"> <data name="goLive.Text" xml:space="preserve">
<value>Перейти к прямому эфиру</value> <value>Перейти к прямому эфиру</value>
</data> </data>
<data name="matureText" xml:space="preserve">
<value>Этот контент может быть неподходящим для лиц младше 18 лет</value>
</data>
<data name="maximize.Text" xml:space="preserve"> <data name="maximize.Text" xml:space="preserve">
<value>Развернуть</value> <value>Развернуть</value>
</data> </data>
@@ -174,6 +192,9 @@
<data name="next.Text" xml:space="preserve"> <data name="next.Text" xml:space="preserve">
<value>Следующее видео</value> <value>Следующее видео</value>
</data> </data>
<data name="no" xml:space="preserve">
<value>Нет</value>
</data>
<data name="openWeb.Label" xml:space="preserve"> <data name="openWeb.Label" xml:space="preserve">
<value>Открыть в браузере</value> <value>Открыть в браузере</value>
</data> </data>
@@ -195,15 +216,30 @@
<data name="related.Header" xml:space="preserve"> <data name="related.Header" xml:space="preserve">
<value>Похожее</value> <value>Похожее</value>
</data> </data>
<data name="schedule.Text" xml:space="preserve">
<value>Расписание эфира</value>
</data>
<data name="share.Label" xml:space="preserve"> <data name="share.Label" xml:space="preserve">
<value>Поделиться</value> <value>Поделиться</value>
</data> </data>
<data name="signin" xml:space="preserve">
<value>Войти</value>
</data>
<data name="signRequired" xml:space="preserve">
<value>Пожалуйста, войдите в аккаунт</value>
</data>
<data name="start" xml:space="preserve">
<value>Начало:</value>
</data>
<data name="streamElapsed.Text" xml:space="preserve"> <data name="streamElapsed.Text" xml:space="preserve">
<value>Время, прошедшее с начала стрима</value> <value>Время, прошедшее с начала стрима</value>
</data> </data>
<data name="subs.Text" xml:space="preserve"> <data name="subs.Text" xml:space="preserve">
<value>Субтитры</value> <value>Субтитры</value>
</data> </data>
<data name="subsSelector.Header" xml:space="preserve">
<value>Язык субтитров</value>
</data>
<data name="subsSelector.PlaceholderText" xml:space="preserve"> <data name="subsSelector.PlaceholderText" xml:space="preserve">
<value>Нет доступных субтитров</value> <value>Нет доступных субтитров</value>
</data> </data>
@@ -213,7 +249,19 @@
<data name="subsSwitch.OnContent" xml:space="preserve"> <data name="subsSwitch.OnContent" xml:space="preserve">
<value>Субтитры</value> <value>Субтитры</value>
</data> </data>
<data name="upcomingHeader.Text" xml:space="preserve">
<value>Прямой эфир еще не начался</value>
</data>
<data name="volume.Text" xml:space="preserve"> <data name="volume.Text" xml:space="preserve">
<value>Громкость</value> <value>Громкость</value>
</data> </data>
<data name="wantContinue" xml:space="preserve">
<value>Хотите продолжить?</value>
</data>
<data name="yes" xml:space="preserve">
<value>Да</value>
</data>
<data name="publishedAt" xml:space="preserve">
<value>Опубликовано</value>
</data>
</root> </root>
+636
View File
@@ -0,0 +1,636 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FoxTube"
xmlns:controls="using:FoxTube.Controls">
<Style TargetType="local:PlayerControls">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="FlowDirection" Value="LeftToRight" />
<Setter Property="UseSystemFocusVisuals" Value="True" />
<Setter Property="IsTextScaleFactorEnabled" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:PlayerControls">
<Grid Background="Transparent">
<Grid.Resources>
<Style TargetType="Button" BasedOn="{StaticResource ButtonRevealStyle}">
<Setter Property="Width" Value="50"/>
<Setter Property="Height" Value="50"/>
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style x:Key="AppBarButtonStyle" TargetType="AppBarButton">
<Setter Property="Width" Value="{ThemeResource MTCMediaButtonWidth}" />
<Setter Property="Height" Value="{ThemeResource MTCMediaButtonHeight}" />
<Setter Property="AllowFocusOnInteraction" Value="True" />
</Style>
<Style x:Key="CommandBarStyle" TargetType="CommandBar">
<Setter Property="Height" Value="{ThemeResource MTCMediaButtonHeight}" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="MediaTextBlockStyle" TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}" />
<Setter Property="FontSize" Value="{ThemeResource MTCMediaFontSize}" />
<Setter Property="FontFamily" Value="{ThemeResource MTCMediaFontFamily}" />
<Setter Property="Style" Value="{ThemeResource CaptionTextBlockStyle }" />
<Setter Property="IsTextScaleFactorEnabled" Value="False" />
</Style>
<Style x:Key="PlayerSeek" TargetType="Slider">
<Setter Property="Background" Value="{ThemeResource SliderTrackFill}" />
<Setter Property="BorderThickness" Value="{ThemeResource SliderBorderThemeThickness}" />
<Setter Property="Foreground" Value="{ThemeResource SliderTrackValueFill}" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="ManipulationMode" Value="None" />
<Setter Property="UseSystemFocusVisuals" Value="True" />
<Setter Property="IsFocusEngagementEnabled" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Slider">
<Grid Margin="{TemplateBinding Padding}">
<Grid.Resources>
<Style TargetType="Thumb" x:Key="SliderThumbStyle">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="{ThemeResource SliderThumbBackground}" />
<Setter Property="Foreground" Value="{ThemeResource SystemControlBackgroundChromeMediumBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Ellipse x:Name="ellipse"
Stroke="{TemplateBinding Background}"
StrokeThickness="2"
Fill="{TemplateBinding Foreground}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="ProgressBar" x:Key="MediaSliderProgressBarStyle">
<Setter Property="Height" Value="{ThemeResource SliderTrackThemeHeight}" />
<Setter Property="Minimum" Value="0" />
<Setter Property="Maximum" Value="100" />
<Setter Property="Foreground" Value="{ThemeResource SystemControlHighlightChromeAltLowBrush}" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="1" />
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPressed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderHeaderForegroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TopTickBar" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BottomTickBar" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LeftTickBar" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RightTickBar" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusEngagementStates">
<VisualState x:Name="FocusDisengaged" />
<VisualState x:Name="FocusEngagedHorizontal">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
<DiscreteObjectKeyFrame KeyTime="0" Value="False" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
<DiscreteObjectKeyFrame KeyTime="0" Value="True" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="FocusEngagedVertical">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
<DiscreteObjectKeyFrame KeyTime="0" Value="False" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
<DiscreteObjectKeyFrame KeyTime="0" Value="True" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter x:Name="HeaderContentPresenter"
x:DeferLoadStrategy="Lazy"
Visibility="Collapsed"
Foreground="{ThemeResource SliderHeaderForeground}"
Margin="{ThemeResource SliderHeaderThemeMargin}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
FontWeight="{ThemeResource SliderHeaderThemeFontWeight}"
TextWrapping="Wrap" />
<Grid x:Name="SliderContainer"
Background="{ThemeResource SliderContainerBackground}"
Grid.Row="1"
Control.IsTemplateFocusTarget="True">
<Grid x:Name="HorizontalTemplate" MinHeight="44">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="15" />
<RowDefinition Height="Auto" />
<RowDefinition Height="15" />
</Grid.RowDefinitions>
<Rectangle x:Name="HorizontalTrackRect"
Fill="{TemplateBinding Background}"
Height="{ThemeResource SliderTrackThemeHeight}"
Grid.Row="1"
Grid.ColumnSpan="3" />
<ProgressBar x:Name="DownloadProgressIndicator"
Style="{StaticResource MediaSliderProgressBarStyle}"
Grid.Row="1"
Grid.ColumnSpan="3"
HorizontalAlignment="Stretch"
VerticalAlignment="Center" />
<Rectangle x:Name="HorizontalDecreaseRect" Fill="{TemplateBinding Foreground}" Grid.Row="1" />
<TickBar x:Name="TopTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderTickBarFill}"
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
VerticalAlignment="Bottom"
Margin="0,0,0,4"
Grid.ColumnSpan="3" />
<TickBar x:Name="HorizontalInlineTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderInlineTickBarFill}"
Height="{ThemeResource SliderTrackThemeHeight}"
Grid.Row="1"
Grid.ColumnSpan="3" />
<TickBar x:Name="BottomTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderTickBarFill}"
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
VerticalAlignment="Top"
Margin="0,4,0,0"
Grid.Row="2"
Grid.ColumnSpan="3" />
<Thumb x:Name="HorizontalThumb"
Style="{StaticResource SliderThumbStyle}"
Height="15"
Width="15"
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="1"
AutomationProperties.AccessibilityView="Raw">
<ToolTipService.ToolTip>
<ToolTip x:Name="ThumbnailTooltip">
<ContentPresenter Content="{Binding}" />
</ToolTip>
</ToolTipService.ToolTip>
<Thumb.DataContext>
<Grid Height="112" Width="192">
<Image x:Name="ThumbnailImage"/>
<Border Background="{ThemeResource SystemControlBackgroundBaseMediumBrush}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left">
<TextBlock x:Name="TimeElapsedPreview"
Margin="6,1,6,3"
Style="{StaticResource BodyTextBlockStyle}"
IsTextScaleFactorEnabled="False"
Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}" />
</Border>
</Grid>
</Thumb.DataContext>
</Thumb>
</Grid>
<Grid x:Name="VerticalTemplate" MinWidth="44" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="18" />
</Grid.ColumnDefinitions>
<Rectangle x:Name="VerticalTrackRect"
Fill="{TemplateBinding Background}"
Width="{ThemeResource SliderTrackThemeHeight}"
Grid.Column="1"
Grid.RowSpan="3" />
<Rectangle x:Name="VerticalDecreaseRect"
Fill="{TemplateBinding Foreground}"
Grid.Column="1"
Grid.Row="2" />
<TickBar x:Name="LeftTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderTickBarFill}"
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
HorizontalAlignment="Right"
Margin="0,0,4,0"
Grid.RowSpan="3" />
<TickBar x:Name="VerticalInlineTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderInlineTickBarFill}"
Width="{ThemeResource SliderTrackThemeHeight}"
Grid.Column="1"
Grid.RowSpan="3" />
<TickBar x:Name="RightTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderTickBarFill}"
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
HorizontalAlignment="Left"
Margin="4,0,0,0"
Grid.Column="2"
Grid.RowSpan="3" />
<Thumb x:Name="VerticalThumb"
Style="{StaticResource SliderThumbStyle}"
DataContext="{TemplateBinding Value}"
Width="24"
Height="8"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
FocusVisualMargin="-6,-14,-6,-14"
AutomationProperties.AccessibilityView="Raw" />
</Grid>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<VisualStateManager.VisualStateGroups>
<!-- ControlPanel Visibility states -->
<VisualStateGroup x:Name="ControlPanelVisibilityStates">
<VisualState x:Name="ControlPanelFadeIn">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Border">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="center">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="centerBgr">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetProperty="Y" Storyboard.TargetName="TranslateVerticalBottom" From="50" To ="0" Duration="0:0:0.3"/>
<DoubleAnimation Storyboard.TargetProperty="Y" Storyboard.TargetName="TranslateVerticalTop" From="-50" To ="0" Duration="0:0:0.3"/>
</Storyboard>
</VisualState>
<VisualState x:Name="ControlPanelFadeOut">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Border">
<EasingDoubleKeyFrame KeyTime="0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="center">
<EasingDoubleKeyFrame KeyTime="0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="centerBgr">
<EasingDoubleKeyFrame KeyTime="0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0" />
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="Border">
<DiscreteObjectKeyFrame KeyTime="0" Value="False" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="center">
<DiscreteObjectKeyFrame KeyTime="0" Value="False" />
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetProperty="Y" Storyboard.TargetName="TranslateVerticalBottom" From="0" To ="50" Duration="0:0:0.7"/>
<DoubleAnimation Storyboard.TargetProperty="Y" Storyboard.TargetName="TranslateVerticalTop" From="0" To ="-50" Duration="0:0:0.7"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<!-- ControlPanel Visibility states -->
<!-- Media state visual states -->
<VisualStateGroup x:Name="MediaStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Buffering">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="BufferingProgressBar">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Loading">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="BufferingProgressBar">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetName="ProgressSlider"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="0" />
</Storyboard>
</VisualState>
<!--<VisualState x:Name="Error">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ErrorBorder">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>-->
<VisualState x:Name="Disabled">
<Storyboard />
</VisualState>
</VisualStateGroup>
<!-- Focus states -->
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="FocusVisualWhite"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0" />
<DoubleAnimation Storyboard.TargetName="FocusVisualBlack"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0" />
</Storyboard>
</VisualState>
<VisualState x:Name="Unfocused" />
<VisualState x:Name="PointerFocused" />
</VisualStateGroup>
<VisualStateGroup x:Name="PlayPauseStates">
<VisualState x:Name="PlayState" />
<VisualState x:Name="PauseState">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlayPauseSymbol" Storyboard.TargetProperty="Symbol">
<DiscreteObjectKeyFrame KeyTime="0" Value="Pause" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<!-- FullWindow states -->
<VisualStateGroup x:Name="FullWindowStates">
<VisualState x:Name="NonFullWindowState" />
<VisualState x:Name="FullWindowState">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="FullWindowSymbol" Storyboard.TargetProperty="Symbol">
<DiscreteObjectKeyFrame KeyTime="0" Value="BackToWindow" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="Border">
<Grid x:Name="ControlPanelGrid">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid x:Name="header">
<Grid.RenderTransform>
<TranslateTransform x:Name="TranslateVerticalTop"/>
</Grid.RenderTransform>
<Grid.Background>
<AcrylicBrush TintColor="#CC000000" TintOpacity=".6"/>
</Grid.Background>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Button x:Name="minimize">
<FontIcon Glyph="&#xE011;"/>
</Button>
<StackPanel Margin="10,0" Grid.Column="1" VerticalAlignment="Center">
<TextBlock x:Name="title" TextTrimming="CharacterEllipsis" FontSize="20" MaxLines="1"/>
<TextBlock Foreground="LightGray" FontStyle="Italic" x:Name="author"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Column="2" x:Name="headerToolbar">
<Button x:Name="close">
<FontIcon Glyph="&#xE106;"/>
</Button>
<Button x:Name="CastButton">
<FontIcon Glyph="&#xEC15;"/>
</Button>
<Button x:Name="CompactOverlayButton" VerticalAlignment="Top" HorizontalAlignment="Right">
<FontIcon Glyph="&#xE2B3;"/>
</Button>
</StackPanel>
</Grid>
<Grid Grid.Row="1" x:Name="centerBgr" Visibility="Collapsed" IsHitTestVisible="False" Background="#66000000"/>
<Grid Grid.Row="1" x:Name="center" Visibility="Collapsed">
<Button x:Name="maximize" VerticalAlignment="Top" HorizontalAlignment="Left" IsHitTestVisible="True">
<FontIcon Glyph="&#xE010;"/>
</Button>
<Button x:Name="compactClose" VerticalAlignment="Top" HorizontalAlignment="Right">
<FontIcon Glyph="&#xE106;"/>
</Button>
<Button Height="32" Width="50" Margin="0,0,48,0" VerticalAlignment="Top" HorizontalAlignment="Right" Visibility="Collapsed" FontFamily="Segoe MDL2 Assets" Content="&#xE700;" IsHitTestVisible="False" x:Name="dragholder"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="centralStack"/>
<ProgressBar VerticalAlignment="Bottom" x:Name="compactSeek" Background="Transparent"/>
</Grid>
<Grid Grid.Row="2" x:Name="footer">
<Grid.RenderTransform>
<TranslateTransform x:Name="TranslateVerticalBottom"/>
</Grid.RenderTransform>
<Grid.Background>
<AcrylicBrush TintColor="#CC000000" TintOpacity=".6"/>
</Grid.Background>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" x:Name="leftStack">
<Button x:Name="PlayPauseButton">
<SymbolIcon x:Name="PlayPauseSymbol" Symbol="Play"/>
</Button>
<Button x:Name="next">
<SymbolIcon Symbol="Next"/>
</Button>
<Button x:Name="volume">
<FontIcon Glyph="&#xE15D;"/>
<Button.Flyout>
<Flyout>
<StackPanel Orientation="Horizontal" Margin="-10">
<Button x:Name="AudioMuteButton" Width="50" Height="50" Background="Transparent" FontFamily="Segoe MDL2 Assets" FontSize="25">
<FontIcon Glyph="&#xE15D;"/>
</Button>
<Slider Foreground="Red" Orientation="Horizontal" Width="150" Margin="10,5,10,0" VerticalAlignment="Center" x:Name="VolumeSlider"/>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
<Button x:Name="goLive" Width="NaN" Visibility="Collapsed">
<TextBlock Text="🔴 LIVE"/>
</Button>
</StackPanel>
<Grid Grid.Column="1" Margin="10,5" x:Name="sliderPan">
<TextBlock VerticalAlignment="Bottom" HorizontalAlignment="Left" x:Name="TimeElapsedElement" Text="00:00"/>
<TextBlock VerticalAlignment="Bottom" HorizontalAlignment="Right" x:Name="TimeRemainingElement" Text="00:00"/>
<Grid VerticalAlignment="Top" Height="4" Margin="0,15,0,0">
<ProgressBar Background="#66FFFFFF" Foreground="#66FFFFFF" x:Name="BufferingProgressBar"/>
</Grid>
<Slider x:Name="ProgressSlider" Style="{StaticResource PlayerSeek}" IsThumbToolTipEnabled="False" Background="Transparent" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
</Grid>
<StackPanel Orientation="Horizontal" Grid.Column="2" x:Name="rightStack">
<Button x:Name="SkipBackwardButton">
<FontIcon Glyph="&#xED3C;"/>
</Button>
<Button x:Name="SkipForwardButton">
<FontIcon Glyph="&#xED3D;"/>
</Button>
<Line Stroke="White" StrokeThickness="2" Y1="5" Y2="45"/>
<Button x:Name="cc">
<SymbolIcon Symbol="ClosedCaption"/>
<Button.Flyout>
<Flyout>
<StackPanel Width="225">
<ToggleSwitch x:Name="ccSwitch" OnContent="Subtitles" OffContent="Subtitles" x:Uid="/VideoPage/subsSwitch"/>
<ComboBox x:Name="ccSelector" Header="Language" x:Uid="/VideoPage/subsSelector" PlaceholderText="No subtitles are available" HorizontalAlignment="Stretch"/>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
<Button x:Name="quality">
<SymbolIcon Symbol="Setting"/>
<Button.Flyout>
<Flyout>
<ComboBox Width="225" x:Uid="/VideoPage/qualitySelector" Header="Quality" x:Name="qualitySelector"/>
</Flyout>
</Button.Flyout>
</Button>
<Button x:Name="FullWindowButton">
<SymbolIcon x:Name="FullWindowSymbol" Symbol="FullScreen"/>
</Button>
</StackPanel>
</Grid>
</Grid>
</Border>
<controls:LiveCaptions Visibility="Collapsed" x:Name="captions"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1023 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1010 KiB

Some files were not shown because too many files have changed in this diff Show More