Archived
1
0

Fresh start

This commit is contained in:
Michael Gordeev
2019-07-19 19:24:20 +03:00
parent 3ded1f4f18
commit d6d37151b8
100 changed files with 30 additions and 10743 deletions
+1 -160
View File
@@ -1,171 +1,12 @@
using Google.Apis.Auth.OAuth2;
using Google.Apis.Oauth2.v2;
using Google.Apis.Services;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Windows.ApplicationModel.Background;
using Windows.Storage;
using Windows.UI.Notifications;
using Windows.ApplicationModel.Background;
namespace FoxTube.Background
{
public sealed class BackgroundProcessor : IBackgroundTask
{
private DateTime lastCheck;
private readonly ApplicationDataContainer settings = ApplicationData.Current.RoamingSettings;
dynamic prefs;
BackgroundTaskDeferral def;
public async void Run(IBackgroundTaskInstance taskInstance)
{
try
{
def = taskInstance.GetDeferral();
if (settings.Values["lastCheck"] == null)
{
settings.Values["lastCheck"] = DateTime.Now.ToString();
def.Complete();
return;
}
else
lastCheck = DateTime.Parse(settings.Values["lastCheck"] as string);
prefs = JsonConvert.DeserializeObject<dynamic>(settings.Values["settings"] as string);
if ((bool)prefs.devNotifications)
CheckAnnouncements();
if ((bool)prefs.videoNotifications && (bool)prefs.hasAccount)
await CheckAccount();
}
finally
{
settings.Values["lastCheck"] = DateTime.Now.ToString();
def.Complete();
}
}
async Task CheckAccount()
{
try
{
UserCredential Credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = "349735264870-2ekqlm0a4mkg3mmrfcv90s3qp3o15dq0.apps.googleusercontent.com",
ClientSecret = "BkVZOAaCU2Zclf0Zlicg6y2_"
},
new[]
{
Oauth2Service.Scope.UserinfoProfile,
Oauth2Service.Scope.UserinfoEmail,
YouTubeService.Scope.YoutubeForceSsl,
YouTubeService.Scope.Youtube,
YouTubeService.Scope.YoutubeUpload,
YouTubeService.Scope.YoutubeReadonly,
YouTubeService.Scope.Youtubepartner
},
"user",
CancellationToken.None);
if (Credential == null)
return;
YouTubeService service = new YouTubeService(new BaseClientService.Initializer
{
HttpClientInitializer = Credential,
ApplicationName = "FoxTube"
});
List<Subscription> subscriptions = new List<Subscription>();
List<SearchResult> results = new List<SearchResult>();
SubscriptionsResource.ListRequest subRequest = service.Subscriptions.List("snippet");
subRequest.Mine = true;
subRequest.MaxResults = 50;
subRequest.Order = SubscriptionsResource.ListRequest.OrderEnum.Relevance;
SubscriptionListResponse subResponse;
string nextToken = null;
do
{
subRequest.PageToken = nextToken;
subResponse = await subRequest.ExecuteAsync();
foreach (Subscription s in subResponse.Items)
subscriptions.Add(s);
nextToken = subResponse.NextPageToken;
} while (!string.IsNullOrWhiteSpace(nextToken));
foreach (Subscription item in subscriptions)
{
SearchResource.ListRequest request = service.Search.List("snippet");
request.PublishedAfter = lastCheck;
request.ChannelId = item.Snippet.ResourceId.ChannelId;
request.Type = "video";
request.MaxResults = 5;
SearchListResponse response = await request.ExecuteAsync();
foreach (SearchResult i in response.Items)
{
results.Add(i);
if (i.Snippet.LiveBroadcastContent == "live")
ToastNotificationManager.CreateToastNotifier().Show(
Notification.GetStreamToast(i.Id.VideoId, i.Snippet.ChannelId, i.Snippet.Title.ConvertEscapeSymbols(), i.Snippet.ChannelTitle, i.Snippet.Thumbnails.Medium.Url, i.Snippet.PublishedAt.Value, item.Snippet.Thumbnails.Default__.Url));
else if (i.Snippet.LiveBroadcastContent == "upcoming")
ToastNotificationManager.CreateToastNotifier().Show(
Notification.GetUpcomingToast(i.Id.VideoId, i.Snippet.ChannelId, i.Snippet.Title.ConvertEscapeSymbols(), i.Snippet.ChannelTitle, i.Snippet.Thumbnails.Medium.Url, i.Snippet.PublishedAt.Value, item.Snippet.Thumbnails.Default__.Url));
else
ToastNotificationManager.CreateToastNotifier().Show(
Notification.GetVideoToast(i.Id.VideoId, i.Snippet.ChannelId, i.Snippet.Title.ConvertEscapeSymbols(), i.Snippet.ChannelTitle, i.Snippet.Thumbnails.Medium.Url, i.Snippet.PublishedAt.Value, item.Snippet.Thumbnails.Default__.Url));
}
}
results.OrderBy(i => i.Snippet.PublishedAt);
TileUpdater updater = TileUpdateManager.CreateTileUpdaterForApplication();
updater.EnableNotificationQueue(true);
updater.Clear();
for (int i = 0; i < 5 && i < results.Count; i++)
updater.Update(Tiles.GetTileLayout(System.Security.SecurityElement.Escape(results[i].Snippet.Title.ConvertEscapeSymbols()), System.Security.SecurityElement.Escape(results[i].Snippet.ChannelTitle), results[i].Snippet.Thumbnails.Medium.Url.Replace("&", "%26"), subscriptions.Find(x => x.Snippet.ResourceId.ChannelId == results[i].Snippet.ChannelId).Snippet.Thumbnails.Medium.Url));
}
catch { }
}
async void CheckAnnouncements()
{
try
{
XmlDocument doc = new XmlDocument();
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(item["id"].InnerText,
item["header"][(string)prefs.language].InnerText,
item["content"][(string)prefs.language].InnerText,
item["thumbnail"].InnerText,
item["avatar"].InnerText));
}
catch { }
}
}
public static class Ext
{
public static string ConvertEscapeSymbols(this string str)
{
return str.Replace("&quot;", "\"").Replace("&apos;", "'").Replace("&lt;", "<").Replace("&gt;", ">").Replace("&amp;", "&").Replace("&#34;", "\"").Replace("&#39;", "'").Replace("&#60;", "<").Replace("&#62;", ">").Replace("&#38;", "&");
}
}
}
@@ -108,7 +108,6 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="BackgroundProcessor.cs" />
<Compile Include="ResourceCreators.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
-197
View File
@@ -1,197 +0,0 @@
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;
using Windows.UI.Notifications;
namespace FoxTube.Background
{
public static class Notification
{
private static readonly Dictionary<string, string> languagePack = LoadPack();
private static Dictionary<string, string> LoadPack()
{
dynamic saved = JsonConvert.DeserializeObject<dynamic>(ApplicationData.Current.RoamingSettings.Values["settings"] as string);
string hl = saved.language;
if (hl == "ru-RU")
return new Dictionary<string, string>()
{
{ "addLater", "Посмотреть позже" },
{ "changelog", "Список изменений" },
{ "changelogHeader", "Что нового в версии" },
{ "videoContent", "загрузил новое видео" },
{ "live", "ПРЯМОЙ ЭФИР" },
{ "upcoming", "Запланирован" },
{ "liveContent", "начал прямой эфир" },
{ "upcomingContent", "запланировал прямой эфир" },
{ "goChannel", "Открыть канал" }
};
else
return new Dictionary<string, string>()
{
{ "addLater", "Add to Watch later" },
{ "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" }
};
}
public static ToastNotification GetChangelogToast(string version)
{
XmlDocument template = new XmlDocument();
template.LoadXml($@"<toast activationType='foreground' launch='changelog|{version}'>
<visual>
<binding template='ToastGeneric'>
<image placement='hero' src='http://foxgame-studio.000webhostapp.com/FoxTubeAssets/WhatsNewThumb.png'/>
<image placement='appLogoOverride' hint-crop='circle' src='http://foxgame-studio.000webhostapp.com/FoxTubeAssets/NewsAvatar.png'/>
<text>{languagePack["changelog"]}</text>
<text>{languagePack["changelogHeader"]} {version}</text>
</binding>
</visual>
</toast>");
return new ToastNotification(template);
}
public static ToastNotification GetVideoToast(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>{System.Security.SecurityElement.Escape(title)}</text>
<text>{System.Security.SecurityElement.Escape(channel)} {languagePack["videoContent"]}</text>
</binding>
</visual>
<actions>
<action content='{languagePack["addLater"]}' activationType='background' arguments='later|{id}'/>
<action content='{languagePack["goChannel"]}' activationType='foreground' arguments='channel|{channelId}'/>
</actions>
</toast>");
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)
{
XmlDocument template = new XmlDocument();
template.LoadXml($@"<toast activationType='foreground' launch='inbox|{id}'>
<visual>
<binding template='ToastGeneric'>
<image placement='hero' src='{thumbnail ?? "http://foxgame-studio.000webhostapp.com/FoxTubeAssets/AnnouncementThumb.png"}'/>
<image placement='appLogoOverride' hint-crop='circle' src='{avatar ?? "http://foxgame-studio.000webhostapp.com/FoxTubeAssets/LogoAvatar.png"}'/>
<text>{header}</text>
<text hint-maxLines='5'>{content}</text>
</binding>
</visual>
</toast>");
return new ToastNotification(template);
}
}
public static class Tiles
{
public static TileNotification GetTileLayout(string title, string channel, string thumbnail, string avatar)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml($@" <tile>
<visual>
<binding template='TileMedium' branding='none'>
<image placement='peek' src='{avatar?.Replace("&", "%26")}'/>
<image placement='background' src='{thumbnail?.Replace("&", "%26")}'/>
<text hint-style='base' hint-overlay='60'>{channel}</text>
<text hint-wrap='true' hint-style='captionSubtle'>{title}</text>
</binding>
<binding template='TileWide' Branding='nameAndLogo'>
<image placement='background' hint-overlay='60' src='{thumbnail?.Replace("&", "%26")}'/>
<group>
<subgroup hint-weight='33'>
<image src='{avatar?.Replace("&", "%26")}' hint-crop='circle' />
</subgroup>
<subgroup hint-textStacking='center'>
<text hint-style='base'>{channel}</text>
<text hint-wrap='true' hint-style='captionSubtle'>{title}</text>
</subgroup>
</group>
</binding>
<binding template='TileLarge' hint-textStacking='top' Branding='nameAndLogo'>
<image placement='background' hint-overlay='60' src='{thumbnail?.Replace("&", "%26")}'/>
<group>
<subgroup hint-weight='33'>
<image src='{avatar?.Replace("&", "%26")}' hint-crop='circle' />
</subgroup>
<subgroup hint-textStacking='center'>
<text hint-style='base'>{channel}</text>
<text hint-wrap='true' hint-style='captionSubtle'>{title}</text>
</subgroup>
</group>
</binding>
</visual>
</tile>");
return new TileNotification(doc);
}
}
}
+7 -47
View File
@@ -1,8 +1,6 @@
using FoxTube.Classes;
using Microsoft.AppCenter.Analytics;
using System.Collections.Generic;
using FoxTube.Pages;
using System.Diagnostics;
using Windows.ApplicationModel.Activation;
using Windows.Globalization;
using Windows.UI.Notifications;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@@ -13,32 +11,10 @@ namespace FoxTube
{
public App()
{
SettingsStorage.LoadData();
switch (SettingsStorage.Theme)
{
case 0:
RequestedTheme = ApplicationTheme.Light;
break;
case 1:
RequestedTheme = ApplicationTheme.Dark;
break;
}
ApplicationLanguages.PrimaryLanguageOverride = SettingsStorage.Language;
InitializeComponent();
Suspending += (s, e) => Processes.SuspendApp();
UnhandledException += (s, e) => Analytics.TrackEvent("The app crashed", new Dictionary<string, string>()
{
{ "Exception", e.Exception.GetType().ToString() },
{ "Details", e.Message },
{ "StackTrace", e.Exception.StackTrace }
});
Processes.InitializeApp();
}
protected override void OnLaunched(LaunchActivatedEventArgs e)
/*protected override void OnLaunched(LaunchActivatedEventArgs e)
{
if (!(Window.Current.Content is Frame))
Window.Current.Content = new Frame();
@@ -50,19 +26,11 @@ namespace FoxTube
Window.Current.Activate();
}
}
}*/
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
var deferral = args.TaskInstance.GetDeferral();
base.OnBackgroundActivated(args);
if (args.TaskInstance.Task.Name == "FoxtubeToastBackground" || !(args.TaskInstance.TriggerDetails is ToastNotificationActionTriggerDetail details))
return;
Processes.ProcessToast(details.Argument);
deferral.Complete();
}
protected override void OnActivated(IActivatedEventArgs e)
@@ -70,26 +38,18 @@ namespace FoxTube
base.OnActivated(e);
//TODO: Check this shit
/*if (!(Window.Current.Content is Frame rootFrame))
if (!(Window.Current.Content is Frame rootFrame))
{
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
Window.Current.Content = rootFrame;
}
if (rootFrame.Content == null)
rootFrame.Navigate(typeof(MainPage));
Window.Current.Activate();*/
Window.Current.Activate();
if (e.Kind != ActivationKind.ToastNotification)
return;
if (SecretsVault.IsAuthorized)
Processes.ProcessToast((e as ToastNotificationActivatedEventArgs).Argument);
else
SecretsVault.AuthorizationStateChanged += (arg) => Processes.ProcessToast((e as ToastNotificationActivatedEventArgs).Argument);
Debug.WriteLine("Hello, World");
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

-10
View File
@@ -1,10 +0,0 @@
using Windows.UI.Xaml.Controls;
namespace FoxTube.Classes
{
class AdaptiveCommandBar : CommandBar
{
public AdaptiveCommandBar() =>
ClosedDisplayMode = SettingsStorage.AppBarClosedMode;
}
}
-74
View File
@@ -1,74 +0,0 @@
using System;
using System.Collections.Generic;
using Windows.Storage;
using Newtonsoft.Json;
using YoutubeExplode.Models.MediaStreams;
using FoxTube.Controls;
using FoxTube.Pages;
using Microsoft.AppCenter.Analytics;
using YoutubeExplode.Models;
namespace FoxTube
{
public static class DownloadAgent
{
public static List<DownloadItem> Items { get; set; } = new List<DownloadItem>();
static readonly ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
public static Downloads Page { get; set; }
public static StorageFolder Downloads { get; set; }
public static async void Initialize()
{
Items.Clear();
try
{
Downloads = await KnownFolders.VideosLibrary.CreateFolderAsync("FoxTube", CreationCollisionOption.OpenIfExists);
List<DownloadItemContainer> containers = JsonConvert.DeserializeObject<List<DownloadItemContainer>>((string)settings.Values[$"downloads"]);
containers.ForEach(i => Items.Add(new DownloadItem(i)));
}
catch (Exception e)
{
settings.Values["downloads"] = JsonConvert.SerializeObject(new List<DownloadItemContainer>());
Analytics.TrackEvent("Failed to load downloads history", new Dictionary<string, string>
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "StackTrace", e.StackTrace }
});
}
}
public static void Add((MediaStreamInfo info, Video meta, string qualty) e) =>
Items.Insert(0, new DownloadItem(e.info, e.meta, e.qualty));
public static void Remove(DownloadItem item)
{
Page?.Remove(item);
Items.Remove(item);
}
public static void Cancel(string id)
{
DownloadItem item = Items.Find(i => i.Container.Id == id);
if (item != null)
item.Cancel();
}
public static void QuitPrompt()
{
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);
settings.Values[$"downloads"] = data;
}
}
}
-34
View File
@@ -1,34 +0,0 @@
using Google.Apis.YouTube.v3.Data;
using System;
using System.Threading.Tasks;
namespace FoxTube.Classes
{
public delegate void SubscriptionsChangedEventHandler(string action, Subscription subscription);
public delegate void AuthorizationChangedEventHandler(bool? isAuthorized);
public delegate void MinimodeChangedEventHandler(bool isOn);
public delegate void SimpleEventHandler();
public delegate void PlaylistItemChangedEventHandler(string id);
public delegate void NavigationEventHanlder(Type sourcePageType, object parameter);
public static class Extensions
{
public static bool Belongs(this double num, double x1, double x2)
{
return num > x1 && num < x2;
}
}
public interface INavigationPage
{
object Parameter { get; set; }
}
public interface ICard
{
Task Initialize();
void ItemClicked();
}
public enum PlayerDisplayState { Normal, Minimized, Compact }
}
-87
View File
@@ -1,87 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using Windows.Storage;
namespace FoxTube
{
public class HistoryItem
{
public string Id { get; set; }
public TimeSpan LeftOn { get; set; } = TimeSpan.FromSeconds(0);
}
public static class HistorySet
{
public static List<HistoryItem> Items { get; set; } = new List<HistoryItem>();
public static void Update(HistoryItem item)
{
if (!SecretsVault.IsAuthorized)
return;
Items.RemoveAll(i => i.Id == item.Id);
Items.Insert(0, item);
if (Items.Count > 200)
Items.RemoveRange(200, Items.Count - 200);
Save();
}
public static void Delete(HistoryItem item)
{
if (!SecretsVault.IsAuthorized)
return;
Items.Remove(item);
Save();
}
public static void Clear()
{
if (!SecretsVault.IsAuthorized)
return;
Items.Clear();
Save();
}
public static void Save()
{
List<HistoryItem>[] parts = new List<HistoryItem>[4]
{
new List<HistoryItem>(), new List<HistoryItem>(),
new List<HistoryItem>(), new List<HistoryItem>()
};
foreach(HistoryItem i in Items)
if (parts[0].Count < 50)
parts[0].Add(i);
else
{
if (parts[1].Count < 50)
parts[1].Add(i);
else
{
if (parts[2].Count < 50)
parts[2].Add(i);
else
parts[3].Add(i);
}
}
for(int k = 0; k < parts.Length; k++)
ApplicationData.Current.RoamingSettings.Values[$"history-{SecretsVault.AccountId}-level{k}"] = JsonConvert.SerializeObject(parts[k]);
}
public static void Load()
{
try
{
for (int k = 0; k < 4; k++)
Items.AddRange(JsonConvert.DeserializeObject<List<HistoryItem>>(ApplicationData.Current.RoamingSettings.Values[$"history-{SecretsVault.AccountId}-level{k}"] as string));
}
catch { }
}
}
}
-42
View File
@@ -1,42 +0,0 @@
using System;
using Windows.ApplicationModel.Resources;
namespace FoxTube.Classes
{
public enum InboxItemType { Default, PatchNote }
public class InboxItem
{
public InboxItemType Type { get; set; } = InboxItemType.Default;
public DateTime TimeStamp { get; set; }
public string TimeStampString => Type == InboxItemType.PatchNote ? TimeStamp.ToShortDateString() : TimeStamp.ToString();
public string Subject { get; set; }
public string Content { get; set; }
public string Id { get; set; }
private ResourceLoader resources = ResourceLoader.GetForCurrentView("Inbox");
public string Icon => Type == InboxItemType.PatchNote ? "\xE728" : "\xE119";
public string Subtitle => Type == InboxItemType.PatchNote ? resources.GetString("changelog") : resources.GetString("dev");
public string Title => Type == InboxItemType.PatchNote ? $"{resources.GetString("whatsnew")}{Id}" : Subject;
public InboxItem(string version, string content, DateTime timeStamp)
{
Type = InboxItemType.PatchNote;
Content = content;
TimeStamp = timeStamp;
Id = version;
}
public InboxItem(string title, string content, DateTime timeStamp, string id, string header)
{
Type = InboxItemType.Default;
Content = header + "\n\n" + content;
Subject = title;
TimeStamp = timeStamp;
Id = id;
}
}
}
-304
View File
@@ -1,304 +0,0 @@
using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser;
using Google.Apis.YouTube.v3.Data;
using Microsoft.AppCenter.Analytics;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using YoutubeExplode.Models.MediaStreams;
namespace FoxTube.Classes
{
public static class ManifestGenerator
{
static readonly StorageFolder roaming = ApplicationData.Current.RoamingFolder;
public static async Task<Uri> GetManifest(Video meta, VideoStreamInfo requestedQuality, MediaStreamInfoSet list)
{
StorageFile manifest;
try { manifest = await roaming.CreateFileAsync("manifest.mpd", CreationCollisionOption.ReplaceExisting); }
catch { manifest = await roaming.CreateFileAsync("manifest.mpd", CreationCollisionOption.GenerateUniqueName); }
try
{
XmlDocument doc = new XmlDocument();
XmlElement mpd = doc.CreateElement("MPD");
mpd.SetAttribute("mediaPresentationDuration", meta.ContentDetails.Duration);
mpd.SetAttribute("minBufferTime", "PT2S");
mpd.SetAttribute("profiles", "urn:mpeg:dash:profile:isoff-on-demand:2011");
mpd.SetAttribute("type", "static");
XmlElement period = doc.CreateElement("Period");
XmlElement videoSet = doc.CreateElement("AdaptationSet");
XmlElement videoMeta = doc.CreateElement("ContentComponent");
videoMeta.SetAttribute("contentType", "video");
videoMeta.SetAttribute("id", "1");
videoSet.AppendChild(videoMeta);
StreamInfo streamInfo = await GetInfoAsync(meta, requestedQuality);
foreach (var i in streamInfo.Video)
videoSet.AppendChild(GetVideoPresentation(doc, i));
XmlElement audioSet = doc.CreateElement("AdaptationSet");
XmlElement audioMeta = doc.CreateElement("ContentComponent");
audioMeta.SetAttribute("contentType", "audio");
audioMeta.SetAttribute("id", "2");
audioSet.AppendChild(audioMeta);
foreach (var i in streamInfo.Audio)
audioSet.AppendChild(GetAudioPresentation(doc, i));
doc.AppendChild(mpd);
mpd.AppendChild(period);
period.AppendChild(videoSet);
period.AppendChild(audioSet);
doc.Save(await manifest.OpenStreamForWriteAsync());
return $"ms-appdata:///roaming/{manifest.Name}".ToUri();
}
catch (Exception e)
{
Analytics.TrackEvent("Failed to generate manifest", new Dictionary<string, string>
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Video ID", meta.Id },
{ "Requested quality", requestedQuality.VideoQualityLabel },
{ "StackTrace", e.StackTrace }
});
return null;
}
}
static XmlElement GetVideoPresentation(XmlDocument doc, StreamInfo.VideoInfo info)
{
XmlElement representation = doc.CreateElement("Representation");
representation.SetAttribute("bandwidth", GetBandwidth(info.Label));
representation.SetAttribute("id", info.Itag);
representation.SetAttribute("mimeType", info.MimeType);
representation.SetAttribute("codecs", info.Codecs);
representation.SetAttribute("fps", info.Fps);
representation.SetAttribute("height", info.Height);
representation.SetAttribute("width", info.Width);
XmlElement baseUrl = doc.CreateElement("BaseURL");
baseUrl.InnerText = info.Url;
representation.AppendChild(baseUrl);
XmlElement segmentBase = doc.CreateElement("SegmentBase");
segmentBase.SetAttribute("indexRange", info.IndexRange);
representation.AppendChild(segmentBase);
XmlElement initialization = doc.CreateElement("Initialization");
initialization.SetAttribute("range", info.InitRange);
segmentBase.AppendChild(initialization);
return representation;
}
static XmlElement GetAudioPresentation(XmlDocument doc, StreamInfo.AudioInfo info)
{
XmlElement audio = doc.CreateElement("Representation");
audio.SetAttribute("bandwidth", "200000");
audio.SetAttribute("id", info.Itag);
audio.SetAttribute("sampleRate", info.SampleRate);
audio.SetAttribute("numChannels", info.ChannelsCount);
audio.SetAttribute("codecs", info.Codecs);
audio.SetAttribute("mimeType", info.MimeType);
XmlElement audioUrl = doc.CreateElement("BaseURL");
audioUrl.InnerText = info.Url;
audio.AppendChild(audioUrl);
XmlElement audioSegmentBase = doc.CreateElement("SegmentBase");
audioSegmentBase.SetAttribute("indexRange", info.IndexRange);
audio.AppendChild(audioSegmentBase);
XmlElement audioInit = doc.CreateElement("Initialization");
audioInit.SetAttribute("range", info.InitRange);
audioSegmentBase.AppendChild(audioInit);
return audio;
}
static async Task<StreamInfo> GetInfoAsync(Video info, VideoStreamInfo requestedQuality)
{
try
{
StreamInfo si = new StreamInfo();
HttpClient http = new HttpClient();
string response = await http.GetStringAsync($"https://youtube.com/watch?v={info.Id}&disable_polymer=true&bpctr=9999999999&hl=en");
IHtmlDocument videoEmbedPageHtml = new HtmlParser().ParseDocument(response);
string playerConfigRaw = Regex.Match(videoEmbedPageHtml.Source.Text,
@"ytplayer\.config = (?<Json>\{[^\{\}]*(((?<Open>\{)[^\{\}]*)+((?<Close-Open>\})[^\{\}]*)+)*(?(Open)(?!))\})")
.Groups["Json"].Value;
JToken playerConfigJson = JToken.Parse(playerConfigRaw);
var playerResponseRaw = playerConfigJson.SelectToken("args.player_response").Value<string>();
JToken playerResponseJson = JToken.Parse(playerResponseRaw);
string errorReason = playerResponseJson.SelectToken("playabilityStatus.reason")?.Value<string>();
if (!string.IsNullOrWhiteSpace(errorReason))
throw new InvalidDataException($"Video [{info.Id}] is unplayable. Reason: {errorReason}");
List<Dictionary<string, string>> adaptiveStreamInfosUrl = playerConfigJson.SelectToken("args.adaptive_fmts")?.Value<string>().Split(',').Select(SplitQuery).ToList();
List<Dictionary<string, string>> video =
requestedQuality == null ?
adaptiveStreamInfosUrl.FindAll(i => i.ContainsKey("quality_label")) :
adaptiveStreamInfosUrl.FindAll(i => i.ContainsValue(requestedQuality.VideoQualityLabel));
List<Dictionary<string, string>> audio = adaptiveStreamInfosUrl.FindAll(i => i.ContainsKey("audio_sample_rate"));
foreach (var i in video)
si.Video.Add(new StreamInfo.VideoInfo
{
IndexRange = i["index"],
Url = i["url"],
Itag = i["itag"],
Fps = i["fps"],
Height = i["size"].Split('x')[1],
Width = i["size"].Split('x')[0],
Codecs = i["type"].Split('"')[1],
MimeType = i["type"].Split(';')[0],
Label = i["quality_label"]
});
foreach (var i in audio)
si.Audio.Add(new StreamInfo.AudioInfo
{
ChannelsCount = i["audio_channels"],
IndexRange = i["index"],
SampleRate = i["audio_sample_rate"],
Codecs = i["type"].Split('"')[1],
MimeType = i["type"].Split(';')[0],
Url = i["url"],
Itag = i["itag"]
});
return si;
}
catch
{
return new StreamInfo();
}
}
static Dictionary<string, string> SplitQuery(string query)
{
Dictionary<string, string> dic = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
string[] paramsEncoded = query.TrimStart('?').Split("&");
foreach (string paramEncoded in paramsEncoded)
{
string param = WebUtility.UrlDecode(paramEncoded);
// Look for the equals sign
int equalsPos = param.IndexOf('=');
if (equalsPos <= 0)
continue;
// Get the key and value
string key = param.Substring(0, equalsPos);
string value = equalsPos < param.Length
? param.Substring(equalsPos + 1)
: string.Empty;
// Add to dictionary
dic[key] = value;
}
return dic;
}
static string GetBandwidth(string quality)
{
string parsed = quality.Split('p')[0];
switch (quality)
{
case "4320":
return $"16763040";
case "3072":
return $"11920384";
case "2880":
return $"11175360";
case "2160":
return $"8381520";
case "1440":
return $"5587680";
case "1080":
return $"4190760";
case "720":
return $"2073921";
case "480":
return $"869460";
case "360":
return $"686521";
case "240":
return $"264835";
case "144":
default:
return $"100000";
}
}
public static async Task<List<StreamQuality>> ResolveLiveSteream(string url)
{
List<StreamQuality> list = new List<StreamQuality>();
string playlistRaw = await new HttpClient().GetStringAsync(url);
List<string> streamsRaw = playlistRaw.Split("#EXT-X-STREAM-INF:").ToList();
streamsRaw.RemoveAt(0);
List<Dictionary<string, string>> streams = new List<Dictionary<string, string>>();
foreach (string i in streamsRaw)
{
Dictionary<string, string> item = new Dictionary<string, string>();
string[] par = i.Split('\n');
item.Add("URL", par[1]);
par = par[0].Split(',');
foreach (string k in par)
{
string[] pair = k.Split('=');
if (pair.Length < 2)
continue;
item[pair[0]] = pair[1];
}
streams.Add(item);
}
foreach (var i in streams)
{
StreamQuality item = new StreamQuality();
item.Resolution = $"{i["RESOLUTION"].Split('x')[1]}p";
item.Url = i["URL"].ToUri();
list.Add(item);
}
list.Add(new StreamQuality
{
Resolution = ResourceLoader.GetForCurrentView("VideoPage").GetString("/VideoPage/auto"),
Url = url.ToUri()
});
list.Reverse();
return list;
}
public static async void ClearRoaming()
{
IReadOnlyList<StorageFile> items = await roaming.GetFilesAsync();
foreach (StorageFile f in items)
await f.DeleteAsync(StorageDeleteOption.PermanentDelete);
}
}
}
-319
View File
@@ -1,319 +0,0 @@
using FoxTube.Classes;
using FoxTube.Controls.VideoPage;
using Google.Apis.YouTube.v3.Data;
using Microsoft.AppCenter.Analytics;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Mail;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using Windows.ApplicationModel.DataTransfer;
using Windows.ApplicationModel.Resources;
using Windows.Storage.Streams;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Documents;
using YoutubeExplode;
using YoutubeExplode.Models.MediaStreams;
namespace FoxTube
{
public delegate void Event();
public delegate void ObjectEventHandler(object sender = null, params object[] args);
public static class Methods
{
private static readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Methods");
public static Comments CommentsPage { get; set; }
public static MainPage MainPage => (Window.Current.Content as Frame).Content as MainPage;
public static async Task<bool> AddItemToWL(string id)
{
try
{
SecretsVault.RefreshToken();
PlaylistItem item = new PlaylistItem
{
Snippet = new PlaylistItemSnippet
{
ResourceId = new ResourceId
{
Kind = "youtube#video",
VideoId = id
},
PlaylistId = "WL"
}
};
await SecretsVault.Service.PlaylistItems.Insert(item, "snippet").ExecuteAsync();
return true;
}
catch (Exception e)
{
Analytics.TrackEvent("Failed to add video to WL", new Dictionary<string, string>
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Video ID", id },
{ "StackTrace", e.StackTrace }
});
return false;
}
}
public static Uri ToUri(this string url)
{
try { return string.IsNullOrWhiteSpace(url) ? null : new Uri(url); }
catch { return null; }
}
public static string GuardFromNull(string str) =>
str ?? string.Empty;
public static void SendMail(string content)
{
MailMessage msg = new MailMessage();
msg.To.Add("michael.xfox@outlook.com");
msg.From = new MailAddress(SecretsVault.EmailCredential.UserName);
msg.Subject = "[Automatic message] FoxTube service message";
msg.Body = content;
SmtpClient client = new SmtpClient("smtp.gmail.com", 587);
client.EnableSsl = true;
client.Credentials = SecretsVault.EmailCredential;
client.Send(msg);
}
public static List<object> ToReversedList(this Array array)
{
List<object> list = new List<object>();
foreach (object i in array)
list.Add(i);
list.Reverse();
return list;
}
public static void ForEach<T>(this IEnumerable<T> array, Action<T> action) =>
array.ToList().ForEach(action);
public static T Find<T>(this IEnumerable<T> array, Predicate<T> match) =>
array.ToList().Find(match);
public static List<T> FindAll<T>(this IEnumerable<T> array, Predicate<T> match) =>
array.ToList().FindAll(match);
public static string ReplaceInvalidChars(this string str, char newValue)
{
foreach (char i in Path.GetInvalidFileNameChars())
str = str.Replace(i, newValue);
return str;
}
[Obsolete("Use *YoutubeExplode.Models.Video instead*")]
public static TimeSpan GetDuration(this string str)
{
try
{
return XmlConvert.ToTimeSpan(str);
}
catch (FormatException)
{
TimeSpan time = XmlConvert.ToTimeSpan("PT" + str.Split('T')[1]);
TimeSpan date = TimeSpan.FromDays(int.Parse(str.Split('W')[0].Remove('P')) * 7);
date.Add(time);
return date;
}
catch (Exception e)
{
Analytics.TrackEvent("Failed to parse duration", new Dictionary<string, string>
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "StackTrace", e.StackTrace }
});
return TimeSpan.FromMilliseconds(0);
}
}
public static string GetAgo(DateTime dateTime)
{
TimeSpan span = DateTime.Now - dateTime;
if (span.TotalMinutes < 1)
return resources.GetString("/Methods/now");
else if (Math.Round(span.TotalMinutes) == 1)
return resources.GetString("/Methods/oneMinute");
else if (span.TotalMinutes < 60)
return Math.Round(span.TotalMinutes) + " " + resources.GetString("/Methods/minutes");
else if (Math.Round(span.TotalHours) == 1)
return resources.GetString("/Methods/oneHr");
else if (span.TotalHours < 24)
return Math.Round(span.TotalHours) + " " + resources.GetString("/Methods/hrs");
else if (Math.Round(span.TotalDays) == 1)
return resources.GetString("/Methods/oneDay");
else if (span.TotalDays < 7)
return Math.Round(span.TotalDays) + " " + resources.GetString("/Methods/days");
else if (Math.Round(span.TotalDays) == 7)
return resources.GetString("/Methods/oneWeek");
else if (span.TotalDays < 30)
return Math.Round(span.TotalDays / 7) + " " + resources.GetString("/Methods/weeks");
else if (Math.Round(span.TotalDays) == 30)
return resources.GetString("/Methods/oneMonth");
else if (Math.Round(span.TotalDays) < 365)
return Math.Round(span.TotalDays / 30) + " " + resources.GetString("/Methods/months");
else if (Math.Round(span.TotalDays / 365) == 365)
return resources.GetString("/Methods/oneYear");
else
return Math.Round(span.TotalDays / 365) + " " + resources.GetString("/Methods/years");
}
public static void FormatText(this 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);
foreach (string item in filter.Split(text))
{
if (link.IsMatch(item))
{
string str = item;
if (!str.Contains("http"))
str = str.Insert(0, "http://");
Hyperlink hl = new Hyperlink();
hl.Click += (s, arg) => ProcessLink(str);
hl.Inlines.Add(new Run { Text = item });
block.Inlines.Add(hl);
}
else if (mail.IsMatch(item))
{
Hyperlink hl = new Hyperlink { NavigateUri = $"mailto:{item}".ToUri() };
hl.Inlines.Add(new Run { Text = item });
block.Inlines.Add(hl);
}
else
block.Inlines.Add(new Run { Text = item });
}
}
public static string GetVideoQualityLabel(this VideoQuality quality)
{
switch (quality)
{
case VideoQuality.High1080:
return "1080p";
case VideoQuality.High1440:
return "1440p";
case VideoQuality.High2160:
return "2160p";
case VideoQuality.High2880:
return "2880p";
case VideoQuality.High3072:
return "3072p";
case VideoQuality.High4320:
return "4320p";
case VideoQuality.High720:
return "720p";
case VideoQuality.Low144:
return "144p";
case VideoQuality.Low240:
return "240p";
case VideoQuality.Medium360:
return "360p";
case VideoQuality.Medium480:
return "480p";
default:
return "Unknown";
}
}
public async static void ProcessLink(string url)
{
try
{
string type;
if (YoutubeClient.TryParseChannelId(url, out string output))
{
type = "channel";
goto LinkFound;
}
else if (YoutubeClient.TryParsePlaylistId(url, out output))
{
type = "playlist";
goto LinkFound;
}
else if (YoutubeClient.TryParseUsername(url, out output))
{
type = "user";
goto LinkFound;
}
else if (YoutubeClient.TryParseVideoId(url, out output))
{
type = "video";
goto LinkFound;
}
await Launcher.LaunchUriAsync(new Uri(url));
return;
LinkFound:
switch (type)
{
case "channel":
Navigation.GoToChannel(output);
break;
case "video":
Navigation.GoToVideo(output);
break;
case "playlist":
Navigation.GoToPlaylist(output);
break;
case "user":
Navigation.GoToChannel(await new YoutubeClient().GetChannelIdAsync(output));
break;
}
}
catch { }
}
public static void Share(DataRequestedEventArgs args, string thumbnail, string title, string url, string type)
{
DataRequest request = args.Request;
request.Data.Properties.Title = title;
request.Data.Properties.Description = $"{resources.GetString("/Methods/sharing")} {type}";
request.Data.SetText(title + "\n" + "#YouTube #FoxTube #SharedWithFoxTube");
request.Data.SetWebLink(url.ToUri());
request.Data.Properties.Thumbnail = RandomAccessStreamReference.CreateFromUri(thumbnail.ToUri());
request.Data.SetBitmap(RandomAccessStreamReference.CreateFromUri(thumbnail.ToUri()));
}
public static async Task<YoutubeExplode.Models.Playlist> GetHistory()
{
SecretsVault.RefreshToken();
return await new YoutubeClient(SecretsVault.HttpClient).GetPlaylistAsync("HL");
}
public static async Task<YoutubeExplode.Models.Playlist> GetLater()
{
SecretsVault.RefreshToken();
return await new YoutubeClient(SecretsVault.HttpClient).GetPlaylistAsync("WL");
}
}
}
-36
View File
@@ -1,36 +0,0 @@
using FoxTube.Pages;
using Google.Apis.YouTube.v3.Data;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace FoxTube.Classes
{
public static class Navigation
{
public static MainFrame Frame => ((Window.Current.Content as Frame).Content as MainPage).Content;
public static void GoToSearch(SearchParameters args) =>
Frame.NavigateTo(typeof(Search), new object[] { args, Frame.Frame });
public static void GoToChannel(string id) =>
Frame.NavigateTo(typeof(ChannelPage), id);
public static void GoToHome() =>
Frame.NavigateTo(typeof(Home));
public static void GoToVideo(string id, string playlistId = null, bool incognito = false) =>
Frame.OpenVideo(id, playlistId, incognito);
public static void GoToDeveloper(string id) =>
Frame.NavigateTo(typeof(Settings), id);
public static void GoToPlaylist(string id) =>
Frame.NavigateTo(typeof(PlaylistPage), id);
public static void GoToHistory() =>
Frame.NavigateTo(typeof(History));
public static void GoToDownloads() =>
Frame.NavigateTo(typeof(Downloads));
}
}
-299
View File
@@ -1,299 +0,0 @@
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.Services.Store.Engagement;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Background;
using Windows.ApplicationModel.DataTransfer;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using Windows.System;
using Windows.System.Power;
using Windows.UI.Notifications;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Core;
namespace FoxTube.Classes
{
public static class Processes
{
static Stopwatch sw = new Stopwatch();
public static async void InitializeApp() => await CoreWindow.GetForCurrentThread().Dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
{
CheckVersion();
RegisterToastTask();
ActivateBackgoundTask();
AppCenter.Start("45774462-9ea7-438a-96fc-03982666f39e", typeof(Analytics));
AppCenter.SetCountryCode(SettingsStorage.Region);
sw.Start();
SecretsVault.Initialize();
DownloadAgent.Initialize();
if (SettingsStorage.ProcessClipboard)
{
Clipboard.ContentChanged += ParseClipboard;
ParseClipboard();
}
PromptFeedback();
});
public static void SuspendApp()
{
sw.Stop();
SettingsStorage.Uptime += sw.Elapsed;
HistorySet.Save();
SettingsStorage.SaveData();
DownloadAgent.QuitPrompt();
ManifestGenerator.ClearRoaming();
Analytics.TrackEvent("Session terminated", new Dictionary<string, string>
{
{ "Uptime", sw.Elapsed.ToString() },
{ "Total time", SettingsStorage.Uptime.ToString() }
});
}
public static async void ProcessToast(string arg)
{
string[] args = arg.Split('|');
switch (args[0])
{
case "changelog":
case "inbox":
Navigation.GoToDeveloper(args[1]);
break;
case "video":
Navigation.GoToVideo(args[1]);
break;
case "channel":
Navigation.GoToChannel(args[1]);
break;
case "download":
Navigation.GoToDownloads();
break;
case "dcancel":
DownloadAgent.Cancel(args[1]);
break;
case "clipboard":
switch (args[1])
{
case "video":
Navigation.GoToVideo(args[2]);
break;
case "channel":
Navigation.GoToChannel(args[2]);
break;
case "playlist":
Navigation.GoToPlaylist(args[2]);
break;
}
break;
case "later":
if (SecretsVault.IsAuthorized)
await Methods.AddItemToWL(args[1]);
else
{
SecretsVault.AuthorizationStateChanged += async (e) =>
{
if (e.Value)
await Methods.AddItemToWL(args[1]);
};
SecretsVault.CheckAuthorization(false);
}
break;
}
}
public static async void PromptFeedback()
{
ResourceLoader resources = ResourceLoader.GetForCurrentView("Main");
if (SettingsStorage.Uptime.TotalHours >= 12 && SettingsStorage.PromptFeedback)
{
MessageDialog dialog = new MessageDialog(resources.GetString("/Main/feedbackMessage"));
dialog.Commands.Add(new UICommand(resources.GetString("/Main/dontAsk"), (command) => SettingsStorage.PromptFeedback = false));
dialog.Commands.Add(new UICommand(resources.GetString("/Main/promptLater")));
dialog.Commands.Add(new UICommand(resources.GetString("/Main/sure"), async (command) =>
{
SettingsStorage.PromptFeedback = false;
if (StoreServicesFeedbackLauncher.IsSupported())
await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync();
else
{
MessageDialog message = new MessageDialog(resources.GetString("/Main/feedbackFail"));
message.Commands.Add(new UICommand(resources.GetString("/Main/sendEmail"), async (c) => await Launcher.LaunchUriAsync("mailto:michael.xfox@outlook.com".ToUri())));
message.Commands.Add(new UICommand(resources.GetString("/Main/goBack")));
message.CancelCommandIndex = 1;
message.DefaultCommandIndex = 0;
await message.ShowAsync();
}
}));
dialog.DefaultCommandIndex = 2;
dialog.CancelCommandIndex = 1;
await dialog.ShowAsync();
}
if (SettingsStorage.Uptime.TotalHours >= 24 && SettingsStorage.PromptReview)
{
MessageDialog dialog = new MessageDialog(resources.GetString("/Main/rate"));
dialog.Commands.Add(new UICommand(resources.GetString("/Main/dontAsk"), (command) => SettingsStorage.PromptReview = false));
dialog.Commands.Add(new UICommand(resources.GetString("/Main/promptLater")));
dialog.Commands.Add(new UICommand(resources.GetString("/Main/sure"), async (command) =>
{
SettingsStorage.PromptReview = false;
await Launcher.LaunchUriAsync("ms-windows-store://review/?ProductId=9NCQQXJTDLFH".ToUri());
}));
dialog.DefaultCommandIndex = 2;
dialog.CancelCommandIndex = 1;
await dialog.ShowAsync();
}
}
public static async void ParseClipboard(object sender = null, object e = null)
{
ResourceLoader resources = ResourceLoader.GetForCurrentView("Main");
if (Window.Current.CoreWindow.ActivationMode != Windows.UI.Core.CoreWindowActivationMode.ActivatedInForeground || !SettingsStorage.ProcessClipboard)
return;
try
{
string link = await Clipboard.GetContent().GetTextAsync();
if (!link.Contains("youtube") && !link.Contains("youtu.be"))
return;
string id;
string type = string.Empty;
string name = string.Empty;
if (YoutubeExplode.YoutubeClient.TryParseChannelId(link, out id))
{
type = "channel";
name = (await new YoutubeExplode.YoutubeClient().GetChannelAsync(id)).Title;
}
else if (YoutubeExplode.YoutubeClient.TryParsePlaylistId(link, out id))
{
type = "playlist";
name = (await new YoutubeExplode.YoutubeClient().GetPlaylistAsync(id)).Title;
}
else if (YoutubeExplode.YoutubeClient.TryParseUsername(link, out id))
{
id = await new YoutubeExplode.YoutubeClient().GetChannelIdAsync(id);
type = "channel";
name = (await new YoutubeExplode.YoutubeClient().GetChannelAsync(id)).Title;
}
else if (YoutubeExplode.YoutubeClient.TryParseVideoId(link, out id))
{
type = "video";
name = (await new YoutubeExplode.YoutubeClient().GetVideoAsync(id)).Title;
}
if(string.IsNullOrWhiteSpace(id))
return;
Windows.Data.Xml.Dom.XmlDocument toastXml = new Windows.Data.Xml.Dom.XmlDocument();
toastXml.LoadXml($@"<toast launch='clipboard|{type}|{id}'>
<visual>
<binding template='ToastGeneric'>
<text>{resources.GetString("/Main/clipboardHead")}</text>
<text>{name}</text>
<text>{resources.GetString($"/Main/{type}")}</text>
</binding>
</visual>
<actions>
<action content='{resources.GetString("/Main/clipboardOpen")}' arguments='clipboard|{type}|{id}'/>
</actions>
</toast>");
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(toastXml));
}
catch { }
}
/// <summary>
/// Comparing current version with last recorded version. If doesn't match, poping up changelog notification
/// </summary>
static async void CheckVersion()
{
PackageVersion ver = Package.Current.Id.Version;
if (SettingsStorage.Version != $"{ver.Major}.{ver.Minor}.{ver.Build}")
try
{
XmlDocument changelog = new XmlDocument();
StorageFile file = await (await Package.Current.InstalledLocation.GetFolderAsync(@"Assets\Data")).GetFileAsync("Patchnotes.xml");
changelog.Load(await file.OpenStreamForReadAsync());
ToastNotificationManager.CreateToastNotifier().Show(Background.Notification.GetChangelogToast(changelog["items"].FirstChild.Attributes["version"].Value));
SettingsStorage.Version = $"{ver.Major}.{ver.Minor}.{ver.Build}";
}
catch (Exception e)
{
Analytics.TrackEvent("Unable to retrieve changelog", new Dictionary<string, string>
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "App version", $"{ver.Major}.{ver.Minor}.{ver.Revision}.{ver.Build}" },
{ "StackTrace", e.StackTrace }
});
}
}
/// <summary>
/// Initializes background task for processing toast notifications' clicks
/// </summary>
static async void RegisterToastTask()
{
const string taskName = "FoxtubeToastBackground";
if (BackgroundTaskRegistration.AllTasks.Any(i => i.Value.Name.Equals(taskName)))
return;
var backgroundRequest = await BackgroundExecutionManager.RequestAccessAsync();
if (backgroundRequest == BackgroundAccessStatus.DeniedBySystemPolicy || backgroundRequest == BackgroundAccessStatus.DeniedByUser)
return;
BackgroundTaskBuilder builder = new BackgroundTaskBuilder() { Name = taskName };
builder.SetTrigger(new ToastNotificationActionTrigger());
BackgroundTaskRegistration registration = builder.Register();
}
/// <summary>
/// Initializes background task for checking user's subscriptions and poping toast notifications when new video is uploaded
/// </summary>
static async void ActivateBackgoundTask()
{
const string taskName = "FoxtubeBackgound";
if (BackgroundTaskRegistration.AllTasks.Any(i => i.Value.Name.Equals(taskName)))
return;
var backgroundRequest = await BackgroundExecutionManager.RequestAccessAsync();
var saverRequest = PowerManager.EnergySaverStatus;
if (backgroundRequest == BackgroundAccessStatus.DeniedBySystemPolicy || backgroundRequest == BackgroundAccessStatus.DeniedByUser || saverRequest == EnergySaverStatus.On)
return;
BackgroundTaskBuilder builder = new BackgroundTaskBuilder()
{
Name = taskName,
IsNetworkRequested = true,
TaskEntryPoint = "FoxTube.Background.BackgroundProcessor"
};
builder.SetTrigger(new TimeTrigger(15, false));
BackgroundTaskRegistration registration = builder.Register();
}
}
}
-30
View File
@@ -1,30 +0,0 @@
using System.Collections.Generic;
namespace FoxTube.Classes
{
public class QualityComparer : IComparer<string>
{
public int Compare(string x, string y)
{
string[] xArr = x.Split('p');
string[] yArr = y.Split('p');
int qualityA = int.Parse(xArr[0]);
int qualityB = int.Parse(yArr[0]);
int framerateA = 30;
int framerateB = 30;
if (!string.IsNullOrWhiteSpace(xArr[1]))
framerateA = int.Parse(xArr[1]);
if (!string.IsNullOrWhiteSpace(yArr[1]))
framerateB = int.Parse(yArr[1]);
if (qualityA > qualityB)
return 1;
else if (qualityA < qualityB)
return -1;
else
return framerateA - framerateB > 0 ? 1 : -1;
}
}
}
-156
View File
@@ -1,156 +0,0 @@
using Google.Apis.YouTube.v3.Data;
using System;
using System.Collections.Generic;
using static Google.Apis.YouTube.v3.SearchResource.ListRequest;
namespace FoxTube
{
public class SearchParameters
{
public class Filters
{
public static class Enumerations
{
public enum Order { Relevance, Date, Views, Rating, Title }
public enum Type { All, Video, Channel, Playlist }
public enum Date { Any, Hour, Today, Week, Month, Year }
public enum Duration { Any, Long, Medium, Short }
public enum ConversionType { Order, Type, Date, Duration, HD, ThreeD, Captions, LiveEvent, CreativeCommons }
}
public Enumerations.Order Order { get; set; } = Enumerations.Order.Relevance;
public Enumerations.Date Date { get; set; } = Enumerations.Date.Any;
public Enumerations.Type Type { get; set; } = Enumerations.Type.All;
public Enumerations.Duration Duration { get; set; } = Enumerations.Duration.Any;
public bool HD { get; set; } = false;
public bool Is3D { get; set; } = false;
public bool Captions { get; set; } = false;
public bool LiveEvent { get; set; } = false;
public bool CreativeCommons { get; set; } = false;
public object GetParameter(Enumerations.ConversionType type)
{
switch(type)
{
case Enumerations.ConversionType.Captions:
if (Captions)
return VideoCaptionEnum.ClosedCaption;
else return VideoCaptionEnum.Any;
case Enumerations.ConversionType.CreativeCommons:
if (CreativeCommons)
return VideoLicenseEnum.CreativeCommon;
else return VideoLicenseEnum.Any;
case Enumerations.ConversionType.Date:
switch(Date)
{
case Enumerations.Date.Any:
return null;
case Enumerations.Date.Hour:
return DateTime.Now.Subtract(TimeSpan.FromHours(1));
case Enumerations.Date.Today:
return DateTime.Now.Subtract(TimeSpan.FromDays(1));
case Enumerations.Date.Week:
return DateTime.Now.Subtract(TimeSpan.FromDays(7));
case Enumerations.Date.Month:
return DateTime.Now.Subtract(TimeSpan.FromDays(31));
case Enumerations.Date.Year:
return DateTime.Now.Subtract(TimeSpan.FromDays(365));
}
break;
case Enumerations.ConversionType.Duration:
return (VideoDurationEnum)Duration;
case Enumerations.ConversionType.HD:
if (HD)
return VideoDefinitionEnum.High;
else return VideoDefinitionEnum.Any;
case Enumerations.ConversionType.LiveEvent:
if (LiveEvent)
return EventTypeEnum.Live;
else return null;
case Enumerations.ConversionType.Order:
Dictionary<int, int> d = new Dictionary<int, int>()
{
{ 0, 2 },
{ 1, 0 },
{ 2, 5 },
{ 3, 1 },
{ 4, 3 }
};
return (OrderEnum)d[(int)Order];
case Enumerations.ConversionType.ThreeD:
if (Is3D)
return VideoDimensionEnum.Value3d;
else return VideoDimensionEnum.Any;
case Enumerations.ConversionType.Type:
switch(Type)
{
case Enumerations.Type.All:
return "video,channel,playlist";
case Enumerations.Type.Channel:
return "channel";
case Enumerations.Type.Playlist:
return "playlist";
case Enumerations.Type.Video:
return "video";
}
break;
}
return null;
}
}
public string Term { get; private set; }
public string Channel { get; private set; }
public VideoCategory Category { get; set; }
public Filters Filter { get; private set; } = new Filters();
public SearchParameters(string term)
{
Term = term;
}
public SearchParameters(VideoCategory category)
{
Category = category;
Filter = new Filters();
Filter.Type = Filters.Enumerations.Type.Video;
}
public SearchParameters(string term, Filters filters)
{
Term = term;
Filter = filters;
}
public SearchParameters(string term, string channelId)
{
Term = term;
Channel = channelId;
}
public SearchParameters(string term, string channelId, Filters filters)
{
Term = term;
Channel = channelId;
Filter = filters;
}
public override string ToString()
{
return $@"Term: {Term}
Channel id: {Channel}
Category id: {Category}
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}";
}
}
}
-275
View File
@@ -1,275 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
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;
using Microsoft.AppCenter.Analytics;
using System.Net;
using FoxTube.Classes;
using System.Linq;
namespace FoxTube
{
public static class SecretsVault
{
#region Properties
//Events
public static event AuthorizationChangedEventHandler AuthorizationStateChanged;
public static event SubscriptionsChangedEventHandler SubscriptionsChanged;
public static event ObjectEventHandler Purchased; //Rising when app finds out that it's not a PRO version or after purchase
//Properties
public static NetworkCredential EmailCredential => new NetworkCredential("foxtube.bot@xfox111.net", "JkY39w$.7?bT57O,8k3a");
private static ClientSecrets Secrets => new ClientSecrets
{
ClientId = "349735264870-2ekqlm0a4mkg3mmrfcv90s3qp3o15dq0.apps.googleusercontent.com",
ClientSecret = "BkVZOAaCU2Zclf0Zlicg6y2_"
};
private static YouTubeService NoAuthService => new YouTubeService(new BaseClientService.Initializer
{
ApiKey = "AIzaSyBgHrCnrlzlVmk0cJKL8RqP9Y8x6XSuk_0",
ApplicationName = "FoxTube"
});
private static BaseClientService.Initializer Initializer => new BaseClientService.Initializer
{
HttpClientInitializer = Credential,
ApplicationName = "FoxTube"
};
public static YouTubeService Service => IsAuthorized ? new YouTubeService(Initializer) : NoAuthService;
public static HttpClient HttpClient { get; } = new HttpClient();
private static bool TestAds => true; //TODO: Change this bool
public static string AppId => TestAds ? "d25517cb-12d4-4699-8bdc-52040c712cab" : "9ncqqxjtdlfh";
public static string AdUnitId => TestAds ? "test" : "1100037769";
public static bool AdsDisabled { get; private set; } = true;
//User info
public static bool IsAuthorized => Credential != null;
private static UserCredential Credential { get; set; } = null;
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 YoutubeExplode.Models.Playlist History { get; set; }
public static YoutubeExplode.Models.Playlist WatchLater { get; set; }
#endregion
#region Methods
public static void RefreshToken() =>
Credential?.RefreshTokenAsync(CancellationToken.None);
/// <summary>
/// Subscribes or unsibscribes authorized user from the channel
/// </summary>
/// <param name="id">The ID of channel which has to be added/removed</param>
/// <returns>Returns 'true' if channel is in subscriptions now; 'false' if it's not</returns>
public static async Task<bool> ChangeSubscriptionState(string id)
{
if(Subscriptions.Find(i => i.Snippet.ResourceId.ChannelId == id) is Subscription subscription)
{
try { await Service.Subscriptions.Delete(subscription.Id).ExecuteAsync(); }
catch { return true; }
SubscriptionsChanged?.Invoke("remove", subscription);
Subscriptions.Remove(subscription);
return false;
}
else
{
var request = Service.Subscriptions.Insert(new Subscription
{
Snippet = new SubscriptionSnippet
{
ResourceId = new ResourceId
{
ChannelId = id,
Kind = "youtube#channel"
}
}
}, "snippet");
if (!(await request.ExecuteAsync() is Subscription sub))
return false;
Subscriptions.Add(sub);
SubscriptionsChanged?.Invoke("add", sub);
return true;
}
}
/// <summary>
/// Sets up **SecretsVault**
/// </summary>
public static void Initialize()
{
CheckAddons();
CheckAuthorization();
}
/// <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
{
Credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
Secrets,
new[]
{
Oauth2Service.Scope.UserinfoProfile,
Oauth2Service.Scope.UserinfoEmail,
YouTubeService.Scope.YoutubeForceSsl,
YouTubeService.Scope.YoutubeUpload
},
"user",
CancellationToken.None);
await Credential.RefreshTokenAsync(CancellationToken.None);
}
catch (AuthenticateException e) when (e.Message.Contains("UserCancel")) { }
catch(Exception e)
{
AuthorizationStateChanged?.Invoke(null);
Analytics.TrackEvent("Failed to authorize", new Dictionary<string, string>
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "StackTrace", e.StackTrace }
});
}
if (Credential == null || !retrieveSubs)
return;
HttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Credential.Token.AccessToken);
SettingsStorage.HasAccount = true;
#endregion
try
{
#region Retrieving user's data
UserInfo = await new Oauth2Service(Initializer).Userinfo.Get().ExecuteAsync();
WatchLater = await Methods.GetLater();
History = await Methods.GetHistory();
SubscriptionsResource.ListRequest subRequest = Service.Subscriptions.List("snippet");
subRequest.Mine = true;
subRequest.MaxResults = 50;
subRequest.Order = SubscriptionsResource.ListRequest.OrderEnum.Relevance;
SubscriptionListResponse subResponse;
string nextToken = null;
do
{
subRequest.PageToken = nextToken;
subResponse = await subRequest.ExecuteAsync();
foreach (Subscription s in subResponse.Items)
Subscriptions.Add(s);
nextToken = subResponse.NextPageToken;
} while (!string.IsNullOrWhiteSpace(nextToken));
ChannelsResource.ListRequest request = Service.Channels.List("snippet,contentDetails");
request.Mine = true;
UserChannel = (await request.ExecuteAsync()).Items.FirstOrDefault();
#endregion
AuthorizationStateChanged?.Invoke(true);
}
catch (Exception e)
{
AuthorizationStateChanged?.Invoke(null);
Methods.SendMail(e.ToString());
Analytics.TrackEvent("Failed to retrieve user's info", new Dictionary<string, string>
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "StackTrace", e.StackTrace }
});
}
}
/// <summary>
/// Deauthenticates current user
/// </summary>
public static async void Deauthenticate()
{
if (!await Credential.RevokeTokenAsync(CancellationToken.None))
return;
Credential = null;
UserChannel = null;
UserInfo = null;
History = null;
WatchLater = null;
Subscriptions.Clear();
HttpClient.DefaultRequestHeaders.Authorization = null;
AuthorizationStateChanged?.Invoke(false);
SettingsStorage.HasAccount = false;
}
/// <summary>
/// Checks if any user has already been logged in. If has, calls *Authorize()* to retrieve his info
/// </summary>
/// <param name="retrieveSubs">Loads user's subscriptions if true</param>
public static void CheckAuthorization(bool retrieveSubs = true)
{
if (SettingsStorage.HasAccount)
Authorize(retrieveSubs);
else
AuthorizationStateChanged.Invoke(false);
}
/// <summary>
/// Connects to MS Store and checks if user has bought ad-free
/// </summary>
public static async void CheckAddons()
{
try
{
StoreProductQueryResult requset = await StoreContext.GetDefault().GetAssociatedStoreProductsAsync(new[] { "Durable" });
if (requset.Products["9NP1QK556625"].IsInUserCollection)
return;
AdsDisabled = false;
Purchased?.Invoke(null, false, requset.Products["9NP1QK556625"].Price.FormattedPrice);
}
catch { }
}
public static async void GetAdblock()
{
StorePurchaseResult request = await StoreContext.GetDefault().RequestPurchaseAsync("9NP1QK556625");
switch (request.Status)
{
case StorePurchaseStatus.AlreadyPurchased:
case StorePurchaseStatus.Succeeded:
Purchased?.Invoke(args: true);
AdsDisabled = true;
break;
}
}
#endregion
}
}
-286
View File
@@ -1,286 +0,0 @@
using Microsoft.AppCenter.Analytics;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.UI.Xaml.Controls;
namespace FoxTube
{
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 = GetLanguage();
public string relevanceLanguage = CultureInfo.InstalledUICulture.TwoLetterISOLanguageName;
public string region = CultureInfo.InstalledUICulture.Name.Split('-')[1];
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 bool processClipboard = true;
public bool minimizeCommandbar = false;
private static string GetLanguage()
{
if (new string[] { "ru-RU", "en-US" }.Contains(CultureInfo.InstalledUICulture.Name))
return CultureInfo.InstalledUICulture.Name;
else if ((new[] { "ua", "ru", "by", "kz", "kg", "md", "lv", "ee" }).Contains(CultureInfo.InstalledUICulture.TwoLetterISOLanguageName))
return "ru-RU";
else
return "en-US";
}
}
public static class SettingsStorage
{
public static string VideoQuality
{
get { return Container.videoQuality; }
set
{
Container.videoQuality = value;
SaveData();
}
}
public static string RememberedQuality
{
get { return Container.rememberedQuality; }
set
{
Container.rememberedQuality = value;
SaveData();
}
}
public static bool VideoNotifications
{
get { return Container.videoNotifications; }
set
{
Container.videoNotifications = value;
SaveData();
}
}
public static bool DevNotifications
{
get { return Container.devNotifications; }
set
{
Container.devNotifications = value;
SaveData();
}
}
public static bool CheckConnection
{
get { return Container.checkConnection; }
set
{
Container.checkConnection = value;
SaveData();
}
}
public static bool Autoplay
{
get { return Container.autoplay; }
set
{
Container.autoplay = value;
SaveData();
}
}
public static double Volume
{
get { return Container.volume; }
set
{
Container.volume = value;
SaveData();
}
}
public static string Language
{
get { return Container.language; }
set
{
Container.language = value;
SaveData();
}
}
public static string RelevanceLanguage
{
get { return Container.relevanceLanguage; }
set
{
Container.relevanceLanguage = value;
SaveData();
}
}
public static string Region
{
get { return Container.region; }
set
{
Container.region = value;
SaveData();
}
}
public static int SafeSearch
{
get { return Container.safeSearch; }
set
{
Container.safeSearch = value;
SaveData();
}
}
public static bool HasAccount
{
get { return Container.hasAccount; }
set
{
Container.hasAccount = value;
SaveData();
}
}
public static int Theme
{
get { return Container.theme; }
set
{
Container.theme = value;
SaveData();
}
}
public static string Version
{
get
{
if (storage.Values["version"] == null)
{
PackageVersion ver = Package.Current.Id.Version;
storage.Values["version"] = $"{ver.Major}.{ver.Minor}";
return $"{ver.Major}.{ver.Minor}";
}
else return (string)storage.Values["version"];
}
set
{
storage.Values["version"] = value;
}
}
public static MatureState Mature
{
get
{
if (storage.Values["mature"] == null)
{
storage.Values["mature"] = (int)MatureState.Blocked;
return MatureState.Blocked;
}
else return (MatureState)storage.Values["mature"];
}
set
{
storage.Values["mature"] = value;
}
}
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();
}
}
public static bool ProcessClipboard
{
get => Container.processClipboard;
set
{
Container.processClipboard = value;
SaveData();
}
}
public static AppBarClosedDisplayMode AppBarClosedMode
{
get => Container.minimizeCommandbar ? AppBarClosedDisplayMode.Minimal : AppBarClosedDisplayMode.Compact;
set
{
Container.minimizeCommandbar = value == AppBarClosedDisplayMode.Minimal;
SaveData();
}
}
//Settings storage
private static readonly ApplicationDataContainer storage = ApplicationData.Current.RoamingSettings;
private static SettingsContainer Container = new SettingsContainer();
public static void LoadData()
{
try
{
Container = JsonConvert.DeserializeObject<SettingsContainer>(storage.Values["settings"] as string);
}
catch (Exception e)
{
SaveData();
if (storage.Values["settings"] != null)
Analytics.TrackEvent("Failed to retrieve settings", new Dictionary<string, string>
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "StackTrace", e.StackTrace }
});
}
}
public static void SaveData()
{
storage.Values["settings"] = JsonConvert.SerializeObject(Container);
}
}
}
-42
View File
@@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
namespace FoxTube.Classes
{
public class StreamInfo
{
public class VideoInfo
{
public string IndexRange { get; set; }
public string InitRange => $"0-{int.Parse(IndexRange.Split('-')[0]) - 1}";
public string Itag { get; set; }
public string Fps { get; set; }
public string Url { get; set; }
public string Codecs { get; set; }
public string MimeType { get; set; }
public string Height { get; set; }
public string Width { get; set; }
public string Label { get; set; }
}
public class AudioInfo
{
public string IndexRange { get; set; }
public string InitRange => $"0-{int.Parse(IndexRange.Split('-')[0]) - 1}";
public string SampleRate { get; set; }
public string ChannelsCount { get; set; }
public string Codecs { get; set; }
public string MimeType { get; set; }
public string Url { get; set; }
public string Itag { get; set; }
}
public List<VideoInfo> Video { get; } = new List<VideoInfo>();
public List<AudioInfo> Audio { get; } = new List<AudioInfo>();
}
public class StreamQuality
{
public Uri Url { get; set; }
public string Resolution { get; set; }
}
}
-53
View File
@@ -1,53 +0,0 @@
<UserControl
x:Class="FoxTube.Controls.Adverts.CardAdvert"
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"
mc:Ignorable="d"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
d:DesignHeight="290"
d:DesignWidth="384"
Opacity="0"
Name="card">
<UserControl.Resources>
<Storyboard x:Name="show">
<DoubleAnimation Storyboard.TargetName="card" Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="{StaticResource CardOpacityDuration}"/>
</Storyboard>
</UserControl.Resources>
<Grid Name="grid" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="20"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions>
<Image Source="/Assets/videoPlaceholder.png" Opacity=".0001" Stretch="Fill"/>
<Image Name="image" Stretch="Fill" ImageOpened="Image_ImageOpened"/>
<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="CALL TO ACTION TEXT" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Gray" FontSize="12"/>
</StackPanel>
<StackPanel Margin="5" Background="Orange" VerticalAlignment="Top" CornerRadius="5" Opacity=".75" BorderThickness="1" HorizontalAlignment="Left" Padding="5,2,5,3">
<TextBlock Text="Sponsored content" x:Uid="/Cards/sponsored" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Black" 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 FontFamily="Segoe MDL2 Assets" Initials="&#xEC24;" Name="icon" Grid.Column="0" Height="46" Margin="5,-30,5,0" BorderBrush="White" BorderThickness="10"/>
<TextBlock Name="sponsor" HorizontalAlignment="Left" Grid.Column="1" Text="[Sponsored by]" TextTrimming="CharacterEllipsis" Foreground="Gray" Margin="0,2,0,0" FontSize="12"/>
<TextBlock Grid.Column="1" Name="desc" Text="[Description]" HorizontalAlignment="Right" Foreground="Gray" Margin="0,2,2,0" FontSize="12"/>
</Grid>
<TextBlock Grid.Row="2" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" Margin="5" MaxLines="2" TextTrimming="CharacterEllipsis"/>
</Grid>
</UserControl>
@@ -1,64 +0,0 @@
using System.Linq;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Microsoft.Advertising.WinRT.UI;
using Windows.UI.Xaml.Media.Imaging;
using Microsoft.Toolkit.Uwp.UI.Controls;
namespace FoxTube.Controls.Adverts
{
/// <summary>
/// Advert which is looks similar to video cards
/// </summary>
public sealed partial class CardAdvert : UserControl
{
readonly NativeAdsManagerV2 manager = new NativeAdsManagerV2(SecretsVault.AppId, SecretsVault.AdUnitId);
NativeAdV2 advert;
public CardAdvert()
{
InitializeComponent();
manager.AdReady += AdReady;
manager.ErrorOccurred += ErrorOccurred;
manager.RequestAd();
}
void ErrorOccurred(object sender, NativeAdErrorEventArgs e)
{
(Parent as AdaptiveGridView)?.Items.Remove(this);
System.Diagnostics.Debug.WriteLine("Error has occured while loading ad");
}
void AdReady(object sender, NativeAdReadyEventArgs e)
{
advert = e.NativeAd;
Initialize();
e.NativeAd.RegisterAdContainer(grid);
}
void Initialize()
{
title.Text = advert.Title;
image.Source = new BitmapImage(advert.MainImages.First().Url.ToUri());
icon.ProfilePicture = advert.AdIcon == null ? null : advert.AdIcon.Source;
sponsor.Text = Methods.GuardFromNull(advert.SponsoredBy);
if (string.IsNullOrWhiteSpace(advert.CallToActionText))
(info.Parent as FrameworkElement).Visibility = Visibility.Collapsed;
else
info.Text = advert.CallToActionText;
desc.Text = string.Empty;
if (!string.IsNullOrWhiteSpace(advert.Price))
desc.Text += advert.Price;
if (!string.IsNullOrWhiteSpace(advert.Rating))
desc.Text += " " + advert.Rating;
}
void Image_ImageOpened(object sender, RoutedEventArgs e) =>
show.Begin();
}
}
-48
View File
@@ -1,48 +0,0 @@
<ListViewItem
x:Class="FoxTube.Controls.Adverts.ChatAdvert"
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"
mc:Ignorable="d">
<Grid x:Name="grid">
<Border BorderBrush="Red" BorderThickness="2" CornerRadius="5" HorizontalAlignment="Stretch" Margin="0,27,0,2">
<Border.Background>
<SolidColorBrush Color="Red" Opacity=".2"/>
</Border.Background>
<Grid Margin="0,5,5,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Margin="5,0">
<PersonPicture Height="20" Name="icon"/>
<FontIcon Glyph="&#xE735;" Margin="2,0">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/Chat/sponsor"/>
</ToolTipService.ToolTip>
</FontIcon>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Margin="0,0,2,0" Grid.Column="1">
<HyperlinkButton Margin="0,-6,0,0" FontWeight="Bold">
<TextBlock TextTrimming="CharacterEllipsis" MaxWidth="150" Name="name"/>
</HyperlinkButton>
<TextBlock Text=":"/>
</StackPanel>
<StackPanel Padding="2,0,0,0" Grid.Column="2" VerticalAlignment="Top">
<TextBlock TextWrapping="WrapWholeWords" FontWeight="Bold" Name="title"/>
<TextBlock TextWrapping="WrapWholeWords" Name="description"/>
<Button x:Name="ctaBtn">
<TextBlock TextWrapping="WrapWholeWords" Name="cta"/>
</Button>
</StackPanel>
</Grid>
</Border>
</Grid>
</ListViewItem>
@@ -1,60 +0,0 @@
using Microsoft.Advertising.WinRT.UI;
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.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.Adverts
{
public sealed partial class ChatAdvert : ListViewItem
{
readonly NativeAdsManagerV2 manager = new NativeAdsManagerV2(SecretsVault.AppId, SecretsVault.AdUnitId);
NativeAdV2 advert;
public ChatAdvert()
{
InitializeComponent();
manager.AdReady += AdReady;
manager.RequestAd();
}
void AdReady(object sender, NativeAdReadyEventArgs e)
{
advert = e.NativeAd;
Initialize();
if (cta.Visibility == Visibility.Collapsed)
e.NativeAd.RegisterAdContainer(grid);
else
e.NativeAd.RegisterAdContainer(grid, new List<FrameworkElement> { cta });
}
void Initialize()
{
name.Text = Methods.GuardFromNull(advert.SponsoredBy);
ToolTipService.SetToolTip(name, name.Text);
icon.ProfilePicture = advert.AdIcon == null ? null : advert.AdIcon.Source;
title.Text = advert.Title;
description.Text = Methods.GuardFromNull(advert.Description);
cta.Text = Methods.GuardFromNull(advert.CallToActionText);
cta.Visibility = string.IsNullOrWhiteSpace(advert.CallToActionText) ? Visibility.Collapsed : Visibility.Visible;
Visibility = Visibility.Visible;
}
}
}
@@ -1,29 +0,0 @@
<UserControl
x:Class="FoxTube.Controls.Adverts.CommentAdvert"
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"
mc:Ignorable="d"
d:DesignWidth="400"
Visibility="Collapsed">
<Grid Margin="2" Name="grid" Background="#02000000">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<PersonPicture Margin="5,30,5,5" Name="icon" Initials="&#xEC24;" FontFamily="Segoe MDL2 Assets" Height="50" VerticalAlignment="Top"/>
<StackPanel Grid.Column="1" Margin="5">
<Border HorizontalAlignment="Left" Background="Red" CornerRadius="5">
<TextBlock Text="Sponsored by" Foreground="White" TextWrapping="WrapWholeWords" FontSize="13" Margin="3,0,3,3"/>
</Border>
<TextBlock Name="meta" Text="" TextWrapping="WrapWholeWords" Foreground="Gray" FontSize="13"/>
<TextBlock Name="title" Text="Title" TextWrapping="WrapWholeWords" FontWeight="Bold"/>
<TextBlock Name="description" Text="Description" TextWrapping="WrapWholeWords"/>
<HyperlinkButton Name="privacy"/>
<Button Name="cta" Content="CALL TO ACTION TEXT" Margin="0,5"/>
</StackPanel>
</Grid>
</UserControl>
@@ -1,53 +0,0 @@
using Microsoft.Advertising.WinRT.UI;
using System.Collections.Generic;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace FoxTube.Controls.Adverts
{
/// <summary>
/// Advert which is looks similar to video comment
/// </summary>
public sealed partial class CommentAdvert : UserControl
{
readonly NativeAdsManagerV2 manager = new NativeAdsManagerV2(SecretsVault.AppId, SecretsVault.AdUnitId);
NativeAdV2 advert;
public CommentAdvert()
{
InitializeComponent();
manager.AdReady += AdReady;
manager.RequestAd();
}
void AdReady(object sender, NativeAdReadyEventArgs e)
{
advert = e.NativeAd;
Initialize();
if(cta.Visibility == Visibility.Collapsed)
e.NativeAd.RegisterAdContainer(grid);
else
e.NativeAd.RegisterAdContainer(grid, new List<FrameworkElement> { cta });
}
void Initialize()
{
title.Text = advert.Title;
description.Text = Methods.GuardFromNull(advert.Description);
icon.ProfilePicture = advert.AdIcon == null ? null : advert.AdIcon.Source;
privacy.NavigateUri = advert.PrivacyUrl.ToUri();
privacy.Content = advert.PrivacyUrl;
privacy.Visibility = string.IsNullOrWhiteSpace(advert.PrivacyUrl) ? Visibility.Collapsed : Visibility.Visible;
cta.Content = Methods.GuardFromNull(advert.CallToActionText);
cta.Visibility = string.IsNullOrWhiteSpace(advert.CallToActionText) ? Visibility.Collapsed : Visibility.Visible;
meta.Text += advert.Price;
meta.Text += " " + advert.Rating;
meta.Visibility = string.IsNullOrWhiteSpace(meta.Text) ? Visibility.Collapsed : Visibility.Visible;
Visibility = Visibility.Visible;
}
}
}
@@ -1,98 +0,0 @@
<UserControl
x:Class="FoxTube.Controls.Adverts.PlayerAdvert"
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"
mc:Ignorable="d"
d:DesignHeight="53"
d:DesignWidth="1920"
Name="card"
Visibility="Collapsed"
Height="0"
Opacity="0">
<UserControl.Resources>
<Storyboard x:Name="show">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="card">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime="0:0:0.1">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetName="card" Storyboard.TargetProperty="Height">
<EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="53" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetName="card" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="1" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetName="countdown" Storyboard.TargetProperty="Value" EnableDependentAnimation="True" From="0" To="100" Duration="0:0:10"/>
</Storyboard>
<Storyboard x:Name="hide">
<DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetName="card" Storyboard.TargetProperty="Height">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="53" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="card" Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0" />
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="card">
<DiscreteObjectKeyFrame KeyTime="0:0:0.3">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime="0:0:0.4">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<Grid Height="53" Background="{ThemeResource SystemChromeMediumColor}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid Name="grid" HorizontalAlignment="Stretch" Background="#02000000">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" Padding="5">
<StackPanel Orientation="Horizontal">
<TextBlock Name="title" FontWeight="Bold" Text="Title"/>
<TextBlock Name="info" Foreground="Gray" VerticalAlignment="Center" Margin="10,0" Style="{StaticResource CaptionTextBlockStyle}" Text=""/>
</StackPanel>
<TextBlock Name="description" Text="Description" TextTrimming="CharacterEllipsis"/>
</StackPanel>
<Button Name="cta" Content="CALL TO ACTION" Grid.Column="2" Margin="10,0"/>
</Grid>
<Button Grid.Column="1" Width="50" Height="50" HorizontalAlignment="Right" FontFamily="Segoe MDL2 Assets" Content="&#xE106;" Background="Transparent" Click="Button_Click"/>
<ProgressBar Name="countdown" VerticalAlignment="Bottom" Grid.Row="1" Grid.ColumnSpan="2"/>
</Grid>
</UserControl>
@@ -1,73 +0,0 @@
using Microsoft.Advertising.WinRT.UI;
using System;
using System.Collections.Generic;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace FoxTube.Controls.Adverts
{
public sealed partial class PlayerAdvert : UserControl
{
readonly NativeAdsManagerV2 manager = new NativeAdsManagerV2(SecretsVault.AppId, SecretsVault.AdUnitId);
NativeAdV2 advert;
readonly DispatcherTimer timer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(10000)
};
public PlayerAdvert()
{
InitializeComponent();
manager.AdReady += AdReady;
timer.Tick += (s, e) =>
{
timer.Stop();
hide.Begin();
};
}
void AdReady(object sender, NativeAdReadyEventArgs arg)
{
advert = arg.NativeAd;
title.Text = advert.Title;
description.Text = Methods.GuardFromNull(advert.Description);
if (advert.AdIcon != null)
{
grid.Children.Insert(0, advert.AdIcon);
advert.AdIcon.HorizontalAlignment = HorizontalAlignment.Left;
advert.AdIcon.Margin = new Thickness(4);
advert.AdIcon.Width = double.NaN;
advert.AdIcon.Height = double.NaN;
advert.AdIcon.SizeChanged += (s, e) => grid.ColumnDefinitions[0].Width = new GridLength(advert.AdIcon.ActualWidth + 8);
}
cta.Content = Methods.GuardFromNull(advert.CallToActionText);
cta.Visibility = string.IsNullOrWhiteSpace(advert.CallToActionText) ? Visibility.Collapsed : Visibility.Visible;
info.Text += advert.Price;
info.Text += " " + advert.Rating;
if (cta.Visibility == Visibility.Collapsed)
advert.RegisterAdContainer(grid);
else
advert.RegisterAdContainer(grid, new List<FrameworkElement> { cta });
timer.Start();
show.Begin();
}
public void PushAdvert() =>
manager.RequestAd();
void Button_Click(object sender, RoutedEventArgs e)
{
timer.Stop();
hide.Begin();
}
}
}
-77
View File
@@ -1,77 +0,0 @@
<UserControl
x:Class="FoxTube.Controls.ChannelCard"
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"
mc:Ignorable="d"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
d:DesignHeight="290"
d:DesignWidth="384"
MaxWidth="700"
MaxHeight="600"
Opacity="0"
Name="card">
<UserControl.Resources>
<Storyboard x:Name="show">
<DoubleAnimation Storyboard.TargetName="card" Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="{StaticResource CardOpacityDuration}"/>
</Storyboard>
</UserControl.Resources>
<Grid Name="grid" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="50"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Image Source="/Assets/ChannelCoverTemplate.png" Stretch="UniformToFill" Opacity=".0001"/>
<Image Name="cover" Source="/Assets/ChannelCoverTemplate.png" Stretch="UniformToFill" ImageOpened="Cover_ImageOpened"/>
<StackPanel Name="liveTag" Margin="5" Background="Red" BorderBrush="White" BorderThickness="1" VerticalAlignment="Bottom" HorizontalAlignment="Right" Padding="5,2,5,3" Orientation="Horizontal" Visibility="Collapsed">
<TextBlock Text="&#xEC44; " VerticalAlignment="Center" Foreground="White" FontSize="12" FontFamily="Segoe MDL2 Assets" FontWeight="Black"/>
<TextBlock x:Uid="/Cards/live" Text="LIVE" VerticalAlignment="Center" Foreground="White" FontSize="12" FontWeight="Bold"/>
</StackPanel>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Ellipse Height="80" Width="80" Margin="0,-45,0,0" VerticalAlignment="Bottom" Fill="{ThemeResource SystemControlBackgroundChromeMediumBrush}"/>
<PersonPicture Name="avatar" Grid.Column="0" Height="74" Margin="3,-45,3,3" VerticalAlignment="Bottom" BorderBrush="White" BorderThickness="10"/>
<TextBlock Name="title" Grid.Column="1" Text="[Channel name]" Margin="5" TextWrapping="WrapWholeWords" MaxLines="2" TextTrimming="CharacterEllipsis"/>
<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 Height="75" Name="description" Grid.Row="2" MaxLines="3" Margin="10,0" 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="5" TextAlignment="Center" Padding="0,16,0,0" Foreground="Gray">
<Hyperlink Click="Hyperlink_Click"><Run x:Uid="/Cards/login">Log in</Run></Hyperlink> <Run x:Uid="/Cards/tomanage">to manage your subscriptions</Run>
</TextBlock>
<Grid Visibility="Collapsed" Grid.Row="3" VerticalAlignment="Stretch" Margin="5" Name="subscriptionPane" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<Button x:Uid="/Cards/subscribe" VerticalAlignment="Stretch" Click="subscribe_Click" Name="subscribe" HorizontalAlignment="Stretch" Height="50" Background="Red" Foreground="White" FontSize="18" FontWeight="SemiBold" Content="Subscirbe" Margin="0,0,0,0"/>
<ToggleButton Name="notify" Height="50" Width="50" Visibility="Collapsed" FontFamily="Segoe MDL2 Assets" FontSize="18" FontWeight="SemiBold" Content="&#xE7ED;" Foreground="White" Background="Red" HorizontalAlignment="Right"/>
</Grid>
</Grid>
<UserControl.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="/Cards/channel" Icon="Contact" Text="View channel" Click="Button_Click"/>
<MenuFlyoutSeparator/>
<MenuFlyoutItem x:Uid="/Cards/getLink" Icon="Link" Text="Copy link" Name="getLink" Click="GetLink_Click"/>
<MenuFlyoutItem x:Uid="/Cards/openWeb" Icon="Globe" Text="Open in browser" Name="inBrowser" Click="InBrowser_Click"/>
<MenuFlyoutItem x:Uid="/Cards/share" Icon="Share" Text="Share" Name="share" Visibility="Collapsed"/>
</MenuFlyout>
</UserControl.ContextFlyout>
</UserControl>
-129
View File
@@ -1,129 +0,0 @@
using FoxTube.Classes;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using Microsoft.Toolkit.Uwp.UI.Controls;
using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.DataTransfer;
using Windows.ApplicationModel.Resources;
using Windows.System;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
namespace FoxTube.Controls
{
/// <summary>
/// Channel item card
/// </summary>
public sealed partial class ChannelCard : UserControl, ICard
{
readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Cards");
readonly string channelId;
readonly string live;
Channel item;
public ChannelCard(string id, string live = null)
{
InitializeComponent();
channelId = id;
this.live = live;
}
public async Task Initialize()
{
try
{
if (channelId == SecretsVault.AccountId)
grid.RowDefinitions[3].Height = new GridLength(0);
ChannelsResource.ListRequest request = SecretsVault.Service.Channels.List("snippet,statistics,brandingSettings");
request.Id = channelId;
ChannelListResponse response = await request.ExecuteAsync();
item = response.Items[0];
title.Text = item.Snippet.Title;
description.Text = item.Snippet.Description;
subs.Text = $"{item.Statistics.SubscriberCount:0,0} {resources.GetString("/Cards/subscribers")}";
uploads.Text = $"{item.Statistics.VideoCount:0,0} {resources.GetString("/Cards/videos")}";
if (live == "live")
liveTag.Visibility = Visibility.Visible;
if (SecretsVault.IsAuthorized)
{
if (SecretsVault.Subscriptions.Exists(i => i.Snippet.ResourceId.ChannelId == channelId))
{
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)) { DecodePixelWidth = 74, DecodePixelHeight = 74 };
if (!item.BrandingSettings.Image.BannerImageUrl.Contains("default"))
cover.Source = new BitmapImage(item.BrandingSettings.Image.BannerMobileImageUrl.ToUri());
}
catch { }
}
catch (Exception e)
{
(Parent as AdaptiveGridView)?.Items.Remove(this);
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", channelId },
{ "StackTrace", e.StackTrace }
});
}
}
public void ItemClicked() =>
Navigation.GoToChannel(channelId);
void Button_Click(object sender, RoutedEventArgs e) =>
ItemClicked();
void Hyperlink_Click(Windows.UI.Xaml.Documents.Hyperlink sender, Windows.UI.Xaml.Documents.HyperlinkClickEventArgs args) =>
SecretsVault.Authorize();
async void subscribe_Click(object sender, RoutedEventArgs e)
{
if (await SecretsVault.ChangeSubscriptionState(channelId))
{
subscribe.Background = new SolidColorBrush(Colors.Transparent);
subscribe.Foreground = new SolidColorBrush(Colors.Gray);
subscribe.Content = resources.GetString("/Cards/unsubscribe");
}
else
{
subscribe.Background = new SolidColorBrush(Colors.Red);
subscribe.Foreground = new SolidColorBrush(Colors.White);
subscribe.Content = resources.GetString("/Cards/subscribe/Content");
}
}
void GetLink_Click(object sender, RoutedEventArgs e)
{
DataPackage data = new DataPackage();
data.SetText($"https://www.youtube.com/channel/{item.Id}");
Clipboard.SetContent(data);
}
async void InBrowser_Click(object sender, RoutedEventArgs e) =>
await Launcher.LaunchUriAsync($"https://www.youtube.com/channel/{item.Id}".ToUri());
void Cover_ImageOpened(object sender, RoutedEventArgs e) =>
show.Begin();
}
}
-97
View File
@@ -1,97 +0,0 @@
<UserControl
x:Class="FoxTube.Controls.CommentCard"
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"
mc:Ignorable="d"
d:DesignWidth="400">
<Grid Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<PersonPicture Tapped="avatar_Tapped" Name="avatar" Height="50" Margin="5" VerticalAlignment="Top"/>
<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 Text="[Publish date] (edited)" Name="meta" TextWrapping="WrapWholeWords" Foreground="Gray" FontSize="13"/>
<TextBlock Name="text" IsTextSelectionEnabled="True" TextWrapping="WrapWholeWords" Text="Content"/>
<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" FontFamily="Segoe MDL2 Assets" Content="&#xE107;" Background="Transparent" FontSize="20" Foreground="Red" Name="deleteComment" Click="DeleteComment_Click"/>
<Button x:Uid="/CommentsPage/editorCancel" Name="editorClose" Content="Cancel" Click="editorClose_Click" Margin="5, 0"/>
<Button x:Uid="/CommentsPage/editorSubmit" Name="editorSend" Content="Submit" Click="editorSend_Click"/>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Horizontal" Name="toolbar">
<TextBlock Name="upvote" Foreground="Gray" Padding="0"
VerticalAlignment="Center" Margin="0,0,5,0"
FontFamily="Segoe MDL2 Assets" Text="&#xE19F;" FontSize="20"/>
<TextBlock Name="rating" Foreground="Gray" VerticalAlignment="Center" Text="123"/>
<Button Click="showReplies_Click" Name="showReplies" Background="Transparent" Foreground="Gray" Padding="5,0" Margin="5,0"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
Height="35">
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xEC42;" Margin="0,0,5,0" FontSize="20"/>
<TextBlock Text="123"/>
</StackPanel>
</Button>
<Button Click="replyBtn_Click" Name="replyBtn" Background="Transparent" Foreground="Gray" Padding="5,0" Margin="5,0"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
Height="35">
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xEE35;" FontSize="20"/>
<TextBlock x:Uid="/CommentsPage/reply" Text="Reply"/>
</StackPanel>
</Button>
<Button Click="editBtn_Click" Visibility="Collapsed" Name="editBtn" Background="Transparent" Foreground="Gray" Padding="5,0" Margin="5,0"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
Height="35">
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xE104;" FontSize="20"/>
<TextBlock x:Uid="/CommentsPage/edit" Text="Edit"/>
</StackPanel>
</Button>
<Button x:Name="spam" Background="Transparent" Foreground="Gray" Padding="5,0" Margin="5,0" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Height="35" Content="&#x205D;">
<Button.Flyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="/CommentsPage/spam" Icon="Flag" Text="Report as spam" Click="MarkAsSpam_Click"/>
</MenuFlyout>
</Button.Flyout>
</Button>
</StackPanel>
<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 Name="send" Click="send_Click" IsEnabled="True" HorizontalAlignment="Right" VerticalAlignment="Top"
Width="32" Height="32" Padding="0"
Background="Transparent"
FontFamily="Segoe MDL2 Assets"
FontSize="30"
Content="&#xE122;"/>
</Grid>
<ProgressBar Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" IsIndeterminate="True" Name="processing" Visibility="Collapsed"/>
<StackPanel Name="replies" Visibility="Collapsed">
<StackPanel.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition IsStaggeringEnabled="True"/>
</TransitionCollection>
</StackPanel.ChildrenTransitions>
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
-337
View File
@@ -1,337 +0,0 @@
using System;
using System.Collections.Generic;
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;
using FoxTube.Classes;
namespace FoxTube.Controls
{
public enum CommentType { TopLevel, Reply }
/// <summary>
/// Control represents video comment entity
/// </summary>
public sealed partial class CommentCard : UserControl
{
ResourceLoader resources = ResourceLoader.GetForCurrentView("CommentsPage");
public Comment item;
public CommentThread thread;
CommentType type;
bool repliesLoaded = false;
public CommentCard(CommentThread comment)
{
InitializeComponent();
Initialize(comment);
}
public void Initialize(CommentThread comment)
{
item = comment.Snippet.TopLevelComment;
thread = comment;
replyBtn.Visibility = comment.Snippet.CanReply.Value && SecretsVault.IsAuthorized ? Visibility.Visible : Visibility.Collapsed;
spam.Visibility = 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;
rating.Visibility = Visibility.Collapsed;
}
else
rating.Text = comment.Snippet.TopLevelComment.Snippet.LikeCount.HasValue ? comment.Snippet.TopLevelComment.Snippet.LikeCount.ToString() : "";
if(item.Snippet.AuthorChannelId != null)
{
if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == SecretsVault.AccountId)
{
(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] == item.Snippet.ChannelId)
{
(specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
specialAuthor.Visibility = Visibility.Visible;
author.Visibility = Visibility.Collapsed;
}
}
author.Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
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") : "");
text.FormatText(comment.Snippet.TopLevelComment.Snippet.TextDisplay);
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)
{
InitializeComponent();
Initialize(comment);
}
public void Initialize(Comment comment)
{
item = comment;
type = CommentType.Reply;
replyBtn.Visibility = Visibility.Collapsed;
showReplies.Visibility = Visibility.Collapsed;
spam.Visibility = SecretsVault.IsAuthorized ? Visibility.Visible : Visibility.Collapsed;
if (comment.Snippet.CanRate == false)
{
upvote.Visibility = Visibility.Collapsed;
rating.Visibility = Visibility.Collapsed;
}
else
rating.Text = comment.Snippet.LikeCount.HasValue ? comment.Snippet.LikeCount.ToString() : "";
if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == SecretsVault.AccountId)
{
(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] == item.Snippet.ChannelId)
{
(specialAuthor.Child as TextBlock).Text = comment.Snippet.AuthorDisplayName;
specialAuthor.Visibility = Visibility.Visible;
author.Visibility = Visibility.Collapsed;
}
else
author.Text = comment.Snippet.AuthorDisplayName;
meta.Text = string.Format("{0} {1}", Methods.GetAgo(comment.Snippet.PublishedAt.Value), comment.Snippet.UpdatedAt != comment.Snippet.PublishedAt ? resources.GetString("/CommentsPage/edited") : "");
text.FormatText(comment.Snippet.TextDisplay);
try { avatar.ProfilePicture = new BitmapImage(new Uri(comment.Snippet.AuthorProfileImageUrl)) { DecodePixelWidth = 50, DecodePixelHeight = 50 }; }
catch { }
}
void replyBtn_Click(object sender, RoutedEventArgs e) =>
replyEditor.Visibility = replyEditor.Visibility == Visibility.Visible? Visibility.Collapsed : Visibility.Visible;
async void showReplies_Click(object sender, RoutedEventArgs e)
{
replies.Visibility = replies.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
if (repliesLoaded)
return;
processing.Visibility = Visibility.Visible;
CommentsResource.ListRequest request = SecretsVault.Service.Comments.List("snippet");
request.ParentId = item.Id;
request.TextFormat = CommentsResource.ListRequest.TextFormatEnum.PlainText;
request.MaxResults = 50;
CommentListResponse response;
do
{
response = await request.ExecuteAsync();
request.PageToken = response.NextPageToken;
response.Items.ForEach(i => replies.Children.Add(new CommentCard(i)));
}
while (!string.IsNullOrWhiteSpace(request.PageToken));
repliesLoaded = true;
processing.Visibility = Visibility.Collapsed;
}
void avatar_Tapped(object sender, TappedRoutedEventArgs e) =>
Navigation.GoToChannel(item.Snippet.AuthorChannelId.ToString().Split('"')[3]);
private void reply_TextChanged(object sender, TextChangedEventArgs e) =>
send.IsEnabled = reply.Text.Length == 0 ? false : true;
private async void send_Click(object sender, RoutedEventArgs args)
{
if (string.IsNullOrWhiteSpace(reply.Text))
return;
send.IsEnabled = false;
reply.IsEnabled = false;
processing.Visibility = Visibility.Visible;
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 = "";
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 (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 },
{ "StackTrace", e.StackTrace }
});
}
send.IsEnabled = true;
reply.IsEnabled = true;
processing.Visibility = Visibility.Collapsed;
}
private void editorClose_Click(object sender, RoutedEventArgs e)
{
editor.Visibility = Visibility.Collapsed;
text.Visibility = Visibility.Visible;
toolbar.Visibility = Visibility.Visible;
}
private async void editorSend_Click(object sender, RoutedEventArgs args)
{
if (string.IsNullOrWhiteSpace(editorText.Text))
return;
editorText.IsEnabled = false;
editorSend.IsEnabled = false;
editorClose.IsEnabled = false;
deleteComment.IsEnabled = false;
processing.Visibility = Visibility.Visible;
try
{
item.Snippet.TextOriginal = editorText.Text;
item.Snippet.UpdatedAt = DateTime.Now;
if (type == CommentType.Reply)
await SecretsVault.Service.Comments.Update(item, "snippet").ExecuteAsync();
else
{
thread.Snippet.TopLevelComment = item;
await SecretsVault.Service.CommentThreads.Update(thread, "snippet").ExecuteAsync();
}
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 (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 },
{ "StackTrace", e.StackTrace }
});
}
editorText.IsEnabled = true;
editorSend.IsEnabled = true;
editorClose.IsEnabled = true;
deleteComment.IsEnabled = true;
processing.Visibility = Visibility.Collapsed;
}
private void editorText_TextChanged(object sender, TextChangedEventArgs e) =>
editorSend.IsEnabled = editorText.Text.Length == 0 ? false : true;
private void editBtn_Click(object sender, RoutedEventArgs e)
{
editor.Visibility = Visibility.Visible;
toolbar.Visibility = Visibility.Collapsed;
text.Visibility = Visibility.Collapsed;
editorText.Text = text.Text;
}
private async void DeleteComment_Click(object sender, RoutedEventArgs args)
{
MessageDialog dialog = new MessageDialog(resources.GetString("/CommentsPage/deleteContent"), resources.GetString("/CommentsPage/deleteHeader"));
dialog.Commands.Add(new UICommand(resources.GetString("/CommentsPage/yes"), async (command) =>
{
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 (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 },
{ "StackTrace", e.StackTrace }
});
}
}));
dialog.Commands.Add(new UICommand(resources.GetString("/CommentsPage/no")));
dialog.CancelCommandIndex = 1;
dialog.DefaultCommandIndex = 0;
await dialog.ShowAsync();
}
private async void MarkAsSpam_Click(object sender, RoutedEventArgs e)
{
ResourceLoader resources = ResourceLoader.GetForCurrentView("Report");
try { await SecretsVault.Service.Comments.MarkAsSpam(item.Id).ExecuteAsync(); }
catch
{
await new MessageDialog(resources.GetString("/Report/err")).ShowAsync();
return;
}
await new MessageDialog(resources.GetString("/Report/submittedHeader"), resources.GetString("/Report/submittedBody")).ShowAsync();
}
}
}
@@ -1,35 +0,0 @@
<Grid
x:Class="FoxTube.Controls.Common.AccountManager"
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"
mc:Ignorable="d">
<Button Background="Transparent" Visibility="Collapsed" Name="manager" Height="41" Width="60" FontSize="15" Padding="0">
<Button.Flyout>
<Flyout>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<PersonPicture Width="65" Name="avatar" VerticalAlignment="Top"/>
<StackPanel Grid.Column="1" Margin="5">
<TextBlock Name="name"/>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Name="email"/>
<HyperlinkButton x:Uid="/Main/signOut" Content="Log out" Click="Logout_Click"/>
</StackPanel>
</Grid>
</Flyout>
</Button.Flyout>
<Ellipse Width="25" Height="25" Name="icon">
<Ellipse.Fill>
<ImageBrush ImageSource="/Assets/Icons/profile.png"/>
</Ellipse.Fill>
</Ellipse>
</Button>
<Button x:Uid="/Main/signIn" Name="account" Click="SignIn_Click" Visibility="Collapsed" FontFamily="Segoe MDL2 Assets" Content="&#xE8FA;" Background="Transparent" Height="41" Width="60" FontSize="15"/>
</Grid>
@@ -1,48 +0,0 @@
using Microsoft.AppCenter.Analytics;
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
namespace FoxTube.Controls.Common
{
public sealed partial class AccountManager : Grid
{
public AccountManager()
{
InitializeComponent();
}
void SignIn_Click(object sender, RoutedEventArgs e)
{
SecretsVault.Authorize();
Analytics.TrackEvent("Initialized authorization sequence");
}
void Logout_Click(object sender, RoutedEventArgs e)
{
manager.Flyout.Hide();
SecretsVault.Deauthenticate();
}
public async void Logged() => await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
account.Visibility = Visibility.Collapsed;
ToolTipService.SetToolTip(avatar, $"{SecretsVault.UserInfo.Name} ({SecretsVault.UserInfo.Email})");
name.Text = SecretsVault.UserInfo.Name;
email.Text = SecretsVault.UserInfo.Email;
avatar.ProfilePicture = new BitmapImage(SecretsVault.UserInfo.Picture.ToUri()) { DecodePixelHeight = 65, DecodePixelWidth = 65 };
(icon.Fill as ImageBrush).ImageSource = new BitmapImage(SecretsVault.UserInfo.Picture.ToUri()) { DecodePixelHeight = 25, DecodePixelWidth = 25 };
manager.Visibility = Visibility.Visible;
});
public void Quit()
{
manager.Visibility = Visibility.Collapsed;
account.Visibility = Visibility.Visible;
}
}
}
-129
View File
@@ -1,129 +0,0 @@
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.ApplicationModel.Resources;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace FoxTube.Controls.Common
{
class AddToPlaylist
{
ResourceLoader resources = ResourceLoader.GetForCurrentView("VideoPage");
IList<MenuFlyoutItemBase> Items { get; }
string videoId;
public AddToPlaylist(IList<MenuFlyoutItemBase> items) =>
Items = items;
public async void Initialize(string id)
{
Items.Clear();
LoadDefaultItems();
videoId = id;
if (SecretsVault.WatchLater.Videos.Any(i => i.Id == id))
(Items[1] as ToggleMenuFlyoutItem).IsChecked = true;
PlaylistsResource.ListRequest request = SecretsVault.Service.Playlists.List("snippet");
request.Mine = true;
request.MaxResults = 50;
PlaylistListResponse response = await request.ExecuteAsync();
PlaylistItemsResource.ListRequest itemRequest = SecretsVault.Service.PlaylistItems.List("snippet");
itemRequest.VideoId = id;
foreach (Playlist i in response.Items)
{
itemRequest.PlaylistId = i.Id;
ToggleMenuFlyoutItem menuItem = new ToggleMenuFlyoutItem
{
Text = i.Snippet.Title,
IsChecked = (await itemRequest.ExecuteAsync()).Items.Count > 0,
Tag = i,
Icon = new SymbolIcon(Symbol.List)
};
menuItem.Click += Item_Click;
Items.Add(menuItem);
}
}
void LoadDefaultItems()
{
MenuFlyoutItem createPlaylist = new MenuFlyoutItem
{
Text = resources.GetString("/VideoPage/newPlaylist/Text"),
Icon = new SymbolIcon(Symbol.Add)
};
createPlaylist.Click += NewPlaylist_Click;
Items.Add(createPlaylist);
ToggleMenuFlyoutItem watchLater = new ToggleMenuFlyoutItem
{
Text = resources.GetString("/VideoPage/wl"),
Icon = new SymbolIcon(Symbol.Clock)
};
watchLater.Click += Wl_Click;
Items.Add(watchLater);
Items.Add(new MenuFlyoutSeparator());
}
async void NewPlaylist_Click(object sender, RoutedEventArgs e)
{
ToggleMenuFlyoutItem menuItem = await new CreateAndAddPlaylist().GetItem();
if (menuItem == null)
return;
menuItem.Click += Item_Click;
Items.Add(menuItem);
Item_Click(menuItem, null);
}
async void Wl_Click(object sender, RoutedEventArgs e) =>
(sender as ToggleMenuFlyoutItem).IsChecked = await Methods.AddItemToWL(videoId);
async void Item_Click(object sender, RoutedEventArgs e)
{
try
{
if(((ToggleMenuFlyoutItem)sender).IsChecked)
{
PlaylistItem playlist = new PlaylistItem
{
Snippet = new PlaylistItemSnippet
{
PlaylistId = (((ToggleMenuFlyoutItem)sender).Tag as Playlist).Id,
ResourceId = new ResourceId
{
VideoId = videoId,
Kind = "youtube#video"
}
}
};
PlaylistItemsResource.InsertRequest request = SecretsVault.Service.PlaylistItems.Insert(playlist, "snippet");
await request.ExecuteAsync();
}
else
{
PlaylistItemsResource.ListRequest itemRequest = SecretsVault.Service.PlaylistItems.List("snippet");
itemRequest.VideoId = videoId;
itemRequest.PlaylistId = ((Playlist)((ToggleMenuFlyoutItem)sender).Tag).Id;
PlaylistItemsResource.DeleteRequest request = SecretsVault.Service.PlaylistItems.Delete((await itemRequest.ExecuteAsync()).Items[0].Id);
await request.ExecuteAsync();
}
}
catch
{
((ToggleMenuFlyoutItem)sender).IsChecked = !((ToggleMenuFlyoutItem)sender).IsChecked;
}
}
}
};
@@ -1,22 +0,0 @@
<ContentDialog
x:Class="FoxTube.Controls.CreateAndAddPlaylist"
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"
mc:Ignorable="d"
x:Uid="/VideoPage/dialog"
PrimaryButtonText="Create and add" Title="New playlist" CloseButtonText="Cancel"
DefaultButton="Primary"
PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
Name="playlistDialog">
<StackPanel>
<TextBox x:Uid="/VideoPage/newPlaylistName" PlaceholderText="Enter playlist name" Name="newListName"/>
<ComboBox x:Uid="/VideoPage/privacy" Header="Availablity" SelectedIndex="0" HorizontalAlignment="Stretch" Name="newListDisc">
<ComboBoxItem x:Uid="/VideoPage/public" Content="Public"/>
<ComboBoxItem x:Uid="/VideoPage/private" Content="Private"/>
<ComboBoxItem x:Uid="/VideoPage/direct" Content="Direct link"/>
</ComboBox>
</StackPanel>
</ContentDialog>
@@ -1,75 +0,0 @@
using Google.Apis.YouTube.v3.Data;
using System;
using System.Threading.Tasks;
using Windows.UI.Xaml.Controls;
namespace FoxTube.Controls
{
public sealed partial class CreateAndAddPlaylist : ContentDialog
{
ToggleMenuFlyoutItem menuItem;
public CreateAndAddPlaylist()
{
InitializeComponent();
}
public async Task<ToggleMenuFlyoutItem> GetItem()
{
if (await ShowAsync() != ContentDialogResult.Primary)
return null;
while (menuItem == null)
await Task.Delay(100);
return menuItem;
}
private async void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
string privacy = "private";
switch (newListDisc.SelectedIndex)
{
case 0:
privacy = "public";
break;
case 1:
privacy = "private";
break;
case 2:
privacy = "unlisted";
break;
}
Playlist newItem = new Playlist
{
Snippet = new PlaylistSnippet
{
Title = newListName.Text
},
Status = new PlaylistStatus
{
PrivacyStatus = privacy,
}
};
Playlist i;
try { i = await SecretsVault.Service.Playlists.Insert(newItem, "snippet,status").ExecuteAsync(); }
catch
{
return;
}
menuItem = new ToggleMenuFlyoutItem
{
Text = i.Snippet.Title,
IsChecked = true,
Tag = i,
Icon = new FontIcon
{
Glyph = "\xE728"
}
};
}
}
}
@@ -1,47 +0,0 @@
using System.Collections.Generic;
using Windows.ApplicationModel.Resources;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using YoutubeExplode;
using YoutubeExplode.Models;
using YoutubeExplode.Models.MediaStreams;
namespace FoxTube.Controls.Common
{
public class DownloadSelector
{
ResourceLoader resources = ResourceLoader.GetForCurrentView("VideoPage");
IList<MenuFlyoutItemBase> Items { get; }
public DownloadSelector(IList<MenuFlyoutItemBase> items) =>
Items = items;
public async void Initialize(Video item)
{
Items.Clear();
MediaStreamInfoSet infoSet = await new YoutubeClient(SecretsVault.HttpClient).GetVideoMediaStreamInfosAsync(item.Id);
foreach (MuxedStreamInfo i in infoSet.Muxed)
{
MenuFlyoutItem menuItem = new MenuFlyoutItem()
{
Text = i.VideoQualityLabel,
Tag = (i, item, i.VideoQualityLabel)
};
menuItem.Click += downloadItemSelected;
Items.Add(menuItem);
}
MenuFlyoutItem audioItem = new MenuFlyoutItem()
{
Text = resources.GetString("/VideoPage/audio"),
Tag = new object[] { infoSet.Audio[0], resources.GetString("/Cards/audioOnly") }
};
audioItem.Click += downloadItemSelected;
Items.Add(audioItem);
}
void downloadItemSelected(object sender, RoutedEventArgs e) =>
DownloadAgent.Add(((MuxedStreamInfo, Video, string))(sender as MenuFlyoutItemBase).Tag);
}
}
-14
View File
@@ -1,14 +0,0 @@
<UserControl
x:Class="FoxTube.Controls.ContentFrame"
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:foxtube="using:FoxTube"
mc:Ignorable="d">
<Grid>
<Frame x:Name="content" Navigating="Content_Navigating"/>
<foxtube:LoadingPage x:Name="loading" RefreshPage="Loading_RefreshPage" Visibility="Collapsed"/>
</Grid>
</UserControl>
-38
View File
@@ -1,38 +0,0 @@
using FoxTube.Classes;
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace FoxTube.Controls
{
public sealed partial class ContentFrame : UserControl
{
public Frame Frame => content;
public LoadingPage LoadingPage => loading;
public event NavigatedEventHandler Navigated;
public new object Content => content.Content;
public ContentFrame()
{
InitializeComponent();
content.Navigated += (s, e) => Navigated?.Invoke(s, e);
}
public void Navigate(Type sourcePageType, object parameter) =>
content.Navigate(sourcePageType, parameter);
void Content_Navigating(object sender, NavigatingCancelEventArgs e) =>
loading.Refresh();
void Loading_RefreshPage(object sender, RoutedEventArgs e)
{
content.Navigate(content.CurrentSourcePageType, (content.Content as INavigationPage).Parameter);
content.BackStack.RemoveAt(content.BackStack.Count - 1);
}
public void Refresh() =>
Loading_RefreshPage(this, null);
}
}
-47
View File
@@ -1,47 +0,0 @@
<UserControl
x:Class="FoxTube.Controls.DownloadItem"
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"
d:DesignHeight="100"
d:DesignWidth="1500">
<Grid Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" Height="100" HorizontalAlignment="Stretch" Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Image Name="thumbnail" Source="/Assets/videoThumbSample.png"/>
<TextBlock Name="title" Grid.Column="1" Margin="5" TextWrapping="WrapWholeWords" Text="[Title]" FontSize="20" MaxLines="2"/>
<TextBlock Margin="5" Name="path" Grid.Column="1" VerticalAlignment="Bottom" Text="C://Users/Michael Gordeev/Downloads/[Title].mp4" Foreground="LightGray" MaxLines="1" TextWrapping="WrapWholeWords"/>
<TextBlock 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">
<StackPanel>
<TextBlock Text="&#xED25;" FontFamily="Segoe MDL2 Assets" FontSize="30" HorizontalAlignment="Center"/>
<TextBlock x:Uid="/Downloads/openFile" Text="Open" TextWrapping="WrapWholeWords" HorizontalTextAlignment="Center"/>
</StackPanel>
</Button>
<Button Name="gotoOriginal" Click="gotoOriginal_Click" Width="80" Height="80" Padding="0" Background="Transparent">
<StackPanel>
<TextBlock Text="&#xE2B4;" FontFamily="Segoe MDL2 Assets" FontSize="30" HorizontalAlignment="Center"/>
<TextBlock x:Uid="/Downloads/gotoOrign" Text="Go to original" TextWrapping="WrapWholeWords" HorizontalTextAlignment="Center" HorizontalAlignment="Center"/>
</StackPanel>
</Button>
</StackPanel>
<StackPanel Name="progressPanel" Grid.Column="4" Margin="10">
<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>
-286
View File
@@ -1,286 +0,0 @@
using System;
using System.IO;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Windows.System;
using YoutubeExplode.Models.MediaStreams;
using YoutubeExplode;
using Windows.Storage;
using System.Threading;
using Windows.UI.Popups;
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;
using YoutubeExplode.Models;
using FoxTube.Classes;
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 { get; private set; }
CancellationTokenSource cts;
Progress<double> progress;
double percentage;
DispatcherTimer timer;
NotificationData data;
public DownloadItem(MediaStreamInfo info, Video meta, string q)
{
InitializeComponent();
Container = new DownloadItemContainer()
{
Channel = meta.Author,
Duration = meta.Duration,
Extension = info.Container.GetFileExtension(),
Id = meta.Id,
IsDownloaded = false,
Quality = q,
Thumbnail = meta.Thumbnails.MediumResUrl.ToUri(),
Title = meta.Title
};
Download(info);
}
public DownloadItem(DownloadItemContainer container)
{
InitializeComponent();
Container = container;
Load();
}
async void Load()
{
File = await DownloadAgent.Downloads.TryGetItemAsync(Container.Name) as StorageFile;
if (File == null)
DownloadAgent.Remove(this);
donePanel.Visibility = Visibility.Visible;
progressPanel.Visibility = Visibility.Collapsed;
SetMeta();
}
async void Download(MediaStreamInfo info)
{
File = await DownloadAgent.Downloads.CreateFileAsync($"{Container.Title.ReplaceInvalidChars('_')}.{Container.Extension}", CreationCollisionOption.GenerateUniqueName);
Container.Name = File.Name;
donePanel.Visibility = Visibility.Collapsed;
progressPanel.Visibility = Visibility.Visible;
SetMeta();
cts = new CancellationTokenSource();
progress = new Progress<double>();
progress.ProgressChanged += (s, e) => percentage = e;
timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
timer.Tick += (s, e) => UpdateInfo();
#region Polling notification
ToastContent toastContent = new ToastContent
{
Visual = new ToastVisual
{
BindingGeneric = new ToastBindingGeneric
{
Children =
{
new AdaptiveText { Text = resources.GetString("/Downloads/toastStartHeader") },
new AdaptiveProgressBar
{
Title = Container.Title,
Status = resources.GetString("/Downloads/downloading/Text"),
Value = new BindableProgressBarValue("value")
}
}
}
},
Actions = new ToastActionsCustom
{
Buttons =
{
new ToastButton(resources.GetString("/Downloads/cancel/Content"), $"dcancel|{Container.Id}")
}
},
Launch = "download",
ActivationType = ToastActivationType.Foreground
};
data = new NotificationData();
data.Values["value"] = "0";
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(toastContent.GetXml()) { Tag = $"download|{Container.Id}", Data = data });
#endregion
timer.Start();
try
{
await new YoutubeClient().DownloadMediaStreamAsync(info, await File.OpenStreamForWriteAsync(), progress, cts.Token);
Container.IsDownloaded = true;
timer.Stop();
if (cts.IsCancellationRequested)
throw new TaskCanceledException();
else
DownloadCompleted();
}
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) },
{ "StackTrace", e.StackTrace }
});
try { await File.DeleteAsync(StorageDeleteOption.PermanentDelete); }
catch { }
DownloadAgent.Remove(this);
}
}
void UpdateInfo()
{
progressBar.Value = percentage;
progressText.Text = Math.Round(percentage * 100, 1) + "%";
data.Values["value"] = percentage.ToString();
ToastNotificationManager.CreateToastNotifier().Update(data, $"download|{Container.Id}");
}
void DownloadCompleted()
{
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/toastCompleteHeader")}</text>
<text>{Container.Title}</text>
</binding>
</visual>
<actions>
<action content='{resources.GetString("/Downloads/gotoOrign/Text")}'
activationType='foreground'
arguments='video|{Container.Id}'/>
</actions>
</toast>");
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(template) { Tag = $"download|{Container.Id}" });
donePanel.Visibility = Visibility.Visible;
progressPanel.Visibility = Visibility.Collapsed;
}
async void open_Click(object sender, RoutedEventArgs e) =>
await Launcher.LaunchFileAsync(File);
void gotoOriginal_Click(object sender, RoutedEventArgs e) =>
Navigation.GoToVideo(Container.Id);
public async void Cancel(bool prompt = true)
{
cancel.IsEnabled = false;
if(prompt)
{
MessageDialog dialog = new MessageDialog(resources.GetString("/Downloads/prompt"), resources.GetString("/Downloads/cancellingHeader"));
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;
if ((bool)(await dialog.ShowAsync()).Id)
{
cancel.IsEnabled = true;
return;
}
}
cts.Cancel();
DownloadAgent.Remove(this);
status.Text = resources.GetString("/Downloads/cancelling");
progressBar.IsIndeterminate = true;
}
void SetMeta()
{
try
{
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}";
}
catch
{
DownloadAgent.Remove(this);
}
}
private void Cancel_Click(object sender, RoutedEventArgs e) =>
Cancel();
}
}
-18
View File
@@ -1,18 +0,0 @@
<UserControl
x:Class="FoxTube.Controls.LiveCaptions"
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"
mc:Ignorable="d"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Margin="0,55">
<Grid>
<Grid.Background>
<AcrylicBrush TintColor="#BF000000" TintOpacity="0.75"/>
</Grid.Background>
<TextBlock Text="Hello, World!" Name="text" Margin="5" FontSize="24"/>
</Grid>
</UserControl>
@@ -1,74 +0,0 @@
using System;
using Windows.Media.Playback;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using YoutubeExplode.Models.ClosedCaptions;
namespace FoxTube.Controls
{
/// <summary>
/// Control for displaying closed captions
/// </summary>
public sealed partial class LiveCaptions : UserControl
{
public double Size
{
get => text.FontSize;
set => text.FontSize = value;
}
public bool IsActive => track != null;
public MediaElement Player { get; set; }
DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(10) };
ClosedCaption currentCaption = null;
ClosedCaptionTrack track = null;
public LiveCaptions()
{
InitializeComponent();
timer.Tick += UpdateCaption;
}
private void UpdateCaption(object sender, object e)
{
currentCaption = track.Captions.Find(i => i.Offset <= Player.Position && i.Offset + i.Duration > Player.Position);
if (currentCaption != null)
text.Text = currentCaption.Text;
Visibility = currentCaption == null ? Visibility.Collapsed : Visibility.Visible;
}
public async void Initialize(ClosedCaptionTrackInfo info)
{
track = await new YoutubeExplode.YoutubeClient().GetClosedCaptionTrackAsync(info);
UpdateCaption(this, null);
timer.Start();
}
public void Close()
{
timer.Stop();
track = null;
currentCaption = null;
Visibility = Visibility.Collapsed;
}
public void Minimize()
{
Margin = new Thickness(0, 0, 0, 20);
text.FontSize = 15;
}
public void Maximize()
{
Margin = new Thickness(0, 0, 0, 55);
text.FontSize = 24;
}
}
}
-456
View File
@@ -1,456 +0,0 @@
using FoxTube.Classes;
using FoxTube.Controls;
using FoxTube.Controls.Adverts;
using FoxTube.Controls.Player;
using Google.Apis.YouTube.v3.Data;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel.Resources;
using Windows.Foundation;
using Windows.Graphics.Display;
using Windows.Media;
using Windows.Media.Core;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Media;
using YoutubeExplode;
using YoutubeExplode.Models.ClosedCaptions;
using YoutubeExplode.Models.MediaStreams;
namespace FoxTube.Controls.Player
{
/// <summary>
/// Custom controls for media player. MARKUP IS IN **Themes/Generic.xaml**!!!
/// </summary>
public sealed partial class PlayerControls : MediaTransportControls
{
#region Controls variables
Button minimize;
Button close;
Button miniview;
Button play;
Button next;
Button prev;
Button volumeMenu;
Button mute;
Button live;
Button fwd;
Button bwd;
Button captionsMenu;
Button drag;
TextBlock title;
TextBlock channel;
TextBlock elapsed;
TextBlock remain;
Slider volume;
Slider seek;
Slider playbackSpeed;
ProgressBar seekIndicator;
ComboBox captions;
ComboBox quality;
ToggleSwitch captionsSwitch;
StackPanel rightFooter;
StackPanel leftFooter;
StackPanel rightHeader;
StackPanel centerStack;
Grid header;
Grid footer;
Grid center;
Grid centerTrigger;
#endregion
public PlayerAdvert Advert;
public LiveCaptions Caption;
TimeSpan timecodeBackup;
bool needUpdateTimecode = false;
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()
{
AssignControls();
isReady = true;
Player.MediaOpened += Player_MediaOpened;
Player.MarkerReached += (s, e) => PushAdvert();
Player.CurrentStateChanged += Player_CurrentStateChanged;
Caption.Player = Player;
minimize.Click += Minimize_Click;
close.Click += Close_Click;
miniview.Click += Miniview_Click;
mute.Click += async (s, e) =>
{
await Task.Delay(10);
Volume_ValueChanged(this, null);
};
next.Click += Next_Click;
prev.Click += Prev_Click;
volume.ValueChanged += Volume_ValueChanged;
playbackSpeed.ValueChanged += PlaybackSpeed_ValueChanged;
live.Click += Live_Click;
captionsSwitch.Toggled += CaptionsSwitch_Toggled;
captions.SelectionChanged += Captions_SelectionChanged;
quality.SelectionChanged += Quality_SelectionChanged;
seek.ValueChanged += Seek_ValueChanged;
IsCompactOverlayButtonVisible = true;
IsCompactOverlayEnabled = true;
IsFullWindowButtonVisible = true;
IsFullWindowEnabled = true;
IsSkipBackwardButtonVisible = true;
IsSkipBackwardEnabled = true;
IsSkipForwardButtonVisible = true;
IsSkipForwardEnabled = true;
Player.Tapped += async (s, e) =>
{
await Task.Delay(10);
Rect view = new Rect(0, 0, centerTrigger.ActualWidth, centerTrigger.ActualHeight);
Point p = e.GetPosition(centerTrigger);
if (!view.Contains(p) || e.PointerDeviceType != Windows.Devices.Input.PointerDeviceType.Mouse || State != PlayerDisplayState.Normal)
return;
if (Player.CurrentState == Windows.UI.Xaml.Media.MediaElementState.Playing)
Player.Pause();
else if (Player.CurrentState == Windows.UI.Xaml.Media.MediaElementState.Paused)
Player.Play();
};
if (queue.Count > 0)
foreach (Action i in queue)
i();
base.OnApplyTemplate();
}
private void Player_CurrentStateChanged(object sender, RoutedEventArgs e)
{
systemControls.PlaybackStatus = Player.CurrentState == MediaElementState.Playing ? MediaPlaybackStatus.Playing : MediaPlaybackStatus.Paused;
if (Player.CurrentState == MediaElementState.Paused)
if (!incognito && Item.Snippet.LiveBroadcastContent == "none")
{
History.LeftOn = Player.Position;
HistorySet.Update(History);
}
}
private void PlaybackSpeed_ValueChanged(object sender, RangeBaseValueChangedEventArgs e) =>
Player.PlaybackRate = playbackSpeed.Value;
void AssignControls()
{
minimize = GetTemplateChild("MinimizeButton") as Button;
close = GetTemplateChild("CloseButton") as Button;
miniview = GetTemplateChild("CompactOverlayButton") as Button;
play = GetTemplateChild("PlayPauseButton") as Button;
next = GetTemplateChild("NextButton") as Button;
prev = GetTemplateChild("PreviousButton") as Button;
volumeMenu = GetTemplateChild("VolumeMenuButton") as Button;
mute = GetTemplateChild("AudioMuteButton") as Button;
live = GetTemplateChild("PlayLiveButton") as Button;
fwd = GetTemplateChild("SkipForwardButton") as Button;
bwd = GetTemplateChild("SkipBackwardButton") as Button;
captionsMenu = GetTemplateChild("CaptionsMenuButton") as Button;
drag = GetTemplateChild("drag") as Button;
Advert = GetTemplateChild("AdvertControl") as PlayerAdvert;
Caption = GetTemplateChild("CaptionControl") as LiveCaptions;
title = GetTemplateChild("title") as TextBlock;
channel = GetTemplateChild("channel") as TextBlock;
elapsed = GetTemplateChild("TimeElapsedElement") as TextBlock;
remain = GetTemplateChild("TimeRemainingElement") as TextBlock;
volume = GetTemplateChild("VolumeSlider") as Slider;
seek = GetTemplateChild("ProgressSlider") as Slider;
playbackSpeed = GetTemplateChild("PlaybackSpeedSlider") as Slider;
seekIndicator = GetTemplateChild("SeekIndicator") as ProgressBar;
captions = GetTemplateChild("CaptionsSelector") as ComboBox;
quality = GetTemplateChild("QualitySelector") as ComboBox;
captionsSwitch = GetTemplateChild("CaptionsToggleSwitch") as ToggleSwitch;
rightFooter = GetTemplateChild("RightFooterControls") as StackPanel;
leftFooter = GetTemplateChild("LeftFooterControls") as StackPanel;
rightHeader = GetTemplateChild("RightHeaderControls") as StackPanel;
centerStack = GetTemplateChild("centerControls") as StackPanel;
header = GetTemplateChild("header") as Grid;
footer = GetTemplateChild("footer") as Grid;
center = GetTemplateChild("center") as Grid;
centerTrigger = GetTemplateChild("centerTrigger") as Grid;
}
private void Seek_ValueChanged(object sender, RangeBaseValueChangedEventArgs e) =>
seekIndicator.Value = seek.Value;
private async void Quality_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count == 0)
return;
if (Item.Snippet.LiveBroadcastContent == "live")
goto SetQuality;
if(!needUpdateTimecode)
timecodeBackup = Player.Position;
needUpdateTimecode = true;
Player.Pause();
Player.Source = null;
SetQuality:
object info = (quality.SelectedItem as ComboBoxItem).Tag;
if (info is MuxedStreamInfo)
Player.SetPlaybackSource(MediaSource.CreateFromUri((info as MuxedStreamInfo).Url.ToUri()));
else if (info is VideoStreamInfo || info == null)
Player.SetPlaybackSource(MediaSource.CreateFromUri(await ManifestGenerator.GetManifest(Item, info as VideoStreamInfo, MediaStreams)));
else if (info is StreamQuality)
Player.SetPlaybackSource(MediaSource.CreateFromUri((info as StreamQuality).Url));
}
private void Captions_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(Caption.IsActive)
{
Caption.Close();
Caption.Initialize((captions.SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
}
}
private void CaptionsSwitch_Toggled(object sender, RoutedEventArgs e)
{
if(captionsSwitch.IsOn)
Caption.Initialize((captions.SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
else
Caption.Close();
}
private void Live_Click(object sender, RoutedEventArgs e)
{
Player.Position = Player.NaturalDuration.TimeSpan;
}
private void Next_Click(object sender, RoutedEventArgs e)
{
if (Playlist == null)
NextRequested?.Invoke();
else
Playlist.Next();
}
private void Prev_Click(object sender, RoutedEventArgs e) =>
Playlist.Previous();
private void Miniview_Click(object sender, RoutedEventArgs e)
{
if (State == PlayerDisplayState.Compact)
Maximize();
else
EnterMiniview();
}
private void Volume_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
double v = volume.Value;
if (v == 0 || Player.IsMuted)
volumeMenu.Content = mute.Content = "\xE74F";
else if (v <= 25 && v > 0)
volumeMenu.Content = mute.Content = "\xE992";
else if (v <= 50 && v > 25)
volumeMenu.Content = mute.Content = "\xE993";
else if (v <= 75 && v > 50)
volumeMenu.Content = mute.Content = "\xE994";
else if (v > 75)
volumeMenu.Content = mute.Content = "\xE995";
}
private void Player_MediaOpened(object sender, RoutedEventArgs args)
{
if ((timecodeBackup == null || timecodeBackup.TotalSeconds < 3) && History.LeftOn.TotalSeconds.Belongs(30, Player.NaturalDuration.TimeSpan.TotalSeconds - 120))
{
Player.Position = History.LeftOn;
needUpdateTimecode = false;
return;
}
if (!needUpdateTimecode)
return;
needUpdateTimecode = false;
Player.Position = timecodeBackup;
}
private void Close_Click(object sender, RoutedEventArgs e)
{
systemControls.IsEnabled = false;
Player.Stop();
Player.Source = null;
//Methods.MainPage.CloseVideo();
}
private void Minimize_Click(object sender, RoutedEventArgs e)
{
if (State == PlayerDisplayState.Normal)
Minimize();
else
Maximize();
}
public void Minimize()
{
if (State == PlayerDisplayState.Minimized)
return;
//Methods.MainPage.MinimizeVideo(true);
header.Children.Remove(minimize);
center.Children.Add(minimize);
rightHeader.Children.Remove(close);
center.Children.Add(close);
leftFooter.Children.Remove(play);
centerStack.Children.Add(play);
rightFooter.Children.Remove(fwd);
centerStack.Children.Add(fwd);
rightFooter.Children.Remove(bwd);
centerStack.Children.Insert(0, bwd);
header.Visibility = Visibility.Collapsed;
center.Visibility = Visibility.Visible;
footer.Visibility = Visibility.Collapsed;
minimize.Content = "\xE010";
MiniModeChanged?.Invoke(true);
Caption.Minimize();
State = PlayerDisplayState.Minimized;
}
public void Maximize()
{
if (State == PlayerDisplayState.Normal)
return;
//Methods.MainPage.MaximizeVideo(true);
if(State == PlayerDisplayState.Compact)
{
center.Children.Remove(miniview);
rightHeader.Children.Add(miniview);
centerStack.Children.Remove(play);
leftFooter.Children.Insert(0, play);
centerStack.Children.Remove(fwd);
rightFooter.Children.Insert(0, fwd);
centerStack.Children.Remove(bwd);
rightFooter.Children.Insert(0, bwd);
miniview.Margin = new Thickness();
miniview.Width = 50;
miniview.Height = 50;
}
else
{
center.Children.Remove(minimize);
header.Children.Insert(0, minimize);
center.Children.Remove(close);
rightHeader.Children.Insert(0, close);
centerStack.Children.Remove(play);
leftFooter.Children.Insert(0, play);
centerStack.Children.Remove(fwd);
rightFooter.Children.Insert(0, fwd);
centerStack.Children.Remove(bwd);
rightFooter.Children.Insert(0, bwd);
MiniModeChanged?.Invoke(false);
}
drag.Visibility = Visibility.Collapsed;
header.Visibility = Visibility.Visible;
center.Visibility = Visibility.Collapsed;
footer.Visibility = Visibility.Visible;
miniview.Content = "\xE2B3";
minimize.Content = "\xE011";
Caption.Maximize();
State = PlayerDisplayState.Normal;
}
public void EnterMiniview()
{
if (State == PlayerDisplayState.Compact)
return;
rightHeader.Children.Remove(miniview);
center.Children.Add(miniview);
leftFooter.Children.Remove(play);
centerStack.Children.Add(play);
rightFooter.Children.Remove(fwd);
centerStack.Children.Add(fwd);
rightFooter.Children.Remove(bwd);
centerStack.Children.Insert(0, bwd);
drag.Visibility = Visibility.Visible;
header.Visibility = Visibility.Collapsed;
center.Visibility = Visibility.Visible;
footer.Visibility = Visibility.Collapsed;
miniview.Margin = new Thickness(0, 32, 0, 0);
miniview.Width = 47;
miniview.Height = 47;
miniview.Content = "\xE2B4";
Caption.Minimize();
State = PlayerDisplayState.Compact;
}
void PushAdvert()
{
if(State == PlayerDisplayState.Normal)
Advert.PushAdvert();
}
private async void SystemControls_Engaged(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args) =>
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
switch (args.Button)
{
case SystemMediaTransportControlsButton.Play:
Player.Play();
break;
case SystemMediaTransportControlsButton.Pause:
Player.Pause();
break;
case SystemMediaTransportControlsButton.Next:
Next_Click(this, null);
break;
}
});
}
}
-208
View File
@@ -1,208 +0,0 @@
using FoxTube.Classes;
using Google.Apis.YouTube.v3.Data;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel.Resources;
using Windows.Graphics.Display;
using Windows.Media;
using Windows.Storage.Streams;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using YoutubeExplode;
using YoutubeExplode.Models.ClosedCaptions;
using YoutubeExplode.Models.MediaStreams;
using FoxTube.Controls.VideoPage;
using Windows.UI.Xaml.Media;
namespace FoxTube.Controls.Player
{
public partial class PlayerControls : MediaTransportControls
{
public event SimpleEventHandler CloseRequested;
public event MinimodeChangedEventHandler MiniModeChanged;
public event SimpleEventHandler NextRequested;
public PlayerDisplayState State { get; set; } = PlayerDisplayState.Normal;
public Video Item { get; private set; }
HistoryItem History { get; set; }
VideoPlaylist Playlist { get; set; }
public MediaElement Player { get; set; }
SystemMediaTransportControls systemControls;
bool incognito;
public void Initialize(Video item, string avatarUrl, bool incognito = false, VideoPlaylist playlist = null)
{
this.incognito = incognito;
Playlist = playlist;
Item = item;
History = HistorySet.Items.Find(i => i.Id == Item.Id);
if (History == null && !incognito)
{
History = new HistoryItem { Id = Item.Id };
HistorySet.Update(History);
}
if (item.Snippet.LiveBroadcastContent == "upcoming")
Player.AreTransportControlsEnabled = false;
else
{
LoadControls(avatarUrl);
if (item.Snippet.LiveBroadcastContent == "none")
LoadVideo();
else
LoadStream();
}
}
void LoadControls(string avatar)
{
if (!isReady)
{
queue.Enqueue(() => LoadControls(avatar));
return;
}
prev.Visibility = Visibility.Collapsed;
captionsSwitch.IsOn = false;
Player.Volume = SettingsStorage.Volume;
Player.AutoPlay = SettingsStorage.Autoplay;
title.Text = Item.Snippet.Title;
channel.Text = Item.Snippet.ChannelTitle;
#region System Media Transport Controls
systemControls = SystemMediaTransportControls.GetForCurrentView();
systemControls.IsPauseEnabled = true;
systemControls.IsPlayEnabled = true;
if(Playlist != null)
{
next.IsEnabled = Playlist.SelectedIndex + 1 < Playlist.Items.Count;
prev.Visibility = Visibility.Visible;
prev.IsEnabled = Playlist.SelectedIndex != 0;
systemControls.IsNextEnabled = systemControls.IsPreviousEnabled = false;
}
else
next.IsEnabled = systemControls.IsNextEnabled = 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
quality.Items.Clear();
captions.Items.Clear();
Player.Markers.Clear();
}
async void LoadVideo()
{
if (Methods.GetDuration(Item.ContentDetails.Duration).TotalMinutes > 5)
Player.Markers.Add(new TimelineMarker { Time = Methods.GetDuration(Item.ContentDetails.Duration) - TimeSpan.FromMinutes(1) });
if (Methods.GetDuration(Item.ContentDetails.Duration).TotalMinutes >= 60)
for (int k = 1; k < Methods.GetDuration(Item.ContentDetails.Duration).TotalMinutes / 30; k++)
Player.Markers.Add(new TimelineMarker { Time = TimeSpan.FromMinutes(k * 30) });
MediaStreamInfoSet set = await new YoutubeClient().GetVideoMediaStreamInfosAsync(Item.Id);
uint screenHeight = DisplayInformation.GetForCurrentView().ScreenHeightInRawPixels;
List<string> qualityList = set.GetAllVideoQualityLabels().ToList();
qualityList.Sort(new QualityComparer());
qualityList.Reverse();
quality.Items.Add(new ComboBoxItem
{
Content = ResourceLoader.GetForCurrentView("VideoPage").GetString("/VideoPage/auto")
});
foreach (string i in qualityList)
{
object tag = (object)set.Muxed.Find(m => m.VideoQualityLabel == i && m.Resolution.Height <= screenHeight) ?? set.Video.Find(m => m.VideoQualityLabel == i && m.Container.GetFileExtension() == "mp4");
if (tag == null)
continue;
quality.Items.Add(new ComboBoxItem
{
Content = i,
Tag = tag
});
}
string s = SettingsStorage.VideoQuality == "remember" ? SettingsStorage.RememberedQuality : SettingsStorage.VideoQuality;
if (quality.Items.Find(i => ((i as ComboBoxItem).Content as string).Contains(s)) is ComboBoxItem item)
quality.SelectedItem = item;
else
quality.SelectedIndex = 0;
IReadOnlyList<ClosedCaptionTrackInfo> cc = await new YoutubeClient().GetVideoClosedCaptionTrackInfosAsync(Item.Id);
captionsMenu.Visibility = cc.Count == 0 ? Visibility.Collapsed : Visibility.Visible;
foreach (ClosedCaptionTrackInfo i in cc)
captions.Items.Add(new ComboBoxItem
{
Content = $"{CultureInfo.GetCultureInfo(i.Language.Code).DisplayName}{(i.IsAutoGenerated ? $" ({ResourceLoader.GetForCurrentView("VideoPage").GetString("/VideoPage/generatedCaption")})" : "")})",
Tag = i
});
ClosedCaptionTrackInfo ccItem = cc.Find(i => SettingsStorage.RelevanceLanguage.Contains(i.Language.Code)) ?? cc.Find(i => "en-US".Contains(i.Language.Code));
ccItem = ccItem ?? cc.FirstOrDefault();
captions.SelectedItem = captions.Items.Find(i => (i as ComboBoxItem).Tag as ClosedCaptionTrackInfo == ccItem);
}
async void LoadStream()
{
List<StreamQuality> qualities = await ManifestGenerator.ResolveLiveSteream((await new YoutubeClient().GetVideoMediaStreamInfosAsync(Item.Id)).HlsLiveStreamUrl);
foreach (StreamQuality i in qualities)
quality.Items.Add(new ComboBoxItem
{
Content = i.Resolution,
Tag = i
});
string s = SettingsStorage.VideoQuality == "remember" ? SettingsStorage.RememberedQuality : SettingsStorage.VideoQuality;
if (quality.Items.Find(i => (i as ComboBoxItem).Content as string == s) is ComboBoxItem item)
quality.SelectedItem = item;
else
quality.SelectedIndex = 0;
}
public void Pause() =>
Player.Pause();
public async void OpenBrowser()
{
Player.Pause();
string timecode = Player.Position.TotalSeconds > 10 ?
"&t=" + (int)Player.Position.TotalSeconds + "s" : string.Empty;
await Launcher.LaunchUriAsync($"https://www.youtube.com/watch?v={Item.Id}{timecode}".ToUri());
}
public void SetMargin(double offset) =>
Player.Margin = new Thickness(Player.Margin.Left, offset * -1, Player.Margin.Right, Player.Margin.Bottom);
}
}
-24
View File
@@ -1,24 +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:foxtube="using:FoxTube.Controls.Player"
mc:Ignorable="d"
d:DesignHeight="1080"
d:DesignWidth="1920"
RequestedTheme="Dark"
Visibility="Collapsed">
<Grid Background="{StaticResource SystemChromeMediumColor}">
<MediaElement Name="videoSource" AreTransportControlsEnabled="True" VolumeChanged="VideoSource_VolumeChanged" CurrentStateChanged="VideoSource_CurrentStateChanged" MarkerReached="VideoSource_MarkerReached" PosterSource="ms-appx:///Assets/videoThumbSample.png">
<MediaElement.TransportControls>
<foxtube:PlayerControls IsCompactOverlayButtonVisible="True" IsCompactOverlayEnabled="True"
IsFullWindowButtonVisible="True" IsFullWindowEnabled="True"
IsSkipBackwardButtonVisible="True" IsSkipBackwardEnabled="True"
IsSkipForwardButtonVisible="True" IsSkipForwardEnabled="True"/>
</MediaElement.TransportControls>
</MediaElement>
</Grid>
</UserControl>
-173
View File
@@ -1,173 +0,0 @@
using System;
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 Windows.UI.Xaml.Media;
using FoxTube.Controls.Player;
namespace FoxTube
{
public sealed partial class VideoPlayer : UserControl
{
public Video item;
public string avatar;
public HistoryItem history;
bool incognito = false;
public event Event NextClicked;
public event ObjectEventHandler MiniMode;
public PlayerControls Controls => videoSource.TransportControls as PlayerControls;
public MediaElement Player => videoSource;
SystemMediaTransportControls systemControls;
public VideoPlayer()
{
InitializeComponent();
}
public void Initialize(Video meta, string channelAvatar, bool privateMode = false)
{
incognito = privateMode;
item = meta;
avatar = channelAvatar;
history = new HistoryItem
{
Id = item.Id
};
videoSource.PosterSource = new BitmapImage((meta.Snippet.Thumbnails.Maxres ?? meta.Snippet.Thumbnails.Medium).Url.ToUri());
if (item.Snippet.LiveBroadcastContent == "none")
{
InitializeContols();
//Controls.Load(item);
if (Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes > 5)
videoSource.Markers.Add(new TimelineMarker { Time = Methods.GetDuration(item.ContentDetails.Duration) - TimeSpan.FromMinutes(1) });
if(Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes >= 60)
for (int k = 1; k < Methods.GetDuration(item.ContentDetails.Duration).TotalMinutes / 30; k++)
videoSource.Markers.Add(new TimelineMarker { Time = TimeSpan.FromMinutes(k * 30) });
if (!privateMode)
HistorySet.Update(history);
}
else if (item.Snippet.LiveBroadcastContent == "live")
{
InitializeContols();
//Controls.Load(item);
}
else
videoSource.AreTransportControlsEnabled = false;
Visibility = Visibility.Visible;
}
public void InitializeContols()
{
videoSource.Volume = SettingsStorage.Volume;
videoSource.AutoPlay = SettingsStorage.Autoplay;
Controls.CloseRequested += Controls_CloseRequested;
Controls.NextRequested += () => NextClicked?.Invoke();
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
}
private async void SystemControls_Engaged(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
switch (args.Button)
{
case SystemMediaTransportControlsButton.Play:
videoSource.Play();
break;
case SystemMediaTransportControlsButton.Pause:
videoSource.Pause();
break;
case SystemMediaTransportControlsButton.Next:
NextClicked?.Invoke();
break;
}
});
}
public void Controls_MiniModeChanged(bool e)
{
videoSource.IsFullWindow = false;
MiniMode?.Invoke(this, e);
}
public void Minimize()
{
Controls.Minimize();
}
public void Controls_CloseRequested()
{
videoSource.Pause();
systemControls.IsEnabled = false;
videoSource.Source = null;
if (!incognito && item.Snippet.LiveBroadcastContent == "none")
{
history.LeftOn = videoSource.Position;
HistorySet.Update(history);
}
//Methods.MainPage.CloseVideo();
}
public void Pause()
{
videoSource.Pause();
}
private void VideoSource_CurrentStateChanged(object sender, RoutedEventArgs e)
{
systemControls.PlaybackStatus = videoSource.CurrentState == MediaElementState.Playing ? MediaPlaybackStatus.Playing : MediaPlaybackStatus.Paused;
if(videoSource.CurrentState == MediaElementState.Paused)
if (!incognito && item.Snippet.LiveBroadcastContent == "none")
{
history.LeftOn = videoSource.Position;
HistorySet.Update(history);
}
}
private void VideoSource_VolumeChanged(object sender, RoutedEventArgs e)
{
if(videoSource.Volume != 0)
SettingsStorage.Volume = videoSource.Volume;
//Controls.UpdateVolumeIcon();
}
private void VideoSource_MarkerReached(object sender, TimelineMarkerRoutedEventArgs e)
{
//Controls.PushAdvert();
}
}
}
-68
View File
@@ -1,68 +0,0 @@
<UserControl
x:Class="FoxTube.Controls.PlaylistCard"
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"
mc:Ignorable="d"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
d:DesignHeight="290"
d:DesignWidth="384"
MaxWidth="700"
MaxHeight="600"
Opacity="0"
Name="card">
<UserControl.Resources>
<Storyboard x:Name="show">
<DoubleAnimation Storyboard.TargetName="card" Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="{StaticResource CardOpacityDuration}"/>
</Storyboard>
</UserControl.Resources>
<Grid Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="20"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions>
<Image Source="/Assets/videoPlaceholder.png" Opacity=".0001" Stretch="Fill"/>
<Image Name="thumbnail" Source="/Assets/videoThumbSample.png" Stretch="Fill" ImageOpened="Thumbnail_ImageOpened"/>
<Grid HorizontalAlignment="Right" Width="100">
<Grid.Background>
<AcrylicBrush TintColor="#7F000000" BackgroundSource="Backdrop" AlwaysUseFallback="False" TintOpacity="1" Opacity="0.97"/>
</Grid.Background>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock HorizontalAlignment="Center" FontFamily="Segoe MDL2 Assets" Foreground="White" Text="&#xE90B;" FontSize="30"/>
<TextBlock Foreground="White" Text="[N/A]" HorizontalAlignment="Center" FontSize="20" Name="counter"/>
</StackPanel>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Height="50" Width="50" Margin="5,-30,5,10" Fill="{ThemeResource SystemControlBackgroundChromeMediumBrush}"/>
<PersonPicture Name="avatar" Grid.Column="0" Height="46" Margin="5,-30,5,0" BorderBrush="White" BorderThickness="10"/>
<TextBlock HorizontalAlignment="Left" MaxWidth="200" TextTrimming="CharacterEllipsis" Name="channelName" Grid.Column="1" Text="[Channel name]" Foreground="Gray" Margin="0,2,0,0" FontSize="12"/>
<TextBlock Grid.Column="1" Name="date" Text="[Published at]" HorizontalAlignment="Right" Foreground="Gray" Margin="0,2,2,0" FontSize="12"/>
</Grid>
<TextBlock Grid.Row="2" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" TextTrimming="CharacterEllipsis" Margin="5" MaxLines="2"/>
</Grid>
<UserControl.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="/Cards/playlist" Icon="List" Text="View playlist" Click="Button_Click"/>
<MenuFlyoutItem x:Uid="/Cards/channel" Icon="Contact" Text="View channel" Name="openChannel" Click="OpenChannel_Click"/>
<MenuFlyoutSeparator/>
<MenuFlyoutItem x:Uid="/Cards/getLink" Icon="Link" Text="Copy link" Name="getLink" Click="GetLink_Click"/>
<MenuFlyoutItem x:Uid="/Cards/openWeb" Icon="Globe" Text="Open in browser" Name="inBrowser" Click="InBrowser_Click"/>
<MenuFlyoutItem x:Uid="/Cards/share" Icon="Share" Text="Share" Name="share" Visibility="Collapsed"/>
</MenuFlyout>
</UserControl.ContextFlyout>
</UserControl>
-89
View File
@@ -1,89 +0,0 @@
using FoxTube.Classes;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using Microsoft.AppCenter.Analytics;
using Microsoft.Toolkit.Uwp.UI.Controls;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.ApplicationModel.DataTransfer;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using YoutubeExplode;
namespace FoxTube.Controls
{
/// <summary>
/// Playlist card control
/// </summary>
public sealed partial class PlaylistCard : UserControl, ICard
{
Playlist item;
readonly string playlistId;
public PlaylistCard(string id)
{
InitializeComponent();
playlistId = id;
}
public async Task Initialize()
{
try
{
PlaylistsResource.ListRequest request = SecretsVault.Service.Playlists.List("snippet,contentDetails");
request.Id = playlistId;
request.Hl = SettingsStorage.RelevanceLanguage;
item = (await request.ExecuteAsync()).Items[0];
title.Text = item.Snippet.Localized.Title;
channelName.Text = item.Snippet.ChannelTitle;
counter.Text = item.ContentDetails.ItemCount.ToString();
date.Text = Methods.GetAgo(item.Snippet.PublishedAt.Value);
try
{
thumbnail.Source = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri());
avatar.ProfilePicture = new BitmapImage((await new YoutubeClient(SecretsVault.HttpClient).GetChannelAsync(item.Snippet.ChannelId)).LogoUrl.ToUri()) { DecodePixelHeight = 46, DecodePixelWidth = 46 };
}
catch { }
}
catch (Exception e)
{
(Parent as AdaptiveGridView)?.Items.Remove(this);
Visibility = Visibility.Collapsed;
Analytics.TrackEvent("PlaylistCard loading failed", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Playlist ID", playlistId },
{ "StackTrace", e.StackTrace }
});
}
}
public void ItemClicked() =>
Navigation.GoToPlaylist(item.Id);
public void Button_Click(object sender, RoutedEventArgs e) =>
ItemClicked();
void OpenChannel_Click(object sender, RoutedEventArgs e) =>
Navigation.GoToChannel(item.Snippet.ChannelId);
void GetLink_Click(object sender, RoutedEventArgs e)
{
DataPackage data = new DataPackage();
data.SetText($"https://www.youtube.com/playlist?list={playlistId}");
Clipboard.SetContent(data);
}
async void InBrowser_Click(object sender, RoutedEventArgs e) =>
await Launcher.LaunchUriAsync($"https://www.youtube.com/playlist?list={playlistId}".ToUri());
void Thumbnail_ImageOpened(object sender, RoutedEventArgs e) =>
show.Begin();
}
}
-13
View File
@@ -1,13 +0,0 @@
<UserControl
x:Class="FoxTube.Controls.ShowMore"
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"
mc:Ignorable="d">
<Grid>
<HyperlinkButton x:Uid="/Cards/more" Foreground="Red" HorizontalAlignment="Center" Content="Show more" Name="btn" Click="btn_Click"/>
<ProgressBar Name="bar" IsIndeterminate="True" Foreground="Red" Visibility="Collapsed"/>
</Grid>
</UserControl>
-25
View File
@@ -1,25 +0,0 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace FoxTube.Controls
{
public sealed partial class ShowMore : UserControl
{
public event Event Clicked;
public ShowMore() =>
InitializeComponent();
private void btn_Click(object sender, RoutedEventArgs e)
{
btn.Visibility = Visibility.Collapsed;
bar.Visibility = Visibility.Visible;
Clicked.Invoke();
}
public void Complete()
{
bar.Visibility = Visibility.Collapsed;
btn.Visibility = Visibility.Visible;
}
}
}
-82
View File
@@ -1,82 +0,0 @@
<UserControl
x:Class="FoxTube.Controls.VideoCard"
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"
mc:Ignorable="d"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
d:DesignHeight="290"
d:DesignWidth="384"
MaxWidth="700"
MaxHeight="600"
Opacity="0"
Name="card">
<UserControl.Resources>
<Storyboard x:Name="show">
<DoubleAnimation Storyboard.TargetName="card" Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="{StaticResource CardOpacityDuration}"/>
</Storyboard>
</UserControl.Resources>
<Grid Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="20"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions>
<Image Source="/Assets/videoPlaceholder.png" Opacity=".0001" Stretch="Fill"/>
<Image Name="thumbnail" Source="/Assets/videoThumbSample.png" Stretch="Fill" ImageOpened="Thumbnail_ImageOpened"/>
<Grid Background="#7F000000" Name="watched" Visibility="Collapsed">
<Border Margin="5" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" VerticalAlignment="Top" HorizontalAlignment="Left" Padding="5,2" BorderBrush="Gray" BorderThickness="1">
<TextBlock x:Uid="/Cards/watched" Text="Watched" Foreground="Gray" FontSize="12"/>
</Border>
<ProgressBar VerticalAlignment="Bottom" Margin="54,0,0,0" Name="leftOn"/>
</Grid>
<Border Margin="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"/>
</Border>
<StackPanel Name="liveTag" Margin="5,30" Background="Red" BorderBrush="White" BorderThickness="1" VerticalAlignment="Bottom" HorizontalAlignment="Right" Padding="5,2,5,3" Orientation="Horizontal" Visibility="Collapsed">
<TextBlock Text="&#xEC44; " VerticalAlignment="Center" Foreground="White" FontSize="12" FontFamily="Segoe MDL2 Assets" FontWeight="Black"/>
<TextBlock x:Uid="/Cards/live" Name="liveContent" Text="LIVE" VerticalAlignment="Center" Foreground="White" FontSize="12" FontWeight="Bold"/>
</StackPanel>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Height="50" Width="50" Margin="5,-30,5,10" Fill="{ThemeResource SystemControlBackgroundChromeMediumBrush}"/>
<PersonPicture Name="avatar" Height="46" Margin="5,-30,5,0"/>
<TextBlock MaxWidth="200" Name="channelName" HorizontalAlignment="Left" Grid.Column="1" Text="[Channel name]" TextTrimming="CharacterEllipsis" Foreground="Gray" Margin="0,2,0,0" FontSize="12"/>
<TextBlock Grid.Column="1" Name="views" Text="[Views]" HorizontalAlignment="Right" Foreground="Gray" Margin="0,2,2,0" FontSize="12"/>
</Grid>
<TextBlock Grid.Row="2" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" Margin="5" MaxLines="2" TextTrimming="CharacterEllipsis"/>
</Grid>
<UserControl.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="/Cards/play" Icon="Play" Text="Play" Name="play" Click="Button_Click"/>
<MenuFlyoutItem x:Uid="/Cards/incognitoPlay" Text="Play incognito" Name="incognito" Click="Button_Click">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE727;"/>
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem x:Uid="/Cards/channel" Icon="Contact" Text="View channel" Name="viewChannel" Click="ViewChannel_Click"/>
<MenuFlyoutSeparator/>
<MenuFlyoutItem x:Uid="/Cards/getLink" Icon="Link" Text="Copy link" Name="getLink" Click="GetLink_Click"/>
<MenuFlyoutItem x:Uid="/Cards/openWeb" Icon="Globe" Text="Open in browser" Name="inBrowser" Click="InBrowser_Click"/>
<MenuFlyoutItem x:Uid="/Cards/share" Icon="Share" Text="Share" Name="share" Click="share_Click"/>
<MenuFlyoutSeparator/>
<MenuFlyoutSubItem x:Uid="/Cards/downloads" Icon="Download" Text="Download" Name="download"/>
<MenuFlyoutSubItem x:Uid="/Cards/addTo" Icon="Add" Text="Add to" Name="addTo"/>
<MenuFlyoutItem x:Uid="/Cards/delete" Text="Remove from playlist" Icon="Delete" Visibility="Collapsed" Name="delete" Click="Delete_Click"/>
</MenuFlyout>
</UserControl.ContextFlyout>
</UserControl>
-289
View File
@@ -1,289 +0,0 @@
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Google.Apis.YouTube.v3;
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 YoutubeExplode.Models;
using Windows.UI.Popups;
using Windows.Foundation;
using FoxTube.Pages;
using Windows.Networking.Connectivity;
using Microsoft.Toolkit.Uwp.UI.Controls;
using FoxTube.Classes;
using FoxTube.Controls.Common;
using System.Threading.Tasks;
using System.Linq;
namespace FoxTube.Controls
{
/// <summary>
/// Video item card
/// </summary>
public sealed partial class VideoCard : UserControl, ICard
{
ResourceLoader resources = ResourceLoader.GetForCurrentView("Cards");
readonly string playlistId;
readonly string id;
Video item;
HistoryItem history;
Google.Apis.YouTube.v3.Data.Video liveItem;
public VideoCard(string id, string playlist = null)
{
InitializeComponent();
this.id = id;
playlistId = playlist;
}
public async Task Initialize()
{
try
{
item = await new YoutubeClient(SecretsVault.HttpClient).GetVideoAsync(id);
title.Text = item.Title;
channelName.Text = item.Author;
ValidatePlaylist();
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("liveStreamingDetails,contentDetails");
request.Id = id;
liveItem = (await request.ExecuteAsync()).Items[0];
if(liveItem.LiveStreamingDetails.ConcurrentViewers != null)
{
if (liveItem.LiveStreamingDetails.ActualStartTime.HasValue)
{
views.Text = $"{liveItem.LiveStreamingDetails.ConcurrentViewers:0,0} {resources.GetString("/Cards/viewers")}";
if (liveItem.LiveStreamingDetails.ScheduledStartTime.HasValue && liveItem.LiveStreamingDetails.ScheduledEndTime.HasValue)
info.Text = $"{liveItem.LiveStreamingDetails.ScheduledEndTime - liveItem.LiveStreamingDetails.ActualStartTime} | {Methods.GetAgo(liveItem.LiveStreamingDetails.ActualStartTime.Value)}";
else
info.Text = Methods.GetAgo(liveItem.LiveStreamingDetails.ActualStartTime.Value);
}
else
{
views.Text = "";
if (liveItem.LiveStreamingDetails.ScheduledStartTime.HasValue && liveItem.LiveStreamingDetails.ScheduledEndTime.HasValue)
info.Text = $"{liveItem.LiveStreamingDetails.ScheduledEndTime - liveItem.LiveStreamingDetails.ScheduledStartTime} | {liveItem.LiveStreamingDetails.ScheduledStartTime}";
else
info.Text = Methods.GetAgo(item.UploadDate.DateTime);
if (liveItem.LiveStreamingDetails.ScheduledStartTime.HasValue)
liveContent.Text = resources.GetString("/Cards/goesLive") + (liveItem.LiveStreamingDetails.ScheduledStartTime.Value > DateTime.Now ? " " : " -") + (liveItem.LiveStreamingDetails.ScheduledStartTime.Value - DateTime.Now).ToString(@"hh\:mm\:ss");
else liveContent.Text = resources.GetString("/Cards/upcoming");
}
liveTag.Visibility = Visibility.Visible;
download.Visibility = Visibility.Collapsed;
}
else
{
views.Text = $"{item.Statistics.ViewCount:0,0} {resources.GetString("/Cards/views")}";
info.Text = $"{item.Duration} | {item.UploadDate}";
try { new DownloadSelector(download.Items).Initialize(item); }
catch { download.Visibility = Visibility.Collapsed; }
}
try { new AddToPlaylist(addTo.Items).Initialize(id); }
catch { addTo.Visibility = Visibility.Collapsed; }
try
{
thumbnail.Source = new BitmapImage(item.Thumbnails.MediumResUrl.ToUri());
avatar.ProfilePicture = new BitmapImage((await new YoutubeClient(SecretsVault.HttpClient).GetVideoAuthorChannelAsync(id)).LogoUrl.ToUri()) { DecodePixelHeight = 46, DecodePixelWidth = 46 };
}
catch { }
if (SecretsVault.History.Videos.Any(i => i.Id == id))
watched.Visibility = Visibility.Visible;
if (HistorySet.Items.Exists(i => i.Id == item.Id))
{
history = HistorySet.Items.Find(i => i.Id == item.Id);
watched.Visibility = Visibility.Visible;
leftOn.Value = 100 * history.LeftOn.TotalSeconds / item.Duration.TotalSeconds;
}
}
catch (Exception e)
{
(Parent as AdaptiveGridView)?.Items.Remove(this);
Visibility = Visibility.Collapsed;
if (item == null)
return;
Analytics.TrackEvent("VideoCard loading failed", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Video ID", item.Id },
{ "StackTrace", e.StackTrace }
});
}
}
async void ValidatePlaylist()
{
if (string.IsNullOrWhiteSpace(playlistId) || !SecretsVault.IsAuthorized)
return;
if (playlistId == "WL" || playlistId == SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.Likes)
delete.Visibility = Visibility.Visible;
else
{
PlaylistsResource.ListRequest playlistRequest = SecretsVault.Service.Playlists.List("snippet");
playlistRequest.Id = playlistId;
var response = await playlistRequest.ExecuteAsync();
if (response.Items[0].Snippet.ChannelId == SecretsVault.AccountId)
delete.Visibility = Visibility.Visible;
}
}
public void ItemClicked() =>
ItemClicked(false);
async void ItemClicked(bool incognito = false)
{
if (liveItem.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;
}
}
ConnectionCost connection = NetworkInformation.GetInternetConnectionProfile().GetConnectionCost();
if (SettingsStorage.CheckConnection && (connection.NetworkCostType == NetworkCostType.Fixed || connection.NetworkCostType == NetworkCostType.Variable))
{
MessageDialog dialog = new MessageDialog(resources.GetString("/Main/metered"))
{
DefaultCommandIndex = 2,
CancelCommandIndex = 1
};
dialog.Commands.Add(new UICommand(resources.GetString("/Main/yes"), null, false));
dialog.Commands.Add(new UICommand(resources.GetString("/Main/no"), null, true));
if(SecretsVault.IsAuthorized)
dialog.Commands.Add(new UICommand(resources.GetString("/Main/metered"), async (command) =>
{
try
{
Google.Apis.YouTube.v3.Data.PlaylistItem playlistItem = new Google.Apis.YouTube.v3.Data.PlaylistItem
{
Snippet = new Google.Apis.YouTube.v3.Data.PlaylistItemSnippet
{
ResourceId = new Google.Apis.YouTube.v3.Data.ResourceId
{
Kind = "youtube#video",
VideoId = item.Id
},
PlaylistId = "WL"
}
};
await SecretsVault.Service.PlaylistItems.Insert(playlistItem, "snippet").ExecuteAsync();
}
catch { }
}, true));
if ((bool)(await dialog.ShowAsync()).Id)
return;
}
Navigation.GoToVideo(id, playlistId == "HL" ? null : playlistId, incognito);
}
void Share(DataTransferManager sender, DataRequestedEventArgs args) =>
Methods.Share(args,
item.Thumbnails.MediumResUrl,
item.Title,
item.GetShortUrl(),
resources.GetString("/Cards/videoShare"));
void share_Click(object sender, RoutedEventArgs e)
{
DataTransferManager.GetForCurrentView().DataRequested += new TypedEventHandler<DataTransferManager, DataRequestedEventArgs>(Share);
DataTransferManager.ShowShareUI();
}
async void ViewChannel_Click(object sender, RoutedEventArgs e) =>
Navigation.GoToChannel((await new YoutubeClient(SecretsVault.HttpClient).GetVideoAuthorChannelAsync(id)).Id);
void GetLink_Click(object sender, RoutedEventArgs e)
{
DataPackage data = new DataPackage();
data.SetText(item.GetShortUrl());
Clipboard.SetContent(data);
}
async void InBrowser_Click(object sender, RoutedEventArgs e) =>
await Launcher.LaunchUriAsync(item.GetShortUrl().ToUri());
void Thumbnail_ImageOpened(object sender, RoutedEventArgs e) =>
show.Begin();
private async void Delete_Click(object sender, RoutedEventArgs e)
{
if (playlistId == "WL")
{
await Launcher.LaunchUriAsync("https://youtube.com/playlist?list=WL".ToUri());
return;
}
else if(playlistId == "HL")
{
if (history == null)
await Launcher.LaunchUriAsync("https://youtube.com/feed/history".ToUri());
else
{
HistorySet.Delete(history);
(Navigation.Frame.Frame.Content as History).Delete(this);
}
return;
}
try
{
PlaylistItemsResource.ListRequest request = SecretsVault.Service.PlaylistItems.List("snippet");
request.PlaylistId = playlistId;
request.VideoId = item.Id;
await SecretsVault.Service.PlaylistItems.Delete((await request.ExecuteAsync()).Items[0].Id).ExecuteAsync();
(Navigation.Frame.Frame.Content as PlaylistPage).DeleteItem(this);
}
catch { }
}
private void Button_Click(object sender, RoutedEventArgs e) =>
ItemClicked(((FrameworkElement)sender).Name == "incognito" ? true : false);
}
}
-84
View File
@@ -1,84 +0,0 @@
<UserControl
x:Class="FoxTube.Controls.VideoPage.Chat"
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:controls="using:FoxTube.Controls.VideoPage"
mc:Ignorable="d"
d:DesignHeight="600"
d:DesignWidth="400">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Name="inputField">
<TextBox x:Uid="/Chat/box" Margin="5,5,42,5" PlaceholderText="Send a message" Name="newMessage" VerticalAlignment="Center" MinHeight="32" TextWrapping="Wrap" AcceptsReturn="True"/>
<Button HorizontalAlignment="Right" Name="send" Click="send_Click" VerticalAlignment="Top"
Height="32" Width="32"
Margin="0,5,5,0" Padding="0"
Background="Transparent"
FontFamily="Segoe MDL2 Assets"
Content="&#xE122;" FontSize="30"/>
</Grid>
<ListView Name="list" Grid.Row="1" SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="controls:ChatMessage">
<Border BorderBrush="Red" BorderThickness="{Binding Path=BorderThickness}" CornerRadius="5" HorizontalAlignment="Stretch" Background="{Binding Path=Background}" Margin="0,2">
<Grid Margin="0,5,5,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Margin="5,0">
<PersonPicture Height="20">
<PersonPicture.ProfilePicture>
<BitmapImage UriSource="{Binding Path=Avatar}" DecodePixelHeight="20" DecodePixelWidth="20"/>
</PersonPicture.ProfilePicture>
</PersonPicture>
<FontIcon Glyph="&#xEC61;" Margin="2,0" Visibility="{Binding Path=IsVerified}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/Chat/verified"/>
</ToolTipService.ToolTip>
</FontIcon>
<FontIcon Glyph="&#xEC1B;" Margin="2,0" Visibility="{Binding Path=IsModerator}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/Chat/moder"/>
</ToolTipService.ToolTip>
</FontIcon>
<FontIcon Glyph="&#xECA7;" Margin="2,0" Visibility="{Binding Path=IsOwner}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/Chat/owner"/>
</ToolTipService.ToolTip>
</FontIcon>
<FontIcon Glyph="&#xE735;" Margin="2,0" Visibility="{Binding Path=IsSponsor}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/Chat/sponsor"/>
</ToolTipService.ToolTip>
</FontIcon>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Column="1" VerticalAlignment="Top" Margin="0,0,5,0">
<HyperlinkButton Tag="{Binding Path=ChannelId}" Margin="0,-6,0,0" FontWeight="Bold" Click="HyperlinkButton_Click">
<ToolTipService.ToolTip>
<TextBlock Text="{Binding Path=Author}" Style="{StaticResource CaptionTextBlockStyle}"/>
</ToolTipService.ToolTip>
<TextBlock TextTrimming="CharacterEllipsis" MaxWidth="150" Text="{Binding Path=Author}"/>
</HyperlinkButton>
<TextBlock Text=":"/>
</StackPanel>
<GroupItem Content="{Binding Path=Message}" Grid.Column="2"/>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
<ListViewItem>
<TextBlock x:Uid="/Chat/welcome" Text="Welcome to the chat room" Foreground="Gray"/>
</ListViewItem>
</ListView>
</Grid>
</UserControl>
-169
View File
@@ -1,169 +0,0 @@
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
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;
using Microsoft.Advertising.WinRT.UI;
using System.Linq;
using FoxTube.Classes;
using FoxTube.Controls.Adverts;
namespace FoxTube.Controls.VideoPage
{
public class ChatMessage
{
public string Author => message.AuthorDetails.DisplayName;
public TextBlock Message
{
get
{
TextBlock block = new TextBlock()
{
IsTextSelectionEnabled = true,
VerticalAlignment = VerticalAlignment.Top,
TextWrapping = TextWrapping.WrapWholeWords
};
block.FormatText(message.Snippet.DisplayMessage);
return block;
}
}
public string ChannelId => message.AuthorDetails.ChannelId;
public string Avatar => message.AuthorDetails.ProfileImageUrl;
public Visibility IsVerified => message.AuthorDetails.IsVerified.Value ? Visibility.Visible : Visibility.Collapsed;
public Visibility IsOwner => message.AuthorDetails.IsChatOwner.Value ? Visibility.Visible : Visibility.Collapsed;
public Visibility IsSponsor => message.AuthorDetails.IsChatSponsor.Value ? Visibility.Visible : Visibility.Collapsed;
public Visibility IsModerator => message.AuthorDetails.IsChatModerator.Value ? Visibility.Visible : Visibility.Collapsed;
public Brush Background
{
get
{
if (IsVerified == Visibility.Visible || IsOwner == Visibility.Visible || IsSponsor == Visibility.Visible || IsModerator == Visibility.Visible)
return new SolidColorBrush() { Color = Colors.Red, Opacity = .2 };
else
return new SolidColorBrush();
}
}
public Thickness BorderThickness
{
get
{
if (IsVerified == Visibility.Visible || IsOwner == Visibility.Visible || IsSponsor == Visibility.Visible || IsModerator == Visibility.Visible)
return new Thickness(2);
else
return new Thickness(0);
}
}
private LiveChatMessage message;
public ChatMessage(LiveChatMessage item) =>
message = item;
}
public sealed partial class Chat : UserControl
{
string chatId;
DateTime lastInsert;
LiveChatMessagesResource.ListRequest request;
LiveChatMessageListResponse response;
DispatcherTimer timer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(3)
};
DispatcherTimer adTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMinutes(5)
};
public Chat(string id)
{
InitializeComponent();
if (!SecretsVault.IsAuthorized)
inputField.Visibility = Visibility.Collapsed;
chatId = id;
request = SecretsVault.Service.LiveChatMessages.List(chatId, "snippet,authorDetails");
timer.Tick += Update;
timer.Start();
if (SecretsVault.AdsDisabled)
return;
adTimer.Tick += (s, e) => list.Items.Add(new ChatAdvert());
adTimer.Start();
}
public async void Update(object sender, object e)
{
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();
}
private void HyperlinkButton_Click(object sender, RoutedEventArgs e) =>
Navigation.GoToChannel(((HyperlinkButton)sender).Tag as string);
private async void send_Click(object sender, RoutedEventArgs args)
{
try
{
if (string.IsNullOrWhiteSpace(newMessage.Text))
return;
newMessage.IsEnabled = false;
send.IsEnabled = false;
LiveChatMessage message = new LiveChatMessage()
{
Snippet = new LiveChatMessageSnippet()
{
Type = "textMessageEvent",
LiveChatId = chatId,
TextMessageDetails = new LiveChatTextMessageDetails()
{
MessageText = newMessage.Text
}
}
};
LiveChatMessagesResource.InsertRequest request = SecretsVault.Service.LiveChatMessages.Insert(message, "snippet,authorDetails");
LiveChatMessage response = await request.ExecuteAsync();
if(response != null)
{
newMessage.Text = string.Empty;
list.Items.Add(new ChatMessage(response));
}
}
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 },
{ "StackTrace", e.StackTrace }
});
}
newMessage.IsEnabled = true;
send.IsEnabled = true;
}
}
}
-53
View File
@@ -1,53 +0,0 @@
<UserControl
x:Class="FoxTube.Controls.VideoPage.Comments"
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:controls="using:FoxTube.Controls"
mc:Ignorable="d">
<Grid x:Name="grid">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="30"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox x:Uid="/CommentsPage/textbox" PlaceholderText="Add a public comment" Name="newComment" MinHeight="32" TextWrapping="Wrap" AcceptsReturn="True" Margin="0,0,40,0"/>
<Button HorizontalAlignment="Right" Name="send" Click="send_Click" VerticalAlignment="Top"
Height="32" Width="32"
Margin="5,0" Padding="0"
Background="Transparent"
FontFamily="Segoe MDL2 Assets"
Content="&#xE122;" FontSize="30"/>
<ProgressBar Name="sending" IsIndeterminate="True" Visibility="Collapsed" VerticalAlignment="Bottom" HorizontalAlignment="Stretch"/>
<TextBlock Name="counter" Grid.Row="1" Text="[Comments count] Comments" VerticalAlignment="Center" FontWeight="SemiBold"/>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock x:Uid="/CommentsPage/sortBy" Text="Sort by: " VerticalAlignment="Center"/>
<Button Name="orderBtn" Background="Transparent" Content="Relevance" Foreground="Red" Padding="0" VerticalAlignment="Bottom" Margin="5,3">
<Button.Flyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="/CommentsPage/relevance" Click="toRelevance_Click" Name="toRelevance" Text="Relevance"/>
<MenuFlyoutItem x:Uid="/CommentsPage/date" Click="toDate_Click" Name="toDate" Text="Date"/>
</MenuFlyout>
</Button.Flyout>
</Button>
</StackPanel>
<ScrollViewer Grid.Row="2" Name="scroll">
<StackPanel>
<StackPanel x:Name="list">
<StackPanel.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition IsStaggeringEnabled="True"/>
</TransitionCollection>
</StackPanel.ChildrenTransitions>
</StackPanel>
<controls:ShowMore x:Name="more" Clicked="ShowMore_Clicked"/>
</StackPanel>
</ScrollViewer>
</Grid>
</UserControl>
-196
View File
@@ -1,196 +0,0 @@
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using FoxTube.Controls;
using Windows.UI.Popups;
using Windows.ApplicationModel.Resources;
using Microsoft.AppCenter.Analytics;
using System.Collections.Generic;
namespace FoxTube.Controls.VideoPage
{
/// <summary>
/// Comments placeholder
/// </summary>
public sealed partial class Comments : UserControl
{
ResourceLoader resources = ResourceLoader.GetForCurrentView("CommentsPage");
string threadId;
string token;
CommentThreadsResource.ListRequest.OrderEnum order = CommentThreadsResource.ListRequest.OrderEnum.Relevance;
CommentThreadsResource.ListRequest request;
public Comments()
{
InitializeComponent();
}
public async void Initialize(Video video)
{
threadId = video.Id;
Methods.CommentsPage = this;
if (!SecretsVault.IsAuthorized)
grid.RowDefinitions[0].Height = new GridLength(0);
counter.Text = $"{video.Statistics.CommentCount:0,0} {resources.GetString("/CommentsPage/comments")}";
orderBtn.Content = resources.GetString("/CommentsPage/relevance/Text");
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();
token = response.NextPageToken;
if (string.IsNullOrWhiteSpace(token))
more.Visibility = Visibility.Collapsed;
foreach (CommentThread comment in response.Items)
{
if ((list.Children.Count - 5) % 20 == 0 && !SecretsVault.AdsDisabled)
list.Children.Add(new Controls.Adverts.CommentAdvert());
list.Children.Add(new CommentCard(comment));
}
}
public void RemoveComment(CommentCard commentCard, string topCommentId = null)
{
if (string.IsNullOrWhiteSpace(topCommentId))
list.Children.Remove(commentCard);
else
(list.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)
return;
more.Visibility = Visibility.Visible;
order = CommentThreadsResource.ListRequest.OrderEnum.Relevance;
orderBtn.Content = resources.GetString("/CommentsPage/relevance/Text");
list.Children.Clear();
request.Order = order;
var response = await request.ExecuteAsync();
token = response.NextPageToken;
if (string.IsNullOrWhiteSpace(token))
more.Visibility = Visibility.Collapsed;
foreach (CommentThread comment in response.Items)
{
if ((list.Children.Count - 5) % 20 == 0 && !SecretsVault.AdsDisabled)
list.Children.Add(new Controls.Adverts.CommentAdvert());
list.Children.Add(new CommentCard(comment));
}
more.Complete();
}
private async void toDate_Click(object sender, RoutedEventArgs e)
{
if (order == CommentThreadsResource.ListRequest.OrderEnum.Time)
return;
more.Visibility = Visibility.Visible;
order = CommentThreadsResource.ListRequest.OrderEnum.Time;
orderBtn.Content = resources.GetString("/CommentsPage/publish");
list.Children.Clear();
request.Order = order;
var response = await request.ExecuteAsync();
token = response.NextPageToken;
if (string.IsNullOrWhiteSpace(token))
more.Visibility = Visibility.Collapsed;
foreach (CommentThread comment in response.Items)
{
if ((list.Children.Count - 5) % 20 == 0 && !SecretsVault.AdsDisabled)
list.Children.Add(new Controls.Adverts.CommentAdvert());
list.Children.Add(new CommentCard(comment));
}
more.Complete();
}
private async void send_Click(object sender, RoutedEventArgs args)
{
if (string.IsNullOrWhiteSpace(newComment.Text))
return;
newComment.IsEnabled = false;
send.IsEnabled = false;
sending.Visibility = Visibility.Visible;
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();
list.Children.Insert(0, new CommentCard(response));
newComment.Text = "";
scroll.ChangeView(null, 0, null);
}
catch (Exception e)
{
await new MessageDialog("Failed to publish your comment. Please, try again later.").ShowAsync();
Analytics.TrackEvent("Failed to post comment", new Dictionary<string, string>
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Thread ID", threadId },
{ "StackTrace", e.StackTrace }
});
}
newComment.IsEnabled = true;
send.IsEnabled = true;
sending.Visibility = Visibility.Collapsed;
}
private async void ShowMore_Clicked()
{
request.PageToken = token;
var response = await request.ExecuteAsync();
token = response.NextPageToken;
if (string.IsNullOrWhiteSpace(token))
more.Visibility = Visibility.Collapsed;
foreach (CommentThread comment in response.Items)
{
if ((list.Children.Count - 5) % 20 == 0 && !SecretsVault.AdsDisabled)
list.Children.Add(new Controls.Adverts.CommentAdvert());
list.Children.Add(new CommentCard(comment));
}
more.Complete();
}
}
}
@@ -1,15 +0,0 @@
<UserControl
x:Class="FoxTube.Controls.VideoPage.RelatedVideos"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FoxTube.Controls.VideoPage"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:pages="using:FoxTube.Pages"
mc:Ignorable="d"
d:DesignWidth="400">
<ScrollViewer>
<pages:VideoGrid x:Name="list"/>
</ScrollViewer>
</UserControl>
@@ -1,35 +0,0 @@
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using Windows.UI.Xaml.Controls;
namespace FoxTube.Controls.VideoPage
{
public sealed partial class RelatedVideos : UserControl
{
public RelatedVideos() =>
InitializeComponent();
public async void Initialize(string id)
{
list.Clear();
SearchResource.ListRequest request = SecretsVault.Service.Search.List("id");
request.RegionCode = SettingsStorage.Region;
request.RelevanceLanguage = SettingsStorage.RelevanceLanguage;
request.RelatedToVideoId = id;
request.SafeSearch = (SearchResource.ListRequest.SafeSearchEnum)SettingsStorage.SafeSearch;
request.MaxResults = 10;
request.Type = "video";
SearchListResponse response = await request.ExecuteAsync();
foreach (SearchResult video in response.Items)
list.Add(new VideoCard(video.Id.VideoId));
list.Children.Insert(1, new Adverts.CardAdvert());
}
public void OpenNext() =>
(list.Children[0] as VideoCard).ItemClicked();
}
}
@@ -1,22 +0,0 @@
<ContentDialog
x:Class="FoxTube.Controls.ReportVideo"
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"
Title="Report a video"
PrimaryButtonText="Report"
PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
IsPrimaryButtonEnabled="False"
CloseButtonText="Cancel"
DefaultButton="Primary"
x:Uid="/Report/report">
<StackPanel>
<ComboBox x:Uid="/Report/reason" x:Name="primaryReason" Header="Reason" PlaceholderText="Select a reason..." SelectionChanged="PrimaryReason_SelectionChanged" Width="300"/>
<ComboBox x:Uid="/Report/secondaryReason" x:Name="secondaryReason" PlaceholderText="Select a category..." Visibility="Collapsed" Width="300"/>
<TextBox x:Uid="/Report/comment" x:Name="comment" Header="Additional information (optional)" MinHeight="200" AcceptsReturn="True" Margin="0,10"/>
</StackPanel>
</ContentDialog>
@@ -1,69 +0,0 @@
using Windows.UI.Xaml.Controls;
using Google.Apis.YouTube.v3.Data;
using Google.Apis.YouTube.v3;
using Windows.UI.Xaml;
using System;
using Windows.UI.Popups;
using Windows.ApplicationModel.Resources;
namespace FoxTube.Controls
{
public sealed partial class ReportVideo : ContentDialog
{
string videoId;
public ReportVideo(string id)
{
InitializeComponent();
Initialize();
videoId = id;
}
async void Initialize()
{
VideoAbuseReportReasonsResource.ListRequest req = SecretsVault.Service.VideoAbuseReportReasons.List("id,snippet");
req.Hl = SettingsStorage.RelevanceLanguage;
VideoAbuseReportReasonListResponse reasons = await req.ExecuteAsync();
foreach (VideoAbuseReportReason i in reasons.Items)
primaryReason.Items.Add(new ComboBoxItem
{
Tag = i,
Content = i.Snippet.Label
});
}
private async void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
VideoAbuseReport report = new VideoAbuseReport
{
Comments = Methods.GuardFromNull(comment.Text),
VideoId = videoId,
ReasonId = ((primaryReason.SelectedItem as ComboBoxItem).Tag as VideoAbuseReportReason).Id,
SecondaryReasonId = secondaryReason.SelectedItem == null ? null : (secondaryReason.SelectedItem as ComboBoxItem).Tag as string
};
ResourceLoader resources = ResourceLoader.GetForCurrentView("Report");
try { await SecretsVault.Service.Videos.ReportAbuse(report).ExecuteAsync(); }
catch
{
await new MessageDialog(resources.GetString("/Report/err")).ShowAsync();
return;
}
await new MessageDialog(resources.GetString("/Report/submittedHeader"), resources.GetString("/Report/submittedBody")).ShowAsync();
}
private void PrimaryReason_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
IsPrimaryButtonEnabled = true;
secondaryReason.Items.Clear();
foreach (VideoAbuseReportSecondaryReason i in ((primaryReason.SelectedItem as ComboBoxItem).Tag as VideoAbuseReportReason).Snippet.SecondaryReasons)
secondaryReason.Items.Add(new ComboBoxItem
{
Tag = i.Id,
Content = i.Label
});
secondaryReason.Visibility = secondaryReason.Items.Count == 0 ? Visibility.Collapsed : Visibility.Visible;
}
}
}
@@ -1,35 +0,0 @@
<UserControl
x:Class="FoxTube.Controls.VideoPage.VideoPlaylist"
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"
mc:Ignorable="d"
d:DesignWidth="400">
<ScrollViewer x:Name="scroll">
<StackPanel>
<StackPanel Padding="8" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<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" Name="list">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Number}" VerticalAlignment="Center" Margin="0,0,8,0"/>
<Image Grid.Column="1" Source="{Binding Path=Thumbnail}" Height="65"/>
<TextBlock Grid.Column="2" Margin="8,0,0,0" VerticalAlignment="Center" TextWrapping="WrapWholeWords" Text="{Binding Path=Title}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -1,124 +0,0 @@
using FoxTube.Classes;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.ApplicationModel.Resources;
using Windows.UI.Xaml.Controls;
// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
namespace FoxTube.Controls.VideoPage
{
public sealed partial class VideoPlaylist : UserControl
{
public class PlaylistItem
{
public int Number { get; }
public string Id { get; }
public string Title { get; }
public string Thumbnail { get; }
public PlaylistItem(int number, Google.Apis.YouTube.v3.Data.PlaylistItem item)
{
Number = number;
Id = item.Snippet.ResourceId.VideoId;
Title = item.Snippet.Title;
Thumbnail = item.Snippet.Thumbnails.Medium.Url;
}
public PlaylistItem(int number, Video item)
{
Number = number;
Id = item.Id;
Title = item.Snippet.Title;
Thumbnail = item.Snippet.Thumbnails.Medium.Url;
}
}
ResourceLoader resources = ResourceLoader.GetForCurrentView("VideoPage");
public event PlaylistItemChangedEventHandler ItemChanged;
public VideoPlaylist() =>
InitializeComponent();
public ItemCollection Items => list.Items;
public int SelectedIndex => list.SelectedIndex;
public async Task Initialize(Video video, string playlist)
{
if (video.Id == "WL")
{
List<Video> items = new List<Video>();
SecretsVault.WatchLater = await Methods.GetLater();
/*if (SecretsVault.WatchLater.Count > 100)
throw new Exception("Too large playlist");*/
/*foreach (string i in SecretsVault.WatchLater)
{
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("snippet");
request.Id = i;
items.Add((await request.ExecuteAsync()).Items[0]);
}*/
for (int k = 1; k <= items.Count; k++)
list.Items.Add(new PlaylistItem(k, items[k - 1]));
playlistName.Text = resources.GetString("/Main/later/Content");
playlistChannel.Text = SecretsVault.UserChannel.Snippet.Title;
list.SelectedItem = list.Items.Find(i => ((PlaylistItem)i).Id == video.Id);
//playlistCounter.Text = $"{list.SelectedIndex + 1}/{SecretsVault.WatchLater.Count}";
}
else
{
//Retrieving data
PlaylistsResource.ListRequest playlistRequest = SecretsVault.Service.Playlists.List("snippet,contentDetails");
playlistRequest.Id = playlist;
playlistRequest.Hl = SettingsStorage.RelevanceLanguage;
Playlist playlistItem = (await playlistRequest.ExecuteAsync()).Items[0];
if (playlistItem.ContentDetails.ItemCount > 100)
throw new Exception("Too large playlist");
PlaylistItemsResource.ListRequest listRequest = SecretsVault.Service.PlaylistItems.List("snippet");
listRequest.MaxResults = 50;
listRequest.PlaylistId = playlist;
PlaylistItemListResponse listResponse;
do
{
listResponse = await listRequest.ExecuteAsync();
listRequest.PageToken = listResponse.NextPageToken;
foreach (Google.Apis.YouTube.v3.Data.PlaylistItem i in listResponse.Items)
list.Items.Add(new PlaylistItem((int)i.Snippet.Position + 1, i));
}
while (!string.IsNullOrWhiteSpace(listRequest.PageToken));
//Setting data
playlistName.Text = playlistItem.Snippet.Localized.Title;
playlistChannel.Text = Methods.GuardFromNull(playlistItem.Snippet.ChannelTitle);
list.SelectedItem = list.Items.Find(i => ((PlaylistItem)i).Id == video.Id);
playlistCounter.Text = $"{list.SelectedIndex + 1}/{playlistItem.ContentDetails.ItemCount}";
}
list.SelectionChanged += ListBox_SelectionChanged;
await Task.Delay(500);
scroll.ChangeView(null, list.SelectedIndex * 86 + 89, null, true);
}
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) =>
ItemChanged.Invoke((e.AddedItems[0] as PlaylistItem).Id);
public void Next() =>
list.SelectedIndex++;
public void Previous() =>
list.SelectedIndex--;
}
}
+4 -273
View File
@@ -105,136 +105,10 @@
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Classes\AdaptiveCommandBar.cs" />
<Compile Include="Classes\Extensions.cs" />
<Compile Include="Classes\HistorySet.cs" />
<Compile Include="Classes\InboxItem.cs" />
<Compile Include="Classes\Methods.cs" />
<Compile Include="Classes\Navigation.cs" />
<Compile Include="Classes\Processes.cs" />
<Compile Include="Classes\QualityComparer.cs" />
<Compile Include="Classes\SearchParameters.cs" />
<Compile Include="Classes\SettingsStorage.cs" />
<Compile Include="Classes\ManifestGenerator.cs" />
<Compile Include="Classes\StreamInfo.cs" />
<Compile Include="Controls\Common\AccountManager.xaml.cs">
<DependentUpon>AccountManager.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\Common\AddToPlaylist.cs" />
<Compile Include="Controls\Adverts\CardAdvert.xaml.cs">
<DependentUpon>CardAdvert.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\Adverts\ChatAdvert.xaml.cs">
<DependentUpon>ChatAdvert.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\Adverts\CommentAdvert.xaml.cs">
<DependentUpon>CommentAdvert.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\Adverts\PlayerAdvert.xaml.cs">
<DependentUpon>PlayerAdvert.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\ChannelCard.xaml.cs">
<DependentUpon>ChannelCard.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\Common\DownloadSelector.cs" />
<Compile Include="Controls\VideoPage\Chat.xaml.cs">
<DependentUpon>Chat.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\CommentCard.xaml.cs">
<DependentUpon>CommentCard.xaml</DependentUpon>
</Compile>
<Compile Include="Classes\DownloadAgent.cs" />
<Compile Include="Controls\ContentFrame.xaml.cs">
<DependentUpon>ContentFrame.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\Common\CreateAndAddPlaylist.xaml.cs">
<DependentUpon>CreateAndAddPlaylist.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\DownloadItem.xaml.cs">
<DependentUpon>DownloadItem.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\Player\LiveCaptions.xaml.cs">
<DependentUpon>LiveCaptions.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\Player\PlayerControls.cs" />
<Compile Include="Controls\Player\PlayerMain.cs" />
<Compile Include="Controls\PlaylistCard.xaml.cs">
<DependentUpon>PlaylistCard.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\VideoPage\ReportVideo.xaml.cs">
<DependentUpon>ReportVideo.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\ShowMore.xaml.cs">
<DependentUpon>ShowMore.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\VideoPage\RelatedVideos.xaml.cs">
<DependentUpon>RelatedVideos.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\VideoPage\VideoPlaylist.xaml.cs">
<DependentUpon>VideoPlaylist.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ChannelPage.xaml.cs">
<DependentUpon>ChannelPage.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\VideoPage\Comments.xaml.cs">
<DependentUpon>Comments.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\Downloads.xaml.cs">
<DependentUpon>Downloads.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\MainFrame.xaml.cs">
<DependentUpon>MainFrame.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\SettingsPages\About.xaml.cs">
<DependentUpon>About.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\History.xaml.cs">
<DependentUpon>History.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\Home.xaml.cs">
<DependentUpon>Home.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\SettingsPages\General.xaml.cs">
<DependentUpon>General.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\SettingsPages\Inbox.xaml.cs">
<DependentUpon>Inbox.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\LoadingPage.xaml.cs">
<DependentUpon>LoadingPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\PlaylistPage.xaml.cs">
<DependentUpon>PlaylistPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\SettingsPages\Translate.xaml.cs">
<DependentUpon>Translate.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\Subscriptions.xaml.cs">
<DependentUpon>Subscriptions.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Pages\Search.xaml.cs">
<DependentUpon>Search.xaml</DependentUpon>
</Compile>
<Compile Include="Classes\SecretsVault.cs" />
<Compile Include="Pages\Settings.xaml.cs">
<DependentUpon>Settings.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\VideoPage.xaml.cs">
<DependentUpon>VideoPage.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\VideoCard.xaml.cs">
<DependentUpon>VideoCard.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\VideoGrid.xaml.cs">
<DependentUpon>VideoGrid.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\Player\VideoPlayer.xaml.cs">
<DependentUpon>VideoPlayer.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
@@ -250,7 +124,6 @@
<Content Include="Assets\BadgeLogo.scale-400.png" />
<Content Include="Assets\ChannelCoverTemplate.png" />
<Content Include="Assets\Data\Patchnotes.xml" />
<Content Include="Assets\FoxGame.png" />
<Content Include="Assets\Icons\Profile.png" />
<Content Include="Assets\Icons\Send.png" />
<Content Include="Assets\LargeTile.scale-100.png" />
@@ -302,7 +175,6 @@
<None Include="FoxTube_StoreKey.pfx" />
<None Include="Package.StoreAssociation.xml" />
<Content Include="Properties\Default.rd.xml" />
<Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\SplashScreen.scale-200.png" />
<Content Include="Assets\Square150x150Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.scale-200.png" />
@@ -318,151 +190,7 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Page Include="Controls\Common\AccountManager.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\Adverts\CardAdvert.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\Adverts\ChatAdvert.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\Adverts\CommentAdvert.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\Adverts\PlayerAdvert.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\ChannelCard.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\VideoPage\Chat.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\CommentCard.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\ContentFrame.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\Common\CreateAndAddPlaylist.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\DownloadItem.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\Player\LiveCaptions.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\PlaylistCard.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\VideoPage\ReportVideo.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\ShowMore.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\VideoPage\RelatedVideos.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\VideoPage\VideoPlaylist.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\ChannelPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\VideoPage\Comments.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\Downloads.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\MainFrame.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\SettingsPages\About.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\History.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\Home.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\SettingsPages\General.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\SettingsPages\Inbox.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\LoadingPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\MainPage.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Pages\PlaylistPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\Search.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\Settings.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\SettingsPages\Translate.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Pages\Subscriptions.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\VideoPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\VideoCard.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Pages\VideoGrid.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\Player\VideoPlayer.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
@@ -575,7 +303,10 @@
<Name>Microsoft Engagement Framework</Name>
</SDKReference>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Folder Include="Classes\" />
<Folder Include="Controls\" />
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
-105
View File
@@ -1,105 +0,0 @@
<Page
NavigationCacheMode="Disabled"
x:Class="FoxTube.Pages.ChannelPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FoxTube"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:pages="using:FoxTube.Pages"
xmlns:controls="using:FoxTube.Controls"
xmlns:classes="using:FoxTube.Classes"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<Storyboard x:Name="showHeader">
<DoubleAnimation Storyboard.TargetName="ColapsedHeader" Storyboard.TargetProperty="Opacity" To="1" Duration="{StaticResource CardOpacityDuration}"/>
</Storyboard>
<Storyboard x:Name="hideHeader">
<DoubleAnimation Storyboard.TargetName="ColapsedHeader" Storyboard.TargetProperty="Opacity" To="0" Duration="{StaticResource CardOpacityDuration}"/>
</Storyboard>
<Storyboard x:Name="showThumb">
<DoubleAnimation Storyboard.TargetName="channelCover" Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="{StaticResource CardOpacityDuration}"/>
</Storyboard>
</Page.Resources>
<Grid SizeChanged="Grid_SizeChanged">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Pivot Name="content" IsHeaderItemsCarouselEnabled="False" SelectionChanged="Content_SelectionChanged">
<PivotItem x:Uid="/Channel/videos" Header="Videos">
<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"/>
</Grid>
</ParallaxView>
<ScrollViewer ViewChanged="ScrollViewer_ViewChanged" Name="videoScroll">
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Name="infoStack">
<Grid Name="infoPanel" Height="80">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Ellipse Fill="{ThemeResource ApplicationPageBackgroundThemeBrush}" Margin="5,-30,5,0"/>
<PersonPicture Name="avatar" Margin="10,-30,10,0"/>
<StackPanel Grid.Column="1">
<TextBlock Name="title" FontWeight="SemiBold" FontSize="22" Text="Channel name"/>
<TextBlock Name="subscribers" Foreground="Gray" Text="1,000,000 subscribers"/>
<TextBlock Name="videosCount" Foreground="Gray" Text="563,000 videos"/>
</StackPanel>
<TextBlock x:Name="logIn" Grid.Column="2" VerticalAlignment="Center" Margin="10" Width="250" HorizontalTextAlignment="Center" 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>
<Button Visibility="Collapsed" x:Name="subscribe" x:Uid="/Cards/subscribe" Grid.Column="2" Content="Subscribe" Height="50" Width="250" Margin="10" VerticalAlignment="Center" FontSize="18" FontWeight="SemiBold" Foreground="White" Background="Red"/>
</Grid>
<pages:VideoGrid x:Name="videoList"/>
<controls:ShowMore Clicked="VideoMore_Clicked" x:Name="videoMore"/>
</StackPanel>
</ScrollViewer>
</Grid>
</PivotItem>
<PivotItem x:Uid="/Channel/playlists" Header="Playlists">
<Grid>
<ScrollViewer>
<StackPanel>
<pages:VideoGrid x:Name="playlistList"/>
<controls:ShowMore Clicked="ShowMorePlaylists_Click" x:Name="playlistMore"/>
</StackPanel>
</ScrollViewer>
<local:LoadingPage Visibility="Collapsed" x:Name="playlistLoading" RefreshPage="Refresh_Click"/>
</Grid>
</PivotItem>
<PivotItem x:Uid="/Channel/about" Header="About channel">
<ScrollViewer>
<TextBlock Name="description" TextWrapping="WrapWholeWords" 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" Margin="10,0">
<StackPanel Orientation="Horizontal" Name="ColapsedHeader" Opacity="0" Margin="10,0">
<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" Visibility="Collapsed"/>
</StackPanel>
<AutoSuggestBox x:Uid="/Channel/search" VerticalAlignment="Center" Width="250" PlaceholderText="Search on channel" QueryIcon="Find" Name="search" QuerySubmitted="AutoSuggestBox_QuerySubmitted"/>
</StackPanel>
</Pivot.RightHeader>
</Pivot>
<classes:AdaptiveCommandBar Grid.Row="1" VerticalAlignment="Bottom" Name="commandBar">
<AppBarButton x:Uid="/Channel/openWeb" Icon="Globe" Label="Open in browser" Name="inBrowser" Click="InBrowser_Click"/>
<AppBarButton x:Uid="/Channel/refresh" Icon="Refresh" Label="Refresh" Name="refresh" Click="Refresh_Click"/>
<AppBarButton x:Uid="/Channel/share" Icon="Share" Label="Share" Name="share" Click="Share_Click"/>
</classes:AdaptiveCommandBar>
</Grid>
</Page>
-305
View File
@@ -1,305 +0,0 @@
using System;
using System.Linq;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using Windows.UI.Xaml.Media.Imaging;
using FoxTube.Controls;
using Windows.ApplicationModel.DataTransfer;
using Windows.System;
using Windows.UI;
using Windows.ApplicationModel.Resources;
using Microsoft.AppCenter.Analytics;
using System.Collections.Generic;
using FoxTube.Classes;
namespace FoxTube.Pages
{
/// <summary>
/// Channel page
/// </summary>
public sealed partial class ChannelPage : Page, INavigationPage
{
public object Parameter { get; set; } = null;
readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Cards");
public Channel item;
SearchResource.ListRequest request;
private string videoToken, playlistToken;
private bool playlistLoaded = false;
public ChannelPage()
{
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Parameter = e.Parameter;
Initialize(e.Parameter as string);
}
public async void Initialize(string id)
{
try
{
ChannelsResource.ListRequest infoRequest = SecretsVault.Service.Channels.List("snippet,statistics,brandingSettings");
infoRequest.Id = id;
item = (await infoRequest.ExecuteAsync()).Items[0];
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")}";
if (item.BrandingSettings != null && !item.BrandingSettings.Image.BannerImageUrl.Contains("default"))
channelCover.Source = new BitmapImage(item.BrandingSettings.Image.BannerTabletExtraHdImageUrl == null ?
item.BrandingSettings.Image.BannerImageUrl.ToUri() : item.BrandingSettings.Image.BannerTabletHdImageUrl.ToUri());
avatar.ProfilePicture = collapsedAvatar.ProfilePicture = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri()) { DecodePixelHeight = 100, DecodePixelWidth = 100 };
description.FormatText(item.Snippet.Description);
request = SecretsVault.Service.Search.List("id");
request.ChannelId = id;
request.Type = "video";
request.Order = SearchResource.ListRequest.OrderEnum.Date;
request.MaxResults = 25;
SearchListResponse response = await request.ExecuteAsync();
foreach (SearchResult i in response.Items)
videoList.Add(new VideoCard(i.Id.VideoId));
if (!string.IsNullOrWhiteSpace(response.NextPageToken))
videoToken = response.NextPageToken;
else
videoMore.Visibility = Visibility.Collapsed;
if (SecretsVault.IsAuthorized)
{
logIn.Visibility = Visibility.Collapsed;
if (SecretsVault.Subscriptions.Any(i => i.Snippet.ResourceId.ChannelId == item.Id))
{
subscribe.Background = Background;
subscribe.Foreground = new SolidColorBrush(Colors.Gray);
subscribe.Content = resources.GetString("/Cards/unsubscribe");
}
else
collapsedBtn.Visibility = Visibility.Visible;
subscribe.Visibility = Visibility.Visible;
}
if (id == SecretsVault.AccountId)
{
infoPanel.ColumnDefinitions[2].Width = new GridLength(0);
collapsedBtn.Visibility = Visibility.Collapsed;
}
ScrollViewer_ViewChanged(this, null);
Navigation.Frame.Frame.LoadingPage.Close();
}
catch (System.Net.Http.HttpRequestException)
{
Navigation.Frame.Frame.LoadingPage.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch (Exception e)
{
if(item == null)
{
Navigation.Frame.Frame.LoadingPage.Error("ChannelNotFound", "Such channel doesn't exist");
return;
}
Navigation.Frame.Frame.LoadingPage.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("Channel loading error", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Channel ID", item.Id },
{ "StackTrace", e.StackTrace }
});
}
}
async void LoadPlaylist()
{
try
{
playlistLoading.Refresh();
request.Type = "playlist";
SearchListResponse response = await request.ExecuteAsync();
foreach (SearchResult i in response.Items)
playlistList.Add(new PlaylistCard(i.Id.PlaylistId));
if (!string.IsNullOrWhiteSpace(response.NextPageToken))
playlistToken = response.NextPageToken;
else
playlistMore.Visibility = Visibility.Collapsed;
playlistLoaded = true;
playlistLoading.Close();
}
catch (System.Net.Http.HttpRequestException)
{
playlistLoading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch (Exception e)
{
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", item.Id },
{ "StackTrace", e.StackTrace }
});
}
}
private void Content_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (content.SelectedIndex == 1 && !playlistLoaded)
LoadPlaylist();
if (content.SelectedIndex == 0)
ScrollViewer_ViewChanged(this, null);
else
showHeader.Begin();
}
private async void ShowMorePlaylists_Click()
{
request.Type = "playlist";
request.PageToken = playlistToken;
SearchListResponse response = await request.ExecuteAsync();
foreach (SearchResult i in response.Items)
playlistList.Add(new PlaylistCard(i.Id.PlaylistId));
if (string.IsNullOrWhiteSpace(response.NextPageToken))
playlistMore.Visibility = Visibility.Collapsed;
else
{
playlistToken = response.NextPageToken;
playlistMore.Complete();
}
}
private async void VideoMore_Clicked()
{
request.Type = "video";
request.PageToken = videoToken;
SearchListResponse response = await request.ExecuteAsync();
foreach (SearchResult i in response.Items)
videoList.Add(new VideoCard(i.Id.VideoId));
if (string.IsNullOrWhiteSpace(response.NextPageToken))
videoMore.Visibility = Visibility.Collapsed;
else
{
videoToken = response.NextPageToken;
videoMore.Complete();
}
}
private async void Subscribe_Click(object sender, RoutedEventArgs e)
{
if(await SecretsVault.ChangeSubscriptionState(item.Id))
{
subscribe.Background = Background;
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;
}
}
private void Hyperlink_Click(Windows.UI.Xaml.Documents.Hyperlink sender, Windows.UI.Xaml.Documents.HyperlinkClickEventArgs args)
{
SecretsVault.Authorize();
}
private void AutoSuggestBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
if (search.Text.Length < 3)
return;
if (content.Items.Count < 4)
content.Items.Add(new PivotItem()
{
Header = resources.GetString("/Channel/searchHeader"),
Content = new ContentFrame()
});
((content.Items[3] as PivotItem).Content as ContentFrame).Frame.Navigate(typeof(Search), new object[] { new SearchParameters(search.Text, item.Id), (content.Items[3] as PivotItem).Content as ContentFrame });
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)))
hideHeader.Begin();
else
showHeader.Begin();
}
private void Refresh_Click(object sender, RoutedEventArgs e)
{
Navigation.Frame.Frame.Refresh();
}
private async void InBrowser_Click(object sender, RoutedEventArgs e)
{
await Launcher.LaunchUriAsync(new Uri($"https://www.youtube.com/channel/{item.Id}"));
}
private void Share_Click(object sender, RoutedEventArgs e)
{
DataTransferManager.GetForCurrentView().DataRequested += new TypedEventHandler<DataTransferManager, DataRequestedEventArgs>(Share);
DataTransferManager.ShowShareUI();
}
private void ChannelCover_ImageOpened(object sender, RoutedEventArgs e)
{
showThumb.Begin();
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,
item.Snippet.Thumbnails.Medium.Url,
item.Snippet.Title,
string.IsNullOrWhiteSpace(item.Snippet.CustomUrl) ? $"https://www.youtube.com/channel/{item.Id}" : $"https://www.youtube.com/user/{item.Snippet.CustomUrl}",
resources.GetString("/Cards/channelShare"));
}
}
}
-36
View File
@@ -1,36 +0,0 @@
<Page
x:Class="FoxTube.Pages.Downloads"
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"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock Name="path" IsTextSelectionEnabled="True" TextWrapping="WrapWholeWords"/>
<Button Grid.Column="1" x:Uid="/Downloads/openFolder" Content="Open folder" Name="open" Click="Open_Click" VerticalAlignment="Center"/>
</Grid>
<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"/>
<ScrollViewer Grid.Row="1">
<StackPanel Name="list">
<StackPanel.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition IsStaggeringEnabled="True"/>
</TransitionCollection>
</StackPanel.ChildrenTransitions>
</StackPanel>
</ScrollViewer>
</Grid>
</Page>
-47
View File
@@ -1,47 +0,0 @@
using FoxTube.Classes;
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, INavigationPage
{
public object Parameter { get; set; } = null;
public Downloads()
{
InitializeComponent();
DownloadAgent.Page = this;
path.Text = DownloadAgent.Downloads.Path;
list.Children.Clear();
DownloadAgent.Items.ForEach(i => list.Children.Add(i));
empty.Visibility = list.Children.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
Navigation.Frame.Frame.LoadingPage.Close();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
list.Children.Clear();
DownloadAgent.Page = null;
}
async void Open_Click(object sender, RoutedEventArgs e) =>
await Launcher.LaunchFolderAsync(DownloadAgent.Downloads);
public void Remove(DownloadItem item)
{
list.Children.Remove(item);
empty.Visibility = list.Children.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
}
}
}
-61
View File
@@ -1,61 +0,0 @@
<Page
x:Class="FoxTube.Pages.History"
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:foxtube="using:FoxTube"
xmlns:controls="using:FoxTube.Controls"
xmlns:classes="using:FoxTube.Classes"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Pivot SelectionChanged="Pivot_SelectionChanged">
<PivotItem x:Uid="/Playlist/appHistory" Header="Application">
<ScrollViewer>
<StackPanel>
<Grid Margin="0,10" BorderBrush="Red" BorderThickness="5" CornerRadius="10" Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<FontIcon Glyph="&#xF6FA;" FontSize="50" VerticalAlignment="Center"/>
<StackPanel Grid.Column="1" Margin="10,0">
<TextBlock x:Uid="/Playlist/hlHeader" FontWeight="Bold" FontSize="25" Text="Missing some stuff?" VerticalAlignment="Center"/>
<TextBlock x:Uid="/Playlist/hlBody" Text="Unfortunately, for now we are unable to manage your youtube history. If you want to see your web history go to 'Website' tab" TextWrapping="WrapWholeWords"/>
</StackPanel>
</Grid>
<Button Margin="5" x:Uid="/Playlist/clear" FontFamily="Segoe UI, Segoe MDL2 Assets" Content="&#xE107; Clear history" Name="clear" Click="Clear_Click"/>
<local:VideoGrid x:Name="list"/>
<controls:ShowMore x:Name="more" Clicked="ShowMore_Clicked"/>
</StackPanel>
</ScrollViewer>
</PivotItem>
<PivotItem x:Uid="/Playlist/webHistory" Header="Website">
<Grid>
<ScrollViewer>
<StackPanel>
<local:VideoGrid x:Name="websiteList"/>
<controls:ShowMore x:Name="websiteMore" Clicked="WebsiteMore_Clicked"/>
</StackPanel>
</ScrollViewer>
<foxtube:LoadingPage x:Name="websiteLoading" Visibility="Collapsed" RefreshPage="Refresh_Click"/>
</Grid>
</PivotItem>
</Pivot>
<classes:AdaptiveCommandBar Grid.Row="1" DefaultLabelPosition="Right">
<AppBarButton x:Uid="/Playlist/refresh" Icon="Refresh" Label="Refresh" Name="refresh" Click="Refresh_Click"/>
<AppBarButton x:Uid="/Playlist/openWeb" Label="Open in browser" Icon="Globe" Name="toBrowser" Click="toBrowser_Click"/>
</classes:AdaptiveCommandBar>
</Grid>
</Page>
-152
View File
@@ -1,152 +0,0 @@
using FoxTube.Classes;
using FoxTube.Controls;
using Microsoft.AppCenter.Analytics;
using System;
using System.Collections.Generic;
using Windows.ApplicationModel.Resources;
using Windows.System;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace FoxTube.Pages
{
/// <summary>
/// YouTube history page
/// </summary>
public sealed partial class History : Page, INavigationPage
{
public object Parameter { get; set; } = null;
int page = 1;
int webPage = 0;
public History() =>
InitializeComponent();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Parameter = e.Parameter;
Initialize();
}
public async void Initialize()
{
Navigation.Frame.Frame.LoadingPage.Close();
try
{
Navigation.Frame.Frame.LoadingPage.Refresh();
await Dispatcher.RunIdleAsync((command) =>
{
for (int k = 0; k < 25 && k < HistorySet.Items.Count; k++)
list.Add(new VideoCard(HistorySet.Items[k].Id, "HL"));
if (list.Count >= HistorySet.Items.Count)
more.Visibility = Visibility.Collapsed;
if (list.Count == 0)
clear.Visibility = Visibility.Collapsed;
});
Navigation.Frame.Frame.LoadingPage.Close();
}
catch (Exception e)
{
Navigation.Frame.Frame.LoadingPage.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("Local history loading error", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "StackTrace", e.StackTrace }
});
}
}
private async void toBrowser_Click(object sender, RoutedEventArgs e) =>
await Launcher.LaunchUriAsync("https://www.youtube.com/feed/history".ToUri());
private void Refresh_Click(object sender, RoutedEventArgs e) =>
Navigation.Frame.Refresh();
private async void ShowMore_Clicked() => await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
for (int k = 25 * page++; k < 25 * page && k < HistorySet.Items.Count; k++)
list.Add(new VideoCard(HistorySet.Items[k].Id, "HL"));
if (list.Count >= HistorySet.Items.Count)
more.Visibility = Visibility.Collapsed;
else
more.Complete();
});
private async void Clear_Click(object sender, RoutedEventArgs e)
{
ResourceLoader resources = ResourceLoader.GetForCurrentView("Playlist");
MessageDialog dialog = new MessageDialog(resources.GetString("/Playlist/historyClear/Body"), resources.GetString("/Playlist/historyClear/Header"));
dialog.Commands.Add(new UICommand(resources.GetString("/Playlist/no")));
dialog.Commands.Add(new UICommand(resources.GetString("/Playlist/yes"), (command) =>
{
HistorySet.Clear();
Refresh_Click(this, null);
}));
dialog.CancelCommandIndex = 0;
dialog.DefaultCommandIndex = 1;
await dialog.ShowAsync();
}
private void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(webPage == 0)
{
webPage++;
LoadWeb();
}
}
async void LoadWeb()
{
try
{
websiteLoading.Refresh();
SecretsVault.History = await Methods.GetHistory();
for (int k = 0; k < 25 && k < SecretsVault.History.Videos.Count; k++)
websiteList.Add(new VideoCard(SecretsVault.History.Videos[k].Id, "HL"));
if (websiteList.Count >= SecretsVault.History.Videos.Count)
websiteMore.Visibility = Visibility.Collapsed;
websiteLoading.Close();
}
catch (Exception e)
{
websiteLoading.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("History loading error", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "StackTrace", e.StackTrace }
});
}
}
private async void WebsiteMore_Clicked() => await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
for (int k = 25 * webPage++; k < 25 * webPage && k < SecretsVault.History.Videos.Count; k++)
websiteList.Add(new VideoCard(SecretsVault.History.Videos[k].Id, "HL"));
if (websiteList.Count >= SecretsVault.History.Videos.Count)
websiteMore.Visibility = Visibility.Collapsed;
else
websiteMore.Complete();
});
public void Delete (VideoCard item) =>
list.DeleteItem(item);
}
}
-62
View File
@@ -1,62 +0,0 @@
<Page
x:Class="FoxTube.Home"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FoxTube"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:pages="using:FoxTube.Pages"
xmlns:controls="using:FoxTube.Controls"
xmlns:classes="using:FoxTube.Classes"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Pivot SelectionChanged="pivot_SelectionChanged" Name="pivot">
<PivotItem Name="recommended" Header="Recommended" x:Uid="/Home/recommended">
<Grid>
<ScrollViewer>
<StackPanel>
<pages:VideoGrid x:Name="recGrid"/>
<controls:ShowMore Clicked="Recommended_More" x:Name="recommendedMore"/>
</StackPanel>
</ScrollViewer>
<local:LoadingPage x:Name="recsLoading" Grid.RowSpan="2" RefreshPage="Refresh_Click"/>
</Grid>
</PivotItem>
<PivotItem Name="trending" Header="Trending" x:Uid="/Home/trending">
<Grid>
<ScrollViewer>
<StackPanel>
<pages:VideoGrid x:Name="trendGrid"/>
<controls:ShowMore Clicked="Trending_More" x:Name="trendingMore"/>
</StackPanel>
</ScrollViewer>
<local:LoadingPage x:Name="trendsLoading" Grid.RowSpan="2" RefreshPage="Refresh_Click"/>
</Grid>
</PivotItem>
<PivotItem Name="subscriptions" Header="Subscriptions" x:Uid="/Home/subs">
<Grid>
<ScrollViewer>
<StackPanel>
<pages:VideoGrid x:Name="subsGrid"/>
<controls:ShowMore Clicked="Subscriptions_More" x:Name="subscriptionsMore"/>
</StackPanel>
</ScrollViewer>
<local:LoadingPage x:Name="subsLoading" Grid.RowSpan="2" RefreshPage="Refresh_Click"/>
</Grid>
</PivotItem>
</Pivot>
<classes:AdaptiveCommandBar Grid.Row="1" Name="commandbar">
<AppBarButton Icon="Refresh" Label="Refresh" x:Uid="/Home/refresh" Click="Refresh_Click"/>
</classes:AdaptiveCommandBar>
</Grid>
</Page>
-222
View File
@@ -1,222 +0,0 @@
using System;
using System.Collections.Generic;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using FoxTube.Controls;
using System.Net.Http;
using System.Text.RegularExpressions;
using Microsoft.AppCenter.Analytics;
using FoxTube.Classes;
namespace FoxTube
{
/// <summary>
/// Home page
/// </summary>
public sealed partial class Home : Page, INavigationPage
{
public object Parameter { get; set; } = null;
private bool trendLoaded = false, recLoaded = false, subsLoaded = false;
List<string> homeList = new List<string>();
List<string> subsList = new List<string>();
VideosResource.ListRequest trendsRequest;
public Home()
{
InitializeComponent();
SecretsVault.RefreshToken();
Initialize();
}
public void Initialize()
{
if(!SecretsVault.IsAuthorized)
{
pivot.Items.Remove(recommended);
pivot.Items.Remove(subscriptions);
}
}
private void pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Navigation.Frame.Frame.LoadingPage.Close();
if (pivot.SelectedItem == recommended && !recLoaded)
LoadRecommendations();
else if (pivot.SelectedItem == trending && !trendLoaded)
LoadTrending();
else if (pivot.SelectedItem == subscriptions && !subsLoaded)
LoadSubscriptions();
}
#region Initializing tabs
async void LoadRecommendations()
{
recLoaded = false;
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)
{
recsLoading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch (Exception e)
{
recsLoading.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("Failed to load recommendations", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "StackTrace", e.StackTrace }
});
}
}
async void LoadTrending()
{
trendLoaded = false;
try
{
trendsLoading.Refresh();
trendsRequest = SecretsVault.Service.Videos.List("id");
trendsRequest.MaxResults = 25;
trendsRequest.Chart = VideosResource.ListRequest.ChartEnum.MostPopular;
trendsRequest.RegionCode = SettingsStorage.Region;
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.Id));
trendsLoading.Close();
trendLoaded = true;
}
catch (HttpRequestException)
{
trendsLoading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch(Exception e)
{
trendsLoading.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("Failed to load trendings", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "StackTrace", e.StackTrace }
});
}
}
async void LoadSubscriptions()
{
subsLoaded = false;
try
{
subsLoading.Refresh();
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 (HttpRequestException)
{
subsLoading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch (Exception e)
{
subsLoading.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("Failed to load subscriptions", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "StackTrace", e.StackTrace }
});
}
}
#endregion
#region More requests
private void Recommended_More()
{
for (int k = recGrid.Count; k < 25 + recGrid.Count && 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.Id));
trendingMore.Complete();
}
private void Subscriptions_More()
{
for (int k = subsGrid.Count; k < 25 + subsGrid.Count && k < subsList.Count; k++)
subsGrid.Add(new VideoCard(subsList[k]));
subscriptionsMore.Visibility = subsGrid.Count >= subsList.Count ? Visibility.Collapsed : Visibility.Visible;
subscriptionsMore.Complete();
}
#endregion
private void Refresh_Click(object sender, RoutedEventArgs e)
{
switch(pivot.SelectedIndex)
{
case 0:
recGrid.Clear();
LoadRecommendations();
break;
case 1:
trendsRequest = null;
trendGrid.Clear();
LoadTrending();
break;
case 2:
subsGrid.Clear();
LoadSubscriptions();
break;
}
}
}
}
-41
View File
@@ -1,41 +0,0 @@
<Page
x:Class="FoxTube.LoadingPage"
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"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<ProgressRing Name="ring" IsActive="True" Foreground="Red" Width="100" Height="100"/>
<StackPanel Name="wifiTrouble" Visibility="Collapsed" VerticalAlignment="Center">
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xEB5E;" FontSize="100"/>
<TextBlock x:Uid="/LoadingPage/checkConnection" Text="Check your internet connection" TextWrapping="WrapWholeWords" FontSize="48" HorizontalAlignment="Center"/>
<TextBlock x:Uid="/LoadingPage/wifiDesc" Text="Please, make sure you are connected to the internet and try again." TextWrapping="WrapWholeWords" HorizontalAlignment="Center"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button x:Uid="/LoadingPage/openWifi" Content="Open network settings" Margin="5" Name="openWifi" Click="openWifi_Click"/>
<Button x:Uid="/LoadingPage/openTroubleshooter" Content="Open troubleshooter" Background="Red" Foreground="White" Margin="5" Name="openTroubleshoot" Click="openTroubleshoot_Click"/>
</StackPanel>
<TextBlock x:Uid="/LoadingPage/or" Text="OR" FontSize="20" HorizontalAlignment="Center"/>
<Button x:Uid="/LoadingPage/refresh" Name="wifiRefresh" Click="wifiRefresh_Click" Content="Refresh page" HorizontalAlignment="Center" Background="Red" Foreground="White" Margin="5"/>
<TextBlock Name="wifiException" Foreground="Gray" Text="Exception:" HorizontalAlignment="Center" IsTextSelectionEnabled="True"/>
<TextBlock Name="wifiMessage" Foreground="Gray" Text="Message:" HorizontalAlignment="Center" IsTextSelectionEnabled="True" TextWrapping="WrapWholeWords"/>
</StackPanel>
<StackPanel Name="trouble" Visibility="Collapsed" VerticalAlignment="Center">
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE7BA;" FontSize="100" HorizontalAlignment="Center"/>
<TextBlock x:Uid="/LoadingPage/err" Text="We are unable to display the page" FontSize="48" HorizontalAlignment="Center" TextWrapping="WrapWholeWords"/>
<TextBlock x:Uid="/LoadingPage/errDescription" Text="It could be caused by YouTube internal server error or by application's bug. Please, try again later" HorizontalAlignment="Center" TextWrapping="WrapWholeWords"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button x:Uid="/LoadingPage/refresh" Name="refresh" Click="wifiRefresh_Click" Content="Refresh page" Margin="5"/>
<Button x:Uid="/LoadingPage/feedback" Content="Leave feedback" Background="Red" Foreground="White" Margin="5" Name="feedback" Click="feedback_Click"/>
</StackPanel>
<TextBlock Name="exception" Foreground="Gray" Text="Exception:" HorizontalAlignment="Center" IsTextSelectionEnabled="True"/>
<TextBlock Name="message" Foreground="Gray" Text="Message:" HorizontalAlignment="Center" IsTextSelectionEnabled="True" TextWrapping="WrapWholeWords"/>
</StackPanel>
<FontIcon Name="blockIcon" Visibility="Collapsed" Glyph="&#xE25B;" FontSize="100" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="Gray"/>
</Grid>
</Page>
-94
View File
@@ -1,94 +0,0 @@
using System;
using Windows.ApplicationModel.Resources;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace FoxTube
{
public enum LoadingState { Loadnig, Loaded, Error, Blocked }
/// <summary>
/// Loading sreen. Represents loading ring or error
/// </summary>
public sealed partial class LoadingPage : Page
{
readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("LoadingPage");
public event RoutedEventHandler RefreshPage;
public LoadingState State { get; private set; } = LoadingState.Loadnig;
public LoadingPage() =>
InitializeComponent();
public void Error(string exceptionId = "Unknown", string details = "N/A", bool isWifiTrouble = false)
{
ring.IsActive = false;
trouble.Visibility = Visibility.Collapsed;
wifiTrouble.Visibility = Visibility.Collapsed;
blockIcon.Visibility = Visibility.Collapsed;
if (isWifiTrouble)
{
wifiException.Text = $"{resources.GetString("/LoadingPage/ex")}: {exceptionId}";
wifiMessage.Text = $"{resources.GetString("/LoadingPage/details")}: {details}";
wifiTrouble.Visibility = Visibility.Visible;
}
else
{
exception.Text = $"{resources.GetString("/LoadingPage/ex")}: {exceptionId}";
message.Text = $"{resources.GetString("/LoadingPage/details")}: {details}";
trouble.Visibility = Visibility.Visible;
}
State = LoadingState.Error;
}
public void Refresh()
{
Visibility = Visibility.Visible;
ring.IsActive = true;
trouble.Visibility = Visibility.Collapsed;
wifiTrouble.Visibility = Visibility.Collapsed;
blockIcon.Visibility = Visibility.Collapsed;
State = LoadingState.Loadnig;
}
public void Close()
{
Visibility = Visibility.Collapsed;
State = LoadingState.Loaded;
}
public void Block()
{
Visibility = Visibility.Visible;
ring.IsActive = false;
trouble.Visibility = Visibility.Collapsed;
wifiTrouble.Visibility = Visibility.Collapsed;
blockIcon.Visibility = Visibility.Visible;
State = LoadingState.Blocked;
}
async void openWifi_Click(object sender, RoutedEventArgs e) =>
await Launcher.LaunchUriAsync("ms-settings:network".ToUri());
async void openTroubleshoot_Click(object sender, RoutedEventArgs e) =>
await Launcher.LaunchUriAsync("ms-settings:troubleshoot".ToUri());
async void feedback_Click(object sender, RoutedEventArgs e) =>
await Launcher.LaunchUriAsync("feedback-hub:".ToUri());
void wifiRefresh_Click(object sender, RoutedEventArgs e)
{
Refresh();
RefreshPage.Invoke(this, null);
}
}
}
-15
View File
@@ -1,15 +0,0 @@
<Page
x:Class="FoxTube.Pages.MainFrame"
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:controls="using:FoxTube.Controls"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<controls:ContentFrame x:Name="main"/>
<controls:ContentFrame x:Name="video"/>
</Grid>
</Page>
-153
View File
@@ -1,153 +0,0 @@
using FoxTube.Classes;
using FoxTube.Controls;
using System;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace FoxTube.Pages
{
public sealed partial class MainFrame : Page
{
public event NavigationEventHanlder Navigated;
public bool CanGoBack => !VideoMinimized || main.Frame.CanGoBack;
public new ContentFrame Frame => main;
public ContentFrame Video => video;
public bool VideoMinimized => video.Width != double.NaN;
public MainFrame() =>
InitializeComponent();
public void NavigateTo(Type pageType, object parameter = null, bool isNavigationPage = false)
{
if (main.Frame.SourcePageType == pageType && parameter == (main.Content as INavigationPage).Parameter)
return;
main.Navigate(pageType, parameter);
if (video.Content != null && !VideoMinimized)
MinimizeVideo();
Methods.MainPage.UpdateView();
if (!isNavigationPage)
Navigated?.Invoke(pageType, parameter);
}
public void Refresh() =>
main.Refresh();
#region Video control methods
public void OpenVideo(string id, string playlistId = null, bool incognito = false)
{
if (VideoMinimized)
MaximizeVideo();
video.Navigate(typeof(VideoPage), (id, playlistId, incognito));
Methods.MainPage.UpdateView();
}
public void MinimizeVideo(bool player = false)
{
if (video.Content == null)
return;
video.Margin = new Thickness(0, 0, 25, 50);
video.HorizontalAlignment = HorizontalAlignment.Right;
video.VerticalAlignment = VerticalAlignment.Bottom;
video.Width = 432;
if (!player)
(video.Content as VideoPage).Minimize();
Methods.MainPage.UpdateView();
}
public void MaximizeVideo(bool player = false)
{
if (video.Content == null)
return;
video.Margin = new Thickness(0);
video.VerticalAlignment = VerticalAlignment.Stretch;
video.HorizontalAlignment = HorizontalAlignment.Stretch;
video.Width = double.NaN;
if (!player)
(video.Content as VideoPage).Maximize();
Methods.MainPage.UpdateView();
}
public void CloseVideo()
{
if (ApplicationView.GetForCurrentView().IsFullScreenMode)
ApplicationView.GetForCurrentView().ExitFullScreenMode();
video.Frame.Content = null;
video.Frame.BackStack.Clear();
if (VideoMinimized)
MaximizeVideo();
GC.Collect();
Methods.MainPage.UpdateView();
}
public void RefreshVideo()
{
if (video.Content == null)
return;
if (VideoMinimized)
MaximizeVideo();
video.Refresh();
Methods.MainPage.UpdateView();
}
#endregion
public void AuthorizationChanged(bool? isAuthorized)
{
switch (isAuthorized)
{
case true:
if (main.Content == null)
NavigateTo(typeof(Home));
else if (main.Frame.CurrentSourcePageType != typeof(Downloads) && main.Frame.CurrentSourcePageType != typeof(Settings))
main.Refresh();
break;
case false:
NavigateTo(typeof(Home));
main.Frame.BackStack.Clear();
video.Frame.BackStack.Clear();
break;
}
RefreshVideo();
}
public void BackRequested()
{
if (video.Content != null && !VideoMinimized)
{
if (video.Frame.CanGoBack)
video.Frame.GoBack();
else if (video.LoadingPage.State != LoadingState.Loaded)
CloseVideo();
else
MinimizeVideo();
}
else if (main.Frame.CanGoBack)
main.Frame.GoBack();
}
}
}
+3 -89
View File
@@ -1,100 +1,14 @@
<Page
x:Class="FoxTube.MainPage"
x:Class="FoxTube.Pages.MainPage"
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"
mc:Ignorable="d"
xmlns:ui="using:Microsoft.UI.Xaml.Controls"
xmlns:controls="using:FoxTube.Controls"
xmlns:data="using:Google.Apis.YouTube.v3.Data"
xmlns:foxtube="using:FoxTube.Controls.Player"
xmlns:common="using:FoxTube.Controls.Common"
xmlns:pages="using:FoxTube.Pages">
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Name="grid">
<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>
<ui:NavigationView Name="nav" SelectionChanged="Nav_SelectionChanged"
PaneClosing="Nav_PaneClosing" PaneOpened="Nav_PaneOpened"
BackRequested="Nav_BackRequested"
IsBackEnabled="{x:Bind content.CanGoBack}">
<ui:NavigationView.Header>
<Grid>
<TextBlock Name="Title" Style="{StaticResource TitleTextBlockStyle}"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Uid="/Main/feedback" Name="feedback" Click="Feedback_Click" Visibility="Collapsed" FontFamily="Segoe MDL2 Assets" Content="&#xED15;" Background="Transparent" Height="41" Width="60" FontSize="15"/>
<common:AccountManager x:Name="manager"/>
</StackPanel>
</Grid>
</ui:NavigationView.Header>
<ui:NavigationView.MenuItemTemplate>
<DataTemplate x:DataType="data:Subscription">
<StackPanel Orientation="Horizontal" Padding="5" Margin="-5,0,0,0">
<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 FontSize="14" Text="{Binding Snippet.Title}"/>
</StackPanel>
</DataTemplate>
</ui:NavigationView.MenuItemTemplate>
<ui:NavigationView.MenuItems>
<ui:NavigationViewItem x:Uid="/Main/home" Icon="Home" Content="Home" Name="toHome"/>
<ui:NavigationViewItem x:Uid="/Main/myChannel" Icon="Contact" Content="My channel" Name="toChannel" Visibility="Collapsed"/>
<ui:NavigationViewItem x:Uid="/Main/subscriptions" Icon="People" Content="Subscriptions" Name="toSubscriptions" Visibility="Collapsed"/>
<ui:NavigationViewItemHeader x:Uid="/Main/myLibrary" FontSize="14" Content="My library" Name="libHeader" Visibility="Collapsed"/>
<ui:NavigationViewItem x:Uid="/Main/history" Content="History" Name="toHistory" Visibility="Collapsed">
<ui:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE81C;"/>
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
<ui:NavigationViewItem x:Uid="/Main/liked" Icon="Like" Content="Liked videos" Name="toLiked" Visibility="Collapsed"/>
<ui:NavigationViewItem x:Uid="/Main/later" Icon="Clock" Content="Watch later" Name="toLater" Visibility="Collapsed"/>
<ui:NavigationViewItem x:Uid="/Main/downloads" Icon="Download" Content="Downloads" Name="toDownloads"/>
<ui:NavigationViewItemHeader x:Uid="/Main/subscriptions" FontSize="14" Content="Subscriptions" Name="subsHeader" Visibility="Collapsed"/>
</ui:NavigationView.MenuItems>
<ui:NavigationView.PaneFooter>
<ui:NavigationViewList>
<ui:NavigationViewItemSeparator/>
<ui:NavigationViewItem Content="Remove ads" Visibility="Collapsed" Tapped="RemoveAds_Tapped" Name="removeAds" Icon="Shop"/>
</ui:NavigationViewList>
</ui:NavigationView.PaneFooter>
<ui:NavigationView.AutoSuggestBox>
<AutoSuggestBox x:Name="search" QueryIcon="Find" QuerySubmitted="Search_QuerySubmitted" TextChanged="Search_TextChanged" x:Uid="/Main/searchPlaceholder" PlaceholderText="Search"/>
</ui:NavigationView.AutoSuggestBox>
<pages:MainFrame x:Name="content" Navigated="Content_Navigated"/>
</ui:NavigationView>
</Grid>
</Page>
+14 -304
View File
@@ -1,320 +1,30 @@
using System;
using System.Collections.Generic;
using Windows.UI;
using Windows.UI.ViewManagement;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Google.Apis.YouTube.v3.Data;
using Windows.ApplicationModel.Core;
using FoxTube.Pages;
using Windows.UI.Popups;
using Windows.ApplicationModel.Resources;
using Microsoft.Services.Store.Engagement;
using System.Xml;
using System.Linq;
using FoxTube.Classes;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace FoxTube
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
namespace FoxTube.Pages
{
/// <summary>
/// Main app's layout
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Main");
readonly Dictionary<Type, string> headers;
public new MainFrame Content => content;
public MainPage()
{
headers = new Dictionary<Type, string>()
{
{ typeof(Settings), resources.GetString("/Main/settings/Content") },
{ typeof(ChannelPage), resources.GetString("/Main/channel") },
{ typeof(PlaylistPage), resources.GetString("/Main/playlist") },
{ typeof(Search), resources.GetString("/Main/searchPlaceholder/PlaceholderText") },
{ typeof(Subscriptions), resources.GetString("/Main/subscriptions/Content") },
{ typeof(History), resources.GetString("/Main/history/Content") },
{ typeof(Home), resources.GetString("/Main/home/Content") },
{ typeof(Downloads), resources.GetString("/Main/downloads/Content") }
};
InitializeComponent();
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 += async (sender, e) =>
{
removeAds.Visibility = (e[0] as bool?).Value ? Visibility.Collapsed : Visibility.Visible;
if (!(bool)e[0])
{
removeAds.Content = $"{resources.GetString("/Main/adsFree/Content")} ({e[1]})";
return;
this.InitializeComponent();
}
MessageDialog dialog = new MessageDialog(resources.GetString("/Main/purchaseSuccess"));
dialog.Commands.Add(new UICommand(resources.GetString("/Main/close"), (command) => CoreApplication.Exit()));
dialog.Commands.Add(new UICommand(resources.GetString("/Main/delay")));
dialog.CancelCommandIndex = 1;
dialog.DefaultCommandIndex = 0;
await dialog.ShowAsync();
};
if (SecretsVault.IsAuthorized)
AuthorizationStateChanged(true);
if (!SecretsVault.AdsDisabled)
SecretsVault.CheckAddons();
if(StoreServicesFeedbackLauncher.IsSupported())
feedback.Visibility = Visibility.Visible;
}
public void SetTitleBar(CoreApplicationViewTitleBar coreTitleBar = null)
{
if (coreTitleBar != null)
{
bool full = ApplicationView.GetForCurrentView().IsFullScreenMode;
double left = 12 + (full ? 0 : coreTitleBar.SystemOverlayLeftInset);
AppTitle.Margin = new Thickness(left, 8, 0, 0);
AppTitleBar.Height = coreTitleBar.Height;
}
var titleBar = ApplicationView.GetForCurrentView().TitleBar;
titleBar.ButtonBackgroundColor = Colors.Transparent;
titleBar.ButtonHoverBackgroundColor = Colors.IndianRed;
titleBar.ButtonPressedBackgroundColor = Colors.DarkRed;
titleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
titleBar.ButtonInactiveForegroundColor = Colors.Gray;
titleBar.ButtonForegroundColor = Application.Current.RequestedTheme == ApplicationTheme.Dark ? Colors.Black : Colors.White;
}
void SecretsVault_SubscriptionsChanged(string action, Subscription subscription)
{
switch(action)
{
case "add":
subsHeader.Visibility = Visibility.Visible;
if (nav.MenuItems.Count < 19)
nav.MenuItems.Add(subscription);
break;
case "remove":
if(nav.MenuItems.Contains(subscription))
{
nav.MenuItems.Remove(subscription);
if (SecretsVault.Subscriptions.Count >= 10)
nav.MenuItems.Add(SecretsVault.Subscriptions[9]);
else if (SecretsVault.Subscriptions.Count == 0)
subsHeader.Visibility = Visibility.Collapsed;
}
break;
}
}
async void AuthorizationStateChanged(bool? isAuthozied)
{
switch (isAuthozied)
{
case true:
manager.Logged();
toChannel.Visibility = Visibility.Visible;
toSubscriptions.Visibility = Visibility.Visible;
libHeader.Visibility = Visibility.Visible;
toHistory.Visibility = Visibility.Visible;
toLiked.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++)
nav.MenuItems.Add(SecretsVault.Subscriptions[k]);
}
HistorySet.Load();
break;
case false:
manager.Quit();
toChannel.Visibility = Visibility.Collapsed;
toSubscriptions.Visibility = Visibility.Collapsed;
libHeader.Visibility = Visibility.Collapsed;
toHistory.Visibility = Visibility.Collapsed;
toLiked.Visibility = Visibility.Collapsed;
toLater.Visibility = Visibility.Collapsed;
subsHeader.Visibility = Visibility.Collapsed;
subsHeader.Visibility = Visibility.Collapsed;
for (int k = nav.MenuItems.Count - 1; k > 8; k--)
nav.MenuItems.RemoveAt(k);
HistorySet.Items.Clear();
break;
default:
MessageDialog dialog = new MessageDialog(resources.GetString("/Main/connectErrContent"), resources.GetString("/Main/connectErrHeader"));
if (StoreServicesFeedbackLauncher.IsSupported())
dialog.Commands.Add(new UICommand(resources.GetString("/About/feedback/Content"), async (command) => await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync()));
dialog.Commands.Add(new UICommand(resources.GetString("/Main/tryAgain"), (command) => SecretsVault.Authorize()));
dialog.Commands.Add(new UICommand(resources.GetString("/Main/quit"), (command) => CoreApplication.Exit()));
dialog.CancelCommandIndex = 1;
dialog.DefaultCommandIndex = 0;
await dialog.ShowAsync();
break;
}
content.AuthorizationChanged(isAuthozied);
}
public void UpdateView()
{
if(content.Video.Content != null && !content.VideoMinimized)
Title.Text = resources.GetString("/Main/video");
else
Title.Text = headers[content.Frame.Frame.SourcePageType];
nav.IsBackEnabled = content.CanGoBack;
if (new[] { typeof(Home), typeof(Settings), typeof(Subscriptions) }.Contains(content.Frame.Frame.SourcePageType))
{
nav.ExpandedModeThresholdWidth = 1008;
nav.IsPaneOpen = nav.DisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Expanded ? true : false;
}
else
{
nav.ExpandedModeThresholdWidth = short.MaxValue;
nav.IsPaneOpen = false;
}
}
object GetChannelMenuItem(object parameter)
{
if (!SecretsVault.IsAuthorized)
return null;
if (parameter.ToString() == SecretsVault.AccountId)
return toChannel;
else
return SecretsVault.Subscriptions.Find(i => i.Snippet.ResourceId.ChannelId == parameter.ToString());
}
object GetPlaylistMenuItem(object parameter)
{
if (!SecretsVault.IsAuthorized)
return null;
if (parameter.ToString() == SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.Likes)
return toLiked;
else if (parameter.Equals("WL"))
return toLater;
else
return null;
}
void Content_Navigated(Type sourcePageType, object parameter)
{
switch (sourcePageType.Name)
{
case "Settings":
nav.SelectedItem = nav.SettingsItem;
break;
case "Subscriptions":
nav.SelectedItem = toSubscriptions;
break;
case "Downloads":
nav.SelectedItem = toDownloads;
break;
case "Home":
nav.SelectedItem = toHome;
break;
case "History":
nav.SelectedItem = toHistory;
break;
case "ChannelPage":
nav.SelectedItem = GetChannelMenuItem(parameter);
break;
case "PlaylistPage":
nav.SelectedItem = GetPlaylistMenuItem(parameter);
break;
default:
nav.SelectedItem = null;
break;
}
}
void Nav_SelectionChanged(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewSelectionChangedEventArgs args)
{
if (args.IsSettingsSelected)
content.NavigateTo(typeof(Settings));
else if (args.SelectedItem == toHome)
content.NavigateTo(typeof(Home));
else if (args.SelectedItem == toHistory)
content.NavigateTo(typeof(History));
else if (args.SelectedItem == toLiked)
content.NavigateTo(typeof(PlaylistPage), SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.Likes);
else if (args.SelectedItem == toLater)
content.NavigateTo(typeof(PlaylistPage), "WL");
else if (args.SelectedItem == toSubscriptions)
content.NavigateTo(typeof(Subscriptions));
else if (args.SelectedItem == toDownloads)
content.NavigateTo(typeof(Downloads));
else if (args.SelectedItem == toChannel)
content.NavigateTo(typeof(ChannelPage), SecretsVault.UserChannel.Id);
else
content.NavigateTo(typeof(ChannelPage), (args.SelectedItem as Subscription).Snippet.ResourceId.ChannelId);
}
void Nav_BackRequested(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewBackRequestedEventArgs args) =>
content.BackRequested();
async void Search_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) => await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
if (search.Text.Length < 3 || args.Reason != AutoSuggestionBoxTextChangeReason.UserInput)
return;
try
{
XmlDocument doc = new XmlDocument();
doc.Load($"http://suggestqueries.google.com/complete/search?ds=yt&client=toolbar&q={search.Text}&hl={SettingsStorage.RelevanceLanguage}");
search.Items.Clear();
for (int i = 0; i < doc["toplevel"].ChildNodes.Count && i < 5; i++)
search.Items.Add(doc["toplevel"].ChildNodes[i]["suggestion"].GetAttribute("data"));
}
catch { }
});
#region Simple UI interaction events
async void Feedback_Click(object sender, RoutedEventArgs e) =>
await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync();
void RemoveAds_Tapped(object sender, TappedRoutedEventArgs e) =>
SecretsVault.GetAdblock();
void Nav_PaneClosing(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewPaneClosingEventArgs args) =>
AppTitle.Visibility = Visibility.Collapsed;
void Nav_PaneOpened(Microsoft.UI.Xaml.Controls.NavigationView sender, object args) =>
AppTitle.Visibility = Visibility.Visible;
void Search_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
if (!string.IsNullOrWhiteSpace(search.Text))
Navigation.GoToSearch(new SearchParameters(search.Text));
}
#endregion
}
}
-86
View File
@@ -1,86 +0,0 @@
<Page
x:Class="FoxTube.Pages.PlaylistPage"
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:controls="using:FoxTube.Controls"
xmlns:classes="using:FoxTube.Classes"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="800"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="cover.(Grid.Column)" Value="0"/>
<Setter Target="cover.(Grid.Row)" Value="1"/>
<Setter Target="cover.(MaxWidth)" Value="300"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ScrollViewer>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Margin="10" Grid.Column="1" x:Name="cover" HorizontalAlignment="Left">
<Image Source="/Assets/videoThumbSample.png" Name="thumbnail"/>
<TextBlock FontSize="20" Text="[Title]" TextWrapping="WrapWholeWords" Name="title"/>
<TextBlock Foreground="Gray" Text="# videos | # views | Updated at: ##-##-## ##:##:##" TextWrapping="WrapWholeWords" Name="info"/>
<TextBlock Foreground="Gray" Text="description" TextWrapping="WrapWholeWords" Name="description"/>
<Button Margin="0,10" Background="Transparent" Name="toChannel" Click="toChannel_Click">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<PersonPicture Height="50" Name="avatar"/>
<TextBlock Grid.Column="1" Text="Channel name" FontSize="18" VerticalAlignment="Center" Margin="10,0" TextWrapping="WrapWholeWords" Name="channelName"/>
</Grid>
</Button>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="1">
<Grid Name="wlAlert" Margin="10" BorderBrush="Red" BorderThickness="5" CornerRadius="10" Padding="10" Visibility="Collapsed">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<FontIcon Glyph="&#xF58F;" FontSize="50" VerticalAlignment="Center"/>
<StackPanel Grid.Column="1" Margin="10,0">
<TextBlock x:Uid="/Playlist/wlHeader" FontWeight="Bold" FontSize="25" Text="Don't know how to delete video from 'Watch later' playlist?" VerticalAlignment="Center"/>
<TextBlock x:Uid="/Playlist/wlBody" Text="Unfortunately, for now we are unable to delete videos from 'Watch later' playlist. If you want to do that you have to visit YouTube web page" TextWrapping="WrapWholeWords"/>
</StackPanel>
</Grid>
<local:VideoGrid x:Name="list"/>
<controls:ShowMore x:Name="more" Clicked="ShowMore_Clicked"/>
</StackPanel>
</Grid>
</ScrollViewer>
<classes:AdaptiveCommandBar Grid.Row="2">
<AppBarButton x:Uid="/Playlist/openWeb" Icon="Globe" Label="Open in browser" Name="inBrowser" Click="inBrowser_Click"/>
<AppBarButton x:Uid="/Playlist/refresh" Icon="Refresh" Label="Refresh" Name="refresh" Click="refresh_Click"/>
<AppBarButton x:Uid="/Playlist/share" Icon="Share" Label="Share" Name="share" Click="share_Click"/>
</classes:AdaptiveCommandBar>
</Grid>
</Page>
-141
View File
@@ -1,141 +0,0 @@
using FoxTube.Classes;
using FoxTube.Controls;
using Microsoft.AppCenter.Analytics;
using System;
using System.Collections.Generic;
using Windows.ApplicationModel.DataTransfer;
using Windows.ApplicationModel.Resources;
using Windows.Foundation;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;
using YoutubeExplode.Models;
using YoutubeExplode;
namespace FoxTube.Pages
{
/// <summary>
/// Playlist page
/// </summary>
public sealed partial class PlaylistPage : Page, INavigationPage
{
public object Parameter { get; set; } = null;
string authorId;
Playlist item;
int page = 1;
public PlaylistPage() =>
InitializeComponent();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Parameter = e.Parameter;
Initialize(e.Parameter as string);
}
async void Initialize(string id)
{
try
{
SecretsVault.RefreshToken();
item = await new YoutubeClient(SecretsVault.HttpClient).GetPlaylistAsync(id);
if (id == "WL")
{
SecretsVault.WatchLater = item;
share.Visibility = Visibility.Collapsed;
wlAlert.Visibility = Visibility.Visible;
}
title.Text = item.Title;
info.Text = $"{item.Videos.Count} {ResourceLoader.GetForCurrentView("Playlist").GetString("/Playlist/videos")} | {item.Statistics.ViewCount} {ResourceLoader.GetForCurrentView("Cards").GetString("/Cards/views")}";
description.Text = item.Description;
channelName.Text = item.Author;
Google.Apis.YouTube.v3.PlaylistsResource.ListRequest infoRequest = SecretsVault.Service.Playlists.List("snippet");
infoRequest.Id = id;
authorId = (await infoRequest.ExecuteAsync()).Items[0].Snippet.ChannelId;
try
{
thumbnail.Source = new BitmapImage((item.Videos.Count > 0 ? item.Videos[0].Thumbnails.MediumResUrl : "/Assets/videoThumbSample.png").ToUri());
avatar.ProfilePicture = new BitmapImage((await new YoutubeClient(SecretsVault.HttpClient).GetChannelAsync(authorId)).LogoUrl.ToUri()) { DecodePixelHeight = 50, DecodePixelWidth = 50 };
}
catch { }
for (int k = 0; k < 25 && k < item.Videos.Count; k++)
list.Add(new VideoCard(item.Videos[k].Id, "WL"));
if (list.Count >= item.Videos.Count)
more.Visibility = Visibility.Collapsed;
Navigation.Frame.Frame.LoadingPage.Close();
}
catch (System.Net.Http.HttpRequestException)
{
Navigation.Frame.Frame.LoadingPage.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch (Exception e)
{
if (item == null)
{
Navigation.Frame.Frame.LoadingPage.Error("PlaylistNotFound", "Such playlist doesn't exist");
return;
}
Navigation.Frame.Frame.LoadingPage.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("Playlist loading error", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Playlist ID", item.Id },
{ "StackTrace", e.StackTrace }
});
}
}
void toChannel_Click(object sender, RoutedEventArgs e) =>
Navigation.GoToChannel(authorId);
async void inBrowser_Click(object sender, RoutedEventArgs e) =>
await Launcher.LaunchUriAsync(item.GetUrl().ToUri());
void refresh_Click(object sender, RoutedEventArgs e) =>
Navigation.Frame.Refresh();
void share_Click(object sender, RoutedEventArgs e)
{
DataTransferManager.GetForCurrentView().DataRequested += new TypedEventHandler<DataTransferManager, DataRequestedEventArgs>(Share);
DataTransferManager.ShowShareUI();
}
void Share(DataTransferManager sender, DataRequestedEventArgs args)
{
Methods.Share(args,
(thumbnail.Source as BitmapImage).UriSource.AbsoluteUri,
item.Title,
item.GetUrl(),
ResourceLoader.GetForCurrentView("Cards").GetString("/Cards/playlistShare"));
}
void ShowMore_Clicked()
{
for (int k = 25 * page++; k < 25 * page && k < item.Videos.Count; k++)
list.Add(new VideoCard(item.Videos[k].Id, "WL"));
if (list.Count >= item.Videos.Count)
more.Visibility = Visibility.Collapsed;
else
more.Complete();
}
public void DeleteItem(FrameworkElement card) =>
list.DeleteItem(card);
}
}
-80
View File
@@ -1,80 +0,0 @@
<Page
x:Class="FoxTube.Search"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pages="using:FoxTube.Pages"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:FoxTube.Controls"
xmlns:classes="using:FoxTube.Classes"
mc:Ignorable="d">
<Grid Name="grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<ScrollViewer>
<StackPanel Margin="10">
<TextBlock Name="searchTerm" Text="Search results for: [searchTerm]" FontSize="28"/>
<TextBlock Name="resultsCount" Text="Found: [resultCount] item(s)" FontSize="14" Foreground="Gray"/>
<HyperlinkButton x:Uid="/Search/filters" Name="toggleFilters" Click="ToggleFilters_Click" Content="Show filters &#xE71C;" FontFamily="Default, Segoe MDL2 Assets" Visibility="Visible"/>
<StackPanel Name="filters" Visibility="Collapsed">
<GridView Padding="5" SelectionMode="None">
<ComboBox x:Uid="/Search/order" Name="order" Header="Sort by" Width="150" SelectedIndex="0">
<ComboBoxItem x:Uid="/Search/relevance" Content="Relevance"/>
<ComboBoxItem x:Uid="/Search/update" Content="Upload date"/>
<ComboBoxItem x:Uid="/Search/views" Content="View count"/>
<ComboBoxItem x:Uid="/Search/rating" Content="Rating"/>
<ComboBoxItem x:Uid="/Search/title" Content="Title"/>
</ComboBox>
<ComboBox x:Uid="/Search/type" Name="type" Header="Type" Width="150" SelectedIndex="0" SelectionChanged="Type_SelectionChanged">
<ComboBoxItem x:Uid="/Search/all" Content="All"/>
<ComboBoxItem x:Uid="/Search/video" Content="Video"/>
<ComboBoxItem x:Uid="/Search/channel" Content="Channel"/>
<ComboBoxItem x:Uid="/Search/playlist" Content="Playlist"/>
</ComboBox>
<ComboBox x:Uid="/Search/updateHeader" Name="date" Header="Upload date" Width="150" SelectedIndex="0">
<ComboBoxItem x:Uid="/Search/anytime" Content="Anytime"/>
<ComboBoxItem x:Uid="/Search/lasthr" Content="Last hour"/>
<ComboBoxItem x:Uid="/Search/today" Content="Today"/>
<ComboBoxItem x:Uid="/Search/week" Content="This week"/>
<ComboBoxItem x:Uid="/Search/month" Content="This month"/>
<ComboBoxItem x:Uid="/Search/year" Content="This year"/>
</ComboBox>
<ComboBox x:Uid="/Search/duration" Visibility="Collapsed" Name="duration" Header="Duration" Width="150" SelectedIndex="0">
<ComboBoxItem x:Uid="/Search/any" Content="Any"/>
<ComboBoxItem x:Uid="/Search/long" Content="Long (&#x3E; 20 minutes)"/>
<ComboBoxItem x:Uid="/Search/medium" Content="Medium"/>
<ComboBoxItem x:Uid="/Search/short" Content="Short (&#x3C; 4 minutes)"/>
</ComboBox>
</GridView>
<StackPanel Orientation="Horizontal">
<Button x:Uid="/Search/features" Visibility="Collapsed" Content="Features" Name="featBtn" Margin="10,0,0,10">
<Button.Flyout>
<Flyout>
<ListView x:Uid="/Search/featuresHeader" Name="features" SelectionMode="Multiple" Header="Features">
<TextBlock Text="HD"/>
<TextBlock Text="3D"/>
<TextBlock x:Uid="/Search/subs" Text="Subtitles/CC"/>
<TextBlock x:Uid="/Search/live" Text="Live"/>
<TextBlock x:Uid="/Search/cc" Text="Creative Commons"/>
</ListView>
</Flyout>
</Button.Flyout>
</Button>
<Button x:Uid="/Search/apply" Content="Apply" Margin="10,0,0,10" Name="apply" Click="Apply_Click"/>
</StackPanel>
</StackPanel>
<pages:VideoGrid x:Name="list"/>
<controls:ShowMore x:Name="more" Clicked="More_Clicked"/>
</StackPanel>
</ScrollViewer>
<classes:AdaptiveCommandBar Grid.Row="1">
<AppBarButton Label="Open in browser" Icon="Globe" Name="inBrowser" Click="InBrowser_Click"/>
<AppBarButton Label="Refresh" Icon="Refresh" Click="AppBarButton_Click"/>
</classes:AdaptiveCommandBar>
</Grid>
</Page>
-233
View File
@@ -1,233 +0,0 @@
using FoxTube.Controls;
using FoxTube.Pages;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using System;
using Windows.UI.Xaml;
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;
using FoxTube.Classes;
namespace FoxTube
{
/// <summary>
/// Search page
/// </summary>
public sealed partial class Search : Page, INavigationPage
{
public object Parameter { get; set; } = null;
readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Search");
ContentFrame frame;
public SearchParameters Parameters;
SearchResource.ListRequest request;
string nextToken;
public Search() =>
InitializeComponent();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Parameter = e.Parameter;
frame = ((object[])e.Parameter)[1] as ContentFrame;
Initialize(((object[])e.Parameter)[0] as SearchParameters);
}
public async void Initialize(SearchParameters arg)
{
try
{
Parameters = arg;
request = SecretsVault.Service.Search.List("id,snippet");
searchTerm.Text = $"{resources.GetString("/Search/header")} '{Parameters.Term}'";
if (!string.IsNullOrWhiteSpace(arg.Channel))
{
request.ChannelId = arg.Channel;
(type.Items[2] as ComboBoxItem).Visibility = Visibility.Collapsed;
(grid.Children[1] as CommandBar).Visibility = Visibility.Collapsed;
}
else if (arg.Category != null)
{
(filters.Children[0] as GridView).Items.Remove(type);
searchTerm.Text = $"{resources.GetString("/Search/category")} '{arg.Category.Snippet.Title}'";
request.VideoCategoryId = arg.Category.Id;
}
request.Q = arg.Term;
request.SafeSearch = (SearchResource.ListRequest.SafeSearchEnum)SettingsStorage.SafeSearch;
request.RegionCode = SettingsStorage.Region;
request.RelevanceLanguage = SettingsStorage.RelevanceLanguage;
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);
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);
}
order.SelectedIndex = (int)Parameters.Filter.Order;
type.SelectedIndex = (int)Parameters.Filter.Type;
duration.SelectedIndex = (int)Parameters.Filter.Duration;
if (Parameters.Filter.HD)
features.SelectedItems.Add(features.Items[0]);
if (Parameters.Filter.Is3D)
features.SelectedItems.Add(features.Items[1]);
if (Parameters.Filter.Captions)
features.SelectedItems.Add(features.Items[2]);
if (Parameters.Filter.LiveEvent)
features.SelectedItems.Add(features.Items[3]);
if (Parameters.Filter.CreativeCommons)
features.SelectedItems.Add(features.Items[4]);
SearchListResponse response = await request.ExecuteAsync();
resultsCount.Text = $"{resources.GetString("/Search/found")}: {GetResultsCout(response.PageInfo.TotalResults)} {resources.GetString("/Search/items")}";
if (!string.IsNullOrWhiteSpace(response.NextPageToken))
nextToken = response.NextPageToken;
else
more.Visibility = Visibility.Collapsed;
list.Clear();
foreach (SearchResult item in response.Items)
AddItem(item);
frame.LoadingPage.Close();
}
catch (System.Net.Http.HttpRequestException)
{
frame.LoadingPage.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch (Exception e)
{
frame.LoadingPage.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() },
{ "StackTrace", e.StackTrace }
});
}
}
public void AddItem(SearchResult result)
{
switch (result.Id.Kind)
{
case "youtube#video":
VideoCard vCard = new VideoCard(result.Id.VideoId);
list.Add(vCard);
break;
case "youtube#channel":
if (Parameters.Channel == null)
list.Add(new ChannelCard(result.Id.ChannelId, result.Snippet.LiveBroadcastContent));
break;
case "youtube#playlist":
PlaylistCard pCard = new PlaylistCard(result.Id.PlaylistId);
list.Add(pCard);
break;
}
}
public string GetResultsCout(int? count)
{
if (count == 1000000)
return count + "+";
else if (count == null)
return "0";
else
return count.ToString();
}
void ToggleFilters_Click(object sender, RoutedEventArgs e)
{
if(filters.Visibility == Visibility.Collapsed)
{
filters.Visibility = Visibility.Visible;
toggleFilters.Content = resources.GetString("/Search/hideFilters");
}
else
{
filters.Visibility = Visibility.Collapsed;
toggleFilters.Content = resources.GetString("/Search/filters/Content");
}
}
void AppBarButton_Click(object sender, RoutedEventArgs e) =>
frame.Refresh();
async void More_Clicked()
{
request.PageToken = nextToken;
SearchListResponse response = await request.ExecuteAsync();
nextToken = response.NextPageToken;
foreach (SearchResult item in response.Items)
AddItem(item);
if (!string.IsNullOrWhiteSpace(nextToken))
more.Complete();
else
more.Visibility = Visibility.Collapsed;
}
void Type_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
if (type.SelectedIndex == 1)
{
duration.Visibility = Visibility.Visible;
featBtn.Visibility = Visibility.Visible;
}
else
{
duration.Visibility = Visibility.Collapsed;
featBtn.Visibility = Visibility.Collapsed;
duration.SelectedIndex = 0;
features.SelectedItems.Clear();
}
}
catch (NullReferenceException) { }
}
void Apply_Click(object sender, RoutedEventArgs e)
{
Parameters.Filter.HD = features.SelectedItems.Contains(features.Items[0]);
Parameters.Filter.Is3D = features.SelectedItems.Contains(features.Items[1]);
Parameters.Filter.Captions = features.SelectedItems.Contains(features.Items[2]);
Parameters.Filter.LiveEvent = features.SelectedItems.Contains(features.Items[3]);
Parameters.Filter.CreativeCommons = features.SelectedItems.Contains(features.Items[4]);
Parameters.Filter.Order = (SearchParameters.Filters.Enumerations.Order)order.SelectedIndex;
Parameters.Filter.Type = (SearchParameters.Filters.Enumerations.Type)type.SelectedIndex;
Parameters.Filter.Date = (SearchParameters.Filters.Enumerations.Date)date.SelectedIndex;
Parameters.Filter.Duration = (SearchParameters.Filters.Enumerations.Duration)duration.SelectedIndex;
frame.LoadingPage.Refresh();
Initialize(Parameters);
}
async void InBrowser_Click(object sender, RoutedEventArgs e) =>
await Launcher.LaunchUriAsync($"https://www.youtube.com/results?search_query={searchTerm}".ToUri());
}
}
-33
View File
@@ -1,33 +0,0 @@
<Page
x:Class="FoxTube.Settings"
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:settingspages="using:FoxTube.Pages.SettingsPages"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Pivot SelectedIndex="0" Name="pivot" IsHeaderItemsCarouselEnabled="False" SelectionChanged="Pivot_SelectionChanged">
<PivotItem Name="generalTab" Header="General" x:Uid="/Settings/general">
<ScrollViewer>
<settingspages:General/>
</ScrollViewer>
</PivotItem>
<PivotItem Name="aboutTab" Header="About us" x:Uid="/Settings/about">
<ScrollViewer>
<settingspages:About/>
</ScrollViewer>
</PivotItem>
<PivotItem Name="translateTab" Header="Help us translate this app" x:Uid="/Settings/helpTranslate">
<ScrollViewer>
<settingspages:Translate/>
</ScrollViewer>
</PivotItem>
<PivotItem Name="inboxTab" Header="Inbox" x:Uid="/Settings/inbox">
<ScrollViewer>
<settingspages:Inbox x:Name="inbox"/>
</ScrollViewer>
</PivotItem>
</Pivot>
</Page>
-53
View File
@@ -1,53 +0,0 @@
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using FoxTube.Pages.SettingsPages;
using FoxTube.Classes;
namespace FoxTube
{
/// <summary>
/// Settings tabs placeholder
/// </summary>
public sealed partial class Settings : Page, INavigationPage
{
public object Parameter { get; set; } = null;
bool inboxLoaded = false;
string inboxId = null;
public Settings() =>
InitializeComponent();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Parameter = e.Parameter;
if(!string.IsNullOrWhiteSpace((string)e.Parameter))
switch((string)e.Parameter)
{
case "about":
pivot.SelectedItem = aboutTab;
break;
case "translate":
pivot.SelectedItem = translateTab;
break;
default:
inboxId = (string)e.Parameter;
pivot.SelectedItem = inboxTab;
break;
}
Navigation.Frame.Frame.LoadingPage.Close();
}
void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (pivot.SelectedItem == inboxTab && !inboxLoaded)
{
inbox.LoadItems(inboxId);
inboxLoaded = true;
inboxId = null;
}
}
}
}
-38
View File
@@ -1,38 +0,0 @@
<Page
x:Class="FoxTube.Pages.SettingsPages.About"
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"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<TextBlock Text="FoxTube" FontSize="28"/>
<TextBlock Name="version" Text="[currentVersion]" FontSize="14" Foreground="Gray" Margin="0,-5,0,0"/>
<TextBlock x:Uid="/About/developed" TextWrapping="WrapWholeWords" Text="Developed by Michael &#x22;XFox&#x22; Gordeev" Margin="0,10,0,0"/>
<TextBlock x:Uid="/About/specialThanks" Text="Special thanks to:" FontSize="22" FontWeight="SemiBold" Margin="0,10,0,0"/>
<TextBlock><Hyperlink NavigateUri="https://github.com/Tyrrrz">@Tyrrrz</Hyperlink> <Run x:Uid="/About/tyrrrzThanks">for his awesome library</Run></TextBlock>
<TextBlock><Hyperlink NavigateUri="https://vk.com/msreviewnet">@msreviewnet</Hyperlink> <Run x:Uid="/About/msreviewThanks">for warm welcome and first feedback</Run></TextBlock>
<TextBlock TextWrapping="WrapWholeWords" Visibility="Collapsed" Text="Special thanks to contributors for motivating me, testers and translators for making this app better everyday and you for using this app;)" Margin="0,10,0,0"/>
<TextBlock x:Uid="/About/contacts" Text="Contacts" FontSize="22" FontWeight="SemiBold" Margin="0,10,0,0"/>
<TextBlock><Run x:Uid="/About/twitter">Twitter:</Run> <Hyperlink NavigateUri="https://twitter.com/xfox111">@xfox111</Hyperlink></TextBlock>
<TextBlock><Run x:Uid="/About/vk">Vkontakte:</Run> <Hyperlink NavigateUri="https://vk.com/XFox.Mike">@XFox.Mike</Hyperlink></TextBlock>
<TextBlock Visibility="Collapsed">YouTube: <Hyperlink NavigateUri="https://youtube.com/c/FoxGameStudioChannel">@xfox</Hyperlink></TextBlock>
<TextBlock>E-mail: <Hyperlink NavigateUri="mailto:michael.xfox@outlook.com">michael.xfox@outlook.com</Hyperlink></TextBlock>
<TextBlock Visibility="Collapsed"><Run x:Uid="/About/myBlog">My website:</Run> <Hyperlink NavigateUri="https://michael-xfox.com">https://michael-xfox.com</Hyperlink></TextBlock>
<TextBlock x:Uid="/About/legal" Text="Legal stuff" FontSize="22" FontWeight="SemiBold" Margin="0,10,0,0"/>
<HyperlinkButton x:Uid="/About/ourPrivacy" Content="Our Privacy Policy" NavigateUri="https://foxgame-studio.000webhostapp.com/FoxTubeAssets/PrivacyPolicy.txt" Padding="0"/>
<HyperlinkButton x:Uid="/About/ytPrivacy" Content="YouTube Privacy Policy" NavigateUri="https://youtube.com/t/privacy" Padding="0"/>
<HyperlinkButton x:Uid="/About/terms" Content="YouTube Terms of use" NavigateUri="https://youtube.com/t/terms" Padding="0"/>
<HyperlinkButton x:Uid="/About/guides" Content="YouTube Community Guidelines" NavigateUri="https://youtube.com/t/community_guidelines" Padding="0,0,0,10"/>
<TextBlock Name="crMe" x:Uid="/About/crMe" Text="© Michael Gordeev"/>
<TextBlock Name="crYt" x:Uid="/About/crYt" Text="© YouTube, LLC"/>
<Button Name="feedback" x:Uid="/About/feedback" Content="Leave feedback" Margin="0,10" Click="Button_Click" Visibility="Collapsed"/>
</StackPanel>
</Page>
-30
View File
@@ -1,30 +0,0 @@
using Microsoft.Services.Store.Engagement;
using System;
using Windows.ApplicationModel;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace FoxTube.Pages.SettingsPages
{
/// <summary>
/// Page with general information about the app
/// </summary>
public sealed partial class About : Page
{
public About()
{
InitializeComponent();
PackageVersion ver = Package.Current.Id.Version;
version.Text = $"{ver.Major}.{ver.Minor}.{ver.Build}";
crMe.Text = crMe.Text.Insert(1, " " + DateTime.Now.Year);
crYt.Text = crYt.Text.Insert(1, " " + DateTime.Now.Year);
if (StoreServicesFeedbackLauncher.IsSupported())
feedback.Visibility = Visibility.Visible;
}
async void Button_Click(object sender, RoutedEventArgs e) =>
await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync();
}
}
-51
View File
@@ -1,51 +0,0 @@
<Page
x:Class="FoxTube.Pages.SettingsPages.General"
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"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Orientation="Vertical">
<TextBlock x:Uid="/General/preferences" Text="Preferences" FontSize="28"/>
<TextBlock x:Uid="/General/regNsearch" Text="Region &#x26; search" FontSize="22" Margin="0,10,0,0"/>
<ComboBox x:Uid="/General/interfaceLang" Header="App interface language" MinWidth="250" Name="language" SelectionChanged="language_SelectionChanged">
<ComboBoxItem x:Uid="/General/en" Content="English (United States)" Tag="en-US"/>
<ComboBoxItem x:Uid="/General/ru" Content="Russian (Russia)" Tag="ru-RU"/>
</ComboBox>
<StackPanel Name="restartNote" Visibility="Collapsed">
<TextBlock x:Uid="/General/restart" Foreground="Red" Text="Reopen the app to apply settings"/>
<Button x:Uid="/General/closeApp" Content="Close app" Background="Red" Margin="5" Click="Button_Click"/>
</StackPanel>
<ComboBox x:Uid="/General/relevanceLanguage" Header="Search relevance language" MinWidth="250" Name="relLanguage" SelectionChanged="RelLanguage_SelectionChanged"/>
<ComboBox x:Uid="/General/region" Header="Region" MinWidth="250" Name="region" SelectionChanged="region_SelectionChanged"/>
<ComboBox x:Uid="/General/safeSearch" Header="SafeSearch" MinWidth="250" Name="safeSearch" SelectionChanged="safeSearch_SelectionChanged">
<ComboBoxItem x:Uid="/General/moderate" Content="Moderate"/>
<ComboBoxItem x:Uid="/General/none" Content="None"/>
<ComboBoxItem x:Uid="/General/strict" Content="Strict"/>
</ComboBox>
<TextBlock x:Uid="/General/interface" Text="Interface" FontSize="22" Margin="0,10,0,0"/>
<ToggleSwitch x:Uid="/General/minimizedCommandBar" Name="minimizedCB" OnContent="Use compact command bar" OffContent="Use minimized command bar" Toggled="MinimizedCB_Toggled"/>
<ToggleSwitch x:Uid="/General/clipboardProcessing" x:Name="clipboardProcessing" OnContent="Process youtube links from your clipboard" OffContent="Process youtube links from your clipboard" Toggled="ClipboardProcessing_Toggled"/>
<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"/>
<ComboBoxItem Tag="auto" x:Uid="/General/auto" Content="Auto"/>
</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" 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" 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"/>
<HyperlinkButton x:Uid="/General/colorSystem" Content="Windows color settings" NavigateUri="ms-settings:colors"/>
</StackPanel>
</Page>
-147
View File
@@ -1,147 +0,0 @@
using System.Linq;
using Windows.ApplicationModel.Core;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using YoutubeExplode.Models.MediaStreams;
using System;
using Windows.Globalization;
namespace FoxTube.Pages.SettingsPages
{
/// <summary>
/// Settings page with general preferences
/// </summary>
public sealed partial class General : Page
{
public General()
{
InitializeComponent();
language.SelectedItem = language.Items.Find(i => ((ComboBoxItem)i).Tag.ToString() == SettingsStorage.Language);
safeSearch.SelectedIndex = SettingsStorage.SafeSearch;
foreach (VideoQuality i in Enum.GetValues(typeof(VideoQuality)).ToReversedList())
quality.Items.Add(new ComboBoxItem() { Tag = i.GetVideoQualityLabel(), Content = i.GetVideoQualityLabel() });
quality.SelectedItem = quality.Items.ToList().Find(i => ((ComboBoxItem)i).Tag.ToString() == SettingsStorage.VideoQuality);
clipboardProcessing.IsOn = SettingsStorage.ProcessClipboard;
minimizedCB.IsOn = SettingsStorage.AppBarClosedMode == AppBarClosedDisplayMode.Minimal;
mobileWarning.IsOn = SettingsStorage.CheckConnection;
autoplay.IsOn = SettingsStorage.Autoplay;
newVideo.IsOn = SettingsStorage.VideoNotifications;
devNews.IsOn = SettingsStorage.DevNotifications;
switch(SettingsStorage.Theme)
{
case 0:
light.IsChecked = true;
break;
case 1:
dark.IsChecked = true;
break;
case 2:
system.IsChecked = true;
break;
}
InitializeRegions();
}
async void InitializeRegions()
{
//Initialize regions
I18nRegionsResource.ListRequest regRequest = SecretsVault.Service.I18nRegions.List("snippet");
regRequest.Hl = SettingsStorage.Language;
I18nRegionListResponse regResponse = await regRequest.ExecuteAsync();
regResponse.Items.ForEach(i => region.Items.Add(new ComboBoxItem
{
Content = i.Snippet.Name,
Tag = i.Snippet.Gl
}));
region.SelectedItem = region.Items.Find(i => ((ComboBoxItem)i).Tag as string == SettingsStorage.Region);
//Initialize relevance language
I18nLanguagesResource.ListRequest langRequest = SecretsVault.Service.I18nLanguages.List("snippet");
langRequest.Hl = SettingsStorage.Language;
I18nLanguageListResponse langResponse = await langRequest.ExecuteAsync();
langResponse.Items.ForEach(i => relLanguage.Items.Add(new ComboBoxItem
{
Content = i.Snippet.Name,
Tag = i.Snippet.Hl
}));
relLanguage.SelectedItem = relLanguage.Items.Find(i => ((ComboBoxItem)i).Tag as string == SettingsStorage.RelevanceLanguage);
}
void language_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if ((language.SelectedItem as ComboBoxItem).Tag.ToString() == SettingsStorage.Language)
return;
ApplicationLanguages.PrimaryLanguageOverride = (language.SelectedItem as ComboBoxItem).Tag.ToString();
SettingsStorage.Language = (language.SelectedItem as ComboBoxItem).Tag.ToString();
restartNote.Visibility = Visibility.Visible;
}
void quality_SelectionChanged(object sender, SelectionChangedEventArgs e) =>
SettingsStorage.VideoQuality = (quality.SelectedItem as ComboBoxItem).Tag as string;
void mobileWarning_Toggled(object sender, RoutedEventArgs e) =>
SettingsStorage.CheckConnection = mobileWarning.IsOn;
void autoplay_Toggled(object sender, RoutedEventArgs e) =>
SettingsStorage.Autoplay = autoplay.IsOn;
void notification_IsEnabledChanged(object sender, RoutedEventArgs e) =>
SettingsStorage.VideoNotifications = newVideo.IsOn;
void devNews_Toggled(object sender, RoutedEventArgs e) =>
SettingsStorage.DevNotifications = devNews.IsOn;
void RelLanguage_SelectionChanged(object sender, SelectionChangedEventArgs e) =>
SettingsStorage.RelevanceLanguage = ((ComboBoxItem)relLanguage.SelectedItem).Tag.ToString();
void region_SelectionChanged(object sender, SelectionChangedEventArgs e) =>
SettingsStorage.Region = ((ComboBoxItem)region.SelectedItem).Tag.ToString();
void safeSearch_SelectionChanged(object sender, SelectionChangedEventArgs e) =>
SettingsStorage.SafeSearch = safeSearch.SelectedIndex;
void RadioButton_Checked(object sender, RoutedEventArgs e)
{
if (sender == light)
{
SettingsStorage.Theme = 0;
Methods.MainPage.RequestedTheme = ElementTheme.Light;
}
else if (sender == dark)
{
SettingsStorage.Theme = 1;
Methods.MainPage.RequestedTheme = ElementTheme.Dark;
}
else if (sender == system)
{
SettingsStorage.Theme = 2;
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;
}
Methods.MainPage.SetTitleBar();
}
void Button_Click(object sender, RoutedEventArgs e) =>
CoreApplication.Exit();
void MinimizedCB_Toggled(object sender, RoutedEventArgs e) =>
SettingsStorage.AppBarClosedMode = minimizedCB.IsOn ? AppBarClosedDisplayMode.Minimal : AppBarClosedDisplayMode.Compact;
void ClipboardProcessing_Toggled(object sender, RoutedEventArgs e) =>
SettingsStorage.ProcessClipboard = clipboardProcessing.IsOn;
}
}
-108
View File
@@ -1,108 +0,0 @@
<Page
x:Class="FoxTube.Pages.SettingsPages.Inbox"
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:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Name="grid">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup CurrentStateChanged="VisualStateGroup_CurrentStateChanged">
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="1500"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="grid.ColumnDefinitions[0].Width" Value="*"/>
<Setter Target="grid.ColumnDefinitions[1].Width" Value="3*"/>
<Setter Target="close.Visibility" Value="Collapsed"/>
<Setter Target="selector.Background" Value="{StaticResource SystemControlBackgroundChromeMediumLowBrush}"/>
</VisualState.Setters>
</VisualState>
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="1000"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="grid.ColumnDefinitions[0].Width" Value="*"/>
<Setter Target="grid.ColumnDefinitions[1].Width" Value="2*"/>
<Setter Target="close.Visibility" Value="Collapsed"/>
<Setter Target="selector.Background" Value="{StaticResource SystemControlBackgroundChromeMediumLowBrush}"/>
</VisualState.Setters>
</VisualState>
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="800"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="grid.ColumnDefinitions[0].Width" Value="*"/>
<Setter Target="grid.ColumnDefinitions[1].Width" Value="*"/>
<Setter Target="close.Visibility" Value="Collapsed"/>
<Setter Target="selector.Background" Value="{StaticResource SystemControlBackgroundChromeMediumLowBrush}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="0"/>
</Grid.ColumnDefinitions>
<StackPanel VerticalAlignment="Stretch" Name="selector">
<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>
<ListView Name="list" SelectionChanged="list_SelectionChanged" Background="Transparent">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Padding" Value="10"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="0, 10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Grid Margin="0,0,10,0">
<Ellipse Fill="Red" Height="40" Width="40"/>
<TextBlock Foreground="White" FontFamily="Segoe MDL2 Assets" Text="{Binding Path=Icon}" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Light" FontSize="17"/>
</Grid>
<StackPanel Grid.Column="1">
<TextBlock FontWeight="Bold" Text="{Binding Path=Title}" MaxLines="1" TextWrapping="Wrap"/>
<TextBlock FontSize="14" Opacity=".5" Text="{Binding Path=Subtitle}"/>
<TextBlock Opacity=".5" FontSize="13" Text="{Binding Path=TimeStampString}" TextWrapping="WrapWholeWords"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<ScrollViewer Grid.Column="1">
<StackPanel Margin="10">
<TextBlock FontWeight="Bold" Text="Hello, World!" FontSize="26" Name="title"/>
<controls:MarkdownTextBlock LinkClicked="Content_LinkClicked" IsTextSelectionEnabled="True" Text="Content" Name="content" Background="Transparent"/>
</StackPanel>
</ScrollViewer>
<Button Grid.Column="1" VerticalAlignment="Top" HorizontalAlignment="Right" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE106;" Width="50" Height="50" Name="close" Click="close_Click"/>
<Grid Grid.Column="1" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Name="block">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xE7EA;" 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>
</Page>
-148
View File
@@ -1,148 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using FoxTube.Classes;
using Windows.Storage;
using System.Xml;
using Microsoft.AppCenter.Analytics;
using System.Globalization;
namespace FoxTube.Pages.SettingsPages
{
/// <summary>
/// Page with changelogs and dev messages
/// </summary>
public sealed partial class Inbox : Page
{
List<InboxItem> items = new List<InboxItem>();
public Inbox()
{
InitializeComponent();
}
string GetLanguage()
{
if ((new[] { "ua", "ru", "by", "kz", "kg", "md", "lv", "ee" }).Contains(CultureInfo.InstalledUICulture.TwoLetterISOLanguageName))
return "ru-RU";
else
return "en-US";
}
public async void LoadItems(string id = null)
{
try
{
XmlDocument doc = new XmlDocument();
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync("ms-appx:///Assets/Data/Patchnotes.xml".ToUri());
doc.Load(await file.OpenStreamForReadAsync());
foreach (XmlElement e in doc["items"].ChildNodes)
items.Add(new InboxItem(
e.GetAttribute("version"),
e["content"][GetLanguage()].InnerText,
DateTime.Parse(e.GetAttribute("time"), CultureInfo.GetCultureInfo("en-US").DateTimeFormat)));
doc.Load("http://foxgame-studio.000webhostapp.com/foxtube-messages.xml");
foreach (XmlElement e in doc["posts"].ChildNodes)
items.Add(new InboxItem(
e["header"][GetLanguage()].InnerText,
e["content"][GetLanguage()].InnerText,
DateTime.Parse(e.GetAttribute("time"), CultureInfo.GetCultureInfo("en-US").DateTimeFormat),
e["id"].InnerText,
e["contentHeader"].InnerText));
}
catch (Exception e)
{
Analytics.TrackEvent("Failed to load inbox", new Dictionary<string, string>
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "StackTrace", e.StackTrace }
});
}
items = items.OrderBy(item => item.TimeStamp).Reverse().ToList();
items.ForEach(i => list.Items.Add(i));
if (!string.IsNullOrWhiteSpace(id))
list.SelectedItem = items.Find(i => i.Id == id);
}
void filter_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (list == null)
return;
list.Items.Clear();
CloseView();
switch (filter.SelectedIndex)
{
case 0:
items.ForEach(i => list.Items.Add(i));
break;
case 1:
List<InboxItem> messages = items.FindAll(i => i.Type == InboxItemType.Default);
messages.ForEach(i => list.Items.Add(i));
break;
case 2:
List<InboxItem> notes = items.FindAll(i => i.Type == InboxItemType.PatchNote);
notes.ForEach(i => list.Items.Add(i));
break;
}
}
void list_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!(list.SelectedItem is InboxItem item))
return;
OpenView(item.Title, item.Content);
}
void CloseView()
{
content.Text = "";
title.Text = "";
list.SelectedItem = null;
block.Visibility = Visibility.Visible;
if (grid.ColumnDefinitions[0].Width.Value == 0)
{
grid.ColumnDefinitions[1].Width = new GridLength(0);
grid.ColumnDefinitions[0].Width = new GridLength(1, GridUnitType.Star);
}
}
void OpenView(string header, string body)
{
content.Text = body;
title.Text = header;
block.Visibility = Visibility.Collapsed;
if (grid.ColumnDefinitions[1].Width.Value == 0)
{
grid.ColumnDefinitions[0].Width = new GridLength(0);
grid.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);
}
}
void close_Click(object sender, RoutedEventArgs e) =>
CloseView();
void VisualStateGroup_CurrentStateChanged(object sender, VisualStateChangedEventArgs e)
{
if (e.NewState == null)
CloseView();
}
void Content_LinkClicked(object sender, Microsoft.Toolkit.Uwp.UI.Controls.LinkClickedEventArgs e) =>
Methods.ProcessLink(e.Link);
}
}
@@ -1,68 +0,0 @@
<Page
x:Class="FoxTube.Pages.SettingsPages.Translate"
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:globalization="using:System.Globalization"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Orientation="Vertical" Margin="10">
<TextBlock x:Uid="/Translate/header" Text="Help us translate this app" FontSize="28"/>
<TextBlock x:Uid="/Translate/description" TextWrapping="WrapWholeWords" Text="You can help us make this app even better by contributing to its development by translating this app" Margin="0,0,0,10"/>
<TextBlock x:Uid="/Translate/guide0" Text="It's quite simple:" Margin="0,0,0,10"/>
<ComboBox x:Uid="/Translate/guide1" Header="1. Choose language you want to translate" PlaceholderText="Choose language..." Name="LangList" Margin="0,0,0,10" MinWidth="350" SelectionChanged="LangList_SelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="globalization:CultureInfo">
<TextBlock Text="{Binding DisplayName}" Tag="{Binding ThreeLetterISOLanguageName}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock x:Uid="/Translate/guide2" Text="2. Save language pack file to your PC" Margin="0,0,0,10"/>
<Button x:Uid="/Translate/export" Content="Export to PC (.zip)" Margin="0,0,0,10" Name="export" Click="export_Click" IsEnabled="False"/>
<TextBlock x:Uid="/Translate/guide3" TextWrapping="WrapWholeWords" Text="3. Open archive's files with any text editor you want (Notepad, Wordpad, Notepad++, VS Code, etc.)" Margin="0,0,0,10"/>
<TextBlock x:Uid="/Translate/guide4" TextWrapping="WrapWholeWords" Text="4. Edit file by translating nececcary words and sentences" Margin="0,0,0,10"/>
<TextBlock x:Uid="/Translate/guide5" TextWrapping="WrapWholeWords" Text="5. Upload final package to our servers" Margin="0,0,0,10"/>
<StackPanel Name="submitNotification" Visibility="Collapsed" BorderThickness="2" BorderBrush="OrangeRed" Width="350" HorizontalAlignment="Left" Margin="0,0,0,10" Padding="0,0,0,3">
<TextBlock Foreground="OrangeRed" FontWeight="SemiBold" Text="Attention! Once you submitted this language pack you won't be able to contribute to the language anymore. Think twice before continuing" TextWrapping="WrapWholeWords" Margin="5"/>
</StackPanel>
<Button x:Uid="/Translate/upload" Content="Choose file to upload" Margin="-1,0,0,10" HorizontalAlignment="Left" VerticalAlignment="Top" Name="upload" IsEnabled="False" Click="upload_Click"/>
<Border Background="{ThemeResource SystemChromeMediumLowColor}" CornerRadius="10" Padding="10" HorizontalAlignment="Left" Name="certification" Visibility="Collapsed">
<StackPanel>
<TextBlock x:Uid="/Translate/certHeader" FontWeight="Bold" Text="Package certification result"/>
<StackPanel Orientation="Horizontal" Margin="0,5" Name="certificationStatus">
<FontIcon Glyph="&#xEC61;" Foreground="Green"/>
<TextBlock Text="Passed" Margin="5,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Visibility="Visible">
<Button x:Uid="/Translate/submit" Content="Upload" Name="submit" Click="Submit_Click"/>
<Button x:Uid="/Translate/log" Content="View log" Name="log" Click="Log_Click"/>
<Button x:Uid="/Translate/choose" Content="Choose another file" Margin="10,0,0,0" Name="chooseFile" Click="upload_Click"/>
</StackPanel>
<ProgressBar HorizontalAlignment="Stretch" IsIndeterminate="True" Name="uploadingProgress" Visibility="Visible"/>
</StackPanel>
</Border>
<TextBlock x:Uid="/Translate/info" TextWrapping="WrapWholeWords">
It takes about 2-3 weeks to process new language pack and include it to the next updateThank you for your help &#x1F61A;<LineBreak/>
Best wishes,</TextBlock>
<TextBlock Text=" XFox"/>
<StackPanel Orientation="Horizontal" BorderBrush="Green" BorderThickness="5" Margin="0,10,30,0" Visibility="Collapsed" Name="greenResult">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xE76E;" FontSize="40" Foreground="Green" Margin="5"/>
<StackPanel>
<TextBlock x:Uid="/Translate/success" Text="Your language pack has been sent!" Foreground="Green" FontWeight="Bold" FontSize="20"/>
<TextBlock x:Uid="/Translate/successSub" Text="Thank you! It's very imortant for us. You help us making the app better" Foreground="Green"/>
</StackPanel>
</StackPanel>
</StackPanel>
</Grid>
</Page>
@@ -1,221 +0,0 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Globalization;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.Storage.Pickers;
using Windows.Storage;
using System.Net.Mail;
using Windows.UI.Popups;
using Windows.System;
using System.Threading.Tasks;
using Windows.UI.Xaml.Media;
using Windows.UI;
using System.IO.Compression;
using System.Xml;
using Windows.ApplicationModel.Resources;
namespace FoxTube.Pages.SettingsPages
{
public sealed partial class Translate : Page
{
ResourceLoader resources = ResourceLoader.GetForCurrentView("Translate");
StorageFile submission;
public Translate()
{
InitializeComponent();
foreach (CultureInfo culture in CultureInfo.GetCultures(CultureTypes.AllCultures))
if (culture.ThreeLetterISOLanguageName != "rus" && culture.ThreeLetterISOLanguageName != "eng" && culture.IsNeutralCulture)
LangList.Items.Add(culture);
}
async void export_Click(object sender, RoutedEventArgs e)
{
FileSavePicker picker = new FileSavePicker();
picker.CommitButtonText = resources.GetString("/Translate/exportPicker");
picker.DefaultFileExtension = ".zip";
picker.SuggestedFileName = "foxtube_langpack_" + ((CultureInfo)LangList.SelectedItem).ThreeLetterISOLanguageName;
picker.SuggestedStartLocation = PickerLocationId.Desktop;
picker.FileTypeChoices.Add(resources.GetString("/Translate/langpackScheme"), new List<string>() { ".zip" });
StorageFile file = await picker.PickSaveFileAsync();
if (file == null)
return;
await (await StorageFile.GetFileFromApplicationUriAsync("ms-appx:///Assets/Data/Package.zip".ToUri())).CopyAndReplaceAsync(file);
await Launcher.LaunchFileAsync(file);
}
void LangList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
greenResult.Visibility = Visibility.Collapsed;
certification.Visibility = Visibility.Collapsed;
upload.Visibility = Visibility.Visible;
upload.IsEnabled = true;
export.IsEnabled = true;
}
async void upload_Click(object sender, RoutedEventArgs e)
{
FileOpenPicker picker = new FileOpenPicker()
{
CommitButtonText = resources.GetString("/Translate/submit/Content"),
SuggestedStartLocation = PickerLocationId.Desktop
};
picker.FileTypeFilter.Clear();
picker.FileTypeFilter.Add(".zip");
StorageFile file = await picker.PickSingleFileAsync();
if (file == null)
return;
export.IsEnabled = false;
upload.Visibility = Visibility.Collapsed;
certification.Visibility = Visibility.Visible;
(certificationStatus.Children[0] as FontIcon).Glyph = "\xECC5";
(certificationStatus.Children[0] as FontIcon).Foreground = new SolidColorBrush(Colors.Orange);
(certificationStatus.Children[1] as TextBlock).Text = resources.GetString("/Translate/inprogress");
submit.IsEnabled = log.IsEnabled = chooseFile.IsEnabled = true;
submit.Visibility = Visibility.Collapsed;
log.Visibility = Visibility.Collapsed;
chooseFile.Visibility = Visibility.Collapsed;
uploadingProgress.Visibility = Visibility.Visible;
CertificationReport report = await PerformCetification(file);
if (report.Status == CertificationReport.CertificationStatus.Failed)
{
(certificationStatus.Children[0] as FontIcon).Glyph = "\xEB90";
(certificationStatus.Children[0] as FontIcon).Foreground = new SolidColorBrush(Colors.Red);
(certificationStatus.Children[1] as TextBlock).Text = resources.GetString("/Translate/failed");
chooseFile.Visibility = Visibility.Visible;
log.Visibility = Visibility.Visible;
log.Tag = report.Report;
export.IsEnabled = true;
uploadingProgress.Visibility = Visibility.Collapsed;
return;
}
submission = file;
(certificationStatus.Children[0] as FontIcon).Glyph = "\xEC61";
(certificationStatus.Children[0] as FontIcon).Foreground = new SolidColorBrush(Colors.Green);
(certificationStatus.Children[1] as TextBlock).Text = resources.GetString("/Translate/passed");
export.IsEnabled = true;
submit.Visibility = Visibility.Visible;
chooseFile.Visibility = Visibility.Visible;
uploadingProgress.Visibility = Visibility.Collapsed;
}
async Task<CertificationReport> PerformCetification(StorageFile file)
{
CertificationReport report = new CertificationReport { Status = CertificationReport.CertificationStatus.Passed };
string log = string.Empty;
StorageFolder folder = await ApplicationData.Current.RoamingFolder.CreateFolderAsync("Package certification");
StorageFile submissionSample = await folder.CreateFileAsync("sample.zip");
await file.CopyAndReplaceAsync(submissionSample);
ZipFile.ExtractToDirectory(submissionSample.Path, folder.Path);
List<string> filesDescriptions = new List<string>
{
"About", "Cards", "Channel", "Chat", "CommentsPage", "Downloads", "General", "Home", "Inbox",
"LoadingPage", "Main", "Methods", "Playlist", "Search", "Settings", "Translate", "VideoPage"
};
foreach(string i in filesDescriptions)
if (await folder.TryGetItemAsync($"{i}.resw") == null)
{
log += $"- {resources.GetString("/Translate/file")} '{i}.resw' {resources.GetString("/Translate/notfound")}\n";
report.Status = CertificationReport.CertificationStatus.Failed;
}
else
{
try { new XmlDocument().Load(await (await folder.GetFileAsync(i + ".resw")).OpenStreamForReadAsync()); }
catch
{
log += $"- {resources.GetString("/Translate/file")} '{i}.resw': {resources.GetString("/Translate/failedRead")}";
report.Status = CertificationReport.CertificationStatus.Failed;
}
}
if(report.Status == CertificationReport.CertificationStatus.Failed)
{
report.Report = await ApplicationData.Current.RoamingFolder.CreateFileAsync("certReport.txt", CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(report.Report, log);
}
await folder.DeleteAsync(StorageDeleteOption.PermanentDelete);
return report;
}
async void Log_Click(object sender, RoutedEventArgs e) =>
await Launcher.LaunchFileAsync(((Button)sender).Tag as StorageFile);
async void Submit_Click(object sender, RoutedEventArgs e)
{
uploadingProgress.Visibility = Visibility.Visible;
submit.IsEnabled = false;
chooseFile.IsEnabled = false;
MailMessage msg = new MailMessage();
msg.To.Add("michael.xfox@outlook.com");
msg.From = new MailAddress(SecretsVault.EmailCredential.UserName, "FoxTube Automatic response system");
msg.Subject = "[Automatic message] FoxTube language pack contribution";
msg.Body = $@"Language: {((CultureInfo)LangList.SelectedItem).EnglishName}
Language code: {((CultureInfo)LangList.SelectedItem).ThreeLetterISOLanguageName}
-----------------
Contributor info:
Username: {(SecretsVault.UserInfo == null ? "/UNAUTHORIZED_USER/" : SecretsVault.UserInfo.Name)}
E-mail: {(SecretsVault.UserInfo == null ? "/UNAUTHORIZED_USER/" : SecretsVault.UserInfo.Email)}";
msg.Attachments.Add(new Attachment(await submission.OpenStreamForReadAsync(), "submission.zip"));
SmtpClient client = new SmtpClient("smtp.gmail.com", 587);
client.EnableSsl = true;
client.Credentials = SecretsVault.EmailCredential;
upload.IsEnabled = false;
export.IsEnabled = false;
uploadingProgress.Visibility = Visibility.Visible;
try
{
client.Send(msg);
if(!string.IsNullOrWhiteSpace(SecretsVault.UserInfo.Email))
{
msg = new MailMessage();
msg.To.Add(SecretsVault.UserInfo.Email);
msg.From = new MailAddress(SecretsVault.EmailCredential.UserName, resources.GetString("/Translate/username"));
msg.Subject = resources.GetString("/Translate/subject");
msg.Body = $@"{SecretsVault.UserInfo.Name},
{resources.GetString("/Translate/body")}";
client.Send(msg);
}
greenResult.Visibility = Visibility.Visible;
submitNotification.Visibility = Visibility.Collapsed;
}
catch
{
MessageDialog message = new MessageDialog(resources.GetString("/Translate/failedSend/Body"), resources.GetString("/Translate/failedSend/Header"));
await message.ShowAsync();
export.IsEnabled = true;
upload.IsEnabled = true;
submit.IsEnabled = true;
chooseFile.IsEnabled = true;
}
uploadingProgress.Visibility = Visibility.Collapsed;
}
}
public class CertificationReport
{
public enum CertificationStatus { Passed, Failed, Processing }
public CertificationStatus Status { get; set; }
public StorageFile Report { get; set; }
}
}
-29
View File
@@ -1,29 +0,0 @@
<Page
x:Class="FoxTube.Pages.Subscriptions"
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:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<controls:AdaptiveGridView ItemsSource="{x:Bind list}" DesiredWidth="250" ItemClick="AdaptiveGridView_ItemClick">
<controls:AdaptiveGridView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch" Padding="5" Background="{StaticResource ButtonBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="55"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<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>
</DataTemplate>
</controls:AdaptiveGridView.ItemTemplate>
</controls:AdaptiveGridView>
</Page>
-25
View File
@@ -1,25 +0,0 @@
using FoxTube.Classes;
using Google.Apis.YouTube.v3.Data;
using System.Collections.Generic;
using Windows.UI.Xaml.Controls;
namespace FoxTube.Pages
{
/// <summary>
/// User's subscriptions page
/// </summary>
public sealed partial class Subscriptions : Page, INavigationPage
{
public object Parameter { get; set; } = null;
readonly List<Subscription> list = SecretsVault.Subscriptions;
public Subscriptions()
{
InitializeComponent();
Navigation.Frame.Frame.LoadingPage.Close();
}
void AdaptiveGridView_ItemClick(object sender, ItemClickEventArgs e) =>
Navigation.GoToChannel(((Subscription)e.ClickedItem).Snippet.ResourceId.ChannelId);
}
}
-20
View File
@@ -1,20 +0,0 @@
<Page
x:Class="FoxTube.Pages.VideoGrid"
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:ui="using:Microsoft.Toolkit.Uwp.UI.Controls"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ui:AdaptiveGridView Name="list" DesiredWidth="400" SelectionMode="None" ItemClick="List_ItemClick" HorizontalContentAlignment="Left">
<ui:AdaptiveGridView.ItemContainerTransitions>
<TransitionCollection>
<EntranceThemeTransition IsStaggeringEnabled="True"/>
</TransitionCollection>
</ui:AdaptiveGridView.ItemContainerTransitions>
</ui:AdaptiveGridView>
<TextBlock Name="empty" Text="&#xD8;" FontSize="200" Foreground="Gray" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.RowSpan="2"/>
</Grid>
</Page>
-64
View File
@@ -1,64 +0,0 @@
using FoxTube.Classes;
using FoxTube.Controls.Adverts;
using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace FoxTube.Pages
{
/// <summary>
/// Items cards container
/// </summary>
public sealed partial class VideoGrid : Page
{
public int Count => list.Items.Count;
public ItemCollection Children => list.Items;
int ItemsCount => Children.FindAll(i => i is ICard).Count;
Queue<ICard> queue = new Queue<ICard>();
public VideoGrid() =>
InitializeComponent();
public void Add(ICard card)
{
if (card == null)
return;
queue.Enqueue(card);
if (LoadItems().Status != TaskStatus.Running)
LoadItems().Start();
}
void Insert(ICard item)
{
list.Items.Add(item);
if ((ItemsCount - 5) % 25 == 0)
list.Items.Add(new CardAdvert());
empty.Visibility = Visibility.Collapsed;
}
public void Clear()
{
list.Items.Clear();
empty.Visibility = Visibility.Visible;
}
public void DeleteItem(FrameworkElement item) =>
Children.Remove(item);
void List_ItemClick(object sender, ItemClickEventArgs e) =>
(e.ClickedItem as ICard).ItemClicked();
async Task LoadItems()
{
while(queue.Count > 0)
{
ICard item = queue.Dequeue();
await item.Initialize();
Insert(item);
}
}
}
}
-147
View File
@@ -1,147 +0,0 @@
<Page
x:Class="FoxTube.Pages.VideoPage"
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:classes="using:FoxTube.Classes"
xmlns:videopage="using:FoxTube.Controls.VideoPage"
xmlns:controls="using:FoxTube.Controls"
mc:Ignorable="d">
<Grid Name="grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" SizeChanged="grid_SizeChanged">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="600"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="ratingPanel.(Grid.Row)" Value="0"/>
<Setter Target="ratingPanel.(HorizontalAlignment)" Value="Right"/>
<Setter Target="rating.Width" Value="250"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="400"/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<ScrollViewer Name="mainScroll" VerticalScrollBarVisibility="Hidden" ViewChanged="MainScroll_ViewChanged">
<StackPanel Name="mainContent">
<Border BorderBrush="Red" BorderThickness="5" CornerRadius="10" Margin="10" Name="upcoming" Visibility="Collapsed">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<FontIcon Glyph="&#xE704;" FontSize="50" VerticalAlignment="Center"/>
<TextBlock x:Uid="/VideoPage/upcomingHeader" FontWeight="Bold" FontSize="25" Text="Stream hasn't started yet" Grid.Column="1" Margin="10,0" VerticalAlignment="Center"/>
<StackPanel Grid.Column="2" Margin="10,0" Name="schedule" Visibility="Collapsed">
<TextBlock x:Uid="/VideoPage/scheduleHeader" FontSize="20" FontWeight="SemiBold" Text="Stream schedule:"/>
<TextBlock Name="start" Text="Starts at: 2/15/2019 21:00:00" Visibility="Collapsed"/>
<TextBlock Name="end" Text="Ends at: 2/15/2019 23:00:00" Visibility="Collapsed"/>
</StackPanel>
<StackPanel Grid.Column="3" Margin="10,0" Name="countdownPanel" Visibility="Collapsed">
<TextBlock x:Uid="/VideoPage/countdown" FontSize="20" FontWeight="SemiBold" Text="Stream starts in:"/>
<TextBlock Name="countdown" FontWeight="Bold" FontSize="35" Text="00:12:12"/>
</StackPanel>
</Grid>
</Border>
<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="meta"/>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Button Padding="0" Background="Transparent" Margin="5" Click="gotoChannel_Click">
<StackPanel Orientation="Horizontal">
<PersonPicture Name="channelAvatar" Width="90"/>
<StackPanel Padding="5" VerticalAlignment="Center">
<TextBlock Name="channelName" Text="[Channel name]" FontSize="18"/>
<TextBlock Name="subscribers" Text="[subscribers]" Foreground="Gray" Margin="0,0,0,5"/>
<Button x:Uid="/Cards/subscribe" Click="subscribe_Click" Height="30" Width="200" Background="Red" Foreground="White" FontSize="14" FontWeight="SemiBold" Content="Subscirbe" Name="subscribe"/>
</StackPanel>
</StackPanel>
</Button>
<StackPanel HorizontalAlignment="Stretch" Name="ratingPanel" Grid.Row="1">
<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>
<FontIcon Foreground="Gray"
HorizontalAlignment="Left"
FontSize="40"
Name="dislike" Tapped="dislike_Click"
Glyph="&#xE19E;"/>
<FontIcon Foreground="Gray"
HorizontalAlignment="Right"
FontSize="40"
Name="like" Tapped="like_Click"
Glyph="&#xE19F;"/>
</Grid>
</StackPanel>
</Grid>
<TextBlock Name="description" Text="[Description]" IsTextSelectionEnabled="True" TextWrapping="WrapWholeWords"/>
</StackPanel>
</PivotItem>
</StackPanel>
</ScrollViewer>
<classes:AdaptiveCommandBar Grid.Row="1" VerticalAlignment="Bottom" x:Name="commandbar">
<AppBarButton x:Uid="/VideoPage/download" Icon="Download" Label="Download video" Name="download">
<AppBarButton.Flyout>
<Flyout x:Name="downloadSelector"/>
</AppBarButton.Flyout>
</AppBarButton>
<AppBarButton x:Uid="/VideoPage/addTo" Name="addTo" Label="Add to" Icon="Add" Visibility="Visible">
<AppBarButton.Flyout>
<Flyout x:Name="addToSelector"/>
</AppBarButton.Flyout>
</AppBarButton>
<AppBarButton x:Uid="/VideoPage/refresh" Name="refresh" Click="refresh_Click" Icon="Refresh" Label="Refresh page"/>
<AppBarButton x:Uid="/VideoPage/share" Name="share" Click="share_Click" Icon="Share" Label="Share"/>
<AppBarButton x:Uid="/VideoPage/openWeb" Name="openBrowser" Click="openBrowser_Click" Icon="Globe" Label="Open in browser"/>
<classes:AdaptiveCommandBar.SecondaryCommands>
<AppBarButton x:Uid="/VideoPage/report" Icon="Flag" x:Name="report" Label="Report this video" Click="Report_Click"/>
</classes:AdaptiveCommandBar.SecondaryCommands>
</classes:AdaptiveCommandBar>
</Grid>
<Grid Grid.Column="1" Name="tabsPlaceholder">
<Pivot Name="pivot" SelectedIndex="0" IsHeaderItemsCarouselEnabled="False">
<PivotItem x:Uid="/VideoPage/related" Header="Suggestions">
<videopage:RelatedVideos x:Name="suggestions"/>
</PivotItem>
<PivotItem x:Uid="/VideoPage/comments" Header="Comments" Name="commentsTab">
<videopage:Comments x:Name="comments"/>
</PivotItem>
<PivotItem x:Uid="/VideoPage/playlist" Header="Playlist" Name="playlistTab">
<videopage:VideoPlaylist x:Name="playlist" ItemChanged="Playlist_ItemChanged"/>
</PivotItem>
</Pivot>
</Grid>
</Grid>
</Page>
-482
View File
@@ -1,482 +0,0 @@
using FoxTube.Classes;
using FoxTube.Controls;
using FoxTube.Controls.VideoPage;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using Microsoft.AppCenter.Analytics;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Threading.Tasks;
using Windows.ApplicationModel.DataTransfer;
using Windows.ApplicationModel.Resources;
using Windows.Foundation;
using Windows.System;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Documents;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;
using YoutubeExplode;
using YoutubeExplode.Models.MediaStreams;
namespace FoxTube.Pages
{
public enum Rating { None, Like, Dislike }
/// <summary>
/// Video page
/// </summary>
public sealed partial class VideoPage : Page, INavigationPage
{
ResourceLoader resources = ResourceLoader.GetForCurrentView("VideoPage");
public object Parameter { get; set; } = null;
string playlistItem = null;
Video item;
Channel channelItem;
bool incognito = false;
Rating userRating = Rating.None;
DispatcherTimer liveTimer;
DispatcherTimer countdownTimer;
public VideoPage()
{
InitializeComponent();
//Methods.Player.SizeChanged += Player_SizeChanged;
if (Window.Current.Bounds.Width > 1000)
return;
mainContent.Children.Remove(descriptionPanel);
pivot.Items.Insert(0, descriptionPanel);
tabsPlaceholder.Children.Remove(pivot);
mainContent.Children.Add(pivot);
grid.ColumnDefinitions[1].Width = new GridLength(0);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Parameter = e.Parameter;
Initialize(((object, Channel, bool, string))e.Parameter);
}
async void Initialize((object video, Channel channel, bool incognito, string playlist) parameter, bool playlistItemSwitch = false)
{
try
{
incognito = parameter.incognito;
channelItem = parameter.channel;
if (parameter.video is Video)
item = parameter.video as Video;
else
{
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("snippet,statistics,status,contentDetails,liveStreamingDetails");
request.Id = parameter.video as string;
request.Hl = SettingsStorage.RelevanceLanguage;
item = (await request.ExecuteAsync()).Items[0];
ChannelsResource.ListRequest channelRequest = SecretsVault.Service.Channels.List("snippet, statistics");
channelRequest.Id = item.Snippet.ChannelId;
channelItem = (await channelRequest.ExecuteAsync()).Items[0];
}
/*Methods.Player.Player.Visibility = Visibility.Visible;
Methods.Player.Player.PosterSource = new BitmapImage((item.Snippet.Thumbnails.Maxres ?? item.Snippet.Thumbnails.Medium).Url.ToUri());
Methods.Player.NextRequested += suggestions.OpenNext;*/
LoadDescription();
if (!playlistItemSwitch)
{
if (parameter.playlist != null)
{
try
{
await playlist.Initialize(item, parameter.playlist);
playlistItem = parameter.playlist;
pivot.SelectedItem = playlistTab;
}
catch
{ pivot.Items.Remove(playlistTab); }
}
else
pivot.Items.Remove(playlistTab);
}
//Initializing player
// Methods.MainPage.Player.Initialize(item, channelItem.Snippet.Thumbnails.Medium.Url, incognito, parameter.playlist == null ? null : playlist);
if (item.Snippet.LiveBroadcastContent == "none")
{
views.Text = $"{item.Statistics.ViewCount:0,0} {resources.GetString("/Cards/views")}";
comments.Initialize(item);
/*try { downloadSelector.Initialize(item); }
catch { download.Visibility = Visibility.Collapsed; }*/
}
else
LoadStream();
if (item.Snippet.LiveBroadcastContent == "upcoming")
SetSchedule();
suggestions.Initialize(item.Id);
//Methods.MainPage.VideoContent.LoadingPage.Close();
}
catch (System.Net.Http.HttpRequestException)
{
//Methods.MainPage.VideoContent.LoadingPage.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch (Exception e)
{
/*Methods.Player.Player.Visibility = Visibility.Collapsed;
if (item == null)
{
Methods.MainPage.PageContent.LoadingPage.Error("VideoNotFound", "Such video doesn't exist");
return;
}
Methods.MainPage.VideoContent.LoadingPage.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("Video loading error", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Video ID", item.Id },
{ "StackTrace", e.StackTrace }
});*/
}
}
void SetSchedule()
{
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 LoadDescription()
{
//Setting meta
title.Text = item.Snippet.Localized.Title;
description.FormatText(item.Snippet.Localized.Description);
//Setting channel button
channelAvatar.ProfilePicture = new BitmapImage(channelItem.Snippet.Thumbnails.Medium.Url.ToUri()) { DecodePixelHeight = 90, DecodePixelWidth = 90 };
channelName.Text = item.Snippet.ChannelTitle;
subscribers.Text = $"{channelItem.Statistics.SubscriberCount:0,0} {resources.GetString("/Cards/subscribers")}";
//Setting ratings
dislikes.Text = $"{item.Statistics.DislikeCount:0,0}";
likes.Text = $"{item.Statistics.LikeCount:0,0}";
try { rating.Value = (double)item.Statistics.DislikeCount / (double)(item.Statistics.DislikeCount + item.Statistics.LikeCount) * 100; }
catch { rating.Visibility = Visibility.Collapsed; }
meta.Text = "";
meta.Inlines.Add(new Run
{
Text = $"{resources.GetString("/VideoPage/publishedAt")}: {item.Snippet.PublishedAt} ({Methods.GetAgo(item.Snippet.PublishedAt.Value)})"
});
if (!string.IsNullOrWhiteSpace(item.Snippet.CategoryId))
{
VideoCategoriesResource.ListRequest request = SecretsVault.Service.VideoCategories.List("snippet,id");
request.Id = item.Snippet.CategoryId;
request.Hl = SettingsStorage.RelevanceLanguage;
VideoCategoryListResponse response = await request.ExecuteAsync();
meta.Inlines.Add(new Run
{
Text = $" {resources.GetString("/VideoPage/inCat")} "
});
Hyperlink hl = new Hyperlink();
hl.Inlines.Add(new Run
{
Text = response.Items[0].Snippet.Title
});
hl.Click += (s, e) => Navigation.GoToSearch(new SearchParameters(response.Items[0]));
meta.Inlines.Add(hl);
}
//Setting User's rate
if (SecretsVault.IsAuthorized)
{
VideoRating rating = (await SecretsVault.Service.Videos.GetRating(item.Id).ExecuteAsync()).Items[0];
if (rating.Rating == "like")
{
userRating = Rating.Like;
like.Foreground = new SolidColorBrush(Colors.Green);
}
else if (rating.Rating == "dislike")
{
userRating = Rating.Dislike;
dislike.Foreground = new SolidColorBrush(Colors.Red);
}
if(SecretsVault.Subscriptions.Exists(i => i.Snippet.ResourceId.ChannelId == item.Snippet.ChannelId))
{
subscribe.Background = new SolidColorBrush(Colors.Transparent);
subscribe.Foreground = new SolidColorBrush(Colors.Gray);
subscribe.Content = resources.GetString("/Cards/unsubscribe");
}
else if (item.Snippet.ChannelId == SecretsVault.AccountId)
subscribe.Visibility = Visibility.Collapsed;
/*try { addToSelector.Initialize(item); }
catch { addTo.Visibility = Visibility.Collapsed; }*/
}
else
{
download.Visibility = Visibility.Collapsed;
addTo.Visibility = Visibility.Collapsed;
subscribe.Visibility = Visibility.Collapsed;
}
}
void LoadStream()
{
liveTimer = new DispatcherTimer() { Interval = TimeSpan.FromMinutes(1) };
liveTimer.Tick += LiveStatsUpdate;
liveTimer.Start();
views.Text = $"{item.LiveStreamingDetails.ConcurrentViewers} {resources.GetString("/Cards/viewers")}";
if (string.IsNullOrWhiteSpace(item.LiveStreamingDetails.ActiveLiveChatId))
comments.Initialize(item);
else
{
commentsTab.Header = resources.GetString("/VideoPage/chat");
commentsTab.Content = new Chat(item.LiveStreamingDetails.ActiveLiveChatId);
pivot.SelectedItem = commentsTab;
}
download.Visibility = Visibility.Collapsed;
}
private async void LiveStatsUpdate(object sender = null, object e = null)
{
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("liveStreamingDetails");
request.Id = item.Id;
views.Text = $"{(await request.ExecuteAsync()).Items[0].LiveStreamingDetails.ConcurrentViewers} {resources.GetString("/Cards/viewers")}";
}
private void gotoChannel_Click(object sender, RoutedEventArgs e) =>
Navigation.GoToChannel(channelItem.Id);
private void openBrowser_Click(object sender, RoutedEventArgs e) => Methods.GuardFromNull("");
//Methods.Player.OpenBrowser();
void refresh_Click(object sender, RoutedEventArgs e) => Methods.GuardFromNull("");
//Methods.MainPage.VideoContent.Refresh();
private void grid_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Width > 1000 && mainContent.Children.Contains(pivot))
{
mainContent.Children.Remove(pivot);
tabsPlaceholder.Children.Add(pivot);
pivot.SelectedItem = descriptionPanel;
descriptionPanel.Opacity = 1;
pivot.Items.RemoveAt(0);
mainContent.Children.Add(descriptionPanel);
grid.ColumnDefinitions[1].Width = new GridLength(400);
pivot.SelectedIndex = 0;
}
else if (e.NewSize.Width <= 1000 && mainContent.Children.Contains(descriptionPanel))
{
mainContent.Children.Remove(descriptionPanel);
pivot.Items.Insert(0, descriptionPanel);
tabsPlaceholder.Children.Remove(pivot);
mainContent.Children.Add(pivot);
grid.ColumnDefinitions[1].Width = new GridLength(0);
pivot.SelectedIndex = 0;
}
}
private void Share(DataTransferManager sender, DataRequestedEventArgs args)
{
//Methods.Player.Pause();
Methods.Share(args,
item.Snippet.Thumbnails.Medium.Url,
item.Snippet.Title,
$"https://www.youtube.com/watch?v={item.Id}",
resources.GetString("/Cards/videoShare"));
}
private void share_Click(object sender, RoutedEventArgs e)
{
DataTransferManager.GetForCurrentView().DataRequested += new TypedEventHandler<DataTransferManager, DataRequestedEventArgs>(Share);
DataTransferManager.ShowShareUI();
}
private async void Report_Click(object sender, RoutedEventArgs e) =>
await new ReportVideo(item.Id).ShowAsync();
private async void dislike_Click(object sender, RoutedEventArgs e)
{
if (SecretsVault.IsAuthorized)
switch (userRating)
{
case Rating.Like:
like.Foreground = new SolidColorBrush(Colors.Gray);
likes.Text = (int.Parse(likes.Text, NumberStyles.AllowThousands) - 1).ToString("0,0");
dislike.Foreground = new SolidColorBrush(Colors.Red);
dislikes.Text = (int.Parse(dislikes.Text, NumberStyles.AllowThousands) + 1).ToString("0,0");
rating.Value--;
await SecretsVault.Service.Videos.Rate(item.Id, VideosResource.RateRequest.RatingEnum.Dislike).ExecuteAsync();
userRating = Rating.Dislike;
break;
case Rating.None:
dislike.Foreground = new SolidColorBrush(Colors.Red);
dislikes.Text = (int.Parse(dislikes.Text, NumberStyles.AllowThousands) + 1).ToString("0,0");
rating.Maximum++;
await SecretsVault.Service.Videos.Rate(item.Id, VideosResource.RateRequest.RatingEnum.Dislike).ExecuteAsync();
userRating = Rating.Dislike;
break;
case Rating.Dislike:
dislike.Foreground = new SolidColorBrush(Colors.Gray);
dislikes.Text = (int.Parse(dislikes.Text, NumberStyles.AllowThousands) - 1).ToString("0,0");
rating.Maximum--;
await SecretsVault.Service.Videos.Rate(item.Id, VideosResource.RateRequest.RatingEnum.None).ExecuteAsync();
break;
}
}
private async void like_Click(object sender, RoutedEventArgs e)
{
if(SecretsVault.IsAuthorized)
switch (userRating)
{
case Rating.Dislike:
dislike.Foreground = new SolidColorBrush(Colors.Gray);
dislikes.Text = (int.Parse(dislikes.Text, NumberStyles.AllowThousands) - 1).ToString("0,0");
like.Foreground = new SolidColorBrush(Colors.Green);
likes.Text = (int.Parse(likes.Text, NumberStyles.AllowThousands) + 1).ToString("0,0");
rating.Value++;
await SecretsVault.Service.Videos.Rate(item.Id, VideosResource.RateRequest.RatingEnum.Like).ExecuteAsync();
userRating = Rating.Like;
break;
case Rating.None:
like.Foreground = new SolidColorBrush(Colors.Green);
likes.Text = (int.Parse(likes.Text, NumberStyles.AllowThousands) + 1).ToString("0,0");
rating.Maximum++;
rating.Value++;
await SecretsVault.Service.Videos.Rate(item.Id, VideosResource.RateRequest.RatingEnum.Like).ExecuteAsync();
userRating = Rating.Like;
break;
case Rating.Like:
like.Foreground = new SolidColorBrush(Colors.Gray);
likes.Text = (int.Parse(likes.Text, NumberStyles.AllowThousands) - 1).ToString("0,0");
rating.Maximum--;
rating.Value--;
await SecretsVault.Service.Videos.Rate(item.Id, VideosResource.RateRequest.RatingEnum.None).ExecuteAsync();
break;
}
}
private async void subscribe_Click(object sender, RoutedEventArgs e)
{
if (await SecretsVault.ChangeSubscriptionState(item.Snippet.ChannelId))
{
subscribe.Background = new SolidColorBrush(Colors.Transparent);
subscribe.Foreground = new SolidColorBrush(Colors.Gray);
subscribe.Content = resources.GetString("/Cards/unsubscribe");
}
else
{
subscribe.Background = new SolidColorBrush(Colors.Red);
subscribe.Foreground = new SolidColorBrush(Colors.White);
subscribe.Content = resources.GetString("/Cards/subscribe/Content");
}
}
private void MainScroll_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e) => Methods.GuardFromNull("");
//Methods.Player.SetMargin(mainScroll.VerticalOffset);
private void Player_SizeChanged(object sender, SizeChangedEventArgs e)
{
//if(Methods.Player.State != PlayerDisplayState.Compact)
mainContent.Margin = new Thickness(mainContent.Margin.Left, e.NewSize.Height, mainContent.Margin.Right, mainContent.Margin.Bottom);
}
public void Maximize()
{
}
public void Minimize()
{
}
private void ResetControl()
{
mainScroll.ChangeView(0, 0, null);
upcoming.Visibility = Visibility.Collapsed;
schedule.Visibility = Visibility.Collapsed;
start.Visibility = Visibility.Collapsed;
end.Visibility = Visibility.Collapsed;
countdownPanel.Visibility = Visibility.Collapsed;
download.Visibility = Visibility.Visible;
addTo.Visibility = Visibility.Visible;
comments = new Comments();
}
private void Playlist_ItemChanged(string id)
{
ResetControl();
Initialize((id, null, incognito, playlistItem), true);
}
}
}