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
@@ -1,24 +1,26 @@
|
||||
using Google.Apis.Services;
|
||||
using Google.Apis.YouTube.v3;
|
||||
using Google.Apis.YouTube.v3.Data;
|
||||
using Microsoft.AppCenter.Analytics;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using Windows.ApplicationModel.Background;
|
||||
using Windows.Storage;
|
||||
using Windows.UI.Notifications;
|
||||
using YoutubeExplode.Models;
|
||||
|
||||
namespace FoxTube.Background
|
||||
{
|
||||
public sealed class BackgroundProcessor : IBackgroundTask
|
||||
{
|
||||
private DateTime lastCheck = DateTime.Now;
|
||||
private readonly ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
|
||||
private YouTubeService Service => new YouTubeService(new BaseClientService.Initializer()
|
||||
private DateTime lastCheck;
|
||||
private readonly ApplicationDataContainer settings = ApplicationData.Current.RoamingSettings;
|
||||
dynamic prefs;
|
||||
private readonly YouTubeService Service = new YouTubeService(new BaseClientService.Initializer()
|
||||
{
|
||||
ApiKey = "AIzaSyBgHrCnrlzlVmk0cJKL8RqP9Y8x6XSuk_0",
|
||||
ApplicationName = "FoxTube"
|
||||
@@ -30,21 +32,20 @@ namespace FoxTube.Background
|
||||
try
|
||||
{
|
||||
def = taskInstance.GetDeferral();
|
||||
taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);
|
||||
|
||||
if (settings.Values["lastCheck"] == null)
|
||||
{
|
||||
settings.Values.Add("lastCheck", DateTime.Now.ToString());
|
||||
settings.Values["lastCheck"] = DateTime.Now.ToString();
|
||||
def.Complete();
|
||||
return;
|
||||
}
|
||||
else
|
||||
lastCheck = DateTime.Parse(settings.Values["lastCheck"] as string);
|
||||
|
||||
bool[] notificationsSettings = JsonConvert.DeserializeObject<bool[]>(await FileIO.ReadTextAsync(await ApplicationData.Current.RoamingFolder.GetFileAsync("notifications.json")));
|
||||
if (notificationsSettings[0])
|
||||
prefs = JsonConvert.DeserializeObject<dynamic>(settings.Values["settings"] as string);
|
||||
if ((bool)prefs.devNotifications)
|
||||
CheckAnnouncements();
|
||||
if (notificationsSettings[1])
|
||||
if ((bool)prefs.videoNotifications)
|
||||
await CheckAccount();
|
||||
}
|
||||
finally
|
||||
@@ -54,20 +55,11 @@ namespace FoxTube.Background
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
|
||||
{
|
||||
Analytics.TrackEvent("Background task caneled", new Dictionary<string, string>()
|
||||
{
|
||||
{ "Reason", reason.ToString() }
|
||||
});
|
||||
settings.Values["lastCheck"] = DateTime.Now.ToString();
|
||||
}
|
||||
|
||||
async Task CheckAccount()
|
||||
{
|
||||
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>();
|
||||
|
||||
@@ -84,8 +76,15 @@ namespace FoxTube.Background
|
||||
{
|
||||
results.Add(i);
|
||||
|
||||
if(i.Snippet.LiveBroadcastContent == "live")
|
||||
ToastNotificationManager.CreateToastNotifier().Show(
|
||||
Notification.GetVideoToast(i.Id.VideoId, i.Snippet.ChannelId, i.Snippet.Title, i.Snippet.ChannelTitle, i.Snippet.Thumbnails.Medium.Url, s.Value));
|
||||
Notification.GetStreamToast(i.Id.VideoId, i.Snippet.ChannelId, i.Snippet.Title, i.Snippet.ChannelTitle, i.Snippet.Thumbnails.Medium.Url, i.Snippet.PublishedAt.Value, s.Value));
|
||||
else if(i.Snippet.LiveBroadcastContent == "upcoming")
|
||||
ToastNotificationManager.CreateToastNotifier().Show(
|
||||
Notification.GetUpcomingToast(i.Id.VideoId, i.Snippet.ChannelId, i.Snippet.Title, i.Snippet.ChannelTitle, i.Snippet.Thumbnails.Medium.Url, i.Snippet.PublishedAt.Value, s.Value));
|
||||
else
|
||||
ToastNotificationManager.CreateToastNotifier().Show(
|
||||
Notification.GetVideoToast(i.Id.VideoId, i.Snippet.ChannelId, i.Snippet.Title, i.Snippet.ChannelTitle, i.Snippet.Thumbnails.Medium.Url, i.Snippet.PublishedAt.Value, s.Value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,25 +93,28 @@ namespace FoxTube.Background
|
||||
TileUpdater updater = TileUpdateManager.CreateTileUpdaterForApplication();
|
||||
updater.EnableNotificationQueue(true);
|
||||
updater.Clear();
|
||||
for (int i = 0; i < 5; i++)
|
||||
updater.Update(Tiles.GetTileLayout(results[i].Snippet.Title, results[i].Snippet.ChannelTitle, results[i].Snippet.Thumbnails.Medium.Url, subscriptions[results[i].Snippet.ChannelId]));
|
||||
for (int i = 0; i < 5 && i < results.Count; i++)
|
||||
updater.Update(Tiles.GetTileLayout(System.Security.SecurityElement.Escape(results[i].Snippet.Title), System.Security.SecurityElement.Escape(results[i].Snippet.ChannelTitle), results[i].Snippet.Thumbnails.Medium.Url.Replace("&", "%26"), subscriptions[results[i].Snippet.ChannelId]));
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
void CheckAnnouncements()
|
||||
async void CheckAnnouncements()
|
||||
{
|
||||
try
|
||||
{
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(XmlReader.Create("http://foxgame-studio.000webhostapp.com/foxtube-messages.xml"));
|
||||
if ((DateTime.Parse((doc["posts"].FirstChild as XmlElement).GetAttribute("time")) - lastCheck).TotalSeconds > 0)
|
||||
doc.LoadXml(await new HttpClient().GetStringAsync("http://foxgame-studio.000webhostapp.com/foxtube-messages.xml"));
|
||||
XmlElement item = doc["posts"].FirstChild as XmlElement;
|
||||
|
||||
DateTime date = DateTime.Parse(item.GetAttribute("time"));
|
||||
if (date > lastCheck && date < DateTime.Now)
|
||||
ToastNotificationManager.CreateToastNotifier().Show(
|
||||
Notification.GetInternalToast(doc["posts"].FirstChild["id"].InnerText,
|
||||
doc["posts"].FirstChild["header"].InnerText,
|
||||
doc["posts"].FirstChild["content"].InnerText,
|
||||
doc["posts"].FirstChild["thumbnail"].InnerText,
|
||||
doc["posts"].FirstChild["avatar"].InnerText));
|
||||
Notification.GetInternalToast(item["id"].InnerText,
|
||||
item["header"][(string)prefs.language].InnerText,
|
||||
item["content"][(string)prefs.language].InnerText,
|
||||
item["thumbnail"].InnerText,
|
||||
item["avatar"].InnerText));
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
@@ -128,10 +128,16 @@
|
||||
<Version>1.29.2.1006</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AppCenter.Analytics">
|
||||
<Version>1.13.0</Version>
|
||||
<Version>1.13.2</Version>
|
||||
</PackageReference>
|
||||
<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>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using Newtonsoft.Json;
|
||||
using Google.Apis.YouTube.v3.Data;
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.Data.Xml.Dom;
|
||||
using Windows.Storage;
|
||||
@@ -8,18 +11,22 @@ namespace FoxTube.Background
|
||||
{
|
||||
public static class Notification
|
||||
{
|
||||
private static Dictionary<string, string> languagePack = LoadPack();
|
||||
private static readonly Dictionary<string, string> languagePack = LoadPack();
|
||||
|
||||
private static Dictionary<string, string> LoadPack()
|
||||
{
|
||||
object[] saved = JsonConvert.DeserializeObject<object[]>(ApplicationData.Current.RoamingSettings.Values["settings"] as string);
|
||||
if (saved[7] as string == "ru-RU")
|
||||
dynamic saved = JsonConvert.DeserializeObject<dynamic>(ApplicationData.Current.RoamingSettings.Values["settings"] as string);
|
||||
if (saved.language as string == "ru-RU")
|
||||
return new Dictionary<string, string>()
|
||||
{
|
||||
{ "addLater", "Посмотреть позже" },
|
||||
{ "changelog", "Список изменений" },
|
||||
{ "changelogHeader", "Что нового в версии" },
|
||||
{ "videoContent", "загрузил новое видео" },
|
||||
{ "live", "ПРЯМОЙ ЭФИР" },
|
||||
{ "upcoming", "Запланирован" },
|
||||
{ "liveContent", "начал прямой эфир" },
|
||||
{ "upcomingContent", "запланировал прямой эфир" },
|
||||
{ "goChannel", "Открыть канал" }
|
||||
};
|
||||
else
|
||||
@@ -29,6 +36,10 @@ namespace FoxTube.Background
|
||||
{ "changelog", "Changelog" },
|
||||
{ "changelogHeader", "What's new in version" },
|
||||
{ "videoContent", "uploaded a new video" },
|
||||
{ "live", "LIVE" },
|
||||
{ "upcoming", "Upcoming" },
|
||||
{ "liveContent", "started live broadcast" },
|
||||
{ "upcomingContent", "planned live broadcast" },
|
||||
{ "goChannel", "Go to channel" }
|
||||
};
|
||||
}
|
||||
@@ -51,17 +62,17 @@ namespace FoxTube.Background
|
||||
return new ToastNotification(template);
|
||||
}
|
||||
|
||||
public static ToastNotification GetVideoToast(string id, string channelId, string title, string channel, string thumbnail, string avatar)
|
||||
public static ToastNotification GetVideoToast(string id, string channelId, string title, string channel, string thumbnail, DateTimeOffset timeStamp, string avatar)
|
||||
{
|
||||
XmlDocument template = new XmlDocument();
|
||||
|
||||
template.LoadXml($@"<toast activationType='foreground' launch='video|{id}'>
|
||||
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>{title}</text>
|
||||
<text>{channel} {languagePack["videoContent"]}</text>
|
||||
<text>{System.Security.SecurityElement.Escape(title)}</text>
|
||||
<text>{System.Security.SecurityElement.Escape(channel)} {languagePack["videoContent"]}</text>
|
||||
</binding>
|
||||
</visual>
|
||||
|
||||
@@ -73,6 +84,48 @@ namespace FoxTube.Background
|
||||
|
||||
return new ToastNotification(template);
|
||||
}
|
||||
public static ToastNotification GetStreamToast(string id, string channelId, string title, string channel, string thumbnail, DateTimeOffset timeStamp, string avatar)
|
||||
{
|
||||
XmlDocument template = new XmlDocument();
|
||||
string ts = $"{timeStamp.Year}-{timeStamp.Month:00}-{timeStamp.Day:00}T{timeStamp.Hour:00}:{timeStamp.Minute:00}:{timeStamp.Second:00}Z";
|
||||
template.LoadXml($@"<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)
|
||||
{
|
||||
|
||||
@@ -5,5 +5,13 @@
|
||||
xmlns:local="using:FoxTube">
|
||||
<Application.Resources>
|
||||
<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>
|
||||
|
||||
@@ -4,13 +4,15 @@ using Microsoft.AppCenter.Analytics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Windows.ApplicationModel.Background;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Globalization;
|
||||
using Windows.Storage;
|
||||
using Windows.System;
|
||||
using Windows.System.Power;
|
||||
using Windows.UI.Notifications;
|
||||
using Windows.UI.Xaml;
|
||||
@@ -19,15 +21,9 @@ using Windows.UI.Xaml.Navigation;
|
||||
|
||||
namespace FoxTube
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
sealed partial class App : Application
|
||||
{
|
||||
/// <summary>
|
||||
/// 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>
|
||||
Stopwatch sw = new Stopwatch();
|
||||
public App()
|
||||
{
|
||||
SettingsStorage.LoadData();
|
||||
@@ -41,20 +37,46 @@ namespace FoxTube
|
||||
RequestedTheme = ApplicationTheme.Dark;
|
||||
break;
|
||||
}
|
||||
|
||||
ApplicationLanguages.PrimaryLanguageOverride = SettingsStorage.Language;
|
||||
|
||||
CheckVersion();
|
||||
|
||||
InitializeComponent();
|
||||
Suspending += OnSuspending;
|
||||
UnhandledException += UnhandledError;
|
||||
|
||||
AppCenter.Start("45774462-9ea7-438a-96fc-03982666f39e", typeof(Analytics));
|
||||
AppCenter.SetCountryCode(SettingsStorage.Region);
|
||||
|
||||
sw.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the application is launched normally by the end user. Other entry points
|
||||
/// will be used such as when the application is launched to open a specific file.
|
||||
/// Comparing current version with last recorded version. If doesn't match, poping up changelog notification
|
||||
/// </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)
|
||||
{
|
||||
// Do not repeat app initialization when the Window already has content,
|
||||
@@ -97,7 +119,6 @@ namespace FoxTube
|
||||
return;
|
||||
|
||||
var backgroundRequest = await BackgroundExecutionManager.RequestAccessAsync();
|
||||
var saverRequest = PowerManager.EnergySaverStatus;
|
||||
if (backgroundRequest == BackgroundAccessStatus.DeniedBySystemPolicy || backgroundRequest == BackgroundAccessStatus.DeniedByUser)
|
||||
return;
|
||||
|
||||
@@ -171,9 +192,6 @@ namespace FoxTube
|
||||
Debug.WriteLine(e.Message);
|
||||
}
|
||||
break;
|
||||
case "download":
|
||||
await Launcher.LaunchFileAsync(await StorageFile.GetFileFromPathAsync(arguments[1]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,8 +217,11 @@ namespace FoxTube
|
||||
|
||||
Window.Current.Activate();
|
||||
|
||||
if (e is ToastNotificationActivatedEventArgs)
|
||||
switch (e.Kind)
|
||||
{
|
||||
case ActivationKind.Protocol:
|
||||
break;
|
||||
case ActivationKind.ToastNotification:
|
||||
string[] args = (e as ToastNotificationActivatedEventArgs).Argument.Split('|');
|
||||
switch (args[0])
|
||||
{
|
||||
@@ -220,36 +241,36 @@ namespace FoxTube
|
||||
Methods.MainPage.GoToDownloads();
|
||||
break;
|
||||
case "dcancel":
|
||||
DownloadAgent.Remove(args[1]);
|
||||
DownloadAgent.Cancel(args[1]);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Launch(string e = null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
var deferral = e.SuspendingOperation.GetDeferral();
|
||||
SettingsStorage.ExportSettings();
|
||||
|
||||
sw.Stop();
|
||||
SettingsStorage.Uptime += sw.Elapsed;
|
||||
|
||||
SettingsStorage.SaveData();
|
||||
DownloadAgent.QuitPrompt();
|
||||
deferral.Complete();
|
||||
Analytics.TrackEvent("Session terminated");
|
||||
}
|
||||
|
||||
private void UnhandledError(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
Analytics.TrackEvent("The app crashed", new Dictionary<string, string>()
|
||||
|
||||
|
Before Width: | Height: | Size: 562 B After Width: | Height: | Size: 374 B |
|
Before Width: | Height: | Size: 667 B After Width: | Height: | Size: 389 B |
|
Before Width: | Height: | Size: 672 B After Width: | Height: | Size: 414 B |
|
Before Width: | Height: | Size: 804 B After Width: | Height: | Size: 520 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 843 B |
@@ -1,5 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<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">
|
||||
<content>
|
||||
<en-US>### What's new:
|
||||
|
||||
@@ -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>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.Storage;
|
||||
using FoxTube.Classes;
|
||||
using Newtonsoft.Json;
|
||||
using YoutubeExplode.Models.MediaStreams;
|
||||
using Google.Apis.YouTube.v3.Data;
|
||||
using FoxTube.Controls;
|
||||
using FoxTube.Pages;
|
||||
|
||||
namespace FoxTube
|
||||
{
|
||||
@@ -13,6 +13,7 @@ namespace FoxTube
|
||||
{
|
||||
public static List<DownloadItem> items = new List<DownloadItem>();
|
||||
private static ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
|
||||
public static Downloads Page { get; set; }
|
||||
public static StorageFolder Downloads { get; set; }
|
||||
|
||||
public static async void Initialize()
|
||||
@@ -31,45 +32,34 @@ namespace FoxTube
|
||||
items.Insert(0, new DownloadItem(info, meta, qualty));
|
||||
}
|
||||
|
||||
public static void CancelItem(string id)
|
||||
public static void Remove(DownloadItem item)
|
||||
{
|
||||
DownloadItem item = items.Find(x => x.Container.Id == id);
|
||||
if (item == null || !item.InProgress)
|
||||
return;
|
||||
|
||||
item.CancelPrompt();
|
||||
try { Page.Remove(item); }
|
||||
catch { }
|
||||
items.Remove(item);
|
||||
}
|
||||
|
||||
public static void Remove(string id)
|
||||
public static void Cancel(string id)
|
||||
{
|
||||
DownloadItem item = items.Find(x => x.Container.Id == id);
|
||||
if (item == null)
|
||||
return;
|
||||
|
||||
if (item.InProgress)
|
||||
DownloadItem item = items.Find(i => i.Container.Id == id);
|
||||
if (item != null)
|
||||
item.Cancel();
|
||||
else
|
||||
items.Remove(item);
|
||||
}
|
||||
|
||||
public static void QuitPrompt()
|
||||
{
|
||||
foreach (DownloadItem i in items.FindAll(i => i.InProgress))
|
||||
foreach (DownloadItem i in items.FindAll(i => !i.Container.IsDownloaded))
|
||||
{
|
||||
i.Cancel();
|
||||
items.Remove(i);
|
||||
}
|
||||
|
||||
List<DownloadItemContainer> containers = new List<DownloadItemContainer>();
|
||||
items.ForEach(i => containers.Add(i.Container));
|
||||
|
||||
string data = JsonConvert.SerializeObject(containers);
|
||||
|
||||
try
|
||||
{
|
||||
settings.Values["downloads"] = data;
|
||||
}
|
||||
catch
|
||||
{
|
||||
settings.Values.Add("downloads", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,16 @@ namespace FoxTube.Classes
|
||||
{
|
||||
public InboxItemType Type { get; set; } = InboxItemType.Default;
|
||||
public DateTime TimeStamp { get; set; }
|
||||
public string TimeStampString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Type == InboxItemType.PatchNote)
|
||||
return TimeStamp.ToShortDateString();
|
||||
else
|
||||
return TimeStamp.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public string Subject { get; set; }
|
||||
public string Content { get; set; }
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
using FoxTube.Pages;
|
||||
using Google.Apis.YouTube.v3;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Xml;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.ApplicationModel.Resources;
|
||||
using Windows.ApplicationModel.Resources.Core;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.System;
|
||||
@@ -19,16 +22,20 @@ using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Documents;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using YoutubeExplode;
|
||||
using YoutubeExplode.Models.MediaStreams;
|
||||
|
||||
namespace FoxTube
|
||||
{
|
||||
public interface NavigationPage
|
||||
{
|
||||
object Parameter { get; set; }
|
||||
}
|
||||
public static class Methods
|
||||
{
|
||||
private static ResourceLoader resources = ResourceLoader.GetForCurrentView("Methods");
|
||||
public static CommentsPage CommentsPage { get; set; }
|
||||
|
||||
public static bool NeedToResponse { get; set; } = false;
|
||||
public static MainPage MainPage
|
||||
{
|
||||
get { return (Window.Current.Content as Frame).Content as MainPage; }
|
||||
@@ -150,41 +157,25 @@ namespace FoxTube
|
||||
public static void FormatText(ref TextBlock block, string text)
|
||||
{
|
||||
block.Inlines.Clear();
|
||||
Regex filter = new Regex(@"\b((?:https?://|www\.)\S+)|(\S+@\S+)\b", RegexOptions.IgnoreCase);
|
||||
Regex link = new Regex(@"\b(?:https?://|www\.)\S+\b", RegexOptions.IgnoreCase);
|
||||
Regex mail = new Regex(@"\b\S+@\S+\b", RegexOptions.IgnoreCase);
|
||||
|
||||
Regex regx = new Regex(@"(http(s)?://[\S]+|www.[\S]+|[\S]+@[\S]+)", RegexOptions.IgnoreCase);
|
||||
Regex isWWW = new Regex(@"(http[s]?://[\S]+|www.[\S]+)");
|
||||
Regex isEmail = new Regex(@"[\S]+@[\S]+");
|
||||
foreach (string item in regx.Split(text))
|
||||
foreach (string item in filter.Split(text))
|
||||
{
|
||||
if (isWWW.IsMatch(item))
|
||||
if (link.IsMatch(item))
|
||||
{
|
||||
try
|
||||
{
|
||||
Hyperlink link = new Hyperlink();
|
||||
link.Click += (s, arg) => { ProcessLink(item); };
|
||||
link.Inlines.Add(new Run { Text = item });
|
||||
block.Inlines.Add(link);
|
||||
Hyperlink hl = new Hyperlink();
|
||||
hl.Click += (s, arg) => ProcessLink(item);
|
||||
hl.Inlines.Add(new Run { Text = item });
|
||||
block.Inlines.Add(hl);
|
||||
}
|
||||
catch
|
||||
else if (mail.IsMatch(item))
|
||||
{
|
||||
block.Inlines.Add(new Run { Text = item });
|
||||
Hyperlink hl = new Hyperlink { NavigateUri = $"mailto:{item}".ToUri() };
|
||||
hl.Inlines.Add(new Run { Text = item });
|
||||
block.Inlines.Add(hl);
|
||||
}
|
||||
}
|
||||
else if (isEmail.IsMatch(item))
|
||||
{
|
||||
try
|
||||
{
|
||||
Hyperlink link = new Hyperlink { NavigateUri = new Uri($"mailto:{item}"), Foreground = new SolidColorBrush(Colors.Red) };
|
||||
link.Inlines.Add(new Run { Text = item });
|
||||
block.Inlines.Add(link);
|
||||
}
|
||||
catch
|
||||
{
|
||||
block.Inlines.Add(new Run { Text = item });
|
||||
}
|
||||
}
|
||||
else if (item == "s")
|
||||
continue;
|
||||
else
|
||||
block.Inlines.Add(new Run { Text = item });
|
||||
}
|
||||
@@ -223,65 +214,48 @@ namespace FoxTube
|
||||
|
||||
public async static void ProcessLink(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.WriteLine($"Processing link: {url}");
|
||||
if (url.Contains("youtube.com/") || url.Contains("youtu.be/"))
|
||||
{
|
||||
Debug.WriteLine("This is an internal youtube link");
|
||||
url = url.Replace("https://", "").Replace("http://", "").Replace("wwww.", "").Replace("//", "");
|
||||
Debug.WriteLine($"Prepared link: {url}");
|
||||
string output;
|
||||
string type;
|
||||
|
||||
if (url.Contains("/playlist"))
|
||||
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"));
|
||||
type = "channel";
|
||||
goto LinkFound;
|
||||
}
|
||||
else if (url.Contains("youtu.be/"))
|
||||
else if (YoutubeClient.TryParsePlaylistId(url, out output))
|
||||
{
|
||||
Debug.WriteLine($"This is obfuscated video link. Video ID: {url.Split('/')[1]}");
|
||||
MainPage.GoToVideo(url.Split('/')[1]);
|
||||
type = "playlist";
|
||||
goto LinkFound;
|
||||
}
|
||||
else if (url.Contains("/watch"))
|
||||
else if (YoutubeClient.TryParseUsername(url, out output))
|
||||
{
|
||||
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"));
|
||||
type = "user";
|
||||
goto LinkFound;
|
||||
}
|
||||
else if (url.Contains("/v/"))
|
||||
else if (YoutubeClient.TryParseVideoId(url, out output))
|
||||
{
|
||||
Debug.WriteLine($"This is video link. ID: {url.Split('/')[2].Split('?')[0]}");
|
||||
MainPage.GoToVideo(url.Split('/')[2].Split('?')[0]);
|
||||
type = "video";
|
||||
goto LinkFound;
|
||||
}
|
||||
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));
|
||||
return;
|
||||
|
||||
LinkFound:
|
||||
switch (type)
|
||||
{
|
||||
case "channel":
|
||||
MainPage.GoToChannel(output);
|
||||
break;
|
||||
case "video":
|
||||
MainPage.GoToVideo(output);
|
||||
break;
|
||||
case "playlist":
|
||||
MainPage.GoToPlaylist(output);
|
||||
break;
|
||||
case "user":
|
||||
MainPage.GoToChannel(await new YoutubeClient().GetChannelIdAsync(output));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,5 +282,31 @@ namespace FoxTube
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,5 +129,21 @@ namespace FoxTube
|
||||
Channel = channelId;
|
||||
Filter = filters;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $@"Term: {Term}
|
||||
Channel id: {Channel}
|
||||
Filters:
|
||||
Order: {Filter.Order}
|
||||
Type: {Filter.Type}
|
||||
Date: {Filter.Date}
|
||||
Duration: {Filter.Duration}
|
||||
HD: {Filter.HD}
|
||||
3D: {Filter.Is3D}
|
||||
Event type: {Filter.LiveEvent}
|
||||
CC: {Filter.Captions}
|
||||
License: {Filter.CreativeCommons}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ using Google.Apis.YouTube.v3.Data;
|
||||
using Newtonsoft.Json;
|
||||
using Windows.Storage;
|
||||
using Windows.Services.Store;
|
||||
using System.Net.Http;
|
||||
using Google.Apis.Oauth2.v2.Data;
|
||||
using Google.Apis.Oauth2.v2;
|
||||
using static Google.Apis.Auth.OAuth2.UwpCodeReceiver;
|
||||
|
||||
namespace FoxTube
|
||||
{
|
||||
@@ -37,18 +41,22 @@ namespace FoxTube
|
||||
ApplicationName = "FoxTube"
|
||||
};
|
||||
public static YouTubeService Service => IsAuthorized ? new YouTubeService(Initializer) : NoAuthService;
|
||||
public static HttpClient HttpClient { get; } = new HttpClient();
|
||||
public static string AppId => true ? "d25517cb-12d4-4699-8bdc-52040c712cab" : "9ncqqxjtdlfh";
|
||||
public static string AdUnitId => true ? "test" : "1100037769";
|
||||
public static bool AdsDisabled { get; private set; } = true;
|
||||
|
||||
//User info
|
||||
public static bool IsAuthorized => Credential != null;
|
||||
public static UserCredential Credential { get; set; }
|
||||
private static UserCredential Credential { get; set; }
|
||||
|
||||
public static string AccountId => UserChannel?.Id;
|
||||
public static Channel UserChannel { get; private set; }
|
||||
public static Userinfoplus UserInfo { get; private set; }
|
||||
|
||||
public static List<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
|
||||
|
||||
#region Methods
|
||||
@@ -69,7 +77,7 @@ namespace FoxTube
|
||||
try { await Service.Subscriptions.Delete(s.Id).ExecuteAsync(); }
|
||||
catch { return true; }
|
||||
|
||||
SubscriptionsChanged?.Invoke(null, "remove", Subscriptions.IndexOf(s));
|
||||
SubscriptionsChanged?.Invoke(null, "remove", s);
|
||||
Subscriptions.Remove(s);
|
||||
return false;
|
||||
}
|
||||
@@ -96,35 +104,67 @@ namespace FoxTube
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up **SecretsVault**
|
||||
/// </summary>
|
||||
public static void Initialize()
|
||||
{
|
||||
CheckAuthorization();
|
||||
// TODO: Reactivate addons initialization
|
||||
//CheckAddons();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prompts to add an Youtube account and retrieves its info when successful
|
||||
/// </summary>
|
||||
/// <param name="retrieveSubs">Loads user's subscriptions if true</param>
|
||||
public static async void Authorize(bool retrieveSubs = true)
|
||||
{
|
||||
#region Retrieving user's credential
|
||||
try
|
||||
{
|
||||
#region Retrieving user's credential
|
||||
Credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
|
||||
Secrets,
|
||||
new[]
|
||||
{
|
||||
Google.Apis.Oauth2.v2.Oauth2Service.Scope.UserinfoProfile,
|
||||
YouTubeService.Scope.YoutubeForceSsl
|
||||
Oauth2Service.Scope.UserinfoProfile,
|
||||
Oauth2Service.Scope.UserinfoEmail,
|
||||
YouTubeService.Scope.YoutubeForceSsl,
|
||||
YouTubeService.Scope.Youtube,
|
||||
YouTubeService.Scope.YoutubeUpload,
|
||||
YouTubeService.Scope.YoutubeReadonly,
|
||||
YouTubeService.Scope.Youtubepartner
|
||||
},
|
||||
"user",
|
||||
CancellationToken.None);
|
||||
}
|
||||
catch (AuthenticateException e)
|
||||
{
|
||||
if (e.Message.Contains("UserCancel"))
|
||||
return;
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (Credential == null || !retrieveSubs)
|
||||
goto InvokeEvent;
|
||||
return;
|
||||
|
||||
SettingsStorage.HasAccount = true;
|
||||
|
||||
HttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Credential.Token.AccessToken);
|
||||
#endregion
|
||||
|
||||
try
|
||||
{
|
||||
#region Retrieving user's data
|
||||
var request = Service.Channels.List("snippet,contentDetails");
|
||||
request.Mine = true;
|
||||
UserChannel = (await request.ExecuteAsync()).Items[0];
|
||||
UserInfo = await new Oauth2Service(Initializer).Userinfo.Get().ExecuteAsync();
|
||||
|
||||
try
|
||||
{
|
||||
WatchLater = await Methods.GetLater();
|
||||
History = await Methods.GetHistory();
|
||||
}
|
||||
catch { }
|
||||
|
||||
SubscriptionsResource.ListRequest subRequest = Service.Subscriptions.List("snippet");
|
||||
subRequest.Mine = true;
|
||||
@@ -143,13 +183,16 @@ namespace FoxTube
|
||||
nextToken = subResponse.NextPageToken;
|
||||
|
||||
} while (!string.IsNullOrWhiteSpace(nextToken));
|
||||
|
||||
var request = Service.Channels.List("snippet,contentDetails");
|
||||
request.Mine = true;
|
||||
UserChannel = (await request.ExecuteAsync()).Items[0];
|
||||
#endregion
|
||||
|
||||
//Saving user's subscriptions for background task
|
||||
SaveSubscriptions();
|
||||
|
||||
InvokeEvent:
|
||||
AuthorizationStateChanged?.Invoke(args: IsAuthorized);
|
||||
AuthorizationStateChanged?.Invoke(args: true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -160,13 +203,11 @@ namespace FoxTube
|
||||
/// <summary>
|
||||
/// Saves user's subscriptions keypairs (channel ID: avatar URL) into "background.json" file for concurrent background processing
|
||||
/// </summary>
|
||||
public static async void SaveSubscriptions()
|
||||
public static void SaveSubscriptions()
|
||||
{
|
||||
Dictionary<string, string> subs = new Dictionary<string, string>();
|
||||
Subscriptions.ForEach(x => subs.Add(x.Snippet.ResourceId.ChannelId, x.Snippet.Thumbnails.Medium.Url));
|
||||
await FileIO.WriteTextAsync(
|
||||
await ApplicationData.Current.RoamingFolder.CreateFileAsync("background.json", CreationCollisionOption.ReplaceExisting),
|
||||
JsonConvert.SerializeObject(subs));
|
||||
Subscriptions.ForEach(x => subs.Add(x.Snippet.ResourceId.ChannelId, x.Snippet.Thumbnails.Default__.Url));
|
||||
ApplicationData.Current.RoamingSettings.Values["subscriptions"] = JsonConvert.SerializeObject(subs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -179,6 +220,8 @@ namespace FoxTube
|
||||
Credential = null;
|
||||
AuthorizationStateChanged?.Invoke(args: false);
|
||||
SettingsStorage.HasAccount = false;
|
||||
|
||||
ApplicationData.Current.RoamingSettings.Values["subscriptions"] = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +233,11 @@ namespace FoxTube
|
||||
{
|
||||
if (SettingsStorage.HasAccount)
|
||||
Authorize(retrieveSubs);
|
||||
else AuthorizationStateChanged.Invoke(args: false);
|
||||
else
|
||||
{
|
||||
AuthorizationStateChanged.Invoke(args: false);
|
||||
ApplicationData.Current.RoamingSettings.Values["subscriptions"] = "";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -201,9 +248,11 @@ namespace FoxTube
|
||||
try
|
||||
{
|
||||
StoreContext store = StoreContext.GetDefault();
|
||||
StoreProductQueryResult requset = await store.GetAssociatedStoreProductsAsync(new[] { "Consumable", "Durable", "UnmanagedConsumable" });
|
||||
StoreProductQueryResult requset = await store.GetAssociatedStoreProductsAsync(new[] { "Durable" });
|
||||
Dictionary<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;
|
||||
Purchased?.Invoke(args:false);
|
||||
@@ -215,7 +264,7 @@ namespace FoxTube
|
||||
public static async void GetAdblock()
|
||||
{
|
||||
StoreContext store = StoreContext.GetDefault();
|
||||
StorePurchaseResult request = await store.RequestPurchaseAsync("foxtube-adsremove");
|
||||
StorePurchaseResult request = await store.RequestPurchaseAsync("9NP1QK556625");
|
||||
|
||||
switch (request.Status)
|
||||
{
|
||||
|
||||
@@ -7,128 +7,152 @@ using Windows.Storage;
|
||||
|
||||
namespace FoxTube
|
||||
{
|
||||
public enum MatureState { Blocked, Allowed, AllowedOnce }
|
||||
public enum MatureState { Blocked, Allowed }
|
||||
|
||||
public class SettingsContainer
|
||||
{
|
||||
public string videoQuality = "remember";
|
||||
public string rememberedQuality = "1080p";
|
||||
|
||||
public bool videoNotifications = true;
|
||||
public bool devNotifications = true;
|
||||
|
||||
public bool checkConnection = true;
|
||||
public bool autoplay = true;
|
||||
public double volume = 100;
|
||||
|
||||
public string language = (new[] { "ua", "ru", "by", "kz", "kg", "md", "lv", "ee" }).Contains(CultureInfo.InstalledUICulture.TwoLetterISOLanguageName) ? "ru-RU" : "en-US";
|
||||
public string relevanceLanguage = (new[] { "ua", "ru", "by", "kz", "kg", "md", "lv", "ee" }).Contains(CultureInfo.InstalledUICulture.TwoLetterISOLanguageName) ? "ru" : "en";
|
||||
public string region = (new[] { "ua", "ru", "by", "kz", "kg", "md", "lv", "ee" }).Contains(CultureInfo.InstalledUICulture.TwoLetterISOLanguageName) ? "ru" : "us";
|
||||
public int safeSearch = 0;
|
||||
|
||||
public bool hasAccount = false;
|
||||
public int theme = 2;
|
||||
|
||||
public TimeSpan uptime = TimeSpan.FromSeconds(0);
|
||||
public bool promptReview = true;
|
||||
public bool promptFeedback = true;
|
||||
}
|
||||
|
||||
public static class SettingsStorage
|
||||
{
|
||||
public static string VideoQuality
|
||||
{
|
||||
get { return (string)settings[0]; }
|
||||
get { return Container.videoQuality; }
|
||||
set
|
||||
{
|
||||
settings[0] = value;
|
||||
Container.videoQuality = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
public static string RememberedQuality
|
||||
{
|
||||
get { return (string)settings[1]; }
|
||||
get { return Container.rememberedQuality; }
|
||||
set
|
||||
{
|
||||
settings[1] = value;
|
||||
Container.rememberedQuality = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool VideoNotifications
|
||||
{
|
||||
get { return (bool)settings[2]; }
|
||||
get { return Container.videoNotifications; }
|
||||
set
|
||||
{
|
||||
settings[2] = value;
|
||||
Container.videoNotifications = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
public static bool DevNotifications
|
||||
{
|
||||
get { return (bool)settings[3]; }
|
||||
get { return Container.devNotifications; }
|
||||
set
|
||||
{
|
||||
settings[3] = value;
|
||||
Container.devNotifications = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CheckConnection
|
||||
{
|
||||
get { return (bool)settings[4]; }
|
||||
get { return Container.checkConnection; }
|
||||
set
|
||||
{
|
||||
settings[4] = value;
|
||||
Container.checkConnection = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
public static bool Autoplay
|
||||
{
|
||||
get { return (bool)settings[5]; }
|
||||
get { return Container.autoplay; }
|
||||
set
|
||||
{
|
||||
settings[5] = value;
|
||||
Container.autoplay = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
public static int Volume
|
||||
public static double Volume
|
||||
{
|
||||
get { return Convert.ToInt32(settings[6]); }
|
||||
get { return Container.volume; }
|
||||
set
|
||||
{
|
||||
settings[6] = value;
|
||||
Container.volume = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
|
||||
public static string Language
|
||||
{
|
||||
get { return (string)settings[7]; }
|
||||
get { return Container.language; }
|
||||
set
|
||||
{
|
||||
settings[7] = value;
|
||||
Container.language = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
public static string RelevanceLanguage
|
||||
{
|
||||
get { return (string)settings[8]; }
|
||||
get { return Container.relevanceLanguage; }
|
||||
set
|
||||
{
|
||||
settings[8] = value;
|
||||
Container.relevanceLanguage = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
public static string Region
|
||||
{
|
||||
get { return (string)settings[9]; }
|
||||
get { return Container.region; }
|
||||
set
|
||||
{
|
||||
settings[9] = value;
|
||||
Container.region = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
public static int SafeSearch
|
||||
{
|
||||
get { return Convert.ToInt32(settings[10]); }
|
||||
get { return Container.safeSearch; }
|
||||
set
|
||||
{
|
||||
settings[10] = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
|
||||
public static int Theme
|
||||
{
|
||||
get { return Convert.ToInt32(settings[11]); }
|
||||
set
|
||||
{
|
||||
settings[11] = value;
|
||||
Container.safeSearch = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
public static bool HasAccount
|
||||
{
|
||||
get { return (bool)settings[12]; }
|
||||
get { return Container.hasAccount; }
|
||||
set
|
||||
{
|
||||
settings[12] = value;
|
||||
Container.hasAccount = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
public static int Theme
|
||||
{
|
||||
get { return Container.theme; }
|
||||
set
|
||||
{
|
||||
Container.theme = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
@@ -167,95 +191,54 @@ namespace FoxTube
|
||||
}
|
||||
}
|
||||
|
||||
public static TimeSpan Uptime
|
||||
{
|
||||
get { return Container.uptime; }
|
||||
set
|
||||
{
|
||||
Container.uptime = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
public static bool PromptReview
|
||||
{
|
||||
get { return Container.promptReview; }
|
||||
set
|
||||
{
|
||||
Container.promptReview = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
public static bool PromptFeedback
|
||||
{
|
||||
get { return Container.promptFeedback; }
|
||||
set
|
||||
{
|
||||
Container.promptFeedback = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
|
||||
//Settings storage
|
||||
private static readonly ApplicationDataContainer storage = ApplicationData.Current.RoamingSettings;
|
||||
|
||||
//Predefined preferences
|
||||
private static object[] settings = new object[]
|
||||
{
|
||||
"remember",
|
||||
"1080p",
|
||||
|
||||
true,
|
||||
true,
|
||||
|
||||
true,
|
||||
true,
|
||||
100,
|
||||
|
||||
(new[] { "ua", "ru", "by", "kz", "kg", "md", "lv", "ee" }).Contains(CultureInfo.InstalledUICulture.TwoLetterISOLanguageName) ? "ru-RU" : "en-US",
|
||||
(new[] { "ua", "ru", "by", "kz", "kg", "md", "lv", "ee" }).Contains(CultureInfo.InstalledUICulture.TwoLetterISOLanguageName) ? "ru" : "en",
|
||||
(new[] { "ua", "ru", "by", "kz", "kg", "md", "lv", "ee" }).Contains(CultureInfo.InstalledUICulture.TwoLetterISOLanguageName) ? "ru" : "US",
|
||||
0,
|
||||
|
||||
2,
|
||||
false
|
||||
};
|
||||
private static SettingsContainer Container;
|
||||
|
||||
public static void LoadData()
|
||||
{
|
||||
/*if(true && (storage.Values["forceUpdate"] == null || storage.Values["forceUpdate"].ToString() != Version))
|
||||
{
|
||||
SaveData();
|
||||
storage.Values["forceUpdate"] = Version;
|
||||
return;
|
||||
}*/
|
||||
|
||||
try
|
||||
{
|
||||
object[] saved = JsonConvert.DeserializeObject<object[]>(storage.Values["settings"] as string);
|
||||
if (settings.Length > saved.Length)
|
||||
Container = JsonConvert.DeserializeObject<SettingsContainer>(storage.Values["settings"] as string);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (saved[0] is string)
|
||||
settings[0] = saved;
|
||||
if (saved[1] is string)
|
||||
settings[1] = saved;
|
||||
if (saved[2] is bool)
|
||||
settings[2] = saved;
|
||||
if (saved[3] is bool)
|
||||
settings[3] = saved;
|
||||
if (saved[4] is bool)
|
||||
settings[4] = saved;
|
||||
if (saved[5] is bool)
|
||||
settings[5] = saved;
|
||||
if (saved[6] is int)
|
||||
settings[6] = saved;
|
||||
if (saved[7] is string)
|
||||
settings[7] = saved;
|
||||
if (saved[8] is string)
|
||||
settings[8] = saved;
|
||||
if (saved[9] is string)
|
||||
settings[9] = saved;
|
||||
if (saved[10] is int)
|
||||
settings[10] = saved;
|
||||
if (saved[11] is int)
|
||||
settings[11] = saved;
|
||||
if (saved[12] is bool)
|
||||
settings[12] = saved;
|
||||
|
||||
Container = new SettingsContainer();
|
||||
SaveData();
|
||||
}
|
||||
else settings = saved;
|
||||
}
|
||||
catch (ArgumentNullException) { }
|
||||
}
|
||||
|
||||
public static void SaveData()
|
||||
{
|
||||
storage.Values["settings"] = JsonConvert.SerializeObject(settings);
|
||||
ExportSettings();
|
||||
}
|
||||
|
||||
public static async void ExportSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
bool[] notificationsSettings = new[] { VideoNotifications, DevNotifications };
|
||||
await FileIO.WriteTextAsync(
|
||||
await ApplicationData.Current.RoamingFolder.CreateFileAsync("notifications.json", CreationCollisionOption.ReplaceExisting),
|
||||
JsonConvert.SerializeObject(notificationsSettings));
|
||||
}
|
||||
catch { }
|
||||
storage.Values["settings"] = JsonConvert.SerializeObject(Container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,38 +7,38 @@
|
||||
mc:Ignorable="d"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
SizeChanged="UserControl_SizeChanged"
|
||||
d:DesignHeight="290"
|
||||
d:DesignWidth="384">
|
||||
d:DesignWidth="384"
|
||||
Visibility="Collapsed">
|
||||
|
||||
<Button Padding="0" Background="Transparent" Name="btn">
|
||||
<Grid Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="75"/>
|
||||
</Grid.RowDefinitions>
|
||||
<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">
|
||||
<TextBlock Name="info" Text="SPONSORED CONTENT" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Gray" FontSize="12"/>
|
||||
</StackPanel>
|
||||
<Grid Grid.Row="1">
|
||||
<Button Padding="0" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="20"/>
|
||||
<RowDefinition Height="55"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Name="contentGrid">
|
||||
|
||||
<Image Source="/Assets/videoPlaceholder.png" Opacity=".0001" 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">
|
||||
<TextBlock Name="info" Text="SPONSORED CONTENT" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Gray" FontSize="12"/>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="1" 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"/>
|
||||
<PersonPicture FontFamily="Segoe MDL2 Assets" Initials="" 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>
|
||||
<TextBlock Grid.Row="1" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" Margin="5" MaxLines="2" TextTrimming="CharacterEllipsis"/>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="2" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" Margin="5" MaxLines="2" TextTrimming="CharacterEllipsis"/>
|
||||
</Grid>
|
||||
</Button>
|
||||
</UserControl>
|
||||
|
||||
@@ -31,9 +31,7 @@ namespace FoxTube.Controls.Adverts
|
||||
{
|
||||
title.Text = advert.Title;
|
||||
image.Source = new BitmapImage(advert.MainImages.First().Url.ToUri());
|
||||
if (advert.AdIcon == null)
|
||||
contentGrid.ColumnDefinitions[0].Width = new GridLength(0);
|
||||
else
|
||||
|
||||
icon.ProfilePicture = advert.AdIcon.Source;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(advert.SponsoredBy))
|
||||
@@ -53,10 +51,5 @@ namespace FoxTube.Controls.Adverts
|
||||
|
||||
Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
Height = e.NewSize.Width * 0.75;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,30 +4,40 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
|
||||
mc:Ignorable="d"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
SizeChanged="UserControl_SizeChanged"
|
||||
d:DesignHeight="290"
|
||||
d:DesignWidth="384">
|
||||
d:DesignWidth="384"
|
||||
Opacity="0">
|
||||
|
||||
<Button Padding="0" Background="Transparent" Click="Button_Click" VerticalContentAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
|
||||
<Windows10version1809:UserControl.OpacityTransition>
|
||||
<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>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="50"/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</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">
|
||||
<TextBlock Text=" " 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"/>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="50"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
@@ -37,20 +47,24 @@
|
||||
<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"/>
|
||||
|
||||
<StackPanel Grid.Column="2" Margin="5">
|
||||
<TextBlock Name="subs" Text="[Subscribers counter]" Foreground="Gray"/>
|
||||
<TextBlock Name="uploads" Text="[Uploads counter]" Foreground="Gray"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<TextBlock Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Height="50" Margin="10" TextAlignment="Center" Padding="0,16,0,0" Foreground="Gray">
|
||||
|
||||
<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="1" VerticalAlignment="Bottom" Margin="10" Name="subscriptionPane" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
|
||||
<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"/>
|
||||
|
||||
<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="" Foreground="White" Background="Red" HorizontalAlignment="Right"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Button>
|
||||
<UserControl.ContextFlyout>
|
||||
<MenuFlyout>
|
||||
|
||||
@@ -22,19 +22,19 @@ namespace FoxTube.Controls
|
||||
string channelId;
|
||||
Channel item;
|
||||
|
||||
public ChannelCard(string id, string live = "null")
|
||||
public ChannelCard(string id, string live = null)
|
||||
{
|
||||
InitializeComponent();
|
||||
Initialize(id, live);
|
||||
}
|
||||
|
||||
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
Height = e.NewSize.Width * 0.75;
|
||||
}
|
||||
|
||||
public async void Initialize(string id, string live)
|
||||
{
|
||||
try
|
||||
{
|
||||
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();
|
||||
@@ -43,6 +43,7 @@ namespace FoxTube.Controls
|
||||
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")}";
|
||||
@@ -52,30 +53,39 @@ namespace FoxTube.Controls
|
||||
|
||||
if (SecretsVault.IsAuthorized)
|
||||
{
|
||||
foreach(Subscription s in SecretsVault.Subscriptions)
|
||||
{
|
||||
if(s.Snippet.ResourceId.ChannelId == id)
|
||||
if (SecretsVault.Subscriptions.Exists(i => i.Snippet.ResourceId.ChannelId == id))
|
||||
{
|
||||
subscribe.Background = new SolidColorBrush(Colors.Transparent);
|
||||
subscribe.Foreground = new SolidColorBrush(Colors.Gray);
|
||||
subscribe.Content = resources.GetString("/Cards/unsubscribe");
|
||||
}
|
||||
}
|
||||
subscriptionPane.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
try { avatar.ProfilePicture = new BitmapImage(new Uri(item.Snippet.Thumbnails.Medium.Url)); }
|
||||
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"))
|
||||
throw new Exception("Default channel cover detected");
|
||||
cover.Source = new BitmapImage((item.BrandingSettings.Image.BannerTvHighImageUrl ?? item.BrandingSettings.Image.BannerTvImageUrl).ToUri());
|
||||
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 }
|
||||
});
|
||||
}
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e)
|
||||
Opacity = 1;
|
||||
}
|
||||
|
||||
public void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Methods.MainPage.GoToChannel(channelId);
|
||||
}
|
||||
@@ -104,13 +114,18 @@ namespace FoxTube.Controls
|
||||
private void GetLink_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DataPackage data = new DataPackage();
|
||||
data.SetText(string.IsNullOrWhiteSpace(item.Snippet.CustomUrl) ? $"https://www.youtube.com/channel/{item.Id}" : $"https://www.youtube.com/user/{item.Snippet.CustomUrl}");
|
||||
data.SetText($"https://www.youtube.com/channel/{item.Id}");
|
||||
Clipboard.SetContent(data);
|
||||
}
|
||||
|
||||
private async void InBrowser_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await Launcher.LaunchUriAsync((string.IsNullOrWhiteSpace(item.Snippet.CustomUrl) ? $"https://www.youtube.com/channel/{item.Id}" : $"https://www.youtube.com/user/{item.Snippet.CustomUrl}").ToUri());
|
||||
await Launcher.LaunchUriAsync($"https://www.youtube.com/channel/{item.Id}".ToUri());
|
||||
}
|
||||
|
||||
private void Cover_ImageOpened(object sender, RoutedEventArgs e)
|
||||
{
|
||||
cover.Opacity = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
Content="" FontSize="30"/>
|
||||
</Grid>
|
||||
|
||||
<ScrollViewer Grid.Row="1">
|
||||
<ListView Name="list" SelectionMode="None">
|
||||
<ListView Name="list" Grid.Row="1" SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border BorderBrush="Red" BorderThickness="{Binding Path=BorderThickness}" CornerRadius="5" HorizontalAlignment="Stretch" Background="{Binding Path=Background}" Margin="0,2">
|
||||
@@ -36,7 +35,11 @@
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Margin="5,0">
|
||||
<PersonPicture Height="20" ProfilePicture="{Binding Path=Avatar}"/>
|
||||
<PersonPicture Height="20">
|
||||
<PersonPicture.ProfilePicture>
|
||||
<BitmapImage UriSource="{Binding Path=Avatar}" DecodePixelHeight="20" DecodePixelWidth="20"/>
|
||||
</PersonPicture.ProfilePicture>
|
||||
</PersonPicture>
|
||||
<FontIcon Glyph="" Margin="2,0" Visibility="{Binding Path=IsVerified}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="/Chat/verified"/>
|
||||
@@ -71,6 +74,5 @@
|
||||
<TextBlock x:Uid="/Chat/welcome" Text="Welcome to the chat room" Foreground="Gray"/>
|
||||
</ListViewItem>
|
||||
</ListView>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -5,6 +5,10 @@ using Windows.UI.Xaml.Media;
|
||||
using Google.Apis.YouTube.v3.Data;
|
||||
using Google.Apis.YouTube.v3;
|
||||
using Windows.UI;
|
||||
using Microsoft.AppCenter.Analytics;
|
||||
using System.Collections.Generic;
|
||||
using Windows.UI.Popups;
|
||||
using Windows.ApplicationModel.Resources;
|
||||
|
||||
namespace FoxTube.Controls
|
||||
{
|
||||
@@ -66,6 +70,10 @@ namespace FoxTube.Controls
|
||||
{
|
||||
string chatId;
|
||||
DateTime lastInsert;
|
||||
|
||||
LiveChatMessagesResource.ListRequest request;
|
||||
LiveChatMessageListResponse response;
|
||||
|
||||
DispatcherTimer timer = new DispatcherTimer()
|
||||
{
|
||||
Interval = TimeSpan.FromSeconds(1)
|
||||
@@ -76,19 +84,20 @@ namespace FoxTube.Controls
|
||||
InitializeComponent();
|
||||
if (!SecretsVault.IsAuthorized)
|
||||
inputField.Visibility = Visibility.Collapsed;
|
||||
|
||||
chatId = id;
|
||||
|
||||
request = SecretsVault.Service.LiveChatMessages.List(chatId, "snippet,authorDetails");
|
||||
|
||||
timer.Tick += Update;
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
public async void Update(object sender, object e)
|
||||
{
|
||||
LiveChatMessagesResource.ListRequest request = SecretsVault.Service.LiveChatMessages.List(chatId, "snippet,authorDetails");
|
||||
LiveChatMessageListResponse response = await request.ExecuteAsync();
|
||||
foreach (LiveChatMessage i in response.Items)
|
||||
if(i.Snippet.PublishedAt >= lastInsert)
|
||||
response = await request.ExecuteAsync();
|
||||
foreach (LiveChatMessage i in response.Items.FindAll(i => i.Snippet.PublishedAt >= lastInsert))
|
||||
list.Items.Insert(0, new ChatMessage(i));
|
||||
|
||||
lastInsert = DateTime.Now;
|
||||
timer.Interval = TimeSpan.FromMilliseconds(response.PollingIntervalMillis.Value);
|
||||
timer.Start();
|
||||
@@ -99,16 +108,16 @@ namespace FoxTube.Controls
|
||||
Methods.MainPage.GoToChannel(((HyperlinkButton)sender).Tag as string);
|
||||
}
|
||||
|
||||
private async void send_Click(object sender, RoutedEventArgs e)
|
||||
private async void send_Click(object sender, RoutedEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
newMessage.IsEnabled = false;
|
||||
send.IsEnabled = false;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newMessage.Text))
|
||||
return;
|
||||
|
||||
newMessage.IsEnabled = false;
|
||||
send.IsEnabled = false;
|
||||
|
||||
LiveChatMessage message = new LiveChatMessage()
|
||||
{
|
||||
Snippet = new LiveChatMessageSnippet()
|
||||
@@ -131,11 +140,18 @@ namespace FoxTube.Controls
|
||||
list.Items.Add(new ChatMessage(response));
|
||||
}
|
||||
}
|
||||
finally
|
||||
catch(Exception e)
|
||||
{
|
||||
await new MessageDialog(ResourceLoader.GetForCurrentView("Chat").GetString("/Chat/failed")).ShowAsync();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,53 +7,35 @@
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="400">
|
||||
|
||||
<Grid Background="{ThemeResource AppBarBackgroundThemeBrush}" Margin="2" Name="grid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="0"/>
|
||||
<RowDefinition Height="0"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid>
|
||||
<Grid Margin="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="60"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<PersonPicture Tapped="avatar_Tapped" Name="avatar" Height="50" Margin="5" VerticalAlignment="Top"/>
|
||||
<Grid Grid.Column="1" Margin="5">
|
||||
<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"/>
|
||||
<StackPanel Grid.Column="1" Margin="5">
|
||||
<TextBlock Text="Channel name" Name="author" TextWrapping="WrapWholeWords" Foreground="Gray" FontSize="13"/>
|
||||
<Border Visibility="Collapsed" Name="specialAuthor" HorizontalAlignment="Left" Background="Red" CornerRadius="5">
|
||||
<TextBlock Text="Channelname" Foreground="White" TextWrapping="WrapWholeWords" FontSize="13" 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]"/>
|
||||
<TextBlock Text="[Publish date] (edited)" Name="meta" TextWrapping="WrapWholeWords" Foreground="Gray" FontSize="13"/>
|
||||
<TextBlock Name="text" IsTextSelectionEnabled="True" TextWrapping="WrapWholeWords" 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"/>
|
||||
<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="" 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/editorSubmit" Name="editorSend" Content="Submit" Click="editorSend_Click"/>
|
||||
</StackPanel>
|
||||
<ProgressBar Name="editorSending" Foreground="Red" IsIndeterminate="True" Visibility="Collapsed"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal">
|
||||
<StackPanel Orientation="Horizontal" Name="toolbar">
|
||||
<TextBlock Name="upvote" Foreground="Gray" Padding="0"
|
||||
VerticalAlignment="Center" Margin="0,0,5,0"
|
||||
FontFamily="Segoe MDL2 Assets" Text="" 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="" FontSize="20"/>
|
||||
|
||||
<Button Click="showReplies_Click" Name="showReplies" Background="Transparent" Foreground="Gray" Padding="0" Margin="10,0,0,0"
|
||||
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
Height="35">
|
||||
@@ -81,21 +63,27 @@
|
||||
</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"
|
||||
<Grid Name="replyEditor" Visibility="Collapsed">
|
||||
<TextBox x:Uid="/CommentsPage/replyBox" Name="reply" TextChanged="reply_TextChanged" TextWrapping="Wrap" AcceptsReturn="True" MaxLength="500"
|
||||
Margin="2,0,34,0"
|
||||
PlaceholderText="Enter your reply..."/>
|
||||
<Button Grid.Row="1" Name="send" Click="send_Click" IsEnabled="False" HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
<Button Name="send" Click="send_Click" IsEnabled="True" HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
Width="32" Height="32" Padding="0"
|
||||
Background="Red" Foreground="White"
|
||||
Background="Transparent"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="30"
|
||||
Content=""/>
|
||||
<ProgressBar Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" IsIndeterminate="True" Foreground="Red" Name="sending" Visibility="Collapsed"/>
|
||||
</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>
|
||||
</UserControl>
|
||||
|
||||
@@ -4,12 +4,12 @@ using System.Linq;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Input;
|
||||
|
||||
using Google.Apis.YouTube.v3;
|
||||
using Google.Apis.YouTube.v3.Data;
|
||||
using Windows.UI.Xaml.Media.Imaging;
|
||||
using Windows.UI.Popups;
|
||||
using Windows.ApplicationModel.Resources;
|
||||
using Microsoft.AppCenter.Analytics;
|
||||
|
||||
namespace FoxTube.Controls
|
||||
{
|
||||
@@ -21,10 +21,11 @@ namespace FoxTube.Controls
|
||||
{
|
||||
ResourceLoader resources = ResourceLoader.GetForCurrentView("CommentsPage");
|
||||
|
||||
Comment item;
|
||||
CommentThread thread;
|
||||
public Comment item;
|
||||
public CommentThread thread;
|
||||
CommentType type;
|
||||
|
||||
bool repliesLoaded = false;
|
||||
CommentType type = CommentType.TopLevel;
|
||||
|
||||
public CommentCard(CommentThread comment)
|
||||
{
|
||||
@@ -37,15 +38,15 @@ namespace FoxTube.Controls
|
||||
item = comment.Snippet.TopLevelComment;
|
||||
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)
|
||||
showReplies.Visibility = Visibility.Collapsed;
|
||||
else
|
||||
((showReplies.Content as StackPanel).Children[1] as TextBlock).Text = comment.Snippet.TotalReplyCount.ToString();
|
||||
|
||||
if (comment.Snippet.TopLevelComment.Snippet.CanRate == false)
|
||||
{
|
||||
upvote.Visibility = Visibility.Collapsed;
|
||||
downvote.Visibility = Visibility.Collapsed;
|
||||
rating.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
@@ -53,15 +54,15 @@ namespace FoxTube.Controls
|
||||
|
||||
if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == SecretsVault.AccountId)
|
||||
{
|
||||
specialAuthor.Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
|
||||
authorBorder.Visibility = Visibility.Visible;
|
||||
(specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
|
||||
specialAuthor.Visibility = Visibility.Visible;
|
||||
author.Visibility = Visibility.Collapsed;
|
||||
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;
|
||||
authorBorder.Visibility = Visibility.Visible;
|
||||
(specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
|
||||
specialAuthor.Visibility = Visibility.Visible;
|
||||
author.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
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") : "");
|
||||
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 { }
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
this.InitializeComponent();
|
||||
InitializeComponent();
|
||||
Initialize(comment);
|
||||
}
|
||||
|
||||
@@ -87,7 +99,6 @@ namespace FoxTube.Controls
|
||||
|
||||
replyBtn.Visibility = Visibility.Collapsed;
|
||||
showReplies.Visibility = Visibility.Collapsed;
|
||||
downvote.Visibility = Visibility.Collapsed;
|
||||
|
||||
if (comment.Snippet.CanRate == false)
|
||||
{
|
||||
@@ -99,15 +110,15 @@ namespace FoxTube.Controls
|
||||
|
||||
if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == SecretsVault.AccountId)
|
||||
{
|
||||
specialAuthor.Text = comment.Snippet.AuthorDisplayName;
|
||||
authorBorder.Visibility = Visibility.Visible;
|
||||
(specialAuthor.Child as TextBlock).Text = comment.Snippet.AuthorDisplayName;
|
||||
specialAuthor.Visibility = Visibility.Visible;
|
||||
author.Visibility = Visibility.Collapsed;
|
||||
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;
|
||||
authorBorder.Visibility = Visibility.Visible;
|
||||
(specialAuthor.Child as TextBlock).Text = comment.Snippet.AuthorDisplayName;
|
||||
specialAuthor.Visibility = Visibility.Visible;
|
||||
author.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
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") : "");
|
||||
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 { }
|
||||
}
|
||||
|
||||
private void replyBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (grid.RowDefinitions[1].Height == new GridLength(0))
|
||||
grid.RowDefinitions[1].Height = GridLength.Auto;
|
||||
else
|
||||
grid.RowDefinitions[1].Height = new GridLength(0);
|
||||
replyEditor.Visibility = replyEditor.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
private async void showReplies_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (grid.RowDefinitions[2].Height == new GridLength(0))
|
||||
{
|
||||
if(type == CommentType.TopLevel && !repliesLoaded)
|
||||
{
|
||||
commentsLoading.Visibility = Visibility.Visible;
|
||||
var request = SecretsVault.Service.Comments.List("snippet");
|
||||
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;
|
||||
|
||||
string token;
|
||||
IList<Comment> list = new List<Comment>();
|
||||
CommentListResponse response;
|
||||
|
||||
var response = await request.ExecuteAsync();
|
||||
token = response.NextPageToken;
|
||||
|
||||
foreach (Comment i in response.Items)
|
||||
list.Add(i);
|
||||
|
||||
while(token != null)
|
||||
do
|
||||
{
|
||||
request.PageToken = token;
|
||||
response = await request.ExecuteAsync();
|
||||
token = response.NextPageToken;
|
||||
request.PageToken = response.NextPageToken;
|
||||
|
||||
foreach (Comment i in response.Items)
|
||||
list.Add(i);
|
||||
response.Items.ForEach(i => replies.Children.Add(new CommentCard(i)));
|
||||
}
|
||||
|
||||
foreach (Comment c in list.Reverse())
|
||||
replies.Children.Add(new CommentCard(c));
|
||||
while (!string.IsNullOrWhiteSpace(request.PageToken));
|
||||
|
||||
repliesLoaded = true;
|
||||
commentsLoading.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
grid.RowDefinitions[2].Height = GridLength.Auto;
|
||||
}
|
||||
else
|
||||
grid.RowDefinitions[2].Height = new GridLength(0);
|
||||
processing.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void avatar_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
@@ -178,99 +172,126 @@ namespace FoxTube.Controls
|
||||
|
||||
private void reply_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (reply.Text.Length == 0)
|
||||
send.IsEnabled = false;
|
||||
else
|
||||
send.IsEnabled = true;
|
||||
send.IsEnabled = reply.Text.Length == 0 ? false : 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;
|
||||
reply.IsEnabled = false;
|
||||
sending.Visibility = Visibility.Visible;
|
||||
processing.Visibility = Visibility.Visible;
|
||||
|
||||
Comment comment = new Comment();
|
||||
comment.Snippet = new CommentSnippet();
|
||||
comment.Snippet.TextOriginal = reply.Text;
|
||||
comment.Snippet.ParentId = item.Id;
|
||||
Comment comment = new Comment()
|
||||
{
|
||||
Snippet = new CommentSnippet()
|
||||
{
|
||||
TextOriginal = reply.Text,
|
||||
ParentId = item.Id
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
Comment response = await SecretsVault.Service.Comments.Insert(comment, "snippet").ExecuteAsync();
|
||||
reply.Text = "";
|
||||
grid.RowDefinitions[1].Height = new GridLength(0);
|
||||
replies.Children.Add(new CommentCard(response));
|
||||
replyEditor.Visibility = Visibility.Collapsed;
|
||||
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();
|
||||
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;
|
||||
reply.IsEnabled = true;
|
||||
sending.Visibility = Visibility.Collapsed;
|
||||
processing.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
editorSend.IsEnabled = false;
|
||||
editorClose.IsEnabled = false;
|
||||
editorSending.Visibility = Visibility.Visible;
|
||||
deleteComment.IsEnabled = false;
|
||||
processing.Visibility = Visibility.Visible;
|
||||
|
||||
try
|
||||
{
|
||||
item.Snippet.TextDisplay = editorText.Text;
|
||||
item.Snippet.TextOriginal = editorText.Text;
|
||||
item.Snippet.UpdatedAt = DateTime.Now;
|
||||
|
||||
if (type == CommentType.Reply)
|
||||
{
|
||||
await SecretsVault.Service.Comments.Update(item, "snippet").ExecuteAsync();
|
||||
Initialize(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
thread.Snippet.TopLevelComment = item;
|
||||
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();
|
||||
Analytics.TrackEvent("Failed to edit comment", new Dictionary<string, string>()
|
||||
{
|
||||
{ "Exception", e.GetType().ToString() },
|
||||
{ "Message", e.Message }
|
||||
});
|
||||
}
|
||||
|
||||
editorText.IsEnabled = true;
|
||||
editorSend.IsEnabled = true;
|
||||
editorClose.IsEnabled = true;
|
||||
editorSending.Visibility = Visibility.Collapsed;
|
||||
deleteComment.IsEnabled = true;
|
||||
processing.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void editorText_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (editorText.Text.Length == 0)
|
||||
editorSend.IsEnabled = false;
|
||||
else editorSend.IsEnabled = true;
|
||||
editorSend.IsEnabled = editorText.Text.Length == 0 ? false : true;
|
||||
}
|
||||
|
||||
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;
|
||||
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"));
|
||||
|
||||
@@ -279,9 +300,20 @@ namespace FoxTube.Controls
|
||||
try
|
||||
{
|
||||
await SecretsVault.Service.Comments.Delete(item.Id).ExecuteAsync();
|
||||
if (type == CommentType.Reply)
|
||||
Methods.CommentsPage.RemoveComment(this, item.Snippet.ParentId);
|
||||
else
|
||||
Methods.CommentsPage.RemoveComment(this);
|
||||
}
|
||||
catch { }
|
||||
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 }
|
||||
});
|
||||
}
|
||||
}));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/CommentsPage/no")));
|
||||
|
||||
|
||||
@@ -22,12 +22,7 @@
|
||||
<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"/>
|
||||
|
||||
<StackPanel Grid.Column="2" Margin="5">
|
||||
<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>
|
||||
<TextBlock Grid.Column="2" Margin="5" Name="meta"/>
|
||||
|
||||
<StackPanel Name="donePanel" Grid.Column="3" Orientation="Horizontal" Visibility="Visible">
|
||||
<Button Name="open" Click="open_Click" Width="80" Height="80" Padding="0" Background="Transparent">
|
||||
@@ -44,10 +39,10 @@
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Name="progressPanel" Grid.Column="4" Margin="10">
|
||||
<TextBlock Name="status" Text="Downloading..." HorizontalAlignment="Left"/>
|
||||
<ProgressBar Name="progressBar" Width="200" Maximum="1" IsIndeterminate="True" Foreground="Red"/>
|
||||
<TextBlock Name="perc" Text="--%"/>
|
||||
<Button x:Uid="/Downloads/cancel" Content="Cancel" Name="cancel" Click="CancelPrompt" HorizontalAlignment="Right"/>
|
||||
<TextBlock x:Uid="/Downloads/downloading" Name="status" Text="Downloading..." HorizontalAlignment="Left"/>
|
||||
<ProgressBar Name="progressBar" Width="200" Maximum="1"/>
|
||||
<TextBlock Name="progressText" Text="--%"/>
|
||||
<Button x:Uid="/Downloads/cancel" Content="Cancel" Name="cancel" Click="Cancel_Click" HorizontalAlignment="Right"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -4,7 +4,6 @@ using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Media.Imaging;
|
||||
using Windows.System;
|
||||
using FoxTube.Classes;
|
||||
using YoutubeExplode.Models.MediaStreams;
|
||||
using YoutubeExplode;
|
||||
using Windows.Storage;
|
||||
@@ -16,30 +15,55 @@ using Windows.UI.Notifications;
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel.Resources;
|
||||
using Microsoft.AppCenter.Analytics;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
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
|
||||
{
|
||||
ResourceLoader resources = ResourceLoader.GetForCurrentView("Downloads");
|
||||
|
||||
public DownloadItemContainer Container { get; private set; }
|
||||
public StorageFile file;
|
||||
public bool InProgress { get; set; } = false;
|
||||
public StorageFile File { get; private set; }
|
||||
|
||||
readonly YoutubeClient client = new YoutubeClient();
|
||||
readonly CancellationTokenSource cts = new CancellationTokenSource();
|
||||
CancellationToken token;
|
||||
readonly Progress<double> progress = new Progress<double>();
|
||||
private readonly NotificationData data = new NotificationData();
|
||||
|
||||
DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
|
||||
CancellationTokenSource cts;
|
||||
Progress<double> progress;
|
||||
double percentage;
|
||||
DispatcherTimer timer;
|
||||
NotificationData data;
|
||||
|
||||
public DownloadItem(MediaStreamInfo info, Video meta, string q)
|
||||
{
|
||||
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)
|
||||
@@ -47,47 +71,38 @@ namespace FoxTube.Controls
|
||||
InitializeComponent();
|
||||
|
||||
Container = container;
|
||||
Initialize();
|
||||
Load();
|
||||
}
|
||||
|
||||
public async void Initialize()
|
||||
async void Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
file = await DownloadAgent.Downloads.GetFileAsync(Container.Name);
|
||||
}
|
||||
catch
|
||||
{
|
||||
DownloadAgent.Remove(Container.Id);
|
||||
return;
|
||||
}
|
||||
File = await DownloadAgent.Downloads.TryGetItemAsync(Container.Name) as StorageFile;
|
||||
if (File == null)
|
||||
DownloadAgent.Remove(this);
|
||||
|
||||
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;
|
||||
progressPanel.Visibility = Visibility.Collapsed;
|
||||
|
||||
SetMeta();
|
||||
}
|
||||
|
||||
async void Download(MediaStreamInfo info, Video meta, string q)
|
||||
async void Download(MediaStreamInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
InProgress = true;
|
||||
Container = new DownloadItemContainer();
|
||||
File = await DownloadAgent.Downloads.CreateFileAsync($"{Container.Title.ReplaceInvalidChars('_')}.{Container.Extension}", CreationCollisionOption.GenerateUniqueName);
|
||||
Container.Name = File.Name;
|
||||
|
||||
token = cts.Token;
|
||||
donePanel.Visibility = Visibility.Collapsed;
|
||||
progressPanel.Visibility = Visibility.Visible;
|
||||
SetMeta();
|
||||
|
||||
cts = new CancellationTokenSource();
|
||||
progress = new Progress<double>();
|
||||
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;
|
||||
timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
|
||||
timer.Tick += (s, e) => UpdateInfo();
|
||||
|
||||
#region Polling notification
|
||||
ToastContent toastContent = new ToastContent()
|
||||
{
|
||||
Visual = new ToastVisual()
|
||||
@@ -99,7 +114,7 @@ namespace FoxTube.Controls
|
||||
new AdaptiveText() { Text = resources.GetString("/Downloads/toastStartHeader") },
|
||||
new AdaptiveProgressBar()
|
||||
{
|
||||
Title = meta.Snippet.Title,
|
||||
Title = Container.Title,
|
||||
Status = resources.GetString("/Downloads/downloading/Text"),
|
||||
Value = new BindableProgressBarValue("value")
|
||||
}
|
||||
@@ -112,65 +127,89 @@ namespace FoxTube.Controls
|
||||
Buttons =
|
||||
{
|
||||
new ToastButton(resources.GetString("/Downloads/cancel/Content"), $"dcancel|{Container.Id}")
|
||||
{
|
||||
ActivationType = ToastActivationType.Background
|
||||
}
|
||||
}
|
||||
},
|
||||
Launch = "download",
|
||||
ActivationType = ToastActivationType.Foreground
|
||||
};
|
||||
|
||||
data = new NotificationData();
|
||||
data.Values["value"] = "0";
|
||||
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(toastContent.GetXml()) { Tag = $"download|{meta.Id}", Data = data });
|
||||
|
||||
Container.Channel = meta.Snippet.ChannelTitle;
|
||||
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));
|
||||
title.Text = meta.Snippet.Title;
|
||||
ext.Text = $"{resources.GetString("/Downloads/ext")}: {info.Container.GetFileExtension()}";
|
||||
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;
|
||||
donePanel.Visibility = Visibility.Collapsed;
|
||||
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(toastContent.GetXml()) { Tag = $"download|{Container.Id}", Data = data });
|
||||
#endregion
|
||||
|
||||
timer.Start();
|
||||
|
||||
await client.DownloadMediaStreamAsync(info, await file.OpenStreamForWriteAsync(), progress, token);
|
||||
try
|
||||
{
|
||||
await new YoutubeClient().DownloadMediaStreamAsync(info, await File.OpenStreamForWriteAsync(), progress, cts.Token);
|
||||
|
||||
progressPanel.Visibility = Visibility.Collapsed;
|
||||
donePanel.Visibility = Visibility.Visible;
|
||||
Container.IsDownloaded = true;
|
||||
timer.Stop();
|
||||
|
||||
InProgress = false;
|
||||
|
||||
if (!cts.IsCancellationRequested)
|
||||
if (cts.IsCancellationRequested)
|
||||
throw new TaskCanceledException();
|
||||
else
|
||||
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;
|
||||
perc.Text = $"{(int)e * 100}%";
|
||||
progressBar.Value = percentage;
|
||||
progressText.Text = Math.Round(percentage * 100, 1) + "%";
|
||||
|
||||
data.Values["value"] = e.ToString();
|
||||
data.Values["value"] = percentage.ToString();
|
||||
ToastNotificationManager.CreateToastNotifier().Update(data, $"download|{Container.Id}");
|
||||
}
|
||||
|
||||
private void DownloadCompleted()
|
||||
{
|
||||
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>
|
||||
<binding template='ToastGeneric'>
|
||||
<text>{resources.GetString("/Downloads/toastCompleteHeader")}</text>
|
||||
@@ -185,11 +224,14 @@ namespace FoxTube.Controls
|
||||
</actions>
|
||||
</toast>");
|
||||
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)
|
||||
{
|
||||
await Launcher.LaunchFileAsync(file);
|
||||
await Launcher.LaunchFileAsync(File);
|
||||
}
|
||||
|
||||
private void gotoOriginal_Click(object sender, RoutedEventArgs e)
|
||||
@@ -197,44 +239,45 @@ namespace FoxTube.Controls
|
||||
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;
|
||||
cts.Cancel();
|
||||
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)
|
||||
if(prompt)
|
||||
{
|
||||
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/no")));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/Downloads/yes"), null, false));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/Downloads/no"), null, true));
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
x:Class="FoxTube.Controls.LiveCaptions"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:FoxTube.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
@@ -10,7 +9,10 @@
|
||||
VerticalAlignment="Bottom"
|
||||
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"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.Media;
|
||||
using YoutubeExplode.Models.ClosedCaptions;
|
||||
|
||||
namespace FoxTube.Controls
|
||||
@@ -17,8 +16,8 @@ namespace FoxTube.Controls
|
||||
set => text.FontSize = value;
|
||||
}
|
||||
|
||||
public MediaTimelineController Player { get; set; }
|
||||
private bool isClosed = false;
|
||||
public MediaElement Player { get; set; }
|
||||
|
||||
DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(10) };
|
||||
ClosedCaption currentCaption = null;
|
||||
|
||||
@@ -32,41 +31,19 @@ namespace FoxTube.Controls
|
||||
|
||||
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(!isClosed)
|
||||
track.Captions.ForEach(i =>
|
||||
{
|
||||
if (Player.Position >= i.Offset && Player.Position <= i.Offset + i.Duration)
|
||||
{
|
||||
currentCaption = i;
|
||||
if (currentCaption != null)
|
||||
text.Text = currentCaption.Text;
|
||||
Visibility = Visibility.Visible;
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!found)
|
||||
{
|
||||
currentCaption = null;
|
||||
Visibility = Visibility.Collapsed;
|
||||
}
|
||||
Visibility = currentCaption == null ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
public async void Initialize(ClosedCaptionTrackInfo info)
|
||||
{
|
||||
track = await new YoutubeExplode.YoutubeClient().GetClosedCaptionTrackAsync(info);
|
||||
|
||||
track.Captions.ForEach(i =>
|
||||
{
|
||||
if (Player.Position > i.Offset && Player.Position < i.Offset + i.Duration)
|
||||
{
|
||||
currentCaption = i;
|
||||
text.Text = currentCaption.Text;
|
||||
Visibility = Visibility.Visible;
|
||||
}
|
||||
});
|
||||
UpdateCaption(this, null);
|
||||
|
||||
timer.Start();
|
||||
}
|
||||
@@ -78,15 +55,5 @@ namespace FoxTube.Controls
|
||||
Visibility = Visibility.Collapsed;
|
||||
timer.Stop();
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
isClosed = true;
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
isClosed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,23 +2,35 @@
|
||||
x:Class="FoxTube.Controls.PlaylistCard"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:FoxTube.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
|
||||
mc:Ignorable="d"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
SizeChanged="UserControl_SizeChanged"
|
||||
d:DesignHeight="290"
|
||||
d:DesignWidth="384">
|
||||
d:DesignWidth="384"
|
||||
Opacity="0">
|
||||
|
||||
<Button Padding="0" Background="Transparent" Click="Button_Click">
|
||||
<Grid Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
|
||||
<Windows10version1809:Page.OpacityTransition>
|
||||
<ScalarTransition Duration="0:0:0.5"/>
|
||||
</Windows10version1809:Page.OpacityTransition>
|
||||
|
||||
<Button Padding="0" Margin="1" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" Click="Button_Click">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="75"/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="20"/>
|
||||
<RowDefinition Height="55"/>
|
||||
</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.Background>
|
||||
<AcrylicBrush TintColor="#7F000000" BackgroundSource="Backdrop" AlwaysUseFallback="False" TintOpacity="1" Opacity="0.97"/>
|
||||
@@ -28,12 +40,8 @@
|
||||
<TextBlock Foreground="White" Text="[N/A]" HorizontalAlignment="Center" FontSize="20" Name="counter"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="20"/>
|
||||
<RowDefinition Height="55"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="60"/>
|
||||
<ColumnDefinition/>
|
||||
@@ -44,8 +52,8 @@
|
||||
<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>
|
||||
<TextBlock Grid.Row="1" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" TextTrimming="CharacterEllipsis" Margin="5" MaxLines="2"/>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="2" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" TextTrimming="CharacterEllipsis" Margin="5" MaxLines="2"/>
|
||||
</Grid>
|
||||
</Button>
|
||||
<UserControl.ContextFlyout>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Google.Apis.YouTube.v3;
|
||||
using Google.Apis.YouTube.v3.Data;
|
||||
using Microsoft.AppCenter.Analytics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.System;
|
||||
using Windows.UI.Xaml;
|
||||
@@ -17,6 +19,8 @@ namespace FoxTube.Controls
|
||||
Playlist item;
|
||||
public string playlistId;
|
||||
|
||||
public bool NeedInitialize { get; set; } = true;
|
||||
|
||||
public PlaylistCard(string id)
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -25,12 +29,12 @@ namespace FoxTube.Controls
|
||||
|
||||
public async void Initialize(string id)
|
||||
{
|
||||
PlaylistsResource.ListRequest request = SecretsVault.Service.Playlists.List("snippet,contentDetails");
|
||||
request.Id = id;
|
||||
PlaylistListResponse response = await request.ExecuteAsync();
|
||||
|
||||
item = response.Items[0];
|
||||
try
|
||||
{
|
||||
playlistId = id;
|
||||
PlaylistsResource.ListRequest request = SecretsVault.Service.Playlists.List("snippet,contentDetails");
|
||||
request.Id = playlistId;
|
||||
item = (await request.ExecuteAsync()).Items[0];
|
||||
|
||||
title.Text = item.Snippet.Title;
|
||||
channelName.Text = item.Snippet.ChannelTitle;
|
||||
@@ -40,19 +44,26 @@ namespace FoxTube.Controls
|
||||
ChannelsResource.ListRequest r = SecretsVault.Service.Channels.List("snippet");
|
||||
r.Id = item.Snippet.ChannelId;
|
||||
|
||||
try
|
||||
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)
|
||||
{
|
||||
thumbnail.Source = new BitmapImage((item.Snippet.Thumbnails.Maxres ?? item.Snippet.Thumbnails.Medium).Url.ToUri());
|
||||
avatar.ProfilePicture = new BitmapImage(new Uri((await r.ExecuteAsync()).Items[0].Snippet.Thumbnails.Medium.Url));
|
||||
} catch { }
|
||||
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)
|
||||
{
|
||||
Height = e.NewSize.Width * 0.75;
|
||||
}
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e)
|
||||
public void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Methods.MainPage.GoToPlaylist(item.Id);
|
||||
}
|
||||
@@ -73,5 +84,10 @@ namespace FoxTube.Controls
|
||||
{
|
||||
await Launcher.LaunchUriAsync($"https://www.youtube.com/playlist?list={playlistId}".ToUri());
|
||||
}
|
||||
|
||||
private void Thumbnail_ImageOpened(object sender, RoutedEventArgs e)
|
||||
{
|
||||
thumbnail.Opacity = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
x:Class="FoxTube.Controls.ShowMore"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:FoxTube.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
@@ -1,19 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
|
||||
// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
|
||||
|
||||
namespace FoxTube.Controls
|
||||
{
|
||||
@@ -22,7 +8,7 @@ namespace FoxTube.Controls
|
||||
public event Event Clicked;
|
||||
public ShowMore()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void btn_Click(object sender, RoutedEventArgs e)
|
||||
@@ -32,10 +18,20 @@ namespace FoxTube.Controls
|
||||
Clicked.Invoke();
|
||||
}
|
||||
|
||||
public void Complete(bool close = false)
|
||||
public void Invoke()
|
||||
{
|
||||
btn_Click(this, null);
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
btn.Visibility = Visibility.Collapsed;
|
||||
bar.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
bar.Visibility = Visibility.Collapsed;
|
||||
if (!close)
|
||||
btn.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,51 +4,62 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
|
||||
mc:Ignorable="d"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
SizeChanged="UserControl_SizeChanged"
|
||||
d:DesignHeight="290"
|
||||
d:DesignWidth="384">
|
||||
d:DesignWidth="384"
|
||||
Opacity="0">
|
||||
|
||||
<Button Padding="0" Background="Transparent" Click="Button_Click">
|
||||
<Grid Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
|
||||
<Windows10version1809:UserControl.OpacityTransition>
|
||||
<ScalarTransition Duration="0:0:0.5"/>
|
||||
</Windows10version1809:UserControl.OpacityTransition>
|
||||
|
||||
<Button Padding="0" Margin="1" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" Click="Button_Click">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="75"/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="20"/>
|
||||
<RowDefinition Height="55"/>
|
||||
</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">
|
||||
<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"/>
|
||||
</StackPanel>
|
||||
<ProgressBar VerticalAlignment="Bottom" Margin="54,0,0,0" Foreground="Red" Name="leftOn"/>
|
||||
</Grid>
|
||||
|
||||
<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"/>
|
||||
</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">
|
||||
<TextBlock Text=" " 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"/>
|
||||
</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"/>
|
||||
<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>
|
||||
<TextBlock Grid.Row="1" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" Margin="5" MaxLines="2" TextTrimming="CharacterEllipsis"/>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="2" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" Margin="5" MaxLines="2" TextTrimming="CharacterEllipsis"/>
|
||||
</Grid>
|
||||
</Button>
|
||||
<UserControl.ContextFlyout>
|
||||
|
||||
@@ -7,6 +7,10 @@ using Windows.UI.Xaml.Media.Imaging;
|
||||
using Windows.System;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.ApplicationModel.Resources;
|
||||
using Microsoft.AppCenter.Analytics;
|
||||
using System.Collections.Generic;
|
||||
using YoutubeExplode;
|
||||
using Windows.UI.Popups;
|
||||
|
||||
namespace FoxTube.Controls
|
||||
{
|
||||
@@ -27,30 +31,35 @@ namespace FoxTube.Controls
|
||||
Initialize(id, playlist);
|
||||
}
|
||||
|
||||
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
public VideoCard(Video meta, string playlist = null)
|
||||
{
|
||||
Height = e.NewSize.Width * 0.75;
|
||||
InitializeComponent();
|
||||
item = meta;
|
||||
playlistId = playlist;
|
||||
LoadMeta();
|
||||
}
|
||||
|
||||
public async void Initialize(string id, string playlist = null)
|
||||
{
|
||||
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("snippet,contentDetails,statistics,liveStreamingDetails");
|
||||
request.Id = id;
|
||||
VideoListResponse response = await request.ExecuteAsync();
|
||||
|
||||
item = response.Items[0];
|
||||
try
|
||||
{
|
||||
videoId = id;
|
||||
playlistId = playlist;
|
||||
|
||||
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("snippet,contentDetails,statistics,liveStreamingDetails");
|
||||
request.Id = id;
|
||||
item = (await request.ExecuteAsync()).Items[0];
|
||||
|
||||
title.Text = item.Snippet.Title;
|
||||
channelName.Text = item.Snippet.ChannelTitle;
|
||||
|
||||
if (item.Snippet.LiveBroadcastContent == "live")
|
||||
{
|
||||
views.Text = $"{item.LiveStreamingDetails.ConcurrentViewers:0,0} {resources.GetString("/Cards/viewers")}";
|
||||
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue && item.LiveStreamingDetails.ScheduledEndTime.HasValue)
|
||||
info.Text = $"{item.LiveStreamingDetails.ScheduledEndTime - item.LiveStreamingDetails.ScheduledStartTime} | {Methods.GetAgo(item.LiveStreamingDetails.ActualStartTime.Value)}";
|
||||
info.Text = $"{item.LiveStreamingDetails.ScheduledEndTime - item.LiveStreamingDetails.ActualStartTime} | {Methods.GetAgo(item.LiveStreamingDetails.ActualStartTime.Value)}";
|
||||
else
|
||||
info.Text = item.LiveStreamingDetails.ActualStartTime.Value.ToString();
|
||||
info.Text = Methods.GetAgo(item.LiveStreamingDetails.ActualStartTime.Value);
|
||||
liveTag.Visibility = Visibility.Visible;
|
||||
}
|
||||
else if (item.Snippet.LiveBroadcastContent == "upcoming")
|
||||
@@ -62,8 +71,8 @@ namespace FoxTube.Controls
|
||||
info.Text = $"{Methods.GetAgo(item.Snippet.PublishedAt.Value)}";
|
||||
liveTag.Visibility = Visibility.Visible;
|
||||
|
||||
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue && (item.LiveStreamingDetails.ScheduledStartTime - DateTime.Now).Value.TotalMilliseconds > 0)
|
||||
liveContent.Text = $"{resources.GetString("/Cards/goesLive")} {item.LiveStreamingDetails.ScheduledStartTime}";
|
||||
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue)
|
||||
liveContent.Text = resources.GetString("/Cards/goesLive") + (item.LiveStreamingDetails.ScheduledStartTime.Value > DateTime.Now ? " " : " -") + (item.LiveStreamingDetails.ScheduledStartTime.Value - DateTime.Now).ToString(@"hh\:mm\:ss");
|
||||
else liveContent.Text = resources.GetString("/Cards/upcoming");
|
||||
}
|
||||
else
|
||||
@@ -72,26 +81,107 @@ namespace FoxTube.Controls
|
||||
info.Text = $"{item.ContentDetails.Duration.GetDuration()} | {Methods.GetAgo(item.Snippet.PublishedAt.Value)}";
|
||||
}
|
||||
|
||||
var request1 = SecretsVault.Service.Channels.List("snippet");
|
||||
request1.Id = item.Snippet.ChannelId;
|
||||
ChannelListResponse response1 = await request1.ExecuteAsync();
|
||||
|
||||
try
|
||||
{
|
||||
avatar.ProfilePicture = new BitmapImage(new Uri(response1.Items[0].Snippet.Thumbnails.Medium.Url));
|
||||
thumbnail.Source = new BitmapImage(new Uri((item.Snippet.Thumbnails.Maxres ?? item.Snippet.Thumbnails.Medium).Url));
|
||||
}
|
||||
try { thumbnail.Source = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri()); }
|
||||
catch { }
|
||||
try { avatar.ProfilePicture = new BitmapImage((await new YoutubeClient().GetChannelAsync(item.Snippet.ChannelId)).LogoUrl.ToUri()) { DecodePixelWidth = 46, DecodePixelHeight = 46 }; }
|
||||
catch { }
|
||||
|
||||
/*if(SecretsVault.UserHistory.Exists(x => x.Id == videoId))
|
||||
{
|
||||
if(SecretsVault.History.Contains(videoId))
|
||||
watched.Visibility = Visibility.Visible;
|
||||
leftOn.Value = SecretsVault.UserHistory.Find(x => x.Id == videoId).LeftOn;
|
||||
}*/
|
||||
|
||||
Opacity = 1;
|
||||
}
|
||||
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 void Button_Click(object sender, RoutedEventArgs e)
|
||||
public async void LoadMeta()
|
||||
{
|
||||
videoId = item.Id;
|
||||
title.Text = item.Snippet.Title;
|
||||
channelName.Text = item.Snippet.ChannelTitle;
|
||||
|
||||
if (item.Snippet.LiveBroadcastContent == "live")
|
||||
{
|
||||
views.Text = $"{item.LiveStreamingDetails.ConcurrentViewers:0,0} {resources.GetString("/Cards/viewers")}";
|
||||
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue && item.LiveStreamingDetails.ScheduledEndTime.HasValue)
|
||||
info.Text = $"{item.LiveStreamingDetails.ScheduledEndTime - item.LiveStreamingDetails.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 - DateTime.Now}";
|
||||
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()) { DecodePixelHeight = 50, DecodePixelWidth = 50 }; }
|
||||
catch { }
|
||||
|
||||
if (SecretsVault.History.Contains(videoId))
|
||||
watched.Visibility = Visibility.Visible;
|
||||
|
||||
Opacity = 1;
|
||||
}
|
||||
|
||||
public async void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (item.ContentDetails.ContentRating != null)
|
||||
{
|
||||
if (SecretsVault.IsAuthorized)
|
||||
{
|
||||
if (SettingsStorage.Mature == MatureState.Blocked)
|
||||
{
|
||||
MessageDialog dialog = new MessageDialog(resources.GetString("/VideoPage/matureText"), resources.GetString("/VideoPage/wantContinue"));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/VideoPage/yes"), null, true));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/VideoPage/always"), (command) => SettingsStorage.Mature = MatureState.Allowed, true));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/VideoPage/no"), null, false));
|
||||
|
||||
dialog.CancelCommandIndex = 2;
|
||||
dialog.DefaultCommandIndex = 0;
|
||||
|
||||
if (!(bool)(await dialog.ShowAsync()).Id)
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageDialog dialog = new MessageDialog(resources.GetString("/VideoPage/matureText"), resources.GetString("/VideoPage/signRequired"));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/VideoPage/signin"), (command) => SecretsVault.Authorize()));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/VideoPage/cancel")));
|
||||
|
||||
dialog.CancelCommandIndex = 1;
|
||||
dialog.DefaultCommandIndex = 0;
|
||||
|
||||
await dialog.ShowAsync();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Methods.MainPage.GoToVideo(videoId, playlistId);
|
||||
}
|
||||
|
||||
@@ -111,5 +201,10 @@ namespace FoxTube.Controls
|
||||
{
|
||||
await Launcher.LaunchUriAsync($"https://www.youtube.com/watch?v={videoId}".ToUri());
|
||||
}
|
||||
|
||||
private void Thumbnail_ImageOpened(object sender, RoutedEventArgs e)
|
||||
{
|
||||
thumbnail.Opacity = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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="" 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="" 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="" 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="" 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="" FontFamily="Segoe MDL2 Assets" Background="Transparent" FontSize="40" Foreground="WhiteSmoke" Name="touchBack10" Click="back10_Click"/>
|
||||
<Button VerticalAlignment="Center" Content="" FontFamily="Segoe MDL2 Assets" Background="Transparent" FontSize="100" Foreground="WhiteSmoke" Name="touchPlay" Click="play_Click"/>
|
||||
<Button VerticalAlignment="Center" Content="" 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="" 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="" 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=""
|
||||
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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.17763.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.16299.0</TargetPlatformMinVersion>
|
||||
<TargetPlatformMinVersion>10.0.17134.0</TargetPlatformMinVersion>
|
||||
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
@@ -21,7 +21,7 @@
|
||||
<PackageCertificateThumbprint>50B93E6A246058D555BA65CD203D7A02064A7409</PackageCertificateThumbprint>
|
||||
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
|
||||
<AppxAutoIncrementPackageRevision>True</AppxAutoIncrementPackageRevision>
|
||||
<AppxPackageDir>D:\XFox\Documents\FoxTube builds\0.3\</AppxPackageDir>
|
||||
<AppxPackageDir>E:\XFox\Documents\FoxTube builds\0.4\</AppxPackageDir>
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<AppxBundlePlatforms>x86|x64|arm</AppxBundlePlatforms>
|
||||
<AppInstallerUpdateFrequency>1</AppInstallerUpdateFrequency>
|
||||
@@ -103,7 +103,6 @@
|
||||
<Compile Include="App.xaml.cs">
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Classes\DownloadItemContainer.cs" />
|
||||
<Compile Include="Classes\InboxItem.cs" />
|
||||
<Compile Include="Classes\Methods.cs" />
|
||||
<Compile Include="Classes\SearchPaameters.cs" />
|
||||
@@ -130,6 +129,7 @@
|
||||
<Compile Include="Controls\LiveCaptions.xaml.cs">
|
||||
<DependentUpon>LiveCaptions.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Controls\Player\PlayerControls.cs" />
|
||||
<Compile Include="Controls\PlaylistCard.xaml.cs">
|
||||
<DependentUpon>PlaylistCard.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@@ -192,7 +192,7 @@
|
||||
<Compile Include="Pages\VideoGrid.xaml.cs">
|
||||
<DependentUpon>VideoGrid.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Controls\VideoPlayer.xaml.cs">
|
||||
<Compile Include="Controls\Player\VideoPlayer.xaml.cs">
|
||||
<DependentUpon>VideoPlayer.xaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
@@ -209,6 +209,7 @@
|
||||
<Content Include="Assets\BadgeLogo.scale-200.png" />
|
||||
<Content Include="Assets\BadgeLogo.scale-400.png" />
|
||||
<Content Include="Assets\ChannelCoverTemplate.png" />
|
||||
<Content Include="Assets\Data\RevEn.xml" />
|
||||
<Content Include="Assets\Data\Patchnotes.xml" />
|
||||
<Content Include="Assets\FoxGame.png" />
|
||||
<Content Include="Assets\Icons\Profile.png" />
|
||||
@@ -251,6 +252,7 @@
|
||||
<Content Include="Assets\StoreLogo.scale-150.png" />
|
||||
<Content Include="Assets\StoreLogo.scale-200.png" />
|
||||
<Content Include="Assets\StoreLogo.scale-400.png" />
|
||||
<Content Include="Assets\videoPlaceholder.png" />
|
||||
<Content Include="Assets\videoThumbSample.png" />
|
||||
<Content Include="Assets\WhatsNewThumb.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-100.png" />
|
||||
@@ -380,14 +382,18 @@
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Controls\VideoPlayer.xaml">
|
||||
<Page Include="Controls\Player\VideoPlayer.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Themes\Generic.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AdaptiveCards.Rendering.Uwp">
|
||||
<Version>1.1.0</Version>
|
||||
<Version>1.1.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Apis">
|
||||
<Version>1.30.0-beta02</Version>
|
||||
@@ -408,29 +414,34 @@
|
||||
<Version>10.1811.22001</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AppCenter.Analytics">
|
||||
<Version>1.13.0</Version>
|
||||
<Version>1.13.2</Version>
|
||||
</PackageReference>
|
||||
<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 Include="Microsoft.Toolkit.Uwp.Notifications">
|
||||
<Version>5.0.0</Version>
|
||||
<Version>5.1.1</Version>
|
||||
</PackageReference>
|
||||
<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 Include="runtime.win10-arm64.runtime.native.System.IO.Compression">
|
||||
<Version>4.3.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="YoutubeExplode">
|
||||
<Version>4.6.4</Version>
|
||||
<Version>4.6.7</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="YoutubeExtractor">
|
||||
<Version>0.10.11</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="FoxTube_TemporaryKey.pfx" />
|
||||
<PRIResource Include="Strings\ru-RU\VideoPage.resw" />
|
||||
<PRIResource Include="Strings\en-US\VideoPage.resw" />
|
||||
<PRIResource Include="Strings\ru-RU\Channel.resw" />
|
||||
@@ -476,6 +487,9 @@
|
||||
<SDKReference Include="Microsoft.Advertising.Xaml, Version=10.0">
|
||||
<Name>Microsoft Advertising SDK for XAML</Name>
|
||||
</SDKReference>
|
||||
<SDKReference Include="Microsoft.Services.Store.Engagement, Version=10.0">
|
||||
<Name>Microsoft Engagement Framework</Name>
|
||||
</SDKReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' ">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?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">
|
||||
<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" />
|
||||
<Properties>
|
||||
<DisplayName>FoxTube</DisplayName>
|
||||
@@ -15,7 +15,7 @@
|
||||
</Resources>
|
||||
<Applications>
|
||||
<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:ShowNameOnTiles>
|
||||
<uap:ShowOn Tile="square150x150Logo" />
|
||||
@@ -27,6 +27,9 @@
|
||||
<uap:LockScreen Notification="badgeAndTileText" BadgeLogo="Assets\BadgeLogo.png"/>
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="foxtube"/>
|
||||
</uap:Extension>
|
||||
<Extension Category="windows.backgroundTasks" EntryPoint="FoxTube.Background.BackgroundProcessor">
|
||||
<BackgroundTasks>
|
||||
<Task Type="general" />
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<Page
|
||||
x:Class="FoxTube.Pages.Browser"
|
||||
x:Class="FoxTube.Pages.Home1"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:FoxTube.Pages"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
|
||||
@@ -1,55 +1,20 @@
|
||||
using Google.Apis.Http;
|
||||
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
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace FoxTube.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class Browser : Page
|
||||
public sealed partial class Home1 : Page
|
||||
{
|
||||
public Browser()
|
||||
public Home1()
|
||||
{
|
||||
InitializeComponent();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
|
||||
public async void Initialize()
|
||||
{
|
||||
/*Debug.WriteLine(SecretsVault.Credential.Token.AccessToken);
|
||||
WebClient client = new WebClient();
|
||||
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;");*/
|
||||
HttpClient client = new HttpClient();
|
||||
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;*/
|
||||
SecretsVault.HttpClient.DefaultRequestHeaders.Referrer = "https://youtube.com/".ToUri();
|
||||
string response = await SecretsVault.Service.HttpClient.GetStringAsync(adress.Text);
|
||||
code.Text = response;
|
||||
//view.NavigateToString(response);
|
||||
}
|
||||
|
||||
private void Adress_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:pages="using:FoxTube.Pages"
|
||||
xmlns:controls="using:FoxTube.Controls"
|
||||
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid Name="grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" SizeChanged="Grid_SizeChanged">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="auto"/>
|
||||
@@ -18,16 +19,25 @@
|
||||
|
||||
<Pivot SelectedIndex="0" Name="content" IsHeaderItemsCarouselEnabled="False" SelectionChanged="Content_SelectionChanged">
|
||||
<PivotItem x:Uid="/Channel/videos" Header="Videos">
|
||||
<ScrollViewer>
|
||||
<StackPanel Name="videos">
|
||||
<Image Name="channelCover" Stretch="Uniform" Source="/Assets/ChannelCoverTemplate.png"/>
|
||||
<Grid>
|
||||
<ParallaxView Source="{x:Bind videoScroll}" VerticalShift="100">
|
||||
<Grid>
|
||||
<Image Source="/Assets/ChannelCoverTemplate.png" Name="channelCover" Stretch="Uniform" HorizontalAlignment="Left" VerticalAlignment="Top" Opacity="0" ImageOpened="ChannelCover_ImageOpened">
|
||||
<Windows10version1809:Image.OpacityTransition>
|
||||
<ScalarTransition Duration="0:0:0.5"/>
|
||||
</Windows10version1809:Image.OpacityTransition>
|
||||
</Image>
|
||||
</Grid>
|
||||
</ParallaxView>
|
||||
<ScrollViewer ViewChanged="ScrollViewer_ViewChanged" Name="videoScroll">
|
||||
<StackPanel Background="{ThemeResource AppBarBackgroundThemeBrush}" Margin="0,300,0,0" Name="infoStack" Visibility="Visible">
|
||||
<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="100"/>
|
||||
<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"/>
|
||||
@@ -42,64 +52,41 @@
|
||||
<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/>
|
||||
<controls:ShowMore Clicked="VideoMore_Clicked"/>
|
||||
<pages:VideoGrid x:Name="videoList"/>
|
||||
<controls:ShowMore Clicked="VideoMore_Clicked" x:Name="videoMore"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</PivotItem>
|
||||
<PivotItem x:Uid="/Channel/playlists" Header="Playlists">
|
||||
<ScrollViewer>
|
||||
<Grid Name="playlists">
|
||||
<Grid>
|
||||
<StackPanel Margin="10" Visibility="Visible">
|
||||
<TextBlock x:Uid="/Channel/playlistTitle" FontSize="28" Text="Playlists"/>
|
||||
<pages:VideoGrid/>
|
||||
<controls:ShowMore Clicked="ShowMorePlaylists_Click"/>
|
||||
<pages:VideoGrid x:Name="playlistList"/>
|
||||
<controls:ShowMore Clicked="ShowMorePlaylists_Click" x:Name="playlistMore"/>
|
||||
</StackPanel>
|
||||
<local:LoadingPage Visibility="Collapsed"/>
|
||||
<local:LoadingPage Visibility="Collapsed" x:Name="playlistLoading"/>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</PivotItem>
|
||||
<PivotItem x:Uid="/Channel/about" Header="About channel">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="10">
|
||||
<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>
|
||||
<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. "/>
|
||||
</ScrollViewer>
|
||||
</PivotItem>
|
||||
|
||||
<Pivot.RightHeader>
|
||||
<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>
|
||||
|
||||
@@ -109,6 +96,6 @@
|
||||
<AppBarButton x:Uid="/Channel/share" Icon="Share" Label="Share" Name="share" Click="Share_Click"/>
|
||||
</CommandBar>
|
||||
|
||||
<local:LoadingPage Grid.RowSpan="2" Visibility="Collapsed"/>
|
||||
<local:LoadingPage Grid.RowSpan="2" Visibility="Collapsed" x:Name="loading" RefreshPage="Refresh_Click"/>
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
@@ -21,42 +21,30 @@ namespace FoxTube.Pages
|
||||
/// <summary>
|
||||
/// Channel page
|
||||
/// </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");
|
||||
|
||||
public string channelId;
|
||||
public Channel item;
|
||||
|
||||
readonly LoadingPage loading, playlistLoading;
|
||||
readonly VideoGrid videoList, playlistList;
|
||||
readonly ShowMore videoMore, playlistMore;
|
||||
SearchResource.ListRequest request;
|
||||
|
||||
SearchResource.ListRequest videoRequest, playlistRequest;
|
||||
|
||||
private string videoToken;
|
||||
private string playlistToken;
|
||||
private string videoToken, playlistToken;
|
||||
private bool playlistLoaded = false;
|
||||
|
||||
public ChannelPage()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
Parameter = e.Parameter;
|
||||
if ((string)e.Parameter == null)
|
||||
loading.Error("NullReferenceException", "Unable to initialize search. Search term is not stated.");
|
||||
else
|
||||
@@ -65,60 +53,50 @@ namespace FoxTube.Pages
|
||||
|
||||
public async void Initialize(string id)
|
||||
{
|
||||
content.SelectedIndex = 0;
|
||||
loading.Refresh();
|
||||
playlistLoading.Refresh();
|
||||
|
||||
try
|
||||
{
|
||||
channelId = id;
|
||||
if (Methods.NeedToResponse)
|
||||
Methods.MainPage.Content_Navigated(this, null);
|
||||
if (id == SecretsVault.AccountId)
|
||||
{
|
||||
infoPanel.ColumnDefinitions[2].Width = new GridLength(0);
|
||||
collapsedBtn.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
ChannelsResource.ListRequest request = SecretsVault.Service.Channels.List("snippet,statistics,brandingSettings");
|
||||
request.Id = id;
|
||||
if (content.Items.Count == 4)
|
||||
content.Items.RemoveAt(3);
|
||||
ChannelsResource.ListRequest infoRequest = SecretsVault.Service.Channels.List("snippet,statistics,brandingSettings");
|
||||
infoRequest.Id = channelId = id;
|
||||
|
||||
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")}";
|
||||
videosCount.Text = $"{item.Statistics.VideoCount:0,0} {resources.GetString("/Cards/videos")}";
|
||||
|
||||
try
|
||||
{
|
||||
if (item.BrandingSettings.Image.BannerImageUrl.Contains("default"))
|
||||
throw new Exception("Default channel cover detected");
|
||||
channelCover.Source = new BitmapImage(new Uri(item.BrandingSettings.Image.BannerImageUrl));
|
||||
}
|
||||
if (!item.BrandingSettings.Image.BannerImageUrl.Contains("default"))
|
||||
try { channelCover.Source = new BitmapImage(item.BrandingSettings.Image.BannerImageUrl.ToUri()); }
|
||||
catch { }
|
||||
try { avatar.ProfilePicture = new BitmapImage(new Uri(item.Snippet.Thumbnails.Medium.Url)); }
|
||||
|
||||
try { avatar.ProfilePicture = collapsedAvatar.ProfilePicture = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri()) { DecodePixelHeight = 100, DecodePixelWidth = 100 }; }
|
||||
catch { }
|
||||
|
||||
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");
|
||||
videoRequest.ChannelId = id;
|
||||
videoRequest.Type = "video";
|
||||
videoRequest.Order = SearchResource.ListRequest.OrderEnum.Date;
|
||||
videoRequest.MaxResults = 48;
|
||||
request = SecretsVault.Service.Search.List("id");
|
||||
request.ChannelId = id;
|
||||
request.Type = "video";
|
||||
request.Order = SearchResource.ListRequest.OrderEnum.Date;
|
||||
request.MaxResults = 25;
|
||||
|
||||
SearchListResponse response = await videoRequest.ExecuteAsync();
|
||||
SearchListResponse response = await request.ExecuteAsync();
|
||||
|
||||
videoList.Clear();
|
||||
foreach (SearchResult i in response.Items)
|
||||
{
|
||||
VideoCard card = new VideoCard(i.Id.VideoId);
|
||||
videoList.Add(card);
|
||||
}
|
||||
videoList.Add(new VideoCard(i.Id.VideoId));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(response.NextPageToken))
|
||||
videoToken = response.NextPageToken;
|
||||
else
|
||||
videoMore.Complete(true);
|
||||
videoMore.Visibility = Visibility.Collapsed;
|
||||
|
||||
if (SecretsVault.IsAuthorized)
|
||||
{
|
||||
@@ -127,6 +105,7 @@ namespace FoxTube.Pages
|
||||
subscribe.Background = new SolidColorBrush(Colors.Transparent);
|
||||
subscribe.Foreground = new SolidColorBrush(Colors.Gray);
|
||||
subscribe.Content = resources.GetString("/Cards/unsubscribe");
|
||||
collapsedBtn.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
subscriptionPane.Visibility = Visibility.Visible;
|
||||
}
|
||||
@@ -147,6 +126,8 @@ namespace FoxTube.Pages
|
||||
{ "Channel ID", channelId }
|
||||
});
|
||||
}
|
||||
|
||||
ScrollViewer_ViewChanged(this, null);
|
||||
}
|
||||
|
||||
async void LoadPlaylist()
|
||||
@@ -155,20 +136,12 @@ namespace FoxTube.Pages
|
||||
{
|
||||
playlistLoading.Refresh();
|
||||
|
||||
playlistRequest = SecretsVault.Service.Search.List("id");
|
||||
playlistRequest.ChannelId = channelId;
|
||||
playlistRequest.Order = SearchResource.ListRequest.OrderEnum.Date;
|
||||
playlistRequest.Type = "playlist";
|
||||
playlistRequest.MaxResults = 48;
|
||||
request.Type = "playlist";
|
||||
|
||||
SearchListResponse response = await playlistRequest.ExecuteAsync();
|
||||
SearchListResponse response = await request.ExecuteAsync();
|
||||
|
||||
playlistList.Clear();
|
||||
foreach (SearchResult i in response.Items)
|
||||
{
|
||||
PlaylistCard card = new PlaylistCard(i.Id.PlaylistId);
|
||||
playlistList.Add(card);
|
||||
}
|
||||
playlistList.Add(new PlaylistCard(i.Id.PlaylistId));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(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);
|
||||
}
|
||||
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)
|
||||
LoadPlaylist();
|
||||
|
||||
if (content.SelectedIndex == 0)
|
||||
ScrollViewer_ViewChanged(this, null);
|
||||
else
|
||||
ColapsedHeader.Opacity = 1;
|
||||
}
|
||||
|
||||
private async void ShowMorePlaylists_Click()
|
||||
{
|
||||
playlistRequest.PageToken = playlistToken;
|
||||
SearchListResponse response = await playlistRequest.ExecuteAsync();
|
||||
request.Type = "playlist";
|
||||
request.PageToken = playlistToken;
|
||||
SearchListResponse response = await request.ExecuteAsync();
|
||||
|
||||
foreach (SearchResult i in response.Items)
|
||||
{
|
||||
PlaylistCard card = new PlaylistCard(i.Id.PlaylistId);
|
||||
playlistList.Add(card);
|
||||
}
|
||||
playlistList.Add(new PlaylistCard(i.Id.PlaylistId));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(response.NextPageToken))
|
||||
if (string.IsNullOrWhiteSpace(response.NextPageToken))
|
||||
playlistMore.Visibility = Visibility.Collapsed;
|
||||
else
|
||||
{
|
||||
playlistToken = response.NextPageToken;
|
||||
playlistMore.Complete();
|
||||
}
|
||||
else
|
||||
playlistMore.Complete(true);
|
||||
}
|
||||
|
||||
private async void VideoMore_Clicked()
|
||||
{
|
||||
videoRequest.PageToken = videoToken;
|
||||
SearchListResponse response = await videoRequest.ExecuteAsync();
|
||||
request.Type = "video";
|
||||
request.PageToken = videoToken;
|
||||
SearchListResponse response = await request.ExecuteAsync();
|
||||
|
||||
foreach (SearchResult i in response.Items)
|
||||
{
|
||||
VideoCard card = new VideoCard(i.Id.VideoId);
|
||||
videoList.Add(card);
|
||||
}
|
||||
videoList.Add(new VideoCard(i.Id.VideoId));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(response.NextPageToken))
|
||||
if (string.IsNullOrWhiteSpace(response.NextPageToken))
|
||||
videoMore.Visibility = Visibility.Collapsed;
|
||||
else
|
||||
{
|
||||
videoToken = response.NextPageToken;
|
||||
videoMore.Complete();
|
||||
}
|
||||
else
|
||||
videoMore.Complete(true);
|
||||
}
|
||||
|
||||
private async void Subscribe_Click(object sender, RoutedEventArgs e)
|
||||
@@ -241,12 +221,14 @@ namespace FoxTube.Pages
|
||||
subscribe.Background = new SolidColorBrush(Colors.Transparent);
|
||||
subscribe.Foreground = new SolidColorBrush(Colors.Gray);
|
||||
subscribe.Content = resources.GetString("/Cards/unsubscribe");
|
||||
collapsedBtn.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
subscribe.Background = new SolidColorBrush(Colors.Red);
|
||||
subscribe.Foreground = new SolidColorBrush(Colors.White);
|
||||
subscribe.Content = resources.GetString("/Cards/subscribe/Content");
|
||||
collapsedBtn.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,21 +241,29 @@ namespace FoxTube.Pages
|
||||
{
|
||||
if(search.Text.Length > 2)
|
||||
{
|
||||
if (content.Items.Count == 4)
|
||||
((content.Items[3] as PivotItem).Content as Frame).Navigate(typeof(Search), new SearchParameters(search.Text, item.Id));
|
||||
else
|
||||
{
|
||||
if(content.Items.Count < 4)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Methods.MainPage.GoToChannel(channelId);
|
||||
@@ -281,9 +271,6 @@ namespace FoxTube.Pages
|
||||
|
||||
private async void InBrowser_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(item.Snippet.CustomUrl))
|
||||
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}"));
|
||||
}
|
||||
|
||||
@@ -292,6 +279,17 @@ namespace FoxTube.Pages
|
||||
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)
|
||||
{
|
||||
Methods.Share(args,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
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:controls="using:FoxTube.Controls"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
@@ -43,9 +44,14 @@
|
||||
|
||||
<ScrollViewer Grid.Row="1" Name="scroll">
|
||||
<StackPanel>
|
||||
<StackPanel Name="placeholder"/>
|
||||
<HyperlinkButton x:Uid="/CommentsPage/more" Visibility="Collapsed" Name="more" Click="more_Click" HorizontalAlignment="Center" Foreground="Red" Content="Show more"/>
|
||||
<ProgressBar Name="moreLoading" Visibility="Collapsed" IsIndeterminate="True" Foreground="Red"/>
|
||||
<StackPanel Name="placeholder">
|
||||
<StackPanel.ChildrenTransitions>
|
||||
<TransitionCollection>
|
||||
<EntranceThemeTransition IsStaggeringEnabled="True"/>
|
||||
</TransitionCollection>
|
||||
</StackPanel.ChildrenTransitions>
|
||||
</StackPanel>
|
||||
<controls:ShowMore x:Name="more" Clicked="ShowMore_Clicked"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
@@ -17,9 +17,10 @@ namespace FoxTube.Pages
|
||||
ResourceLoader resources = ResourceLoader.GetForCurrentView("CommentsPage");
|
||||
|
||||
string threadId;
|
||||
string nextPageToken;
|
||||
string token;
|
||||
|
||||
CommentThreadsResource.ListRequest.OrderEnum order = CommentThreadsResource.ListRequest.OrderEnum.Relevance;
|
||||
CommentThreadsResource.ListRequest request;
|
||||
|
||||
public CommentsPage()
|
||||
{
|
||||
@@ -33,144 +34,134 @@ namespace FoxTube.Pages
|
||||
|
||||
if (!SecretsVault.IsAuthorized)
|
||||
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")}";
|
||||
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.VideoId = video.Id;
|
||||
request.TextFormat = CommentThreadsResource.ListRequest.TextFormatEnum.PlainText;
|
||||
|
||||
var response = await request.ExecuteAsync();
|
||||
|
||||
if(response.NextPageToken != null)
|
||||
{
|
||||
nextPageToken = response.NextPageToken;
|
||||
more.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
foreach (CommentThread comment in response.Items)
|
||||
placeholder.Children.Add(new CommentCard(comment));
|
||||
}
|
||||
|
||||
public void RemoveComment(CommentCard commentCard)
|
||||
{
|
||||
placeholder.Children.Remove(commentCard);
|
||||
}
|
||||
|
||||
private async void more_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
token = response.NextPageToken;
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
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;
|
||||
|
||||
public void RemoveComment(CommentCard commentCard, string topCommentId = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(topCommentId))
|
||||
placeholder.Children.Remove(commentCard);
|
||||
else
|
||||
(placeholder.Children.Find(i => (i as CommentCard).thread.Id == topCommentId) as CommentCard).DeleteComment(commentCard);
|
||||
}
|
||||
|
||||
private async void toRelevance_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(order != CommentThreadsResource.ListRequest.OrderEnum.Relevance)
|
||||
{
|
||||
more.Visibility = Visibility.Collapsed;
|
||||
moreLoading.Visibility = Visibility.Visible;
|
||||
if (order == CommentThreadsResource.ListRequest.OrderEnum.Relevance)
|
||||
return;
|
||||
|
||||
more.Show();
|
||||
|
||||
order = CommentThreadsResource.ListRequest.OrderEnum.Relevance;
|
||||
orderBtn.Content = resources.GetString("/CommentsPage/relevance/Text");
|
||||
|
||||
placeholder.Children.Clear();
|
||||
|
||||
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;
|
||||
token = response.NextPageToken;
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
more.Visibility = Visibility.Collapsed;
|
||||
|
||||
foreach (CommentThread comment in response.Items)
|
||||
placeholder.Children.Add(new CommentCard(comment));
|
||||
|
||||
more.Visibility = Visibility.Visible;
|
||||
moreLoading.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
more.Complete();
|
||||
}
|
||||
|
||||
private async void toDate_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (order != CommentThreadsResource.ListRequest.OrderEnum.Time)
|
||||
{
|
||||
more.Visibility = Visibility.Collapsed;
|
||||
moreLoading.Visibility = Visibility.Visible;
|
||||
if (order == CommentThreadsResource.ListRequest.OrderEnum.Time)
|
||||
return;
|
||||
|
||||
more.Show();
|
||||
|
||||
order = CommentThreadsResource.ListRequest.OrderEnum.Time;
|
||||
orderBtn.Content = resources.GetString("/CommentsPage/publish");
|
||||
|
||||
placeholder.Children.Clear();
|
||||
|
||||
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;
|
||||
token = response.NextPageToken;
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
more.Visibility = Visibility.Collapsed;
|
||||
|
||||
foreach (CommentThread comment in response.Items)
|
||||
placeholder.Children.Add(new CommentCard(comment));
|
||||
|
||||
more.Visibility = Visibility.Visible;
|
||||
moreLoading.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
more.Complete();
|
||||
}
|
||||
|
||||
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();
|
||||
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;
|
||||
CommentThread thread = new CommentThread()
|
||||
{
|
||||
Snippet = new CommentThreadSnippet()
|
||||
{
|
||||
TopLevelComment = new Comment()
|
||||
{
|
||||
Snippet = new CommentSnippet()
|
||||
{
|
||||
TextOriginal = newComment.Text
|
||||
}
|
||||
},
|
||||
VideoId = threadId
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
CommentThread response = await SecretsVault.Service.CommentThreads.Insert(thread, "snippet").ExecuteAsync();
|
||||
newComment.Text = "";
|
||||
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.", "Failed to publish your comment").ShowAsync();
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,20 @@
|
||||
<Button Grid.Column="1" x:Uid="/Downloads/openFolder" Content="Open folder" Name="open" Click="Open_Click" VerticalAlignment="Center"/>
|
||||
</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"/>
|
||||
|
||||
<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"/>
|
||||
</CommandBar>
|
||||
</Grid>
|
||||
|
||||
@@ -1,59 +1,42 @@
|
||||
using System;
|
||||
using FoxTube.Controls;
|
||||
using System;
|
||||
using Windows.System;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
|
||||
namespace FoxTube.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Downloads page
|
||||
/// </summary>
|
||||
public sealed partial class Downloads : Page
|
||||
public sealed partial class Downloads : Page, NavigationPage
|
||||
{
|
||||
public object Parameter { get; set; } = null;
|
||||
public Downloads()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
SetPath();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
InitializeComponent();
|
||||
DownloadAgent.Page = this;
|
||||
path.Text = DownloadAgent.Downloads.Path;
|
||||
}
|
||||
|
||||
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;
|
||||
Refresh(this, null);
|
||||
}
|
||||
|
||||
private async void Open_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<Page
|
||||
NavigationCacheMode="Enabled"
|
||||
x:Class="FoxTube.Pages.History"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
@@ -7,21 +6,25 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:foxtube="using:FoxTube"
|
||||
xmlns:controls="using:FoxTube.Controls"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid Name="grid">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollViewer Name="scroll">
|
||||
<local:VideoGrid/>
|
||||
<ScrollViewer>
|
||||
<StackPanel>
|
||||
<local:VideoGrid x:Name="list"/>
|
||||
<controls:ShowMore Clicked="ShowMore_Clicked" x:Name="more"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<CommandBar Grid.Row="1" DefaultLabelPosition="Right">
|
||||
<AppBarButton Icon="Refresh" Label="Refresh" Name="refresh" Click="Refresh_Click"/>
|
||||
<AppBarButton Label="Open in browser" Icon="Globe" Name="toBrowser" Click="toBrowser_Click"/>
|
||||
</CommandBar>
|
||||
<foxtube:LoadingPage Visibility="Collapsed" Grid.RowSpan="2"/>
|
||||
<foxtube:LoadingPage Visibility="Collapsed" Grid.RowSpan="2" x:Name="loading"/>
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using FoxTube.Controls;
|
||||
using Microsoft.AppCenter.Analytics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.System;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
@@ -9,10 +12,12 @@ namespace FoxTube.Pages
|
||||
/// <summary>
|
||||
/// YouTube history page
|
||||
/// </summary>
|
||||
public sealed partial class History : Page
|
||||
public sealed partial class History : Page, NavigationPage
|
||||
{
|
||||
LoadingPage loading;
|
||||
VideoGrid list;
|
||||
public object Parameter { get; set; } = null;
|
||||
List<string> entries;
|
||||
int page = 1;
|
||||
public string id = "HL";
|
||||
|
||||
public History()
|
||||
{
|
||||
@@ -22,27 +27,68 @@ namespace FoxTube.Pages
|
||||
protected override void OnNavigatedTo(NavigationEventArgs 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();
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
public async void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
loading.Refresh();
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,36 +9,69 @@
|
||||
xmlns:controls="using:FoxTube.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Name="grid">
|
||||
<Pivot SelectionChanged="pivot_SelectionChanged" Name="pivot">
|
||||
<PivotItem Name="recommended" Header="Recommended" x:Uid="/Home/recommended">
|
||||
<Grid>
|
||||
<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>
|
||||
<StackPanel>
|
||||
<pages:VideoGrid/>
|
||||
<controls:ShowMore Clicked="TrendMore_Clicked"/>
|
||||
<pages:VideoGrid x:Name="recGrid"/>
|
||||
<controls:ShowMore Clicked="Recommended_More" x:Name="recommendedMore"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</PivotItem>
|
||||
<PivotItem x:Uid="/Home/subs" Header="Subscriptions" Name="subscriptions">
|
||||
<ScrollViewer>
|
||||
<StackPanel/>
|
||||
</ScrollViewer>
|
||||
</PivotItem>
|
||||
</Pivot>
|
||||
|
||||
<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>
|
||||
<local:LoadingPage Grid.RowSpan="2" Margin="0,50,0,0" Visibility="Visible" RefreshPage="refreshPage"/>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -5,166 +5,219 @@ using Windows.UI.Xaml.Controls;
|
||||
using Google.Apis.YouTube.v3;
|
||||
using Google.Apis.YouTube.v3.Data;
|
||||
using FoxTube.Controls;
|
||||
using FoxTube.Pages;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AppCenter.Analytics;
|
||||
|
||||
namespace FoxTube
|
||||
{
|
||||
/// <summary>
|
||||
/// Home page
|
||||
/// </summary>
|
||||
public sealed partial class Home : Page
|
||||
public sealed partial class Home : Page, NavigationPage
|
||||
{
|
||||
// TODO: Refactor home page
|
||||
private bool trendLoaded = false;
|
||||
public object Parameter { get; set; } = null;
|
||||
private bool trendLoaded = false, recLoaded = false, subsLoaded = false;
|
||||
|
||||
VideoGrid trendGrid;
|
||||
ShowMore trendMore;
|
||||
LoadingPage loading;
|
||||
List<string> homeList = new List<string>();
|
||||
List<string> subsList = new List<string>();
|
||||
|
||||
string trendToken;
|
||||
Dictionary<string, string> subsTokens = new Dictionary<string, string>();
|
||||
VideosResource.ListRequest trendsRequest;
|
||||
|
||||
public Home()
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
/*if(SecretsVault.IsAuthorized)
|
||||
LoadRecommendations();
|
||||
else*/
|
||||
if(!SecretsVault.IsAuthorized)
|
||||
{
|
||||
pivot.Items.Remove(recommended);
|
||||
pivot.Items.Remove(subscriptions);
|
||||
|
||||
LoadTrending();
|
||||
}
|
||||
}
|
||||
|
||||
private void pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
loading.Close();
|
||||
if (pivot.SelectedItem == recommended)
|
||||
if (pivot.SelectedItem == recommended && !recLoaded)
|
||||
LoadRecommendations();
|
||||
else if (pivot.SelectedItem == trending && !trendLoaded)
|
||||
LoadTrending();
|
||||
else if (pivot.SelectedItem == subscriptions)
|
||||
else if (pivot.SelectedItem == subscriptions && !subsLoaded)
|
||||
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()
|
||||
{
|
||||
try
|
||||
{
|
||||
loading.Refresh();
|
||||
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("id");
|
||||
request.MaxResults = 48;
|
||||
trendsLoading.Refresh();
|
||||
|
||||
request.Chart = VideosResource.ListRequest.ChartEnum.MostPopular;
|
||||
request.RegionCode = SettingsStorage.Region;
|
||||
VideoListResponse response = await request.ExecuteAsync();
|
||||
trendsRequest = SecretsVault.Service.Videos.List("snippet,contentDetails,statistics,liveStreamingDetails");
|
||||
trendsRequest.MaxResults = 25;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(response.NextPageToken))
|
||||
trendToken = response.NextPageToken;
|
||||
else
|
||||
trendMore.Complete(true);
|
||||
trendsRequest.Chart = VideosResource.ListRequest.ChartEnum.MostPopular;
|
||||
trendsRequest.RegionCode = SettingsStorage.Region;
|
||||
|
||||
foreach (Video vid in response.Items)
|
||||
{
|
||||
VideoCard vCard = new VideoCard(vid.Id);
|
||||
trendGrid.Add(vCard);
|
||||
}
|
||||
VideoListResponse response = await trendsRequest.ExecuteAsync();
|
||||
|
||||
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;
|
||||
}
|
||||
catch (System.Net.Http.HttpRequestException)
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
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)
|
||||
{
|
||||
trendLoaded = false;
|
||||
loading.Error(e.GetType().ToString(), e.Message);
|
||||
}
|
||||
}
|
||||
trendsLoading.Error(e.GetType().ToString(), e.Message);
|
||||
|
||||
void LoadRecommendations()
|
||||
Analytics.TrackEvent("Failed to load trendings", new Dictionary<string, string>()
|
||||
{
|
||||
{ "Exception", e.GetType().ToString() },
|
||||
{ "Message", e.Message }
|
||||
});
|
||||
}
|
||||
}
|
||||
async void LoadSubscriptions()
|
||||
{
|
||||
try
|
||||
{
|
||||
loading.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);
|
||||
}
|
||||
}
|
||||
subsLoading.Refresh();
|
||||
|
||||
void LoadSubscriptions()
|
||||
{
|
||||
try
|
||||
{
|
||||
loading.Refresh();
|
||||
throw new NotImplementedException("This page has not implemented yet.");
|
||||
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");
|
||||
|
||||
foreach (Match match in Regex.Matches(response, @"\bdata-context-item-id=(\S*)\b", RegexOptions.IgnoreCase))
|
||||
subsList.Add(match.Value.Split('"')[1]);
|
||||
|
||||
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)
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
@@ -4,13 +4,58 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
|
||||
xmlns:Windows10version1803="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 6)"
|
||||
mc:Ignorable="d"
|
||||
SizeChanged="Page_SizeChanged"
|
||||
PreviewKeyUp="Page_PreviewKeyUp">
|
||||
mc:Ignorable="d">
|
||||
|
||||
<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>
|
||||
<NavigationViewItem x:Uid="/Main/home" Icon="Home" Content="Home" Name="toHome"/>
|
||||
@@ -30,8 +75,14 @@
|
||||
|
||||
<NavigationView.PaneFooter>
|
||||
<NavigationViewList>
|
||||
<NavigationViewList.ItemContainerTransitions>
|
||||
<TransitionCollection>
|
||||
<EntranceThemeTransition IsStaggeringEnabled="True"/>
|
||||
<ReorderThemeTransition/>
|
||||
</TransitionCollection>
|
||||
</NavigationViewList.ItemContainerTransitions>
|
||||
<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>
|
||||
<FontIcon Glyph=""/>
|
||||
</NavigationViewItem.Icon>
|
||||
@@ -43,18 +94,10 @@
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
|
||||
<NavigationViewItem Name="account" Tapped="OpenContext">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<FontIcon Glyph="" FontSize="16" Margin="0,0,16,0"/>
|
||||
<TextBlock x:Uid="/Main/signIn" Text="Add account"/>
|
||||
</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 Name="account" x:Uid="/Main/signIn" Content="Add account" Tapped="SignIn_Click" Visibility="Collapsed">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
|
||||
<NavigationViewItem Visibility="Collapsed" Name="avatar" Tapped="OpenContext" Padding="-5">
|
||||
@@ -64,11 +107,20 @@
|
||||
</StackPanel>
|
||||
|
||||
<NavigationViewItem.ContextFlyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem x:Uid="/Main/myChannelContext" Text="My channel" Name="myChannel" Click="MyChannel_Click"/>
|
||||
<MenuFlyoutSeparator/>
|
||||
<MenuFlyoutItem x:Uid="/Main/signOut" Text="Log out" Name="logout" Click="Logout_Click"/>
|
||||
</MenuFlyout>
|
||||
<Flyout>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<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>
|
||||
</NavigationViewList>
|
||||
|
||||
@@ -7,7 +7,6 @@ using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using System.Diagnostics;
|
||||
using Windows.UI.Notifications;
|
||||
using Windows.UI.Xaml.Media.Imaging;
|
||||
using System.Xml;
|
||||
using Google.Apis.YouTube.v3.Data;
|
||||
@@ -16,76 +15,111 @@ using Windows.System;
|
||||
using Google.Apis.Oauth2.v2;
|
||||
using Google.Apis.Oauth2.v2.Data;
|
||||
using FoxTube.Pages;
|
||||
using Windows.ApplicationModel;
|
||||
using System.Net;
|
||||
using Windows.UI.Popups;
|
||||
using Windows.Networking.Connectivity;
|
||||
using Windows.UI.Core;
|
||||
using Windows.ApplicationModel.Resources;
|
||||
using Windows.Storage;
|
||||
using System.IO;
|
||||
using Microsoft.Services.Store.Engagement;
|
||||
using System.Linq;
|
||||
|
||||
namespace FoxTube
|
||||
{
|
||||
public enum Sender { Menu, Frame, None }
|
||||
|
||||
/// <summary>
|
||||
/// Main app's layout
|
||||
/// </summary>
|
||||
public sealed partial class MainPage : Page
|
||||
{
|
||||
// TODO: Refactor main page
|
||||
Sender s = Sender.None;
|
||||
bool wasInvoked = false;
|
||||
readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Main");
|
||||
Dictionary<Type, Action> headers;
|
||||
public MainPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
CheckVersion();
|
||||
Window.Current.SetTitleBar(AppTitleBar);
|
||||
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true;
|
||||
CoreApplication.GetCurrentView().TitleBar.LayoutMetricsChanged += (s, e) => SetTitleBar(s);
|
||||
|
||||
SecretsVault.AuthorizationStateChanged += AuthorizationStateChanged;
|
||||
SecretsVault.SubscriptionsChanged += SecretsVault_SubscriptionsChanged;
|
||||
SecretsVault.Purchased += (sender, e) =>
|
||||
SecretsVault.Purchased += async (sender, e) =>
|
||||
{
|
||||
//TODO: Localize strings
|
||||
removeAds.Visibility = (e[0] as bool?).Value ? Visibility.Collapsed : Visibility.Visible;
|
||||
content.Navigate(typeof(Home));
|
||||
MessageDialog dialog = new MessageDialog("Thanks for purchasing full version of the app (^∇^) In order to complete changes we need to reopen it. But you can do it later");
|
||||
dialog.Commands.Add(new UICommand("Close the app", (command) => Methods.CloseApp()));
|
||||
dialog.Commands.Add(new UICommand("Later"));
|
||||
dialog.CancelCommandIndex = 1;
|
||||
dialog.DefaultCommandIndex = 0;
|
||||
await dialog.ShowAsync();
|
||||
};
|
||||
SecretsVault.CheckAuthorization();
|
||||
SecretsVault.CheckAddons();
|
||||
SecretsVault.Initialize();
|
||||
|
||||
SetTitleBar();
|
||||
headers = new Dictionary<Type, Action>()
|
||||
{
|
||||
{ typeof(Settings), () => Title.Text = resources.GetString("/Main/settings/Content") },
|
||||
{ typeof(ChannelPage), () => Title.Text = resources.GetString("/Main/channel") },
|
||||
{ typeof(PlaylistPage), () => Title.Text = resources.GetString("/Main/playlist") },
|
||||
{ typeof(Search), () => Title.Text = resources.GetString("/Main/searchPlaceholder/PlaceholderText") },
|
||||
{ typeof(Subscriptions), () => Title.Text = resources.GetString("/Main/subscriptions/Content") },
|
||||
{ typeof(History), () =>
|
||||
{
|
||||
if((content.Content as History).id == "HL")
|
||||
Title.Text = resources.GetString("/Main/history/Content");
|
||||
else
|
||||
Title.Text = resources.GetString("/Main/later/Content");
|
||||
} },
|
||||
{ typeof(Home), () => Title.Text = resources.GetString("/Main/home/Content") },
|
||||
{ typeof(Downloads), () => Title.Text = resources.GetString("/Main/downloads/Content") }
|
||||
};
|
||||
|
||||
if(StoreServicesFeedbackLauncher.IsSupported())
|
||||
feedback.Visibility = Visibility.Visible;
|
||||
|
||||
PromptFeedback();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Comparing current version with last recorded version. If doesn't match, poping up changelog notification
|
||||
/// </summary>
|
||||
public async void CheckVersion()
|
||||
async void PromptFeedback()
|
||||
{
|
||||
PackageVersion ver = Package.Current.Id.Version;
|
||||
if (SettingsStorage.Version != $"{ver.Major}.{ver.Minor}")
|
||||
//TODO: Localize strings
|
||||
if (SettingsStorage.Uptime.TotalHours >= 12 && SettingsStorage.PromptFeedback)
|
||||
{
|
||||
try
|
||||
MessageDialog dialog = new MessageDialog("Have some thoughts to share about the app or any suggestions? Leave feedback!");
|
||||
dialog.Commands.Add(new UICommand("Don't ask me anymore", (command) => SettingsStorage.PromptFeedback = false));
|
||||
dialog.Commands.Add(new UICommand("Maybe later"));
|
||||
dialog.Commands.Add(new UICommand("Sure!", async (command) =>
|
||||
{
|
||||
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(FoxTube.Background.Notification.GetChangelogToast(e.GetAttribute("version")));
|
||||
|
||||
SettingsStorage.Version = $"{ver.Major}.{ver.Minor}";
|
||||
}
|
||||
catch
|
||||
{
|
||||
Debug.WriteLine("Unable to retrieve changelog");
|
||||
}
|
||||
SettingsStorage.PromptFeedback = false;
|
||||
if (StoreServicesFeedbackLauncher.IsSupported())
|
||||
await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync();
|
||||
else
|
||||
{
|
||||
MessageDialog message = new MessageDialog("Oops. Seems like you don't have a Feedback Hub app. But you can stil send your feedback to michael.xfox@outlook.com");
|
||||
message.Commands.Add(new UICommand("Send an E-mail", async (c) => await Launcher.LaunchUriAsync("mailto:michael.xfox@outlook.com".ToUri())));
|
||||
message.Commands.Add(new UICommand("Nevermind. Get me back"));
|
||||
message.CancelCommandIndex = 1;
|
||||
message.DefaultCommandIndex = 0;
|
||||
await message.ShowAsync();
|
||||
}
|
||||
}));
|
||||
dialog.DefaultCommandIndex = 2;
|
||||
dialog.CancelCommandIndex = 1;
|
||||
await dialog.ShowAsync();
|
||||
}
|
||||
|
||||
public Video GetCurrentItem()
|
||||
if (SettingsStorage.Uptime.TotalHours >= 24 && SettingsStorage.PromptReview)
|
||||
{
|
||||
try { return (videoPlaceholder.Content as VideoPage).item; }
|
||||
catch { return null; }
|
||||
MessageDialog dialog = new MessageDialog("Like our app? Review it on Microsoft Store!");
|
||||
dialog.Commands.Add(new UICommand("Don't ask me anymore", (command) => SettingsStorage.PromptReview = false));
|
||||
dialog.Commands.Add(new UICommand("Maybe later"));
|
||||
dialog.Commands.Add(new UICommand("Sure!", async (command) =>
|
||||
{
|
||||
SettingsStorage.PromptReview = false;
|
||||
await Launcher.LaunchUriAsync("ms-windows-store://review/?ProductId=9NCQQXJTDLFH".ToUri());
|
||||
}));
|
||||
dialog.DefaultCommandIndex = 2;
|
||||
dialog.CancelCommandIndex = 1;
|
||||
await dialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public string GetPlaylist()
|
||||
@@ -94,133 +128,78 @@ namespace FoxTube
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
public void SetTitleBar()
|
||||
public void SetTitleBar(CoreApplicationViewTitleBar coreTitleBar = null)
|
||||
{
|
||||
if (coreTitleBar != null)
|
||||
AppTitleBar.Height = coreTitleBar.Height;
|
||||
|
||||
var titleBar = ApplicationView.GetForCurrentView().TitleBar;
|
||||
|
||||
titleBar.BackgroundColor = Colors.Red;
|
||||
titleBar.ButtonBackgroundColor = Colors.Red;
|
||||
titleBar.ButtonBackgroundColor = Colors.Transparent;
|
||||
titleBar.ButtonHoverBackgroundColor = Colors.IndianRed;
|
||||
titleBar.ButtonPressedBackgroundColor = Colors.DarkRed;
|
||||
titleBar.ButtonInactiveBackgroundColor = Colors.Black;
|
||||
titleBar.ForegroundColor = Colors.White;
|
||||
titleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
|
||||
titleBar.ButtonInactiveForegroundColor = Colors.Gray;
|
||||
|
||||
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = false;
|
||||
if(RequestedTheme == ElementTheme.Dark || (RequestedTheme == ElementTheme.Default && Application.Current.RequestedTheme == ApplicationTheme.Dark))
|
||||
titleBar.ButtonForegroundColor = Colors.White;
|
||||
else
|
||||
titleBar.ButtonForegroundColor = Colors.Black;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when subscriptions are edited to edit menu
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
private void SecretsVault_SubscriptionsChanged(object sender, params object[] args)
|
||||
{
|
||||
if ((string)args[0] == "add" && nav.MenuItems.Count < 19)
|
||||
switch(args[0] as string)
|
||||
{
|
||||
subsHeader.Visibility = Visibility.Visible;
|
||||
case "add":
|
||||
if (nav.MenuItems.Count < 19)
|
||||
nav.MenuItems.Add(args[1] as Subscription);
|
||||
break;
|
||||
|
||||
case "remove":
|
||||
NavigationViewItem item = nav.MenuItems.Find(i => ((i as NavigationViewItem).Content as StackPanel).Tag.ToString() == (args[1] as Subscription).Snippet.ResourceId.ChannelId) as NavigationViewItem;
|
||||
if (item == null)
|
||||
break;
|
||||
else
|
||||
nav.MenuItems.Remove(item);
|
||||
|
||||
Subscription s = args[1] as Subscription;
|
||||
StackPanel panel = new StackPanel()
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
Padding = new Thickness(5)
|
||||
};
|
||||
panel.Children.Add(new PersonPicture()
|
||||
{
|
||||
Height = 20,
|
||||
Margin = new Thickness(-5, 0, 15, 0),
|
||||
ProfilePicture = new BitmapImage(new Uri(s.Snippet.Thumbnails.Medium.Url))
|
||||
});
|
||||
panel.Children.Add(new TextBlock() { Text = s.Snippet.Title });
|
||||
nav.MenuItems.Add(new NavigationViewItem()
|
||||
{
|
||||
Content = panel,
|
||||
Name = (nav.MenuItems.Count - 9).ToString(),
|
||||
Padding = new Thickness(-5)
|
||||
});
|
||||
}
|
||||
else if ((string)args[0] == "remove" && (int)args[1] < 10)
|
||||
{
|
||||
nav.MenuItems.RemoveAt((int)args[1] + 9);
|
||||
if (SecretsVault.Subscriptions.Count >= 10)
|
||||
{
|
||||
Subscription s = SecretsVault.Subscriptions[9];
|
||||
StackPanel panel = new StackPanel()
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
Padding = new Thickness(5)
|
||||
};
|
||||
panel.Children.Add(new PersonPicture()
|
||||
{
|
||||
Height = 20,
|
||||
Margin = new Thickness(-5, 0, 15, 0),
|
||||
ProfilePicture = new BitmapImage(new Uri(s.Snippet.Thumbnails.Medium.Url))
|
||||
});
|
||||
panel.Children.Add(new TextBlock() { Text = s.Snippet.Title });
|
||||
nav.MenuItems.Add(new NavigationViewItem()
|
||||
{
|
||||
Content = panel,
|
||||
Name = (nav.MenuItems.Count - 9).ToString(),
|
||||
Padding = new Thickness(-5)
|
||||
});
|
||||
nav.MenuItems.Add(SecretsVault.Subscriptions[9]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async void AuthorizationStateChanged(object sender, params object[] e)
|
||||
{
|
||||
if(e[0] as bool? == true)
|
||||
switch(e[0] as bool?)
|
||||
{
|
||||
case true:
|
||||
account.Visibility = Visibility.Collapsed;
|
||||
try
|
||||
{
|
||||
Userinfoplus info = await new Oauth2Service(SecretsVault.Initializer).Userinfo.Get().ExecuteAsync();
|
||||
|
||||
myName.Text = info.Name;
|
||||
((avatar.Content as StackPanel).Children[0] as PersonPicture).ProfilePicture = new BitmapImage(new Uri(info.Picture));
|
||||
}
|
||||
catch { }
|
||||
myName.Text = myNameFlyout.Text = SecretsVault.UserInfo.Name;
|
||||
myEmail.Text = SecretsVault.UserInfo.Email;
|
||||
avatarFlyout.ProfilePicture = new BitmapImage(SecretsVault.UserInfo.Picture.ToUri());
|
||||
((avatar.Content as StackPanel).Children[0] as PersonPicture).ProfilePicture = avatarFlyout.ProfilePicture;
|
||||
|
||||
avatar.Visibility = Visibility.Visible;
|
||||
|
||||
if(SecretsVault.UserChannel != null)
|
||||
toChannel.Visibility = Visibility.Visible;
|
||||
toSubscriptions.Visibility = Visibility.Visible;
|
||||
libHeader.Visibility = Visibility.Visible;
|
||||
//toHistory.Visibility = Visibility.Visible;
|
||||
toHistory.Visibility = Visibility.Visible;
|
||||
toLiked.Visibility = Visibility.Visible;
|
||||
//toLater.Visibility = Visibility.Visible;
|
||||
toLater.Visibility = Visibility.Visible;
|
||||
|
||||
if (SecretsVault.Subscriptions.Count > 0)
|
||||
{
|
||||
subsHeader.Visibility = Visibility.Visible;
|
||||
for (int k = 0; k < SecretsVault.Subscriptions.Count && k < 10; k++)
|
||||
try
|
||||
{
|
||||
Subscription s = SecretsVault.Subscriptions[k];
|
||||
StackPanel panel = new StackPanel()
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
Padding = new Thickness(5)
|
||||
};
|
||||
panel.Children.Add(new PersonPicture()
|
||||
{
|
||||
Height = 20,
|
||||
Margin = new Thickness(-5, 0, 15, 0),
|
||||
ProfilePicture = new BitmapImage(new Uri(s.Snippet.Thumbnails.Medium.Url))
|
||||
});
|
||||
panel.Children.Add(new TextBlock() { Text = s.Snippet.Title });
|
||||
nav.MenuItems.Add(new NavigationViewItem()
|
||||
{
|
||||
Content = panel,
|
||||
Name = k.ToString(),
|
||||
Padding = new Thickness(-5)
|
||||
});
|
||||
nav.MenuItems.Add(SecretsVault.Subscriptions[k]);
|
||||
}
|
||||
catch { continue; }
|
||||
}
|
||||
}
|
||||
else if (e[0] as bool? == false)
|
||||
{
|
||||
break;
|
||||
|
||||
case false:
|
||||
for (int k = nav.MenuItems.Count - 1; k > 8; k--)
|
||||
nav.MenuItems.RemoveAt(k);
|
||||
|
||||
@@ -230,15 +209,15 @@ namespace FoxTube
|
||||
toChannel.Visibility = Visibility.Collapsed;
|
||||
toSubscriptions.Visibility = Visibility.Collapsed;
|
||||
libHeader.Visibility = Visibility.Collapsed;
|
||||
//toHistory.Visibility = Visibility.Collapsed;
|
||||
toHistory.Visibility = Visibility.Collapsed;
|
||||
toLiked.Visibility = Visibility.Collapsed;
|
||||
//toLater.Visibility = Visibility.Collapsed;
|
||||
toLater.Visibility = Visibility.Collapsed;
|
||||
subsHeader.Visibility = Visibility.Collapsed;
|
||||
|
||||
subsHeader.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
|
||||
default:
|
||||
MessageDialog dialog = new MessageDialog(resources.GetString("/Main/connectErrContent"), resources.GetString("/Main/connectErrHeader"));
|
||||
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/Main/tryAgain"), (command) =>
|
||||
@@ -254,37 +233,32 @@ namespace FoxTube
|
||||
dialog.DefaultCommandIndex = 0;
|
||||
|
||||
await dialog.ShowAsync();
|
||||
break;
|
||||
}
|
||||
|
||||
if(e[0] as bool? != null)
|
||||
DownloadAgent.Initialize();
|
||||
await Dispatcher.RunIdleAsync((command) => DownloadAgent.Initialize());
|
||||
|
||||
wasInvoked = false;
|
||||
|
||||
if (content.Content != null)
|
||||
content.Navigate(content.CurrentSourcePageType, (content.Content as NavigationPage).Parameter);
|
||||
else
|
||||
content.Navigate(typeof(Home));
|
||||
|
||||
if (videoPlaceholder.Content != null)
|
||||
GoToVideo((videoPlaceholder.Content as VideoPage).videoId);
|
||||
GoToVideo((videoPlaceholder.Content as VideoPage).videoId, (videoPlaceholder.Content as VideoPage).playlistId);
|
||||
}
|
||||
|
||||
private async void Feedback_Click(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
await Launcher.LaunchUriAsync(new Uri("feedback-hub:"));
|
||||
await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync();
|
||||
}
|
||||
|
||||
private async void CreateAccount_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await Launcher.LaunchUriAsync(new Uri("https://accounts.google.com/signup/v2/webcreateaccount?ManageAccount&flowName=GlifWebSignIn&flowEntry=SignUp"));
|
||||
}
|
||||
|
||||
private void SignIn_Click(object sender, RoutedEventArgs e)
|
||||
private void SignIn_Click(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
SecretsVault.Authorize();
|
||||
}
|
||||
|
||||
private void MyChannel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
GoToChannel(SecretsVault.AccountId);
|
||||
}
|
||||
|
||||
private void Logout_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SecretsVault.Deauthenticate();
|
||||
@@ -308,11 +282,12 @@ namespace FoxTube
|
||||
|
||||
public async void GoToVideo(string id, string playlistId = null)
|
||||
{
|
||||
bool cancel = false;
|
||||
if (SettingsStorage.CheckConnection)
|
||||
try
|
||||
{
|
||||
var connection = NetworkInformation.GetInternetConnectionProfile().GetConnectionCost();
|
||||
if (SettingsStorage.CheckConnection && (connection.NetworkCostType == NetworkCostType.Fixed || connection.NetworkCostType == NetworkCostType.Variable))
|
||||
bool cancel = false;
|
||||
ConnectionCost connection = NetworkInformation.GetInternetConnectionProfile().GetConnectionCost();
|
||||
if (connection.NetworkCostType == NetworkCostType.Fixed || connection.NetworkCostType == NetworkCostType.Variable)
|
||||
{
|
||||
MessageDialog dialog = new MessageDialog(resources.GetString("/Main/metered"))
|
||||
{
|
||||
@@ -349,25 +324,26 @@ namespace FoxTube
|
||||
}));
|
||||
|
||||
await dialog.ShowAsync();
|
||||
|
||||
if (cancel)
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
if (cancel)
|
||||
return;
|
||||
|
||||
if (videoPlaceholder.Content != null)
|
||||
(videoPlaceholder.Content as VideoPage).player.close_Click(this, null);
|
||||
(videoPlaceholder.Content as VideoPage).CloseVideo();
|
||||
|
||||
videoPlaceholder.Content = null;
|
||||
Fullscreen(false);
|
||||
nav.IsBackEnabled = true;
|
||||
nav.ExpandedModeThresholdWidth = short.MaxValue;
|
||||
nav.IsPaneOpen = false;
|
||||
|
||||
videoPlaceholder.Navigate(typeof(VideoPage), new string[2] { id, playlistId });
|
||||
MaximizeVideo();
|
||||
}
|
||||
|
||||
public void GoToDeveloper(string id)
|
||||
{
|
||||
content.Navigate(typeof(Settings), $"inbox&{id}");
|
||||
content.Navigate(typeof(Settings), id);
|
||||
}
|
||||
|
||||
public void GoToPlaylist(string id)
|
||||
@@ -380,25 +356,19 @@ namespace FoxTube
|
||||
content.Navigate(typeof(Downloads));
|
||||
}
|
||||
|
||||
private void Page_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
if (videoPlaceholder.Content != null)
|
||||
(videoPlaceholder.Content as VideoPage).player.UpdateSize();
|
||||
}
|
||||
|
||||
public void MinimizeAsInitializer()
|
||||
{
|
||||
if(videoPlaceholder.Content != null)
|
||||
{
|
||||
if ((videoPlaceholder.Content as VideoPage).loading.State != LoadingState.Loaded)
|
||||
if (videoPlaceholder.Content == null)
|
||||
return;
|
||||
|
||||
if ((videoPlaceholder.Content as VideoPage).LoadingPage.State != LoadingState.Loaded)
|
||||
CloseVideo();
|
||||
else
|
||||
{
|
||||
try { (videoPlaceholder.Content as VideoPage).player.minimize_Click(this, null); }
|
||||
(videoPlaceholder.Content as VideoPage).Player.Minimize();
|
||||
|
||||
try { headers[content.SourcePageType](); }
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void MinimizeVideo()
|
||||
{
|
||||
@@ -413,29 +383,21 @@ namespace FoxTube
|
||||
else
|
||||
nav.IsBackEnabled = false;
|
||||
|
||||
Dictionary<Type, Action> switchCase = new Dictionary<Type, Action>()
|
||||
{
|
||||
{ typeof(Settings), () => nav.Header = resources.GetString("/Main/settings/Content") },
|
||||
{ typeof(ChannelPage), () => nav.Header = resources.GetString("/Main/channel") },
|
||||
{ typeof(PlaylistPage), () => nav.Header = resources.GetString("/Main/playlist") },
|
||||
{ typeof(Search), () => nav.Header = resources.GetString("/Main/searchPlaceholder/PlaceholderText") },
|
||||
{ typeof(Subscriptions), () => nav.Header = resources.GetString("/Main/subscriptions/Content") },
|
||||
{ typeof(History), () => nav.Header = resources.GetString("/Main/history/Content") },
|
||||
{ typeof(Home), () => nav.Header = resources.GetString("/Main/home/Content") },
|
||||
{ typeof(Downloads), () => nav.Header = resources.GetString("/Main/downloads/Content") }
|
||||
};
|
||||
SetNavigationMenu();
|
||||
|
||||
try { headers[content.SourcePageType](); }
|
||||
catch { }
|
||||
}
|
||||
|
||||
void SetNavigationMenu()
|
||||
{
|
||||
if (content.SourcePageType == typeof(Home) || content.SourcePageType == typeof(Settings) || content.SourcePageType == typeof(Subscriptions))
|
||||
{
|
||||
nav.ExpandedModeThresholdWidth = 1008;
|
||||
if (nav.DisplayMode == NavigationViewDisplayMode.Expanded)
|
||||
nav.IsPaneOpen = true;
|
||||
nav.IsPaneOpen = nav.DisplayMode == NavigationViewDisplayMode.Expanded ? true : false;
|
||||
}
|
||||
else
|
||||
nav.ExpandedModeThresholdWidth = short.MaxValue;
|
||||
|
||||
try { switchCase[content.SourcePageType](); }
|
||||
catch { }
|
||||
}
|
||||
|
||||
public void MaximizeVideo()
|
||||
@@ -446,71 +408,30 @@ namespace FoxTube
|
||||
videoPlaceholder.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||
videoPlaceholder.Margin = new Thickness(0);
|
||||
|
||||
nav.IsBackEnabled = true;
|
||||
|
||||
if (videoPlaceholder.Content != null)
|
||||
{
|
||||
nav.Header = resources.GetString("/Main/video");
|
||||
nav.ExpandedModeThresholdWidth = short.MaxValue;
|
||||
nav.IsPaneOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Fullscreen(bool on)
|
||||
{
|
||||
if (on)
|
||||
{
|
||||
nav.CompactModeThresholdWidth = short.MaxValue;
|
||||
nav.ExpandedModeThresholdWidth = short.MaxValue;
|
||||
try
|
||||
{
|
||||
nav.IsPaneVisible = false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
nav.CompactPaneLength = 0;
|
||||
nav.OpenPaneLength = 0;
|
||||
}
|
||||
nav.Margin = new Thickness(0, -45, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
nav.CompactModeThresholdWidth = 641;
|
||||
if (videoPlaceholder.Content == null)
|
||||
nav.ExpandedModeThresholdWidth = 1008;
|
||||
nav.Margin = new Thickness(0);
|
||||
try
|
||||
{
|
||||
nav.IsPaneVisible = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
nav.CompactPaneLength = new NavigationView().CompactPaneLength;
|
||||
nav.OpenPaneLength = new NavigationView().OpenPaneLength; ;
|
||||
}
|
||||
if (videoPlaceholder.Content != null && nav.IsPaneOpen)
|
||||
return;
|
||||
|
||||
nav.IsBackEnabled = true;
|
||||
Title.Text = resources.GetString("/Main/video");
|
||||
nav.ExpandedModeThresholdWidth = short.MaxValue;
|
||||
nav.IsPaneOpen = false;
|
||||
SetTitleBar();
|
||||
}
|
||||
nav.UpdateLayout();
|
||||
}
|
||||
|
||||
public void CloseVideo()
|
||||
{
|
||||
if (ApplicationView.GetForCurrentView().IsFullScreenMode)
|
||||
{
|
||||
ApplicationView.GetForCurrentView().ExitFullScreenMode();
|
||||
Fullscreen(false);
|
||||
}
|
||||
|
||||
videoPlaceholder.Content = null;
|
||||
GC.Collect();
|
||||
Window.Current.CoreWindow.PointerCursor = new CoreCursor(CoreCursorType.Arrow, 0);
|
||||
MaximizeVideo();
|
||||
|
||||
if (content.CanGoBack)
|
||||
nav.IsBackEnabled = true;
|
||||
else
|
||||
nav.IsBackEnabled = false;
|
||||
|
||||
SetNavigationMenu();
|
||||
}
|
||||
|
||||
private void Search_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
||||
@@ -521,6 +442,7 @@ namespace FoxTube
|
||||
|
||||
private void Search_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
|
||||
{
|
||||
//TODO: Make it run async
|
||||
if (search.Text.Length > 2 && args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
|
||||
{
|
||||
try
|
||||
@@ -535,229 +457,85 @@ namespace FoxTube
|
||||
|
||||
search.ItemsSource = suggestions;
|
||||
}
|
||||
catch (WebException)
|
||||
{
|
||||
search.ItemsSource = new List<string>();
|
||||
}
|
||||
catch { search.ItemsSource = new List<string>(); }
|
||||
}
|
||||
}
|
||||
|
||||
private void Nav_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
|
||||
void SetNavigationItem(object item)
|
||||
{
|
||||
Debug.WriteLine("Menu selection changed");
|
||||
try
|
||||
{
|
||||
if (s == Sender.None)
|
||||
{
|
||||
s = Sender.Menu;
|
||||
if (args.IsSettingsSelected)
|
||||
content.Navigate(typeof(Settings));
|
||||
if (nav.SelectedItem != item)
|
||||
nav.SelectedItem = item;
|
||||
else
|
||||
{
|
||||
if (args.SelectedItem == toHome)
|
||||
content.Navigate(typeof(Home));
|
||||
else if (args.SelectedItem == toHistory)
|
||||
content.Navigate(typeof(History));
|
||||
else if (args.SelectedItem == toLiked)
|
||||
content.Navigate(typeof(PlaylistPage), SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.Likes);
|
||||
else if (args.SelectedItem == toLater)
|
||||
content.Navigate(typeof(PlaylistPage), SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.WatchLater);
|
||||
else if (args.SelectedItem == toSubscriptions)
|
||||
content.Navigate(typeof(Subscriptions));
|
||||
else if (args.SelectedItem == toDownloads)
|
||||
content.Navigate(typeof(Downloads));
|
||||
else if (args.SelectedItem == toChannel)
|
||||
content.Navigate(typeof(ChannelPage), SecretsVault.UserChannel.Id);
|
||||
else
|
||||
content.Navigate(typeof(ChannelPage), SecretsVault.Subscriptions[Convert.ToInt32((args.SelectedItem as NavigationViewItem).Name)].Snippet.ResourceId.ChannelId);
|
||||
}
|
||||
}
|
||||
else
|
||||
s = Sender.None;
|
||||
wasInvoked = false;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public void Content_Navigated(object sender, NavigationEventArgs e)
|
||||
{
|
||||
Dictionary<Type, Action> switchCase = new Dictionary<Type, Action>()
|
||||
{
|
||||
{ typeof(Settings), () => nav.Header = resources.GetString("/Main/settings/Content") },
|
||||
{ typeof(ChannelPage), () => nav.Header = resources.GetString("/Main/channel") },
|
||||
{ typeof(PlaylistPage), () => nav.Header = resources.GetString("/Main/playlist") },
|
||||
{ typeof(Search), () => nav.Header = resources.GetString("/Main/searchPlaceholder/PlaceholderText") },
|
||||
{ typeof(Subscriptions), () => nav.Header = resources.GetString("/Main/subscriptions/Content") },
|
||||
{ typeof(History), () => nav.Header = resources.GetString("/Main/history/Content") },
|
||||
{ typeof(Home), () => nav.Header = resources.GetString("/Main/home/Content") },
|
||||
{ typeof(Downloads), () => nav.Header = resources.GetString("/Main/downloads/Content") }
|
||||
};
|
||||
|
||||
try { switchCase[e.SourcePageType](); }
|
||||
try { headers[e.SourcePageType](); }
|
||||
catch { }
|
||||
|
||||
if (s == Sender.None)
|
||||
if (!wasInvoked)
|
||||
{
|
||||
s = Sender.Frame;
|
||||
wasInvoked = true;
|
||||
|
||||
Dictionary<Type, Action> navCase = new Dictionary<Type, Action>()
|
||||
if (e.SourcePageType == typeof(Settings))
|
||||
SetNavigationItem(nav.SettingsItem);
|
||||
else if (e.SourcePageType == typeof(Subscriptions))
|
||||
SetNavigationItem(toSubscriptions);
|
||||
else if (e.SourcePageType == typeof(Downloads))
|
||||
SetNavigationItem(toDownloads);
|
||||
else if (e.SourcePageType == typeof(Home))
|
||||
SetNavigationItem(toHome);
|
||||
else if (e.SourcePageType == typeof(Search))
|
||||
SetNavigationItem(null);
|
||||
else if(e.SourcePageType == typeof(ChannelPage) && SecretsVault.IsAuthorized)
|
||||
{
|
||||
{ typeof(Settings), () =>
|
||||
{
|
||||
if(nav.SelectedItem != nav.SettingsItem)
|
||||
nav.SelectedItem = nav.SettingsItem;
|
||||
if (e.Parameter.ToString() == SecretsVault.AccountId)
|
||||
SetNavigationItem(toChannel);
|
||||
else if (nav.MenuItems.Contains(SecretsVault.Subscriptions.Find(i => i.Snippet.ResourceId.ChannelId == e.Parameter.ToString())))
|
||||
SetNavigationItem(SecretsVault.Subscriptions.Find(i => i.Snippet.ResourceId.ChannelId == e.Parameter.ToString()));
|
||||
else
|
||||
s = Sender.None;
|
||||
SetNavigationItem(null);
|
||||
}
|
||||
else if(e.SourcePageType == typeof(History))
|
||||
{
|
||||
if (e.Parameter.ToString() == "HL")
|
||||
SetNavigationItem(toHistory);
|
||||
else
|
||||
SetNavigationItem(toLater);
|
||||
}
|
||||
else if(e.SourcePageType == typeof(PlaylistPage))
|
||||
{
|
||||
if (e.Parameter.ToString() == SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.Likes)
|
||||
SetNavigationItem(toLiked);
|
||||
}
|
||||
}
|
||||
else
|
||||
wasInvoked = false;
|
||||
|
||||
} },
|
||||
{ typeof(ChannelPage), () =>
|
||||
if(e.SourcePageType == typeof(History))
|
||||
{
|
||||
if(sender.GetType() != typeof(ChannelPage))
|
||||
{
|
||||
Methods.NeedToResponse = true;
|
||||
s = Sender.None;
|
||||
return;
|
||||
if(e.Parameter.ToString() == "HL")
|
||||
Title.Text = resources.GetString("/Main/history/Content");
|
||||
else if(e.Parameter.ToString() == "WL")
|
||||
Title.Text = resources.GetString("/Main/later/Content");
|
||||
}
|
||||
Methods.NeedToResponse = false;
|
||||
if(SecretsVault.IsAuthorized)
|
||||
{
|
||||
if((content.Content as ChannelPage).channelId == SecretsVault.UserChannel.Id)
|
||||
{
|
||||
if(nav.SelectedItem != toChannel)
|
||||
nav.SelectedItem = toChannel;
|
||||
else
|
||||
s = Sender.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool found = false;
|
||||
for(int k = 0; k < SecretsVault.Subscriptions.Count && k < 10; k++)
|
||||
if(SecretsVault.Subscriptions[k].Snippet.ResourceId.ChannelId == (content.Content as ChannelPage).channelId)
|
||||
{
|
||||
if(nav.SelectedItem != nav.MenuItems[k + 9])
|
||||
nav.SelectedItem = nav.MenuItems[k + 9];
|
||||
else
|
||||
s = Sender.None;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!found)
|
||||
{
|
||||
nav.SelectedItem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
nav.SelectedItem = null;
|
||||
} },
|
||||
{ typeof(PlaylistPage), () =>
|
||||
{
|
||||
if(sender.GetType() != typeof(PlaylistPage))
|
||||
{
|
||||
Methods.NeedToResponse = true;
|
||||
s = Sender.None;
|
||||
return;
|
||||
}
|
||||
Methods.NeedToResponse = false;
|
||||
if((content.Content as PlaylistPage).playlistId == SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.Likes)
|
||||
{
|
||||
if(nav.SelectedItem != toLiked)
|
||||
nav.SelectedItem = toLiked;
|
||||
else
|
||||
s = Sender.None;
|
||||
}
|
||||
else if ((content.Content as PlaylistPage).playlistId == SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.WatchLater)
|
||||
{
|
||||
if(nav.SelectedItem != toLater)
|
||||
nav.SelectedItem = toLater;
|
||||
else
|
||||
s = Sender.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
nav.SelectedItem = null;
|
||||
}
|
||||
} },
|
||||
{ typeof(Search), () => nav.SelectedItem = null },
|
||||
{typeof(Subscriptions), () =>
|
||||
{
|
||||
if(nav.SelectedItem != toSubscriptions)
|
||||
nav.SelectedItem = toSubscriptions;
|
||||
else
|
||||
s = Sender.None;
|
||||
} },
|
||||
{ typeof(History), () =>
|
||||
{
|
||||
if(nav.SelectedItem != toHistory)
|
||||
nav.SelectedItem = toHistory;
|
||||
else
|
||||
s = Sender.None;
|
||||
} },
|
||||
{ typeof(Home), () =>
|
||||
{
|
||||
if(nav.SelectedItem != toHome)
|
||||
nav.SelectedItem = toHome;
|
||||
else
|
||||
s = Sender.None;
|
||||
} },
|
||||
{ typeof(Downloads), () =>
|
||||
{
|
||||
if(nav.SelectedItem != toDownloads)
|
||||
nav.SelectedItem = toDownloads;
|
||||
else
|
||||
s = Sender.None;
|
||||
} }
|
||||
};
|
||||
|
||||
try { navCase[content.SourcePageType](); }
|
||||
catch
|
||||
{
|
||||
nav.SelectedItem = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
s = Sender.None;
|
||||
|
||||
if (content.CanGoBack)
|
||||
nav.IsBackEnabled = true;
|
||||
else
|
||||
nav.IsBackEnabled = false;
|
||||
|
||||
if (content.SourcePageType == typeof(Home) || content.SourcePageType == typeof(Settings) || content.SourcePageType == typeof(Subscriptions))
|
||||
{
|
||||
nav.ExpandedModeThresholdWidth = 1008;
|
||||
if (nav.DisplayMode == NavigationViewDisplayMode.Expanded)
|
||||
nav.IsPaneOpen = true;
|
||||
}
|
||||
else
|
||||
nav.ExpandedModeThresholdWidth = short.MaxValue;
|
||||
SetNavigationMenu();
|
||||
|
||||
if (videoPlaceholder.Content != null && videoPlaceholder.HorizontalAlignment == HorizontalAlignment.Stretch)
|
||||
MinimizeAsInitializer();
|
||||
}
|
||||
|
||||
private void Nav_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args)
|
||||
{
|
||||
if (videoPlaceholder.Content != null)
|
||||
{
|
||||
if ((videoPlaceholder.Content as VideoPage).loading.State != LoadingState.Loaded)
|
||||
CloseVideo();
|
||||
else if (videoPlaceholder.HorizontalAlignment == HorizontalAlignment.Stretch)
|
||||
MinimizeAsInitializer();
|
||||
}
|
||||
else
|
||||
content.GoBack();
|
||||
}
|
||||
|
||||
private void Page_PreviewKeyUp(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if(videoPlaceholder.Content != null && FocusManager.GetFocusedElement().GetType() != typeof(TextBox))
|
||||
{
|
||||
e.Handled = true;
|
||||
(videoPlaceholder.Content as VideoPage).player.KeyUpPressed(sender, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenContext(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
((NavigationViewItem)sender).ContextFlyout.ShowAt((NavigationViewItem)sender);
|
||||
@@ -770,7 +548,70 @@ namespace FoxTube
|
||||
|
||||
private void Web_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
content.Navigate(typeof(Browser));
|
||||
content.Navigate(typeof(Home1));
|
||||
}
|
||||
|
||||
private void Nav_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!wasInvoked)
|
||||
{
|
||||
wasInvoked = true;
|
||||
if (args.IsSettingsSelected)
|
||||
content.Navigate(typeof(Settings));
|
||||
else
|
||||
{
|
||||
if (args.SelectedItem == toHome)
|
||||
content.Navigate(typeof(Home));
|
||||
else if (args.SelectedItem == toHistory)
|
||||
content.Navigate(typeof(History), "HL");
|
||||
else if (args.SelectedItem == toLiked)
|
||||
content.Navigate(typeof(PlaylistPage), SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.Likes);
|
||||
else if (args.SelectedItem == toLater)
|
||||
content.Navigate(typeof(History), "WL");
|
||||
else if (args.SelectedItem == toSubscriptions)
|
||||
content.Navigate(typeof(Subscriptions));
|
||||
else if (args.SelectedItem == toDownloads)
|
||||
content.Navigate(typeof(Downloads));
|
||||
else if (args.SelectedItem == toChannel)
|
||||
content.Navigate(typeof(ChannelPage), SecretsVault.UserChannel.Id);
|
||||
else
|
||||
content.Navigate(typeof(ChannelPage), (args.SelectedItem as Subscription).Snippet.ResourceId.ChannelId);
|
||||
}
|
||||
}
|
||||
else
|
||||
wasInvoked = false;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void Nav_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args)
|
||||
{
|
||||
if (videoPlaceholder.Content != null)
|
||||
{
|
||||
if ((videoPlaceholder.Content as VideoPage).LoadingPage.State != LoadingState.Loaded)
|
||||
CloseVideo();
|
||||
else if (videoPlaceholder.HorizontalAlignment == HorizontalAlignment.Stretch)
|
||||
MinimizeAsInitializer();
|
||||
}
|
||||
else
|
||||
content.GoBack();
|
||||
}
|
||||
|
||||
private void Nav_PaneClosing(NavigationView sender, NavigationViewPaneClosingEventArgs args)
|
||||
{
|
||||
AppTitle.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void Nav_PaneOpened(NavigationView sender, object args)
|
||||
{
|
||||
AppTitle.Visibility = Visibility.Visible;
|
||||
|
||||
if (sender.DisplayMode == NavigationViewDisplayMode.Expanded && sender.IsPaneOpen)
|
||||
AppTitleBar.Margin = new Thickness(40, 0, 0, 0);
|
||||
else
|
||||
AppTitleBar.Margin = new Thickness();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<Page
|
||||
NavigationCacheMode="Enabled"
|
||||
x:Class="FoxTube.Pages.PlaylistPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
@@ -7,10 +6,11 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:foxtube="using:FoxTube"
|
||||
xmlns:controls="using:FoxTube.Controls"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid Name="grid">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="auto"/>
|
||||
@@ -57,11 +57,14 @@
|
||||
</Button>
|
||||
</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>
|
||||
</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/addTo" Icon="Add" Label="Add to" IsEnabled="False" Visibility="Collapsed">
|
||||
<AppBarButton.Flyout>
|
||||
@@ -74,6 +77,6 @@
|
||||
<AppBarButton x:Uid="/Playlist/share" Icon="Share" Label="Share" Name="share" Click="share_Click"/>
|
||||
</CommandBar>
|
||||
|
||||
<foxtube:LoadingPage Grid.RowSpan="2" Visibility="Collapsed"/>
|
||||
<foxtube:LoadingPage Grid.RowSpan="2" Visibility="Collapsed" RefreshPage="refresh_Click" x:Name="loading"/>
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
@@ -18,26 +18,26 @@ namespace FoxTube.Pages
|
||||
/// <summary>
|
||||
/// Playlist page
|
||||
/// </summary>
|
||||
public sealed partial class PlaylistPage : Page
|
||||
public sealed partial class PlaylistPage : Page, NavigationPage
|
||||
{
|
||||
public object Parameter { get; set; } = null;
|
||||
public string playlistId;
|
||||
Playlist item;
|
||||
|
||||
LoadingPage loading;
|
||||
VideoGrid list;
|
||||
PlaylistItemsResource.ListRequest request;
|
||||
string token;
|
||||
|
||||
public PlaylistPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
loading = grid.Children[2] as LoadingPage;
|
||||
list = ((grid.Children[0] as ScrollViewer).Content as Grid).Children[1] as VideoGrid;
|
||||
loading.RefreshPage += refresh_Click;
|
||||
|
||||
DataTransferManager.GetForCurrentView().DataRequested += new TypedEventHandler<DataTransferManager, DataRequestedEventArgs>(Share);
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
Parameter = e.Parameter;
|
||||
if (e.Parameter == null)
|
||||
loading.Error("NullReferenceException", "Unable to initialize page. Playlist ID is not stated.");
|
||||
else
|
||||
@@ -51,13 +51,11 @@ namespace FoxTube.Pages
|
||||
try
|
||||
{
|
||||
playlistId = id;
|
||||
if (Methods.NeedToResponse)
|
||||
Methods.MainPage.Content_Navigated(this, null);
|
||||
|
||||
PlaylistsResource.ListRequest request = SecretsVault.Service.Playlists.List("snippet,contentDetails");
|
||||
request.Id = id;
|
||||
PlaylistsResource.ListRequest infoRequest = SecretsVault.Service.Playlists.List("snippet,contentDetails");
|
||||
infoRequest.Id = id;
|
||||
|
||||
item = (await request.ExecuteAsync()).Items[0];
|
||||
item = (await infoRequest.ExecuteAsync()).Items[0];
|
||||
|
||||
title.Text = item.Snippet.Title;
|
||||
info.Text = $"{item.ContentDetails.ItemCount} {ResourceLoader.GetForCurrentView("Playlist").GetString("/Playlist/videos")}";
|
||||
@@ -65,34 +63,26 @@ namespace FoxTube.Pages
|
||||
|
||||
channelName.Text = item.Snippet.ChannelTitle;
|
||||
|
||||
try
|
||||
{
|
||||
thumbnail.Source = new BitmapImage(new Uri(item.Snippet.Thumbnails.Medium.Url));
|
||||
ChannelsResource.ListRequest channelRequest = SecretsVault.Service.Channels.List("snippet");
|
||||
channelRequest.Id = item.Snippet.ChannelId;
|
||||
Channel channel = (await channelRequest.ExecuteAsync()).Items[0];
|
||||
avatar.ProfilePicture = new BitmapImage(new Uri(channel.Snippet.Thumbnails.Medium.Url));
|
||||
}
|
||||
|
||||
try { avatar.ProfilePicture = new BitmapImage(channel.Snippet.Thumbnails.Medium.Url.ToUri()) { DecodePixelWidth = 50, DecodePixelHeight = 50 }; }
|
||||
catch { }
|
||||
try { thumbnail.Source = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri()); }
|
||||
catch { }
|
||||
|
||||
PlaylistItemsResource.ListRequest listRequest = SecretsVault.Service.PlaylistItems.List("contentDetails");
|
||||
listRequest.PlaylistId = id;
|
||||
listRequest.MaxResults = 50;
|
||||
request = SecretsVault.Service.PlaylistItems.List("contentDetails");
|
||||
request.PlaylistId = id;
|
||||
request.MaxResults = 25;
|
||||
|
||||
PlaylistItemListResponse response = await listRequest.ExecuteAsync();
|
||||
|
||||
list.Clear();
|
||||
foreach (PlaylistItem i in response.Items)
|
||||
list.Add(new VideoCard(i.ContentDetails.VideoId, playlistId));
|
||||
|
||||
while (response.NextPageToken != null)
|
||||
{
|
||||
listRequest.PageToken = response.NextPageToken;
|
||||
response = await listRequest.ExecuteAsync();
|
||||
PlaylistItemListResponse response = await request.ExecuteAsync();
|
||||
token = response.NextPageToken;
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
more.Visibility = Visibility.Collapsed;
|
||||
|
||||
foreach (PlaylistItem i in response.Items)
|
||||
list.Add(new VideoCard(i.ContentDetails.VideoId, playlistId));
|
||||
}
|
||||
|
||||
loading.Close();
|
||||
}
|
||||
@@ -140,5 +130,22 @@ namespace FoxTube.Pages
|
||||
$"https://www.youtube.com/playlist?list={item.Id}",
|
||||
ResourceLoader.GetForCurrentView("Cards").GetString("/Cards/playlistShare"));
|
||||
}
|
||||
|
||||
private async void ShowMore_Clicked()
|
||||
{
|
||||
request.PageToken = token;
|
||||
PlaylistItemListResponse response = await request.ExecuteAsync();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.PageToken))
|
||||
more.Visibility = Visibility.Collapsed;
|
||||
else
|
||||
{
|
||||
token = response.NextPageToken;
|
||||
more.Complete();
|
||||
}
|
||||
|
||||
foreach (PlaylistItem i in response.Items)
|
||||
list.Add(new VideoCard(i.ContentDetails.VideoId, playlistId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,16 +67,16 @@
|
||||
<Button x:Uid="/Search/apply" Content="Apply" Margin="10,0,0,10" Name="apply" Click="Apply_Click"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<pages:VideoGrid/>
|
||||
<controls:ShowMore Clicked="More_Clicked"/>
|
||||
<pages:VideoGrid x:Name="list"/>
|
||||
<controls:ShowMore x:Name="more" Clicked="More_Clicked"/>
|
||||
</StackPanel>
|
||||
</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="Refresh" Icon="Refresh" Click="AppBarButton_Click"/>
|
||||
</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>
|
||||
</Page>
|
||||
|
||||
@@ -8,30 +8,26 @@ using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using Windows.System;
|
||||
using Windows.ApplicationModel.Resources;
|
||||
using Microsoft.AppCenter.Analytics;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FoxTube
|
||||
{
|
||||
/// <summary>
|
||||
/// Search page
|
||||
/// </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");
|
||||
|
||||
public SearchParameters Parameters;
|
||||
SearchResource.ListRequest request;
|
||||
string nextToken;
|
||||
|
||||
readonly VideoGrid list;
|
||||
readonly LoadingPage loading;
|
||||
readonly ShowMore more;
|
||||
|
||||
public Search()
|
||||
{
|
||||
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)
|
||||
@@ -68,6 +64,7 @@ namespace FoxTube
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
Parameter = e.Parameter;
|
||||
if (e.Parameter == null)
|
||||
loading.Error("NullReferenceException", "Unable to initialize search. Search term is not stated.");
|
||||
else
|
||||
@@ -77,7 +74,6 @@ namespace FoxTube
|
||||
else
|
||||
loading.Error("ArgumentException", "Wrong parameter");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async void Initialize(SearchParameters arg)
|
||||
@@ -100,20 +96,21 @@ namespace FoxTube
|
||||
request.RegionCode = SettingsStorage.Region;
|
||||
request.RelevanceLanguage = SettingsStorage.RelevanceLanguage;
|
||||
|
||||
request.MaxResults = 48;
|
||||
try
|
||||
{
|
||||
request.MaxResults = 25;
|
||||
|
||||
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.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.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.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);
|
||||
}
|
||||
catch { }
|
||||
|
||||
order.SelectedIndex = (int)Parameters.Filter.Order;
|
||||
type.SelectedIndex = (int)Parameters.Filter.Type;
|
||||
@@ -135,7 +132,7 @@ namespace FoxTube
|
||||
if (!string.IsNullOrWhiteSpace(response.NextPageToken))
|
||||
nextToken = response.NextPageToken;
|
||||
else
|
||||
more.Complete(true);
|
||||
more.Visibility = Visibility.Collapsed;
|
||||
|
||||
list.Clear();
|
||||
foreach (SearchResult item in response.Items)
|
||||
@@ -150,6 +147,12 @@ namespace FoxTube
|
||||
catch (Exception e)
|
||||
{
|
||||
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();
|
||||
}
|
||||
else
|
||||
more.Complete(true);
|
||||
more.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void Type_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
|
||||
@@ -7,8 +7,9 @@ namespace FoxTube
|
||||
/// <summary>
|
||||
/// Settings tabs placeholder
|
||||
/// </summary>
|
||||
public sealed partial class Settings : Page
|
||||
public sealed partial class Settings : Page, NavigationPage
|
||||
{
|
||||
public object Parameter { get; set; } = null;
|
||||
bool inboxLoaded = false;
|
||||
string inboxId = null;
|
||||
public Settings()
|
||||
@@ -19,17 +20,12 @@ namespace FoxTube
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
Parameter = e.Parameter;
|
||||
if (!string.IsNullOrWhiteSpace(e.Parameter as string))
|
||||
{
|
||||
if ((e.Parameter as string).Contains("inbox") || (e.Parameter as string).Contains("changelog"))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
|
||||
@@ -27,9 +27,7 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="/About/aboutHeader" Text="About us and this app" FontSize="28"/>
|
||||
|
||||
<TextBlock Text="FoxTube" FontSize="24"/>
|
||||
<TextBlock Text="FoxTube" FontSize="28"/>
|
||||
<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"/>
|
||||
@@ -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"/>
|
||||
<TextBlock x:Uid="/About/crMe" Text="© 2018 Michael Gordeev"/>
|
||||
<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>
|
||||
<Image Grid.Column="1" Source="/Assets/LogoAvatar.png" VerticalAlignment="Top" Width="128"/>
|
||||
</Grid>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Microsoft.Services.Store.Engagement;
|
||||
using System;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
@@ -10,16 +11,19 @@ namespace FoxTube.Pages.SettingsPages
|
||||
/// </summary>
|
||||
public sealed partial class About : Page
|
||||
{
|
||||
PackageVersion ver = Package.Current.Id.Version;
|
||||
public About()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
version.Text = string.Format("{0}.{1}.{2}", ver.Major, ver.Minor, ver.Build);
|
||||
InitializeComponent();
|
||||
PackageVersion ver = Package.Current.Id.Version;
|
||||
version.Text = $"{ver.Major}.{ver.Minor}.{ver.Build}";
|
||||
|
||||
if (StoreServicesFeedbackLauncher.IsSupported())
|
||||
feedback.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private async void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await Windows.System.Launcher.LaunchUriAsync(new Uri("feedback-hub:"));
|
||||
await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock x:Uid="/General/preferences" Text="Preferences" FontSize="28"/>
|
||||
<TextBlock x:Uid="/General/regNsearch" Text="Region & search" FontSize="22"/>
|
||||
<TextBlock x:Uid="/General/regNsearch" Text="Region & search" FontSize="22" Margin="0,10,0,0"/>
|
||||
<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/ru" Content="Russian (Russia)" Tag="ru-RU"/>
|
||||
@@ -26,18 +26,18 @@
|
||||
<ComboBoxItem x:Uid="/General/strict" Content="Strict"/>
|
||||
</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">
|
||||
<ComboBoxItem Tag="remember" x:Uid="/General/remember" Content="Remember my choice"/>
|
||||
</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/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/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/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"/>
|
||||
|
||||
@@ -7,6 +7,7 @@ using Google.Apis.YouTube.v3;
|
||||
using Google.Apis.YouTube.v3.Data;
|
||||
using YoutubeExplode.Models.MediaStreams;
|
||||
using System;
|
||||
using Windows.Globalization;
|
||||
|
||||
namespace FoxTube.Pages.SettingsPages
|
||||
{
|
||||
@@ -50,19 +51,17 @@ namespace FoxTube.Pages.SettingsPages
|
||||
async void InitializeRegions()
|
||||
{
|
||||
I18nRegionsResource.ListRequest regRequest = SecretsVault.Service.I18nRegions.List("snippet");
|
||||
regRequest.Hl = SettingsStorage.Language;
|
||||
I18nRegionListResponse regResponse = await regRequest.ExecuteAsync();
|
||||
foreach(I18nRegion i in regResponse.Items)
|
||||
{
|
||||
region.Items.Add(new ComboBoxItem
|
||||
regResponse.Items.ForEach(i => region.Items.Add(new ComboBoxItem
|
||||
{
|
||||
Content = i.Snippet.Name,
|
||||
Tag = i.Snippet.Gl
|
||||
});
|
||||
if (SettingsStorage.Region == i.Snippet.Gl)
|
||||
region.SelectedItem = region.Items.Last();
|
||||
}
|
||||
}));
|
||||
region.SelectedItem = region.Items.Find(i => ((ComboBoxItem)i).Tag as string == SettingsStorage.Region) ?? region.Items.Find(i => ((ComboBoxItem)i).Tag as string == SettingsStorage.Language.Remove(0, 3));
|
||||
|
||||
I18nLanguagesResource.ListRequest langRequest = SecretsVault.Service.I18nLanguages.List("snippet");
|
||||
langRequest.Hl = SettingsStorage.Language;
|
||||
I18nLanguageListResponse langResponse = await langRequest.ExecuteAsync();
|
||||
foreach(I18nLanguage i in langResponse.Items)
|
||||
{
|
||||
@@ -80,7 +79,7 @@ namespace FoxTube.Pages.SettingsPages
|
||||
{
|
||||
if (SettingsStorage.Language == (language.SelectedItem as ComboBoxItem).Tag.ToString())
|
||||
return;
|
||||
|
||||
ApplicationLanguages.PrimaryLanguageOverride = (language.SelectedItem as ComboBoxItem).Tag.ToString();
|
||||
SettingsStorage.Language = (language.SelectedItem as ComboBoxItem).Tag.ToString();
|
||||
restartNote.Visibility = Visibility.Visible;
|
||||
}
|
||||
@@ -135,8 +134,7 @@ namespace FoxTube.Pages.SettingsPages
|
||||
else if (sender == system && SettingsStorage.Theme != 2)
|
||||
{
|
||||
SettingsStorage.Theme = 2;
|
||||
Color uiTheme = new Windows.UI.ViewManagement.UISettings().GetColorValue(Windows.UI.ViewManagement.UIColorType.Background);
|
||||
if (uiTheme == Colors.Black)
|
||||
if (new Windows.UI.ViewManagement.UISettings().GetColorValue(Windows.UI.ViewManagement.UIColorType.Background) == Colors.Black)
|
||||
Methods.MainPage.RequestedTheme = ElementTheme.Dark;
|
||||
else
|
||||
Methods.MainPage.RequestedTheme = ElementTheme.Light;
|
||||
|
||||
@@ -52,27 +52,26 @@
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="0"/>
|
||||
</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">
|
||||
<ComboBoxItem x:Uid="/Inbox/all" Content="All"/>
|
||||
<ComboBoxItem x:Uid="/Inbox/messages" Content="Messages"/>
|
||||
<ComboBoxItem x:Uid="/Inbox/changelogs" Content="Patch notes"/>
|
||||
</ComboBox>
|
||||
<ListBox Name="list" SelectionChanged="list_SelectionChanged" Background="Transparent">
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem">
|
||||
<ListView Name="list" SelectionChanged="list_SelectionChanged" Background="Transparent">
|
||||
<ListView.ItemContainerStyle>
|
||||
<Style TargetType="ListViewItem">
|
||||
<Setter Property="Padding" Value="10"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
<ListBox.ItemTemplate>
|
||||
</ListView.ItemContainerStyle>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid Margin="0, 10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition Width="2*"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Margin="0,0,10,0">
|
||||
<Ellipse Fill="Red" Height="40" Width="40"/>
|
||||
@@ -81,14 +80,13 @@
|
||||
<StackPanel Grid.Column="1">
|
||||
<TextBlock FontWeight="Bold" Text="{Binding Path=Title}" MaxLines="1" TextWrapping="Wrap"/>
|
||||
<TextBlock Opacity=".5" Text="{Binding Path=Subtitle}"/>
|
||||
<TextBlock Opacity=".5" FontSize="13" Text="{Binding Path=TimeStampString}" TextWrapping="WrapWholeWords"/>
|
||||
</StackPanel>
|
||||
<TextBlock Opacity=".5" FontSize="13" Text="{Binding Path=TimeStamp}" Grid.Column="2" TextWrapping="WrapWholeWords"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<ScrollViewer Grid.Column="1">
|
||||
<StackPanel Margin="10">
|
||||
@@ -99,8 +97,8 @@
|
||||
<Button Grid.Column="1" VerticalAlignment="Top" HorizontalAlignment="Right" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="" Width="50" Height="50" Name="close" Click="close_Click"/>
|
||||
<Grid Grid.Column="1" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Name="block">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" Text="" FontSize="70" Foreground="Gray" Margin="10"/>
|
||||
<TextBlock x:Uid="/Inbox/select" Text="Select item from list" FontSize="30" VerticalAlignment="Center" TextWrapping="WrapWholeWords" Foreground="Gray"/>
|
||||
<TextBlock FontFamily="Segoe MDL2 Assets" Text="" FontSize="50" Foreground="Gray" Margin="10"/>
|
||||
<TextBlock x:Uid="/Inbox/select" Text="Select item from list" FontSize="24" VerticalAlignment="Center" TextWrapping="WrapWholeWords" Foreground="Gray"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -6,7 +6,6 @@ using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using FoxTube.Classes;
|
||||
using Windows.Storage;
|
||||
using System.Diagnostics;
|
||||
using System.Xml;
|
||||
|
||||
namespace FoxTube.Pages.SettingsPages
|
||||
@@ -17,6 +16,8 @@ namespace FoxTube.Pages.SettingsPages
|
||||
public sealed partial class Inbox : Page
|
||||
{
|
||||
List<InboxItem> items = new List<InboxItem>();
|
||||
|
||||
string open;
|
||||
public Inbox()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -39,7 +40,7 @@ namespace FoxTube.Pages.SettingsPages
|
||||
doc.Load("http://foxgame-studio.000webhostapp.com/foxtube-messages.xml");
|
||||
foreach (XmlElement e in doc["posts"].ChildNodes)
|
||||
items.Add(new InboxItem(
|
||||
e["header"].InnerText,
|
||||
e["header"][SettingsStorage.Language].InnerText,
|
||||
e["content"][SettingsStorage.Language].InnerText,
|
||||
DateTime.Parse(e.GetAttribute("time")),
|
||||
e["id"].InnerText
|
||||
@@ -50,6 +51,11 @@ namespace FoxTube.Pages.SettingsPages
|
||||
catch { }
|
||||
|
||||
items.ForEach(i => list.Items.Add(i));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(open))
|
||||
Open(open);
|
||||
|
||||
open = null;
|
||||
}
|
||||
|
||||
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[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[1].Width = new GridLength(0);
|
||||
Debug.WriteLine("Closed");
|
||||
}
|
||||
}
|
||||
|
||||
public void Open(string arg)
|
||||
{
|
||||
string id = arg.Split('&')[1];
|
||||
InboxItem item = items.Find(x => x.Id == id);
|
||||
if(items.Count == 0)
|
||||
{
|
||||
open = arg;
|
||||
return;
|
||||
}
|
||||
|
||||
InboxItem item = items.Find(i => i.Id == arg);
|
||||
if(item != null)
|
||||
list.SelectedItem = item;
|
||||
}
|
||||
|
||||
@@ -20,8 +20,12 @@
|
||||
<ColumnDefinition Width="55"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<PersonPicture Height="50" HorizontalAlignment="Left" ProfilePicture="{Binding Path=Snippet.Thumbnails.Medium.Url}"/>
|
||||
<TextBlock Grid.Column="1" TextWrapping="Wrap" VerticalAlignment="Center" Text="{Binding Path=Snippet.Title}"/>
|
||||
<PersonPicture Height="50" HorizontalAlignment="Left">
|
||||
<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>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -8,8 +8,9 @@ namespace FoxTube.Pages
|
||||
/// <summary>
|
||||
/// User's subscriptions page
|
||||
/// </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;
|
||||
public Subscriptions()
|
||||
{
|
||||
|
||||
@@ -4,18 +4,22 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="using:Microsoft.Toolkit.Uwp.UI.Controls"
|
||||
xmlns:controls="using:FoxTube.Controls"
|
||||
x:Name="root"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<controls:Advert/>
|
||||
<ui:AdaptiveGridView Name="list" OneRowModeEnabled="False" DesiredWidth="384" SelectionMode="None" Grid.Row="1"/>
|
||||
<TextBlock Name="empty" Text="Ø" FontSize="200" Foreground="Gray" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.RowSpan="2"/>
|
||||
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Name="grid" SizeChanged="Grid_SizeChanged">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="0"/>
|
||||
<ColumnDefinition Width="0"/>
|
||||
<ColumnDefinition Width="0"/>
|
||||
<ColumnDefinition Width="0"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Name="empty" Grid.ColumnSpan="5" Text="Ø" 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>
|
||||
</Page>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Windows.UI.Xaml;
|
||||
using System.Collections.Generic;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace FoxTube.Pages
|
||||
@@ -8,6 +9,19 @@ namespace FoxTube.Pages
|
||||
/// </summary>
|
||||
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()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -15,7 +29,8 @@ namespace FoxTube.Pages
|
||||
|
||||
public void Add(UIElement card)
|
||||
{
|
||||
list.Items.Add(card);
|
||||
(grid.Children[Count % cols + 1] as StackPanel).Children.Add(card);
|
||||
Children.Add(card);
|
||||
/*if (list.Items.Count % 10 == 0)
|
||||
list.Items.Add(new CardAdvert());*/
|
||||
empty.Visibility = Visibility.Collapsed;
|
||||
@@ -23,8 +38,39 @@ namespace FoxTube.Pages
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
list.Items.Clear();
|
||||
for (int k = 1; k <= 5; k++)
|
||||
(grid.Children[k] as StackPanel).Children.Clear();
|
||||
|
||||
empty.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
void UpdateGrid()
|
||||
{
|
||||
for (int k = 1; k <= 5; k++)
|
||||
(grid.Children[k] as StackPanel).Children.Clear();
|
||||
|
||||
for (int k = 0; k < Count; k++)
|
||||
(grid.Children[k % cols + 1] as StackPanel).Children.Add(Children[k]);
|
||||
|
||||
for (int k = 0; k < cols; k++)
|
||||
grid.ColumnDefinitions[k].Width = new GridLength(1, GridUnitType.Star);
|
||||
|
||||
for (int k = cols; k < 5; k++)
|
||||
grid.ColumnDefinitions[k].Width = new GridLength(0);
|
||||
}
|
||||
|
||||
private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
if (e.NewSize.Width >= 1600 && Columns != 5)
|
||||
Columns = 5;
|
||||
else if (e.NewSize.Width >= 1200 && e.NewSize.Width < 1600 && Columns != 4)
|
||||
Columns = 4;
|
||||
else if (e.NewSize.Width >= 900 && e.NewSize.Width < 1200 && Columns != 3)
|
||||
Columns = 3;
|
||||
else if (e.NewSize.Width >= 550 && e.NewSize.Width < 900 && Columns != 2)
|
||||
Columns = 2;
|
||||
else if (e.NewSize.Width < 550 && Columns != 1)
|
||||
Columns = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
xmlns:controls1="using:FoxTube.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
|
||||
<Grid Name="grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" SizeChanged="grid_SizeChanged">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup>
|
||||
@@ -30,12 +29,38 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="400"/>
|
||||
</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">
|
||||
<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="" 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">
|
||||
<StackPanel Margin="0,10">
|
||||
<TextBlock IsTextSelectionEnabled="True" Name="title" Text="[Video title]" FontSize="25" TextWrapping="WrapWholeWords" HorizontalTextAlignment="Start"/>
|
||||
<TextBlock Text="Published at: " Name="date"/>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -55,6 +80,10 @@
|
||||
<TextBlock Name="views" Text="[views]" FontSize="24" Foreground="Gray"/>
|
||||
<ProgressBar Name="rating" Background="Green" Foreground="Red"/>
|
||||
<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"
|
||||
HorizontalAlignment="Left"
|
||||
FontSize="40"
|
||||
@@ -67,14 +96,9 @@
|
||||
Name="like" Tapped="like_Click"
|
||||
Glyph=""/>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<TextBlock Foreground="Gray" Text="[dislikes]" Name="dislikes"/>
|
||||
<TextBlock HorizontalAlignment="Right" Foreground="Gray" Text="[likes]" Name="likes"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<TextBlock Name="description" Text="[Description]" IsTextSelectionEnabled="True" TextWrapping="WrapWholeWords"/>
|
||||
<TextBlock Margin="0,20" Name="category" Text="Category: "/>
|
||||
</StackPanel>
|
||||
</PivotItem>
|
||||
</StackPanel>
|
||||
@@ -88,7 +112,35 @@
|
||||
</AppBarButton>
|
||||
<AppBarButton x:Uid="/VideoPage/addTo" Name="addTo" Label="Add to" Icon="Add" Visibility="Collapsed">
|
||||
<AppBarButton.Flyout>
|
||||
<Flyout>
|
||||
<MenuFlyout x:Name="addList">
|
||||
<MenuFlyoutItem Text="New playlist" Name="newPlaylist" Click="NewPlaylist_Click">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<ToggleMenuFlyoutItem Text="Watch later" Background="Red" Name="wl" Click="Wl_Click">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</MenuFlyoutItem.Icon>
|
||||
</ToggleMenuFlyoutItem>
|
||||
<MenuFlyoutSeparator/>
|
||||
<ToggleMenuFlyoutItem Text="Cats">
|
||||
<ToggleMenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</ToggleMenuFlyoutItem.Icon>
|
||||
</ToggleMenuFlyoutItem>
|
||||
<ToggleMenuFlyoutItem Text="Dogs">
|
||||
<ToggleMenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</ToggleMenuFlyoutItem.Icon>
|
||||
</ToggleMenuFlyoutItem>
|
||||
<ToggleMenuFlyoutItem Text="Porn">
|
||||
<ToggleMenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</ToggleMenuFlyoutItem.Icon>
|
||||
</ToggleMenuFlyoutItem>
|
||||
</MenuFlyout>
|
||||
<!--<Flyout>
|
||||
<ScrollViewer Margin="-12" MaxHeight="300">
|
||||
<NavigationViewList Width="200" IsMultiSelectCheckBoxEnabled="True" SelectionMode="Multiple">
|
||||
<NavigationViewItem Content="Watch later">
|
||||
@@ -124,7 +176,7 @@
|
||||
</NavigationViewItem>
|
||||
</NavigationViewList>
|
||||
</ScrollViewer>
|
||||
</Flyout>
|
||||
</Flyout>-->
|
||||
</AppBarButton.Flyout>
|
||||
</AppBarButton>
|
||||
<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">
|
||||
<PivotItem x:Uid="/VideoPage/related" Header="Suggestions">
|
||||
<ScrollViewer>
|
||||
<StackPanel>
|
||||
<controls1:Advert/>
|
||||
<StackPanel Name="relatedVideos"/>
|
||||
</StackPanel>
|
||||
<pages:VideoGrid x:Name="relatedVideos"/>
|
||||
</ScrollViewer>
|
||||
</PivotItem>
|
||||
<PivotItem x:Uid="/VideoPage/comments" Header="Comments" Name="commentsPlaceholder">
|
||||
<pages:CommentsPage/>
|
||||
<pages:CommentsPage x:Name="comments"/>
|
||||
</PivotItem>
|
||||
<PivotItem x:Uid="/VideoPage/playlist" Header="Playlist" Name="playlist">
|
||||
<ScrollViewer>
|
||||
<ScrollViewer Name="playlistScroll">
|
||||
<StackPanel>
|
||||
<StackPanel Padding="8" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
|
||||
<TextBlock Text="Music" FontSize="26" TextWrapping="WrapWholeWords" Name="playlistName"/>
|
||||
<TextBlock Text="DAGames" Name="playlistChannel"/>
|
||||
<TextBlock Text="15/155" Name="playlistCounter"/>
|
||||
<TextBlock Text="[Playlsit name]" FontSize="26" TextWrapping="WrapWholeWords" Name="playlistName"/>
|
||||
<TextBlock Text="[Channel name]" Name="playlistChannel"/>
|
||||
<TextBlock Text="[Counter]" Name="playlistCounter"/>
|
||||
</StackPanel>
|
||||
<ListBox Background="Transparent" SelectionChanged="ListBox_SelectionChanged" Name="playlistList">
|
||||
<ListBox.ItemTemplate>
|
||||
@@ -175,6 +224,13 @@
|
||||
</Pivot>
|
||||
</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>
|
||||
</Page>
|
||||
|
||||
@@ -7,6 +7,8 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.ApplicationModel.Resources;
|
||||
using Windows.Foundation;
|
||||
@@ -50,26 +52,19 @@ namespace FoxTube.Pages
|
||||
public string playlistId = null;
|
||||
public Video item;
|
||||
|
||||
bool wide;
|
||||
bool isExtended = false;
|
||||
|
||||
Rating userRating = Rating.None;
|
||||
|
||||
public VideoPlayer player;
|
||||
public CommentsPage comments;
|
||||
public LoadingPage loading;
|
||||
|
||||
DispatcherTimer liveTimer;
|
||||
DispatcherTimer countdownTimer;
|
||||
|
||||
public LoadingPage LoadingPage => loading;
|
||||
public VideoPlayer Player => player;
|
||||
|
||||
public VideoPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
loading = grid.Children[3] as LoadingPage;
|
||||
loading.RefreshPage += refresh_Click;
|
||||
player = mainContent.Children[0] as VideoPlayer;
|
||||
player.SetFullSize += Player_SetFullSize;
|
||||
player.NextClicked += Player_NextClicked;
|
||||
comments = commentsPlaceholder.Content as CommentsPage;
|
||||
|
||||
DataTransferManager.GetForCurrentView().DataRequested += new TypedEventHandler<DataTransferManager, DataRequestedEventArgs>(Share);
|
||||
|
||||
@@ -127,6 +122,9 @@ namespace FoxTube.Pages
|
||||
else
|
||||
LoadStream();
|
||||
|
||||
if (item.Snippet.LiveBroadcastContent == "upcoming")
|
||||
SetSchedule();
|
||||
|
||||
LoadInfo();
|
||||
|
||||
loading.Close();
|
||||
@@ -147,6 +145,36 @@ namespace FoxTube.Pages
|
||||
}
|
||||
}
|
||||
|
||||
void SetSchedule()
|
||||
{
|
||||
views.Visibility = Visibility.Collapsed;
|
||||
upcoming.Visibility = Visibility.Visible;
|
||||
if (item.LiveStreamingDetails.ScheduledEndTime.HasValue || item.LiveStreamingDetails.ScheduledStartTime.HasValue)
|
||||
schedule.Visibility = Visibility.Visible;
|
||||
|
||||
if (item.LiveStreamingDetails.ScheduledEndTime.HasValue)
|
||||
{
|
||||
end.Text = $"{resources.GetString("/VideoPage/end")} {item.LiveStreamingDetails.ScheduledEndTime.Value}";
|
||||
end.Visibility = Visibility.Visible;
|
||||
}
|
||||
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue)
|
||||
{
|
||||
start.Text = $"{resources.GetString("/VideoPage/start")} {item.LiveStreamingDetails.ScheduledStartTime.Value}";
|
||||
start.Visibility = Visibility.Visible;
|
||||
|
||||
countdownPanel.Visibility = Visibility.Visible;
|
||||
|
||||
countdownTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
|
||||
countdownTimer.Tick += (s, e) =>
|
||||
{
|
||||
countdown.Text = item.LiveStreamingDetails.ScheduledStartTime.Value > DateTime.Now ? "" : "-" + (item.LiveStreamingDetails.ScheduledStartTime.Value - DateTime.Now).ToString(@"hh\:mm\:ss");
|
||||
if (countdown.Text == "00:00:00")
|
||||
refresh_Click(this, null);
|
||||
};
|
||||
countdownTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
async void LoadPlaylist(string id)
|
||||
{
|
||||
playlistId = id;
|
||||
@@ -161,30 +189,19 @@ namespace FoxTube.Pages
|
||||
PlaylistItemsResource.ListRequest listRequest = SecretsVault.Service.PlaylistItems.List("snippet");
|
||||
listRequest.MaxResults = 50;
|
||||
listRequest.PlaylistId = id;
|
||||
PlaylistItemListResponse listResponse = await listRequest.ExecuteAsync();
|
||||
|
||||
foreach (PlaylistItem i in listResponse.Items)
|
||||
PlaylistItemListResponse listResponse;
|
||||
do
|
||||
{
|
||||
items.Add(new VideoPlaylistItem(i.Snippet.Thumbnails.Medium.Url, i.Snippet.Title, i.Snippet.ResourceId.VideoId));
|
||||
if (items.Last().Id == videoId)
|
||||
selection = items.Last();
|
||||
}
|
||||
|
||||
string token = listResponse.NextPageToken;
|
||||
while (!string.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
listRequest.PageToken = token;
|
||||
listResponse = await listRequest.ExecuteAsync();
|
||||
listRequest.PageToken = listResponse.NextPageToken;
|
||||
|
||||
foreach(PlaylistItem i in listResponse.Items)
|
||||
{
|
||||
items.Add(new VideoPlaylistItem(i.Snippet.Thumbnails.Medium.Url, i.Snippet.Title, i.Snippet.ResourceId.VideoId));
|
||||
if (items.Last().Id == videoId)
|
||||
selection = items.Last();
|
||||
}
|
||||
while (!string.IsNullOrWhiteSpace(listRequest.PageToken));
|
||||
|
||||
token = listResponse.NextPageToken;
|
||||
}
|
||||
selection = items.Find(i => i.Id == item.Id);
|
||||
|
||||
for (int k = 0; k < items.Count; k++)
|
||||
items[k].Number = k + 1;
|
||||
@@ -199,14 +216,19 @@ namespace FoxTube.Pages
|
||||
playlistList.SelectedItem = selection;
|
||||
pivot.SelectedItem = playlist;
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
playlistScroll.ChangeView(null, playlistList.SelectedIndex * 86 + 89, null, true);
|
||||
|
||||
if (playlistList.SelectedIndex == playlistList.Items.Count - 1)
|
||||
player.Next.Visibility = Visibility.Collapsed;
|
||||
player.Controls.IsNextTrackButtonVisible = false;
|
||||
}
|
||||
|
||||
async void LoadInfo()
|
||||
{
|
||||
//Setting meta
|
||||
title.Text = item.Snippet.Title;
|
||||
date.Text = $"{resources.GetString("/VideoPage/publishedAt")}: {item.Snippet.PublishedAt} ({Methods.GetAgo(item.Snippet.PublishedAt.Value)})";
|
||||
Methods.FormatText(ref description, item.Snippet.Description);
|
||||
|
||||
//Setting channel button
|
||||
@@ -214,7 +236,7 @@ namespace FoxTube.Pages
|
||||
channelRequest.Id = item.Snippet.ChannelId;
|
||||
var item1 = (await channelRequest.ExecuteAsync()).Items[0];
|
||||
|
||||
channelAvatar.ProfilePicture = new BitmapImage(new Uri(item1.Snippet.Thumbnails.Medium.Url));
|
||||
channelAvatar.ProfilePicture = new BitmapImage(item1.Snippet.Thumbnails.Medium.Url.ToUri()) { DecodePixelHeight = 90, DecodePixelWidth = 90 };
|
||||
channelName.Text = item.Snippet.ChannelTitle;
|
||||
subscribers.Text = $"{item1.Statistics.SubscriberCount:0,0} {resources.GetString("/Cards/subscribers")}";
|
||||
|
||||
@@ -223,11 +245,6 @@ namespace FoxTube.Pages
|
||||
likes.Text = $"{item.Statistics.LikeCount:0,0}";
|
||||
rating.Value = (double)item.Statistics.DislikeCount / (double)(item.Statistics.DislikeCount + item.Statistics.LikeCount) * 100;
|
||||
|
||||
//Setting category
|
||||
VideoCategoriesResource.ListRequest categoryRequest = SecretsVault.Service.VideoCategories.List("snippet");
|
||||
categoryRequest.Id = item.Snippet.CategoryId;
|
||||
category.Text = (await categoryRequest.ExecuteAsync()).Items[0].Snippet.Title;
|
||||
|
||||
//Setting User's rate
|
||||
if (SecretsVault.IsAuthorized)
|
||||
{
|
||||
@@ -289,9 +306,8 @@ namespace FoxTube.Pages
|
||||
{
|
||||
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("liveStreamingDetails");
|
||||
request.Id = videoId;
|
||||
Video video = (await request.ExecuteAsync()).Items[0];
|
||||
|
||||
views.Text = $"{video.LiveStreamingDetails.ConcurrentViewers} {resources.GetString("/Cards/viewers")}";
|
||||
views.Text = $"{(await request.ExecuteAsync()).Items[0].LiveStreamingDetails.ConcurrentViewers} {resources.GetString("/Cards/viewers")}";
|
||||
}
|
||||
|
||||
void LoadStats()
|
||||
@@ -326,9 +342,9 @@ namespace FoxTube.Pages
|
||||
audioItem.Click += downloadItemSelected;
|
||||
downloadSelector.Items.Add(audioItem);
|
||||
}
|
||||
catch (Exception e)
|
||||
catch
|
||||
{
|
||||
loading.Error(e.GetType().ToString(), e.Message);
|
||||
download.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,32 +366,36 @@ namespace FoxTube.Pages
|
||||
SearchListResponse response = await request.ExecuteAsync();
|
||||
|
||||
foreach (SearchResult video in response.Items)
|
||||
relatedVideos.Children.Add(new VideoCard(video.Id.VideoId));
|
||||
relatedVideos.Add(new VideoCard(video.Id.VideoId));
|
||||
}
|
||||
|
||||
private void Player_SetFullSize(object sender, params object[] e)
|
||||
private void Player_Minimize(object sender, params object[] e)
|
||||
{
|
||||
if (isExtended == (bool)e[0])
|
||||
return;
|
||||
|
||||
isExtended = (bool)e[0];
|
||||
if(isExtended)
|
||||
{
|
||||
grid.ColumnDefinitions[1].Width = new GridLength(0);
|
||||
commandbar.Visibility = Visibility.Collapsed;
|
||||
|
||||
mainScroll.Margin = new Thickness(0);
|
||||
mainScroll.ChangeView(0, 0, null);
|
||||
mainScroll.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden;
|
||||
|
||||
mainScroll.Margin = new Thickness(0);
|
||||
mainScroll.VerticalScrollMode = ScrollMode.Disabled;
|
||||
|
||||
Methods.MainPage.MinimizeVideo();
|
||||
}
|
||||
else
|
||||
{
|
||||
grid.ColumnDefinitions[1].Width = new GridLength(400);
|
||||
commandbar.Visibility = Visibility.Visible;
|
||||
|
||||
mainScroll.Margin = new Thickness(0,0,0,50);
|
||||
mainScroll.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
|
||||
mainScroll.VerticalScrollMode = ScrollMode.Auto;
|
||||
|
||||
if (wide)
|
||||
grid.ColumnDefinitions[1].Width = new GridLength(400);
|
||||
Methods.MainPage.MaximizeVideo();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,8 +407,8 @@ namespace FoxTube.Pages
|
||||
private async void openBrowser_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
player.Pause();
|
||||
string timecode = player.Elapsed.TotalSeconds > 10 ?
|
||||
"&t=" + (int)player.Elapsed.TotalSeconds + "s" : string.Empty;
|
||||
string timecode = player.Position.TotalSeconds > 10 ?
|
||||
"&t=" + (int)player.Position.TotalSeconds + "s" : string.Empty;
|
||||
|
||||
await Launcher.LaunchUriAsync($"https://www.youtube.com/watch?v={videoId}{timecode}".ToUri());
|
||||
}
|
||||
@@ -398,15 +418,14 @@ namespace FoxTube.Pages
|
||||
Methods.MainPage.GoToVideo(videoId, playlistId);
|
||||
}
|
||||
|
||||
public void CloseVideo()
|
||||
{
|
||||
player.Controls_CloseRequested(this, null);
|
||||
}
|
||||
|
||||
private void grid_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
Debug.WriteLine(e.NewSize.Width);
|
||||
if (e.NewSize.Width > 1000)
|
||||
wide = true;
|
||||
else
|
||||
wide = false;
|
||||
|
||||
if (e.NewSize.Width > 1000 && mainContent.Children.Contains(pivot) && !isExtended)
|
||||
if (e.NewSize.Width > 1000 && mainContent.Children.Contains(pivot))
|
||||
{
|
||||
mainContent.Children.Remove(pivot);
|
||||
tabsPlaceholder.Children.Add(pivot);
|
||||
@@ -547,5 +566,21 @@ namespace FoxTube.Pages
|
||||
subscribe.Content = resources.GetString("/Cards/subscribe/Content");
|
||||
}
|
||||
}
|
||||
|
||||
private async void NewPlaylist_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
//TODO: Localize strings
|
||||
await playlistDialog.ShowAsync();
|
||||
}
|
||||
|
||||
private void Wl_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,4 +156,7 @@
|
||||
<data name="views.Text" xml:space="preserve">
|
||||
<value>Views:</value>
|
||||
</data>
|
||||
<data name="searchHeader" xml:space="preserve">
|
||||
<value>Search results</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -120,6 +120,9 @@
|
||||
<data name="box.PlaceholderText" xml:space="preserve">
|
||||
<value>Send a message</value>
|
||||
</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">
|
||||
<value>Moderator</value>
|
||||
</data>
|
||||
|
||||
@@ -138,12 +138,15 @@
|
||||
<data name="editorCancel.Content" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</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>
|
||||
</data>
|
||||
<data name="editorSubmin.Content" xml:space="preserve">
|
||||
<value>Submit</value>
|
||||
</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">
|
||||
<value>Failed to edit your commentary. Please, try again later.</value>
|
||||
</data>
|
||||
|
||||
@@ -138,6 +138,12 @@
|
||||
<data name="ext" xml:space="preserve">
|
||||
<value>Extension</value>
|
||||
</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">
|
||||
<value>Go to original</value>
|
||||
</data>
|
||||
|
||||
@@ -180,13 +180,13 @@
|
||||
<data name="signEx.Text" xml:space="preserve">
|
||||
<value>Sign in with existing account</value>
|
||||
</data>
|
||||
<data name="signIn.Text" xml:space="preserve">
|
||||
<data name="signIn.Content" xml:space="preserve">
|
||||
<value>Add account</value>
|
||||
</data>
|
||||
<data name="signNew.Text" xml:space="preserve">
|
||||
<value>Create new Google account</value>
|
||||
</data>
|
||||
<data name="signOut.Text" xml:space="preserve">
|
||||
<data name="signOut.Content" xml:space="preserve">
|
||||
<value>Log out</value>
|
||||
</data>
|
||||
<data name="subscriptions.Content" xml:space="preserve">
|
||||
|
||||
@@ -120,6 +120,9 @@
|
||||
<data name="addTo.Label" xml:space="preserve">
|
||||
<value>Add to</value>
|
||||
</data>
|
||||
<data name="always" xml:space="preserve">
|
||||
<value>Don't ask me again</value>
|
||||
</data>
|
||||
<data name="audio" xml:space="preserve">
|
||||
<value>Audio track</value>
|
||||
</data>
|
||||
@@ -129,6 +132,9 @@
|
||||
<data name="back.Text" xml:space="preserve">
|
||||
<value>Go back for 10 seconds</value>
|
||||
</data>
|
||||
<data name="cancel" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="cast.Text" xml:space="preserve">
|
||||
<value>Cast to device</value>
|
||||
</data>
|
||||
@@ -141,12 +147,18 @@
|
||||
<data name="comments.Header" xml:space="preserve">
|
||||
<value>Comments</value>
|
||||
</data>
|
||||
<data name="countdown.Text" xml:space="preserve">
|
||||
<value>Stream starts in:</value>
|
||||
</data>
|
||||
<data name="desc" xml:space="preserve">
|
||||
<value>Description</value>
|
||||
</data>
|
||||
<data name="download.Label" xml:space="preserve">
|
||||
<value>Download video</value>
|
||||
</data>
|
||||
<data name="end" xml:space="preserve">
|
||||
<value>Ends at:</value>
|
||||
</data>
|
||||
<data name="exitminiview.Text" xml:space="preserve">
|
||||
<value>Exit compact view mode</value>
|
||||
</data>
|
||||
@@ -156,9 +168,15 @@
|
||||
<data name="fwd.Text" xml:space="preserve">
|
||||
<value>Skip forward for 30 seconds</value>
|
||||
</data>
|
||||
<data name="generatedCaption" xml:space="preserve">
|
||||
<value>(Auto-generated)</value>
|
||||
</data>
|
||||
<data name="goLive.Text" xml:space="preserve">
|
||||
<value>Go to live broadcast</value>
|
||||
</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">
|
||||
<value>Maximize</value>
|
||||
</data>
|
||||
@@ -174,6 +192,9 @@
|
||||
<data name="next.Text" xml:space="preserve">
|
||||
<value>Next video</value>
|
||||
</data>
|
||||
<data name="no" xml:space="preserve">
|
||||
<value>No</value>
|
||||
</data>
|
||||
<data name="openWeb.Label" xml:space="preserve">
|
||||
<value>Open in browser</value>
|
||||
</data>
|
||||
@@ -195,15 +216,30 @@
|
||||
<data name="related.Header" xml:space="preserve">
|
||||
<value>Suggestons</value>
|
||||
</data>
|
||||
<data name="schedule.Text" xml:space="preserve">
|
||||
<value>Stream schedule:</value>
|
||||
</data>
|
||||
<data name="share.Label" xml:space="preserve">
|
||||
<value>Share</value>
|
||||
</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">
|
||||
<value>Elapsed time since stream start</value>
|
||||
</data>
|
||||
<data name="subs.Text" xml:space="preserve">
|
||||
<value>Captions</value>
|
||||
</data>
|
||||
<data name="subsSelector.Header" xml:space="preserve">
|
||||
<value>Language</value>
|
||||
</data>
|
||||
<data name="subsSelector.PlaceholderText" xml:space="preserve">
|
||||
<value>No captions are available</value>
|
||||
</data>
|
||||
@@ -213,7 +249,19 @@
|
||||
<data name="subsSwitch.OnContent" xml:space="preserve">
|
||||
<value>Captions</value>
|
||||
</data>
|
||||
<data name="upcomingHeader.Text" xml:space="preserve">
|
||||
<value>Stream hasn't started yet</value>
|
||||
</data>
|
||||
<data name="volume.Text" xml:space="preserve">
|
||||
<value>Volume</value>
|
||||
</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>
|
||||
@@ -156,4 +156,7 @@
|
||||
<data name="views.Text" xml:space="preserve">
|
||||
<value>Просмотры: </value>
|
||||
</data>
|
||||
<data name="searchHeader" xml:space="preserve">
|
||||
<value>Результаты поиска</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -120,6 +120,9 @@
|
||||
<data name="box.PlaceholderText" xml:space="preserve">
|
||||
<value>Отправить сообщение</value>
|
||||
</data>
|
||||
<data name="failed" xml:space="preserve">
|
||||
<value>Не удалось отправить ваше сообщение. Это могло произойти из-за включенного в этом чате замедленного режима. Пожалуйста, попробуйте позже. P.S. Отчет об ошибке был отправлен разработчикам</value>
|
||||
</data>
|
||||
<data name="moder.Text" xml:space="preserve">
|
||||
<value>Модератор</value>
|
||||
</data>
|
||||
|
||||
@@ -138,12 +138,15 @@
|
||||
<data name="editorCancel.Content" xml:space="preserve">
|
||||
<value>Отмена</value>
|
||||
</data>
|
||||
<data name="editorDelete.Content" xml:space="preserve">
|
||||
<data name="editorDelete.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Удалить комментарий</value>
|
||||
</data>
|
||||
<data name="editorSubmin.Content" xml:space="preserve">
|
||||
<value>Отправить</value>
|
||||
</data>
|
||||
<data name="failedDelete" xml:space="preserve">
|
||||
<value>Не удалось удалить комментарий. Пожалуйста, попробуйте позже</value>
|
||||
</data>
|
||||
<data name="failedEdit" xml:space="preserve">
|
||||
<value>Не удалось изменить комментарий. Пожалуйста, попробуйте позже</value>
|
||||
</data>
|
||||
|
||||
@@ -138,6 +138,12 @@
|
||||
<data name="ext" xml:space="preserve">
|
||||
<value>Расширение</value>
|
||||
</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">
|
||||
<value>Открыть оригинал</value>
|
||||
</data>
|
||||
|
||||
@@ -180,13 +180,13 @@
|
||||
<data name="signEx.Text" xml:space="preserve">
|
||||
<value>Войти с помощью существующего аккаунта Google</value>
|
||||
</data>
|
||||
<data name="signIn.Text" xml:space="preserve">
|
||||
<data name="signIn.Content" xml:space="preserve">
|
||||
<value>Войти в аккаунт</value>
|
||||
</data>
|
||||
<data name="signNew.Text" xml:space="preserve">
|
||||
<value>Создать новый аккаунт Google</value>
|
||||
</data>
|
||||
<data name="signOut.Text" xml:space="preserve">
|
||||
<data name="signOut.Content" xml:space="preserve">
|
||||
<value>Выйти</value>
|
||||
</data>
|
||||
<data name="subscriptions.Content" xml:space="preserve">
|
||||
|
||||
@@ -120,6 +120,9 @@
|
||||
<data name="addTo.Label" xml:space="preserve">
|
||||
<value>Добавить в</value>
|
||||
</data>
|
||||
<data name="always" xml:space="preserve">
|
||||
<value>Больше не спрашивать</value>
|
||||
</data>
|
||||
<data name="audio" xml:space="preserve">
|
||||
<value>Аудио дорожка</value>
|
||||
</data>
|
||||
@@ -129,6 +132,9 @@
|
||||
<data name="back.Text" xml:space="preserve">
|
||||
<value>Назад на 10 секунд</value>
|
||||
</data>
|
||||
<data name="cancel" xml:space="preserve">
|
||||
<value>Отмена</value>
|
||||
</data>
|
||||
<data name="cast.Text" xml:space="preserve">
|
||||
<value>Отправить на устройство</value>
|
||||
</data>
|
||||
@@ -141,12 +147,18 @@
|
||||
<data name="comments.Header" xml:space="preserve">
|
||||
<value>Комментарии</value>
|
||||
</data>
|
||||
<data name="countdown.Text" xml:space="preserve">
|
||||
<value>Стрим начнется через:</value>
|
||||
</data>
|
||||
<data name="desc" xml:space="preserve">
|
||||
<value>Описание</value>
|
||||
</data>
|
||||
<data name="download.Label" xml:space="preserve">
|
||||
<value>Скачать видео</value>
|
||||
</data>
|
||||
<data name="end" xml:space="preserve">
|
||||
<value>Конец:</value>
|
||||
</data>
|
||||
<data name="exitminiview.Text" xml:space="preserve">
|
||||
<value>Развернуть</value>
|
||||
</data>
|
||||
@@ -156,9 +168,15 @@
|
||||
<data name="fwd.Text" xml:space="preserve">
|
||||
<value>Вперед на 30 секунд</value>
|
||||
</data>
|
||||
<data name="generatedCaption" xml:space="preserve">
|
||||
<value>(Авто-перевод)</value>
|
||||
</data>
|
||||
<data name="goLive.Text" xml:space="preserve">
|
||||
<value>Перейти к прямому эфиру</value>
|
||||
</data>
|
||||
<data name="matureText" xml:space="preserve">
|
||||
<value>Этот контент может быть неподходящим для лиц младше 18 лет</value>
|
||||
</data>
|
||||
<data name="maximize.Text" xml:space="preserve">
|
||||
<value>Развернуть</value>
|
||||
</data>
|
||||
@@ -174,6 +192,9 @@
|
||||
<data name="next.Text" xml:space="preserve">
|
||||
<value>Следующее видео</value>
|
||||
</data>
|
||||
<data name="no" xml:space="preserve">
|
||||
<value>Нет</value>
|
||||
</data>
|
||||
<data name="openWeb.Label" xml:space="preserve">
|
||||
<value>Открыть в браузере</value>
|
||||
</data>
|
||||
@@ -195,15 +216,30 @@
|
||||
<data name="related.Header" xml:space="preserve">
|
||||
<value>Похожее</value>
|
||||
</data>
|
||||
<data name="schedule.Text" xml:space="preserve">
|
||||
<value>Расписание эфира</value>
|
||||
</data>
|
||||
<data name="share.Label" xml:space="preserve">
|
||||
<value>Поделиться</value>
|
||||
</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">
|
||||
<value>Время, прошедшее с начала стрима</value>
|
||||
</data>
|
||||
<data name="subs.Text" xml:space="preserve">
|
||||
<value>Субтитры</value>
|
||||
</data>
|
||||
<data name="subsSelector.Header" xml:space="preserve">
|
||||
<value>Язык субтитров</value>
|
||||
</data>
|
||||
<data name="subsSelector.PlaceholderText" xml:space="preserve">
|
||||
<value>Нет доступных субтитров</value>
|
||||
</data>
|
||||
@@ -213,7 +249,19 @@
|
||||
<data name="subsSwitch.OnContent" xml:space="preserve">
|
||||
<value>Субтитры</value>
|
||||
</data>
|
||||
<data name="upcomingHeader.Text" xml:space="preserve">
|
||||
<value>Прямой эфир еще не начался</value>
|
||||
</data>
|
||||
<data name="volume.Text" xml:space="preserve">
|
||||
<value>Громкость</value>
|
||||
</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>
|
||||
@@ -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=""/>
|
||||
</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=""/>
|
||||
</Button>
|
||||
<Button x:Name="CastButton">
|
||||
<FontIcon Glyph=""/>
|
||||
</Button>
|
||||
<Button x:Name="CompactOverlayButton" VerticalAlignment="Top" HorizontalAlignment="Right">
|
||||
<FontIcon Glyph=""/>
|
||||
</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=""/>
|
||||
</Button>
|
||||
<Button x:Name="compactClose" VerticalAlignment="Top" HorizontalAlignment="Right">
|
||||
<FontIcon Glyph=""/>
|
||||
</Button>
|
||||
<Button Height="32" Width="50" Margin="0,0,48,0" VerticalAlignment="Top" HorizontalAlignment="Right" Visibility="Collapsed" FontFamily="Segoe MDL2 Assets" Content="" 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=""/>
|
||||
<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=""/>
|
||||
</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=""/>
|
||||
</Button>
|
||||
<Button x:Name="SkipForwardButton">
|
||||
<FontIcon Glyph=""/>
|
||||
</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>
|
||||
|
After Width: | Height: | Size: 1023 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 1010 KiB |