Authentication proccess
This commit is contained in:
@@ -66,6 +66,9 @@ namespace FoxTube
|
||||
Window.Current.Content = new MainPage(e.SplashScreen, e);
|
||||
Window.Current.Activate();
|
||||
|
||||
if (e.Kind == ActivationKind.Protocol)
|
||||
Authenticator.SetResponse((e as ProtocolActivatedEventArgs).Uri);
|
||||
|
||||
/*if(e.Kind == ActivationKind.ToastNotification)
|
||||
{
|
||||
if (UserManagement.IsAuthorized)
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
using Google.Apis.Oauth2.v2;
|
||||
using Google.Apis.YouTube.v3;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Data.Json;
|
||||
using Windows.Security.Cryptography;
|
||||
using Windows.Security.Cryptography.Core;
|
||||
using Windows.Storage.Streams;
|
||||
using Google.Apis.Auth.OAuth2.Responses;
|
||||
using Google.Apis.Auth.OAuth2;
|
||||
|
||||
namespace FoxTube.Classes
|
||||
{
|
||||
public static class Authenticator
|
||||
{
|
||||
const string redirectURI = "xfox111.foxtube:/oauth2redirect";
|
||||
const string authorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth";
|
||||
const string tokenEndpoint = "https://www.googleapis.com/oauth2/v4/token";
|
||||
|
||||
readonly static string scopes = Oauth2Service.Scope.UserinfoProfile + " " +
|
||||
Oauth2Service.Scope.UserinfoEmail + " " +
|
||||
YouTubeService.Scope.YoutubeForceSsl;
|
||||
|
||||
|
||||
static Uri responseUri = null;
|
||||
static string state = null;
|
||||
static string codeVerifier = null;
|
||||
|
||||
|
||||
public static async Task<TokenResponse> Authorize(string clientId)
|
||||
{
|
||||
await SendRequest(clientId);
|
||||
await WaitForResponse();
|
||||
string code = ProcessResponse(responseUri);
|
||||
JsonObject response = await ExchangeForTokens(clientId, code);
|
||||
|
||||
ClearFields();
|
||||
|
||||
return new TokenResponse
|
||||
{
|
||||
AccessToken = response.GetNamedString("access_token"),
|
||||
ExpiresInSeconds = 3600,
|
||||
IssuedUtc = DateTime.UtcNow,
|
||||
TokenType = response.GetNamedString("token_type"),
|
||||
IdToken = response.GetNamedString("id_token"),
|
||||
RefreshToken = response.GetNamedString("refresh_token"),
|
||||
Scope = response.GetNamedString("scope")
|
||||
};
|
||||
}
|
||||
|
||||
public static async Task<TokenResponse> RefreshToken(string clientId, string refreshToken) =>
|
||||
await RefreshToken(clientId, refreshToken, null);
|
||||
|
||||
public static async Task<TokenResponse> RefreshToken(string clientId, string refreshToken, string idToken)
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Host = "www.googleapis.com";
|
||||
client.DefaultRequestHeaders.Add("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
Dictionary<string, string> body = new Dictionary<string, string>
|
||||
{
|
||||
{ "client_id", clientId },
|
||||
{ "refresh_token", refreshToken },
|
||||
{ "grant_type", "refresh_token" }
|
||||
};
|
||||
|
||||
using(HttpResponseMessage raw = await client.PostAsync("https://www.googleapis.com/oauth2/v4/token", new FormUrlEncodedContent(body)))
|
||||
{
|
||||
if (!raw.IsSuccessStatusCode)
|
||||
return null;
|
||||
|
||||
JsonObject response = JsonObject.Parse(await raw.Content.ReadAsStringAsync());
|
||||
|
||||
return new TokenResponse
|
||||
{
|
||||
AccessToken = response.GetNamedString("access_token"),
|
||||
IssuedUtc = DateTime.UtcNow,
|
||||
RefreshToken = refreshToken,
|
||||
Scope = scopes,
|
||||
TokenType = "Bearer",
|
||||
ExpiresInSeconds = int.Parse(response.GetNamedString("expires_in")),
|
||||
IdToken = idToken
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static async Task WaitForResponse()
|
||||
{
|
||||
while (responseUri == null)
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
public static void SetResponse(Uri response) =>
|
||||
responseUri = response;
|
||||
|
||||
static async Task SendRequest(string clientId)
|
||||
{
|
||||
// Generates state and PKCE values.
|
||||
state = randomDataBase64url(32);
|
||||
codeVerifier = randomDataBase64url(32);
|
||||
string code_challenge = base64urlencodeNoPadding(sha256(codeVerifier));
|
||||
|
||||
// Creates the OAuth 2.0 authorization request.
|
||||
string authorizationRequest = string.Format("{0}?response_type=code&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}&scope={6}",
|
||||
authorizationEndpoint,
|
||||
Uri.EscapeDataString(redirectURI),
|
||||
clientId,
|
||||
state,
|
||||
code_challenge,
|
||||
"S256",
|
||||
scopes);
|
||||
|
||||
// Opens the Authorization URI in the browser.
|
||||
await Windows.System.Launcher.LaunchUriAsync(new Uri(authorizationRequest));
|
||||
}
|
||||
|
||||
public static string ProcessResponse(Uri response)
|
||||
{
|
||||
// Gets URI from navigation parameters.
|
||||
string queryString = response.Query;
|
||||
|
||||
// Parses URI params into a dictionary
|
||||
// ref: http://stackoverflow.com/a/11957114/72176
|
||||
Dictionary<string, string> queryStringParams =
|
||||
queryString.Substring(1).Split('&')
|
||||
.ToDictionary(c => c.Split('=')[0],
|
||||
c => Uri.UnescapeDataString(c.Split('=')[1]));
|
||||
|
||||
if (!queryStringParams.ContainsKey("code")
|
||||
|| !queryStringParams.ContainsKey("state")
|
||||
|| queryStringParams.ContainsKey("error"))
|
||||
return null;
|
||||
|
||||
// Gets the Authorization state
|
||||
string incoming_state = queryStringParams["state"];
|
||||
|
||||
// Compares the receieved state to the expected value, to ensure that
|
||||
// this app made the request which resulted in authorization
|
||||
if (incoming_state != state)
|
||||
return null;
|
||||
|
||||
// Returns the Authorization code
|
||||
return queryStringParams["code"];
|
||||
}
|
||||
|
||||
|
||||
static async Task<JsonObject> ExchangeForTokens(string clientId, string code)
|
||||
{
|
||||
// Builds the Token request
|
||||
string tokenRequestBody = string.Format("code={0}&redirect_uri={1}&client_id={2}&code_verifier={3}&scope=&grant_type=authorization_code",
|
||||
code,
|
||||
Uri.EscapeDataString(redirectURI),
|
||||
clientId,
|
||||
codeVerifier
|
||||
);
|
||||
StringContent content = new StringContent(tokenRequestBody, Encoding.UTF8, "application/x-www-form-urlencoded");
|
||||
|
||||
// Performs the authorization code exchange.
|
||||
HttpClientHandler handler = new HttpClientHandler();
|
||||
handler.AllowAutoRedirect = true;
|
||||
HttpClient client = new HttpClient(handler);
|
||||
|
||||
HttpResponseMessage response = await client.PostAsync(tokenEndpoint, content);
|
||||
string responseString = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
|
||||
// Retrieves access token
|
||||
return JsonObject.Parse(responseString);
|
||||
}
|
||||
|
||||
static void ClearFields()
|
||||
{
|
||||
codeVerifier = null;
|
||||
state = null;
|
||||
responseUri = null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns URI-safe data with a given input length.
|
||||
/// </summary>
|
||||
/// <param name="length">Input length (nb. output will be longer)</param>
|
||||
/// <returns></returns>
|
||||
static string randomDataBase64url(uint length)
|
||||
{
|
||||
IBuffer buffer = CryptographicBuffer.GenerateRandom(length);
|
||||
return base64urlencodeNoPadding(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the SHA256 hash of the input string.
|
||||
/// </summary>
|
||||
/// <param name="inputStirng"></param>
|
||||
/// <returns></returns>
|
||||
static IBuffer sha256(string inputStirng)
|
||||
{
|
||||
HashAlgorithmProvider sha = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
|
||||
IBuffer buff = CryptographicBuffer.ConvertStringToBinary(inputStirng, BinaryStringEncoding.Utf8);
|
||||
return sha.HashData(buff);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base64url no-padding encodes the given input buffer.
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <returns></returns>
|
||||
static string base64urlencodeNoPadding(IBuffer buffer)
|
||||
{
|
||||
string base64 = CryptographicBuffer.EncodeToBase64String(buffer);
|
||||
|
||||
// Converts base64 to base64url.
|
||||
base64 = base64.Replace("+", "-");
|
||||
base64 = base64.Replace("/", "_");
|
||||
// Strips padding.
|
||||
base64 = base64.Replace("=", "");
|
||||
|
||||
return base64;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,5 +32,8 @@ namespace FoxTube
|
||||
|
||||
public static bool Belongs<T>(this T obj, params T[] values) =>
|
||||
values.Contains(obj);
|
||||
|
||||
public static string GuardFromNull(this object obj, string str) =>
|
||||
string.IsNullOrWhiteSpace(str) ? string.Empty : str;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.ViewManagement;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
@@ -80,6 +82,8 @@ namespace FoxTube
|
||||
titleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
|
||||
titleBar.ButtonInactiveForegroundColor = Colors.Gray;
|
||||
titleBar.ButtonForegroundColor = Colors.White;
|
||||
|
||||
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,14 @@ namespace FoxTube
|
||||
public static void GoToSubscriptions()
|
||||
{
|
||||
|
||||
}
|
||||
public static void GoToSettings()
|
||||
{
|
||||
|
||||
}
|
||||
public static void GoToUploadPage()
|
||||
{
|
||||
|
||||
}
|
||||
public static void GoToSearch(SearchParameters args)
|
||||
{
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace FoxTube
|
||||
{
|
||||
public static class Service
|
||||
{
|
||||
public static bool AdsCheckPerformed { get; private set; } = false;
|
||||
public static bool AdsDisabled { get; private set; } = true;
|
||||
public static event ProVersionStateChangedEventHandler Purchased;
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ namespace FoxTube.Classes
|
||||
public bool processClipboard = true;
|
||||
public bool minimizeCommandbar = false;
|
||||
|
||||
public int selectedUser = -1;
|
||||
|
||||
static string GetLanguage()
|
||||
{
|
||||
if ((new[] { "ua", "ru", "by", "kz", "kg", "md", "lv", "ee" }).Contains(CultureInfo.InstalledUICulture.TwoLetterISOLanguageName))
|
||||
@@ -157,6 +159,15 @@ namespace FoxTube.Classes
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
public static int SelectedUser
|
||||
{
|
||||
get => Container.selectedUser;
|
||||
set
|
||||
{
|
||||
Container.selectedUser = value;
|
||||
SaveData();
|
||||
}
|
||||
}
|
||||
public static bool[] HasAccount => Container.hasAccount;
|
||||
public static void SetAccount(int index, bool isAuthorized)
|
||||
{
|
||||
|
||||
+268
-39
@@ -3,58 +3,239 @@ using YoutubeExplode.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Google.Apis.Oauth2.v2.Data;
|
||||
using Google.Apis.Auth.OAuth2;
|
||||
using Google.Apis.YouTube.v3;
|
||||
using Google.Apis.Services;
|
||||
using System.Threading;
|
||||
using YoutubeExplode;
|
||||
using System.Net.Http;
|
||||
using Google.Apis.Auth.OAuth2.Responses;
|
||||
using Newtonsoft.Json;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Popups;
|
||||
|
||||
namespace FoxTube.Classes
|
||||
{
|
||||
public class User
|
||||
{
|
||||
public List<Subscription> Subscriptions { get; }
|
||||
public event SubscriptionChangedEventHandler SubscriptionsChanged;
|
||||
|
||||
public List<YoutubeExplode.Models.Video> WatchLater { get; }
|
||||
public List<YoutubeExplode.Models.Video> WebHistory { get; }
|
||||
public List<Subscription> Subscriptions { get; } = new List<Subscription>();
|
||||
|
||||
public List<YoutubeExplode.Models.Video> WatchLater { get; private set; }
|
||||
public List<YoutubeExplode.Models.Video> WebHistory { get; private set; }
|
||||
public List<object> AppHistory { get; }
|
||||
|
||||
public Userinfoplus UserInfo { get; }
|
||||
public UserCredential Credential { get; }
|
||||
|
||||
public YouTubeService Service { get; }
|
||||
public Userinfoplus UserInfo { get; private set; }
|
||||
public TokenResponse Tokens { get; private set; }
|
||||
|
||||
public YoutubeClient YoutubeClient { get; }
|
||||
|
||||
public string AccountId { get; }
|
||||
public Google.Apis.YouTube.v3.Data.Channel Channel { get; }
|
||||
public string AccountId => Channel.Id;
|
||||
public Google.Apis.YouTube.v3.Data.Channel Channel { get; private set; }
|
||||
|
||||
public async void Deauthenticate()
|
||||
HttpClient HttpClient { get; }
|
||||
|
||||
DispatcherTimer timer = new DispatcherTimer();
|
||||
|
||||
public async Task Deauthenticate() =>
|
||||
await HttpClient.GetAsync($"https://accounts.google.com/o/oauth2/revoke?token={Tokens.AccessToken}");
|
||||
|
||||
public User(TokenResponse tokens)
|
||||
{
|
||||
await Credential.RevokeTokenAsync(CancellationToken.None);
|
||||
Tokens = tokens;
|
||||
|
||||
HttpClient = new HttpClient();
|
||||
HttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Tokens.AccessToken);
|
||||
YoutubeClient = new YoutubeClient(HttpClient);
|
||||
|
||||
timer.Interval = TimeSpan.FromSeconds((int)tokens.ExpiresInSeconds);
|
||||
timer.Tick += Timer_Tick;
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
public User(UserCredential credential)
|
||||
async void Timer_Tick(object sender, object e)
|
||||
{
|
||||
Credential = credential;
|
||||
Service = new YouTubeService(new BaseClientService.Initializer
|
||||
Tokens = await UserManagement.RefreshToken();
|
||||
timer.Interval = TimeSpan.FromSeconds((int)Tokens.ExpiresInSeconds);
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
public async Task Initialize()
|
||||
{
|
||||
string responseRaw = await HttpClient.GetStringAsync("https://www.googleapis.com/oauth2/v2/userinfo");
|
||||
dynamic response = JsonConvert.DeserializeObject(responseRaw);
|
||||
|
||||
UserInfo = new Userinfoplus
|
||||
{
|
||||
HttpClientInitializer = Credential,
|
||||
ApplicationName = "FoxTube"
|
||||
});
|
||||
Email = this.GuardFromNull((string)response.email),
|
||||
FamilyName = this.GuardFromNull((string)response.family_name),
|
||||
Gender = this.GuardFromNull((string)response.gender),
|
||||
GivenName = this.GuardFromNull((string)response.given_name),
|
||||
Hd = this.GuardFromNull((string)response.hd),
|
||||
Id = this.GuardFromNull((string)response.id),
|
||||
Link = this.GuardFromNull((string)response.link),
|
||||
Locale = this.GuardFromNull((string)response.locale),
|
||||
Name = this.GuardFromNull((string)response.name),
|
||||
Picture = this.GuardFromNull((string)response.picture),
|
||||
VerifiedEmail = true //As it said in documentation it's always true because it applies to primary e-mail
|
||||
};
|
||||
|
||||
HttpClient httpClient = new HttpClient();
|
||||
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", credential.Token.AccessToken);
|
||||
YoutubeClient = new YoutubeClient(httpClient);
|
||||
string nextToken = null;
|
||||
do
|
||||
{
|
||||
string query = $"https://www.googleapis.com/youtube/v3/subscriptions?part=snippet&maxResults=50&mine=true&order=relevance&access_token={Tokens.AccessToken}{(string.IsNullOrWhiteSpace(nextToken) ? "" : "&pageToken=" + nextToken)}";
|
||||
HttpResponseMessage r = await HttpClient.GetAsync(query);
|
||||
MessageDialog dialog = new MessageDialog(await r.Content.ReadAsStringAsync(), r.StatusCode.ToString());
|
||||
await dialog.ShowAsync();
|
||||
|
||||
responseRaw = await HttpClient.GetStringAsync(query);
|
||||
response = JsonConvert.DeserializeObject(responseRaw);
|
||||
nextToken = response.nextPageToken;
|
||||
foreach (dynamic item in response.items)
|
||||
Subscriptions.Add(new Subscription
|
||||
{
|
||||
Snippet = new SubscriptionSnippet
|
||||
{
|
||||
ChannelId = item.channelId,
|
||||
ChannelTitle = item.channelTitle,
|
||||
Description = item.description,
|
||||
PublishedAt = item.publishedAt,
|
||||
Title = item.title,
|
||||
Thumbnails = new ThumbnailDetails
|
||||
{
|
||||
Default__ = new Thumbnail
|
||||
{
|
||||
Height = item.thumbnails["default"].height,
|
||||
Width = item.thumbnails["default"].width,
|
||||
Url = item.thumbnails["default"].url,
|
||||
},
|
||||
Medium = new Thumbnail
|
||||
{
|
||||
Height = item.thumbnails.medium.height,
|
||||
Width = item.thumbnails.medium.width,
|
||||
Url = item.thumbnails.medium.url,
|
||||
},
|
||||
High = new Thumbnail
|
||||
{
|
||||
Height = item.thumbnails.high.height,
|
||||
Width = item.thumbnails.high.width,
|
||||
Url = item.thumbnails.high.url,
|
||||
}
|
||||
},
|
||||
ResourceId = new ResourceId
|
||||
{
|
||||
ChannelId = item.resourceId.channelId,
|
||||
Kind = item.resourceId.kind
|
||||
}
|
||||
},
|
||||
ETag = item.etag,
|
||||
Id = item.id,
|
||||
Kind = "youtube#subscription"
|
||||
});
|
||||
}
|
||||
while (!string.IsNullOrWhiteSpace(nextToken));
|
||||
|
||||
responseRaw = await HttpClient.GetStringAsync("https://www.googleapis.com/youtube/v3/channels?mine=true&part=contentDetails,snippet,image");
|
||||
response = JsonConvert.DeserializeObject(responseRaw);
|
||||
|
||||
Channel = new Google.Apis.YouTube.v3.Data.Channel
|
||||
{
|
||||
Kind = "youtube#channel",
|
||||
ETag = response.items[0].etag,
|
||||
Id = response.items[0].id,
|
||||
ContentDetails = new ChannelContentDetails
|
||||
{
|
||||
RelatedPlaylists = new ChannelContentDetails.RelatedPlaylistsData
|
||||
{
|
||||
Likes = response.items[0].contentDetails.relatedPlaylists.likes,
|
||||
Favorites = response.items[0].contentDetails.relatedPlaylists.favorites,
|
||||
Uploads = response.items[0].contentDetails.relatedPlaylists.uploads,
|
||||
WatchHistory = "HL",
|
||||
WatchLater = "WL"
|
||||
}
|
||||
},
|
||||
Snippet = new ChannelSnippet
|
||||
{
|
||||
Title = response.items[0].snippet.title,
|
||||
Description = response.items[0].snippet.description,
|
||||
CustomUrl = response.items[0].snippet.customUrl,
|
||||
PublishedAt = response.items[0].snippet.publishedAt,
|
||||
DefaultLanguage = response.items[0].snippet.defaultLanguage,
|
||||
Country = response.items[0].snippet.country,
|
||||
Localized = new ChannelLocalization
|
||||
{
|
||||
Description = response.items[0].snippet.localized.description,
|
||||
Title = response.items[0].snippet.localized.title,
|
||||
},
|
||||
Thumbnails = new ThumbnailDetails
|
||||
{
|
||||
Default__ = new Thumbnail
|
||||
{
|
||||
Height = response.items[0].snippet.thumbnails["default"].height,
|
||||
Width = response.items[0].snippet.thumbnails["default"].width,
|
||||
Url = response.items[0].snippet.thumbnails["default"].url,
|
||||
},
|
||||
Medium = new Thumbnail
|
||||
{
|
||||
Height = response.items[0].snippet.thumbnails.medium.height,
|
||||
Width = response.items[0].snippet.thumbnails.medium.width,
|
||||
Url = response.items[0].snippet.thumbnails.medium.url,
|
||||
},
|
||||
High = new Thumbnail
|
||||
{
|
||||
Height = response.items[0].snippet.thumbnails.high.height,
|
||||
Width = response.items[0].snippet.thumbnails.high.width,
|
||||
Url = response.items[0].snippet.thumbnails.high.url,
|
||||
}
|
||||
}
|
||||
},
|
||||
BrandingSettings = new ChannelBrandingSettings
|
||||
{
|
||||
Channel = new ChannelSettings
|
||||
{
|
||||
Country = response.items[0].brandingSettings.channel.country,
|
||||
DefaultLanguage = response.items[0].brandingSettings.channel.defaultLanguage,
|
||||
DefaultTab = response.items[0].brandingSettings.channel.defaultTab,
|
||||
Description = response.items[0].brandingSettings.channel.description,
|
||||
FeaturedChannelsTitle = response.items[0].brandingSettings.channel.featuredChannelsTitle,
|
||||
FeaturedChannelsUrls = response.items[0].brandingSettings.channel.featuredChannelsUrls,
|
||||
Keywords = response.items[0].brandingSettings.channel.keywords,
|
||||
ModerateComments = response.items[0].brandingSettings.channel.moderateComments,
|
||||
ProfileColor = response.items[0].brandingSettings.channel.profileColor,
|
||||
ShowBrowseView = response.items[0].brandingSettings.channel.showBrowseView,
|
||||
ShowRelatedChannels = response.items[0].brandingSettings.channel.showRelatedChannels,
|
||||
Title = response.items[0].brandingSettings.channel.title,
|
||||
TrackingAnalyticsAccountId = response.items[0].brandingSettings.channel.trackingAnalyticsAccountId,
|
||||
UnsubscribedTrailer = response.items[0].brandingSettings.channel.unsubscribedTrailer
|
||||
},
|
||||
Image = new ImageSettings
|
||||
{
|
||||
BannerImageUrl = response.items[0].brandingSettings.image.bannerImageUrl,
|
||||
BannerMobileImageUrl = response.items[0].brandingSettings.image.bannerMobileImage.Url,
|
||||
WatchIconImageUrl = response.items[0].brandingSettings.image.watchIconImageUrl,
|
||||
TrackingImageUrl = response.items[0].brandingSettings.image.trackingImageUrl,
|
||||
BannerTabletLowImageUrl = response.items[0].brandingSettings.image.bannerTabletLowImageUrl,
|
||||
BannerTabletImageUrl = response.items[0].brandingSettings.image.bannerTabletImageUrl,
|
||||
BannerTabletHdImageUrl = response.items[0].brandingSettings.image.bannerTabletHdImageUrl,
|
||||
BannerTabletExtraHdImageUrl = response.items[0].brandingSettings.image.bannerTabletExtraHdImageUrl,
|
||||
BannerMobileLowImageUrl = response.items[0].brandingSettings.image.bannerMobileLowImageUrl,
|
||||
BannerMobileMediumHdImageUrl = response.items[0].brandingSettings.image.bannerMobileMediumHdImageUrl,
|
||||
BannerMobileHdImageUrl = response.items[0].brandingSettings.image.bannerMobileHdImageUrl,
|
||||
BannerMobileExtraHdImageUrl = response.items[0].brandingSettings.image.bannerMobileExtraHdImageUrl,
|
||||
BannerTvLowImageUrl = response.items[0].brandingSettings.image.bannerTvLowImageUrl,
|
||||
BannerTvImageUrl = response.items[0].brandingSettings.image.bannerTvImageUrl,
|
||||
BannerTvMediumImageUrl = response.items[0].brandingSettings.image.bannerTvMediumImageUrl,
|
||||
BannerTvHighImageUrl = response.items[0].brandingSettings.image.bannerTvHighImageUrl,
|
||||
BannerExternalUrl = response.items[0].brandingSettings.image.bannerExternalUrl
|
||||
},
|
||||
Hints = response.items[0].brandingSettings.hints
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
WatchLater = (await YoutubeClient.GetPlaylistAsync("WL")).Videos.ToList();
|
||||
WebHistory = (await YoutubeClient.GetPlaylistAsync("HL")).Videos.ToList();
|
||||
}
|
||||
|
||||
public async void RefreshToken() =>
|
||||
await Credential.RefreshTokenAsync(CancellationToken.None);
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes or unsibscribes authorized user from the channel
|
||||
/// </summary>
|
||||
@@ -64,36 +245,84 @@ namespace FoxTube.Classes
|
||||
{
|
||||
if (Subscriptions.Find(i => i.Snippet.ResourceId.ChannelId == id) is Subscription subscription)
|
||||
{
|
||||
try { await Service.Subscriptions.Delete(subscription.Id).ExecuteAsync(); }
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await HttpClient.DeleteAsync("https://www.googleapis.com/youtube/v3/subscriptions?id=" + subscription.Id);
|
||||
if (response.StatusCode != System.Net.HttpStatusCode.NoContent)
|
||||
return true;
|
||||
}
|
||||
catch { return true; }
|
||||
|
||||
//SubscriptionsChanged?.Invoke("remove", subscription);
|
||||
SubscriptionsChanged?.Invoke("remove", subscription);
|
||||
Subscriptions.Remove(subscription);
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var request = Service.Subscriptions.Insert(new Subscription
|
||||
Dictionary<string, string> body = new Dictionary<string, string>
|
||||
{
|
||||
{ "snippet", " { \"resourceId\" : { \"channelId\" : " + id + " } }" }
|
||||
};
|
||||
HttpResponseMessage response = await HttpClient.PostAsync("https://www.googleapis.com/youtube/v3/subscriptions?part=snippet", new FormUrlEncodedContent(body));
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return false;
|
||||
|
||||
dynamic raw = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync());
|
||||
|
||||
Subscription sub = new Subscription
|
||||
{
|
||||
Snippet = new SubscriptionSnippet
|
||||
{
|
||||
ChannelId = raw.channelId,
|
||||
ChannelTitle = raw.channelTitle,
|
||||
Description = raw.description,
|
||||
PublishedAt = raw.publishedAt,
|
||||
Title = raw.title,
|
||||
Thumbnails = new ThumbnailDetails
|
||||
{
|
||||
Default__ = new Thumbnail
|
||||
{
|
||||
Height = raw.thumbnails["default"].height,
|
||||
Width = raw.thumbnails["default"].width,
|
||||
Url = raw.thumbnails["default"].url,
|
||||
},
|
||||
Medium = new Thumbnail
|
||||
{
|
||||
Height = raw.thumbnails.medium.height,
|
||||
Width = raw.thumbnails.medium.width,
|
||||
Url = raw.thumbnails.medium.url,
|
||||
},
|
||||
High = new Thumbnail
|
||||
{
|
||||
Height = raw.thumbnails.high.height,
|
||||
Width = raw.thumbnails.high.width,
|
||||
Url = raw.thumbnails.high.url,
|
||||
}
|
||||
},
|
||||
ResourceId = new ResourceId
|
||||
{
|
||||
ChannelId = id,
|
||||
Kind = "youtube#channel"
|
||||
ChannelId = raw.resourceId.channelId,
|
||||
Kind = raw.resourceId.channelId
|
||||
}
|
||||
}
|
||||
}, "snippet");
|
||||
|
||||
if (!(await request.ExecuteAsync() is Subscription sub))
|
||||
return false;
|
||||
},
|
||||
ETag = raw.etag,
|
||||
Id = raw.id,
|
||||
Kind = "youtube#subscription"
|
||||
};
|
||||
|
||||
Subscriptions.Add(sub);
|
||||
//SubscriptionsChanged?.Invoke("add", sub);
|
||||
SubscriptionsChanged?.Invoke("add", sub);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
timer.Stop();
|
||||
timer.Tick -= Timer_Tick;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,38 @@
|
||||
using FoxTube.Classes;
|
||||
using Google.Apis.Auth.OAuth2;
|
||||
using Google.Apis.Auth.OAuth2.Flows;
|
||||
using Google.Apis.Auth.OAuth2.Responses;
|
||||
using Google.Apis.Oauth2.v2.Data;
|
||||
using Google.Apis.Services;
|
||||
using Google.Apis.YouTube.v3;
|
||||
using Microsoft.AppCenter.Analytics;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Security.Credentials;
|
||||
using Windows.Storage;
|
||||
using YoutubeExplode;
|
||||
|
||||
namespace FoxTube
|
||||
{
|
||||
public static class UserManagement
|
||||
{
|
||||
public static event AuthorizationChangedEventHandler AuthorizationStateChanged;
|
||||
public static event SubscriptionChangedEventHandler SubscriptionChanged;
|
||||
static ApplicationDataContainer storage = ApplicationData.Current.LocalSettings;
|
||||
|
||||
static ClientSecrets[] Secrets => new ClientSecrets[5]
|
||||
public static event AuthorizationChangedEventHandler AuthorizationStateChanged;
|
||||
public static event SubscriptionChangedEventHandler SubscriptionsChanged;
|
||||
|
||||
static string[] Secrets => new string[5]
|
||||
{
|
||||
new ClientSecrets
|
||||
{
|
||||
ClientId = "349735264870-2ekqlm0a4mkg3mmrfcv90s3qp3o15dq0.apps.googleusercontent.com",
|
||||
ClientSecret = "BkVZOAaCU2Zclf0Zlicg6y2_"
|
||||
},
|
||||
new ClientSecrets
|
||||
{
|
||||
ClientId = "1096685398208-ha281qmg52ga8g8nn4n6943oct1tq6nj.apps.googleusercontent.com",
|
||||
ClientSecret = "Cew9WbJcsoisYnR-Wuyhukov"
|
||||
},
|
||||
new ClientSecrets
|
||||
{
|
||||
ClientId = "1096685398208-59ktbv2jsvalrmli1pevur9aujnjhjeb.apps.googleusercontent.com",
|
||||
ClientSecret = "eHvXHlIJ9r64yReOmMnuben7"
|
||||
},
|
||||
new ClientSecrets
|
||||
{
|
||||
ClientId = "1096685398208-ebckt9ncp69qutsmdbbufov60nq4rvpa.apps.googleusercontent.com",
|
||||
ClientSecret = "XCaBFmjoT2ZAKsGf-y80lBvc"
|
||||
},
|
||||
new ClientSecrets
|
||||
{
|
||||
ClientId = "1096685398208-ligbo8gqkp4qar1uo0e5o5di434qklma.apps.googleusercontent.com",
|
||||
ClientSecret = "oXauUkFY9I7Q1CmNkKGmszRQ"
|
||||
},
|
||||
"1096685398208-bktbnoe59bt69nhnrrb5j0tpld58orsv.apps.googleusercontent.com",
|
||||
"1096685398208-u4a2pgpcn27c2kb3ud2eck1oh2ot68vs.apps.googleusercontent.com",
|
||||
"1096685398208-a65ebfpqnhl7u3iipfmfe5cif6j07db3.apps.googleusercontent.com",
|
||||
"1096685398208-in0gco58ckrumgjuo68st55fvb0ntllj.apps.googleusercontent.com",
|
||||
"1096685398208-7gd029f6tku4sc756v2338g1f0fu4k2k.apps.googleusercontent.com"
|
||||
};
|
||||
|
||||
static YouTubeService NoAuthService { get; } = new YouTubeService(new BaseClientService.Initializer
|
||||
@@ -53,20 +42,40 @@ namespace FoxTube
|
||||
});
|
||||
static YoutubeClient NoAuthClient { get; } = new YoutubeClient();
|
||||
|
||||
public static YouTubeService Service => Current == null ? NoAuthService : Current.Service;
|
||||
public static YoutubeClient YoutubeClient => Current == null ? NoAuthClient : Current.YoutubeClient;
|
||||
public static YoutubeClient YoutubeClient => Current?.YoutubeClient ?? NoAuthClient;
|
||||
|
||||
public static bool IsAuthorized => Current != null;
|
||||
|
||||
public static User Current { get; set; }
|
||||
|
||||
static (Userinfoplus info, UserCredential credential)[] UserInfos = new (Userinfoplus, UserCredential)[5];
|
||||
public static Userinfoplus[] UserInfos { get; private set; } = new Userinfoplus[5];
|
||||
static string[] tokens = new string[5];
|
||||
|
||||
public static bool CheckPerformed { get; }
|
||||
|
||||
public static void Initialize()
|
||||
public static async void Initialize()
|
||||
{
|
||||
if(storage.Values["users"] != null)
|
||||
UserInfos = JsonConvert.DeserializeObject<Userinfoplus[]>(storage.Values["users"] as string);
|
||||
PasswordVault vault = new PasswordVault();
|
||||
foreach (Userinfoplus info in UserInfos)
|
||||
{
|
||||
if (info == null)
|
||||
continue;
|
||||
PasswordCredential credential = vault.Retrieve("FoxTube", info.Id);
|
||||
credential.RetrievePassword();
|
||||
tokens[UserInfos.ToList().IndexOf(info)] = credential.Password;
|
||||
}
|
||||
|
||||
if(Settings.SelectedUser >= 0)
|
||||
{
|
||||
try { Current = new User(await Authenticator.RefreshToken(Secrets[Settings.SelectedUser], tokens[Settings.SelectedUser])); }
|
||||
catch { Current = new User(await Authenticator.Authorize(Secrets[Settings.SelectedUser])); }
|
||||
|
||||
await Current.Initialize();
|
||||
Current.SubscriptionsChanged += SubscriptionsChanged;
|
||||
UserInfos[Settings.SelectedUser] = Current.UserInfo;
|
||||
}
|
||||
|
||||
AuthorizationStateChanged?.Invoke(Settings.SelectedUser > -1);
|
||||
}
|
||||
|
||||
public static async Task AddItemToWL(string id)
|
||||
@@ -74,14 +83,91 @@ namespace FoxTube
|
||||
|
||||
}
|
||||
|
||||
public static async void ChangeAccount(Userinfoplus newUser)
|
||||
{
|
||||
Current.Dispose();
|
||||
Current.SubscriptionsChanged -= SubscriptionsChanged;
|
||||
|
||||
Current = null;
|
||||
|
||||
Settings.SelectedUser = UserInfos.ToList().IndexOf(newUser);
|
||||
|
||||
try { Current = new User(await Authenticator.RefreshToken(Secrets[Settings.SelectedUser], tokens[Settings.SelectedUser])); }
|
||||
catch { Current = new User(await Authenticator.Authorize(Secrets[Settings.SelectedUser])); }
|
||||
|
||||
await Current.Initialize();
|
||||
Current.SubscriptionsChanged += SubscriptionsChanged;
|
||||
UserInfos[Settings.SelectedUser] = Current.UserInfo;
|
||||
|
||||
AuthorizationStateChanged?.Invoke(true);
|
||||
}
|
||||
|
||||
public static async void CreateNew()
|
||||
{
|
||||
Analytics.TrackEvent("Initialized authorization sequence");
|
||||
try
|
||||
{
|
||||
for (int k = 0; k < 5; k++)
|
||||
if (tokens[k] == null)
|
||||
{
|
||||
TokenResponse response = await Authenticator.Authorize(Secrets[k]);
|
||||
tokens[k] = response.RefreshToken;
|
||||
Current?.Dispose();
|
||||
if (Current != null)
|
||||
Current.SubscriptionsChanged -= SubscriptionsChanged;
|
||||
Current = new User(response);
|
||||
await Current.Initialize();
|
||||
Current.SubscriptionsChanged += SubscriptionsChanged;
|
||||
UserInfos[k] = Current.UserInfo;
|
||||
|
||||
storage.Values["users"] = JsonConvert.SerializeObject(UserInfos);
|
||||
PasswordVault vault = new PasswordVault();
|
||||
vault.Add(new PasswordCredential("FoxTube", Current.UserInfo.Id, response.RefreshToken));
|
||||
|
||||
Settings.SelectedUser = k;
|
||||
AuthorizationStateChanged?.Invoke(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public static async void Logout()
|
||||
{
|
||||
Current?.Deauthenticate();
|
||||
Current.Dispose();
|
||||
Current.SubscriptionsChanged -= SubscriptionsChanged;
|
||||
await Current.Deauthenticate();
|
||||
|
||||
PasswordVault vault = new PasswordVault();
|
||||
vault.Remove(vault.Retrieve("FoxTube", Current.UserInfo.Id));
|
||||
UserInfos[Settings.SelectedUser] = null;
|
||||
tokens[Settings.SelectedUser] = null;
|
||||
storage.Values["users"] = JsonConvert.SerializeObject(UserInfos);
|
||||
|
||||
Current = null;
|
||||
|
||||
Settings.SelectedUser = -1;
|
||||
for (int i = 0; i < 5; i++)
|
||||
if (UserInfos[i] != null)
|
||||
{
|
||||
Settings.SelectedUser = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (Settings.SelectedUser >= 0)
|
||||
{
|
||||
try { Current = new User(await Authenticator.RefreshToken(Secrets[Settings.SelectedUser], tokens[Settings.SelectedUser])); }
|
||||
catch { Current = new User(await Authenticator.Authorize(Secrets[Settings.SelectedUser])); }
|
||||
|
||||
await Current.Initialize();
|
||||
Current.SubscriptionsChanged += SubscriptionsChanged;
|
||||
UserInfos[Settings.SelectedUser] = Current.UserInfo;
|
||||
}
|
||||
|
||||
AuthorizationStateChanged?.Invoke(Settings.SelectedUser > -1);
|
||||
}
|
||||
|
||||
public static async Task<TokenResponse> RefreshToken() =>
|
||||
await Authenticator.RefreshToken(Secrets[Settings.SelectedUser], tokens[Settings.SelectedUser]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
xmlns:ui="using:Microsoft.UI.Xaml.Controls"
|
||||
xmlns:data="using:Google.Apis.Oauth2.v2.Data"
|
||||
Orientation="Horizontal">
|
||||
|
||||
<ui:NavigationViewItem Width="42" Visibility="Visible" Name="manager" Padding="0" Tapped="Manager_Tapped">
|
||||
@@ -17,7 +18,7 @@
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Image Stretch="UniformToFill" MaxHeight="60" Source="https://yt3.ggpht.com/TckH7MhPH7UcxPnHeEj6R78xe1uOmrlHdUD1Usy7sr36xLDzl86NxAyfmDOIUrxl6kpiBgtKgA=w2276-fcrop64=1,00005a57ffffa5a8-nd-c0xffffffff-rj-k-no"/>
|
||||
<Image Stretch="UniformToFill" Name="banner" MaxHeight="60" Source="https://yt3.ggpht.com/TckH7MhPH7UcxPnHeEj6R78xe1uOmrlHdUD1Usy7sr36xLDzl86NxAyfmDOIUrxl6kpiBgtKgA=w2276-fcrop64=1,00005a57ffffa5a8-nd-c0xffffffff-rj-k-no"/>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -28,32 +29,38 @@
|
||||
<Grid.Background>
|
||||
<SolidColorBrush Color="Black" Opacity=".5"/>
|
||||
</Grid.Background>
|
||||
<PersonPicture HorizontalAlignment="Left" Margin="5" Height="50"/>
|
||||
<PersonPicture HorizontalAlignment="Left" Margin="5" Height="50" Name="avatar"/>
|
||||
<StackPanel VerticalAlignment="Center" Grid.Column="1">
|
||||
<TextBlock Text="Michael Gordeev" FontWeight="Bold"/>
|
||||
<TextBlock Text="michael.xfox@outlook.com" Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
<TextBlock Text="Michael Gordeev" FontWeight="Bold" Name="name"/>
|
||||
<TextBlock Text="michael.xfox@outlook.com" Style="{StaticResource CaptionTextBlockStyle}" Name="email"/>
|
||||
</StackPanel>
|
||||
<ui:NavigationViewItem Grid.Column="2" Width="42" Icon="BlockContact" Foreground="Red"/>
|
||||
<ui:NavigationViewItem Grid.Column="2" Width="42" Icon="BlockContact" Foreground="Red" Tapped="Logout_Tapped"/>
|
||||
</Grid>
|
||||
|
||||
<ui:NavigationViewList Grid.Row="1">
|
||||
<ui:NavigationViewItem Content="My channel" Icon="Contact"/>
|
||||
<ui:NavigationViewItem Content="Upload a video" Icon="Upload" Tapped="NavigationViewItem_Tapped"/>
|
||||
<ui:NavigationViewItem Content="My channel" Icon="Contact" Tapped="MyChannel_Tapped"/>
|
||||
<ui:NavigationViewItem Content="Upload a video" Icon="Upload" Tapped="Upload_Tapped"/>
|
||||
<ui:NavigationViewItemSeparator/>
|
||||
<ui:NavigationViewItem>
|
||||
<StackPanel Orientation="Horizontal" Padding="5" Margin="-5,0,0,0">
|
||||
<PersonPicture Height="20" Margin="-5,0,15,0">
|
||||
<PersonPicture.ProfilePicture>
|
||||
<BitmapImage UriSource="https://yt3.ggpht.com/a/AGF-l7_hHK5yjEPhlM72Tmk26zoOVkNx88pWoZnFVQ=s240-mo-c-c0xffffffff-rj-k-no" DecodePixelHeight="20" DecodePixelWidth="20"/>
|
||||
</PersonPicture.ProfilePicture>
|
||||
</PersonPicture>
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="BadComedian"/>
|
||||
<TextBlock Text="eugene.comedian@fondkino-sdoh.net" FontSize="11" Margin="0,-4,0,0"/>
|
||||
</ui:NavigationViewList>
|
||||
<ui:NavigationViewList Name="accountsList" ItemClick="AccountsList_ItemClick">
|
||||
<ui:NavigationViewList.ItemTemplate>
|
||||
<DataTemplate x:DataType="data:Userinfoplus">
|
||||
<StackPanel Orientation="Horizontal" Padding="5" Margin="-5,0,0,0">
|
||||
<PersonPicture Height="20" Margin="-5,0,15,0">
|
||||
<PersonPicture.ProfilePicture>
|
||||
<BitmapImage UriSource="{Binding Picture}" DecodePixelHeight="20" DecodePixelWidth="20"/>
|
||||
</PersonPicture.ProfilePicture>
|
||||
</PersonPicture>
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="{Binding Name}"/>
|
||||
<TextBlock Text="{Binding Email}" FontSize="11" Margin="0,-4,0,0"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ui:NavigationViewItem>
|
||||
<ui:NavigationViewItem Content="Add account" Icon="Add"/>
|
||||
</DataTemplate>
|
||||
</ui:NavigationViewList.ItemTemplate>
|
||||
<ui:NavigationViewList>
|
||||
</ui:NavigationViewList>
|
||||
<ui:NavigationViewItem Content="Add account" Icon="Add" Tapped="AddAccount_Tapped"/>
|
||||
<ui:NavigationViewItemSeparator/>
|
||||
<ui:NavigationViewItem Name="removeAds" Content="Remove ads (5$)" Icon="Shop" Tapped="RemoveAds_Tapped"/>
|
||||
<ui:NavigationViewItem Content="Settings" Icon="Setting" Tapped="Settings_Click"/>
|
||||
@@ -62,7 +69,7 @@
|
||||
</Flyout>
|
||||
</ui:NavigationViewItem.ContextFlyout>
|
||||
|
||||
<PersonPicture Height="25" Margin="-8,0,0,0"/>
|
||||
<PersonPicture Height="25" Margin="-8,0,0,0" Name="icon"/>
|
||||
</ui:NavigationViewItem>
|
||||
|
||||
<ui:NavigationViewItem Width="42" Name="account" Tapped="Manager_Tapped" Icon="AddFriend">
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Microsoft.AppCenter.Analytics;
|
||||
using Google.Apis.Oauth2.v2.Data;
|
||||
using Microsoft.AppCenter.Analytics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Windows.ApplicationModel.Resources;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.UI.Xaml;
|
||||
@@ -21,25 +23,21 @@ namespace FoxTube.Controls
|
||||
{
|
||||
public sealed partial class AccountManager : StackPanel
|
||||
{
|
||||
readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Main");
|
||||
|
||||
public AccountManager()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Service.Purchased += (purchased, price) =>
|
||||
{
|
||||
/*removeAds.Visibility = purchased ? Visibility.Collapsed : Visibility.Visible;
|
||||
removeAds.Content = $"{resources.GetString("/Main/adsFree/Content")} ({price})";*/
|
||||
removeAds.Visibility = purchased ? Visibility.Collapsed : Visibility.Visible;
|
||||
removeAds.Content = $"{resources.GetString("/Main/adsFree/Content")} ({price})";
|
||||
};
|
||||
|
||||
if (Service.AdsCheckPerformed)
|
||||
Service.CheckAddons();
|
||||
}
|
||||
|
||||
void SignIn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
void SignIn_Click(object sender, RoutedEventArgs e) =>
|
||||
UserManagement.CreateNew();
|
||||
Analytics.TrackEvent("Initialized authorization sequence");
|
||||
}
|
||||
|
||||
void Logout_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
@@ -47,18 +45,23 @@ namespace FoxTube.Controls
|
||||
UserManagement.Logout();
|
||||
}
|
||||
|
||||
public async void Logged() //=> await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
public async void Logged() => await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
account.Visibility = Visibility.Collapsed;
|
||||
|
||||
/*ToolTipService.SetToolTip(avatar, $"{UserManagement.Current.UserInfo.Name} ({UserManagement.Current.UserInfo.Email})");
|
||||
ToolTipService.SetToolTip(manager, $"{UserManagement.Current.UserInfo.Name} ({UserManagement.Current.UserInfo.Email})");
|
||||
name.Text = UserManagement.Current.UserInfo.Name;
|
||||
email.Text = UserManagement.Current.UserInfo.Email;
|
||||
banner.Source = new BitmapImage(UserManagement.Current.Channel.BrandingSettings.Image.BannerMobileLowImageUrl.ToUri());
|
||||
avatar.ProfilePicture = new BitmapImage(UserManagement.Current.UserInfo.Picture.ToUri()) { DecodePixelHeight = 65, DecodePixelWidth = 65 };
|
||||
(icon.Fill as ImageBrush).ImageSource = new BitmapImage(UserManagement.Current.UserInfo.Picture.ToUri()) { DecodePixelHeight = 25, DecodePixelWidth = 25 };*/
|
||||
icon.ProfilePicture = new BitmapImage(UserManagement.Current.UserInfo.Picture.ToUri()) { DecodePixelHeight = 25, DecodePixelWidth = 25 };
|
||||
|
||||
foreach (Userinfoplus user in UserManagement.UserInfos)
|
||||
if (user != UserManagement.Current.UserInfo)
|
||||
accountsList.Items.Add(user);
|
||||
|
||||
manager.Visibility = Visibility.Visible;
|
||||
}//);
|
||||
});
|
||||
|
||||
public void Quit()
|
||||
{
|
||||
@@ -66,27 +69,31 @@ namespace FoxTube.Controls
|
||||
account.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void NavigationViewItem_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Manager_Tapped(object sender, TappedRoutedEventArgs e) =>
|
||||
(sender as Microsoft.UI.Xaml.Controls.NavigationViewItem).ContextFlyout.ShowAt(sender as FrameworkElement);
|
||||
|
||||
private void Settings_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
void Settings_Click(object sender, RoutedEventArgs e) =>
|
||||
Navigation.GoToSettings();
|
||||
|
||||
}
|
||||
void Settings_Click(object sender, TappedRoutedEventArgs e) =>
|
||||
Navigation.GoToSettings();
|
||||
|
||||
private void Settings_Click(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
void RemoveAds_Tapped(object sender, TappedRoutedEventArgs e) =>
|
||||
Service.GetPro();
|
||||
|
||||
}
|
||||
void Upload_Tapped(object sender, TappedRoutedEventArgs e) =>
|
||||
Navigation.GoToUploadPage();
|
||||
|
||||
private void RemoveAds_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
void MyChannel_Tapped(object sender, TappedRoutedEventArgs e) =>
|
||||
Navigation.GoToChannel(UserManagement.Current.AccountId);
|
||||
|
||||
}
|
||||
void AccountsList_ItemClick(object sender, ItemClickEventArgs e) =>
|
||||
UserManagement.ChangeAccount(e.ClickedItem as Userinfoplus);
|
||||
|
||||
void AddAccount_Tapped(object sender, TappedRoutedEventArgs e) =>
|
||||
UserManagement.CreateNew();
|
||||
|
||||
void Logout_Tapped(object sender, TappedRoutedEventArgs e) =>
|
||||
UserManagement.Logout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace FoxTube.Controls
|
||||
/// <summary>
|
||||
/// Hides loading UI
|
||||
/// </summary>
|
||||
public void Complete(IEnumerable pivotCollection = null, int? selectedPivot = null)
|
||||
public void Complete(IList pivotCollection = null, int? selectedPivot = null)
|
||||
{
|
||||
Navigated?.Invoke(pivotCollection, selectedPivot);
|
||||
loaded.Storyboard.Begin();
|
||||
|
||||
@@ -151,7 +151,7 @@ namespace FoxTube.Controls
|
||||
content.GoBack();
|
||||
}
|
||||
|
||||
void Content_Navigated(System.Collections.IEnumerable pivotCollection, int? selectedPivot) =>
|
||||
void Content_Navigated(System.Collections.IList pivotCollection, int? selectedPivot) =>
|
||||
Navigated?.Invoke(pivotCollection, selectedPivot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
<Compile Include="App.xaml.cs">
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Classes\Authenticator.cs" />
|
||||
<Compile Include="Classes\Downloads.cs" />
|
||||
<Compile Include="Classes\Extensions.cs" />
|
||||
<Compile Include="Classes\Methods.cs" />
|
||||
@@ -248,19 +249,19 @@
|
||||
<Version>0.12.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Apis">
|
||||
<Version>1.30.0-beta02</Version>
|
||||
<Version>1.40.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Apis.Auth">
|
||||
<Version>1.30.0-beta02</Version>
|
||||
<Version>1.40.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Apis.Core">
|
||||
<Version>1.30.0-beta02</Version>
|
||||
<Version>1.40.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Apis.Oauth2.v2">
|
||||
<Version>1.29.2.994</Version>
|
||||
<Version>1.40.3.1602</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Apis.YouTube.v3">
|
||||
<Version>1.29.2.1006</Version>
|
||||
<Version>1.40.3.1663</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Advertising.XAML">
|
||||
<Version>10.1811.22001</Version>
|
||||
@@ -290,7 +291,7 @@
|
||||
<Version>4.3.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="YoutubeExplode">
|
||||
<Version>4.7.6</Version>
|
||||
<Version>4.7.8</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -28,7 +28,9 @@
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<uap:Extension Category="windows.protocol">
|
||||
<uap:Protocol Name="foxtube"/>
|
||||
<uap:Protocol Name="xfox111.foxtube">
|
||||
<uap:DisplayName>FoxTube</uap:DisplayName>
|
||||
</uap:Protocol >
|
||||
</uap:Extension>
|
||||
<Extension Category="windows.backgroundTasks" EntryPoint="FoxTube.Background.BackgroundProcessor">
|
||||
<BackgroundTasks>
|
||||
|
||||
@@ -23,20 +23,14 @@ namespace FoxTube.Pages
|
||||
Methods.SetTitleBar();
|
||||
|
||||
UserManagement.AuthorizationStateChanged += AuthorizationStateChanged;
|
||||
UserManagement.SubscriptionChanged += SubscriptionChanged;
|
||||
UserManagement.SubscriptionsChanged += SubscriptionChanged;
|
||||
|
||||
if (StoreServicesFeedbackLauncher.IsSupported())
|
||||
feedback.Visibility = Visibility.Visible;
|
||||
|
||||
UserManagement.Initialize();
|
||||
Service.CheckAddons();
|
||||
//TODO: Initialize stuff
|
||||
//TODO: Remove this method
|
||||
Begin();
|
||||
}
|
||||
|
||||
async void Begin()
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
FadeOut.Storyboard.Begin();
|
||||
}
|
||||
|
||||
void SetSplashScreen(SplashScreen splash)
|
||||
@@ -82,6 +76,7 @@ namespace FoxTube.Pages
|
||||
|
||||
void AuthorizationStateChanged(bool isAuthorized)
|
||||
{
|
||||
subscriptionsList.Items.Clear();
|
||||
if (isAuthorized)
|
||||
{
|
||||
accountManager.Logged();
|
||||
@@ -109,11 +104,12 @@ namespace FoxTube.Pages
|
||||
likedVideos.Visibility =
|
||||
watchLater.Visibility =
|
||||
subscriptionsHeader.Visibility = Visibility.Collapsed;
|
||||
|
||||
subscriptionsList.Items.Clear();
|
||||
}
|
||||
|
||||
content.AuthorizationChanged(isAuthorized);
|
||||
|
||||
if (splashScreen.Visibility == Visibility.Visible)
|
||||
FadeOut.Storyboard.Begin();
|
||||
}
|
||||
|
||||
public void UpdateView()
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
<Page
|
||||
x:Class="FoxTube.MainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
xmlns:ui="using:Microsoft.UI.Xaml.Controls"
|
||||
xmlns:controls="using:FoxTube.Controls">
|
||||
|
||||
<Grid Name="grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup>
|
||||
<VisualState>
|
||||
<VisualState.StateTriggers>
|
||||
<AdaptiveTrigger MinWindowWidth="{x:Bind nav.CompactModeThresholdWidth}"/>
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="Title.Margin" Value="0"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
|
||||
<Border x:Name="AppTitleBar"
|
||||
IsHitTestVisible="True"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent"
|
||||
Canvas.ZIndex="1">
|
||||
|
||||
<TextBlock x:Name="AppTitle"
|
||||
Text="FoxTube"
|
||||
VerticalAlignment="Center"
|
||||
Margin="12, 8, 0, 0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}" />
|
||||
</Border>
|
||||
|
||||
<ui:NavigationView SelectedItem="toHome" BackRequested="Nav_BackRequested" PaneClosing="Nav_PaneClosing" PaneOpened="Nav_PaneOpened" OpenPaneLength="300" Name="nav" SelectionChanged="Nav_SelectionChanged">
|
||||
<ui:NavigationView.Header>
|
||||
<Grid>
|
||||
<TextBlock Name="Title" Style="{StaticResource TitleTextBlockStyle}"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button x:Uid="/Main/feedback" Name="feedback" Click="Feedback_Click" Visibility="Collapsed" FontFamily="Segoe MDL2 Assets" Content="" Background="Transparent" Height="41" Width="60" FontSize="15"/>
|
||||
<Button x:Uid="/Main/signIn" Name="account" Click="SignIn_Click" Visibility="Collapsed" FontFamily="Segoe MDL2 Assets" Content="" Background="Transparent" Height="41" Width="60" FontSize="15"/>
|
||||
<Button Background="Transparent" Visibility="Collapsed" Name="avatar" Height="41" Width="60" FontSize="15" Padding="0">
|
||||
<Button.Flyout>
|
||||
<Flyout>
|
||||
<Grid Margin="-10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<PersonPicture Width="65" Name="avatarFlyout" VerticalAlignment="Top" Visibility="Collapsed"/>
|
||||
<StackPanel Grid.Column="1" Margin="5" Visibility="Collapsed">
|
||||
<TextBlock Name="myNameFlyout"/>
|
||||
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Name="myEmail"/>
|
||||
<HyperlinkButton x:Uid="/Main/signOut" Content="Log out" Click="Logout_Click"/>
|
||||
</StackPanel>
|
||||
<Grid Width="300">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="60"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Image Stretch="UniformToFill" MaxHeight="60" Source="https://yt3.ggpht.com/TckH7MhPH7UcxPnHeEj6R78xe1uOmrlHdUD1Usy7sr36xLDzl86NxAyfmDOIUrxl6kpiBgtKgA=w2276-fcrop64=1,00005a57ffffa5a8-nd-c0xffffffff-rj-k-no"/>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.Background>
|
||||
<SolidColorBrush Color="Black" Opacity=".5"/>
|
||||
</Grid.Background>
|
||||
<PersonPicture HorizontalAlignment="Left" Margin="5" Height="50"/>
|
||||
<StackPanel VerticalAlignment="Center" Grid.Column="1">
|
||||
<TextBlock Text="Michael Gordeev" FontWeight="Bold"/>
|
||||
<TextBlock Text="michael.xfox@outlook.com" Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="2" Background="Transparent" Foreground="Red">
|
||||
<FontIcon Glyph=""/>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<ui:NavigationViewList Grid.Row="1">
|
||||
<ui:NavigationViewItem Content="My channel" Icon="Contact"/>
|
||||
<ui:NavigationViewItem Content="Upload a video" Icon="Upload" Tapped="NavigationViewItem_Tapped"/>
|
||||
<ui:NavigationViewItemSeparator/>
|
||||
<ui:NavigationViewItem>
|
||||
<StackPanel Orientation="Horizontal" Padding="5" Margin="-5,0,0,0">
|
||||
<PersonPicture Height="20" Margin="-5,0,15,0">
|
||||
<PersonPicture.ProfilePicture>
|
||||
<BitmapImage UriSource="https://yt3.ggpht.com/a/AGF-l7_hHK5yjEPhlM72Tmk26zoOVkNx88pWoZnFVQ=s240-mo-c-c0xffffffff-rj-k-no" DecodePixelHeight="20" DecodePixelWidth="20"/>
|
||||
</PersonPicture.ProfilePicture>
|
||||
</PersonPicture>
|
||||
<TextBlock FontSize="14" Text="BadComedian"/>
|
||||
</StackPanel>
|
||||
</ui:NavigationViewItem>
|
||||
<ui:NavigationViewItem Content="Add account" Icon="Add"/>
|
||||
</ui:NavigationViewList>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
<Ellipse Width="25" Height="25">
|
||||
<Ellipse.Fill>
|
||||
<ImageBrush ImageSource="/Assets/Icons/profile.png"/>
|
||||
</Ellipse.Fill>
|
||||
</Ellipse>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ui:NavigationView.Header>
|
||||
|
||||
<ui:NavigationView.MenuItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" Padding="5" Margin="-5,0,0,0" Tag="{Binding Snippet.ResourceId.ChannelId}">
|
||||
<PersonPicture Height="20" Margin="-5,0,15,0">
|
||||
<PersonPicture.ProfilePicture>
|
||||
<BitmapImage UriSource="{Binding Snippet.Thumbnails.Medium.Url}" DecodePixelHeight="20" DecodePixelWidth="20"/>
|
||||
</PersonPicture.ProfilePicture>
|
||||
</PersonPicture>
|
||||
<TextBlock FontSize="14" Text="{Binding Snippet.Title}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ui:NavigationView.MenuItemTemplate>
|
||||
|
||||
<ui:NavigationView.MenuItems>
|
||||
<ui:NavigationViewItem x:Uid="/Main/home" Icon="Home" Content="Home" Name="toHome"/>
|
||||
<ui:NavigationViewItem x:Uid="/Main/myChannel" Icon="Contact" Content="My channel" Name="toChannel" Visibility="Collapsed"/>
|
||||
<ui:NavigationViewItem x:Uid="/Main/subscriptions" Icon="People" Content="Subscriptions" Name="toSubscriptions" Visibility="Collapsed"/>
|
||||
<ui:NavigationViewItemHeader x:Uid="/Main/myLibrary" FontSize="14" Content="My library" Name="libHeader" Visibility="Collapsed"/>
|
||||
<ui:NavigationViewItem x:Uid="/Main/history" Content="History" Name="toHistory" Visibility="Collapsed">
|
||||
<ui:NavigationViewItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</ui:NavigationViewItem.Icon>
|
||||
</ui:NavigationViewItem>
|
||||
<ui:NavigationViewItem x:Uid="/Main/liked" Icon="Like" Content="Liked videos" Name="toLiked" Visibility="Collapsed"/>
|
||||
<ui:NavigationViewItem x:Uid="/Main/later" Icon="Clock" Content="Watch later" Name="toLater" Visibility="Collapsed"/>
|
||||
<ui:NavigationViewItem x:Uid="/Main/downloads" Icon="Download" Content="Downloads" Name="toDownloads"/>
|
||||
<ui:NavigationViewItemHeader x:Uid="/Main/subscriptions" FontSize="14" Content="Subscriptions" Name="subsHeader" Visibility="Collapsed"/>
|
||||
</ui:NavigationView.MenuItems>
|
||||
|
||||
<ui:NavigationView.PaneFooter>
|
||||
<ui:NavigationViewItem Content="Remove ads" Visibility="Collapsed" Tapped="RemoveAds_Tapped" Name="removeAds">
|
||||
<ui:NavigationViewItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</ui:NavigationViewItem.Icon>
|
||||
</ui:NavigationViewItem>
|
||||
</ui:NavigationView.PaneFooter>
|
||||
|
||||
<ui:NavigationView.AutoSuggestBox>
|
||||
<AutoSuggestBox x:Name="search" QueryIcon="Find" QuerySubmitted="Search_QuerySubmitted" TextChanged="Search_TextChanged" x:Uid="/Main/searchPlaceholder" PlaceholderText="Search"/>
|
||||
</ui:NavigationView.AutoSuggestBox>
|
||||
|
||||
<Grid>
|
||||
<controls:ContentFrame x:Name="content" Navigated="Content_Navigated"/>
|
||||
<controls:ContentFrame x:Name="videoPlaceholder"/>
|
||||
</Grid>
|
||||
|
||||
</ui:NavigationView>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,569 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.UI;
|
||||
using Windows.UI.ViewManagement;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using Windows.UI.Xaml.Media.Imaging;
|
||||
using System.Xml;
|
||||
using Google.Apis.YouTube.v3.Data;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.System;
|
||||
using FoxTube.Pages;
|
||||
using Windows.UI.Popups;
|
||||
using Windows.ApplicationModel.Resources;
|
||||
using Microsoft.Services.Store.Engagement;
|
||||
using Windows.UI.Xaml.Shapes;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using FoxTube.Controls;
|
||||
|
||||
namespace FoxTube
|
||||
{
|
||||
/// <summary>
|
||||
/// Main app's layout
|
||||
/// </summary>
|
||||
public sealed partial class MainPage : Page
|
||||
{
|
||||
bool wasInvoked = false;
|
||||
readonly ResourceLoader resources = ResourceLoader.GetForCurrentView("Main");
|
||||
Dictionary<Type, string> headers;
|
||||
|
||||
public ContentFrame PageContent => content;
|
||||
public ContentFrame VideoContent => videoPlaceholder;
|
||||
|
||||
public MainPage()
|
||||
{
|
||||
headers = new Dictionary<Type, string>()
|
||||
{
|
||||
{ typeof(Settings), resources.GetString("/Main/settings/Content") },
|
||||
{ typeof(ChannelPage), resources.GetString("/Main/channel") },
|
||||
{ typeof(PlaylistPage), resources.GetString("/Main/playlist") },
|
||||
{ typeof(Search), resources.GetString("/Main/searchPlaceholder/PlaceholderText") },
|
||||
{ typeof(Subscriptions), resources.GetString("/Main/subscriptions/Content") },
|
||||
{ typeof(History), resources.GetString("/Main/history/Content") },
|
||||
{ typeof(Home), resources.GetString("/Main/home/Content") },
|
||||
{ typeof(UploadPage), "Uploading a video" },
|
||||
{ typeof(Downloads), resources.GetString("/Main/downloads/Content") }
|
||||
};
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
Window.Current.SetTitleBar(AppTitleBar);
|
||||
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true;
|
||||
CoreApplication.GetCurrentView().TitleBar.LayoutMetricsChanged += (s, e) => SetTitleBar(s);
|
||||
|
||||
SecretsVault.AuthorizationStateChanged += AuthorizationStateChanged;
|
||||
SecretsVault.SubscriptionsChanged += SecretsVault_SubscriptionsChanged;
|
||||
SecretsVault.Purchased += async (sender, e) =>
|
||||
{
|
||||
removeAds.Visibility = (e[0] as bool?).Value ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
if (!(bool)e[0])
|
||||
{
|
||||
removeAds.Content = $"{resources.GetString("/Main/adsFree/Content")} ({e[1]})";
|
||||
return;
|
||||
}
|
||||
|
||||
MessageDialog dialog = new MessageDialog(resources.GetString("/Main/purchaseSuccess"));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/Main/close"), (command) => Methods.CloseApp()));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/Main/delay")));
|
||||
dialog.CancelCommandIndex = 1;
|
||||
dialog.DefaultCommandIndex = 0;
|
||||
await dialog.ShowAsync();
|
||||
};
|
||||
SecretsVault.Initialize();
|
||||
|
||||
if(StoreServicesFeedbackLauncher.IsSupported())
|
||||
feedback.Visibility = Visibility.Visible;
|
||||
|
||||
PromptFeedback();
|
||||
}
|
||||
|
||||
async void PromptFeedback()
|
||||
{
|
||||
if (SettingsStorage.Uptime.TotalHours >= 12 && SettingsStorage.PromptFeedback)
|
||||
{
|
||||
MessageDialog dialog = new MessageDialog(resources.GetString("/Main/feedbackMessage"));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/Main/dontAsk"), (command) => SettingsStorage.PromptFeedback = false));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/Main/promptLater")));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/Main/sure"), async (command) =>
|
||||
{
|
||||
SettingsStorage.PromptFeedback = false;
|
||||
if (StoreServicesFeedbackLauncher.IsSupported())
|
||||
await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync();
|
||||
else
|
||||
{
|
||||
MessageDialog message = new MessageDialog(resources.GetString("/Main/feedbackFail"));
|
||||
message.Commands.Add(new UICommand(resources.GetString("/Main/sendEmail"), async (c) => await Launcher.LaunchUriAsync("mailto:michael.xfox@outlook.com".ToUri())));
|
||||
message.Commands.Add(new UICommand(resources.GetString("/Main/goBack")));
|
||||
message.CancelCommandIndex = 1;
|
||||
message.DefaultCommandIndex = 0;
|
||||
await message.ShowAsync();
|
||||
}
|
||||
}));
|
||||
dialog.DefaultCommandIndex = 2;
|
||||
dialog.CancelCommandIndex = 1;
|
||||
await dialog.ShowAsync();
|
||||
}
|
||||
|
||||
if (SettingsStorage.Uptime.TotalHours >= 24 && SettingsStorage.PromptReview)
|
||||
{
|
||||
MessageDialog dialog = new MessageDialog(resources.GetString("/Main/rate"));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/Main/dontAsk"), (command) => SettingsStorage.PromptReview = false));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/Main/promptLater")));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/Main/sure"), async (command) =>
|
||||
{
|
||||
SettingsStorage.PromptReview = false;
|
||||
await Launcher.LaunchUriAsync("ms-windows-store://review/?ProductId=9NCQQXJTDLFH".ToUri());
|
||||
}));
|
||||
dialog.DefaultCommandIndex = 2;
|
||||
dialog.CancelCommandIndex = 1;
|
||||
await dialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTitleBar(CoreApplicationViewTitleBar coreTitleBar = null)
|
||||
{
|
||||
if (coreTitleBar != null)
|
||||
{
|
||||
bool full = ApplicationView.GetForCurrentView().IsFullScreenMode;
|
||||
double left = 12 + (full ? 0 : coreTitleBar.SystemOverlayLeftInset);
|
||||
AppTitle.Margin = new Thickness(left, 8, 0, 0);
|
||||
AppTitleBar.Height = coreTitleBar.Height;
|
||||
}
|
||||
|
||||
var titleBar = ApplicationView.GetForCurrentView().TitleBar;
|
||||
|
||||
titleBar.ButtonBackgroundColor = Colors.Transparent;
|
||||
titleBar.ButtonHoverBackgroundColor = Colors.IndianRed;
|
||||
titleBar.ButtonPressedBackgroundColor = Colors.DarkRed;
|
||||
titleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
|
||||
titleBar.ButtonInactiveForegroundColor = Colors.Gray;
|
||||
|
||||
if(RequestedTheme == ElementTheme.Dark || (RequestedTheme == ElementTheme.Default && Application.Current.RequestedTheme == ApplicationTheme.Dark))
|
||||
titleBar.ButtonForegroundColor = Colors.White;
|
||||
else
|
||||
titleBar.ButtonForegroundColor = Colors.Black;
|
||||
}
|
||||
|
||||
private void SecretsVault_SubscriptionsChanged(object sender, params object[] args)
|
||||
{
|
||||
switch((string)args[0])
|
||||
{
|
||||
case "add":
|
||||
if (nav.MenuItems.Count < 19)
|
||||
nav.MenuItems.Add(args[1] as Subscription);
|
||||
break;
|
||||
|
||||
case "remove":
|
||||
if (nav.MenuItems.Find(i => ((i as Microsoft.UI.Xaml.Controls.NavigationViewItem).Content as StackPanel).Tag.ToString() == (string)args[1]) is Microsoft.UI.Xaml.Controls.NavigationViewItem item)
|
||||
{
|
||||
nav.MenuItems.Remove(item);
|
||||
if (SecretsVault.Subscriptions.Count >= 10)
|
||||
nav.MenuItems.Add(SecretsVault.Subscriptions[9]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async void AuthorizationStateChanged(object sender, params object[] e)
|
||||
{
|
||||
wasInvoked = false;
|
||||
|
||||
switch (e[0] as bool?)
|
||||
{
|
||||
case true:
|
||||
account.Visibility = Visibility.Collapsed;
|
||||
|
||||
ToolTipService.SetToolTip(avatar, $"{SecretsVault.UserInfo.Name} ({SecretsVault.UserInfo.Email})");
|
||||
myNameFlyout.Text = SecretsVault.UserInfo.Name;
|
||||
myEmail.Text = SecretsVault.UserInfo.Email;
|
||||
avatarFlyout.ProfilePicture = new BitmapImage(SecretsVault.UserInfo.Picture.ToUri()) { DecodePixelHeight = 65, DecodePixelWidth = 65 };
|
||||
((avatar.Content as Ellipse).Fill as ImageBrush).ImageSource = new BitmapImage(SecretsVault.UserInfo.Picture.ToUri()) { DecodePixelHeight = 25, DecodePixelWidth = 25 };
|
||||
|
||||
avatar.Visibility = Visibility.Visible;
|
||||
|
||||
toChannel.Visibility = Visibility.Visible;
|
||||
toSubscriptions.Visibility = Visibility.Visible;
|
||||
libHeader.Visibility = Visibility.Visible;
|
||||
toHistory.Visibility = Visibility.Visible;
|
||||
toLiked.Visibility = Visibility.Visible;
|
||||
toLater.Visibility = Visibility.Visible;
|
||||
|
||||
if (SecretsVault.Subscriptions.Count > 0)
|
||||
{
|
||||
subsHeader.Visibility = Visibility.Visible;
|
||||
for (int k = 0; k < SecretsVault.Subscriptions.Count && k < 10; k++)
|
||||
nav.MenuItems.Add(SecretsVault.Subscriptions[k]);
|
||||
}
|
||||
HistorySet.Load();
|
||||
|
||||
if (content.Frame.Content != null)
|
||||
content.Refresh();
|
||||
else
|
||||
content.Frame.Navigate(typeof(Home));
|
||||
break;
|
||||
|
||||
case false:
|
||||
content.Frame.Navigate(typeof(Home));
|
||||
|
||||
for (int k = nav.MenuItems.Count - 1; k > 8; k--)
|
||||
nav.MenuItems.RemoveAt(k);
|
||||
|
||||
account.Visibility = Visibility.Visible;
|
||||
avatar.Visibility = Visibility.Collapsed;
|
||||
|
||||
toChannel.Visibility = Visibility.Collapsed;
|
||||
toSubscriptions.Visibility = Visibility.Collapsed;
|
||||
libHeader.Visibility = Visibility.Collapsed;
|
||||
toHistory.Visibility = Visibility.Collapsed;
|
||||
toLiked.Visibility = Visibility.Collapsed;
|
||||
toLater.Visibility = Visibility.Collapsed;
|
||||
subsHeader.Visibility = Visibility.Collapsed;
|
||||
|
||||
subsHeader.Visibility = Visibility.Collapsed;
|
||||
|
||||
content.Frame.BackStack.Clear();
|
||||
content.Frame.ForwardStack.Clear();
|
||||
break;
|
||||
|
||||
default:
|
||||
MessageDialog dialog = new MessageDialog(resources.GetString("/Main/connectErrContent"), resources.GetString("/Main/connectErrHeader"));
|
||||
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/Main/tryAgain"), (command) =>
|
||||
{
|
||||
SecretsVault.Authorize();
|
||||
}));
|
||||
dialog.Commands.Add(new UICommand(resources.GetString("/Main/quit"), (command) =>
|
||||
{
|
||||
Methods.CloseApp();
|
||||
}));
|
||||
|
||||
dialog.CancelCommandIndex = 1;
|
||||
dialog.DefaultCommandIndex = 0;
|
||||
|
||||
await dialog.ShowAsync();
|
||||
break;
|
||||
}
|
||||
|
||||
if (videoPlaceholder.Frame.Content != null)
|
||||
{
|
||||
MaximizeVideo();
|
||||
videoPlaceholder.Refresh();
|
||||
}
|
||||
|
||||
DownloadAgent.Initialize();
|
||||
}
|
||||
|
||||
private async void Feedback_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync();
|
||||
}
|
||||
|
||||
private void SignIn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SecretsVault.Authorize();
|
||||
}
|
||||
|
||||
private void Logout_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
avatar.Flyout.Hide();
|
||||
SecretsVault.Deauthenticate();
|
||||
}
|
||||
|
||||
public void GoToSearch(SearchParameters args)
|
||||
{
|
||||
content.Frame.Navigate(typeof(Search), new object[] { args, content });
|
||||
}
|
||||
|
||||
public void GoToChannel(string id)
|
||||
{
|
||||
content.Frame.Navigate(typeof(ChannelPage), id);
|
||||
}
|
||||
|
||||
public void GoToHome()
|
||||
{
|
||||
content.Frame.Navigate(typeof(Home));
|
||||
}
|
||||
|
||||
public void GoToVideo(string id, string playlistId = null, bool incognito = false)
|
||||
{
|
||||
MaximizeVideo();
|
||||
|
||||
nav.IsBackEnabled = true;
|
||||
nav.ExpandedModeThresholdWidth = short.MaxValue;
|
||||
nav.IsPaneOpen = false;
|
||||
|
||||
videoPlaceholder.Frame.Navigate(typeof(VideoPage), new object[3] { id, playlistId, incognito });
|
||||
|
||||
Title.Text = resources.GetString("/Main/video");
|
||||
}
|
||||
|
||||
public void GoToDeveloper(string id)
|
||||
{
|
||||
content.Frame.Navigate(typeof(Settings), id);
|
||||
}
|
||||
|
||||
public void GoToPlaylist(string id)
|
||||
{
|
||||
content.Frame.Navigate(typeof(PlaylistPage), id);
|
||||
}
|
||||
|
||||
public void GoToHistory()
|
||||
{
|
||||
content.Frame.Navigate(typeof(History));
|
||||
}
|
||||
|
||||
public void GoToDownloads()
|
||||
{
|
||||
content.Frame.Navigate(typeof(Downloads));
|
||||
}
|
||||
|
||||
public void MinimizeAsInitializer()
|
||||
{
|
||||
if (videoPlaceholder.Frame.Content == null)
|
||||
return;
|
||||
|
||||
if (videoPlaceholder.LoadingPage.State != LoadingState.Loaded)
|
||||
CloseVideo();
|
||||
else
|
||||
(videoPlaceholder.Frame.Content as VideoPage).Player.Minimize();
|
||||
|
||||
Title.Text = headers[content.Frame.SourcePageType];
|
||||
}
|
||||
|
||||
public void MinimizeVideo()
|
||||
{
|
||||
videoPlaceholder.Width = 432;
|
||||
videoPlaceholder.Height = 243;
|
||||
videoPlaceholder.VerticalAlignment = VerticalAlignment.Bottom;
|
||||
videoPlaceholder.HorizontalAlignment = HorizontalAlignment.Right;
|
||||
videoPlaceholder.Margin = new Thickness(0, 0, 25, 50);
|
||||
|
||||
if (content.Frame.CanGoBack)
|
||||
nav.IsBackEnabled = true;
|
||||
else
|
||||
nav.IsBackEnabled = false;
|
||||
|
||||
SetNavigationMenu();
|
||||
|
||||
Title.Text = headers[content.Frame.SourcePageType];
|
||||
}
|
||||
|
||||
void SetNavigationMenu()
|
||||
{
|
||||
if (content.Frame.SourcePageType == typeof(Home) || content.Frame.SourcePageType == typeof(Settings) || content.Frame.SourcePageType == typeof(Subscriptions))
|
||||
{
|
||||
nav.ExpandedModeThresholdWidth = 1008;
|
||||
nav.IsPaneOpen = nav.DisplayMode == Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Expanded ? true : false;
|
||||
}
|
||||
else
|
||||
{
|
||||
nav.ExpandedModeThresholdWidth = short.MaxValue;
|
||||
nav.IsPaneOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void MaximizeVideo()
|
||||
{
|
||||
videoPlaceholder.Width = double.NaN;
|
||||
videoPlaceholder.Height = double.NaN;
|
||||
videoPlaceholder.VerticalAlignment = VerticalAlignment.Stretch;
|
||||
videoPlaceholder.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||
videoPlaceholder.Margin = new Thickness(0);
|
||||
|
||||
if (videoPlaceholder.Frame.Content == null)
|
||||
return;
|
||||
|
||||
nav.IsBackEnabled = true;
|
||||
Title.Text = resources.GetString("/Main/video");
|
||||
nav.ExpandedModeThresholdWidth = short.MaxValue;
|
||||
nav.IsPaneOpen = false;
|
||||
}
|
||||
|
||||
public void CloseVideo()
|
||||
{
|
||||
if (ApplicationView.GetForCurrentView().IsFullScreenMode)
|
||||
ApplicationView.GetForCurrentView().ExitFullScreenMode();
|
||||
|
||||
videoPlaceholder.Frame.Content = null;
|
||||
GC.Collect();
|
||||
MaximizeVideo();
|
||||
|
||||
nav.IsBackEnabled = content.Frame.CanGoBack;
|
||||
|
||||
SetNavigationMenu();
|
||||
|
||||
Title.Text = headers[content.Frame.SourcePageType];
|
||||
}
|
||||
|
||||
private void Search_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(search.Text))
|
||||
GoToSearch(new SearchParameters(search.Text));
|
||||
}
|
||||
|
||||
private async void Search_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
|
||||
{
|
||||
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
if (search.Text.Length > 2 && args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
|
||||
{
|
||||
try
|
||||
{
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load($"http://suggestqueries.google.com/complete/search?ds=yt&client=toolbar&q={search.Text}&hl={SettingsStorage.RelevanceLanguage}");
|
||||
|
||||
List<string> suggestions = new List<string>();
|
||||
|
||||
for (int i = 0; i < doc["toplevel"].ChildNodes.Count && i < 5; i++)
|
||||
suggestions.Add(doc["toplevel"].ChildNodes[i]["suggestion"].GetAttribute("data"));
|
||||
|
||||
search.ItemsSource = suggestions;
|
||||
}
|
||||
catch { search.ItemsSource = new List<string>(); }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SetNavigationItem(object item)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (nav.SelectedItem != item)
|
||||
nav.SelectedItem = item;
|
||||
else
|
||||
wasInvoked = false;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public void Content_Navigated(object sender, NavigationEventArgs e)
|
||||
{
|
||||
Title.Text = headers[content.Frame.SourcePageType];
|
||||
|
||||
if (!wasInvoked)
|
||||
{
|
||||
wasInvoked = true;
|
||||
|
||||
if (e.SourcePageType == typeof(Settings))
|
||||
SetNavigationItem(nav.SettingsItem);
|
||||
else if (e.SourcePageType == typeof(Subscriptions))
|
||||
SetNavigationItem(toSubscriptions);
|
||||
else if (e.SourcePageType == typeof(Downloads))
|
||||
SetNavigationItem(toDownloads);
|
||||
else if (e.SourcePageType == typeof(Home))
|
||||
SetNavigationItem(toHome);
|
||||
else if (e.SourcePageType == typeof(Search))
|
||||
SetNavigationItem(null);
|
||||
else if(e.SourcePageType == typeof(ChannelPage))
|
||||
{
|
||||
if (SecretsVault.IsAuthorized)
|
||||
{
|
||||
if (e.Parameter.ToString() == SecretsVault.AccountId)
|
||||
SetNavigationItem(toChannel);
|
||||
else if (nav.MenuItems.Contains(SecretsVault.Subscriptions.Find(i => i.Snippet.ResourceId.ChannelId == e.Parameter.ToString())))
|
||||
SetNavigationItem(SecretsVault.Subscriptions.Find(i => i.Snippet.ResourceId.ChannelId == e.Parameter.ToString()));
|
||||
else
|
||||
SetNavigationItem(null);
|
||||
}
|
||||
else
|
||||
SetNavigationItem(null);
|
||||
}
|
||||
else if(e.SourcePageType == typeof(History))
|
||||
SetNavigationItem(toHistory);
|
||||
else if(e.SourcePageType == typeof(PlaylistPage))
|
||||
{
|
||||
if (SecretsVault.IsAuthorized)
|
||||
{
|
||||
if (e.Parameter.ToString() == SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.Likes)
|
||||
SetNavigationItem(toLiked);
|
||||
else if (e.Parameter.Equals("WL"))
|
||||
SetNavigationItem(toLater);
|
||||
}
|
||||
else
|
||||
SetNavigationItem(null);
|
||||
}
|
||||
}
|
||||
else
|
||||
wasInvoked = false;
|
||||
|
||||
nav.IsBackEnabled = content.Frame.CanGoBack;
|
||||
|
||||
SetNavigationMenu();
|
||||
|
||||
if (videoPlaceholder.Frame.Content != null && videoPlaceholder.HorizontalAlignment == HorizontalAlignment.Stretch)
|
||||
MinimizeAsInitializer();
|
||||
}
|
||||
|
||||
private void RemoveAds_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
SecretsVault.GetAdblock();
|
||||
}
|
||||
|
||||
private void Nav_SelectionChanged(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewSelectionChangedEventArgs args)
|
||||
{
|
||||
if (!wasInvoked)
|
||||
{
|
||||
if (content == null)
|
||||
return;
|
||||
wasInvoked = true;
|
||||
if (args.IsSettingsSelected)
|
||||
content.Frame.Navigate(typeof(Settings));
|
||||
else
|
||||
{
|
||||
if (args.SelectedItem == toHome)
|
||||
content.Frame.Navigate(typeof(Home));
|
||||
else if (args.SelectedItem == toHistory)
|
||||
content.Frame.Navigate(typeof(History));
|
||||
else if (args.SelectedItem == toLiked)
|
||||
content.Frame.Navigate(typeof(PlaylistPage), SecretsVault.UserChannel.ContentDetails.RelatedPlaylists.Likes);
|
||||
else if (args.SelectedItem == toLater)
|
||||
content.Frame.Navigate(typeof(PlaylistPage), "WL");
|
||||
else if (args.SelectedItem == toSubscriptions)
|
||||
content.Frame.Navigate(typeof(Subscriptions));
|
||||
else if (args.SelectedItem == toDownloads)
|
||||
content.Frame.Navigate(typeof(Downloads));
|
||||
else if (args.SelectedItem == toChannel)
|
||||
content.Frame.Navigate(typeof(ChannelPage), SecretsVault.UserChannel.Id);
|
||||
else
|
||||
content.Frame.Navigate(typeof(ChannelPage), (args.SelectedItem as Subscription).Snippet.ResourceId.ChannelId);
|
||||
}
|
||||
}
|
||||
else
|
||||
wasInvoked = false;
|
||||
}
|
||||
|
||||
private void Nav_BackRequested(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewBackRequestedEventArgs args)
|
||||
{
|
||||
if (videoPlaceholder.Frame.Content != null && double.IsNaN(videoPlaceholder.Width))
|
||||
{
|
||||
if (videoPlaceholder.Frame.CanGoBack)
|
||||
videoPlaceholder.Frame.GoBack();
|
||||
else if (videoPlaceholder.LoadingPage.State != LoadingState.Loaded)
|
||||
CloseVideo();
|
||||
else
|
||||
MinimizeAsInitializer();
|
||||
}
|
||||
else
|
||||
content.Frame.GoBack();
|
||||
}
|
||||
|
||||
private void Nav_PaneClosing(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewPaneClosingEventArgs args)
|
||||
{
|
||||
AppTitle.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void Nav_PaneOpened(Microsoft.UI.Xaml.Controls.NavigationView sender, object args)
|
||||
{
|
||||
AppTitle.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void NavigationViewItem_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
content.Frame.Navigate(typeof(UploadPage));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<Page
|
||||
x:Class="FoxTube.Pages.UploadPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:FoxTube.Pages"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Margin="10">
|
||||
<MediaElement PosterSource="/Assets/videoThumbSample.png" AreTransportControlsEnabled="True" VerticalAlignment="Top" Width="400">
|
||||
<MediaElement.TransportControls>
|
||||
<MediaTransportControls IsCompact="True" IsZoomButtonVisible="False"/>
|
||||
</MediaElement.TransportControls>
|
||||
</MediaElement>
|
||||
<TextBlock>
|
||||
Your video will be live at: <Hyperlink>https://youtu.be/hulumulu</Hyperlink>
|
||||
</TextBlock>
|
||||
<ComboBox Margin="0,10" Header="Visibility" SelectedIndex="0" HorizontalAlignment="Stretch">
|
||||
<ComboBoxItem Content="Public"/>
|
||||
<ComboBoxItem Content="Private"/>
|
||||
<ComboBoxItem Content="Unlisted"/>
|
||||
</ComboBox>
|
||||
<Button Content="Add to playlists">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<ToggleMenuFlyoutItem Text="Watch later" Icon="Clock"/>
|
||||
<MenuFlyoutItem Text="Create playlist" Icon="Add"/>
|
||||
<MenuFlyoutSeparator/>
|
||||
<ToggleMenuFlyoutItem Text="Some other dipshit playlist">
|
||||
<ToggleMenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</ToggleMenuFlyoutItem.Icon>
|
||||
</ToggleMenuFlyoutItem>
|
||||
<ToggleMenuFlyoutItem Text="Lorem Ipsum, bleat">
|
||||
<ToggleMenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph=""/>
|
||||
</ToggleMenuFlyoutItem.Icon>
|
||||
</ToggleMenuFlyoutItem>
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="0 playlists are selected"/>
|
||||
</StackPanel>
|
||||
|
||||
<Pivot Grid.Column="1">
|
||||
<Pivot.RightHeader>
|
||||
<StackPanel Margin="10,0" Orientation="Horizontal">
|
||||
<Grid>
|
||||
<ProgressBar Height="32" Width="300" Value="50" Foreground="YellowGreen"/>
|
||||
<TextBlock Text="⌛ Uploading: 50%" VerticalAlignment="Center" Margin="10,0" Foreground="White"/>
|
||||
<ProgressRing HorizontalAlignment="Right" Height="32" IsActive="True" Margin="10,0"/>
|
||||
</Grid>
|
||||
<Button FontWeight="SemiBold" Foreground="White" Background="Red" Content="Upload video!" Margin="5,0"/>
|
||||
</StackPanel>
|
||||
</Pivot.RightHeader>
|
||||
|
||||
<PivotItem Header="General">
|
||||
<StackPanel>
|
||||
<TextBox Header="Title"/>
|
||||
<TextBox Header="Description" AcceptsReturn="True" Height="auto" MinHeight="128"/>
|
||||
<TextBox Header="Tags"/>
|
||||
<RichTextBlock TextWrapping="Wrap" IsTextSelectionEnabled="False" Margin="0,5">
|
||||
<Paragraph>
|
||||
<InlineUIContainer>
|
||||
<Button Background="Transparent" Padding="0">
|
||||
<Grid>
|
||||
<TextBlock FontFamily="Segoe UI, Segoe MDL2 Assets" Text=" Remove tag" Foreground="Red" Visibility="Collapsed"/>
|
||||
<Border CornerRadius="5" Background="Red" Padding="5" Visibility="Visible">
|
||||
<TextBlock Text="Hello, World" Foreground="White" FontWeight="SemiBold"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Button>
|
||||
</InlineUIContainer>
|
||||
<InlineUIContainer>
|
||||
<Button Background="Transparent" Padding="0">
|
||||
<Grid>
|
||||
<TextBlock FontFamily="Segoe UI, Segoe MDL2 Assets" Text=" Remove tag" Foreground="Red" Visibility="Collapsed"/>
|
||||
<Border CornerRadius="5" Background="Red" Padding="5" Visibility="Visible">
|
||||
<TextBlock Text="This is a tag" Foreground="White" FontWeight="SemiBold"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Button>
|
||||
</InlineUIContainer>
|
||||
<InlineUIContainer>
|
||||
<Button Background="Transparent" Padding="0">
|
||||
<Grid>
|
||||
<TextBlock FontFamily="Segoe UI, Segoe MDL2 Assets" Text=" Remove tag" Foreground="Red" Visibility="Collapsed"/>
|
||||
<Border CornerRadius="5" Background="Red" Padding="5" Visibility="Visible">
|
||||
<TextBlock Text="FoxTube" Foreground="White" FontWeight="SemiBold"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Button>
|
||||
</InlineUIContainer>
|
||||
<InlineUIContainer>
|
||||
<Button Background="Transparent" Padding="0">
|
||||
<Grid>
|
||||
<TextBlock FontFamily="Segoe UI, Segoe MDL2 Assets" Text=" Remove tag" Foreground="Red" Visibility="Collapsed"/>
|
||||
<Border CornerRadius="5" Background="Red" Padding="5" Visibility="Visible">
|
||||
<TextBlock Text="YouTube" Foreground="White" FontWeight="SemiBold"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Button>
|
||||
</InlineUIContainer>
|
||||
<InlineUIContainer>
|
||||
<Button Background="Transparent" Padding="0">
|
||||
<Grid>
|
||||
<TextBlock FontFamily="Segoe UI, Segoe MDL2 Assets" Text=" Remove tag" Foreground="Red" Visibility="Collapsed"/>
|
||||
<Border CornerRadius="5" Background="Red" Padding="5" Visibility="Visible">
|
||||
<TextBlock Text="FuckTube" Foreground="White" FontWeight="SemiBold"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Button>
|
||||
</InlineUIContainer>
|
||||
<InlineUIContainer>
|
||||
<Button Background="Transparent" Padding="0">
|
||||
<Grid>
|
||||
<TextBlock FontFamily="Segoe UI, Segoe MDL2 Assets" Text=" Remove tag" Foreground="Red" Visibility="Collapsed"/>
|
||||
<Border CornerRadius="5" Background="Red" Padding="5" Visibility="Visible">
|
||||
<TextBlock Text="WTFTube" Foreground="White" FontWeight="SemiBold"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Button>
|
||||
</InlineUIContainer>
|
||||
<InlineUIContainer>
|
||||
<Button Background="Transparent" Padding="0">
|
||||
<Grid>
|
||||
<TextBlock FontFamily="Segoe UI, Segoe MDL2 Assets" Text=" Remove tag" Foreground="Red" Visibility="Collapsed"/>
|
||||
<Border CornerRadius="5" Background="Red" Padding="5" Visibility="Visible">
|
||||
<TextBlock Text="Tuuube" Foreground="White" FontWeight="SemiBold"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Button>
|
||||
</InlineUIContainer>
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
<!--<RichTextBlock TextWrapping="Wrap">
|
||||
<InlineUIContainer>
|
||||
<Border CornerRadius="5" Background="Red" Padding="5" Margin="5,0">
|
||||
<TextBlock Text="Hello, World" Foreground="White" FontWeight="SemiBold"/>
|
||||
</Border>
|
||||
</InlineUIContainer>
|
||||
</RichTextBlock>-->
|
||||
</StackPanel>
|
||||
</PivotItem>
|
||||
<PivotItem Header="Localization">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Margin="10,0">
|
||||
<ComboBox Header="Original language" PlaceholderText="Select language" HorizontalAlignment="Stretch"/>
|
||||
<TextBlock Text="Title" Margin="0,42,0,5"/>
|
||||
<TextBlock IsTextSelectionEnabled="True" Text="Title text" Margin="0,5"/>
|
||||
<TextBlock Text="Description" Margin="0,5"/>
|
||||
<TextBlock IsTextSelectionEnabled="True" Text="Description text" Margin="0,5"/>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="10,0" Grid.Column="1">
|
||||
<ComboBox Header="Translate to" PlaceholderText="Select language" HorizontalAlignment="Stretch"/>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,5">
|
||||
<Button Content=" Merge text from original language" FontFamily="Segoe UI, Segoe MDL2 Assets"/>
|
||||
<Button Content=" Remove translation" FontFamily="Segoe UI, Segoe MDL2 Assets" Background="Red" Foreground="White" Margin="5,0"/>
|
||||
</StackPanel>
|
||||
<TextBox Header="Title"/>
|
||||
<TextBox Height="auto" Header="Description" AcceptsReturn="True" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</PivotItem>
|
||||
<PivotItem Header="Metadata">
|
||||
|
||||
</PivotItem>
|
||||
</Pivot>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
|
||||
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
|
||||
|
||||
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 UploadPage : Page
|
||||
{
|
||||
public UploadPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
Methods.MainPage.PageContent.LoadingPage.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user