Archived
1
0

Optimization, refactoring, debugging

Related Work Items: #251, #252, #261
This commit is contained in:
Michael Gordeev
2019-02-17 20:55:19 +03:00
parent be2418409f
commit 6a12c7809d
72 changed files with 2995 additions and 2632 deletions
+32 -26
View File
@@ -12,7 +12,6 @@ using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel.Background;
using Windows.Globalization;
using Windows.Storage;
using Windows.System;
using Windows.System.Power;
using Windows.UI.Notifications;
using Windows.UI.Xaml;
@@ -63,7 +62,7 @@ namespace FoxTube
changelog.Load(await file.OpenStreamForReadAsync());
XmlElement e = changelog["items"].ChildNodes[0] as XmlElement;
ToastNotificationManager.CreateToastNotifier().Show(FoxTube.Background.Notification.GetChangelogToast(e.GetAttribute("version")));
ToastNotificationManager.CreateToastNotifier().Show(Background.Notification.GetChangelogToast(e.GetAttribute("version")));
SettingsStorage.Version = $"{ver.Major}.{ver.Minor}";
}
@@ -189,9 +188,6 @@ namespace FoxTube
Debug.WriteLine(e.Message);
}
break;
case "download":
await Launcher.LaunchFileAsync(await StorageFile.GetFileFromPathAsync(arguments[1]));
break;
}
}
@@ -217,32 +213,41 @@ namespace FoxTube
Window.Current.Activate();
if (e is ToastNotificationActivatedEventArgs)
switch (e.Kind)
{
string[] args = (e as ToastNotificationActivatedEventArgs).Argument.Split('|');
switch (args[0])
{
case "changelog":
case "inbox":
Methods.MainPage.GoToDeveloper(args[1]);
break;
case ActivationKind.Protocol:
break;
case ActivationKind.ToastNotification:
string[] args = (e as ToastNotificationActivatedEventArgs).Argument.Split('|');
switch (args[0])
{
case "changelog":
case "inbox":
Methods.MainPage.GoToDeveloper(args[1]);
break;
case "video":
Methods.MainPage.GoToVideo(args[1]);
break;
case "video":
Methods.MainPage.GoToVideo(args[1]);
break;
case "channel":
Methods.MainPage.GoToChannel(args[1]);
break;
case "download":
Methods.MainPage.GoToDownloads();
break;
case "dcancel":
DownloadAgent.Remove(args[1]);
break;
}
case "channel":
Methods.MainPage.GoToChannel(args[1]);
break;
case "download":
Methods.MainPage.GoToDownloads();
break;
case "dcancel":
DownloadAgent.Cancel(args[1]);
break;
}
break;
}
}
void Launch(string e = null)
{
}
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
@@ -255,6 +260,7 @@ namespace FoxTube
SettingsStorage.SaveData();
DownloadAgent.QuitPrompt();
deferral.Complete();
Analytics.TrackEvent("Session terminated");
}
private void UnhandledError(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
+28
View File
@@ -1,5 +1,33 @@
<?xml version="1.0" encoding="utf-8" ?>
<items>
<item time="2019-02-12" version="0.4">
<content>
<en-US>### What's new:
- Improved stability and speed of the app
- Fixed a lot of bugs
- Fixed player
- Added animations and acrylic
### Following features awaits their implementation:
- 'History' and 'Watch later' playlists (Progress has been made but it still incomplete)
- The same problem with 'Recommended' and 'Subscriptions' tabs on home page
- Playlists management will be introduced in the next version but it may miss some playlists
- Even though I've done ads delivering system it's not introduced because of some problems with ads' lookup. I just want to make your ads experience the best one. So it won't be implemented until I make sure that they are ready
</en-US>
<ru-RU>### Что нового:
- Улучшена стабильность и скорость приложения
- Исправлена куча багов
- Исправлен плеер
- Добавлены анимации и акрил
### Следующие функции ждут своего внедрения:
- История и плейлист 'Посмотреть позже' (прогресс есть, но нужно еще работать)
- Та же фигня и со вкладками 'Рекомендованные' и 'Подписки' на домашней странице
- Управление плейлистами будет добавлено в следующей версии, но некоторые плейлисты могут отсутствовать
- Хотя я сделал систему доставки рекламы, она не введена из-за некоторых проблем с видом банеров. Я хочу сделать ваш опыт взаимодействия с рекламой в приложении лучше, так что она не будет введена до тех пор, пока я не буду в этом уверен
</ru-RU>
</content>
</item>
<item time="2019-02-02" version="0.3">
<content>
<en-US>### What's new:
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

+14 -25
View File
@@ -1,19 +1,19 @@
using System;
using System.Collections.Generic;
using Windows.Storage;
using FoxTube.Classes;
using Newtonsoft.Json;
using YoutubeExplode.Models.MediaStreams;
using Google.Apis.YouTube.v3.Data;
using FoxTube.Controls;
using FoxTube.Pages;
namespace FoxTube
{
// TODO: Refactor DownloadAgent
public static class DownloadAgent
{
public static List<DownloadItem> items = new List<DownloadItem>();
private static ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
public static Downloads Page { get; set; }
public static StorageFolder Downloads { get; set; }
public static async void Initialize()
@@ -32,45 +32,34 @@ namespace FoxTube
items.Insert(0, new DownloadItem(info, meta, qualty));
}
public static void CancelItem(string id)
public static void Remove(DownloadItem item)
{
DownloadItem item = items.Find(x => x.Container.Id == id);
if (item == null || !item.InProgress)
return;
item.CancelPrompt();
try { Page.Remove(item); }
catch { }
items.Remove(item);
}
public static void Remove(string id)
public static void Cancel(string id)
{
DownloadItem item = items.Find(x => x.Container.Id == id);
if (item == null)
return;
if (item.InProgress)
DownloadItem item = items.Find(i => i.Container.Id == id);
if (item != null)
item.Cancel();
else
items.Remove(item);
}
public static void QuitPrompt()
{
foreach (DownloadItem i in items.FindAll(i => i.InProgress))
foreach (DownloadItem i in items.FindAll(i => !i.Container.IsDownloaded))
{
i.Cancel();
items.Remove(i);
}
List<DownloadItemContainer> containers = new List<DownloadItemContainer>();
items.ForEach(i => containers.Add(i.Container));
string data = JsonConvert.SerializeObject(containers);
try
{
settings.Values["downloads"] = data;
}
catch
{
settings.Values.Add("downloads", data);
}
settings.Values["downloads"] = data;
}
}
}
-16
View File
@@ -1,16 +0,0 @@
using System;
namespace FoxTube.Classes
{
public class DownloadItemContainer
{
public string Title { get; set; }
public string Channel { get; set; }
public string Id { get; set; }
public string Name { get; set; }
public string Extension { get; set; }
public Uri Thumbnail { get; set; }
public string Quality { get; set; }
public TimeSpan Duration { get; set; }
}
}
+10
View File
@@ -9,6 +9,16 @@ namespace FoxTube.Classes
{
public InboxItemType Type { get; set; } = InboxItemType.Default;
public DateTime TimeStamp { get; set; }
public string TimeStampString
{
get
{
if (Type == InboxItemType.PatchNote)
return TimeStamp.ToShortDateString();
else
return TimeStamp.ToString();
}
}
public string Subject { get; set; }
public string Content { get; set; }
+55 -96
View File
@@ -21,6 +21,7 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Documents;
using Windows.UI.Xaml.Media;
using YoutubeExplode;
using YoutubeExplode.Models.MediaStreams;
namespace FoxTube
@@ -45,15 +46,6 @@ namespace FoxTube
return new Uri(url);
}
public static void Try(Action action)
{
try
{
action();
}
catch { }
}
public static string GetChars(this string str, int count)
{
try
@@ -160,41 +152,25 @@ namespace FoxTube
public static void FormatText(ref TextBlock block, string text)
{
block.Inlines.Clear();
Regex filter = new Regex(@"\b((?:https?://|www\.)\S+)|(\S+@\S+)\b", RegexOptions.IgnoreCase);
Regex link = new Regex(@"\b(?:https?://|www\.)\S+\b", RegexOptions.IgnoreCase);
Regex mail = new Regex(@"\b\S+@\S+\b", RegexOptions.IgnoreCase);
Regex regx = new Regex(@"(http(s)?://[\S]+|www.[\S]+|[\S]+@[\S]+)", RegexOptions.IgnoreCase);
Regex isWWW = new Regex(@"(http[s]?://[\S]+|www.[\S]+)");
Regex isEmail = new Regex(@"[\S]+@[\S]+");
foreach (string item in regx.Split(text))
foreach (string item in filter.Split(text))
{
if (isWWW.IsMatch(item))
if (link.IsMatch(item))
{
try
{
Hyperlink link = new Hyperlink();
link.Click += (s, arg) => { ProcessLink(item); };
link.Inlines.Add(new Run { Text = item });
block.Inlines.Add(link);
}
catch
{
block.Inlines.Add(new Run { Text = item });
}
Hyperlink hl = new Hyperlink();
hl.Click += (s, arg) => ProcessLink(item);
hl.Inlines.Add(new Run { Text = item });
block.Inlines.Add(hl);
}
else if (isEmail.IsMatch(item))
else if (mail.IsMatch(item))
{
try
{
Hyperlink link = new Hyperlink { NavigateUri = new Uri($"mailto:{item}"), Foreground = new SolidColorBrush(Colors.Red) };
link.Inlines.Add(new Run { Text = item });
block.Inlines.Add(link);
}
catch
{
block.Inlines.Add(new Run { Text = item });
}
Hyperlink hl = new Hyperlink { NavigateUri = $"mailto:{item}".ToUri() };
hl.Inlines.Add(new Run { Text = item });
block.Inlines.Add(hl);
}
else if (item == "s")
continue;
else
block.Inlines.Add(new Run { Text = item });
}
@@ -230,68 +206,51 @@ namespace FoxTube
return "Unknown";
}
}
public async static void ProcessLink(string url)
{
try
{
Debug.WriteLine($"Processing link: {url}");
if (url.Contains("youtube.com/") || url.Contains("youtu.be/"))
{
Debug.WriteLine("This is an internal youtube link");
url = url.Replace("https://", "").Replace("http://", "").Replace("wwww.", "").Replace("//", "");
Debug.WriteLine($"Prepared link: {url}");
string output;
string type;
if (url.Contains("/playlist"))
{
Debug.WriteLine($"This is a playlist link. ID: {HttpUtility.ParseQueryString(url).Get("list")}");
MainPage.GoToPlaylist(HttpUtility.ParseQueryString(url).Get("list"));
}
else if (url.Contains("youtu.be/"))
{
Debug.WriteLine($"This is obfuscated video link. Video ID: {url.Split('/')[1]}");
MainPage.GoToVideo(url.Split('/')[1]);
}
else if (url.Contains("/watch"))
{
Debug.WriteLine($"This is regular video link. Video ID: {HttpUtility.ParseQueryString(url).Get("v")}");
MainPage.GoToVideo(HttpUtility.ParseQueryString(url).Get(0), HttpUtility.ParseQueryString(url).Get("list"));
}
else if (url.Contains("/v/"))
{
Debug.WriteLine($"This is video link. ID: {url.Split('/')[2].Split('?')[0]}");
MainPage.GoToVideo(url.Split('/')[2].Split('?')[0]);
}
else if (url.Contains("/channel/"))
{
Debug.WriteLine($"This is channel link. ID: {url.Split('/')[2]}");
MainPage.GoToChannel(url.Split('/')[2]);
}
else if (url.Contains("/user/"))
{
Debug.WriteLine($"This is channel link with username. Username: {url.Split('/')[2]}");
ChannelsResource.ListRequest request = SecretsVault.Service.Channels.List("id");
Debug.WriteLine(request.ForUsername = url.Split('/')[2]);
request.MaxResults = 1;
MainPage.GoToChannel((await request.ExecuteAsync()).Items[0].Id);
}
else if (url.Contains("/c/"))
{
Debug.WriteLine($"This is channel link with custom url. Custom name: {url.Split('/')[2]}");
SearchResource.ListRequest request = SecretsVault.Service.Search.List("id");
Debug.WriteLine(request.Q = url.Split('/')[2]);
request.MaxResults = 1;
MainPage.GoToChannel((await request.ExecuteAsync()).Items[0].Id.ChannelId);
}
else
throw new Exception();
}
else
throw new Exception();
}
catch
if (YoutubeClient.TryParseChannelId(url, out output))
{
await Launcher.LaunchUriAsync(new Uri(url));
type = "channel";
goto LinkFound;
}
else if (YoutubeClient.TryParsePlaylistId(url, out output))
{
type = "playlist";
goto LinkFound;
}
else if (YoutubeClient.TryParseUsername(url, out output))
{
type = "user";
goto LinkFound;
}
else if (YoutubeClient.TryParseVideoId(url, out output))
{
type = "video";
goto LinkFound;
}
await Launcher.LaunchUriAsync(new Uri(url));
return;
LinkFound:
switch (type)
{
case "channel":
MainPage.GoToChannel(output);
break;
case "video":
MainPage.GoToVideo(output);
break;
case "playlist":
MainPage.GoToPlaylist(output);
break;
case "user":
MainPage.GoToChannel(await new YoutubeClient().GetChannelIdAsync(output));
break;
}
}
+6 -14
View File
@@ -174,7 +174,7 @@ namespace FoxTube
//Saving user's subscriptions for background task
SaveSubscriptions();
InvokeEvent:
InvokeEvent:
AuthorizationStateChanged?.Invoke(args: IsAuthorized);
}
catch
@@ -186,13 +186,11 @@ namespace FoxTube
/// <summary>
/// Saves user's subscriptions keypairs (channel ID: avatar URL) into "background.json" file for concurrent background processing
/// </summary>
public static async void SaveSubscriptions()
public static void SaveSubscriptions()
{
Dictionary<string, string> subs = new Dictionary<string, string>();
Subscriptions.ForEach(x => subs.Add(x.Snippet.ResourceId.ChannelId, x.Snippet.Thumbnails.Medium.Url));
await FileIO.WriteTextAsync(
await ApplicationData.Current.RoamingFolder.CreateFileAsync("background.json", CreationCollisionOption.ReplaceExisting),
JsonConvert.SerializeObject(subs));
ApplicationData.Current.RoamingSettings.Values["subscriptions"] = JsonConvert.SerializeObject(subs);
}
/// <summary>
@@ -206,10 +204,7 @@ namespace FoxTube
AuthorizationStateChanged?.Invoke(args: false);
SettingsStorage.HasAccount = false;
await FileIO.WriteTextAsync(
await ApplicationData.Current.RoamingFolder.CreateFileAsync(
"background.json", CreationCollisionOption.ReplaceExisting)
, "");
ApplicationData.Current.RoamingSettings.Values["subscriptions"] = "";
}
}
@@ -217,17 +212,14 @@ namespace FoxTube
/// 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 async void CheckAuthorization(bool retrieveSubs = true)
public static void CheckAuthorization(bool retrieveSubs = true)
{
if (SettingsStorage.HasAccount)
Authorize(retrieveSubs);
else
{
AuthorizationStateChanged.Invoke(args: false);
await FileIO.WriteTextAsync(
await ApplicationData.Current.RoamingFolder.CreateFileAsync(
"background.json", CreationCollisionOption.ReplaceExisting),
"");
ApplicationData.Current.RoamingSettings.Values["subscriptions"] = "";
}
}
+1 -14
View File
@@ -7,7 +7,7 @@ using Windows.Storage;
namespace FoxTube
{
public enum MatureState { Blocked, Allowed, AllowedOnce }
public enum MatureState { Blocked, Allowed }
public class SettingsContainer
{
@@ -207,19 +207,6 @@ namespace FoxTube
public static void SaveData()
{
storage.Values["settings"] = JsonConvert.SerializeObject(Container);
ExportSettings();
}
public static async void ExportSettings()
{
try
{
bool[] notificationsSettings = new[] { VideoNotifications, DevNotifications };
await FileIO.WriteTextAsync(
await ApplicationData.Current.RoamingFolder.CreateFileAsync("notifications.json", CreationCollisionOption.ReplaceExisting),
JsonConvert.SerializeObject(notificationsSettings));
}
catch { }
}
}
}
+22 -22
View File
@@ -7,38 +7,38 @@
mc:Ignorable="d"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
SizeChanged="UserControl_SizeChanged"
d:DesignHeight="290"
d:DesignWidth="384">
d:DesignWidth="384"
Visibility="Collapsed">
<Button Padding="0" Background="Transparent" Name="btn">
<Grid Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<Button Padding="0" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="75"/>
<RowDefinition/>
<RowDefinition Height="20"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions>
<Image Source="/Assets/videoPlaceholder.png" Opacity=".0001" Stretch="Fill"/>
<Image Name="image" Source="/Assets/videoThumbSample.png" Stretch="Fill"/>
<StackPanel Margin="0,0,5,5" Background="Orange" VerticalAlignment="Bottom" BorderBrush="OrangeRed" BorderThickness="1" HorizontalAlignment="Right" Padding="5,2,5,3">
<TextBlock Name="info" Text="SPONSORED CONTENT" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Gray" FontSize="12"/>
</StackPanel>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions>
<Grid Name="contentGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Height="50" Width="50" Margin="5,-30,5,10" Fill="{ThemeResource SystemControlBackgroundChromeMediumBrush}"/>
<PersonPicture Name="icon" Grid.Column="0" Height="46" Margin="5,-30,5,0" BorderBrush="White" BorderThickness="10"/>
<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="1" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" Margin="5" MaxLines="2" TextTrimming="CharacterEllipsis"/>
<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>
</Button>
</UserControl>
+2 -9
View File
@@ -31,10 +31,8 @@ namespace FoxTube.Controls.Adverts
{
title.Text = advert.Title;
image.Source = new BitmapImage(advert.MainImages.First().Url.ToUri());
if (advert.AdIcon == null)
contentGrid.ColumnDefinitions[0].Width = new GridLength(0);
else
icon.ProfilePicture = advert.AdIcon.Source;
icon.ProfilePicture = advert.AdIcon.Source;
if (string.IsNullOrWhiteSpace(advert.SponsoredBy))
sponsor.Visibility = Visibility.Collapsed;
@@ -53,10 +51,5 @@ namespace FoxTube.Controls.Adverts
Visibility = Visibility.Visible;
}
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
Height = e.NewSize.Width * 0.75;
}
}
}
+45 -31
View File
@@ -4,51 +4,65 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
mc:Ignorable="d"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
SizeChanged="UserControl_SizeChanged"
d:DesignHeight="290"
d:DesignWidth="384">
d:DesignWidth="384"
MaxWidth="500">
<Button Padding="0" Background="Transparent" Click="Button_Click" VerticalContentAlignment="Stretch" VerticalAlignment="Stretch">
<Grid Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<Windows10version1809:UserControl.OpacityTransition>
<ScalarTransition Duration="0:0:0.5"/>
</Windows10version1809:UserControl.OpacityTransition>
<Button Padding="0" Margin="1" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" Click="Button_Click">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="50"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Image Name="cover" Source="/Assets/ChannelCoverTemplate.png" Stretch="UniformToFill" VerticalAlignment="Center"/>
<Image Source="/Assets/ChannelCoverTemplate.png" Stretch="UniformToFill" Opacity=".0001"/>
<Image Name="cover" Source="/Assets/ChannelCoverTemplate.png" Stretch="UniformToFill" ImageOpened="Cover_ImageOpened" Opacity="0">
<Windows10version1809:Image.OpacityTransition>
<ScalarTransition Duration="0:0:0.5"/>
</Windows10version1809:Image.OpacityTransition>
</Image>
<StackPanel Name="liveTag" Margin="5" Background="Red" BorderBrush="White" BorderThickness="1" VerticalAlignment="Bottom" HorizontalAlignment="Right" Padding="5,2,5,3" Orientation="Horizontal" Visibility="Collapsed">
<TextBlock Text="&#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.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Ellipse Height="80" Width="80" Margin="0,-45,0,0" VerticalAlignment="Bottom" Fill="{ThemeResource SystemControlBackgroundChromeMediumBrush}"/>
<PersonPicture Name="avatar" Grid.Column="0" Height="74" Margin="3,-45,3,3" VerticalAlignment="Bottom" BorderBrush="White" BorderThickness="10"/>
<TextBlock Name="title" Grid.Column="1" Text="[Channel name]" Margin="5" TextWrapping="WrapWholeWords" MaxLines="2" TextTrimming="CharacterEllipsis"/>
<StackPanel Grid.Column="2" Margin="5">
<TextBlock Name="subs" Text="[Subscribers counter]" Foreground="Gray"/>
<TextBlock Name="uploads" Text="[Uploads counter]" Foreground="Gray"/>
</StackPanel>
</Grid>
<TextBlock Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Height="50" Margin="10" TextAlignment="Center" Padding="0,16,0,0" Foreground="Gray">
<Hyperlink Click="Hyperlink_Click"><Run x:Uid="/Cards/login">Log in</Run></Hyperlink> <Run x:Uid="/Cards/tomanage">to manage your subscriptions</Run>
</TextBlock>
<Grid Visibility="Collapsed" Grid.Row="1" VerticalAlignment="Bottom" Margin="10" Name="subscriptionPane" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<Button x:Uid="/Cards/subscribe" Click="subscribe_Click" Name="subscribe" HorizontalAlignment="Stretch" Height="50" Background="Red" Foreground="White" FontSize="18" FontWeight="SemiBold" Content="Subscirbe" Margin="0,0,0,0"/>
<ToggleButton Name="notify" Height="50" Width="50" Visibility="Collapsed" FontFamily="Segoe MDL2 Assets" FontSize="18" FontWeight="SemiBold" Content="&#xE7ED;" Foreground="White" Background="Red" HorizontalAlignment="Right"/>
</Grid>
<Grid Grid.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" Margin="10" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin molestie vulputate leo, sed faucibus ex rutrum nec. Donec quis diam nisi. Suspendisse sollicitudin sapien quis eros vulputate, sed scelerisque enim ullamcorper. Donec vulputate commodo mi, vel vestibulum quam posuere ac. Curabitur ac nunc augue. Phasellus aliquam neque ac condimentum bibendum." TextWrapping="WrapWholeWords" TextTrimming="CharacterEllipsis"/>
<TextBlock Grid.Row="3" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Height="50" Margin="10" TextAlignment="Center" Padding="0,16,0,0" Foreground="Gray">
<Hyperlink Click="Hyperlink_Click"><Run x:Uid="/Cards/login">Log in</Run></Hyperlink> <Run x:Uid="/Cards/tomanage">to manage your subscriptions</Run>
</TextBlock>
<Grid Visibility="Collapsed" Grid.Row="3" VerticalAlignment="Stretch" Margin="10" Name="subscriptionPane" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<Button x:Uid="/Cards/subscribe" VerticalAlignment="Stretch" Click="subscribe_Click" Name="subscribe" HorizontalAlignment="Stretch" Height="50" Background="Red" Foreground="White" FontSize="18" FontWeight="SemiBold" Content="Subscirbe" Margin="0,0,0,0"/>
<ToggleButton Name="notify" Height="50" Width="50" Visibility="Collapsed" FontFamily="Segoe MDL2 Assets" FontSize="18" FontWeight="SemiBold" Content="&#xE7ED;" Foreground="White" Background="Red" HorizontalAlignment="Right"/>
</Grid>
</Grid>
</Button>
+19 -20
View File
@@ -15,24 +15,19 @@ namespace FoxTube.Controls
/// <summary>
/// Channel item card
/// </summary>
public sealed partial class ChannelCard : UserControl, IItemCard
public sealed partial class ChannelCard : UserControl
{
ResourceLoader resources = ResourceLoader.GetForCurrentView("Cards");
string channelId;
Channel item;
public ChannelCard(string id, string live = "null")
public ChannelCard(string id, string live = null)
{
InitializeComponent();
Initialize(id, live);
}
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
Height = e.NewSize.Width * 0.75;
}
public async void Initialize(string id, string live)
{
ChannelsResource.ListRequest request = SecretsVault.Service.Channels.List("snippet,statistics,brandingSettings");
@@ -43,6 +38,7 @@ namespace FoxTube.Controls
channelId = id;
title.Text = item.Snippet.Title;
description.Text = item.Snippet.Description;
subs.Text = $"{item.Statistics.SubscriberCount:0,0} {resources.GetString("/Cards/subscribers")}";
uploads.Text = $"{item.Statistics.VideoCount:0,0} {resources.GetString("/Cards/videos")}";
@@ -52,14 +48,11 @@ namespace FoxTube.Controls
if(SecretsVault.IsAuthorized)
{
foreach(Subscription s in SecretsVault.Subscriptions)
if(SecretsVault.Subscriptions.Exists(i => i.Snippet.ResourceId.ChannelId == id))
{
if(s.Snippet.ResourceId.ChannelId == id)
{
subscribe.Background = new SolidColorBrush(Colors.Transparent);
subscribe.Foreground = new SolidColorBrush(Colors.Gray);
subscribe.Content = resources.GetString("/Cards/unsubscribe");
}
subscribe.Background = new SolidColorBrush(Colors.Transparent);
subscribe.Foreground = new SolidColorBrush(Colors.Gray);
subscribe.Content = resources.GetString("/Cards/unsubscribe");
}
subscriptionPane.Visibility = Visibility.Visible;
}
@@ -68,14 +61,15 @@ namespace FoxTube.Controls
catch { }
try
{
if (item.BrandingSettings.Image.BannerImageUrl.Contains("default"))
throw new Exception("Default channel cover detected");
cover.Source = new BitmapImage((item.BrandingSettings.Image.BannerTvHighImageUrl ?? item.BrandingSettings.Image.BannerTvImageUrl).ToUri());
if (!item.BrandingSettings.Image.BannerImageUrl.Contains("default"))
cover.Source = new BitmapImage(item.BrandingSettings.Image.BannerImageUrl.ToUri());
}
catch { }
Opacity = 1;
}
private void Button_Click(object sender, RoutedEventArgs e)
public void Button_Click(object sender, RoutedEventArgs e)
{
Methods.MainPage.GoToChannel(channelId);
}
@@ -104,13 +98,18 @@ namespace FoxTube.Controls
private void GetLink_Click(object sender, RoutedEventArgs e)
{
DataPackage data = new DataPackage();
data.SetText(string.IsNullOrWhiteSpace(item.Snippet.CustomUrl) ? $"https://www.youtube.com/channel/{item.Id}" : $"https://www.youtube.com/user/{item.Snippet.CustomUrl}");
data.SetText($"https://www.youtube.com/channel/{item.Id}");
Clipboard.SetContent(data);
}
private async void InBrowser_Click(object sender, RoutedEventArgs e)
{
await Launcher.LaunchUriAsync((string.IsNullOrWhiteSpace(item.Snippet.CustomUrl) ? $"https://www.youtube.com/channel/{item.Id}" : $"https://www.youtube.com/user/{item.Snippet.CustomUrl}").ToUri());
await Launcher.LaunchUriAsync($"https://www.youtube.com/channel/{item.Id}".ToUri());
}
private void Cover_ImageOpened(object sender, RoutedEventArgs e)
{
cover.Opacity = 1;
}
}
}
+46 -48
View File
@@ -24,53 +24,51 @@
Content="&#xE122;" FontSize="30"/>
</Grid>
<ScrollViewer Grid.Row="1">
<ListView Name="list" SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<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" ProfilePicture="{Binding Path=Avatar}"/>
<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 Content="{Binding Path=Author}" Tag="{Binding Path=ChannelId}" Grid.Column="1" Margin="0,-6,0,0" FontWeight="Bold" Click="HyperlinkButton_Click"/>
<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>
</ScrollViewer>
<ListView Name="list" Grid.Row="1" SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Red" BorderThickness="{Binding Path=BorderThickness}" CornerRadius="5" HorizontalAlignment="Stretch" Background="{Binding Path=Background}" Margin="0,2">
<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" ProfilePicture="{Binding Path=Avatar}"/>
<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 Content="{Binding Path=Author}" Tag="{Binding Path=ChannelId}" Grid.Column="1" Margin="0,-6,0,0" FontWeight="Bold" Click="HyperlinkButton_Click"/>
<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>
+29 -13
View File
@@ -5,6 +5,10 @@ using Windows.UI.Xaml.Media;
using Google.Apis.YouTube.v3.Data;
using Google.Apis.YouTube.v3;
using Windows.UI;
using Microsoft.AppCenter.Analytics;
using System.Collections.Generic;
using Windows.UI.Popups;
using Windows.ApplicationModel.Resources;
namespace FoxTube.Controls
{
@@ -66,6 +70,10 @@ namespace FoxTube.Controls
{
string chatId;
DateTime lastInsert;
LiveChatMessagesResource.ListRequest request;
LiveChatMessageListResponse response;
DispatcherTimer timer = new DispatcherTimer()
{
Interval = TimeSpan.FromSeconds(1)
@@ -76,19 +84,20 @@ namespace FoxTube.Controls
InitializeComponent();
if (!SecretsVault.IsAuthorized)
inputField.Visibility = Visibility.Collapsed;
chatId = id;
request = SecretsVault.Service.LiveChatMessages.List(chatId, "snippet,authorDetails");
timer.Tick += Update;
timer.Start();
}
public async void Update(object sender, object e)
{
LiveChatMessagesResource.ListRequest request = SecretsVault.Service.LiveChatMessages.List(chatId, "snippet,authorDetails");
LiveChatMessageListResponse response = await request.ExecuteAsync();
foreach (LiveChatMessage i in response.Items)
if(i.Snippet.PublishedAt >= lastInsert)
list.Items.Insert(0, new ChatMessage(i));
response = await request.ExecuteAsync();
foreach (LiveChatMessage i in response.Items.FindAll(i => i.Snippet.PublishedAt >= lastInsert))
list.Items.Insert(0, new ChatMessage(i));
lastInsert = DateTime.Now;
timer.Interval = TimeSpan.FromMilliseconds(response.PollingIntervalMillis.Value);
timer.Start();
@@ -99,16 +108,16 @@ namespace FoxTube.Controls
Methods.MainPage.GoToChannel(((HyperlinkButton)sender).Tag as string);
}
private async void send_Click(object sender, RoutedEventArgs e)
private async void send_Click(object sender, RoutedEventArgs args)
{
try
{
newMessage.IsEnabled = false;
send.IsEnabled = false;
if (string.IsNullOrWhiteSpace(newMessage.Text))
return;
newMessage.IsEnabled = false;
send.IsEnabled = false;
LiveChatMessage message = new LiveChatMessage()
{
Snippet = new LiveChatMessageSnippet()
@@ -131,11 +140,18 @@ namespace FoxTube.Controls
list.Items.Add(new ChatMessage(response));
}
}
finally
catch(Exception e)
{
newMessage.IsEnabled = true;
send.IsEnabled = true;
await new MessageDialog(ResourceLoader.GetForCurrentView("Chat").GetString("/Chat/failed")).ShowAsync();
Analytics.TrackEvent("Failed to send a chat message", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message }
});
}
newMessage.IsEnabled = true;
send.IsEnabled = true;
}
}
}
+66 -78
View File
@@ -6,96 +6,84 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignWidth="400">
<Grid Background="{ThemeResource AppBarBackgroundThemeBrush}" Margin="2" Name="grid">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="0"/>
<RowDefinition Height="0"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<PersonPicture Tapped="avatar_Tapped" Name="avatar" Height="50" Margin="5" VerticalAlignment="Top"/>
<Grid Grid.Column="1" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock Name="author" TextWrapping="WrapWholeWords" Foreground="Gray" FontSize="13" Text="[Author's name]"/>
<Border Name="authorBorder" HorizontalAlignment="Left" Background="Red" CornerRadius="5" Visibility="Collapsed">
<TextBlock Name="specialAuthor" TextWrapping="WrapWholeWords" Foreground="White" FontSize="13" Text="[Author's name]" Padding="2,0,2,2"/>
</Border>
<TextBlock Name="meta" Grid.Row="1" TextWrapping="WrapWholeWords" Foreground="Gray" FontSize="13" Text="[Published time span] [?](edited)"/>
<TextBlock Name="text" IsTextSelectionEnabled="True" TextWrapping="WrapWholeWords" Grid.Row="2" Text="[Content]"/>
<StackPanel Grid.Row="2" Name="editor" Visibility="Collapsed">
<TextBox Name="editorText" Text="[Content]" AcceptsReturn="True" TextWrapping="Wrap" TextChanged="editorText_TextChanged"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,5,0,5">
<Button x:Uid="/CommentsPage/editorDelete" Content="Delete comment" Background="Red" Name="deleteComment" Click="DeleteComment_Click"/>
<Button x:Uid="/CommentsPage/editorCancel" Name="editorClose" Content="Cancel" Click="editorClose_Click" Margin="5, 0"/>
<Button x:Uid="/CommentsPage/editorSubmit" Name="editorSend" Content="Submit" Click="editorSend_Click"/>
<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="0" Margin="10,0,0,0"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
Height="35">
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xEC42;" Margin="0,0,5,0" FontSize="20"/>
<TextBlock Text="123"/>
</StackPanel>
<ProgressBar Name="editorSending" Foreground="Red" IsIndeterminate="True" Visibility="Collapsed"/>
</StackPanel>
</Button>
<StackPanel Grid.Row="3" Orientation="Horizontal">
<TextBlock Name="upvote" Foreground="Gray" Padding="0"
VerticalAlignment="Center" Margin="0,0,5,0"
FontFamily="Segoe MDL2 Assets" Text="&#xE19F;" FontSize="20"/>
<TextBlock Name="rating" Foreground="Gray" VerticalAlignment="Center" Text="123"/>
<Button Visibility="Collapsed" Name="downvote" Background="Transparent" Foreground="Gray" Padding="0"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
Height="35" Width="35"
FontFamily="Segoe MDL2 Assets" Content="&#xE19E;" FontSize="20"/>
<Button Click="showReplies_Click" Name="showReplies" Background="Transparent" Foreground="Gray" Padding="0" Margin="10,0,0,0"
<Button Click="replyBtn_Click" Name="replyBtn" Background="Transparent" Foreground="Gray" Padding="0" Margin="10,0,0,0"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
Height="35">
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xEC42;" Margin="0,0,5,0" FontSize="20"/>
<TextBlock Text="123"/>
</StackPanel>
</Button>
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xEE35;" FontSize="20"/>
<TextBlock x:Uid="/CommentsPage/reply" Text="Reply"/>
</StackPanel>
</Button>
<Button Click="replyBtn_Click" Name="replyBtn" Background="Transparent" Foreground="Gray" Padding="0" Margin="10,0,0,0"
<Button Click="editBtn_Click" Visibility="Collapsed" Name="editBtn" Background="Transparent" Foreground="Gray" Padding="0" Margin="10,0,0,0"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
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="0" Margin="10,0,0,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>
</StackPanel>
<ProgressBar Grid.Row="3" VerticalAlignment="Bottom" IsIndeterminate="True" Visibility="Collapsed" Name="commentsLoading"/>
</Grid>
</Grid>
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xE104;" FontSize="20"/>
<TextBlock x:Uid="/CommentsPage/edit" Text="Edit"/>
</StackPanel>
</Button>
</StackPanel>
<TextBox x:Uid="/CommentsPage/replyBox" Grid.Row="1" Name="reply" TextChanged="reply_TextChanged" TextWrapping="Wrap" BorderThickness="0" Background="{ThemeResource SystemControlBackgroundChromeMediumLowBrush}" AcceptsReturn="True" MaxLength="500"
Padding="5" Margin="0,0,32,0"
<Grid Name="replyEditor" Visibility="Collapsed">
<TextBox x:Uid="/CommentsPage/replyBox" Name="reply" TextChanged="reply_TextChanged" TextWrapping="Wrap" AcceptsReturn="True" MaxLength="500"
Margin="2,0,34,0"
PlaceholderText="Enter your reply..."/>
<Button Grid.Row="1" Name="send" Click="send_Click" IsEnabled="False" HorizontalAlignment="Right" VerticalAlignment="Top"
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
<Button Name="send" Click="send_Click" IsEnabled="True" HorizontalAlignment="Right" VerticalAlignment="Top"
Width="32" Height="32" Padding="0"
Background="Red" Foreground="White"
Background="Transparent"
FontFamily="Segoe MDL2 Assets"
Content="&#xE122;" />
<ProgressBar Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" IsIndeterminate="True" Foreground="Red" Name="sending" Visibility="Collapsed"/>
FontSize="30"
Content="&#xE122;"/>
</Grid>
<ProgressBar Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" IsIndeterminate="True" Name="processing" Visibility="Collapsed"/>
<StackPanel Grid.Row="2" Margin="60,0,0,0" Name="replies"/>
<StackPanel Name="replies" Visibility="Collapsed">
<StackPanel.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition IsStaggeringEnabled="True"/>
</TransitionCollection>
</StackPanel.ChildrenTransitions>
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
+124 -92
View File
@@ -4,12 +4,12 @@ using System.Linq;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Popups;
using Windows.ApplicationModel.Resources;
using Microsoft.AppCenter.Analytics;
namespace FoxTube.Controls
{
@@ -21,10 +21,11 @@ namespace FoxTube.Controls
{
ResourceLoader resources = ResourceLoader.GetForCurrentView("CommentsPage");
Comment item;
CommentThread thread;
public Comment item;
public CommentThread thread;
CommentType type;
bool repliesLoaded = false;
CommentType type = CommentType.TopLevel;
public CommentCard(CommentThread comment)
{
@@ -37,15 +38,15 @@ namespace FoxTube.Controls
item = comment.Snippet.TopLevelComment;
thread = comment;
replyBtn.Visibility = !comment.Snippet.CanReply.Value || !SecretsVault.IsAuthorized ? Visibility.Collapsed : Visibility.Visible;
replyBtn.Visibility = comment.Snippet.CanReply.Value && SecretsVault.IsAuthorized ? Visibility.Visible : Visibility.Collapsed;
if (!comment.Snippet.TotalReplyCount.HasValue || comment.Snippet.TotalReplyCount.Value == 0)
showReplies.Visibility = Visibility.Collapsed;
else
((showReplies.Content as StackPanel).Children[1] as TextBlock).Text = comment.Snippet.TotalReplyCount.ToString();
if (comment.Snippet.TopLevelComment.Snippet.CanRate == false)
{
upvote.Visibility = Visibility.Collapsed;
downvote.Visibility = Visibility.Collapsed;
rating.Visibility = Visibility.Collapsed;
}
else
@@ -53,15 +54,15 @@ namespace FoxTube.Controls
if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == SecretsVault.AccountId)
{
specialAuthor.Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
authorBorder.Visibility = Visibility.Visible;
(specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
specialAuthor.Visibility = Visibility.Visible;
author.Visibility = Visibility.Collapsed;
editBtn.Visibility = Visibility.Visible;
}
else if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == Methods.MainPage.GetCurrentItem().Snippet.ChannelId)
else if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == item.Snippet.ChannelId)
{
specialAuthor.Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
authorBorder.Visibility = Visibility.Visible;
(specialAuthor.Child as TextBlock).Text = comment.Snippet.TopLevelComment.Snippet.AuthorDisplayName;
specialAuthor.Visibility = Visibility.Visible;
author.Visibility = Visibility.Collapsed;
}
else
@@ -73,10 +74,21 @@ namespace FoxTube.Controls
try { avatar.ProfilePicture = new BitmapImage(new Uri(comment.Snippet.TopLevelComment.Snippet.AuthorProfileImageUrl)); }
catch { }
}
public void DeleteComment(CommentCard comment)
{
replies.Children.Remove(comment);
((showReplies.Content as StackPanel).Children[1] as TextBlock).Text = (--thread.Snippet.TotalReplyCount).ToString();
if(thread.Snippet.TotalReplyCount == 0)
showReplies.Visibility = Visibility.Collapsed;
}
public CommentCard(Comment comment)
{
this.InitializeComponent();
InitializeComponent();
Initialize(comment);
}
@@ -87,7 +99,6 @@ namespace FoxTube.Controls
replyBtn.Visibility = Visibility.Collapsed;
showReplies.Visibility = Visibility.Collapsed;
downvote.Visibility = Visibility.Collapsed;
if (comment.Snippet.CanRate == false)
{
@@ -99,15 +110,15 @@ namespace FoxTube.Controls
if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == SecretsVault.AccountId)
{
specialAuthor.Text = comment.Snippet.AuthorDisplayName;
authorBorder.Visibility = Visibility.Visible;
(specialAuthor.Child as TextBlock).Text = comment.Snippet.AuthorDisplayName;
specialAuthor.Visibility = Visibility.Visible;
author.Visibility = Visibility.Collapsed;
editBtn.Visibility = Visibility.Visible;
}
else if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == Methods.MainPage.GetCurrentItem().Snippet.ChannelId)
else if (item.Snippet.AuthorChannelId.ToString().Split('"')[3] == item.Snippet.ChannelId)
{
specialAuthor.Text = comment.Snippet.AuthorDisplayName;
authorBorder.Visibility = Visibility.Visible;
(specialAuthor.Child as TextBlock).Text = comment.Snippet.AuthorDisplayName;
specialAuthor.Visibility = Visibility.Visible;
author.Visibility = Visibility.Collapsed;
}
else
@@ -122,53 +133,36 @@ namespace FoxTube.Controls
private void replyBtn_Click(object sender, RoutedEventArgs e)
{
if (grid.RowDefinitions[1].Height == new GridLength(0))
grid.RowDefinitions[1].Height = GridLength.Auto;
else
grid.RowDefinitions[1].Height = new GridLength(0);
replyEditor.Visibility = replyEditor.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
}
private async void showReplies_Click(object sender, RoutedEventArgs e)
{
if (grid.RowDefinitions[2].Height == new GridLength(0))
replies.Visibility = replies.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
if (repliesLoaded)
return;
processing.Visibility = Visibility.Visible;
CommentsResource.ListRequest request = SecretsVault.Service.Comments.List("snippet");
request.ParentId = item.Id;
request.TextFormat = CommentsResource.ListRequest.TextFormatEnum.PlainText;
request.MaxResults = 50;
CommentListResponse response;
do
{
if(type == CommentType.TopLevel && !repliesLoaded)
{
commentsLoading.Visibility = Visibility.Visible;
var request = SecretsVault.Service.Comments.List("snippet");
request.ParentId = item.Id;
request.TextFormat = CommentsResource.ListRequest.TextFormatEnum.PlainText;
request.MaxResults = 50;
response = await request.ExecuteAsync();
request.PageToken = response.NextPageToken;
string token;
IList<Comment> list = new List<Comment>();
var response = await request.ExecuteAsync();
token = response.NextPageToken;
foreach (Comment i in response.Items)
list.Add(i);
while(token != null)
{
request.PageToken = token;
response = await request.ExecuteAsync();
token = response.NextPageToken;
foreach (Comment i in response.Items)
list.Add(i);
}
foreach (Comment c in list.Reverse())
replies.Children.Add(new CommentCard(c));
repliesLoaded = true;
commentsLoading.Visibility = Visibility.Collapsed;
}
grid.RowDefinitions[2].Height = GridLength.Auto;
response.Items.ForEach(i => replies.Children.Add(new CommentCard(i)));
}
else
grid.RowDefinitions[2].Height = new GridLength(0);
while (!string.IsNullOrWhiteSpace(request.PageToken));
repliesLoaded = true;
processing.Visibility = Visibility.Collapsed;
}
private void avatar_Tapped(object sender, TappedRoutedEventArgs e)
@@ -178,99 +172,126 @@ namespace FoxTube.Controls
private void reply_TextChanged(object sender, TextChangedEventArgs e)
{
if (reply.Text.Length == 0)
send.IsEnabled = false;
else
send.IsEnabled = true;
send.IsEnabled = reply.Text.Length == 0 ? false : true;
}
private async void send_Click(object sender, RoutedEventArgs e)
private async void send_Click(object sender, RoutedEventArgs args)
{
if (string.IsNullOrWhiteSpace(reply.Text))
return;
send.IsEnabled = false;
reply.IsEnabled = false;
sending.Visibility = Visibility.Visible;
processing.Visibility = Visibility.Visible;
Comment comment = new Comment();
comment.Snippet = new CommentSnippet();
comment.Snippet.TextOriginal = reply.Text;
comment.Snippet.ParentId = item.Id;
Comment comment = new Comment()
{
Snippet = new CommentSnippet()
{
TextOriginal = reply.Text,
ParentId = item.Id
}
};
try
{
Comment response = await SecretsVault.Service.Comments.Insert(comment, "snippet").ExecuteAsync();
reply.Text = "";
grid.RowDefinitions[1].Height = new GridLength(0);
replies.Children.Add(new CommentCard(response));
replyEditor.Visibility = Visibility.Collapsed;
if (repliesLoaded)
replies.Children.Insert(0, new CommentCard(response));
else
showReplies_Click(this, null);
showReplies.Visibility = Visibility.Visible;
((showReplies.Content as StackPanel).Children[1] as TextBlock).Text = (++thread.Snippet.TotalReplyCount).ToString();
}
catch
catch (Exception e)
{
await new MessageDialog(resources.GetString("/CommentsPage/failedReply")).ShowAsync();
Analytics.TrackEvent("Failed to send comment reply", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Top comment ID", item.Id }
});
}
send.IsEnabled = true;
reply.IsEnabled = true;
sending.Visibility = Visibility.Collapsed;
processing.Visibility = Visibility.Collapsed;
}
private void editorClose_Click(object sender, RoutedEventArgs e)
{
((grid.Children[0] as Grid).Children[1] as Grid).RowDefinitions[3].Height = GridLength.Auto;
text.Visibility = Visibility.Visible;
editor.Visibility = Visibility.Collapsed;
text.Visibility = Visibility.Visible;
toolbar.Visibility = Visibility.Visible;
}
private async void editorSend_Click(object sender, RoutedEventArgs e)
private async void editorSend_Click(object sender, RoutedEventArgs args)
{
if (string.IsNullOrWhiteSpace(editorText.Text))
return;
editorText.IsEnabled = false;
editorSend.IsEnabled = false;
editorClose.IsEnabled = false;
editorSending.Visibility = Visibility.Visible;
deleteComment.IsEnabled = false;
processing.Visibility = Visibility.Visible;
try
{
item.Snippet.TextDisplay = editorText.Text;
item.Snippet.TextOriginal = editorText.Text;
item.Snippet.UpdatedAt = DateTime.Now;
if (type == CommentType.Reply)
{
await SecretsVault.Service.Comments.Update(item, "snippet").ExecuteAsync();
Initialize(item);
}
else
{
thread.Snippet.TopLevelComment = item;
await SecretsVault.Service.CommentThreads.Update(thread, "snippet").ExecuteAsync();
Initialize(thread);
}
editorClose_Click(this, null);
text.Text = editorText.Text;
meta.Text = $"{Methods.GetAgo(item.Snippet.PublishedAt.Value)} {resources.GetString("/CommentsPage/edited")}";
editor.Visibility = Visibility.Collapsed;
text.Visibility = Visibility.Visible;
toolbar.Visibility = Visibility.Visible;
}
catch
catch (Exception e)
{
await new MessageDialog(resources.GetString("/CommentsPage/failedEdit")).ShowAsync();
Analytics.TrackEvent("Failed to edit comment", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message }
});
}
editorText.IsEnabled = true;
editorSend.IsEnabled = true;
editorClose.IsEnabled = true;
editorSending.Visibility = Visibility.Collapsed;
deleteComment.IsEnabled = true;
processing.Visibility = Visibility.Collapsed;
}
private void editorText_TextChanged(object sender, TextChangedEventArgs e)
{
if (editorText.Text.Length == 0)
editorSend.IsEnabled = false;
else editorSend.IsEnabled = true;
editorSend.IsEnabled = editorText.Text.Length == 0 ? false : true;
}
private void editBtn_Click(object sender, RoutedEventArgs e)
{
((grid.Children[0] as Grid).Children[1] as Grid).RowDefinitions[3].Height = new GridLength(0);
editor.Visibility = Visibility.Visible;
toolbar.Visibility = Visibility.Collapsed;
text.Visibility = Visibility.Collapsed;
editorText.Text = text.Text;
editor.Visibility = Visibility.Visible;
}
private async void DeleteComment_Click(object sender, RoutedEventArgs e)
private async void DeleteComment_Click(object sender, RoutedEventArgs args)
{
MessageDialog dialog = new MessageDialog(resources.GetString("/CommentsPage/deleteContent"), resources.GetString("/CommentsPage/deleteHeader"));
@@ -279,9 +300,20 @@ namespace FoxTube.Controls
try
{
await SecretsVault.Service.Comments.Delete(item.Id).ExecuteAsync();
Methods.CommentsPage.RemoveComment(this);
if (type == CommentType.Reply)
Methods.CommentsPage.RemoveComment(this, item.Snippet.ParentId);
else
Methods.CommentsPage.RemoveComment(this);
}
catch (Exception e)
{
await new MessageDialog(resources.GetString("/CommentsPage/failedDelete")).ShowAsync();
Analytics.TrackEvent("Failed delete comment", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message }
});
}
catch { }
}));
dialog.Commands.Add(new UICommand(resources.GetString("/CommentsPage/no")));
+5 -10
View File
@@ -22,12 +22,7 @@
<TextBlock Name="title" Grid.Column="1" Margin="5" TextWrapping="WrapWholeWords" Text="[Title]" FontSize="20" MaxLines="2"/>
<TextBlock Margin="5" Name="path" Grid.Column="1" VerticalAlignment="Bottom" Text="C://Users/Michael Gordeev/Downloads/[Title].mp4" Foreground="LightGray" MaxLines="1" TextWrapping="WrapWholeWords"/>
<StackPanel Grid.Column="2" Margin="5">
<TextBlock Text="Extension:" Foreground="Gray" Name="ext"/>
<TextBlock Text="Quality:" Foreground="Gray" Name="quality"/>
<TextBlock Text="Duration:" Foreground="Gray" Name="duration"/>
<TextBlock Text="Author:" Foreground="Gray" Name="channel"/>
</StackPanel>
<TextBlock Grid.Column="2" Margin="5" Name="meta"/>
<StackPanel Name="donePanel" Grid.Column="3" Orientation="Horizontal" Visibility="Visible">
<Button Name="open" Click="open_Click" Width="80" Height="80" Padding="0" Background="Transparent">
@@ -44,10 +39,10 @@
</Button>
</StackPanel>
<StackPanel Name="progressPanel" Grid.Column="4" Margin="10">
<TextBlock Name="status" Text="Downloading..." HorizontalAlignment="Left"/>
<ProgressBar Name="progressBar" Width="200" Maximum="1" IsIndeterminate="True" Foreground="Red"/>
<TextBlock Name="perc" Text="--%"/>
<Button x:Uid="/Downloads/cancel" Content="Cancel" Name="cancel" Click="CancelPrompt" HorizontalAlignment="Right"/>
<TextBlock x:Uid="/Downloads/downloading" Name="status" Text="Downloading..." HorizontalAlignment="Left"/>
<ProgressBar Name="progressBar" Width="200" Maximum="1"/>
<TextBlock Name="progressText" Text="--%"/>
<Button x:Uid="/Downloads/cancel" Content="Cancel" Name="cancel" Click="Cancel_Click" HorizontalAlignment="Right"/>
</StackPanel>
</Grid>
</UserControl>
+171 -128
View File
@@ -4,7 +4,6 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Windows.System;
using FoxTube.Classes;
using YoutubeExplode.Models.MediaStreams;
using YoutubeExplode;
using Windows.Storage;
@@ -16,30 +15,55 @@ using Windows.UI.Notifications;
using Microsoft.Toolkit.Uwp.Notifications;
using System.Threading.Tasks;
using Windows.ApplicationModel.Resources;
using Microsoft.AppCenter.Analytics;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace FoxTube.Controls
{
public class DownloadItemContainer
{
public string Title { get; set; }
public string Channel { get; set; }
public string Id { get; set; }
public string Name { get; set; }
public string Extension { get; set; }
public Uri Thumbnail { get; set; }
public string Quality { get; set; }
public TimeSpan Duration { get; set; }
public bool IsDownloaded { get; set; }
}
public sealed partial class DownloadItem : UserControl
{
ResourceLoader resources = ResourceLoader.GetForCurrentView("Downloads");
public DownloadItemContainer Container { get; private set; }
public StorageFile file;
public bool InProgress { get; set; } = false;
readonly YoutubeClient client = new YoutubeClient();
readonly CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token;
readonly Progress<double> progress = new Progress<double>();
private readonly NotificationData data = new NotificationData();
DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
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();
Download(info, meta, q);
Container = new DownloadItemContainer()
{
Channel = meta.Snippet.ChannelTitle,
Duration = Methods.GetDuration(meta.ContentDetails.Duration),
Extension = info.Container.GetFileExtension(),
Id = meta.Id,
IsDownloaded = false,
Quality = q,
Thumbnail = meta.Snippet.Thumbnails.Medium.Url.ToUri(),
Title = meta.Snippet.Title
};
Download(info);
}
public DownloadItem(DownloadItemContainer container)
@@ -47,130 +71,145 @@ namespace FoxTube.Controls
InitializeComponent();
Container = container;
Initialize();
Load();
}
public async void Initialize()
async void Load()
{
try
{
file = await DownloadAgent.Downloads.GetFileAsync(Container.Name);
}
catch
{
DownloadAgent.Remove(Container.Id);
return;
}
title.Text = Container.Title;
path.Text = file.Name;
thumbnail.Source = new BitmapImage(Container.Thumbnail);
quality.Text = $"{resources.GetString("/Downloads/quality")}: {Container.Quality}";
duration.Text = $"{resources.GetString("/Downloads/duration")}: {Container.Duration}";
channel.Text = $"{resources.GetString("/Downloads/author")}: {Container.Channel}";
ext.Text = $"{resources.GetString("/Downloads/ext")}: {Container.Extension}";
File = await DownloadAgent.Downloads.TryGetItemAsync(Container.Name) as StorageFile;
if (File == null)
DownloadAgent.Remove(this);
progressPanel.Visibility = Visibility.Collapsed;
donePanel.Visibility = Visibility.Visible;
progressPanel.Visibility = Visibility.Collapsed;
SetMeta();
}
async void Download(MediaStreamInfo info, Video meta, string q)
async void Download(MediaStreamInfo info)
{
try
File = await DownloadAgent.Downloads.CreateFileAsync($"{Container.Title.ReplaceInvalidChars('_')}.{Container.Extension}", CreationCollisionOption.GenerateUniqueName);
Container.Name = File.Name;
donePanel.Visibility = Visibility.Collapsed;
progressPanel.Visibility = Visibility.Visible;
SetMeta();
cts = new CancellationTokenSource();
progress = new Progress<double>();
progress.ProgressChanged += (s, e) => percentage = e;
timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
timer.Tick += (s, e) => UpdateInfo();
#region Polling notification
ToastContent toastContent = new ToastContent()
{
InProgress = true;
Container = new DownloadItemContainer();
token = cts.Token;
progress.ProgressChanged += (s, e) => percentage = e;
timer.Tick += (s, e) => UpdateInfo(percentage);
file = await DownloadAgent.Downloads.CreateFileAsync($"{meta.Snippet.Title.ReplaceInvalidChars('_')}.{info.Container.GetFileExtension()}", CreationCollisionOption.GenerateUniqueName);
Container.Name = file.Name;
ToastContent toastContent = new ToastContent()
Visual = new ToastVisual()
{
Visual = new ToastVisual()
BindingGeneric = new ToastBindingGeneric()
{
BindingGeneric = new ToastBindingGeneric()
{
Children =
Children =
{
new AdaptiveText() { Text = resources.GetString("/Downloads/toastStartHeader") },
new AdaptiveProgressBar()
{
Title = meta.Snippet.Title,
Title = Container.Title,
Status = resources.GetString("/Downloads/downloading/Text"),
Value = new BindableProgressBarValue("value")
}
}
}
},
}
},
Actions = new ToastActionsCustom()
{
Buttons =
Actions = new ToastActionsCustom()
{
Buttons =
{
new ToastButton(resources.GetString("/Downloads/cancel/Content"), $"dcancel|{Container.Id}")
{
ActivationType = ToastActivationType.Background
}
}
},
Launch = "download",
ActivationType = ToastActivationType.Foreground
};
},
Launch = "download",
ActivationType = ToastActivationType.Foreground
};
data.Values["value"] = "0";
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(toastContent.GetXml()) { Tag = $"download|{meta.Id}", Data = data });
data = new NotificationData();
data.Values["value"] = "0";
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(toastContent.GetXml()) { Tag = $"download|{Container.Id}", Data = data });
#endregion
Container.Channel = meta.Snippet.ChannelTitle;
Container.Duration = XmlConvert.ToTimeSpan(meta.ContentDetails.Duration);
Container.Id = meta.Id;
Container.Quality = q;
Container.Thumbnail = meta.Snippet.Thumbnails.Medium.Url.ToUri();
Container.Title = meta.Snippet.Title;
Container.Extension = info.Container.GetFileExtension();
timer.Start();
thumbnail.Source = new BitmapImage(new Uri(meta.Snippet.Thumbnails.Medium.Url));
title.Text = meta.Snippet.Title;
ext.Text = $"{resources.GetString("/Downloads/ext")}: {info.Container.GetFileExtension()}";
quality.Text = $"{resources.GetString("/Downloads/quality")}: {q}";
duration.Text = $"{resources.GetString("/Downloads/duration")}: {XmlConvert.ToTimeSpan(meta.ContentDetails.Duration)}";
channel.Text = $"{resources.GetString("/Downloads/author")}: {meta.Snippet.ChannelTitle}";
path.Text = file.Name;
try
{
await new YoutubeClient().DownloadMediaStreamAsync(info, await File.OpenStreamForWriteAsync(), progress, cts.Token);
progressPanel.Visibility = Visibility.Visible;
donePanel.Visibility = Visibility.Collapsed;
Container.IsDownloaded = true;
timer.Stop();
timer.Start();
await client.DownloadMediaStreamAsync(info, await file.OpenStreamForWriteAsync(), progress, token);
progressPanel.Visibility = Visibility.Collapsed;
donePanel.Visibility = Visibility.Visible;
InProgress = false;
if (!cts.IsCancellationRequested)
if (cts.IsCancellationRequested)
throw new TaskCanceledException();
else
DownloadCompleted();
}
catch (TaskCanceledException) { }
catch (TaskCanceledException)
{
Windows.Data.Xml.Dom.XmlDocument template = new Windows.Data.Xml.Dom.XmlDocument();
template.LoadXml($@"<toast>
<visual>
<binding template='ToastGeneric'>
<text>{resources.GetString("/Downloads/toastCanceledHeader")}</text>
<text>{Container.Title}</text>
</binding>
</visual>
</toast>");
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(template) { Tag = $"download|{Container.Id}" });
try { await File.DeleteAsync(StorageDeleteOption.PermanentDelete); }
catch { }
DownloadAgent.Remove(this);
}
catch (Exception e)
{
Windows.Data.Xml.Dom.XmlDocument template = new Windows.Data.Xml.Dom.XmlDocument();
template.LoadXml($@"<toast>
<visual>
<binding template='ToastGeneric'>
<text>{resources.GetString("/Downloads/failedHead")}</text>
<text>{resources.GetString("/Downloads/failedBody")}</text>
</binding>
</visual>
</toast>");
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(template) { Tag = $"download|{Container.Id}" });
Analytics.TrackEvent("Failed to download video", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Container", JsonConvert.SerializeObject(Container) }
});
try { await File.DeleteAsync(StorageDeleteOption.PermanentDelete); }
catch { }
DownloadAgent.Remove(this);
}
}
private void UpdateInfo(double e)
private void UpdateInfo()
{
progressBar.Value = e;
perc.Text = $"{(int)e * 100}%";
progressBar.Value = percentage;
progressText.Text = Math.Round(percentage * 100, 1) + "%";
data.Values["value"] = e.ToString();
data.Values["value"] = percentage.ToString();
ToastNotificationManager.CreateToastNotifier().Update(data, $"download|{Container.Id}");
}
private void DownloadCompleted()
{
Windows.Data.Xml.Dom.XmlDocument template = new Windows.Data.Xml.Dom.XmlDocument();
template.LoadXml($@"<toast activationType='background' launch='download|{file.Path}'>
template.LoadXml($@"<toast activationType='foreground' launch='download'>
<visual>
<binding template='ToastGeneric'>
<text>{resources.GetString("/Downloads/toastCompleteHeader")}</text>
@@ -185,11 +224,14 @@ namespace FoxTube.Controls
</actions>
</toast>");
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(template) { Tag = $"download|{Container.Id}" });
donePanel.Visibility = Visibility.Visible;
progressPanel.Visibility = Visibility.Collapsed;
}
private async void open_Click(object sender, RoutedEventArgs e)
{
await Launcher.LaunchFileAsync(file);
await Launcher.LaunchFileAsync(File);
}
private void gotoOriginal_Click(object sender, RoutedEventArgs e)
@@ -197,44 +239,45 @@ namespace FoxTube.Controls
Methods.MainPage.GoToVideo(Container.Id);
}
public async void Cancel()
public async void Cancel(bool prompt = true)
{
status.Text = resources.GetString("/Downloads/cancelling");
progressBar.IsIndeterminate = true;
cancel.IsEnabled = false;
cts.Cancel();
try
{
await file.DeleteAsync();
}
catch { }
Windows.Data.Xml.Dom.XmlDocument template = new Windows.Data.Xml.Dom.XmlDocument();
template.LoadXml($@"<toast activationType='foreground' launch='download'>
<visual>
<binding template='ToastGeneric'>
<text>{resources.GetString("/Downloads/toastCanceledHeader")}</text>
<text>{Container.Title}</text>
</binding>
</visual>
</toast>");
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(template) { Tag = $"download|{Container.Id}" });
DownloadAgent.Remove(Container.Id);
}
public async void CancelPrompt(object sender = null, RoutedEventArgs e = null)
{
if(InProgress)
if(prompt)
{
MessageDialog dialog = new MessageDialog(resources.GetString("/Downloads/prompt"), resources.GetString("/Downloads/cancellingHeader"));
dialog.Commands.Add(new UICommand(resources.GetString("/Downloads/yes"), (command) => Cancel()));
dialog.Commands.Add(new UICommand(resources.GetString("/Downloads/no")));
dialog.Commands.Add(new UICommand(resources.GetString("/Downloads/yes"), null, false));
dialog.Commands.Add(new UICommand(resources.GetString("/Downloads/no"), null, true));
dialog.DefaultCommandIndex = 1;
await dialog.ShowAsync();
if ((bool)(await dialog.ShowAsync()).Id)
{
cancel.IsEnabled = true;
return;
}
}
cts.Cancel();
status.Text = resources.GetString("/Downloads/cancelling");
progressBar.IsIndeterminate = true;
}
void SetMeta()
{
thumbnail.Source = new BitmapImage(Container.Thumbnail);
title.Text = Container.Title;
path.Text = File.Path;
meta.Text = $@"{resources.GetString("/Downloads/ext")}: {Container.Extension}
{resources.GetString("/Downloads/quality")}: {Container.Quality}
{resources.GetString("/Downloads/duration")}: {Container.Duration}
{resources.GetString("/Downloads/author")}: {Container.Channel}";
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
Cancel();
}
}
}
+4 -2
View File
@@ -2,7 +2,6 @@
x:Class="FoxTube.Controls.LiveCaptions"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FoxTube.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
@@ -10,7 +9,10 @@
VerticalAlignment="Bottom"
Margin="0,55">
<Grid Background="#99000000">
<Grid>
<Grid.Background>
<AcrylicBrush TintColor="#BF000000" TintOpacity="0.75"/>
</Grid.Background>
<TextBlock Text="Hello, World!" Name="text" Margin="5" FontSize="24"/>
</Grid>
</UserControl>
+8 -41
View File
@@ -1,7 +1,6 @@
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.Media;
using YoutubeExplode.Models.ClosedCaptions;
namespace FoxTube.Controls
@@ -17,8 +16,8 @@ namespace FoxTube.Controls
set => text.FontSize = value;
}
public MediaTimelineController Player { get; set; }
private bool isClosed = false;
public MediaElement Player { get; set; }
DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(10) };
ClosedCaption currentCaption = null;
@@ -32,41 +31,19 @@ namespace FoxTube.Controls
private void UpdateCaption(object sender, object e)
{
TimeSpan currentPosition = Player.Position;
currentCaption = track.Captions.Find(i => i.Offset <= Player.Position && i.Offset + i.Duration > Player.Position);
if (currentCaption != null)
text.Text = currentCaption.Text;
bool found = false;
if(!isClosed)
track.Captions.ForEach(i =>
{
if (Player.Position >= i.Offset && Player.Position <= i.Offset + i.Duration)
{
currentCaption = i;
text.Text = currentCaption.Text;
Visibility = Visibility.Visible;
found = true;
}
});
if (!found)
{
currentCaption = null;
Visibility = Visibility.Collapsed;
}
Visibility = currentCaption == null ? Visibility.Collapsed : Visibility.Visible;
}
public async void Initialize(ClosedCaptionTrackInfo info)
{
track = await new YoutubeExplode.YoutubeClient().GetClosedCaptionTrackAsync(info);
track.Captions.ForEach(i =>
{
if (Player.Position > i.Offset && Player.Position < i.Offset + i.Duration)
{
currentCaption = i;
text.Text = currentCaption.Text;
Visibility = Visibility.Visible;
}
});
UpdateCaption(this, null);
timer.Start();
}
@@ -78,15 +55,5 @@ namespace FoxTube.Controls
Visibility = Visibility.Collapsed;
timer.Stop();
}
public void Hide()
{
isClosed = true;
}
public void Show()
{
isClosed = false;
}
}
}
+318
View File
@@ -0,0 +1,318 @@
using FoxTube.Controls;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Windows.ApplicationModel.Resources;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using YoutubeExplode.Models.ClosedCaptions;
using YoutubeExplode.Models.MediaStreams;
namespace FoxTube
{
public delegate void QualityChangedEventHandler(object sender, MediaStreamInfo requestedQuality, MediaStreamInfoSet list);
public delegate void MinimodeChangedEventHandler(object sender, bool isOn);
/// <summary>
/// Custom controls for media player. MARKUP IS IN **Themes/Generic.xaml**!!!
/// </summary>
public sealed class PlayerControls : MediaTransportControls
{
public event RoutedEventHandler CloseRequested;
public event MinimodeChangedEventHandler MiniModeChanged;
public event RoutedEventHandler NextRequested;
public event QualityChangedEventHandler QualityChanged;
public MediaElement Player;
public IReadOnlyList<ClosedCaptionTrackInfo> ClosedCaptions { get; set; }
public MediaStreamInfoSet MediaStreams { get; set; }
Queue<Action> queue = new Queue<Action>();
bool isReady = false;
public PlayerControls()
{
DefaultStyleKey = typeof(PlayerControls);
}
protected override void OnApplyTemplate()
{
isReady = true;
(GetTemplateChild("close") as Button).Click += Close_Click;
(GetTemplateChild("compactClose") as Button).Click += Close_Click;
(GetTemplateChild("minimize") as Button).Click += Minimize_Click;
(GetTemplateChild("maximize") as Button).Click += Minimize_Click;
(GetTemplateChild("CompactOverlayButton") as Button).Click += CompactOverlay_Click;
(GetTemplateChild("qualitySelector") as ComboBox).SelectionChanged += QualitySelector_SelectionChanged;
(GetTemplateChild("ccSwitch") as ToggleSwitch).Toggled += CcSwitch_Toggled;
(GetTemplateChild("ccSelector") as ComboBox).SelectionChanged += CcSelector_SelectionChanged;
(GetTemplateChild("next") as Button).Click += (s, e) => NextRequested.Invoke(s, e);
(GetTemplateChild("AudioMuteButton") as Button).Click += Mute_Click;
(GetTemplateChild("VolumeSlider") as Slider).ValueChanged += Volume_ValueChanged;
(GetTemplateChild("ProgressSlider") as Slider).ValueChanged += ProgressSlider_ValueChanged;
if (queue.Count > 0)
foreach (Action i in queue)
i();
base.OnApplyTemplate();
}
private void Minimize_Click(object sender, RoutedEventArgs e)
{
if (sender == (GetTemplateChild("minimize") as Button))
{
MiniModeChanged.Invoke(this, true);
SetMinimized();
}
else
{
MiniModeChanged.Invoke(this, false);
SetNormal();
}
}
private void ProgressSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
(GetTemplateChild("compactSeek") as ProgressBar).Value = e.NewValue;
}
private void Mute_Click(object sender, RoutedEventArgs e)
{
if (((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph == "\xE74F")
Volume_ValueChanged(this, null);
else
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE74F";
}
private void Volume_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
double v = (GetTemplateChild("VolumeSlider") as Slider).Value;
if (v == 0)
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE74F";
else if (v <= 25 && v > 0)
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE992";
else if (v <= 50 && v > 25)
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE993";
else if (v <= 75 && v > 50)
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE994";
else if (v > 75)
((GetTemplateChild("AudioMuteButton") as Button).Content as FontIcon).Glyph = ((GetTemplateChild("volume") as Button).Content as FontIcon).Glyph = "\xE995";
}
private void CcSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
CcSwitch_Toggled((GetTemplateChild("ccSwitch") as ToggleSwitch), null);
}
private void CcSwitch_Toggled(object sender, RoutedEventArgs e)
{
if((GetTemplateChild("captions") as LiveCaptions).Player == null)
(GetTemplateChild("captions") as LiveCaptions).Player = Player;
if ((sender as ToggleSwitch).IsOn)
(GetTemplateChild("captions") as LiveCaptions).Initialize(((GetTemplateChild("ccSelector") as ComboBox).SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
else
(GetTemplateChild("captions") as LiveCaptions).Close();
}
private void QualitySelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SettingsStorage.RememberedQuality = e.AddedItems[0] as string;
MediaStreamInfo item = MediaStreams.Muxed.Find(i => i.VideoQualityLabel == e.AddedItems[0] as string);
if (item == null)
item = MediaStreams.Video.Find(i => i.VideoQualityLabel == e.AddedItems[0] as string);
QualityChanged?.Invoke(sender, item, MediaStreams);
}
private void CompactOverlay_Click(object sender, RoutedEventArgs e)
{
if((sender as Button).Margin.Top > 0)
{
Button btnCompact = sender as Button;
(GetTemplateChild("center") as Grid).Children.Remove(btnCompact);
(GetTemplateChild("headerToolbar") as StackPanel).Children.Add(btnCompact);
btnCompact.Margin = new Thickness(0);
(btnCompact.Content as FontIcon).Glyph = "\xE2B3";
SetNormal();
}
else
SetCompactView();
}
private void Close_Click(object sender, RoutedEventArgs e)
{
CloseRequested?.Invoke(sender, e);
}
public void SetCompactView()
{
Button btn = GetTemplateChild("CompactOverlayButton") as Button;
(GetTemplateChild("headerToolbar") as StackPanel).Children.Remove(btn);
(GetTemplateChild("center") as Grid).Children.Add(btn);
btn.Margin = new Thickness(0, 32, 0, 0);
(btn.Content as FontIcon).Glyph = "\xE2B4";
Button btnPlay = GetTemplateChild("PlayPauseButton") as Button;
(GetTemplateChild("leftStack") as StackPanel).Children.Remove(btnPlay);
(GetTemplateChild("centralStack") as StackPanel).Children.Add(btnPlay);
Button btnFwd = GetTemplateChild("SkipForwardButton") as Button;
(GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnFwd);
(GetTemplateChild("centralStack") as StackPanel).Children.Insert(1, btnFwd);
Button btnBwd = GetTemplateChild("SkipBackwardButton") as Button;
(GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnBwd);
(GetTemplateChild("centralStack") as StackPanel).Children.Insert(0, btnBwd);
(GetTemplateChild("header") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("centerBgr") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("center") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("footer") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("maximize") as Button).Visibility = Visibility.Collapsed;
(GetTemplateChild("compactClose") as Button).Visibility = Visibility.Collapsed;
(GetTemplateChild("captions") as LiveCaptions).Size = 15;
}
public void SetMinimized()
{
Button btnPlay = GetTemplateChild("PlayPauseButton") as Button;
(GetTemplateChild("leftStack") as StackPanel).Children.Remove(btnPlay);
(GetTemplateChild("centralStack") as StackPanel).Children.Add(btnPlay);
Button btnFwd = GetTemplateChild("SkipForwardButton") as Button;
(GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnFwd);
(GetTemplateChild("centralStack") as StackPanel).Children.Insert(1, btnFwd);
Button btnBwd = GetTemplateChild("SkipBackwardButton") as Button;
(GetTemplateChild("rightStack") as StackPanel).Children.Remove(btnBwd);
(GetTemplateChild("centralStack") as StackPanel).Children.Insert(0, btnBwd);
(GetTemplateChild("header") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("centerBgr") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("center") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("footer") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("compactClose") as Button).Visibility = Visibility.Visible;
(GetTemplateChild("maximize") as Button).Visibility = Visibility.Visible;
(GetTemplateChild("captions") as LiveCaptions).Size = 15;
}
public void SetNormal()
{
Button btnPlay = GetTemplateChild("PlayPauseButton") as Button;
(GetTemplateChild("centralStack") as StackPanel).Children.Remove(btnPlay);
(GetTemplateChild("leftStack") as StackPanel).Children.Insert(0, btnPlay);
Button btnFwd = GetTemplateChild("SkipForwardButton") as Button;
(GetTemplateChild("centralStack") as StackPanel).Children.Remove(btnFwd);
(GetTemplateChild("rightStack") as StackPanel).Children.Insert(0, btnFwd);
Button btnBwd = GetTemplateChild("SkipBackwardButton") as Button;
(GetTemplateChild("centralStack") as StackPanel).Children.Remove(btnBwd);
(GetTemplateChild("rightStack") as StackPanel).Children.Insert(0, btnBwd);
(GetTemplateChild("header") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("centerBgr") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("center") as Grid).Visibility = Visibility.Collapsed;
(GetTemplateChild("footer") as Grid).Visibility = Visibility.Visible;
(GetTemplateChild("captions") as LiveCaptions).Size = 24;
}
public void SetMeta(string title, string channel)
{
if (!isReady)
{
queue.Enqueue(() => SetMeta(title, channel));
return;
}
(GetTemplateChild("title") as TextBlock).Text = title;
(GetTemplateChild("author") as TextBlock).Text = channel;
}
public void SetCaptions(IReadOnlyList<ClosedCaptionTrackInfo> list)
{
if (!isReady)
{
queue.Enqueue(() => SetCaptions(list));
return;
}
ClosedCaptions = list;
if (list.Count > 0)
{
foreach(ClosedCaptionTrackInfo i in list)
(GetTemplateChild("ccSelector") as ComboBox).Items.Add(new ComboBoxItem()
{
Content = string.Format("{0} {1}", CultureInfo.GetCultureInfo(i.Language.Code).DisplayName, i.IsAutoGenerated ? ResourceLoader.GetForCurrentView("VideoPage").GetString("/VideoPage/generatedCaption") : ""),
Tag = i
});
ClosedCaptionTrackInfo item = list.Find(i => SettingsStorage.RelevanceLanguage.Contains(i.Language.Code)) ?? list.Find(i => "en-US".Contains(i.Language.Code));
if (item == null)
item = list.First();
(GetTemplateChild("ccSelector") as ComboBox).SelectedItem = (GetTemplateChild("ccSelector") as ComboBox).Items.Find(i => (i as ComboBoxItem).Tag == item);
}
else
(GetTemplateChild("cc") as Button).Visibility = Visibility.Collapsed;
}
public void SetQualities(MediaStreamInfoSet list)
{
if (!isReady)
{
queue.Enqueue(() => SetQualities(list));
return;
}
MediaStreams = list;
List<VideoQuality> q = list.GetAllVideoQualities().ToList();
q.Sort();
q.Reverse();
List<string> labels = new List<string>();
q.ForEach(i => labels.Add(i.GetVideoQualityLabel()));
(GetTemplateChild("qualitySelector") as ComboBox).ItemsSource = labels;
string s = SettingsStorage.VideoQuality == "remember" ? SettingsStorage.RememberedQuality : SettingsStorage.VideoQuality;
if (labels.Contains(s))
(GetTemplateChild("qualitySelector") as ComboBox).SelectedItem = s;
else
(GetTemplateChild("qualitySelector") as ComboBox).SelectedIndex = 0;
}
public void SetStream(string url)
{
if (!isReady)
{
queue.Enqueue(() => SetStream(url));
return;
}
// TODO: Set up stream UI
}
}
}
+26
View File
@@ -0,0 +1,26 @@
<UserControl
x:Class="FoxTube.VideoPlayer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls1="using:FoxTube.Controls"
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
xmlns:foxtube="using:FoxTube"
mc:Ignorable="d"
d:DesignHeight="1080"
d:DesignWidth="1920"
RequestedTheme="Dark">
<Grid Background="White" Name="grid">
<MediaElement Name="videoSource" AreTransportControlsEnabled="True" PosterSource="ms-appx:///Assets/videoThumbSample.png" CurrentStateChanged="VideoSource_CurrentStateChanged" MediaOpened="VideoSource_MediaOpened" VolumeChanged="VideoSource_VolumeChanged">
<MediaElement.TransportControls>
<foxtube:PlayerControls IsCompactOverlayButtonVisible="True" IsCompactOverlayEnabled="True"
IsFullWindowButtonVisible="True" IsFullWindowEnabled="True"
IsSkipBackwardButtonVisible="True" IsSkipBackwardEnabled="True"
IsSkipForwardButtonVisible="True" IsSkipForwardEnabled="True"/>
</MediaElement.TransportControls>
</MediaElement>
<MediaElement Name="audioSource" Width="0" AreTransportControlsEnabled="False" Visibility="Collapsed" Height="0" VerticalAlignment="Top" HorizontalAlignment="Left" CurrentStateChanged="AudioSource_CurrentStateChanged" MediaOpened="AudioSource_MediaOpened"/>
</Grid>
</UserControl>
+239
View File
@@ -0,0 +1,239 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Foundation;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;
using Google.Apis.YouTube.v3.Data;
using Windows.UI.Xaml.Media.Imaging;
using Windows.Media;
using Windows.Storage.Streams;
using Windows.UI.ViewManagement;
using System.Xml;
using Windows.ApplicationModel.Core;
using Windows.UI;
using Windows.Media.Casting;
using YoutubeExplode.Models.MediaStreams;
using YoutubeExplode;
using YoutubeExplode.Models.ClosedCaptions;
using System.Globalization;
using FoxTube.Controls;
using Windows.System;
using Windows.Media.Core;
using Windows.Media.Playback;
using System.Net.Http;
using System.Diagnostics;
using Windows.UI.Popups;
namespace FoxTube
{
public sealed partial class VideoPlayer : UserControl
{
public Video item;
public string avatar;
public event Event NextClicked;
public event ObjectEventHandler MiniMode;
public PlayerControls Controls => videoSource.TransportControls as PlayerControls;
public TimeSpan Position => videoSource.Position;
bool audioLoaded = false;
bool videoLoaded = false;
bool isMuxed = false;
DispatcherTimer muxedTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
TimeSpan timecodeBackup;
bool needUpdateTimecode = false;
SystemMediaTransportControls systemControls;
public VideoPlayer()
{
InitializeComponent();
}
public async void Initialize(Video meta, string channelAvatar, bool privateMode = false)
{
item = meta;
avatar = channelAvatar;
videoSource.PosterSource = new BitmapImage((meta.Snippet.Thumbnails.Maxres ?? meta.Snippet.Thumbnails.Medium).Url.ToUri());
Controls.SetMeta(meta.Snippet.Title, meta.Snippet.ChannelTitle);
if (item.Snippet.LiveBroadcastContent == "none")
{
InitializeContols();
Controls.SetQualities(await new YoutubeClient().GetVideoMediaStreamInfosAsync(item.Id));
Controls.SetCaptions(await new YoutubeClient().GetVideoClosedCaptionTrackInfosAsync(item.Id));
}
else if (item.Snippet.LiveBroadcastContent == "live")
{
InitializeContols();
Controls.SetStream((await new YoutubeClient().GetVideoMediaStreamInfosAsync(item.Id)).HlsLiveStreamUrl);
}
else
videoSource.AreTransportControlsEnabled = false;
if (!privateMode)
Debug.WriteLine("TODO: history entry creation");
// TODO: Create history entry
Visibility = Visibility.Visible;
}
public void InitializeContols()
{
videoSource.Volume = SettingsStorage.Volume;
muxedTimer.Tick += (s, e) =>
{
if (!Enumerable.Range(-100, 100).Contains((int)(videoSource.Position - audioSource.Position).TotalMilliseconds))
audioSource.Position = videoSource.Position;
};
Controls.CloseRequested += Controls_CloseRequested;
Controls.NextRequested += (s, e) => NextClicked?.Invoke();
Controls.QualityChanged += Controls_QualityChanged;
Controls.MiniModeChanged += Controls_MiniModeChanged;
Controls.Player = videoSource;
#region System Media Transport Controls
systemControls = SystemMediaTransportControls.GetForCurrentView();
systemControls.IsNextEnabled = true;
systemControls.IsPauseEnabled = true;
systemControls.IsPlayEnabled = true;
systemControls.DisplayUpdater.Type = MediaPlaybackType.Video;
systemControls.DisplayUpdater.VideoProperties.Title = item.Snippet.Title;
systemControls.DisplayUpdater.VideoProperties.Subtitle = item.Snippet.ChannelTitle;
systemControls.DisplayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromUri(avatar.ToUri());
systemControls.DisplayUpdater.Update();
systemControls.ButtonPressed += SystemControls_Engaged;
systemControls.IsEnabled = true;
#endregion
}
public void Controls_MiniModeChanged(object sender, bool e)
{
videoSource.IsFullWindow = false;
MiniMode?.Invoke(this, e);
}
private void Controls_QualityChanged(object sender, MediaStreamInfo requestedQuality, MediaStreamInfoSet list)
{
videoSource.Pause();
timecodeBackup = videoSource.Position;
needUpdateTimecode = true;
audioLoaded = false;
videoLoaded = false;
muxedTimer.Stop();
videoSource.Source = requestedQuality.Url.ToUri();
if(requestedQuality is MuxedStreamInfo)
{
isMuxed = true;
audioSource.Source = null;
}
else
audioSource.Source = list.Audio.First().Url.ToUri();
}
public void Controls_CloseRequested(object sender, RoutedEventArgs e)
{
systemControls.IsEnabled = false;
videoSource.Stop();
audioSource.Stop();
Methods.MainPage.CloseVideo();
}
private async void SystemControls_Engaged(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
if (args.Button == SystemMediaTransportControlsButton.Next)
NextClicked?.Invoke();
switch (args.Button)
{
case SystemMediaTransportControlsButton.Play:
videoSource.Play();
break;
case SystemMediaTransportControlsButton.Pause:
videoSource.Pause();
break;
case SystemMediaTransportControlsButton.Next:
NextClicked.Invoke();
break;
}
});
}
public void Pause()
{
videoSource.Pause();
}
private void VideoSource_CurrentStateChanged(object sender, RoutedEventArgs e)
{
if (audioSource.Source == null)
return;
switch(videoSource.CurrentState)
{
case Windows.UI.Xaml.Media.MediaElementState.Buffering:
case Windows.UI.Xaml.Media.MediaElementState.Paused:
systemControls.PlaybackStatus = MediaPlaybackStatus.Paused;
// TODO: Create history entry
audioSource.Pause();
break;
case Windows.UI.Xaml.Media.MediaElementState.Playing:
systemControls.PlaybackStatus = MediaPlaybackStatus.Playing;
audioSource.Play();
break;
}
}
private void AudioSource_CurrentStateChanged(object sender, RoutedEventArgs e)
{
if(audioSource.CurrentState == Windows.UI.Xaml.Media.MediaElementState.Playing)
muxedTimer.Start();
else
muxedTimer.Stop();
}
private void VideoSource_MediaOpened(object sender, RoutedEventArgs e)
{
videoLoaded = true;
if (needUpdateTimecode)
{
videoSource.Position = timecodeBackup;
needUpdateTimecode = false;
}
if (audioLoaded || isMuxed)
videoSource.Play();
}
private void AudioSource_MediaOpened(object sender, RoutedEventArgs e)
{
audioLoaded = true;
if (needUpdateTimecode)
audioSource.Position = timecodeBackup;
if (videoLoaded)
videoSource.Play();
}
private void VideoSource_VolumeChanged(object sender, RoutedEventArgs e)
{
audioSource.Volume = videoSource.Volume;
}
}
}
+32 -23
View File
@@ -2,23 +2,36 @@
x:Class="FoxTube.Controls.PlaylistCard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FoxTube.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
mc:Ignorable="d"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
SizeChanged="UserControl_SizeChanged"
d:DesignHeight="290"
d:DesignWidth="384">
d:DesignWidth="384"
MaxWidth="500"
Opacity="0">
<Button Padding="0" Background="Transparent" Click="Button_Click">
<Grid Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}">
<Windows10version1809:Page.OpacityTransition>
<ScalarTransition Duration="0:0:0.5"/>
</Windows10version1809:Page.OpacityTransition>
<Button Padding="0" Margin="1" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" Click="Button_Click">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="75"/>
<RowDefinition/>
<RowDefinition Height="20"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions>
<Image Name="thumbnail" Source="/Assets/videoThumbSample.png" Stretch="Fill"/>
<Image Source="/Assets/videoPlaceholder.png" Opacity=".0001" Stretch="Fill"/>
<Image Name="thumbnail" Source="/Assets/videoThumbSample.png" Stretch="Fill" Opacity="0" ImageOpened="Thumbnail_ImageOpened">
<Windows10version1809:Image.OpacityTransition>
<ScalarTransition Duration="0:0:0.5"/>
</Windows10version1809:Image.OpacityTransition>
</Image>
<Grid HorizontalAlignment="Right" Width="100">
<Grid.Background>
<AcrylicBrush TintColor="#7F000000" BackgroundSource="Backdrop" AlwaysUseFallback="False" TintOpacity="1" Opacity="0.97"/>
@@ -28,24 +41,20 @@
<TextBlock Foreground="White" Text="[N/A]" HorizontalAlignment="Center" FontSize="20" Name="counter"/>
</StackPanel>
</Grid>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition/>
</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"/>
<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="1" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" TextTrimming="CharacterEllipsis" Margin="5" MaxLines="2"/>
<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>
</Button>
<UserControl.ContextFlyout>
+41 -25
View File
@@ -1,6 +1,8 @@
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using Microsoft.AppCenter.Analytics;
using System;
using System.Collections.Generic;
using Windows.ApplicationModel.DataTransfer;
using Windows.System;
using Windows.UI.Xaml;
@@ -12,11 +14,13 @@ namespace FoxTube.Controls
/// <summary>
/// Playlist card control
/// </summary>
public sealed partial class PlaylistCard : Page, IItemCard
public sealed partial class PlaylistCard : Page
{
Playlist item;
public string playlistId;
public bool NeedInitialize { get; set; } = true;
public PlaylistCard(string id)
{
InitializeComponent();
@@ -25,34 +29,41 @@ namespace FoxTube.Controls
public async void Initialize(string id)
{
PlaylistsResource.ListRequest request = SecretsVault.Service.Playlists.List("snippet,contentDetails");
request.Id = id;
PlaylistListResponse response = await request.ExecuteAsync();
item = response.Items[0];
playlistId = id;
title.Text = item.Snippet.Title;
channelName.Text = item.Snippet.ChannelTitle;
counter.Text = item.ContentDetails.ItemCount.ToString();
date.Text = Methods.GetAgo(item.Snippet.PublishedAt.Value);
ChannelsResource.ListRequest r = SecretsVault.Service.Channels.List("snippet");
r.Id = item.Snippet.ChannelId;
try
{
thumbnail.Source = new BitmapImage((item.Snippet.Thumbnails.Maxres ?? item.Snippet.Thumbnails.Medium).Url.ToUri());
avatar.ProfilePicture = new BitmapImage(new Uri((await r.ExecuteAsync()).Items[0].Snippet.Thumbnails.Medium.Url));
} catch { }
playlistId = id;
PlaylistsResource.ListRequest request = SecretsVault.Service.Playlists.List("snippet,contentDetails");
request.Id = playlistId;
item = (await request.ExecuteAsync()).Items[0];
title.Text = item.Snippet.Title;
channelName.Text = item.Snippet.ChannelTitle;
counter.Text = item.ContentDetails.ItemCount.ToString();
date.Text = Methods.GetAgo(item.Snippet.PublishedAt.Value);
ChannelsResource.ListRequest r = SecretsVault.Service.Channels.List("snippet");
r.Id = item.Snippet.ChannelId;
try { thumbnail.Source = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri()); }
catch { }
try { avatar.ProfilePicture = new BitmapImage(new Uri((await r.ExecuteAsync()).Items[0].Snippet.Thumbnails.Medium.Url)); }
catch { }
Opacity = 1;
}
catch (Exception e)
{
Visibility = Visibility.Collapsed;
Analytics.TrackEvent("PlaylistCard loading failed", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Playlist ID", playlistId }
});
}
}
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
Height = e.NewSize.Width * 0.75;
}
private void Button_Click(object sender, RoutedEventArgs e)
public void Button_Click(object sender, RoutedEventArgs e)
{
Methods.MainPage.GoToPlaylist(item.Id);
}
@@ -73,5 +84,10 @@ namespace FoxTube.Controls
{
await Launcher.LaunchUriAsync($"https://www.youtube.com/playlist?list={playlistId}".ToUri());
}
private void Thumbnail_ImageOpened(object sender, RoutedEventArgs e)
{
thumbnail.Opacity = 1;
}
}
}
-1
View File
@@ -2,7 +2,6 @@
x:Class="FoxTube.Controls.ShowMore"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FoxTube.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
+15 -19
View File
@@ -1,19 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
namespace FoxTube.Controls
{
@@ -22,7 +8,7 @@ namespace FoxTube.Controls
public event Event Clicked;
public ShowMore()
{
this.InitializeComponent();
InitializeComponent();
}
private void btn_Click(object sender, RoutedEventArgs e)
@@ -32,11 +18,21 @@ namespace FoxTube.Controls
Clicked.Invoke();
}
public void Complete(bool close = false)
public void Invoke()
{
btn_Click(this, null);
}
public void Show()
{
btn.Visibility = Visibility.Collapsed;
bar.Visibility = Visibility.Visible;
}
public void Complete()
{
bar.Visibility = Visibility.Collapsed;
if (!close)
btn.Visibility = Visibility.Visible;
btn.Visibility = Visibility.Visible;
}
}
}
+33 -21
View File
@@ -4,51 +4,63 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
mc:Ignorable="d"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
SizeChanged="UserControl_SizeChanged"
d:DesignHeight="290"
d:DesignWidth="384">
d:DesignWidth="384"
MaxWidth="500"
Opacity="0">
<Button Padding="0" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" Click="Button_Click">
<Windows10version1809:UserControl.OpacityTransition>
<ScalarTransition Duration="0:0:0.5"/>
</Windows10version1809:UserControl.OpacityTransition>
<Button Padding="0" Margin="1" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" Click="Button_Click">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="75"/>
<RowDefinition/>
<RowDefinition Height="20"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions>
<Image Name="thumbnail" Source="/Assets/videoThumbSample.png" Stretch="Fill"/>
<Image Source="/Assets/videoPlaceholder.png" Opacity=".0001" Stretch="Fill"/>
<Image Name="thumbnail" Source="/Assets/videoThumbSample.png" Stretch="Fill" ImageOpened="Thumbnail_ImageOpened" Opacity="0">
<Windows10version1809:Image.OpacityTransition>
<ScalarTransition Duration="0:0:0.5"/>
</Windows10version1809:Image.OpacityTransition>
</Image>
<Grid Background="#7F000000" Name="watched" Visibility="Collapsed">
<StackPanel Margin="5" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" VerticalAlignment="Top" HorizontalAlignment="Left" Padding="5,2,5,2" BorderBrush="Gray" BorderThickness="1">
<TextBlock x:Uid="/Cards/watched" Text="Watched" Foreground="Gray" FontSize="12"/>
</StackPanel>
<ProgressBar VerticalAlignment="Bottom" Margin="54,0,0,0" Foreground="Red" Name="leftOn"/>
</Grid>
<StackPanel Margin="0,0,5,5" Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}" VerticalAlignment="Bottom" HorizontalAlignment="Right" Padding="5,2,5,3">
<TextBlock Name="info" Text="[Duration] | [Published at]" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Gray" FontSize="12"/>
</StackPanel>
<StackPanel Name="liveTag" Margin="0,0,5,30" Background="Red" BorderBrush="White" BorderThickness="1" VerticalAlignment="Bottom" HorizontalAlignment="Right" Padding="5,2,5,3" Orientation="Horizontal" Visibility="Collapsed">
<TextBlock Text="&#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.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Height="50" Width="50" Margin="5,-30,5,10" Fill="{ThemeResource SystemControlBackgroundChromeMediumBrush}"/>
<PersonPicture Name="avatar" Grid.Column="0" Height="46" Margin="5,-30,5,0" BorderBrush="White" BorderThickness="10"/>
<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="1" Name="title" Text="[Title]" TextWrapping="WrapWholeWords" Margin="5" MaxLines="2" TextTrimming="CharacterEllipsis"/>
<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>
</Button>
<UserControl.ContextFlyout>
+118 -29
View File
@@ -7,51 +7,59 @@ using Windows.UI.Xaml.Media.Imaging;
using Windows.System;
using Windows.ApplicationModel.DataTransfer;
using Windows.ApplicationModel.Resources;
using Microsoft.AppCenter.Analytics;
using System.Collections.Generic;
using YoutubeExplode;
using Windows.UI.Popups;
namespace FoxTube.Controls
{
/// <summary>
/// Video item card
/// </summary>
public sealed partial class VideoCard : UserControl, IItemCard
public sealed partial class VideoCard : UserControl
{
ResourceLoader resources = ResourceLoader.GetForCurrentView("Cards");
public string playlistId;
public string videoId;
Video item;
public VideoCard(string id, string playlist = null)
{
InitializeComponent();
Initialize(id, playlist);
}
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
public VideoCard(Video meta, string playlist = null)
{
Height = e.NewSize.Width * 0.75;
InitializeComponent();
item = meta;
playlistId = playlist;
LoadMeta();
}
public async void Initialize(string id, string playlist = null)
{
try {
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("snippet,contentDetails,statistics,liveStreamingDetails");
request.Id = id;
VideoListResponse response = await request.ExecuteAsync();
item = response.Items[0];
try
{
videoId = id;
playlistId = playlist;
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("snippet,contentDetails,statistics,liveStreamingDetails");
request.Id = id;
item = (await request.ExecuteAsync()).Items[0];
title.Text = item.Snippet.Title;
channelName.Text = item.Snippet.ChannelTitle;
if (item.Snippet.LiveBroadcastContent == "live")
{
views.Text = $"{item.LiveStreamingDetails.ConcurrentViewers:0,0} {resources.GetString("/Cards/viewers")}";
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue && item.LiveStreamingDetails.ScheduledEndTime.HasValue)
info.Text = $"{item.LiveStreamingDetails.ScheduledEndTime - item.LiveStreamingDetails.ScheduledStartTime} | {Methods.GetAgo(item.LiveStreamingDetails.ActualStartTime.Value)}";
info.Text = $"{item.LiveStreamingDetails.ScheduledEndTime - item.LiveStreamingDetails.ActualStartTime} | {Methods.GetAgo(item.LiveStreamingDetails.ActualStartTime.Value)}";
else
info.Text = item.LiveStreamingDetails.ActualStartTime.Value.ToString();
info.Text = Methods.GetAgo(item.LiveStreamingDetails.ActualStartTime.Value);
liveTag.Visibility = Visibility.Visible;
}
else if (item.Snippet.LiveBroadcastContent == "upcoming")
@@ -63,8 +71,8 @@ namespace FoxTube.Controls
info.Text = $"{Methods.GetAgo(item.Snippet.PublishedAt.Value)}";
liveTag.Visibility = Visibility.Visible;
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue && (item.LiveStreamingDetails.ScheduledStartTime - DateTime.Now).Value.TotalMilliseconds > 0)
liveContent.Text = $"{resources.GetString("/Cards/goesLive")} {item.LiveStreamingDetails.ScheduledStartTime}";
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue)
liveContent.Text = $"{resources.GetString("/Cards/goesLive")} {(item.LiveStreamingDetails.ScheduledStartTime.Value - DateTime.Now).ToString(@"hh\:mm\:ss")}";
else liveContent.Text = resources.GetString("/Cards/upcoming");
}
else
@@ -73,31 +81,107 @@ namespace FoxTube.Controls
info.Text = $"{item.ContentDetails.Duration.GetDuration()} | {Methods.GetAgo(item.Snippet.PublishedAt.Value)}";
}
var request1 = SecretsVault.Service.Channels.List("snippet");
request1.Id = item.Snippet.ChannelId;
ChannelListResponse response1 = await request1.ExecuteAsync();
try
{
avatar.ProfilePicture = new BitmapImage(new Uri(response1.Items[0].Snippet.Thumbnails.Medium.Url));
thumbnail.Source = new BitmapImage(new Uri((item.Snippet.Thumbnails.Maxres ?? item.Snippet.Thumbnails.Medium).Url));
}
try { thumbnail.Source = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri()); }
catch { }
try { avatar.ProfilePicture = new BitmapImage((await new YoutubeClient().GetChannelAsync(item.Snippet.ChannelId)).LogoUrl.ToUri()); }
catch { }
/*if(SecretsVault.UserHistory.Exists(x => x.Id == videoId))
{
if(SecretsVault.History.Contains(videoId))
watched.Visibility = Visibility.Visible;
leftOn.Value = SecretsVault.UserHistory.Find(x => x.Id == videoId).LeftOn;
}*/
Opacity = 1;
}
catch
catch (Exception e)
{
Visibility = Visibility.Collapsed;
Analytics.TrackEvent("VideoCard loading failed", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Video ID", videoId }
});
}
}
public void Button_Click(object sender, RoutedEventArgs e)
public async void LoadMeta()
{
videoId = item.Id;
title.Text = item.Snippet.Title;
channelName.Text = item.Snippet.ChannelTitle;
if (item.Snippet.LiveBroadcastContent == "live")
{
views.Text = $"{item.LiveStreamingDetails.ConcurrentViewers:0,0} {resources.GetString("/Cards/viewers")}";
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue && item.LiveStreamingDetails.ScheduledEndTime.HasValue)
info.Text = $"{item.LiveStreamingDetails.ScheduledEndTime - item.LiveStreamingDetails.ActualStartTime} | {Methods.GetAgo(item.LiveStreamingDetails.ActualStartTime.Value)}";
else
info.Text = Methods.GetAgo(item.LiveStreamingDetails.ActualStartTime.Value);
liveTag.Visibility = Visibility.Visible;
}
else if (item.Snippet.LiveBroadcastContent == "upcoming")
{
views.Text = "";
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue && item.LiveStreamingDetails.ScheduledEndTime.HasValue)
info.Text = $"{item.LiveStreamingDetails.ScheduledEndTime - item.LiveStreamingDetails.ScheduledStartTime} | {item.LiveStreamingDetails.ScheduledStartTime}";
else
info.Text = $"{Methods.GetAgo(item.Snippet.PublishedAt.Value)}";
liveTag.Visibility = Visibility.Visible;
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue)
liveContent.Text = $"{resources.GetString("/Cards/goesLive")} {item.LiveStreamingDetails.ScheduledStartTime - DateTime.Now}";
else liveContent.Text = resources.GetString("/Cards/upcoming");
}
else
{
views.Text = $"{item.Statistics.ViewCount:0,0} {resources.GetString("/Cards/views")}";
info.Text = $"{item.ContentDetails.Duration.GetDuration()} | {Methods.GetAgo(item.Snippet.PublishedAt.Value)}";
}
try { thumbnail.Source = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri()); }
catch { }
try { avatar.ProfilePicture = new BitmapImage((await new YoutubeClient().GetChannelAsync(item.Snippet.ChannelId)).LogoUrl.ToUri()) { DecodePixelHeight = 50, DecodePixelWidth = 50 }; }
catch { }
if (SecretsVault.History.Contains(videoId))
watched.Visibility = Visibility.Visible;
Opacity = 1;
}
public async void Button_Click(object sender, RoutedEventArgs e)
{
if (item.ContentDetails.ContentRating != null)
{
if (SecretsVault.IsAuthorized)
{
if (SettingsStorage.Mature == MatureState.Blocked)
{
MessageDialog dialog = new MessageDialog(resources.GetString("/VideoPage/matureText"), resources.GetString("/VideoPage/wantContinue"));
dialog.Commands.Add(new UICommand(resources.GetString("/VideoPage/yes"), null, true));
dialog.Commands.Add(new UICommand(resources.GetString("/VideoPage/always"), (command) => SettingsStorage.Mature = MatureState.Allowed, true));
dialog.Commands.Add(new UICommand(resources.GetString("/VideoPage/no"), null, false));
dialog.CancelCommandIndex = 2;
dialog.DefaultCommandIndex = 0;
if (!(bool)(await dialog.ShowAsync()).Id)
return;
}
}
else
{
MessageDialog dialog = new MessageDialog(resources.GetString("/VideoPage/matureText"), resources.GetString("/VideoPage/signRequired"));
dialog.Commands.Add(new UICommand(resources.GetString("/VideoPage/signin"), (command) => SecretsVault.Authorize()));
dialog.Commands.Add(new UICommand(resources.GetString("/VideoPage/cancel")));
dialog.CancelCommandIndex = 1;
dialog.DefaultCommandIndex = 0;
await dialog.ShowAsync();
return;
}
}
Methods.MainPage.GoToVideo(videoId, playlistId);
}
@@ -117,5 +201,10 @@ namespace FoxTube.Controls
{
await Launcher.LaunchUriAsync($"https://www.youtube.com/watch?v={videoId}".ToUri());
}
private void Thumbnail_ImageOpened(object sender, RoutedEventArgs e)
{
thumbnail.Opacity = 1;
}
}
}
-266
View File
@@ -1,266 +0,0 @@
<UserControl
x:Class="FoxTube.VideoPlayer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls1="using:FoxTube.Controls"
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
mc:Ignorable="d"
d:DesignHeight="1080"
d:DesignWidth="1920"
RequestedTheme="Dark"
PointerMoved="UserControl_PointerMoved"
PointerExited="UserControl_PointerExited"
PointerEntered="UserControl_PointerEntered">
<Grid Background="White" Name="grid" Tapped="UserControl_Tapped">
<MediaPlayerElement IsDoubleTapEnabled="False" Name="videoSource" AreTransportControlsEnabled="False" PosterSource="ms-appx:///Assets/videoThumbSample.png"/>
<MediaPlayerElement Name="audioSource" Width="0" Height="0" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<controls1:LiveCaptions Player="{x:Bind controller}" Visibility="Collapsed"/>
<Grid Name="controls" Visibility="Visible">
<Windows10version1809:Grid.OpacityTransition>
<Windows10version1809:ScalarTransition/>
</Windows10version1809:Grid.OpacityTransition>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid Name="header" Height="50">
<Grid.Background>
<AcrylicBrush TintColor="#CC000000" TintOpacity="0.6"/>
</Grid.Background>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Button Click="minimize_Click" Name="minimize" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE011;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/minimize"/>
</ToolTipService.ToolTip>
</Button>
<StackPanel Grid.Column="1" Margin="10,0,10,0" VerticalAlignment="Center">
<TextBlock Name="title" Text="[Title]" Foreground="White" VerticalAlignment="Center" TextWrapping="WrapWholeWords" FontSize="20" MaxLines="1"/>
<TextBlock Foreground="LightGray" Text="[Channel name]" Name="channelName" FontStyle="Italic"/>
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Name="closeHeader" Click="close_Click" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE10A;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/close"/>
</ToolTipService.ToolTip>
</Button>
<Button Name="cast" Click="cast_Click" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xEC15;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/cast"/>
</ToolTipService.ToolTip>
</Button>
<Button Name="miniViewBtn" Click="miniView_Click" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE2B3;" Foreground="White" Width="50" Height="50" FontSize="25" HorizontalAlignment="Center" Margin="0,0,0,0" VerticalAlignment="Center">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/miniview"/>
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</Grid>
<Grid Grid.Row="1" Name="playPauseArea" Tapped="playPauseArea_Tapped" DoubleTapped="playPauseArea_DoubleTapped" Background="Black" Opacity=".0001"/>
<Grid Name="touchCentral" Visibility="Collapsed" Grid.Row="1">
<Grid.Background>
<AcrylicBrush TintColor="#CC000000" TintOpacity="0.1"/>
</Grid.Background>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button VerticalAlignment="Center" Content="&#xED3C;" FontFamily="Segoe MDL2 Assets" Background="Transparent" FontSize="40" Foreground="WhiteSmoke" Name="touchBack10" Click="back10_Click"/>
<Button VerticalAlignment="Center" Content="&#xE102;" FontFamily="Segoe MDL2 Assets" Background="Transparent" FontSize="100" Foreground="WhiteSmoke" Name="touchPlay" Click="play_Click"/>
<Button VerticalAlignment="Center" Content="&#xED3D;" FontFamily="Segoe MDL2 Assets" Background="Transparent" FontSize="40" Foreground="WhiteSmoke" Name="touchFwd30" Click="fwd30_Click"/>
</StackPanel>
<Button Visibility="Collapsed" Margin="0,32,0,0" VerticalAlignment="Top" HorizontalAlignment="Right" Name="miniViewExit" Grid.Row="1" Click="miniView_Click" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE2B4;" Foreground="White" Width="45" Height="45" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/exitminiview"/>
</ToolTipService.ToolTip>
</Button>
<Button Visibility="Collapsed" VerticalAlignment="Top" HorizontalAlignment="Right" Name="close" Click="close_Click" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE10A;" Foreground="White" Width="45" Height="45" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/close"/>
</ToolTipService.ToolTip>
</Button>
<Button Visibility="Collapsed"
VerticalAlignment="Top" HorizontalAlignment="Left"
Name="maximize" Click="maximize_Click"
Background="Transparent" FontFamily="Segoe MDL2 Assets" Foreground="White" FontSize="25"
Content="&#xE010;"
Width="45" Height="45">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/maximize"/>
</ToolTipService.ToolTip>
</Button>
<ProgressBar VerticalAlignment="Bottom" Foreground="Red" Name="seekIndicator" Visibility="Collapsed"/>
</Grid>
<StackPanel Name="schedulePanel" Visibility="Collapsed" VerticalAlignment="Bottom" HorizontalAlignment="Right" Grid.Row="1" BorderBrush="Black" BorderThickness="2" Padding="10" Margin="0,25" Orientation="Horizontal">
<StackPanel.Background>
<AcrylicBrush TintColor="#CC000000" TintOpacity="0.6"/>
</StackPanel.Background>
<FontIcon Glyph="&#xE704;" FontSize="30" Margin="0,0,10,0" VerticalAlignment="Top"/>
<StackPanel>
<TextBlock FontWeight="Bold" Text="Stream hasn't started yet"/>
<TextBlock Name="scheduleHeader" Visibility="Collapsed" Text="Stream schedule:"/>
<TextBlock Name="scheduleStart" Visibility="Collapsed" Text="Start time: "/>
<TextBlock Name="scheduleEnd" Visibility="Collapsed" Text="End time:"/>
<TextBlock Name="countdownHeader" Visibility="Collapsed" FontWeight="Bold" Text="Stream will be started in:" Margin="0,10,0,0"/>
<TextBlock Name="countdown" Visibility="Collapsed" FontWeight="SemiBold" FontSize="20" Text="1:00:00:00"/>
</StackPanel>
</StackPanel>
<Grid Grid.Row="2" Height="50" Name="mainControls">
<Grid.Background>
<AcrylicBrush TintColor="#CC000000" TintOpacity="0.6"/>
</Grid.Background>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<Button Click="play_Click" Name="play" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE768;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/play"/>
</ToolTipService.ToolTip>
</Button>
<Button Name="next" Click="next_Click" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE101;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/next"/>
</ToolTipService.ToolTip>
</Button>
<Button Name="openVolume" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE995;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/volume"/>
</ToolTipService.ToolTip>
<Button.Flyout>
<Flyout>
<StackPanel Orientation="Horizontal" Margin="-10">
<Button Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE995;" Foreground="White" Width="50" Height="50" FontSize="25" Name="muteBtn" Click="muteBtn_Click">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/mute"/>
</ToolTipService.ToolTip>
</Button>
<Slider Orientation="Horizontal" Width="150" Margin="10,5,10,0" VerticalAlignment="Center" Name="volume" ValueChanged="volume_ValueChanged"/>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
<Button Name="gotoLive" Visibility="Collapsed" Click="GotoLive_Click" Background="Transparent" Foreground="White" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/goLive"/>
</ToolTipService.ToolTip>
<StackPanel Orientation="Horizontal">
<FontIcon Glyph="&#xE91F;" Foreground="Red"/>
<TextBlock Text="Live" Margin="5,0,0,0" FontSize="15" VerticalAlignment="Center"/>
</StackPanel>
</Button>
</StackPanel>
<Grid Grid.Column="1" Name="seekPanel">
<TextBlock Name="elapsedTime" Foreground="White" Text="[Elapsed]" VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<TextBlock Name="remainingTime" Foreground="White" Text="[Remaining]" VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
<Grid VerticalAlignment="Top" Margin="0,15,0,0" Height="2">
<ProgressBar Background="#66FFFFFF" Foreground="LightGray" Name="bufferingLevel"/>
</Grid>
<Slider PointerCaptureLost="seek_PointerCaptureLost" ManipulationStarted="Seek_PointerCaptured" ManipulationMode="TranslateRailsX" ValueChanged="seek_ValueChanged" Name="seek" VerticalAlignment="Top" IsThumbToolTipEnabled="False" Background="Transparent" HorizontalAlignment="Stretch"/>
</Grid>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<TextBlock Text="1:12:32" Name="liveElapsed" Visibility="Collapsed" Margin="10,0" FontSize="20" VerticalAlignment="Center">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/streamElapsed"/>
</ToolTipService.ToolTip>
</TextBlock>
<StackPanel Orientation="Horizontal" Name="rewindPanel">
<Button Click="back10_Click" Name="back10" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xED3C;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/back"/>
</ToolTipService.ToolTip>
</Button>
<Button Click="fwd30_Click" Name="fwd30" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xED3D;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/fwd"/>
</ToolTipService.ToolTip>
</Button>
</StackPanel>
<Line Stroke="White" StrokeThickness="2" Y1="5" Y2="45"/>
<Button Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE190;" Foreground="White" Width="50" Height="50" FontSize="25" Name="captionsBtn">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/subs"/>
</ToolTipService.ToolTip>
<Button.Flyout>
<Flyout>
<StackPanel Width="225">
<ToggleSwitch x:Uid="/VideoPage/subsSwitch" Name="subsSwitch" Toggled="subsSwitch_Toggled" OnContent="Subtitles" OffContent="Subtitles"/>
<ComboBox x:Uid="/VideoPage/subsSelector" Name="subsLang" Header="Language" PlaceholderText="No subtitles are available" Visibility="Collapsed" HorizontalAlignment="Stretch" SelectionChanged="subsLang_SelectionChanged"/>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
<Button Name="qualityBtn" Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE713;" Foreground="White" Width="50" Height="50" FontSize="25">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/quality"/>
</ToolTipService.ToolTip>
<Button.Flyout>
<Flyout>
<ComboBox x:Uid="/VideoPage/qualitySelector" Width="225" Header="Quality" Name="quality" SelectionChanged="quality_SelectionChanged"/>
</Flyout>
</Button.Flyout>
</Button>
<Button Background="Transparent" FontFamily="Segoe MDL2 Assets" Content="&#xE740;" Foreground="White" Width="50" Height="50" FontSize="25" Name="fullscreen" Click="fullscreen_Click">
<ToolTipService.ToolTip>
<TextBlock x:Uid="/VideoPage/fullscreen"/>
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</Grid>
</Grid>
</Grid>
<Grid Name="matureBlock" Visibility="Collapsed" Background="#FF333333" Padding="25">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel>
<TextBlock Text="Warning! Mature content!" Foreground="White" FontSize="30"/>
<Line Stroke="White" StrokeThickness="2" X1="0" X2="350"/>
</StackPanel>
<Grid Name="proceedMature" Visibility="Collapsed" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock TextWrapping="WrapWholeWords" Foreground="White" Text="This content isn't advised for children. It can represent violance, blood or sexual scenes." FontSize="20"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="1" VerticalAlignment="Bottom">
<CheckBox Foreground="White" Name="matureDisable">
<TextBlock Foreground="White" Text="Don't show me it again"/>
</CheckBox>
<Button Content="Continue" Name="matureDismiss" Click="matureDismiss_Click" Margin="5,0,0,0" Foreground="White" Background="Gray"/>
</StackPanel>
</Grid>
<Grid BorderBrush="OrangeRed" BorderThickness="5" Margin="0,10,0,0" Visibility="Collapsed" Name="signReq" Grid.Row="1" VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xE192;" FontSize="40" Foreground="OrangeRed" Margin="5"/>
<StackPanel HorizontalAlignment="Stretch" Grid.Column="1">
<TextBlock Text="Sign in into your account to continue" Foreground="OrangeRed" FontWeight="Bold" FontSize="20"/>
<TextBlock Text="To watch this video you have to confirm your age by sign in into your account which contains your age" Foreground="OrangeRed"/>
</StackPanel>
<Button Name="signin" Click="signin_Click" Content="Sign in now" Foreground="White" Background="Gray" HorizontalAlignment="Right" Grid.Column="1" Margin="0,0,10,0"/>
</Grid>
</Grid>
</Grid>
</UserControl>
-824
View File
@@ -1,824 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Foundation;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;
using Google.Apis.YouTube.v3.Data;
using Windows.UI.Xaml.Media.Imaging;
using Windows.Media;
using Windows.Storage.Streams;
using Windows.UI.ViewManagement;
using System.Xml;
using Windows.ApplicationModel.Core;
using Windows.UI;
using Windows.Media.Casting;
using YoutubeExplode.Models.MediaStreams;
using YoutubeExplode;
using YoutubeExplode.Models.ClosedCaptions;
using System.Globalization;
using FoxTube.Controls;
using Windows.System;
using Windows.Media.Core;
using Windows.Media.Playback;
using System.Net.Http;
using System.Diagnostics;
namespace FoxTube
{
public enum PlayerLayout { Normal, Fullscreen, Minimized }
public sealed partial class VideoPlayer : UserControl
{
public string videoId;
public Video item;
public string avatar;
public bool incognito = false;
public PlayerLayout layout = PlayerLayout.Normal;
public bool pointerCaptured = false;
bool seekCaptured = false;
Point cursorBackup;
public event ObjectEventHandler SetFullSize;
public event Event NextClicked;
public Button Next => next;
public TimeSpan Elapsed { get; set; } = TimeSpan.FromMilliseconds(0);
public TimeSpan Remaining => Total.Subtract(Elapsed);
public TimeSpan Total { get; set; }
double timecodeBackup = 0;
bool needUpdateTimecode = false;
SystemMediaTransportControls systemControls;
IReadOnlyList<ClosedCaptionTrackInfo> ccInfo;
MediaStreamInfoSet streamInfo;
readonly DispatcherTimer timer = new DispatcherTimer()
{
Interval = TimeSpan.FromSeconds(1)
};
DispatcherTimer ctrlsFadeTimer = null;
LiveCaptions captions;
MediaPlayer videoPlayer;
MediaPlayer audioPlayer;
MediaTimelineController controller;
public VideoPlayer()
{
InitializeComponent();
}
public void Initialize(Video meta, string channelAvatar, bool privateMode = false)
{
Visibility = Visibility.Collapsed;
item = meta;
avatar = channelAvatar;
videoId = item.Id;
incognito = privateMode;
if (item.ContentDetails.ContentRating != null)
{
if (SecretsVault.IsAuthorized)
{
if (SettingsStorage.Mature == MatureState.AllowedOnce)
SettingsStorage.Mature = MatureState.Blocked;
else if (SettingsStorage.Mature == MatureState.Blocked)
{
Visibility = Visibility.Visible;
proceedMature.Visibility = Visibility.Visible;
matureBlock.Visibility = Visibility.Visible;
return;
}
}
else
{
Visibility = Visibility.Visible;
signReq.Visibility = Visibility.Visible;
matureBlock.Visibility = Visibility.Visible;
return;
}
}
if (item.Snippet.LiveBroadcastContent == "none")
LoadVideo();
else if (item.Snippet.LiveBroadcastContent == "live")
LoadStream();
else
LoadUpcoming();
if (!incognito)
Debug.WriteLine("TODO: history entry creation");
// TODO: Create history entry
Visibility = Visibility.Visible;
}
public void InitializeContols()
{
videoPlayer = new MediaPlayer();
controller = new MediaTimelineController();
videoPlayer.TimelineController = controller;
videoSource.SetMediaPlayer(videoPlayer);
videoPlayer.MediaOpened += async (s, e) =>
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
if (controller.State == MediaTimelineControllerState.Running && needUpdateTimecode)
{
controller.Position = TimeSpan.FromSeconds(timecodeBackup);
needUpdateTimecode = false;
controller.Resume();
return;
}
seek.IsEnabled = true;
play.IsEnabled = true;
touchPlay.IsEnabled = true;
if (SettingsStorage.Autoplay)
controller.Resume();
});
};
volume.Value = SettingsStorage.Volume;
if (!ApplicationView.GetForCurrentView().IsViewModeSupported(ApplicationViewMode.CompactOverlay))
miniViewBtn.Visibility = Visibility.Collapsed;
controller.StateChanged += CurrentStateChanged;
controller.Ended += (s, arg) =>
{
seek.Value = seek.Maximum;
seekIndicator.Value = seekIndicator.Maximum;
};
#region System Media Transport Controls
systemControls = SystemMediaTransportControls.GetForCurrentView();
systemControls.IsNextEnabled = true;
systemControls.IsPauseEnabled = true;
systemControls.IsPlayEnabled = true;
systemControls.DisplayUpdater.Type = MediaPlaybackType.Video;
systemControls.DisplayUpdater.VideoProperties.Title = item.Snippet.Title;
systemControls.DisplayUpdater.VideoProperties.Subtitle = item.Snippet.ChannelTitle;
systemControls.DisplayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromUri(avatar.ToUri());
systemControls.DisplayUpdater.Update();
systemControls.ButtonPressed += SystemControls_Engaged;
systemControls.IsEnabled = true;
#endregion
videoSource.PosterSource = new BitmapImage((item.Snippet.Thumbnails.Maxres ?? item.Snippet.Thumbnails.Medium).Url.ToUri());
title.Text = item.Snippet.Title;
channelName.Text = item.Snippet.ChannelTitle;
ctrlsFadeTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(5)
};
ctrlsFadeTimer.Tick += ControlsFade;
}
public void LoadUpcoming()
{
schedulePanel.Visibility = Visibility.Visible;
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue)
{
scheduleHeader.Visibility = Visibility.Visible;
scheduleStart.Visibility = Visibility.Visible;
countdownHeader.Visibility = Visibility.Visible;
countdown.Visibility = Visibility.Visible;
scheduleStart.Text = $"Start time: {item.LiveStreamingDetails.ScheduledStartTime.Value}";
timer.Tick += UpdateCountdown;
timer.Start();
}
if (item.LiveStreamingDetails.ScheduledEndTime.HasValue)
{
scheduleHeader.Visibility = Visibility.Visible;
scheduleEnd.Visibility = Visibility.Visible;
scheduleEnd.Text = $"End time: {item.LiveStreamingDetails.ScheduledEndTime.Value}";
}
}
public async void LoadStream()
{
InitializeContols();
seekPanel.Visibility = Visibility.Collapsed;
rewindPanel.Visibility = Visibility.Collapsed;
captionsBtn.Visibility = Visibility.Collapsed;
qualityBtn.Visibility = Visibility.Collapsed;
gotoLive.Visibility = Visibility.Visible;
liveElapsed.Visibility = Visibility.Visible;
timer.Tick += UpdateLive;
timer.Start();
streamInfo = await new YoutubeClient().GetVideoMediaStreamInfosAsync(videoId);
videoPlayer.Source = MediaSource.CreateFromUri(streamInfo.HlsLiveStreamUrl.ToUri());
}
public async void LoadVideo()
{
InitializeContols();
captions = grid.Children[2] as LiveCaptions;
captions.Player = controller;
controller.PositionChanged += UpdateSeek;
Total = XmlConvert.ToTimeSpan(item.ContentDetails.Duration);
seek.Maximum = Total.TotalMilliseconds;
seekIndicator.Maximum = Total.TotalMilliseconds;
elapsedTime.Text = Elapsed.Hours > 0 ? $"{Elapsed:hh\\:mm\\:ss}" : $"{Elapsed:mm\\:ss}";
remainingTime.Text = Remaining.Hours > 0 ? $"{Remaining:hh\\:mm\\:ss}" : $"{Remaining:mm\\:ss}";
#region Retrieving info for CC and Media streams
//Loading streams
streamInfo = await new YoutubeClient().GetVideoMediaStreamInfosAsync(videoId);
List<VideoQuality> q = streamInfo.GetAllVideoQualities().ToList();
q.Sort();
q.Reverse();
foreach (VideoQuality i in q)
quality.Items.Add(new ComboBoxItem() { Content = i.GetVideoQualityLabel() });
string s = SettingsStorage.VideoQuality == "remember" ? SettingsStorage.RememberedQuality : SettingsStorage.VideoQuality;
quality.SelectedItem = quality.Items.ToList().Exists(x => (x as ComboBoxItem).Content as string == s) ? quality.Items.Find(x => (x as ComboBoxItem).Content as string == s) : quality.Items.First();
//Loading captions
ccInfo = await new YoutubeClient().GetVideoClosedCaptionTrackInfosAsync(videoId);
if (ccInfo.Count > 0)
{
foreach (ClosedCaptionTrackInfo cc in ccInfo)
{
subsLang.Items.Add(new ComboBoxItem()
{
Content = string.Format("{0}{1}", CultureInfo.GetCultureInfo(cc.Language.Code).DisplayName, cc.IsAutoGenerated ? " (Auto-generated)" : ""),
Tag = cc
});
if (SettingsStorage.RelevanceLanguage.Contains(cc.Language.Code))
subsLang.SelectedItem = subsLang.Items.Last();
}
if (subsLang.SelectedItem == null)
if(ccInfo.ToList().Exists(i => i.Language.Code == "en"))
subsLang.SelectedItem = subsLang.Items.Find(i => (((ComboBoxItem)i).Tag as ClosedCaptionTrackInfo).Language.Code == "en");
else
subsLang.SelectedIndex = 0;
}
else
captionsBtn.Visibility = Visibility.Collapsed;
#endregion
}
public void UpdateCountdown(object sender, object e)
{
countdown.Text = $"{item.LiveStreamingDetails.ScheduledStartTime.Value - DateTime.Now:hh\\:mm\\:ss}";
}
public void UpdateLive(object sender, object e)
{
liveElapsed.Text = $"{DateTime.Now - item.LiveStreamingDetails.ActualStartTime:hh\\:mm\\:ss}";
}
public async void UpdateSeek(MediaTimelineController sender, object e)
{
try
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
bufferingLevel.Value = videoPlayer.PlaybackSession.DownloadProgress * 100;
if (seekCaptured)
return;
if(needUpdateTimecode)
if (controller.State == MediaTimelineControllerState.Running)
needUpdateTimecode = false;
else
return;
seek.Value = controller.Position.TotalMilliseconds;
seekIndicator.Value = seek.Value;
});
}
catch { }
}
private async void SystemControls_Engaged(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
switch (args.Button)
{
case SystemMediaTransportControlsButton.Pause:
controller.Pause();
break;
case SystemMediaTransportControlsButton.Play:
controller.Resume();
break;
case SystemMediaTransportControlsButton.Next:
NextClicked?.Invoke();
break;
}
});
}
void ControlsFade(object sender, object e)
{
if (seekCaptured)
return;
controls.Opacity = 0;
//controls.Visibility = Visibility.Collapsed;
if (layout != PlayerLayout.Minimized)
touchCentral.Visibility = Visibility.Collapsed;
if (pointerCaptured)
Window.Current.CoreWindow.PointerCursor = null;
seekIndicator.Visibility = Visibility.Collapsed;
ctrlsFadeTimer.Stop();
}
public void UpdateSize()
{
if(layout != PlayerLayout.Normal)
Height = Window.Current.Bounds.Height;
}
void ShowControls()
{
if (ctrlsFadeTimer == null)
return;
controls.Opacity = 1;
//controls.Visibility = Visibility.Visible;
Window.Current.CoreWindow.PointerCursor = new CoreCursor(CoreCursorType.Arrow, 0);
ctrlsFadeTimer.Start();
}
private void volume_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
double v = volume.Value;
if (v == 0)
muteBtn.Content = openVolume.Content = "\xE74F";
else if (v <= 25 && v > 0)
muteBtn.Content = openVolume.Content = "\xE992";
else if (v <= 50 && v > 25)
muteBtn.Content = openVolume.Content = "\xE993";
else if (v <= 75 && v > 50)
muteBtn.Content = openVolume.Content = "\xE994";
else if (v > 75)
muteBtn.Content = openVolume.Content = "\xE995";
SettingsStorage.Volume = (int) volume.Value;
if(audioPlayer != null)
audioPlayer.Volume = volume.Value * .01;
videoPlayer.Volume = volume.Value * .01;
}
private void muteBtn_Click(object sender, RoutedEventArgs e)
{
if (volume.Value != 0)
{
int v = SettingsStorage.Volume;
volume.Value = 0;
SettingsStorage.Volume = v;
}
else volume.Value = SettingsStorage.Volume;
}
private void UserControl_PointerMoved(object sender, PointerRoutedEventArgs e)
{
if (ctrlsFadeTimer == null)
return;
else if (cursorBackup != Window.Current.CoreWindow.PointerPosition)
ShowControls();
cursorBackup = Window.Current.CoreWindow.PointerPosition;
}
private void UserControl_PointerExited(object sender, PointerRoutedEventArgs e)
{
if (e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse && ctrlsFadeTimer != null)
{
pointerCaptured = false;
ControlsFade(this, null);
}
}
private void UserControl_PointerEntered(object sender, PointerRoutedEventArgs e)
{
if (e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse && ctrlsFadeTimer != null)
pointerCaptured = true;
}
private void playPauseArea_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
switch (layout)
{
case PlayerLayout.Minimized:
if (ApplicationView.GetForCurrentView().ViewMode == ApplicationViewMode.CompactOverlay)
miniView_Click(this, null);
else if (ApplicationView.GetForCurrentView().ViewMode == ApplicationViewMode.Default)
maximize_Click(this, null);
break;
case PlayerLayout.Fullscreen:
case PlayerLayout.Normal:
fullscreen_Click(this, null);
break;
}
}
private void playPauseArea_Tapped(object sender, TappedRoutedEventArgs e)
{
if (e.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse && layout != PlayerLayout.Minimized)
play_Click(this, null);
UserControl_Tapped(sender, e);
}
private void UserControl_Tapped(object sender, TappedRoutedEventArgs e)
{
if (e.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Touch && ctrlsFadeTimer != null)
{
touchCentral.Visibility = Visibility.Visible;
if (ctrlsFadeTimer.IsEnabled)
ControlsFade(this, null);
else
ShowControls();
}
}
private void quality_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
SettingsStorage.RememberedQuality = ((ComboBoxItem)quality.SelectedItem).Content.ToString();
controller.Pause();
timecodeBackup = controller.Position.TotalSeconds;
needUpdateTimecode = true;
if (streamInfo.Muxed.ToList().Exists(x => x.VideoQualityLabel == (quality.SelectedItem as ComboBoxItem).Content.ToString()))
{
videoPlayer.Source = MediaSource.CreateFromUri(streamInfo.Muxed.Find(x => x.VideoQualityLabel == (quality.SelectedItem as ComboBoxItem).Content as string).Url.ToUri());
audioPlayer = null;
}
else
{
if (audioPlayer == null)
{
audioPlayer = new MediaPlayer
{
TimelineController = controller,
Volume = volume.Value * .01
};
audioSource.SetMediaPlayer(audioPlayer);
}
VideoStreamInfo videoInfo = streamInfo.Video.Find(i => i.VideoQualityLabel == (quality.SelectedItem as ComboBoxItem).Content.ToString());
AudioStreamInfo audioInfo = streamInfo.Audio.First();
videoPlayer.Source = MediaSource.CreateFromUri(videoInfo.Url.ToUri());
audioPlayer.Source = MediaSource.CreateFromUri(audioInfo.Url.ToUri());
}
controller.Resume();
}
catch { }
}
private void subsSwitch_Toggled(object sender, RoutedEventArgs e)
{
if (subsSwitch.IsOn)
subsLang.Visibility = Visibility.Visible;
else
subsLang.Visibility = Visibility.Collapsed;
LoadTrack();
}
void LoadTrack()
{
if (subsSwitch.IsOn)
captions.Initialize((subsLang.SelectedItem as ComboBoxItem).Tag as ClosedCaptionTrackInfo);
else
captions?.Close();
}
private void fullscreen_Click(object sender, RoutedEventArgs e)
{
bool fullScreen = layout == PlayerLayout.Fullscreen ? false : true;
SetFullSize.Invoke(this, fullScreen);
Methods.MainPage.Fullscreen(fullScreen);
if(fullScreen)
{
ApplicationView.GetForCurrentView().TryEnterFullScreenMode();
fullscreen.Content = "\xE1D8";
layout = PlayerLayout.Fullscreen;
}
else
{
ApplicationView.GetForCurrentView().ExitFullScreenMode();
fullscreen.Content = "\xE1D9";
layout = PlayerLayout.Normal;
Height = double.NaN;
}
}
private void play_Click(object sender, RoutedEventArgs e)
{
if (controller.State == MediaTimelineControllerState.Running)
controller.Pause();
else if (controller.State == MediaTimelineControllerState.Paused)
controller.Resume();
}
private async void CurrentStateChanged(MediaTimelineController sender, object e)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
needUpdateTimecode = false;
switch (sender.State)
{
case MediaTimelineControllerState.Paused:
play.Content = "\xE102";
touchPlay.Content = "\xE102";
systemControls.PlaybackStatus = MediaPlaybackStatus.Paused;
break;
case MediaTimelineControllerState.Running:
play.Content = "\xE103";
touchPlay.Content = "\xE103";
systemControls.PlaybackStatus = MediaPlaybackStatus.Playing;
break;
default:
systemControls.PlaybackStatus = MediaPlaybackStatus.Closed;
break;
}
//SecretsVault.HistoryAdd(videoId, elapsed, total);
});
}
private async void miniView_Click(object sender, RoutedEventArgs e)
{
ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar;
bool MiniView = layout == PlayerLayout.Minimized ? false : true;
SetFullSize(this, MiniView);
if (MiniView)
{
if (layout == PlayerLayout.Fullscreen)
fullscreen.Content = "\xE740";
await ApplicationView.GetForCurrentView().TryEnterViewModeAsync(ApplicationViewMode.CompactOverlay);
pointerCaptured = false;
titleBar.ButtonBackgroundColor = Colors.Transparent;
titleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true;
mainControls.Visibility = Visibility.Collapsed;
header.Visibility = Visibility.Collapsed;
touchCentral.Visibility = Visibility.Visible;
miniViewExit.Visibility = Visibility.Visible;
touchBack10.FontSize = touchFwd30.FontSize = 20;
touchPlay.FontSize = 50;
Methods.MainPage.Fullscreen(true);
layout = PlayerLayout.Minimized;
if(captions != null)
captions.Size = 15;
}
else
{
await ApplicationView.GetForCurrentView().TryEnterViewModeAsync(ApplicationViewMode.Default);
mainControls.Visibility = Visibility.Visible;
header.Visibility = Visibility.Visible;
touchCentral.Visibility = Visibility.Collapsed;
miniViewExit.Visibility = Visibility.Collapsed;
touchBack10.FontSize = touchFwd30.FontSize = 40;
touchPlay.FontSize = 100;
Methods.MainPage.Fullscreen(false);
Height = double.NaN;
layout = PlayerLayout.Normal;
if(captions != null)
captions.Size = 24;
}
}
public void minimize_Click(object sender, RoutedEventArgs e)
{
if (layout == PlayerLayout.Fullscreen)
{
ApplicationView.GetForCurrentView().ExitFullScreenMode();
fullscreen.Content = "\xE740";
layout = PlayerLayout.Normal;
Methods.MainPage.Fullscreen(false);
}
else
SetFullSize?.Invoke(this, true);
Width = 432;
Height = 243;
layout = PlayerLayout.Minimized;
Methods.MainPage.MinimizeVideo();
mainControls.Visibility = Visibility.Collapsed;
header.Visibility = Visibility.Collapsed;
touchCentral.Visibility = Visibility.Visible;
maximize.Visibility = Visibility.Visible;
close.Visibility = Visibility.Visible;
touchBack10.FontSize = touchFwd30.FontSize = 20;
touchPlay.FontSize = 50;
if (captions != null)
captions.Size = 15;
}
private void maximize_Click(object sender, RoutedEventArgs e)
{
SetFullSize?.Invoke(this, false);
Width = double.NaN;
Height = double.NaN;
layout = PlayerLayout.Normal;
Methods.MainPage.MaximizeVideo();
mainControls.Visibility = Visibility.Visible;
header.Visibility = Visibility.Visible;
touchCentral.Visibility = Visibility.Collapsed;
maximize.Visibility = Visibility.Collapsed;
close.Visibility = Visibility.Collapsed;
touchBack10.FontSize = touchFwd30.FontSize = 40;
touchPlay.FontSize = 100;
if (captions != null)
captions.Size = 24;
}
private void seek_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
Elapsed = TimeSpan.FromMilliseconds(seek.Value);
elapsedTime.Text = Elapsed.Hours > 0 ? $"{Elapsed:hh\\:mm\\:ss}" : $"{Elapsed:mm\\:ss}";
remainingTime.Text = Remaining.Hours > 0 ? $"{Remaining:hh\\:mm\\:ss}" : $"{Remaining:mm\\:ss}";
}
private void seek_PointerCaptureLost(object sender, PointerRoutedEventArgs e)
{
seekCaptured = false;
needUpdateTimecode = true;
controller.Position = Elapsed;
}
private void fwd30_Click(object sender, RoutedEventArgs e)
{
if(Remaining.TotalSeconds >= 30)
controller.Position = Elapsed.Add(TimeSpan.FromSeconds(30));
}
private void back10_Click(object sender, RoutedEventArgs e)
{
if (Elapsed.TotalSeconds >= 10)
controller.Position = Elapsed.Subtract(TimeSpan.FromSeconds(10));
}
private void next_Click(object sender, RoutedEventArgs e)
{
NextClicked?.Invoke();
}
private void matureDismiss_Click(object sender, RoutedEventArgs e)
{
if ((bool)matureDisable.IsChecked)
SettingsStorage.Mature = MatureState.Allowed;
else
SettingsStorage.Mature = MatureState.AllowedOnce;
Methods.MainPage.GoToVideo(videoId, Methods.MainPage.GetPlaylist());
}
private void signin_Click(object sender, RoutedEventArgs e)
{
SecretsVault.Authorize();
}
public void Pause()
{
controller.Pause();
}
public void close_Click(object sender, RoutedEventArgs e)
{
systemControls.IsEnabled = false;
pointerCaptured = false;
controller.Pause();
ctrlsFadeTimer?.Stop();
timer?.Stop();
Methods.MainPage.CloseVideo();
}
private void cast_Click(object sender, RoutedEventArgs e)
{
if (videoPlayer.Source == null)
return;
controller.Pause();
CastingDevicePicker picker = new CastingDevicePicker();
picker.Filter.SupportsVideo = true;
picker.CastingDeviceSelected += async (s, args) =>
{
CastingConnection connection = args.SelectedCastingDevice.CreateCastingConnection();
await connection.RequestStartCastingAsync(videoPlayer.GetAsCastingSource());
};
Point positinon = cast.TransformToVisual(Window.Current.Content).TransformPoint(new Point(0, 0));
picker.Show(new Rect(positinon.X, positinon.Y, cast.ActualWidth, cast.ActualHeight), Windows.UI.Popups.Placement.Below);
}
public void KeyUpPressed(object sender, KeyRoutedEventArgs e)
{
switch(e.Key)
{
case VirtualKey.Escape:
if (layout == PlayerLayout.Fullscreen)
fullscreen_Click(this, null);
break;
case VirtualKey.F11:
fullscreen_Click(this, null);
break;
case VirtualKey.Space:
play_Click(this, null);
break;
case VirtualKey.Left:
back10_Click(this, null);
break;
case VirtualKey.Right:
fwd30_Click(this, null);
break;
}
}
private void subsLang_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
LoadTrack();
}
private void GotoLive_Click(object sender, RoutedEventArgs e) //TODO: Refactor
{
try
{
MediaTimeRange range = videoPlayer.PlaybackSession.GetSeekableRanges().Last();
controller.Position = range.End.Subtract(TimeSpan.FromMilliseconds(100));
}
catch { }
}
private void Seek_PointerCaptured(object sender, ManipulationStartedRoutedEventArgs e)
{
seekCaptured = true;
}
}
}
+8 -3
View File
@@ -103,7 +103,6 @@
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Classes\DownloadItemContainer.cs" />
<Compile Include="Classes\InboxItem.cs" />
<Compile Include="Classes\Methods.cs" />
<Compile Include="Classes\SearchPaameters.cs" />
@@ -130,6 +129,7 @@
<Compile Include="Controls\LiveCaptions.xaml.cs">
<DependentUpon>LiveCaptions.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\Player\PlayerControls.cs" />
<Compile Include="Controls\PlaylistCard.xaml.cs">
<DependentUpon>PlaylistCard.xaml</DependentUpon>
</Compile>
@@ -192,7 +192,7 @@
<Compile Include="Pages\VideoGrid.xaml.cs">
<DependentUpon>VideoGrid.xaml</DependentUpon>
</Compile>
<Compile Include="Controls\VideoPlayer.xaml.cs">
<Compile Include="Controls\Player\VideoPlayer.xaml.cs">
<DependentUpon>VideoPlayer.xaml</DependentUpon>
</Compile>
</ItemGroup>
@@ -252,6 +252,7 @@
<Content Include="Assets\StoreLogo.scale-150.png" />
<Content Include="Assets\StoreLogo.scale-200.png" />
<Content Include="Assets\StoreLogo.scale-400.png" />
<Content Include="Assets\videoPlaceholder.png" />
<Content Include="Assets\videoThumbSample.png" />
<Content Include="Assets\WhatsNewThumb.png" />
<Content Include="Assets\Wide310x150Logo.scale-100.png" />
@@ -381,10 +382,14 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Controls\VideoPlayer.xaml">
<Page Include="Controls\Player\VideoPlayer.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Themes\Generic.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup>
<PackageReference Include="AdaptiveCards.Rendering.Uwp">
+3
View File
@@ -27,6 +27,9 @@
<uap:LockScreen Notification="badgeAndTileText" BadgeLogo="Assets\BadgeLogo.png"/>
</uap:VisualElements>
<Extensions>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="foxtube"/>
</uap:Extension>
<Extension Category="windows.backgroundTasks" EntryPoint="FoxTube.Background.BackgroundProcessor">
<BackgroundTasks>
<Task Type="general" />
+2 -5
View File
@@ -1,11 +1,9 @@
<Page
x:Class="FoxTube.Pages.Browser"
x:Class="FoxTube.Pages.Home1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FoxTube.Pages"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
@@ -18,8 +16,7 @@
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<AutoSuggestBox Grid.ColumnSpan="2" Text="https://youtube.com/" QueryIcon="Forward" QuerySubmitted="Adress_QuerySubmitted" Name="adress" Margin="60,0,0,0"/>
<Button Content="POST" Click="Button_Click"/>
<AutoSuggestBox Grid.ColumnSpan="2" Text="https://youtube.com/" QueryIcon="Forward" QuerySubmitted="Adress_QuerySubmitted" Name="adress"/>
<ScrollViewer Grid.Row="1">
<TextBlock TextWrapping="Wrap" Name="code" IsTextSelectionEnabled="True"/>
</ScrollViewer>
+4 -36
View File
@@ -1,57 +1,25 @@
using Google.Apis.Http;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
using Windows.UI.Xaml.Controls;
namespace FoxTube.Pages
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class Browser : Page
public sealed partial class Home1 : Page
{
public Browser()
public Home1()
{
InitializeComponent();
}
public async void Initialize()
{
SecretsVault.HttpClient.DefaultRequestHeaders.Referrer = "https://youtube.com/".ToUri();
string response = await SecretsVault.Service.HttpClient.GetStringAsync(adress.Text);
code.Text = response;
view.NavigateToString(response);
//view.NavigateToString(response);
}
private void Adress_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
Initialize();
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
/*HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", SecretsVault.Credential.Token.AccessToken);
//string response = await SecretsVault.Service.HttpClient.Po(adress.Text);
code.Text = response;
view.NavigateToString(response);*/
}
}
}
+46 -42
View File
@@ -11,7 +11,7 @@
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
mc:Ignorable="d">
<Grid Name="grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
@@ -19,55 +19,59 @@
<Pivot SelectedIndex="0" Name="content" IsHeaderItemsCarouselEnabled="False" SelectionChanged="Content_SelectionChanged">
<PivotItem x:Uid="/Channel/videos" Header="Videos">
<ScrollViewer>
<ScrollViewer.Background>
<ImageBrush ImageSource="/Assets/ChannelCoverTemplate.png" x:Name="channelCover" Stretch="Uniform" AlignmentY="Top"/>
</ScrollViewer.Background>
<StackPanel Name="videos" Background="{ThemeResource AppBarBackgroundThemeBrush}" Margin="0,310,0,0" Visibility="Visible">
<Grid>
<ParallaxView Source="{x:Bind videoScroll}" VerticalShift="100">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Ellipse HorizontalAlignment="Left" Margin="10,-40,0,0" Fill="Black" Width="100" Height="100"/>
<PersonPicture Name="avatar" HorizontalAlignment="Left" Margin="10,-40,0,0"/>
<StackPanel Grid.Column="1" Orientation="Vertical" Margin="10,0,0,5">
<TextBlock Name="title" FontWeight="SemiBold" FontSize="22" Text="Channel name"/>
<TextBlock Name="subscribers" Foreground="Gray" Text="1,000,000 subscribers"/>
<TextBlock Name="videosCount" Foreground="Gray" Text="563,000 videos"/>
</StackPanel>
<TextBlock Grid.Column="2" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Height="50" Margin="10" TextAlignment="Center" Padding="0,16,0,0" Foreground="Gray">
<Hyperlink Click="Hyperlink_Click"><Run x:Uid="/Cards/login">Log in</Run></Hyperlink> <Run x:Uid="/Cards/tomanage">to manage your subscriptions</Run>
</TextBlock>
<Grid Visibility="Collapsed" Grid.Column="2" VerticalAlignment="Bottom" Margin="10" Name="subscriptionPane" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button x:Uid="/Cards/subscribe" Click="Subscribe_Click" Name="subscribe" Width="250" Height="50" Background="Red" Foreground="White" FontSize="18" FontWeight="SemiBold" Content="Subscirbe"/>
</Grid>
<Image Source="/Assets/ChannelCoverTemplate.png" Name="channelCover" Stretch="Uniform" HorizontalAlignment="Left" VerticalAlignment="Top" Opacity="0" ImageOpened="ChannelCover_ImageOpened">
<Windows10version1809:Image.OpacityTransition>
<ScalarTransition Duration="0:0:0.5"/>
</Windows10version1809:Image.OpacityTransition>
</Image>
</Grid>
<pages:VideoGrid/>
<controls:ShowMore Clicked="VideoMore_Clicked"/>
</StackPanel>
</ScrollViewer>
</ParallaxView>
<ScrollViewer ViewChanged="ScrollViewer_ViewChanged" Name="videoScroll">
<StackPanel Background="{ThemeResource AppBarBackgroundThemeBrush}" Margin="0,300,0,0" Visibility="Visible">
<Grid Name="infoPanel">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Ellipse HorizontalAlignment="Left" Margin="10,-40,0,0" Fill="Black" Width="100" Height="1"/>
<PersonPicture Name="avatar" HorizontalAlignment="Left" Margin="10,-40,0,0"/>
<StackPanel Grid.Column="1" Orientation="Vertical" Margin="10,0,0,5">
<TextBlock Name="title" FontWeight="SemiBold" FontSize="22" Text="Channel name"/>
<TextBlock Name="subscribers" Foreground="Gray" Text="1,000,000 subscribers"/>
<TextBlock Name="videosCount" Foreground="Gray" Text="563,000 videos"/>
</StackPanel>
<TextBlock Grid.Column="2" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Height="50" Margin="10" TextAlignment="Center" Padding="0,16,0,0" Foreground="Gray">
<Hyperlink Click="Hyperlink_Click"><Run x:Uid="/Cards/login">Log in</Run></Hyperlink> <Run x:Uid="/Cards/tomanage">to manage your subscriptions</Run>
</TextBlock>
<Grid Visibility="Collapsed" Grid.Column="2" VerticalAlignment="Bottom" Margin="10" Name="subscriptionPane" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button x:Uid="/Cards/subscribe" Click="Subscribe_Click" Name="subscribe" Width="250" Height="50" Background="Red" Foreground="White" FontSize="18" FontWeight="SemiBold" Content="Subscirbe"/>
</Grid>
</Grid>
<pages:VideoGrid x:Name="videoList"/>
<controls:ShowMore Clicked="VideoMore_Clicked" x:Name="videoMore"/>
</StackPanel>
</ScrollViewer>
</Grid>
</PivotItem>
<PivotItem x:Uid="/Channel/playlists" Header="Playlists">
<ScrollViewer>
<Grid Name="playlists">
<Grid>
<StackPanel Margin="10" Visibility="Visible">
<TextBlock x:Uid="/Channel/playlistTitle" FontSize="28" Text="Playlists"/>
<pages:VideoGrid/>
<controls:ShowMore Clicked="ShowMorePlaylists_Click"/>
<pages:VideoGrid x:Name="playlistList"/>
<controls:ShowMore Clicked="ShowMorePlaylists_Click" x:Name="playlistMore"/>
</StackPanel>
<local:LoadingPage Visibility="Collapsed"/>
<local:LoadingPage Visibility="Collapsed" x:Name="playlistLoading"/>
</Grid>
</ScrollViewer>
</PivotItem>
<PivotItem x:Uid="/Channel/about" Header="About channel">
<ScrollViewer>
<StackPanel Margin="10">
<TextBlock x:Uid="/Channel/aboutTitle" FontSize="28" Text="About this channel"/>
<TextBlock Name="description" TextWrapping="WrapWholeWords" IsTextSelectionEnabled="True"/>
</StackPanel>
<TextBlock Name="description" TextWrapping="WrapWholeWords" Margin="10" IsTextSelectionEnabled="True" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum justo erat, dapibus sit amet maximus eget, volutpat non turpis. Suspendisse. "/>
</ScrollViewer>
</PivotItem>
@@ -77,9 +81,9 @@
<Windows10version1809:StackPanel.OpacityTransition>
<ScalarTransition/>
</Windows10version1809:StackPanel.OpacityTransition>
<PersonPicture Height="32"/>
<TextBlock Text="Channel name" VerticalAlignment="Center" Margin="10,0"/>
<Button Background="Red" Foreground="White" FontWeight="SemiBold" Content="Subscribe" Width="150" Padding="2"/>
<PersonPicture Height="32" Name="collapsedAvatar"/>
<TextBlock Text="Channel name" VerticalAlignment="Center" Margin="10,0" Name="collapsedTitle"/>
<Button x:Uid="/Cards/subscribe" Background="Red" Foreground="White" FontWeight="SemiBold" Content="Subscribe" Width="150" Name="collapsedBtn" Click="Subscribe_Click" Padding="2"/>
</StackPanel>
<AutoSuggestBox x:Uid="/Channel/search" VerticalAlignment="Center" Width="250" Margin="8" PlaceholderText="Search on channel" QueryIcon="Find" Name="search" QuerySubmitted="AutoSuggestBox_QuerySubmitted"/>
</StackPanel>
@@ -92,6 +96,6 @@
<AppBarButton x:Uid="/Channel/share" Icon="Share" Label="Share" Name="share" Click="Share_Click"/>
</CommandBar>
<local:LoadingPage Grid.RowSpan="2" Visibility="Collapsed"/>
<local:LoadingPage Grid.RowSpan="2" Visibility="Collapsed" x:Name="loading" RefreshPage="Refresh_Click"/>
</Grid>
</Page>
+72 -91
View File
@@ -28,29 +28,15 @@ namespace FoxTube.Pages
public string channelId;
public Channel item;
readonly LoadingPage loading, playlistLoading;
readonly VideoGrid videoList, playlistList;
readonly ShowMore videoMore, playlistMore;
SearchResource.ListRequest request;
SearchResource.ListRequest videoRequest, playlistRequest;
private string videoToken;
private string playlistToken;
private string videoToken, playlistToken;
private bool playlistLoaded = false;
public ChannelPage()
{
InitializeComponent();
loading = grid.Children[2] as LoadingPage;
playlistLoading = playlists.Children[1] as LoadingPage;
videoList = videos.Children[1] as VideoGrid;
playlistList = (playlists.Children[0] as StackPanel).Children[1] as VideoGrid;
videoMore = videos.Children[2] as ShowMore;
playlistMore = (playlists.Children[0] as StackPanel).Children[2] as ShowMore;
loading.RefreshPage += Refresh_Click;
DataTransferManager.GetForCurrentView().DataRequested += new TypedEventHandler<DataTransferManager, DataRequestedEventArgs>(Share);
}
@@ -65,56 +51,44 @@ namespace FoxTube.Pages
public async void Initialize(string id)
{
content.SelectedIndex = 0;
loading.Refresh();
playlistLoading.Refresh();
try
{
channelId = id;
ChannelsResource.ListRequest request = SecretsVault.Service.Channels.List("snippet,statistics,brandingSettings");
request.Id = id;
if (content.Items.Count == 4)
content.Items.RemoveAt(3);
ChannelsResource.ListRequest infoRequest = SecretsVault.Service.Channels.List("snippet,statistics,brandingSettings");
infoRequest.Id = channelId = id;
item = (await request.ExecuteAsync()).Items[0];
item = (await infoRequest.ExecuteAsync()).Items[0];
title.Text = item.Snippet.Title;
title.Text = collapsedTitle.Text = item.Snippet.Title;
subscribers.Text = $"{item.Statistics.SubscriberCount:0,0} {resources.GetString("/Cards/subscribers")}";
videosCount.Text = $"{item.Statistics.VideoCount:0,0} {resources.GetString("/Cards/videos")}";
try
{
if (item.BrandingSettings.Image.BannerImageUrl.Contains("default"))
throw new Exception("Default channel cover detected");
channelCover.ImageSource = new BitmapImage(new Uri(item.BrandingSettings.Image.BannerImageUrl));
}
catch { }
try { avatar.ProfilePicture = new BitmapImage(new Uri(item.Snippet.Thumbnails.Medium.Url)); }
if (!item.BrandingSettings.Image.BannerImageUrl.Contains("default"))
try { channelCover.Source = new BitmapImage(new Uri(item.BrandingSettings.Image.BannerImageUrl)); }
catch { }
try { avatar.ProfilePicture = collapsedAvatar.ProfilePicture = new BitmapImage(new Uri(item.Snippet.Thumbnails.Medium.Url)); }
catch { }
Methods.FormatText(ref description, item.Snippet.Description);
videoRequest = SecretsVault.Service.Search.List("id");
videoRequest.ChannelId = id;
videoRequest.Type = "video";
videoRequest.Order = SearchResource.ListRequest.OrderEnum.Date;
videoRequest.MaxResults = 25;
request = SecretsVault.Service.Search.List("id");
request.ChannelId = id;
request.Type = "video";
request.Order = SearchResource.ListRequest.OrderEnum.Date;
request.MaxResults = 25;
SearchListResponse response = await videoRequest.ExecuteAsync();
videoList.Clear();
SearchListResponse response = await request.ExecuteAsync();
foreach (SearchResult i in response.Items)
{
VideoCard card = new VideoCard(i.Id.VideoId);
videoList.Add(card);
}
videoList.Add(new VideoCard(i.Id.VideoId));
if (!string.IsNullOrWhiteSpace(response.NextPageToken))
videoToken = response.NextPageToken;
else
videoMore.Complete(true);
videoMore.Visibility = Visibility.Collapsed;
if (SecretsVault.IsAuthorized)
{
@@ -123,6 +97,7 @@ namespace FoxTube.Pages
subscribe.Background = new SolidColorBrush(Colors.Transparent);
subscribe.Foreground = new SolidColorBrush(Colors.Gray);
subscribe.Content = resources.GetString("/Cards/unsubscribe");
collapsedBtn.Visibility = Visibility.Collapsed;
}
subscriptionPane.Visibility = Visibility.Visible;
}
@@ -150,21 +125,13 @@ namespace FoxTube.Pages
try
{
playlistLoading.Refresh();
request.Type = "playlist";
playlistRequest = SecretsVault.Service.Search.List("id");
playlistRequest.ChannelId = channelId;
playlistRequest.Order = SearchResource.ListRequest.OrderEnum.Date;
playlistRequest.Type = "playlist";
playlistRequest.MaxResults = 25;
SearchListResponse response = await playlistRequest.ExecuteAsync();
playlistList.Clear();
SearchListResponse response = await request.ExecuteAsync();
foreach (SearchResult i in response.Items)
{
PlaylistCard card = new PlaylistCard(i.Id.PlaylistId);
playlistList.Add(card);
}
playlistList.Add(new PlaylistCard(i.Id.PlaylistId));
if (!string.IsNullOrWhiteSpace(response.NextPageToken))
playlistToken = response.NextPageToken;
@@ -178,9 +145,15 @@ namespace FoxTube.Pages
{
playlistLoading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch
catch (Exception e)
{
playlistLoading.Error();
playlistLoading.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("Channel playlists list loading error", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message },
{ "Channel ID", channelId }
});
}
}
@@ -192,42 +165,38 @@ namespace FoxTube.Pages
private async void ShowMorePlaylists_Click()
{
playlistRequest.PageToken = playlistToken;
SearchListResponse response = await playlistRequest.ExecuteAsync();
request.Type = "playlist";
request.PageToken = playlistToken;
SearchListResponse response = await request.ExecuteAsync();
foreach (SearchResult i in response.Items)
{
PlaylistCard card = new PlaylistCard(i.Id.PlaylistId);
playlistList.Add(card);
}
playlistList.Add(new PlaylistCard(i.Id.PlaylistId));
if (!string.IsNullOrWhiteSpace(response.NextPageToken))
if (string.IsNullOrWhiteSpace(response.NextPageToken))
playlistMore.Visibility = Visibility.Collapsed;
else
{
playlistToken = response.NextPageToken;
playlistMore.Complete();
}
else
playlistMore.Complete(true);
}
private async void VideoMore_Clicked()
{
videoRequest.PageToken = videoToken;
SearchListResponse response = await videoRequest.ExecuteAsync();
request.Type = "video";
request.PageToken = videoToken;
SearchListResponse response = await request.ExecuteAsync();
foreach (SearchResult i in response.Items)
{
VideoCard card = new VideoCard(i.Id.VideoId);
videoList.Add(card);
}
videoList.Add(new VideoCard(i.Id.VideoId));
if (!string.IsNullOrWhiteSpace(response.NextPageToken))
if (string.IsNullOrWhiteSpace(response.NextPageToken))
videoMore.Visibility = Visibility.Collapsed;
else
{
videoToken = response.NextPageToken;
videoMore.Complete();
}
else
videoMore.Complete(true);
}
private async void Subscribe_Click(object sender, RoutedEventArgs e)
@@ -237,12 +206,14 @@ namespace FoxTube.Pages
subscribe.Background = new SolidColorBrush(Colors.Transparent);
subscribe.Foreground = new SolidColorBrush(Colors.Gray);
subscribe.Content = resources.GetString("/Cards/unsubscribe");
collapsedBtn.Visibility = Visibility.Collapsed;
}
else
{
subscribe.Background = new SolidColorBrush(Colors.Red);
subscribe.Foreground = new SolidColorBrush(Colors.White);
subscribe.Content = resources.GetString("/Cards/subscribe/Content");
collapsedBtn.Visibility = Visibility.Visible;
}
}
@@ -255,21 +226,29 @@ namespace FoxTube.Pages
{
if(search.Text.Length > 2)
{
if (content.Items.Count == 4)
((content.Items[3] as PivotItem).Content as Frame).Navigate(typeof(Search), new SearchParameters(search.Text, item.Id));
else
{
if(content.Items.Count < 4)
content.Items.Add(new PivotItem()
{
Content = new Frame()
Header = resources.GetString("/Channel/searchHeader"),
Content = new Search()
});
((content.Items[3] as PivotItem).Content as Frame).Navigate(typeof(Search), new SearchParameters(search.Text, item.Id));
}
((content.Items[3] as PivotItem).Content as Search).Initialize(new SearchParameters(search.Text, item.Id));
content.SelectedIndex = 3;
}
}
private void ScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
Rect panel = infoPanel.TransformToVisual(videoScroll).TransformBounds(new Rect(0.0, 0.0, infoPanel.ActualWidth, infoPanel.ActualHeight));
Rect view = new Rect(0.0, 0.0, videoScroll.ActualWidth, videoScroll.ActualHeight);
if (view.Contains(new Point(panel.Left, panel.Bottom)))
ColapsedHeader.Opacity = 0;
else
ColapsedHeader.Opacity = 1;
}
private void Refresh_Click(object sender, RoutedEventArgs e)
{
Methods.MainPage.GoToChannel(channelId);
@@ -277,10 +256,7 @@ namespace FoxTube.Pages
private async void InBrowser_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(item.Snippet.CustomUrl))
await Launcher.LaunchUriAsync(new Uri($"https://www.youtube.com/user/{item.Snippet.CustomUrl}"));
else
await Launcher.LaunchUriAsync(new Uri($"https://www.youtube.com/channel/{item.Id}"));
await Launcher.LaunchUriAsync(new Uri($"https://www.youtube.com/channel/{item.Id}"));
}
private void Share_Click(object sender, RoutedEventArgs e)
@@ -288,6 +264,11 @@ namespace FoxTube.Pages
DataTransferManager.ShowShareUI();
}
private void ChannelCover_ImageOpened(object sender, RoutedEventArgs e)
{
channelCover.Opacity = 1;
}
private void Share(DataTransferManager sender, DataRequestedEventArgs args)
{
Methods.Share(args,
+9 -3
View File
@@ -4,6 +4,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:FoxTube.Controls"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
@@ -43,9 +44,14 @@
<ScrollViewer Grid.Row="1" Name="scroll">
<StackPanel>
<StackPanel Name="placeholder"/>
<HyperlinkButton x:Uid="/CommentsPage/more" Visibility="Collapsed" Name="more" Click="more_Click" HorizontalAlignment="Center" Foreground="Red" Content="Show more"/>
<ProgressBar Name="moreLoading" Visibility="Collapsed" IsIndeterminate="True" Foreground="Red"/>
<StackPanel Name="placeholder">
<StackPanel.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition IsStaggeringEnabled="True"/>
</TransitionCollection>
</StackPanel.ChildrenTransitions>
</StackPanel>
<controls:ShowMore x:Name="more" Clicked="ShowMore_Clicked"/>
</StackPanel>
</ScrollViewer>
</Grid>
+92 -101
View File
@@ -17,9 +17,10 @@ namespace FoxTube.Pages
ResourceLoader resources = ResourceLoader.GetForCurrentView("CommentsPage");
string threadId;
string nextPageToken;
string token;
CommentThreadsResource.ListRequest.OrderEnum order = CommentThreadsResource.ListRequest.OrderEnum.Relevance;
CommentThreadsResource.ListRequest request;
public CommentsPage()
{
@@ -33,144 +34,134 @@ namespace FoxTube.Pages
if (!SecretsVault.IsAuthorized)
grid.RowDefinitions[0].Height = new GridLength(0);
else
grid.RowDefinitions[0].Height = GridLength.Auto;
counter.Text = $"{video.Statistics.CommentCount:0,0} {resources.GetString("/CommentsPage/comments")}";
orderBtn.Content = resources.GetString("/CommentsPage/relevance/Text");
var request = SecretsVault.Service.CommentThreads.List("snippet,replies");
request = SecretsVault.Service.CommentThreads.List("snippet,replies");
request.MaxResults = 25;
request.Order = order;
request.VideoId = video.Id;
request.TextFormat = CommentThreadsResource.ListRequest.TextFormatEnum.PlainText;
var response = await request.ExecuteAsync();
if(response.NextPageToken != null)
{
nextPageToken = response.NextPageToken;
more.Visibility = Visibility.Visible;
}
token = response.NextPageToken;
if (string.IsNullOrWhiteSpace(token))
more.Visibility = Visibility.Collapsed;
foreach (CommentThread comment in response.Items)
placeholder.Children.Add(new CommentCard(comment));
}
public void RemoveComment(CommentCard commentCard)
public void RemoveComment(CommentCard commentCard, string topCommentId = null)
{
placeholder.Children.Remove(commentCard);
}
private async void more_Click(object sender, RoutedEventArgs e)
{
more.Visibility = Visibility.Collapsed;
moreLoading.Visibility = Visibility.Visible;
var request = SecretsVault.Service.CommentThreads.List("snippet,replies");
request.Order = order;
request.VideoId = threadId;
request.TextFormat = CommentThreadsResource.ListRequest.TextFormatEnum.PlainText;
request.PageToken = nextPageToken;
var response = await request.ExecuteAsync();
foreach (CommentThread comment in response.Items)
placeholder.Children.Add(new CommentCard(comment));
if(response.NextPageToken != null)
{
nextPageToken = response.NextPageToken;
more.Visibility = Visibility.Visible;
}
moreLoading.Visibility = Visibility.Collapsed;
if (string.IsNullOrWhiteSpace(topCommentId))
placeholder.Children.Remove(commentCard);
else
(placeholder.Children.Find(i => (i as CommentCard).thread.Id == topCommentId) as CommentCard).DeleteComment(commentCard);
}
private async void toRelevance_Click(object sender, RoutedEventArgs e)
{
if(order != CommentThreadsResource.ListRequest.OrderEnum.Relevance)
{
if (order == CommentThreadsResource.ListRequest.OrderEnum.Relevance)
return;
more.Show();
order = CommentThreadsResource.ListRequest.OrderEnum.Relevance;
orderBtn.Content = resources.GetString("/CommentsPage/relevance/Text");
placeholder.Children.Clear();
request.Order = order;
var response = await request.ExecuteAsync();
token = response.NextPageToken;
if (string.IsNullOrWhiteSpace(token))
more.Visibility = Visibility.Collapsed;
moreLoading.Visibility = Visibility.Visible;
order = CommentThreadsResource.ListRequest.OrderEnum.Relevance;
orderBtn.Content = resources.GetString("/CommentsPage/relevance/Text");
foreach (CommentThread comment in response.Items)
placeholder.Children.Add(new CommentCard(comment));
placeholder.Children.Clear();
var request = SecretsVault.Service.CommentThreads.List("snippet,replies");
request.Order = order;
request.VideoId = threadId;
request.TextFormat = CommentThreadsResource.ListRequest.TextFormatEnum.PlainText;
var response = await request.ExecuteAsync();
nextPageToken = response.NextPageToken;
foreach (CommentThread comment in response.Items)
placeholder.Children.Add(new CommentCard(comment));
more.Visibility = Visibility.Visible;
moreLoading.Visibility = Visibility.Collapsed;
}
more.Complete();
}
private async void toDate_Click(object sender, RoutedEventArgs e)
{
if (order != CommentThreadsResource.ListRequest.OrderEnum.Time)
{
if (order == CommentThreadsResource.ListRequest.OrderEnum.Time)
return;
more.Show();
order = CommentThreadsResource.ListRequest.OrderEnum.Time;
orderBtn.Content = resources.GetString("/CommentsPage/publish");
placeholder.Children.Clear();
request.Order = order;
var response = await request.ExecuteAsync();
token = response.NextPageToken;
if (string.IsNullOrWhiteSpace(token))
more.Visibility = Visibility.Collapsed;
moreLoading.Visibility = Visibility.Visible;
order = CommentThreadsResource.ListRequest.OrderEnum.Time;
orderBtn.Content = resources.GetString("/CommentsPage/publish");
foreach (CommentThread comment in response.Items)
placeholder.Children.Add(new CommentCard(comment));
placeholder.Children.Clear();
var request = SecretsVault.Service.CommentThreads.List("snippet,replies");
request.Order = order;
request.VideoId = threadId;
request.TextFormat = CommentThreadsResource.ListRequest.TextFormatEnum.PlainText;
var response = await request.ExecuteAsync();
nextPageToken = response.NextPageToken;
foreach (CommentThread comment in response.Items)
placeholder.Children.Add(new CommentCard(comment));
more.Visibility = Visibility.Visible;
moreLoading.Visibility = Visibility.Collapsed;
}
more.Complete();
}
private async void send_Click(object sender, RoutedEventArgs e)
{
if(newComment.Text.Length > 0)
if (string.IsNullOrWhiteSpace(newComment.Text))
return;
newComment.IsEnabled = false;
send.IsEnabled = false;
sending.Visibility = Visibility.Visible;
CommentThread thread = new CommentThread()
{
newComment.IsEnabled = false;
send.IsEnabled = false;
sending.Visibility = Visibility.Visible;
CommentThread thread = new CommentThread();
thread.Snippet = new CommentThreadSnippet();
Comment comment = new Comment();
comment.Snippet = new CommentSnippet();
comment.Snippet.TextOriginal = newComment.Text;
thread.Snippet.VideoId = threadId;
thread.Snippet.TopLevelComment = comment;
try
Snippet = new CommentThreadSnippet()
{
CommentThread response = await SecretsVault.Service.CommentThreads.Insert(thread, "snippet").ExecuteAsync();
newComment.Text = "";
placeholder.Children.Insert(0, new CommentCard(response));
scroll.ChangeView(null, 0, null);
}
catch
{
await new MessageDialog("Failed to publish your comment. Please, try again later.", "Failed to publish your comment").ShowAsync();
TopLevelComment = new Comment()
{
Snippet = new CommentSnippet()
{
TextOriginal = newComment.Text
}
},
VideoId = threadId
}
};
newComment.IsEnabled = true;
send.IsEnabled = true;
sending.Visibility = Visibility.Collapsed;
try
{
CommentThread response = await SecretsVault.Service.CommentThreads.Insert(thread, "snippet").ExecuteAsync();
placeholder.Children.Insert(0, new CommentCard(response));
newComment.Text = "";
scroll.ChangeView(null, 0, null);
}
catch { await new MessageDialog("Failed to publish your comment. Please, try again later.").ShowAsync(); }
newComment.IsEnabled = true;
send.IsEnabled = true;
sending.Visibility = Visibility.Collapsed;
}
private async void ShowMore_Clicked()
{
request.PageToken = token;
var response = await request.ExecuteAsync();
foreach (CommentThread comment in response.Items)
placeholder.Children.Add(new CommentCard(comment));
token = response.NextPageToken;
more.Complete();
if (string.IsNullOrWhiteSpace(token))
more.Visibility = Visibility.Collapsed;
}
}
}
+11 -4
View File
@@ -22,13 +22,20 @@
<Button Grid.Column="1" x:Uid="/Downloads/openFolder" Content="Open folder" Name="open" Click="Open_Click" VerticalAlignment="Center"/>
</Grid>
<ScrollViewer Grid.Row="1">
<StackPanel Name="stack"/>
</ScrollViewer>
<TextBlock x:Uid="/Downloads/noItems" Grid.Row="1" Name="empty" HorizontalAlignment="Center" VerticalAlignment="Top" FontSize="28" Text="You haven't downloaded anything yet" Margin="10" TextWrapping="WrapWholeWords" Foreground="Gray" FontWeight="SemiBold"/>
<CommandBar DefaultLabelPosition="Right" Grid.Row="2">
<ScrollViewer Grid.Row="1">
<StackPanel Name="list">
<StackPanel.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition IsStaggeringEnabled="True"/>
</TransitionCollection>
</StackPanel.ChildrenTransitions>
</StackPanel>
</ScrollViewer>
<CommandBar Grid.Row="2">
<AppBarButton x:Uid="/Downloads/refresh" Label="Refresh" Icon="Refresh" Click="Refresh"/>
</CommandBar>
</Grid>
+19 -37
View File
@@ -1,8 +1,8 @@
using System;
using FoxTube.Controls;
using System;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace FoxTube.Pages
{
@@ -13,47 +13,29 @@ namespace FoxTube.Pages
{
public Downloads()
{
this.InitializeComponent();
SetPath();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
stack.Children.Clear();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
DownloadAgent.items.ForEach(i =>
{
stack.Children.Add(i);
i.Initialize();
});
empty.Visibility = stack.Children.Count > 0 ? Visibility.Collapsed : Visibility.Visible;
}
void SetPath()
{
InitializeComponent();
DownloadAgent.Page = this;
path.Text = DownloadAgent.Downloads.Path;
}
void Refresh(object sender, RoutedEventArgs e)
{
stack.Children.Clear();
DownloadAgent.items.ForEach(i =>
{
stack.Children.Add(i);
i.Initialize();
});
empty.Visibility = stack.Children.Count > 0 ? Visibility.Collapsed : Visibility.Visible;
Refresh(this, null);
}
private async void Open_Click(object sender, RoutedEventArgs e)
{
await Launcher.LaunchFolderAsync(DownloadAgent.Downloads);
}
private void Refresh(object sender, RoutedEventArgs e)
{
list.Children.Clear();
DownloadAgent.items.ForEach(i => list.Children.Add(i));
empty.Visibility = list.Children.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
}
public void Remove(DownloadItem item)
{
list.Children.Remove(item);
empty.Visibility = list.Children.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
}
}
}
+5 -5
View File
@@ -10,21 +10,21 @@
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Name="grid">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<ScrollViewer>
<StackPanel Name="stack">
<local:VideoGrid/>
<controls:ShowMore Clicked="ShowMore_Clicked"/>
<StackPanel>
<local:VideoGrid x:Name="list"/>
<controls:ShowMore Clicked="ShowMore_Clicked" x:Name="more"/>
</StackPanel>
</ScrollViewer>
<CommandBar Grid.Row="1" DefaultLabelPosition="Right">
<AppBarButton Icon="Refresh" Label="Refresh" Name="refresh" Click="Refresh_Click"/>
<AppBarButton Label="Open in browser" Icon="Globe" Name="toBrowser" Click="toBrowser_Click"/>
</CommandBar>
<foxtube:LoadingPage Visibility="Collapsed" Grid.RowSpan="2"/>
<foxtube:LoadingPage Visibility="Collapsed" Grid.RowSpan="2" x:Name="loading"/>
</Grid>
</Page>
+3 -8
View File
@@ -17,9 +17,6 @@ namespace FoxTube.Pages
List<string> entries;
int page = 1;
public string id = "HL";
LoadingPage loading;
VideoGrid list;
ShowMore more;
public History()
{
@@ -30,10 +27,6 @@ namespace FoxTube.Pages
{
base.OnNavigatedTo(e);
loading = grid.Children[2] as LoadingPage;
list = stack.Children[0] as VideoGrid;
more = stack.Children[1] as ShowMore;
loading.RefreshPage += Refresh_Click;
if (!string.IsNullOrWhiteSpace(e.Parameter.ToString()))
@@ -91,7 +84,9 @@ namespace FoxTube.Pages
list.Add(new VideoCard(entries[k]));
if (list.Count >= entries.Count)
more.Complete(true);
more.Visibility = Visibility.Collapsed;
else
more.Complete();
}
}
}
+55 -25
View File
@@ -9,39 +9,69 @@
xmlns:controls="using:FoxTube.Controls"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Name="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>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Pivot Name="pivot" SelectionChanged="pivot_SelectionChanged">
<PivotItem x:Uid="/Home/recommended" Header="Recommended" Name="recommended">
<ScrollViewer>
<StackPanel>
<pages:VideoGrid/>
<controls:ShowMore Clicked="RecMore_Clicked"/>
<pages:VideoGrid x:Name="recGrid"/>
<controls:ShowMore Clicked="Recommended_More" x:Name="recommendedMore"/>
</StackPanel>
</ScrollViewer>
</PivotItem>
<PivotItem x:Uid="/Home/trending" Header="Trending" Name="trending">
<CommandBar Grid.Row="1">
<AppBarButton Icon="Refresh" Label="Refresh" x:Uid="/Home/refresh" Click="Recommended_Refresh"/>
</CommandBar>
<local:LoadingPage x:Name="recsLoading" Grid.RowSpan="2" RefreshPage="Recommended_Refresh"/>
</Grid>
</PivotItem>
<PivotItem Name="trending" Header="Trending" x:Uid="/Home/trending">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<ScrollViewer>
<StackPanel>
<pages:VideoGrid/>
<controls:ShowMore Clicked="TrendMore_Clicked"/>
<pages:VideoGrid x:Name="trendGrid"/>
<controls:ShowMore Clicked="Trending_More" x:Name="trendingMore"/>
</StackPanel>
</ScrollViewer>
</PivotItem>
<PivotItem x:Uid="/Home/subs" Header="Subscriptions" Name="subscriptions">
<ScrollViewer>
<StackPanel/>
</ScrollViewer>
</PivotItem>
</Pivot>
<CommandBar Grid.Row="1">
<AppBarButton x:Uid="/Home/refresh" Icon="Refresh" Label="Refresh page" Name="refresh" Click="refreshPage"/>
</CommandBar>
<local:LoadingPage Grid.RowSpan="2" Margin="0,50,0,0" Visibility="Visible" RefreshPage="refreshPage"/>
</Grid>
<CommandBar Grid.Row="1">
<AppBarButton Icon="Refresh" Label="Refresh" x:Uid="/Home/refresh" Click="Trends_Refresh"/>
</CommandBar>
<local:LoadingPage x:Name="trendsLoading" Grid.RowSpan="2" RefreshPage="Trends_Refresh"/>
</Grid>
</PivotItem>
<PivotItem Name="subscriptions" Header="Subscriptions" x:Uid="/Home/subs">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<ScrollViewer>
<StackPanel>
<pages:VideoGrid x:Name="subsGrid"/>
<controls:ShowMore Clicked="Subscriptions_More" x:Name="subscriptionsMore"/>
</StackPanel>
</ScrollViewer>
<CommandBar Grid.Row="1">
<AppBarButton Icon="Refresh" Label="Refresh" x:Uid="/Home/refresh" Click="Subscriptions_Refresh"/>
</CommandBar>
<local:LoadingPage x:Name="subsLoading" Grid.RowSpan="2" RefreshPage="Subscriptions_Refresh"/>
</Grid>
</PivotItem>
</Pivot>
</Page>
+142 -113
View File
@@ -5,10 +5,9 @@ using Windows.UI.Xaml.Controls;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using FoxTube.Controls;
using FoxTube.Pages;
using System.Net.Http;
using System.Xml;
using System.Text.RegularExpressions;
using Microsoft.AppCenter.Analytics;
namespace FoxTube
{
@@ -17,137 +16,46 @@ namespace FoxTube
/// </summary>
public sealed partial class Home : Page
{
// TODO: Refactor home page
private bool trendLoaded = false, recLoaded = false;
VideoGrid trendGrid, recGrid;
ShowMore trendMore, recMore;
LoadingPage loading;
string trendToken;
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();
trendGrid = ((trending.Content as ScrollViewer).Content as StackPanel).Children[0] as VideoGrid;
trendMore = ((trending.Content as ScrollViewer).Content as StackPanel).Children[1] as ShowMore;
recGrid = ((recommended.Content as ScrollViewer).Content as StackPanel).Children[0] as VideoGrid;
recMore = ((recommended.Content as ScrollViewer).Content as StackPanel).Children[1] as ShowMore;
loading = grid.Children[2] as LoadingPage;
Initialize();
}
private void refreshPage(object sender, RoutedEventArgs e)
{
Methods.MainPage.GoToHome();
}
private async void TrendMore_Clicked()
{
try
{
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("id");
request.MaxResults = 25;
request.PageToken = trendToken;
request.Chart = VideosResource.ListRequest.ChartEnum.MostPopular;
request.RegionCode = SettingsStorage.Region;
VideoListResponse response = await request.ExecuteAsync();
if (!string.IsNullOrWhiteSpace(response.NextPageToken))
trendToken = response.NextPageToken;
else
trendMore.Complete(true);
foreach (Video vid in response.Items)
{
VideoCard vCard = new VideoCard(vid.Id);
trendGrid.Add(vCard);
}
trendMore.Complete();
}
catch
{
trendMore.Complete(true);
}
}
public void Initialize()
{
if(SecretsVault.IsAuthorized)
LoadRecommendations();
else
if(!SecretsVault.IsAuthorized)
{
pivot.Items.Remove(recommended);
pivot.Items.Remove(subscriptions);
LoadTrending();
}
}
private void pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
loading.Close();
if (pivot.SelectedItem == recommended && !recLoaded)
LoadRecommendations();
else if (pivot.SelectedItem == trending && !trendLoaded)
LoadTrending();
else if (pivot.SelectedItem == subscriptions)
else if (pivot.SelectedItem == subscriptions && !subsLoaded)
LoadSubscriptions();
}
async void LoadTrending()
{
try
{
loading.Refresh();
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("id");
request.MaxResults = 25;
request.Chart = VideosResource.ListRequest.ChartEnum.MostPopular;
request.RegionCode = SettingsStorage.Region;
VideoListResponse response = await request.ExecuteAsync();
if (!string.IsNullOrWhiteSpace(response.NextPageToken))
trendToken = response.NextPageToken;
else
trendMore.Complete(true);
foreach (Video vid in response.Items)
{
VideoCard vCard = new VideoCard(vid.Id);
trendGrid.Add(vCard);
}
loading.Close();
trendLoaded = true;
}
catch (HttpRequestException)
{
trendLoaded = false;
loading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch(Exception e)
{
trendLoaded = false;
loading.Error(e.GetType().ToString(), e.Message);
}
}
#region Initializing tabs
async void LoadRecommendations()
{
try
{
loading.Refresh();
//throw new NotImplementedException("This page has not implemented yet.");
recsLoading.Refresh();
HttpResponseMessage r = await SecretsVault.HttpClient.GetAsync("https://www.youtube.com/list_ajax?style=json&action_get_list=1&list=WL");
string response = await SecretsVault.HttpClient.GetStringAsync("https://youtube.com/");
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]);
@@ -155,39 +63,160 @@ namespace FoxTube
for (int k = 0; k < 25 && k < homeList.Count; k++)
recGrid.Add(new VideoCard(homeList[k]));
loading.Close();
recommendedMore.Visibility = recGrid.Count >= homeList.Count ? Visibility.Collapsed : Visibility.Visible;
recsLoading.Close();
recLoaded = true;
}
catch (HttpRequestException)
{
loading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
recLoaded = false;
recsLoading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch (Exception e)
{
loading.Error(e.GetType().ToString(), e.Message);
recLoaded = false;
recsLoading.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("Failed to load recommendations", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message }
});
}
}
void LoadSubscriptions()
async void LoadTrending()
{
try
{
loading.Refresh();
throw new NotImplementedException("This page isn't not implemented yet.");
trendsLoading.Refresh();
trendsRequest = SecretsVault.Service.Videos.List("snippet,contentDetails,statistics,liveStreamingDetails");
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));
trendsLoading.Close();
trendLoaded = true;
}
catch (HttpRequestException)
{
loading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
trendLoaded = false;
trendsLoading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch(Exception e)
{
trendLoaded = false;
trendsLoading.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("Failed to load trendings", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message }
});
}
}
async void LoadSubscriptions()
{
try
{
subsLoading.Refresh();
await SecretsVault.HttpClient.GetStringAsync("https://www.youtube.com/list_ajax?style=json&action_get_list=1&list=WL");
string response = await SecretsVault.HttpClient.GetStringAsync("https://www.youtube.com/feed/subscriptions");
foreach (Match match in Regex.Matches(response, @"\bdata-context-item-id=(\S*)\b", RegexOptions.IgnoreCase))
subsList.Add(match.Value.Split('"')[1]);
for (int k = 0; k < 25 && k < subsList.Count; k++)
subsGrid.Add(new VideoCard(subsList[k]));
subscriptionsMore.Visibility = subsGrid.Count >= subsList.Count ? Visibility.Collapsed : Visibility.Visible;
subsLoading.Close();
subsLoaded = true;
}
catch (HttpRequestException)
{
subsLoaded = false;
subsLoading.Error("System.Net.Http.HttpRequestException", "Unable to connect to Google servers.", true);
}
catch (Exception e)
{
loading.Error(e.GetType().ToString(), e.Message);
subsLoaded = false;
subsLoading.Error(e.GetType().ToString(), e.Message);
Analytics.TrackEvent("Failed to load subscriptions", new Dictionary<string, string>()
{
{ "Exception", e.GetType().ToString() },
{ "Message", e.Message }
});
}
}
#endregion
private void RecMore_Clicked()
#region More requests
private void Recommended_More()
{
int l = 25 + recGrid.Count;
for (int k = recGrid.Count; k < l && k < homeList.Count; k++)
recGrid.Add(new VideoCard(homeList[k]));
recommendedMore.Visibility = recGrid.Count >= homeList.Count ? Visibility.Collapsed : Visibility.Visible;
recommendedMore.Complete();
}
private async void Trending_More()
{
VideoListResponse response = await trendsRequest.ExecuteAsync();
trendsRequest.PageToken = response.NextPageToken;
trendingMore.Visibility = string.IsNullOrWhiteSpace(response.NextPageToken) ? Visibility.Collapsed : Visibility.Visible;
foreach (Video i in response.Items)
trendGrid.Add(new VideoCard(i));
trendingMore.Complete();
}
private void Subscriptions_More()
{
int l = 25 + subsGrid.Count;
for (int k = subsGrid.Count; k < l && k < subsList.Count; k++)
subsGrid.Add(new VideoCard(subsList[k]));
subscriptionsMore.Visibility = subsGrid.Count >= subsList.Count ? Visibility.Collapsed : Visibility.Visible;
subscriptionsMore.Complete();
}
#endregion
#region Refreshing tabs
private void Recommended_Refresh(object sender, RoutedEventArgs e)
{
recGrid.Clear();
LoadRecommendations();
}
private void Trends_Refresh(object sender, RoutedEventArgs e)
{
trendsRequest = null;
trendGrid.Clear();
LoadTrending();
}
private void Subscriptions_Refresh(object sender, RoutedEventArgs e)
{
subsGrid.Clear();
LoadSubscriptions();
}
#endregion
}
}
+9 -6
View File
@@ -3,13 +3,10 @@
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:mux="using:Microsoft.UI.Xaml.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Windows10version1809="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 7)"
xmlns:Windows10version1803="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 6)"
mc:Ignorable="d"
SizeChanged="Page_SizeChanged"
PreviewKeyUp="Page_PreviewKeyUp">
mc:Ignorable="d">
<Grid Name="grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Windows10version1809:Grid.BackgroundTransition>
@@ -29,7 +26,7 @@
Style="{StaticResource CaptionTextBlockStyle}" />
</Border>
<NavigationView Header="History" SelectedItem="toHome" Windows10version1803:BackRequested="Nav_BackRequested" Windows10version1803:PaneClosing="Nav_PaneChanged" Windows10version1803:PaneOpening="Nav_PaneChanged" Windows10version1803:PaneTitle="FoxTube" OpenPaneLength="300" Name="nav" SelectionChanged="Nav_SelectionChanged">
<NavigationView SelectedItem="toHome" Windows10version1803:BackRequested="Nav_BackRequested" Windows10version1803:PaneClosing="Nav_PaneChanged" Windows10version1803:PaneOpening="Nav_PaneChanged" Windows10version1803:PaneTitle="FoxTube" OpenPaneLength="300" Name="nav" SelectionChanged="Nav_SelectionChanged">
<NavigationView.MenuItemTemplate>
<DataTemplate>
@@ -58,7 +55,13 @@
<NavigationView.PaneFooter>
<NavigationViewList>
<NavigationViewItem Name="openWeb" Tapped="Web_Tapped" Icon="Globe" Content="Browser" Visibility="Visible"/>
<NavigationViewList.ItemContainerTransitions>
<TransitionCollection>
<EntranceThemeTransition IsStaggeringEnabled="True"/>
<ReorderThemeTransition/>
</TransitionCollection>
</NavigationViewList.ItemContainerTransitions>
<NavigationViewItem Name="openWeb" Tapped="Web_Tapped" Icon="Globe" Content="Browser" Visibility="Collapsed"/>
<NavigationViewItem x:Uid="/Main/feedback" Name="feedback" Content="Give a feedback" Tapped="Feedback_Click">
<NavigationViewItem.Icon>
<FontIcon Glyph="&#xED15;"/>
+28 -75
View File
@@ -7,7 +7,6 @@ using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Navigation;
using System.Diagnostics;
using Windows.UI.Notifications;
using Windows.UI.Xaml.Media.Imaging;
using System.Xml;
using Google.Apis.YouTube.v3.Data;
@@ -16,14 +15,9 @@ using Windows.System;
using Google.Apis.Oauth2.v2;
using Google.Apis.Oauth2.v2.Data;
using FoxTube.Pages;
using Windows.ApplicationModel;
using System.Net;
using Windows.UI.Popups;
using Windows.Networking.Connectivity;
using Windows.UI.Core;
using Windows.ApplicationModel.Resources;
using Windows.Storage;
using System.IO;
namespace FoxTube
{
@@ -34,7 +28,6 @@ namespace FoxTube
/// </summary>
public sealed partial class MainPage : Page
{
// TODO: Refactor main page
Sender s = Sender.None;
readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Main");
Dictionary<Type, Action> headers;
@@ -71,12 +64,6 @@ namespace FoxTube
};
}
public Video GetCurrentItem()
{
try { return (videoPlaceholder.Content as VideoPage).item; }
catch { return null; }
}
public string GetPlaylist()
{
try { return (videoPlaceholder.Content as VideoPage).playlistId; }
@@ -90,7 +77,8 @@ namespace FoxTube
titleBar.ButtonBackgroundColor = Colors.Transparent;
titleBar.ButtonHoverBackgroundColor = Colors.IndianRed;
titleBar.ButtonPressedBackgroundColor = Colors.DarkRed;
titleBar.ButtonInactiveBackgroundColor = Colors.Gray;
titleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
titleBar.ButtonInactiveForegroundColor = Colors.Gray;
if(Application.Current.RequestedTheme == ApplicationTheme.Dark)
titleBar.ForegroundColor = Colors.White;
@@ -197,7 +185,11 @@ namespace FoxTube
if(e[0] as bool? != null)
DownloadAgent.Initialize();
content.Navigate(typeof(Home));
s = Sender.None;
if (content.Content != null)
content.Navigate(content.SourcePageType);
else
content.Navigate(typeof(Home));
if (videoPlaceholder.Content != null)
GoToVideo((videoPlaceholder.Content as VideoPage).videoId, (videoPlaceholder.Content as VideoPage).playlistId);
@@ -291,8 +283,9 @@ namespace FoxTube
catch { }
if (videoPlaceholder.Content != null)
(videoPlaceholder.Content as VideoPage).player.close_Click(this, null);
(videoPlaceholder.Content as VideoPage).CloseVideo();
nav.IsBackEnabled = true;
nav.ExpandedModeThresholdWidth = short.MaxValue;
nav.IsPaneOpen = false;
@@ -301,7 +294,7 @@ namespace FoxTube
public void GoToDeveloper(string id)
{
content.Navigate(typeof(Settings), $"inbox&{id}");
content.Navigate(typeof(Settings), id);
}
public void GoToPlaylist(string id)
@@ -314,22 +307,18 @@ namespace FoxTube
content.Navigate(typeof(Downloads));
}
private void Page_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (videoPlaceholder.Content != null)
(videoPlaceholder.Content as VideoPage).player.UpdateSize();
}
public void MinimizeAsInitializer()
{
if (videoPlaceholder.Content != null)
return;
if ((videoPlaceholder.Content as VideoPage).loading.State != LoadingState.Loaded)
if ((videoPlaceholder.Content as VideoPage).LoadingPage.State != LoadingState.Loaded)
CloseVideo();
else
try { headers[content.SourcePageType](); }
catch { }
(videoPlaceholder.Content as VideoPage).Player.Controls_MiniModeChanged(this, true);
try { headers[content.SourcePageType](); }
catch { }
}
public void MinimizeVideo()
@@ -356,8 +345,7 @@ namespace FoxTube
if (content.SourcePageType == typeof(Home) || content.SourcePageType == typeof(Settings) || content.SourcePageType == typeof(Subscriptions))
{
nav.ExpandedModeThresholdWidth = 1008;
if (nav.DisplayMode == NavigationViewDisplayMode.Expanded)
nav.IsPaneOpen = true;
nav.IsPaneOpen = nav.DisplayMode == NavigationViewDisplayMode.Expanded ? true : false;
}
else
nav.ExpandedModeThresholdWidth = short.MaxValue;
@@ -371,7 +359,7 @@ namespace FoxTube
videoPlaceholder.HorizontalAlignment = HorizontalAlignment.Stretch;
videoPlaceholder.Margin = new Thickness(0);
if (videoPlaceholder.Content != null)
if (videoPlaceholder.Content == null)
return;
nav.IsBackEnabled = true;
@@ -380,41 +368,13 @@ namespace FoxTube
nav.IsPaneOpen = false;
}
public void Fullscreen(bool on)
{
if (on)
{
nav.CompactModeThresholdWidth = short.MaxValue;
nav.ExpandedModeThresholdWidth = short.MaxValue;
try { nav.IsPaneVisible = false; }
catch { nav.CompactPaneLength = 0; }
nav.Margin = new Thickness(0, -45, 0, 0);
}
else
{
nav.CompactModeThresholdWidth = 641;
if (videoPlaceholder.Content == null)
SetNavigationMenu();
try { nav.IsPaneVisible = true; }
catch { nav.CompactPaneLength = new NavigationView().CompactPaneLength; }
nav.Margin = new Thickness(0);
if (videoPlaceholder.Content != null && nav.IsPaneOpen)
nav.IsPaneOpen = false;
}
}
public void CloseVideo()
{
if (ApplicationView.GetForCurrentView().IsFullScreenMode)
{
ApplicationView.GetForCurrentView().ExitFullScreenMode();
Fullscreen(false);
}
videoPlaceholder.Content = null;
GC.Collect();
Window.Current.CoreWindow.PointerCursor = new CoreCursor(CoreCursorType.Arrow, 0);
MaximizeVideo();
if (content.CanGoBack)
@@ -492,15 +452,9 @@ namespace FoxTube
else if(e.SourcePageType == typeof(History))
{
if (e.Parameter.ToString() == "HL")
{
SetNavigationItem(toHistory);
nav.Header = resources.GetString("/Main/history/Content");
}
else
{
SetNavigationItem(toLater);
nav.Header = resources.GetString("/Main/later/Content");
}
}
else if(e.SourcePageType == typeof(PlaylistPage))
{
@@ -511,6 +465,14 @@ namespace FoxTube
else
s = Sender.None;
if(e.SourcePageType == typeof(History))
{
if(e.Parameter.ToString() == "HL")
nav.Header = resources.GetString("/Main/history/Content");
else if(e.Parameter.ToString() == "WL")
nav.Header = resources.GetString("/Main/later/Content");
}
if (content.CanGoBack)
nav.IsBackEnabled = true;
else
@@ -522,15 +484,6 @@ namespace FoxTube
MinimizeAsInitializer();
}
private void Page_PreviewKeyUp(object sender, KeyRoutedEventArgs e)
{
if(videoPlaceholder.Content != null && FocusManager.GetFocusedElement().GetType() != typeof(TextBox))
{
e.Handled = true;
(videoPlaceholder.Content as VideoPage).player.KeyUpPressed(sender, e);
}
}
private void OpenContext(object sender, TappedRoutedEventArgs e)
{
((NavigationViewItem)sender).ContextFlyout.ShowAt((NavigationViewItem)sender);
@@ -543,7 +496,7 @@ namespace FoxTube
private void Web_Tapped(object sender, TappedRoutedEventArgs e)
{
content.Navigate(typeof(Browser));
content.Navigate(typeof(Home1));
}
private void Nav_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
@@ -585,7 +538,7 @@ namespace FoxTube
{
if (videoPlaceholder.Content != null)
{
if ((videoPlaceholder.Content as VideoPage).loading.State != LoadingState.Loaded)
if ((videoPlaceholder.Content as VideoPage).LoadingPage.State != LoadingState.Loaded)
CloseVideo();
else if (videoPlaceholder.HorizontalAlignment == HorizontalAlignment.Stretch)
MinimizeAsInitializer();
+7 -4
View File
@@ -1,5 +1,4 @@
<Page
NavigationCacheMode="Enabled"
x:Class="FoxTube.Pages.PlaylistPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -7,10 +6,11 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:foxtube="using:FoxTube"
xmlns:controls="using:FoxTube.Controls"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Name="grid">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
@@ -57,7 +57,10 @@
</Button>
</StackPanel>
<local:VideoGrid Grid.Column="1" Grid.Row="1"/>
<StackPanel Grid.Column="1" Grid.Row="1">
<local:VideoGrid x:Name="list"/>
<controls:ShowMore x:Name="more" Clicked="ShowMore_Clicked"/>
</StackPanel>
</Grid>
</ScrollViewer>
@@ -74,6 +77,6 @@
<AppBarButton x:Uid="/Playlist/share" Icon="Share" Label="Share" Name="share" Click="share_Click"/>
</CommandBar>
<foxtube:LoadingPage Grid.RowSpan="2" Visibility="Collapsed"/>
<foxtube:LoadingPage Grid.RowSpan="2" Visibility="Collapsed" RefreshPage="refresh_Click" x:Name="loading"/>
</Grid>
</Page>
+36 -25
View File
@@ -23,15 +23,13 @@ namespace FoxTube.Pages
public string playlistId;
Playlist item;
LoadingPage loading;
VideoGrid list;
PlaylistItemsResource.ListRequest request;
string token;
public PlaylistPage()
{
InitializeComponent();
loading = grid.Children[2] as LoadingPage;
list = ((grid.Children[0] as ScrollViewer).Content as Grid).Children[1] as VideoGrid;
loading.RefreshPage += refresh_Click;
DataTransferManager.GetForCurrentView().DataRequested += new TypedEventHandler<DataTransferManager, DataRequestedEventArgs>(Share);
}
@@ -52,42 +50,38 @@ namespace FoxTube.Pages
{
playlistId = id;
PlaylistsResource.ListRequest request = SecretsVault.Service.Playlists.List("snippet,contentDetails");
request.Id = id;
PlaylistsResource.ListRequest infoRequest = SecretsVault.Service.Playlists.List("snippet,contentDetails");
infoRequest.Id = id;
item = (await request.ExecuteAsync()).Items[0];
item = (await infoRequest.ExecuteAsync()).Items[0];
title.Text = item.Snippet.Title;
info.Text = $"{item.ContentDetails.ItemCount} {ResourceLoader.GetForCurrentView("Playlist").GetString("/Playlist/videos")}";
description.Text = item.Snippet.Description;
channelName.Text = item.Snippet.ChannelTitle;
thumbnail.Source = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri());
ChannelsResource.ListRequest channelRequest = SecretsVault.Service.Channels.List("snippet");
channelRequest.Id = item.Snippet.ChannelId;
Channel channel = (await channelRequest.ExecuteAsync()).Items[0];
avatar.ProfilePicture = new BitmapImage(channel.Snippet.Thumbnails.Medium.Url.ToUri());
PlaylistItemsResource.ListRequest listRequest = SecretsVault.Service.PlaylistItems.List("contentDetails");
listRequest.PlaylistId = id;
listRequest.MaxResults = 50;
try { avatar.ProfilePicture = new BitmapImage(channel.Snippet.Thumbnails.Medium.Url.ToUri()); }
catch { }
try { thumbnail.Source = new BitmapImage(item.Snippet.Thumbnails.Medium.Url.ToUri()); }
catch { }
PlaylistItemListResponse response = await listRequest.ExecuteAsync();
request = SecretsVault.Service.PlaylistItems.List("contentDetails");
request.PlaylistId = id;
request.MaxResults = 25;
list.Clear();
PlaylistItemListResponse response = await request.ExecuteAsync();
token = response.NextPageToken;
if (string.IsNullOrWhiteSpace(token))
more.Visibility = Visibility.Collapsed;
foreach (PlaylistItem i in response.Items)
list.Add(new VideoCard(i.ContentDetails.VideoId, playlistId));
while (response.NextPageToken != null)
{
listRequest.PageToken = response.NextPageToken;
response = await listRequest.ExecuteAsync();
foreach (PlaylistItem i in response.Items)
list.Add(new VideoCard(i.ContentDetails.VideoId, playlistId));
}
loading.Close();
}
catch (System.Net.Http.HttpRequestException)
@@ -134,5 +128,22 @@ namespace FoxTube.Pages
$"https://www.youtube.com/playlist?list={item.Id}",
ResourceLoader.GetForCurrentView("Cards").GetString("/Cards/playlistShare"));
}
private async void ShowMore_Clicked()
{
request.PageToken = token;
PlaylistItemListResponse response = await request.ExecuteAsync();
if (string.IsNullOrWhiteSpace(request.PageToken))
more.Visibility = Visibility.Collapsed;
else
{
token = response.NextPageToken;
more.Complete();
}
foreach (PlaylistItem i in response.Items)
list.Add(new VideoCard(i.ContentDetails.VideoId, playlistId));
}
}
}
+3 -3
View File
@@ -67,8 +67,8 @@
<Button x:Uid="/Search/apply" Content="Apply" Margin="10,0,0,10" Name="apply" Click="Apply_Click"/>
</StackPanel>
</StackPanel>
<pages:VideoGrid/>
<controls:ShowMore Clicked="More_Clicked"/>
<pages:VideoGrid x:Name="list"/>
<controls:ShowMore x:Name="more" Clicked="More_Clicked"/>
</StackPanel>
</ScrollViewer>
@@ -77,6 +77,6 @@
<AppBarButton Label="Refresh" Icon="Refresh" Click="AppBarButton_Click"/>
</CommandBar>
<local:LoadingPage Grid.RowSpan="2" Visibility="Collapsed" RefreshPage="AppBarButton_Click"/>
<local:LoadingPage x:Name="loading" Grid.RowSpan="2" Visibility="Collapsed" RefreshPage="AppBarButton_Click"/>
</Grid>
</Page>
+4 -10
View File
@@ -24,16 +24,9 @@ namespace FoxTube
SearchResource.ListRequest request;
string nextToken;
readonly VideoGrid list;
readonly LoadingPage loading;
readonly ShowMore more;
public Search()
{
InitializeComponent();
loading = grid.Children[2] as LoadingPage;
list = ((grid.Children[0] as ScrollViewer).Content as StackPanel).Children[4] as VideoGrid;
more = ((grid.Children[0] as ScrollViewer).Content as StackPanel).Children[5] as ShowMore;
}
public string SetResults(int? count)
@@ -105,10 +98,11 @@ namespace FoxTube
request.Order = (SearchResource.ListRequest.OrderEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.Order);
request.Type = (string)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.Type);
request.PublishedAfter = (DateTime)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.Date);
request.PublishedAfter = (DateTime?)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.Date);
if (Parameters.Filter.Type == SearchParameters.Filters.Enumerations.Type.Video)
{
request.VideoDuration = (SearchResource.ListRequest.VideoDurationEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.Duration);
request.VideoDefinition = (SearchResource.ListRequest.VideoDefinitionEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.HD);
request.VideoDimension = (SearchResource.ListRequest.VideoDimensionEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.ThreeD);
request.VideoCaption = (SearchResource.ListRequest.VideoCaptionEnum)arg.Filter.GetParameter(SearchParameters.Filters.Enumerations.ConversionType.Captions);
@@ -136,7 +130,7 @@ namespace FoxTube
if (!string.IsNullOrWhiteSpace(response.NextPageToken))
nextToken = response.NextPageToken;
else
more.Complete(true);
more.Visibility = Visibility.Collapsed;
list.Clear();
foreach (SearchResult item in response.Items)
@@ -192,7 +186,7 @@ namespace FoxTube
more.Complete();
}
else
more.Complete(true);
more.Visibility = Visibility.Collapsed;
}
private void Type_SelectionChanged(object sender, SelectionChangedEventArgs e)
+2 -8
View File
@@ -21,14 +21,8 @@ namespace FoxTube
base.OnNavigatedTo(e);
if(!string.IsNullOrWhiteSpace(e.Parameter as string))
{
if ((e.Parameter as string).Contains("inbox") || (e.Parameter as string).Contains("changelog"))
{
if ((string)e.Parameter != "inbox" && (string)e.Parameter != "changelog")
inboxId = e.Parameter as string;
pivot.SelectedIndex = 2;
}
else if ((e.Parameter as string) == "about")
pivot.SelectedIndex = 1;
inboxId = e.Parameter as string;
pivot.SelectedIndex = 2;
}
}
+34 -35
View File
@@ -52,42 +52,41 @@
<ColumnDefinition/>
<ColumnDefinition Width="0"/>
</Grid.ColumnDefinitions>
<ScrollViewer Background="{ThemeResource SystemControlBackgroundChromeMediumLowBrush}">
<StackPanel VerticalAlignment="Stretch">
<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 Opacity=".5" Text="{Binding Path=Subtitle}"/>
<TextBlock Opacity=".5" FontSize="13" Text="{Binding Path=TimeStamp}" TextWrapping="WrapWholeWords"/>
</StackPanel>
<StackPanel VerticalAlignment="Stretch" Background="{StaticResource SystemControlBackgroundChromeMediumLowBrush}">
<ComboBox x:Uid="/Inbox/filter" Header="Filter" Margin="10" HorizontalAlignment="Stretch" SelectedIndex="0" Name="filter" SelectionChanged="filter_SelectionChanged">
<ComboBoxItem x:Uid="/Inbox/all" Content="All"/>
<ComboBoxItem x:Uid="/Inbox/messages" Content="Messages"/>
<ComboBoxItem x:Uid="/Inbox/changelogs" Content="Patch notes"/>
</ComboBox>
<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>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</ScrollViewer>
<StackPanel Grid.Column="1">
<TextBlock FontWeight="Bold" Text="{Binding Path=Title}" MaxLines="1" TextWrapping="Wrap"/>
<TextBlock Opacity=".5" Text="{Binding Path=Subtitle}"/>
<TextBlock Opacity=".5" FontSize="13" Text="{Binding Path=TimeStampString}" TextWrapping="WrapWholeWords"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<ScrollViewer Grid.Column="1">
<StackPanel Margin="10">
+15 -3
View File
@@ -16,6 +16,8 @@ namespace FoxTube.Pages.SettingsPages
public sealed partial class Inbox : Page
{
List<InboxItem> items = new List<InboxItem>();
string open;
public Inbox()
{
InitializeComponent();
@@ -38,7 +40,7 @@ namespace FoxTube.Pages.SettingsPages
doc.Load("http://foxgame-studio.000webhostapp.com/foxtube-messages.xml");
foreach (XmlElement e in doc["posts"].ChildNodes)
items.Add(new InboxItem(
e["header"].InnerText,
e["header"][SettingsStorage.Language].InnerText,
e["content"][SettingsStorage.Language].InnerText,
DateTime.Parse(e.GetAttribute("time")),
e["id"].InnerText
@@ -49,6 +51,11 @@ namespace FoxTube.Pages.SettingsPages
catch { }
items.ForEach(i => list.Items.Add(i));
if (!string.IsNullOrWhiteSpace(open))
Open(open);
open = null;
}
private void filter_SelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -118,8 +125,13 @@ namespace FoxTube.Pages.SettingsPages
public void Open(string arg)
{
string id = arg.Split('&')[1];
InboxItem item = items.Find(x => x.Id == id);
if(items.Count == 0)
{
open = arg;
return;
}
InboxItem item = items.Find(i => i.Id == arg);
if(item != null)
list.SelectedItem = item;
}
+1 -7
View File
@@ -5,17 +5,11 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:controls="using:FoxTube.Controls"
x:Name="root"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<controls:Advert/>
<ui:AdaptiveGridView Name="list" OneRowModeEnabled="False" DesiredWidth="384" SelectionMode="None" Grid.Row="1">
<ui:AdaptiveGridView Name="list" DesiredWidth="400" SelectionMode="None" HorizontalContentAlignment="Left">
<ui:AdaptiveGridView.ItemContainerTransitions>
<TransitionCollection>
<EntranceThemeTransition IsStaggeringEnabled="True"/>
+5 -11
View File
@@ -1,17 +1,11 @@
using Windows.UI.Xaml;
using FoxTube.Controls;
using System;
using System.Collections.Generic;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace FoxTube
{
public interface IItemCard
{
}
}
namespace FoxTube.Pages
{
/// <summary>
/// Items cards container
/// </summary>
@@ -24,7 +18,7 @@ namespace FoxTube.Pages
InitializeComponent();
}
public void Add(IItemCard card)
public void Add(UIElement card)
{
list.Items.Add(card);
/*if (list.Items.Count % 10 == 0)
+29 -5
View File
@@ -30,12 +30,37 @@
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="400"/>
</Grid.ColumnDefinitions>
<ScrollViewer Margin="0,0,0,50" Name="mainScroll">
<ScrollViewer Margin="0,0,0,50" Name="mainScroll" VerticalScrollBarVisibility="Hidden">
<StackPanel Orientation="Vertical" Name="mainContent">
<local:VideoPlayer/>
<local:VideoPlayer x:Name="player" NextClicked="Player_NextClicked" MiniMode="Player_Minimize"/>
<PivotItem Header="Description" Name="descriptionPanel">
<StackPanel Margin="0,10">
<TextBlock IsTextSelectionEnabled="True" Name="title" Text="[Video title]" FontSize="25" TextWrapping="WrapWholeWords" HorizontalTextAlignment="Start"/>
<Border BorderBrush="Red" BorderThickness="5" CornerRadius="10" Margin="0,10" Name="upcoming" Visibility="Collapsed">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<FontIcon Glyph="&#xE704;" FontSize="50" VerticalAlignment="Center"/>
<TextBlock x:Uid="/VideoPage/upcomingHeader" FontWeight="Bold" FontSize="25" Text="Stream hasn't started yet" Grid.Column="1" Margin="10,0" VerticalAlignment="Center"/>
<StackPanel Grid.Column="2" Margin="10,0" Name="schedule" Visibility="Collapsed">
<TextBlock x:Uid="/VideoPage/scheduleHeader" FontSize="20" FontWeight="SemiBold" Text="Stream schedule:"/>
<TextBlock Name="start" Text="Starts at: 2/15/2019 21:00:00" Visibility="Collapsed"/>
<TextBlock Name="end" Text="Ends at: 2/15/2019 23:00:00" Visibility="Collapsed"/>
</StackPanel>
<StackPanel Grid.Column="3" Margin="10,0" Name="countdownPanel" Visibility="Collapsed">
<TextBlock x:Uid="/VideoPage/countdown" FontSize="20" FontWeight="SemiBold" Text="Stream starts in:"/>
<TextBlock Name="countdown" FontWeight="Bold" FontSize="35" Text="00:12:12"/>
</StackPanel>
</Grid>
</Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@@ -74,7 +99,6 @@
</StackPanel>
</Grid>
<TextBlock Name="description" Text="[Description]" IsTextSelectionEnabled="True" TextWrapping="WrapWholeWords"/>
<TextBlock Margin="0,20" Name="category" Text="Category: "/>
</StackPanel>
</PivotItem>
</StackPanel>
@@ -149,7 +173,7 @@
</ScrollViewer>
</PivotItem>
<PivotItem x:Uid="/VideoPage/comments" Header="Comments" Name="commentsPlaceholder">
<pages:CommentsPage/>
<pages:CommentsPage x:Name="comments"/>
</PivotItem>
<PivotItem x:Uid="/VideoPage/playlist" Header="Playlist" Name="playlist">
<ScrollViewer>
@@ -181,6 +205,6 @@
</Pivot>
</Grid>
<local:LoadingPage Grid.ColumnSpan="2" Visibility="Collapsed"/>
<local:LoadingPage Grid.ColumnSpan="2" Visibility="Collapsed" x:Name="loading" RefreshPage="refresh_Click"/>
</Grid>
</Page>
+61 -39
View File
@@ -1,4 +1,5 @@
using FoxTube.Controls;
using FoxTube.Controls.Adverts;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using Microsoft.AppCenter.Analytics;
@@ -50,26 +51,19 @@ namespace FoxTube.Pages
public string playlistId = null;
public Video item;
bool wide;
bool isExtended = false;
Rating userRating = Rating.None;
public VideoPlayer player;
public CommentsPage comments;
public LoadingPage loading;
DispatcherTimer liveTimer;
DispatcherTimer countdownTimer;
public LoadingPage LoadingPage => loading;
public VideoPlayer Player => player;
public VideoPage()
{
InitializeComponent();
loading = grid.Children[3] as LoadingPage;
loading.RefreshPage += refresh_Click;
player = mainContent.Children[0] as VideoPlayer;
player.SetFullSize += Player_SetFullSize;
player.NextClicked += Player_NextClicked;
comments = commentsPlaceholder.Content as CommentsPage;
DataTransferManager.GetForCurrentView().DataRequested += new TypedEventHandler<DataTransferManager, DataRequestedEventArgs>(Share);
@@ -127,6 +121,9 @@ namespace FoxTube.Pages
else
LoadStream();
if (item.Snippet.LiveBroadcastContent == "upcoming")
SetSchedule();
LoadInfo();
loading.Close();
@@ -147,6 +144,34 @@ namespace FoxTube.Pages
}
}
void SetSchedule()
{
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.ActualEndTime.Value}";
end.Visibility = Visibility.Visible;
}
if (item.LiveStreamingDetails.ScheduledStartTime.HasValue)
{
start.Text = $"{resources.GetString("/VideoPage/start")} {item.LiveStreamingDetails.ActualStartTime.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).ToString(@"hh\:mm\:ss");
if (countdown.Text == "00:00:00")
refresh_Click(this, null);
};
countdownTimer.Start();
}
}
async void LoadPlaylist(string id)
{
playlistId = id;
@@ -200,7 +225,7 @@ namespace FoxTube.Pages
pivot.SelectedItem = playlist;
if (playlistList.SelectedIndex == playlistList.Items.Count - 1)
player.Next.Visibility = Visibility.Collapsed;
player.Controls.IsNextTrackButtonVisible = false;
}
async void LoadInfo()
@@ -223,11 +248,6 @@ namespace FoxTube.Pages
likes.Text = $"{item.Statistics.LikeCount:0,0}";
rating.Value = (double)item.Statistics.DislikeCount / (double)(item.Statistics.DislikeCount + item.Statistics.LikeCount) * 100;
//Setting category
VideoCategoriesResource.ListRequest categoryRequest = SecretsVault.Service.VideoCategories.List("snippet");
categoryRequest.Id = item.Snippet.CategoryId;
category.Text = (await categoryRequest.ExecuteAsync()).Items[0].Snippet.Title;
//Setting User's rate
if (SecretsVault.IsAuthorized)
{
@@ -289,9 +309,8 @@ namespace FoxTube.Pages
{
VideosResource.ListRequest request = SecretsVault.Service.Videos.List("liveStreamingDetails");
request.Id = videoId;
Video video = (await request.ExecuteAsync()).Items[0];
views.Text = $"{video.LiveStreamingDetails.ConcurrentViewers} {resources.GetString("/Cards/viewers")}";
views.Text = $"{(await request.ExecuteAsync()).Items[0].LiveStreamingDetails.ConcurrentViewers} {resources.GetString("/Cards/viewers")}";
}
void LoadStats()
@@ -326,9 +345,9 @@ namespace FoxTube.Pages
audioItem.Click += downloadItemSelected;
downloadSelector.Items.Add(audioItem);
}
catch (Exception e)
catch
{
loading.Error(e.GetType().ToString(), e.Message);
download.Visibility = Visibility.Collapsed;
}
}
@@ -353,29 +372,33 @@ namespace FoxTube.Pages
relatedVideos.Children.Add(new VideoCard(video.Id.VideoId));
}
private void Player_SetFullSize(object sender, params object[] e)
private void Player_Minimize(object sender, params object[] e)
{
if (isExtended == (bool)e[0])
return;
isExtended = (bool)e[0];
if(isExtended)
{
grid.ColumnDefinitions[1].Width = new GridLength(0);
commandbar.Visibility = Visibility.Collapsed;
mainScroll.Margin = new Thickness(0);
mainScroll.ChangeView(0, 0, null);
mainScroll.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden;
mainScroll.Margin = new Thickness(0);
mainScroll.VerticalScrollMode = ScrollMode.Disabled;
Methods.MainPage.MinimizeVideo();
}
else
{
grid.ColumnDefinitions[1].Width = new GridLength(400);
commandbar.Visibility = Visibility.Visible;
mainScroll.Margin = new Thickness(0,0,0,50);
mainScroll.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
mainScroll.VerticalScrollMode = ScrollMode.Auto;
if (wide)
grid.ColumnDefinitions[1].Width = new GridLength(400);
Methods.MainPage.MaximizeVideo();
}
}
@@ -387,8 +410,8 @@ namespace FoxTube.Pages
private async void openBrowser_Click(object sender, RoutedEventArgs e)
{
player.Pause();
string timecode = player.Elapsed.TotalSeconds > 10 ?
"&t=" + (int)player.Elapsed.TotalSeconds + "s" : string.Empty;
string timecode = player.Position.TotalSeconds > 10 ?
"&t=" + (int)player.Position.TotalSeconds + "s" : string.Empty;
await Launcher.LaunchUriAsync($"https://www.youtube.com/watch?v={videoId}{timecode}".ToUri());
}
@@ -398,15 +421,14 @@ namespace FoxTube.Pages
Methods.MainPage.GoToVideo(videoId, playlistId);
}
public void CloseVideo()
{
player.Controls_CloseRequested(this, null);
}
private void grid_SizeChanged(object sender, SizeChangedEventArgs e)
{
Debug.WriteLine(e.NewSize.Width);
if (e.NewSize.Width > 1000)
wide = true;
else
wide = false;
if (e.NewSize.Width > 1000 && mainContent.Children.Contains(pivot) && !isExtended)
if (e.NewSize.Width > 1000 && mainContent.Children.Contains(pivot))
{
mainContent.Children.Remove(pivot);
tabsPlaceholder.Children.Add(pivot);
+3
View File
@@ -156,4 +156,7 @@
<data name="views.Text" xml:space="preserve">
<value>Views:</value>
</data>
<data name="searchHeader" xml:space="preserve">
<value>Search results</value>
</data>
</root>
+3
View File
@@ -120,6 +120,9 @@
<data name="box.PlaceholderText" xml:space="preserve">
<value>Send a message</value>
</data>
<data name="failed" xml:space="preserve">
<value>Failed to send your message. It may be caused by turned on chat slow mode. Please, try again later. P.S. Report has been sent to developers</value>
</data>
<data name="moder.Text" xml:space="preserve">
<value>Moderator</value>
</data>
+4 -1
View File
@@ -138,12 +138,15 @@
<data name="editorCancel.Content" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="editorDelete.Content" xml:space="preserve">
<data name="editorDelete.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Delete comment</value>
</data>
<data name="editorSubmin.Content" xml:space="preserve">
<value>Submit</value>
</data>
<data name="failedDelete" xml:space="preserve">
<value>Failed to delete your commentary. Please, try again later.</value>
</data>
<data name="failedEdit" xml:space="preserve">
<value>Failed to edit your commentary. Please, try again later.</value>
</data>
+6
View File
@@ -138,6 +138,12 @@
<data name="ext" xml:space="preserve">
<value>Extension</value>
</data>
<data name="failedBody" xml:space="preserve">
<value>Bug report has been sent</value>
</data>
<data name="failedHead" xml:space="preserve">
<value>Error occured while dowloading a video</value>
</data>
<data name="gotoOrign.Text" xml:space="preserve">
<value>Go to original</value>
</data>
+45
View File
@@ -120,6 +120,9 @@
<data name="addTo.Label" xml:space="preserve">
<value>Add to</value>
</data>
<data name="always" xml:space="preserve">
<value>Don't ask me again</value>
</data>
<data name="audio" xml:space="preserve">
<value>Audio track</value>
</data>
@@ -129,6 +132,9 @@
<data name="back.Text" xml:space="preserve">
<value>Go back for 10 seconds</value>
</data>
<data name="cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="cast.Text" xml:space="preserve">
<value>Cast to device</value>
</data>
@@ -141,12 +147,18 @@
<data name="comments.Header" xml:space="preserve">
<value>Comments</value>
</data>
<data name="countdown.Text" xml:space="preserve">
<value>Stream starts in:</value>
</data>
<data name="desc" xml:space="preserve">
<value>Description</value>
</data>
<data name="download.Label" xml:space="preserve">
<value>Download video</value>
</data>
<data name="end" xml:space="preserve">
<value>Ends at:</value>
</data>
<data name="exitminiview.Text" xml:space="preserve">
<value>Exit compact view mode</value>
</data>
@@ -156,9 +168,15 @@
<data name="fwd.Text" xml:space="preserve">
<value>Skip forward for 30 seconds</value>
</data>
<data name="generatedCaption" xml:space="preserve">
<value>(Auto-generated)</value>
</data>
<data name="goLive.Text" xml:space="preserve">
<value>Go to live broadcast</value>
</data>
<data name="matureText" xml:space="preserve">
<value>This content may be not apropriate for children under 18</value>
</data>
<data name="maximize.Text" xml:space="preserve">
<value>Maximize</value>
</data>
@@ -174,6 +192,9 @@
<data name="next.Text" xml:space="preserve">
<value>Next video</value>
</data>
<data name="no" xml:space="preserve">
<value>No</value>
</data>
<data name="openWeb.Label" xml:space="preserve">
<value>Open in browser</value>
</data>
@@ -195,15 +216,30 @@
<data name="related.Header" xml:space="preserve">
<value>Suggestons</value>
</data>
<data name="schedule.Text" xml:space="preserve">
<value>Stream schedule:</value>
</data>
<data name="share.Label" xml:space="preserve">
<value>Share</value>
</data>
<data name="signin" xml:space="preserve">
<value>Sign in</value>
</data>
<data name="signRequired" xml:space="preserve">
<value>Please, sign into your account</value>
</data>
<data name="start" xml:space="preserve">
<value>Starts at:</value>
</data>
<data name="streamElapsed.Text" xml:space="preserve">
<value>Elapsed time since stream start</value>
</data>
<data name="subs.Text" xml:space="preserve">
<value>Captions</value>
</data>
<data name="subsSelector.Header" xml:space="preserve">
<value>Language</value>
</data>
<data name="subsSelector.PlaceholderText" xml:space="preserve">
<value>No captions are available</value>
</data>
@@ -213,7 +249,16 @@
<data name="subsSwitch.OnContent" xml:space="preserve">
<value>Captions</value>
</data>
<data name="upcomingHeader.Text" xml:space="preserve">
<value>Stream hasn't started yet</value>
</data>
<data name="volume.Text" xml:space="preserve">
<value>Volume</value>
</data>
<data name="wantContinue" xml:space="preserve">
<value>Do you want to continue</value>
</data>
<data name="yes" xml:space="preserve">
<value>Yes</value>
</data>
</root>
+3
View File
@@ -156,4 +156,7 @@
<data name="views.Text" xml:space="preserve">
<value>Просмотры: </value>
</data>
<data name="searchHeader" xml:space="preserve">
<value>Результаты поиска</value>
</data>
</root>
+3
View File
@@ -120,6 +120,9 @@
<data name="box.PlaceholderText" xml:space="preserve">
<value>Отправить сообщение</value>
</data>
<data name="failed" xml:space="preserve">
<value>Не удалось отправить ваше сообщение. Это могло произойти из-за включенного в этом чате замедленного режима. Пожалуйста, попробуйте позже. P.S. Отчет об ошибке был отправлен разработчикам</value>
</data>
<data name="moder.Text" xml:space="preserve">
<value>Модератор</value>
</data>
+4 -1
View File
@@ -138,12 +138,15 @@
<data name="editorCancel.Content" xml:space="preserve">
<value>Отмена</value>
</data>
<data name="editorDelete.Content" xml:space="preserve">
<data name="editorDelete.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Удалить комментарий</value>
</data>
<data name="editorSubmin.Content" xml:space="preserve">
<value>Отправить</value>
</data>
<data name="failedDelete" xml:space="preserve">
<value>Не удалось удалить комментарий. Пожалуйста, попробуйте позже</value>
</data>
<data name="failedEdit" xml:space="preserve">
<value>Не удалось изменить комментарий. Пожалуйста, попробуйте позже</value>
</data>
+6
View File
@@ -138,6 +138,12 @@
<data name="ext" xml:space="preserve">
<value>Расширение</value>
</data>
<data name="failedBody" xml:space="preserve">
<value>Отчет об ошибке был отправлен</value>
</data>
<data name="failedHead" xml:space="preserve">
<value>При загрузке видео произошла ошибка</value>
</data>
<data name="gotoOrign.Text" xml:space="preserve">
<value>Открыть оригинал</value>
</data>
+45
View File
@@ -120,6 +120,9 @@
<data name="addTo.Label" xml:space="preserve">
<value>Добавить в</value>
</data>
<data name="always" xml:space="preserve">
<value>Больше не спрашивать</value>
</data>
<data name="audio" xml:space="preserve">
<value>Аудио дорожка</value>
</data>
@@ -129,6 +132,9 @@
<data name="back.Text" xml:space="preserve">
<value>Назад на 10 секунд</value>
</data>
<data name="cancel" xml:space="preserve">
<value>Отмена</value>
</data>
<data name="cast.Text" xml:space="preserve">
<value>Отправить на устройство</value>
</data>
@@ -141,12 +147,18 @@
<data name="comments.Header" xml:space="preserve">
<value>Комментарии</value>
</data>
<data name="countdown.Text" xml:space="preserve">
<value>Стрим начнется через:</value>
</data>
<data name="desc" xml:space="preserve">
<value>Описание</value>
</data>
<data name="download.Label" xml:space="preserve">
<value>Скачать видео</value>
</data>
<data name="end" xml:space="preserve">
<value>Конец:</value>
</data>
<data name="exitminiview.Text" xml:space="preserve">
<value>Развернуть</value>
</data>
@@ -156,9 +168,15 @@
<data name="fwd.Text" xml:space="preserve">
<value>Вперед на 30 секунд</value>
</data>
<data name="generatedCaption" xml:space="preserve">
<value>(Авто-перевод)</value>
</data>
<data name="goLive.Text" xml:space="preserve">
<value>Перейти к прямому эфиру</value>
</data>
<data name="matureText" xml:space="preserve">
<value>Этот контент может быть неподходящим для лиц младше 18 лет</value>
</data>
<data name="maximize.Text" xml:space="preserve">
<value>Развернуть</value>
</data>
@@ -174,6 +192,9 @@
<data name="next.Text" xml:space="preserve">
<value>Следующее видео</value>
</data>
<data name="no" xml:space="preserve">
<value>Нет</value>
</data>
<data name="openWeb.Label" xml:space="preserve">
<value>Открыть в браузере</value>
</data>
@@ -195,15 +216,30 @@
<data name="related.Header" xml:space="preserve">
<value>Похожее</value>
</data>
<data name="schedule.Text" xml:space="preserve">
<value>Расписание эфира</value>
</data>
<data name="share.Label" xml:space="preserve">
<value>Поделиться</value>
</data>
<data name="signin" xml:space="preserve">
<value>Войти</value>
</data>
<data name="signRequired" xml:space="preserve">
<value>Пожалуйста, войдите в аккаунт</value>
</data>
<data name="start" xml:space="preserve">
<value>Начало:</value>
</data>
<data name="streamElapsed.Text" xml:space="preserve">
<value>Время, прошедшее с начала стрима</value>
</data>
<data name="subs.Text" xml:space="preserve">
<value>Субтитры</value>
</data>
<data name="subsSelector.Header" xml:space="preserve">
<value>Язык субтитров</value>
</data>
<data name="subsSelector.PlaceholderText" xml:space="preserve">
<value>Нет доступных субтитров</value>
</data>
@@ -213,7 +249,16 @@
<data name="subsSwitch.OnContent" xml:space="preserve">
<value>Субтитры</value>
</data>
<data name="upcomingHeader.Text" xml:space="preserve">
<value>Прямой эфир еще не начался</value>
</data>
<data name="volume.Text" xml:space="preserve">
<value>Громкость</value>
</data>
<data name="wantContinue" xml:space="preserve">
<value>Хотите продолжить?</value>
</data>
<data name="yes" xml:space="preserve">
<value>Да</value>
</data>
</root>
+632
View File
@@ -0,0 +1,632 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FoxTube"
xmlns:controls="using:FoxTube.Controls">
<Style TargetType="local:PlayerControls">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="FlowDirection" Value="LeftToRight" />
<Setter Property="UseSystemFocusVisuals" Value="True" />
<Setter Property="IsTextScaleFactorEnabled" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:PlayerControls">
<Grid Background="Transparent">
<Grid.Resources>
<Style TargetType="Button" BasedOn="{StaticResource ButtonRevealStyle}">
<Setter Property="Width" Value="50"/>
<Setter Property="Height" Value="50"/>
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style x:Key="AppBarButtonStyle" TargetType="AppBarButton">
<Setter Property="Width" Value="{ThemeResource MTCMediaButtonWidth}" />
<Setter Property="Height" Value="{ThemeResource MTCMediaButtonHeight}" />
<Setter Property="AllowFocusOnInteraction" Value="True" />
</Style>
<Style x:Key="CommandBarStyle" TargetType="CommandBar">
<Setter Property="Height" Value="{ThemeResource MTCMediaButtonHeight}" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="MediaTextBlockStyle" TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}" />
<Setter Property="FontSize" Value="{ThemeResource MTCMediaFontSize}" />
<Setter Property="FontFamily" Value="{ThemeResource MTCMediaFontFamily}" />
<Setter Property="Style" Value="{ThemeResource CaptionTextBlockStyle }" />
<Setter Property="IsTextScaleFactorEnabled" Value="False" />
</Style>
<Style x:Key="PlayerSeek" TargetType="Slider">
<Setter Property="Background" Value="{ThemeResource SliderTrackFill}" />
<Setter Property="BorderThickness" Value="{ThemeResource SliderBorderThemeThickness}" />
<Setter Property="Foreground" Value="{ThemeResource SliderTrackValueFill}" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="ManipulationMode" Value="None" />
<Setter Property="UseSystemFocusVisuals" Value="True" />
<Setter Property="IsFocusEngagementEnabled" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Slider">
<Grid Margin="{TemplateBinding Padding}">
<Grid.Resources>
<Style TargetType="Thumb" x:Key="SliderThumbStyle">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="{ThemeResource SliderThumbBackground}" />
<Setter Property="Foreground" Value="{ThemeResource SystemControlBackgroundChromeMediumBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Ellipse x:Name="ellipse"
Stroke="{TemplateBinding Background}"
StrokeThickness="2"
Fill="{TemplateBinding Foreground}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="ProgressBar" x:Key="MediaSliderProgressBarStyle">
<Setter Property="Height" Value="{ThemeResource SliderTrackThemeHeight}" />
<Setter Property="Minimum" Value="0" />
<Setter Property="Maximum" Value="100" />
<Setter Property="Foreground" Value="{ThemeResource SystemControlHighlightChromeAltLowBrush}" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="1" />
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPressed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderHeaderForegroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TopTickBar" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BottomTickBar" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LeftTickBar" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RightTickBar" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalTrackRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalDecreaseRect" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusEngagementStates">
<VisualState x:Name="FocusDisengaged" />
<VisualState x:Name="FocusEngagedHorizontal">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
<DiscreteObjectKeyFrame KeyTime="0" Value="False" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalThumb" Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
<DiscreteObjectKeyFrame KeyTime="0" Value="True" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="FocusEngagedVertical">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SliderContainer" Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
<DiscreteObjectKeyFrame KeyTime="0" Value="False" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalThumb" Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
<DiscreteObjectKeyFrame KeyTime="0" Value="True" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter x:Name="HeaderContentPresenter"
x:DeferLoadStrategy="Lazy"
Visibility="Collapsed"
Foreground="{ThemeResource SliderHeaderForeground}"
Margin="{ThemeResource SliderHeaderThemeMargin}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
FontWeight="{ThemeResource SliderHeaderThemeFontWeight}"
TextWrapping="Wrap" />
<Grid x:Name="SliderContainer"
Background="{ThemeResource SliderContainerBackground}"
Grid.Row="1"
Control.IsTemplateFocusTarget="True">
<Grid x:Name="HorizontalTemplate" MinHeight="44">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="15" />
<RowDefinition Height="Auto" />
<RowDefinition Height="15" />
</Grid.RowDefinitions>
<Rectangle x:Name="HorizontalTrackRect"
Fill="{TemplateBinding Background}"
Height="{ThemeResource SliderTrackThemeHeight}"
Grid.Row="1"
Grid.ColumnSpan="3" />
<ProgressBar x:Name="DownloadProgressIndicator"
Style="{StaticResource MediaSliderProgressBarStyle}"
Grid.Row="1"
Grid.ColumnSpan="3"
HorizontalAlignment="Stretch"
VerticalAlignment="Center" />
<Rectangle x:Name="HorizontalDecreaseRect" Fill="{TemplateBinding Foreground}" Grid.Row="1" />
<TickBar x:Name="TopTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderTickBarFill}"
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
VerticalAlignment="Bottom"
Margin="0,0,0,4"
Grid.ColumnSpan="3" />
<TickBar x:Name="HorizontalInlineTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderInlineTickBarFill}"
Height="{ThemeResource SliderTrackThemeHeight}"
Grid.Row="1"
Grid.ColumnSpan="3" />
<TickBar x:Name="BottomTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderTickBarFill}"
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
VerticalAlignment="Top"
Margin="0,4,0,0"
Grid.Row="2"
Grid.ColumnSpan="3" />
<Thumb x:Name="HorizontalThumb"
Style="{StaticResource SliderThumbStyle}"
Height="15"
Width="15"
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="1"
AutomationProperties.AccessibilityView="Raw">
<ToolTipService.ToolTip>
<ToolTip x:Name="ThumbnailTooltip">
<ContentPresenter Content="{Binding}" />
</ToolTip>
</ToolTipService.ToolTip>
<Thumb.DataContext>
<Grid Height="112" Width="192">
<Image x:Name="ThumbnailImage"/>
<Border Background="{ThemeResource SystemControlBackgroundBaseMediumBrush}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left">
<TextBlock x:Name="TimeElapsedPreview"
Margin="6,1,6,3"
Style="{StaticResource BodyTextBlockStyle}"
IsTextScaleFactorEnabled="False"
Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}" />
</Border>
</Grid>
</Thumb.DataContext>
</Thumb>
</Grid>
<Grid x:Name="VerticalTemplate" MinWidth="44" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="18" />
</Grid.ColumnDefinitions>
<Rectangle x:Name="VerticalTrackRect"
Fill="{TemplateBinding Background}"
Width="{ThemeResource SliderTrackThemeHeight}"
Grid.Column="1"
Grid.RowSpan="3" />
<Rectangle x:Name="VerticalDecreaseRect"
Fill="{TemplateBinding Foreground}"
Grid.Column="1"
Grid.Row="2" />
<TickBar x:Name="LeftTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderTickBarFill}"
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
HorizontalAlignment="Right"
Margin="0,0,4,0"
Grid.RowSpan="3" />
<TickBar x:Name="VerticalInlineTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderInlineTickBarFill}"
Width="{ThemeResource SliderTrackThemeHeight}"
Grid.Column="1"
Grid.RowSpan="3" />
<TickBar x:Name="RightTickBar"
Visibility="Collapsed"
Fill="{ThemeResource SliderTickBarFill}"
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
HorizontalAlignment="Left"
Margin="4,0,0,0"
Grid.Column="2"
Grid.RowSpan="3" />
<Thumb x:Name="VerticalThumb"
Style="{StaticResource SliderThumbStyle}"
DataContext="{TemplateBinding Value}"
Width="24"
Height="8"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
FocusVisualMargin="-6,-14,-6,-14"
AutomationProperties.AccessibilityView="Raw" />
</Grid>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<VisualStateManager.VisualStateGroups>
<!-- ControlPanel Visibility states -->
<VisualStateGroup x:Name="ControlPanelVisibilityStates">
<VisualState x:Name="ControlPanelFadeIn">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Border">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="center">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="centerBgr">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetProperty="Y" Storyboard.TargetName="TranslateVerticalBottom" From="50" To ="0" Duration="0:0:0.3"/>
<DoubleAnimation Storyboard.TargetProperty="Y" Storyboard.TargetName="TranslateVerticalTop" From="-50" To ="0" Duration="0:0:0.3"/>
</Storyboard>
</VisualState>
<VisualState x:Name="ControlPanelFadeOut">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Border">
<EasingDoubleKeyFrame KeyTime="0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="center">
<EasingDoubleKeyFrame KeyTime="0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="centerBgr">
<EasingDoubleKeyFrame KeyTime="0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0" />
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="Border">
<DiscreteObjectKeyFrame KeyTime="0" Value="False" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="center">
<DiscreteObjectKeyFrame KeyTime="0" Value="False" />
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetProperty="Y" Storyboard.TargetName="TranslateVerticalBottom" From="0" To ="50" Duration="0:0:0.7"/>
<DoubleAnimation Storyboard.TargetProperty="Y" Storyboard.TargetName="TranslateVerticalTop" From="0" To ="-50" Duration="0:0:0.7"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<!-- ControlPanel Visibility states -->
<!-- Media state visual states -->
<VisualStateGroup x:Name="MediaStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Buffering">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="BufferingProgressBar">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Loading">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="BufferingProgressBar">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetName="ProgressSlider"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="0" />
</Storyboard>
</VisualState>
<!--<VisualState x:Name="Error">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ErrorBorder">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>-->
<VisualState x:Name="Disabled">
<Storyboard />
</VisualState>
</VisualStateGroup>
<!-- Focus states -->
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="FocusVisualWhite"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0" />
<DoubleAnimation Storyboard.TargetName="FocusVisualBlack"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0" />
</Storyboard>
</VisualState>
<VisualState x:Name="Unfocused" />
<VisualState x:Name="PointerFocused" />
</VisualStateGroup>
<VisualStateGroup x:Name="PlayPauseStates">
<VisualState x:Name="PlayState" />
<VisualState x:Name="PauseState">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlayPauseSymbol" Storyboard.TargetProperty="Symbol">
<DiscreteObjectKeyFrame KeyTime="0" Value="Pause" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<!-- FullWindow states -->
<VisualStateGroup x:Name="FullWindowStates">
<VisualState x:Name="NonFullWindowState" />
<VisualState x:Name="FullWindowState">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="FullWindowSymbol" Storyboard.TargetProperty="Symbol">
<DiscreteObjectKeyFrame KeyTime="0" Value="BackToWindow" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="Border">
<Grid x:Name="ControlPanelGrid">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid x:Name="header">
<Grid.RenderTransform>
<TranslateTransform x:Name="TranslateVerticalTop"/>
</Grid.RenderTransform>
<Grid.Background>
<AcrylicBrush TintColor="#CC000000" TintOpacity=".6"/>
</Grid.Background>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Button x:Name="minimize">
<FontIcon Glyph="&#xE011;"/>
</Button>
<StackPanel Margin="10,0" Grid.Column="1" VerticalAlignment="Center">
<TextBlock x:Name="title" TextTrimming="CharacterEllipsis" FontSize="20" MaxLines="1"/>
<TextBlock Foreground="LightGray" FontStyle="Italic" x:Name="author"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Column="2" x:Name="headerToolbar">
<Button x:Name="close">
<FontIcon Glyph="&#xE106;"/>
</Button>
<Button x:Name="CastButton">
<FontIcon Glyph="&#xEC15;"/>
</Button>
<Button x:Name="CompactOverlayButton" VerticalAlignment="Top" HorizontalAlignment="Right">
<FontIcon Glyph="&#xE2B3;"/>
</Button>
</StackPanel>
</Grid>
<Grid Grid.Row="1" x:Name="centerBgr" Visibility="Collapsed" IsHitTestVisible="False" Background="#66000000"/>
<Grid Grid.Row="1" x:Name="center" Visibility="Collapsed">
<Button x:Name="maximize" VerticalAlignment="Top" HorizontalAlignment="Left" IsHitTestVisible="True">
<FontIcon Glyph="&#xE010;"/>
</Button>
<Button x:Name="compactClose" VerticalAlignment="Top" HorizontalAlignment="Right">
<FontIcon Glyph="&#xE106;"/>
</Button>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="centralStack"/>
<ProgressBar VerticalAlignment="Bottom" x:Name="compactSeek" Background="Transparent"/>
</Grid>
<Grid Grid.Row="2" x:Name="footer">
<Grid.RenderTransform>
<TranslateTransform x:Name="TranslateVerticalBottom"/>
</Grid.RenderTransform>
<Grid.Background>
<AcrylicBrush TintColor="#CC000000" TintOpacity=".6"/>
</Grid.Background>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" x:Name="leftStack">
<Button x:Name="PlayPauseButton">
<SymbolIcon x:Name="PlayPauseSymbol" Symbol="Play"/>
</Button>
<Button x:Name="next">
<SymbolIcon Symbol="Next"/>
</Button>
<Button x:Name="volume">
<FontIcon Glyph="&#xE15D;"/>
<Button.Flyout>
<Flyout>
<StackPanel Orientation="Horizontal" Margin="-10">
<Button x:Name="AudioMuteButton" Width="50" Height="50" Background="Transparent" FontFamily="Segoe MDL2 Assets" FontSize="25">
<FontIcon Glyph="&#xE15D;"/>
</Button>
<Slider Foreground="Red" Orientation="Horizontal" Width="150" Margin="10,5,10,0" VerticalAlignment="Center" x:Name="VolumeSlider"/>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
</StackPanel>
<Grid Grid.Column="1" Margin="10,5">
<TextBlock VerticalAlignment="Bottom" HorizontalAlignment="Left" x:Name="TimeElapsedElement" Text="00:00"/>
<TextBlock VerticalAlignment="Bottom" HorizontalAlignment="Right" x:Name="TimeRemainingElement" Text="00:00"/>
<Grid VerticalAlignment="Top" Height="3" Margin="0,15,0,0">
<ProgressBar Background="#66FFFFFF" Foreground="#66FFFFFF" x:Name="BufferingProgressBar"/>
</Grid>
<Slider x:Name="ProgressSlider" Style="{StaticResource PlayerSeek}" IsThumbToolTipEnabled="False" Background="Transparent" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
</Grid>
<StackPanel Orientation="Horizontal" Grid.Column="2" x:Name="rightStack">
<Button x:Name="SkipBackwardButton">
<FontIcon Glyph="&#xED3C;"/>
</Button>
<Button x:Name="SkipForwardButton">
<FontIcon Glyph="&#xED3D;"/>
</Button>
<Line Stroke="White" StrokeThickness="2" Y1="5" Y2="45"/>
<Button x:Name="cc">
<SymbolIcon Symbol="ClosedCaption"/>
<Button.Flyout>
<Flyout>
<StackPanel Width="225">
<ToggleSwitch x:Name="ccSwitch" OnContent="Subtitles" OffContent="Subtitles" x:Uid="/VideoPage/subsSwitch"/>
<ComboBox x:Name="ccSelector" Header="Language" x:Uid="/VideoPage/subsSelector" PlaceholderText="No subtitles are available" HorizontalAlignment="Stretch"/>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
<Button x:Name="quality">
<SymbolIcon Symbol="Setting"/>
<Button.Flyout>
<Flyout>
<ComboBox Width="225" x:Uid="/VideoPage/qualitySelector" Header="Quality" x:Name="qualitySelector"/>
</Flyout>
</Button.Flyout>
</Button>
<Button x:Name="FullWindowButton">
<SymbolIcon x:Name="FullWindowSymbol" Symbol="FullScreen"/>
</Button>
</StackPanel>
</Grid>
</Grid>
</Border>
<controls:LiveCaptions Visibility="Collapsed" x:Name="captions"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>