Archived
1
0

Core refactoring (app doesn't work)

This commit is contained in:
Michael Gordeev
2020-05-09 23:16:19 +03:00
parent 2b1f06dd93
commit 2a987e33a2
35 changed files with 1135 additions and 817 deletions
+114
View File
@@ -0,0 +1,114 @@
using FoxTube.Utils;
using SQLitePCL;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml;
using Windows.UI;
namespace FoxTube
{
public static class Extensions
{
public static Uri ToUri(this string url)
{
Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out Uri result);
return result;
}
public static string ReplaceInvalidChars(this string str, char newValue)
{
foreach (char i in Path.GetInvalidFileNameChars())
str = str.Replace(i, newValue);
return str;
}
public static Windows.Data.Xml.Dom.XmlDocument ToXml(this string text)
{
Windows.Data.Xml.Dom.XmlDocument doc = new Windows.Data.Xml.Dom.XmlDocument();
try
{
doc.LoadXml(text);
return doc;
}
catch { return null; }
}
public static bool Belongs<T>(this T obj, params T[] args) =>
args.Contains(obj);
public static string ToHex(this Color color) =>
$"#{color.R:X}{color.G:X}{color.B:X}";
public static Color FromHex(this Color parent, string hex)
{
hex = hex.Replace("#", "");
List<byte> values = new List<byte>();
for (int k = 0; k < hex.Length; k++)
values.Add(byte.Parse(string.Join("", hex[k], hex[++k]), System.Globalization.NumberStyles.HexNumber));
return hex.Length switch
{
6 => Color.FromArgb(255, values[0], values[1], values[2]),
8 => Color.FromArgb(values[0], values[1], values[2], values[3]),
_ => Colors.Black
};
}
public static TimeSpan GetDuration(this string rawDuration)
{
try
{
return XmlConvert.ToTimeSpan(rawDuration);
}
catch (FormatException)
{
TimeSpan time = XmlConvert.ToTimeSpan("PT" + rawDuration.Split('T')[1]);
TimeSpan date = TimeSpan.FromDays(int.Parse(rawDuration.Split('W')[0].Remove('P')) * 7);
date.Add(time);
return date;
}
catch (Exception e)
{
Metrics.SendReport(new Exception("Failed to parse duration", e), null, ("RawDuration", rawDuration));
return TimeSpan.FromMilliseconds(0);
}
}
public static string GetFriendlyDate(this DateTime date)
{
TimeSpan span = DateTime.Now - date;
if (span.TotalMinutes < 1)
return "Just now";
else if (Math.Round(span.TotalMinutes) == 1)
return "Minute ago";
else if (span.TotalMinutes < 60)
return Math.Round(span.TotalMinutes) + " " + "minutes ago";
else if (Math.Round(span.TotalHours) == 1)
return "Hour ago";
else if (span.TotalHours < 24)
return Math.Round(span.TotalHours) + " " + "hours ago";
else if (Math.Round(span.TotalDays) == 1)
return "Day ago";
else if (span.TotalDays < 7)
return Math.Round(span.TotalDays) + " " + "days ago";
else if (Math.Round(span.TotalDays) == 7)
return "Week ago";
else if (span.TotalDays < 30)
return Math.Round(span.TotalDays / 7) + " " + "weeks ago";
else if (Math.Round(span.TotalDays) == 30)
return "Month ago";
else if (Math.Round(span.TotalDays) < 365)
return Math.Round(span.TotalDays / 30) + " " + "months ago";
else if (Math.Round(span.TotalDays / 365) == 365)
return "Year ago";
else
return Math.Round(span.TotalDays / 365) + " " + "years ago";
}
}
}
+40 -23
View File
@@ -7,7 +7,7 @@
<ProjectGuid>{29C01E10-76E7-4527-984F-B0EEF7E1AC64}</ProjectGuid> <ProjectGuid>{29C01E10-76E7-4527-984F-B0EEF7E1AC64}</ProjectGuid>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>FoxTube.Core</RootNamespace> <RootNamespace>FoxTube</RootNamespace>
<AssemblyName>FoxTube.Core</AssemblyName> <AssemblyName>FoxTube.Core</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage> <DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier> <TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
@@ -130,59 +130,73 @@
<RestoreProjectStyle>PackageReference</RestoreProjectStyle> <RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Helpers\Constants.cs" /> <Compile Include="Models\SuspendedUser.cs" />
<Compile Include="Helpers\Extensions.cs" /> <Compile Include="Services\DownloadsCenter.cs" />
<Compile Include="Helpers\Feedback.cs" /> <Compile Include="Extensions.cs" />
<Compile Include="Helpers\Inbox.cs" /> <Compile Include="Utils\Feedback.cs" />
<Compile Include="Helpers\Metrics.cs" /> <Compile Include="Services\History.cs" />
<Compile Include="Helpers\Settings.cs" /> <Compile Include="Services\Inbox.cs" />
<Compile Include="Helpers\StoreInterop.cs" /> <Compile Include="Utils\Metrics.cs" />
<Compile Include="Helpers\UsersControl.cs" /> <Compile Include="Services\Search.cs" />
<Compile Include="Helpers\Utils.cs" /> <Compile Include="Settings.cs" />
<Compile Include="Models\Inbox\Changelog.cs" /> <Compile Include="Utils\StoreInterop.cs" />
<Compile Include="Models\Inbox\DeveloperMessage.cs" /> <Compile Include="UserManagement.cs" />
<Compile Include="Models\Inbox\InboxItem.cs" /> <Compile Include="Utils\Utils.cs" />
<Compile Include="Models\IRefreshable.cs" /> <Compile Include="Models\DownloadItem.cs" />
<Compile Include="Models\Notifications.cs" /> <Compile Include="Models\InboxItem.cs" />
<Compile Include="Models\PageView.cs" /> <Compile Include="Models\Attributes.cs" />
<Compile Include="Models\SavedVideo.cs" />
<Compile Include="Models\SearchSuggestion.cs" /> <Compile Include="Models\SearchSuggestion.cs" />
<Compile Include="Models\User.cs" /> <Compile Include="Models\User.cs" />
<Compile Include="Models\VideoItem.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Properties\FoxTube.Core.rd.xml" /> <EmbeddedResource Include="Properties\FoxTube.Core.rd.xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AngleSharp"> <PackageReference Include="AngleSharp">
<Version>0.13.0</Version> <Version>0.14.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="ExtendedYouTubeAPI"> <PackageReference Include="ExtendedYouTubeAPI">
<Version>1.0.2</Version> <Version>1.0.2</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Google.Apis.Auth"> <PackageReference Include="Google.Apis.Auth">
<Version>1.42.0</Version> <Version>1.45.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Google.Apis.Oauth2.v2"> <PackageReference Include="Google.Apis.Oauth2.v2">
<Version>1.42.0.1602</Version> <Version>1.44.1.1869</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Google.Apis.YouTube.v3"> <PackageReference Include="Google.Apis.YouTube.v3">
<Version>1.42.0.1758</Version> <Version>1.45.0.1929</Version>
</PackageReference>
<PackageReference Include="Microsoft.Advertising.XAML">
<Version>10.1811.22001</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.AppCenter.Analytics"> <PackageReference Include="Microsoft.AppCenter.Analytics">
<Version>2.6.2</Version> <Version>3.2.1</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.AppCenter.Crashes"> <PackageReference Include="Microsoft.AppCenter.Crashes">
<Version>2.6.2</Version> <Version>3.2.1</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.9</Version> <Version>6.2.10</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Services.Store.Engagement"> <PackageReference Include="Microsoft.Services.Store.Engagement">
<Version>10.1901.28001</Version> <Version>10.1901.28001</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.UI.Xaml">
<Version>2.3.200213001</Version>
</PackageReference>
<PackageReference Include="YoutubeExplode">
<Version>5.0.2</Version>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<WCFMetadata Include="Connected Services\" /> <WCFMetadata Include="Connected Services\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<SDKReference Include="Microsoft.Advertising.Xaml, Version=10.0">
<Name>Microsoft Advertising SDK for XAML</Name>
</SDKReference>
<SDKReference Include="Microsoft.Services.Store.Engagement, Version=10.0"> <SDKReference Include="Microsoft.Services.Store.Engagement, Version=10.0">
<Name>Microsoft Engagement Framework</Name> <Name>Microsoft Engagement Framework</Name>
</SDKReference> </SDKReference>
@@ -190,6 +204,9 @@
<Name>Visual C++ 2015 Runtime for Universal Windows Platform Apps</Name> <Name>Visual C++ 2015 Runtime for Universal Windows Platform Apps</Name>
</SDKReference> </SDKReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="ValueConverters\" />
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' "> <PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion> <VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup> </PropertyGroup>
-8
View File
@@ -1,8 +0,0 @@
namespace FoxTube.Core.Helpers
{
public static class Constants
{
public static string ApplicationId => "9ncqqxjtdlfh";
public static string AdsId => "1100044398";
}
}
-49
View File
@@ -1,49 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Data.Xml.Dom;
using Windows.UI;
namespace FoxTube
{
public static class Extensions
{
public static Uri ToUri(this string url)
{
try { return new Uri(url); }
catch { return null; }
}
public static XmlDocument ToXml(this string text)
{
XmlDocument doc = new XmlDocument();
try
{
doc.LoadXml(text);
return doc;
}
catch { return null; }
}
public static bool Belongs<T>(this T obj, params T[] args) =>
args.Contains(obj);
public static string ToHex(this Color color) =>
$"#{color.R:X}{color.G:X}{color.B:X}";
public static Color FromHex(this Color parent, string hex)
{
hex = hex.Replace("#", "");
List<byte> values = new List<byte>();
for(int k = 0; k < hex.Length; k++)
values.Add(byte.Parse(string.Join("", hex[k], hex[++k]), System.Globalization.NumberStyles.HexNumber));
return hex.Length switch
{
6 => Color.FromArgb(255, values[0], values[1], values[2]),
8 => Color.FromArgb(values[0], values[1], values[2], values[3]),
_ => Colors.Black
};
}
}
}
-52
View File
@@ -1,52 +0,0 @@
using System;
using Microsoft.Services.Store.Engagement;
using Windows.UI.Popups;
namespace FoxTube.Core.Helpers
{
public static class Feedback
{
public static bool HasFeedbackHub => StoreServicesFeedbackLauncher.IsSupported();
public static async void OpenFeedbackHub()
{
if (HasFeedbackHub)
await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync();
}
public static async void PromptFeedback()
{
if (!HasFeedbackHub)
{
Settings.PromptFeedback = false;
return;
}
MessageDialog dialog = new MessageDialog("Have some thoughts to share about the app or any suggestions? Leave feedback!");
dialog.Commands.Add(new UICommand("Don't ask me anymore", (command) => Settings.PromptFeedback = false));
dialog.Commands.Add(new UICommand("Maybe later"));
dialog.Commands.Add(new UICommand("Sure!", (command) =>
{
Settings.PromptFeedback = false;
OpenFeedbackHub();
}));
dialog.DefaultCommandIndex = 2;
dialog.CancelCommandIndex = 1;
await dialog.ShowAsync();
}
public static async void PromptReview()
{
MessageDialog dialog = new MessageDialog("Like our app? Review it on Microsoft Store!");
dialog.Commands.Add(new UICommand("Don't ask me anymore", (command) => Settings.PromptReview = false));
dialog.Commands.Add(new UICommand("Maybe later"));
dialog.Commands.Add(new UICommand("Sure!", (command) =>
{
StoreInterop.RequestReview();
Settings.PromptReview = false;
}));
dialog.DefaultCommandIndex = 2;
dialog.CancelCommandIndex = 1;
await dialog.ShowAsync();
}
}
}
-137
View File
@@ -1,137 +0,0 @@
using FoxTube.Core.Models;
using FoxTube.Core.Models.Inbox;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Windows.Data.Xml.Dom;
using Windows.Storage;
using Windows.UI.Notifications;
namespace FoxTube.Core.Helpers
{
public static class Inbox
{
static HttpClient client = new HttpClient();
static ApplicationDataContainer storage = ApplicationData.Current.RoamingSettings;
public static async Task<List<InboxItem>>GetInbox()
{
List<InboxItem> list = new List<InboxItem>();
list.AddRange(await GetMessages());
list.AddRange(await GetChangelogs());
list.OrderByDescending(i => i.TimeStamp);
return list;
}
public static async void PushNew()
{
try
{
// TODO: Add backend
HttpResponseMessage response = await client.GetAsync("https://xfox111.net/FoxTube/Messages?toast=true&publishedAfter=" + storage.Values["Inbox.lastCheck"] + "&lang=" + Settings.Language);
if (response.StatusCode == System.Net.HttpStatusCode.NoContent)
return;
XmlDocument doc = new XmlDocument();
doc.LoadXml(await response.Content.ReadAsStringAsync());
foreach (IXmlNode toast in doc.LastChild.ChildNodes)
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(toast.GetXml().ToXml()));
storage.Values["Inbox.lastCheck"] = DateTime.Now;
}
catch (Exception e)
{
Metrics.AddEvent("Unable to retrieve developers' messages",
("Exception", e.GetType().ToString()),
("Message", e.Message),
("App version", Metrics.CurrentVersion),
("StackTrace", e.StackTrace));
}
}
static async Task<List<Changelog>>GetChangelogs()
{
List<Changelog> list = new List<Changelog>();
try
{
// TODO: Add backend
HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/FoxTube/Changelogs?lang={Settings.Language}&currentVersion={Metrics.CurrentVersion}");
if (response.StatusCode == System.Net.HttpStatusCode.NoContent)
return list;
dynamic responseObj = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync());
foreach (var item in responseObj)
list.Add(new Changelog(item.Version, item.Content, item.Description, item.TimeStamp));
}
catch (Exception e)
{
Metrics.AddEvent("Unable to retrieve changelogs",
("Exception", e.GetType().ToString()),
("Message", e.Message),
("App version", Metrics.CurrentVersion),
("StackTrace", e.StackTrace));
}
return list;
}
static async Task<List<DeveloperMessage>>GetMessages()
{
List<DeveloperMessage> list = new List<DeveloperMessage>();
try
{
// TODO: Add backend
HttpResponseMessage response = await client.GetAsync("https://xfox111.net/FoxTube/Messages?lang=" + Settings.Language);
if (response.StatusCode == System.Net.HttpStatusCode.NoContent)
return list;
dynamic responseObj = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync());
foreach (var item in responseObj)
list.Add(new DeveloperMessage(item.Id, item.Title, item.Content, item.TimeStamp, item.Avatar));
}
catch (Exception e)
{
Metrics.AddEvent("Unable to retrieve developers' messages",
("Exception", e.GetType().ToString()),
("Message", e.Message),
("App version", Metrics.CurrentVersion),
("StackTrace", e.StackTrace));
}
return list;
}
/// <summary>
/// Fires toast notification with the last changelog content
/// </summary>
public static async void PushChangelog()
{
try
{
// TODO: Add backend
Settings.LastReviewedVersion = Metrics.CurrentVersion;
HttpResponseMessage response = await client.GetAsync("https://xfox111.net/FoxTube/Changelogs?toast=true&lang=" + Settings.Language + "&version=" + Metrics.CurrentVersion);
if (response.StatusCode == System.Net.HttpStatusCode.NoContent)
return;
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification((await response.Content.ReadAsStringAsync()).ToXml()));
}
catch (Exception e)
{
Metrics.AddEvent("Unable to retrieve changelog",
("Exception", e.GetType().ToString()),
("Message", e.Message),
("App version", Metrics.CurrentVersion),
("StackTrace", e.StackTrace));
}
}
}
}
-76
View File
@@ -1,76 +0,0 @@
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Storage;
namespace FoxTube
{
public static class Metrics
{
static readonly ApplicationDataContainer storage = ApplicationData.Current.RoamingSettings;
static readonly Stopwatch sw = new Stopwatch();
public static TimeSpan Uptime
{
get => (TimeSpan?)storage.Values["Metrics.SpentTime"] ?? TimeSpan.FromSeconds(0);
set => storage.Values["Metrics.SpentTime"] = value;
}
public static string CurrentVersion
{
get
{
PackageVersion v = Package.Current.Id.Version;
return $"{v.Major}.{v.Minor}.{v.Revision}.{v.Build}";
}
}
public static void StartSession()
{
sw.Start();
AppCenter.Start("45774462-9ea7-438a-96fc-03982666f39e", typeof(Analytics), typeof(Crashes));
AppCenter.SetCountryCode(Settings.Region);
}
public static void EndSession()
{
sw.Stop();
Uptime += sw.Elapsed;
AddEvent("Session closed",
("Duration", sw.Elapsed.ToString()),
("Spend time total", Uptime.ToString()));
}
public static void AddEvent(string eventName, params (string key, string value)[] details)
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
foreach (var (key, value) in details)
parameters.Add(key, value);
Analytics.TrackEvent(eventName, parameters.Count > 0 ? parameters : null);
}
public static async Task<string> SendExtendedData(string packageTitle, string content)
{
// TODO: Add backend
using(HttpClient client = new HttpClient())
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://xfox111.net/FoxTube/AddMetrics");
Dictionary<string, string> body = new Dictionary<string, string>
{
{ "Title", packageTitle },
{ "Content", content },
{ "Version", CurrentVersion }
};
request.Content = new FormUrlEncodedContent(body);
HttpResponseMessage response = await client.SendAsync(request);
return await response.Content.ReadAsStringAsync();
}
}
}
}
-118
View File
@@ -1,118 +0,0 @@
using System.Globalization;
using Windows.Storage;
namespace FoxTube
{
public static class Settings
{
static readonly ApplicationDataContainer settings = ApplicationData.Current.RoamingSettings;
public static string DesiredVideoQuality
{
get => (string)settings.Values["DesiredVideoQuality"] ?? "auto";
set => settings.Values["DesiredVideoQuality"] = value;
}
public static string RememberedQuality
{
get => (string)settings.Values["RememberedVideoQuality"] ?? "1080p";
set => settings.Values["RememberedVideoQuality"] = value;
}
public static bool VideoNotifications
{
get => (bool?)settings.Values["NewVideosNotificationsAll"] ?? true;
set => settings.Values["NewVideosNotificationsAll"] = value;
}
public static bool DevNotifications
{
get => (bool?)settings.Values["DevelopersNewsNotifications"] ?? true;
set => settings.Values["DevelopersNewsNotifications"] = value;
}
public static bool CheckConnection
{
get => (bool?)settings.Values["WarnIfOnMeteredConnection"] ?? false;
set => settings.Values["WarnIfOnMeteredConnection"] = value;
}
public static bool Autoplay
{
get => (bool?)settings.Values["VideoAutoplay"] ?? true;
set => settings.Values["VideoAutoplay"] = value;
}
public static double Volume
{
get => (double?)settings.Values["Volume"] ?? 1;
set => settings.Values["Volume"] = value;
}
public static string Language
{
get => (string)settings.Values["InterfaceLanguage"] ?? GetDefaultLanguage();
set => settings.Values["InterfaceLanguage"] = value;
}
public static string RelevanceLanguage
{
get => (string)settings.Values["DesiredContentLanguage"] ?? CultureInfo.InstalledUICulture.TwoLetterISOLanguageName;
set => settings.Values["DesiredContentLanguage"] = value;
}
public static string Region
{
get => (string)settings.Values["Region"] ?? CultureInfo.InstalledUICulture.Name.Split('-')[1];
set => settings.Values["Region"] = value;
}
public static int SafeSearch
{
get => (int?)settings.Values["SafeSearch"] ?? 0; //Moderate
set => settings.Values["SafeSearch"] = value;
}
public static bool BlockExplicitContent
{
get => (bool?)settings.Values["BlockExplicitContent"] ?? true;
set => settings.Values["BlockExplicitContent"] = value;
}
public static bool HasAccount
{
get => (bool?)settings.Values["HasAccount"] ?? false;
set => settings.Values["HasAccount"] = value;
}
public static int Theme
{
get => (int?)settings.Values["PreferedUITheme"] ?? 2; //System
set => settings.Values["PreferedUITheme"] = value;
}
public static bool PromptReview
{
get => (bool?)settings.Values["PromptReview"] ?? Metrics.Uptime.TotalHours > 24;
set => settings.Values["PromptReview"] = value;
}
public static bool PromptFeedback
{
get => (bool?)settings.Values["PromptFeedback"] ?? Metrics.Uptime.TotalHours > 12;
set => settings.Values["PromptFeedback"] = value;
}
public static bool ProcessClipboard
{
get => (bool?)settings.Values["ProcessClipboardEntry"] ?? true;
set => settings.Values["ProcessClipboardEntry"] = value;
}
public static string LastReviewedVersion
{
get
{
if (settings.Values["LastReviewedVersion"] == null)
settings.Values["LastReviewedVersion"] = Metrics.CurrentVersion;
return (string)settings.Values["LastReviewedVersion"];
}
set => settings.Values["LastReviewedVersion"] = value;
}
static string GetDefaultLanguage()
{
if (CultureInfo.InstalledUICulture.TwoLetterISOLanguageName.Belongs("ua", "ru", "by", "kz", "kg", "md", "lv", "ee")) //Languages for Russian-speaking countries
return "ru-RU";
else
return "en-US";
}
public static void ResetSettings() =>
settings.Values.Clear();
}
}
-48
View File
@@ -1,48 +0,0 @@
using System;
using System.Threading.Tasks;
using Windows.Services.Store;
namespace FoxTube.Core.Helpers
{
public static class StoreInterop
{
public static bool AdsDisabled { get; private set; } = true;
public static string Price { get; private set; }
public static async Task UpdateStoreState()
{
StoreProductQueryResult requset = await StoreContext.GetDefault().GetAssociatedStoreProductsAsync(new[] { "Durable" });
if (requset.Products["9NP1QK556625"].IsInUserCollection)
return;
Price = requset.Products["9NP1QK556625"].Price.FormattedPrice;
AdsDisabled = false;
}
public static async Task<bool> PurchaseApp()
{
StorePurchaseResult request = await StoreContext.GetDefault().RequestPurchaseAsync("9NP1QK556625");
switch (request.Status)
{
case StorePurchaseStatus.AlreadyPurchased:
case StorePurchaseStatus.Succeeded:
AdsDisabled = true;
return true;
default:
return false;
}
}
public static async void RequestReview()
{
StoreRateAndReviewResult result = await StoreContext.GetDefault().RequestRateAndReviewAppAsync();
string attachedPackageId = result.Status == StoreRateAndReviewStatus.Error ? await Metrics.SendExtendedData("StoreReviewRequestError", result.ExtendedJsonData) : "Success";
Metrics.AddEvent("Store review request has been recieved",
("Result", result.Status.ToString()),
("ErrorData", attachedPackageId));
}
}
}
-93
View File
@@ -1,93 +0,0 @@
using Google.Apis.Auth.OAuth2;
using Google.Apis.Oauth2.v2;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.Security.Authentication.Web;
using YouTube.Authorization;
using System.Text.RegularExpressions;
using Windows.Security.Credentials;
using FoxTube.Core.Models;
using YouTube;
using System.Threading;
using Google.Apis.YouTube.v3;
using System.Net.Http;
using Google.Apis.Auth.OAuth2.Responses;
using Newtonsoft.Json;
using Google.Apis.Auth.OAuth2.Flows;
namespace FoxTube
{
public static class UsersControl
{
static ExtendedYouTubeService _defaultService = new ExtendedYouTubeService(new Google.Apis.Services.BaseClientService.Initializer
{
ApplicationName = "FoxTube",
ApiKey = "AIzaSyD7tpbuvmYDv9h4udo9L_g3r0sLPFAnN00"
});
static string[] Scopes { get; } = new string[]
{
Oauth2Service.Scope.UserinfoProfile,
Oauth2Service.Scope.UserinfoEmail,
YouTubeService.Scope.YoutubeForceSsl
};
static ClientSecrets ClientSecrets { get; } = new ClientSecrets
{
ClientId = "1096685398208-u95rcpkqb4e1ijfmb8jdq3jsg37l8igv.apps.googleusercontent.com",
ClientSecret = "IU5bbdjwvmx8ttJoXQ7e6JWd"
};
public static User CurrentUser { get; set; }
public static bool Authorized => CurrentUser != null;
public static ExtendedYouTubeService Service => CurrentUser?.Service ?? _defaultService;
public static async Task<bool> AddUser()
{
Uri requestString = AuthorizationHelpers.FormQueryString(ClientSecrets, Scopes);
WebAuthenticationResult result = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.UseTitle, requestString, AuthorizationHelpers.Endpoint);
switch(result.ResponseStatus)
{
case WebAuthenticationStatus.Success:
string successCode = new Regex(@"(?<=code=)(.*?)(?=&)").Match(result.ResponseData).Value;
UserCredential credential = await AuthorizationHelpers.ExchangeToken(ClientSecrets, successCode);
CurrentUser = new User(credential);
PasswordVault passwordVault = new PasswordVault();
passwordVault.Add(new PasswordCredential("foxtube", CurrentUser.UserInfo.Id, credential.Token.RefreshToken));
return true;
case WebAuthenticationStatus.UserCancel:
break;
case WebAuthenticationStatus.ErrorHttp:
Metrics.AddEvent("Authorization failed (HTTP Error)", ("Response data", result.ResponseData), ("Error details", result.ResponseErrorDetail.ToString()));
break;
}
return false;
}
public static async Task Initialize()
{
PasswordVault passwordVault = new PasswordVault();
IReadOnlyList<PasswordCredential> credentials;
credentials = passwordVault.RetrieveAll();
if (credentials.Count == 0)
return;
credentials[0].RetrievePassword();
UserCredential credential = await AuthorizationHelpers.RestoreUser(ClientSecrets, credentials[0].Password);
CurrentUser = new User(credential);
}
public static async Task Logout()
{
PasswordVault passwordVault = new PasswordVault();
PasswordCredential credential = passwordVault.Retrieve("foxtube", CurrentUser.UserInfo.Id);
passwordVault.Remove(credential);
await CurrentUser.Credential.RevokeTokenAsync(CancellationToken.None);
}
}
}
-40
View File
@@ -1,40 +0,0 @@
using System;
using System.Collections.Generic;
using Windows.ApplicationModel.Core;
using Windows.Security.Credentials;
namespace FoxTube.Core.Helpers
{
public static class Utils
{
/// <summary>
/// Terminates current application session
/// </summary>
public static void CloseApp() =>
CoreApplication.Exit();
/// <summary>
/// Restarts application
/// </summary>
public static void RestartApp() =>
RestartApp(null);
/// <summary>
/// Restarts application with specified parameters
/// </summary>
/// <param name="args">Parameters which will be provided to new application instance</param>
public static async void RestartApp(string args) =>
await CoreApplication.RequestRestartAsync(args ?? "");
public static void InitializeFailsafeProtocol()
{
Metrics.AddEvent("Failsafe protocol initiated");
Settings.ResetSettings();
PasswordVault passwordVault = new PasswordVault();
IReadOnlyList<PasswordCredential> credentialEntries = passwordVault.RetrieveAll();
foreach (PasswordCredential credential in credentialEntries)
passwordVault.Remove(credential);
RestartApp();
}
}
}
+7
View File
@@ -0,0 +1,7 @@
using System;
namespace FoxTube.Attributes
{
[AttributeUsage(AttributeTargets.Class)]
public class RefreshableAttribute : Attribute { }
}
+42
View File
@@ -0,0 +1,42 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Windows.Storage;
using YoutubeExplode;
using YoutubeExplode.Videos.Streams;
namespace FoxTube.Models
{
public enum DownloadState { Initializing, Downloading, Cancelling }
public class DownloadItem : SavedVideo
{
public DownloadState State { get; private set; } = DownloadState.Initializing;
public IProgress<double> DownloadPercentage { get; set; }
private CancellationTokenSource CTS { get; set; } = new CancellationTokenSource();
public async Task CommenceDownload(IStreamInfo stream, IStorageFile destination)
{
Path = destination.Path;
YoutubeClient client = new YoutubeClient(UserManagement.Service.HttpClient);
State = DownloadState.Downloading;
Task task = client.Videos.Streams.DownloadAsync(stream, Path, DownloadPercentage, CTS.Token);
await task.ConfigureAwait(false);
if (!task.IsCanceled)
return;
await destination.DeleteAsync(StorageDeleteOption.PermanentDelete);
throw new OperationCanceledException();
}
public void Cancel()
{
State = DownloadState.Cancelling;
CTS.Cancel();
}
}
}
-7
View File
@@ -1,7 +0,0 @@
namespace FoxTube.Core.Models
{
public interface IRefreshable
{
void RefreshPage();
}
}
-19
View File
@@ -1,19 +0,0 @@
using System;
namespace FoxTube.Core.Models.Inbox
{
public class Changelog : InboxItem
{
public override string DefaultIcon => "\xE728";
public override string Title => "What's new in version " + Id;
public override string Type => "Changelog";
public Changelog(string version, string content, string description, DateTime timeStamp)
{
Id = version;
Content = content;
Description = description;
TimeStamp = timeStamp;
}
}
}
@@ -1,22 +0,0 @@
using System;
namespace FoxTube.Core.Models.Inbox
{
public class DeveloperMessage : InboxItem
{
public override string DefaultIcon => "\xE119";
public override string Title => _title;
string _title;
public override string Type => "Message from developers";
public DeveloperMessage(string id, string title, string content, DateTime timeStamp, string avatar)
{
Id = id;
_title = title;
Content = content;
Description = content;
TimeStamp = timeStamp;
Avatar = avatar;
}
}
}
-18
View File
@@ -1,18 +0,0 @@
using System;
namespace FoxTube.Core.Models
{
public abstract class InboxItem
{
public string Id { get; set; }
public abstract string DefaultIcon { get; }
public string Avatar { get; set; }
public abstract string Title { get; }
public string Description { get; set; }
public string Content { get; set; }
public DateTime TimeStamp { get; set; }
public abstract string Type { get; }
public string ShortTimeStamp => $"{TimeStamp.ToShortDateString()} {TimeStamp.ToShortTimeString()}";
}
}
+21
View File
@@ -0,0 +1,21 @@
using System;
namespace FoxTube.Models
{
public class InboxItem
{
public string Id { get; set; }
public string DefaultIcon { get; set; }
public string Avatar { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Content { get; set; }
public DateTime TimeStamp { get; set; }
public string Type { get; set; }
public string ShortTimeStamp => $"{TimeStamp.ToShortDateString()} {TimeStamp.ToShortTimeString()}";
}
}
-27
View File
@@ -1,27 +0,0 @@
using Windows.Data.Xml.Dom;
using Windows.UI.Notifications;
namespace FoxTube.Core.Models
{
public static class Notifications
{
public static ToastNotification GetChangelogToast(string version)
{
XmlDocument template = new XmlDocument();
// TODO: Add backend
template.LoadXml($@"<toast activationType='foreground' launch='changelog|{version}'>
<visual>
<binding template='ToastGeneric'>
<image placement='hero' src='https://xfox111.net/FoxTube/Thumbnails/Changelog?ver={version}'/>
<image placement='appLogoOverride' hint-crop='circle' src='https://xfox111.net/FoxTube/Avatars/Changelog'/>
<text>Changelog</text>
<text>See what's new in {version}</text>
</binding>
</visual>
</toast>");
return new ToastNotification(template);
}
}
}
-28
View File
@@ -1,28 +0,0 @@
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace FoxTube.Core.Models
{
public class PageView : Page
{
public string Header
{
get => _header;
set
{
_header = value;
UpdateTitle();
}
}
string _header;
public object Parameter { get; private set; }
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Parameter = e.Parameter;
}
public virtual void UpdateTitle() { }
}
}
+18
View File
@@ -0,0 +1,18 @@
using System;
namespace FoxTube.Models
{
public class SavedVideo
{
public string Title { get; set; }
public string Author { get; set; }
public string Thumbnail { get; set; }
public TimeSpan Duration { get; set; }
public string Id { get; set; }
public string AccessToken { get; set; }
public string Path { get; set; }
public bool IsPathValid { get; set; } = true;
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
namespace FoxTube.Core.Models namespace FoxTube.Models
{ {
public class SearchSuggestion public class SearchSuggestion
{ {
+10
View File
@@ -0,0 +1,10 @@
namespace FoxTube.Models
{
public class SuspendedUser
{
public string Name { get; set; }
public string Email { get; set; }
public string Avatar { get; set; }
public string RefreshToken { get; set; }
}
}
+2 -2
View File
@@ -7,7 +7,7 @@ using Google.Apis.YouTube.v3.Data;
using System.Collections.Generic; using System.Collections.Generic;
using YouTube; using YouTube;
namespace FoxTube.Core.Models namespace FoxTube.Models
{ {
public class User public class User
{ {
@@ -50,7 +50,7 @@ namespace FoxTube.Core.Models
} while (!string.IsNullOrWhiteSpace(nextToken)); } while (!string.IsNullOrWhiteSpace(nextToken));
var request = Service.Channels.List("snippet,contentDetails"); var request = Service.Channels.List("snippet,contentDetails,brandingSettings");
request.Mine = true; request.Mine = true;
Channel = request.Execute().Items[0]; Channel = request.Execute().Items[0];
} }
+48
View File
@@ -0,0 +1,48 @@
using Google.Apis.YouTube.v3.Data;
using System;
using YoutubeExplode;
namespace FoxTube.Models
{
public class VideoItem
{
public Video Meta { get; set; }
public YoutubeExplode.Videos.Video AdditionalMeta { get; set; }
public YoutubeExplode.Channels.Channel ChannelMeta { get; set; }
public string TimeLabel { get; set; }
public string ViewsLabel { get; set; }
public int LiveLabelOpacity => Meta?.LiveStreamingDetails == null ? 0 : 1;
public string LiveLabel
{
get
{
if (Meta?.LiveStreamingDetails == null)
return "";
else if (Meta.LiveStreamingDetails.ActualStartTime.HasValue)
return "LIVE";
else if (Meta.LiveStreamingDetails.ScheduledStartTime.HasValue)
return $"Live in {Meta.LiveStreamingDetails.ScheduledStartTime - DateTime.Now}";
else
return "Upcoming";
}
}
public VideoItem(Video meta)
{
Meta = meta;
LoadInfo();
}
private async void LoadInfo()
{
YoutubeClient client = new YoutubeClient(UserManagement.Service.HttpClient);
AdditionalMeta = await client.Videos.GetAsync(Meta.Id);
ChannelMeta = await client.Channels.GetByVideoAsync(Meta.Id);
TimeLabel = $"{AdditionalMeta?.Duration} • {AdditionalMeta.UploadDate.DateTime.GetFriendlyDate()}";
ViewsLabel = $"{AdditionalMeta?.Engagement.ViewCount} views";
}
}
}
+127
View File
@@ -0,0 +1,127 @@
using Newtonsoft.Json;
using System;
using System.IO;
using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.AccessCache;
using YoutubeExplode.Videos;
using YoutubeExplode.Videos.Streams;
using FoxTube.Utils;
using FoxTube.Models;
namespace FoxTube.Services
{
public static class DownloadsCenter
{
public static List<SavedVideo> History { get; private set; }
public static List<DownloadItem> Queue { get; } = new List<DownloadItem>();
static DownloadsCenter() =>
Initialize();
private static async void Initialize()
{
StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync("DownloadHistory.json", CreationCollisionOption.OpenIfExists);
try
{
History = JsonConvert.DeserializeObject<List<SavedVideo>>(File.ReadAllText(file.Path) ?? "") ?? new List<SavedVideo>();
foreach (SavedVideo i in History)
try { i.IsPathValid = await StorageApplicationPermissions.MostRecentlyUsedList.GetFileAsync(i.AccessToken) != null; }
catch { i.IsPathValid = false; }
}
catch (Exception e)
{
History = new List<SavedVideo>();
await file.DeleteAsync(StorageDeleteOption.PermanentDelete);
StorageApplicationPermissions.MostRecentlyUsedList.Clear();
Metrics.SendReport(new Exception("Failed to load downloads history", e));
}
}
public static Task DownloadVideo(Video meta, IStreamInfo streamInfo) =>
DownloadVideo(meta, streamInfo, null);
public static async Task DownloadVideo(Video meta, IStreamInfo streamInfo, IStorageFile destination)
{
DownloadItem item = new DownloadItem
{
Title = $"[{(streamInfo as IVideoStreamInfo)?.VideoQualityLabel ?? "Audio"}] {meta.Title}",
Author = meta.Author,
Thumbnail = meta.Thumbnails.LowResUrl,
Duration = meta.Duration,
Id = meta.Id
};
Queue.Add(item);
if (destination == null)
destination = await (await GetDefaultDownloadsFolder()).CreateFileAsync($"{meta.Title.ReplaceInvalidChars('_')}.{streamInfo.Container.Name}", CreationCollisionOption.GenerateUniqueName);
item.Path = destination.Path;
try
{
await item.CommenceDownload(streamInfo, destination);
SavedVideo savedItem = item as SavedVideo;
savedItem.AccessToken = StorageApplicationPermissions.MostRecentlyUsedList.Add(destination);
History.Add(savedItem);
StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync("DownloadHistory.json", CreationCollisionOption.OpenIfExists);
File.WriteAllText(file.Path, JsonConvert.SerializeObject(History));
}
catch (OperationCanceledException) { }
catch (Exception e)
{
await destination.DeleteAsync(StorageDeleteOption.PermanentDelete);
Metrics.SendReport(new Exception("Failed to download video", e), null,
("Video ID", meta.Id),
("Stream tag", streamInfo.Tag.ToString()));
}
finally
{
Queue.Remove(item);
}
}
public static async Task RemoveItems(bool removeFiles, params SavedVideo[] items)
{
foreach(SavedVideo i in items)
{
History.Remove(i);
try
{
if (removeFiles)
{
StorageFile file = await StorageApplicationPermissions.MostRecentlyUsedList.GetFileAsync(i.AccessToken);
await file?.DeleteAsync();
}
}
finally
{
StorageApplicationPermissions.MostRecentlyUsedList.Remove(i.AccessToken);
}
}
StorageFile historyFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("DownloadHistory.json", CreationCollisionOption.OpenIfExists);
File.WriteAllText(historyFile.Path, JsonConvert.SerializeObject(History));
}
public static async Task<StorageFolder> GetDefaultDownloadsFolder()
{
if (string.IsNullOrWhiteSpace(Settings.DefaultDownloadsFolder))
return await KnownFolders.VideosLibrary.CreateFolderAsync("FoxTube", CreationCollisionOption.OpenIfExists);
else
return await StorageApplicationPermissions.FutureAccessList.GetFolderAsync(Settings.DefaultDownloadsFolder);
}
public static async Task CancelAll()
{
Queue.ForEach(i => i.Cancel());
while (Queue.Count > 0)
await Task.Delay(500);
}
}
}
+32
View File
@@ -0,0 +1,32 @@
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using Windows.Storage;
namespace FoxTube.Services
{
public static class History
{
static readonly ApplicationDataContainer storage = ApplicationData.Current.RoamingSettings;
public static string[] SearchHistory
{
get => JsonConvert.DeserializeObject<string[]>(storage.Values["SearchHistory"] as string) ?? new string[0];
private set => JsonConvert.SerializeObject(value);
}
public static void AddSearchHistoryEntry(string term)
{
List<string> history = SearchHistory.ToList();
history.Insert(0, term);
if (history.Count > 5)
history.RemoveRange(5, history.Count - 5);
SearchHistory = history.ToArray();
}
public static void ClearSearchHistory() =>
SearchHistory = new string[0];
}
}
+83
View File
@@ -0,0 +1,83 @@
using FoxTube.Models;
using FoxTube.Utils;
using Newtonsoft.Json;
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.UI.Notifications;
namespace FoxTube.Services
{
public static class Inbox
{
private static readonly HttpClient client = new HttpClient();
private static readonly ApplicationDataContainer storage = ApplicationData.Current.RoamingSettings;
public static async void PushNew()
{
try
{
// TODO: Add backend
HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/FoxTube/Messages?toast=true&publishedAfter={storage.Values["Inbox.lastCheck"]}&lang={Settings.Language}&appVersion={Metrics.CurrentVersion}");
storage.Values["Inbox.lastCheck"] = DateTime.UtcNow.Ticks;
if (response.StatusCode == HttpStatusCode.NoContent)
return;
string[] toasts = JsonConvert.DeserializeObject<string[]>(await response.Content.ReadAsStringAsync());
foreach (string toast in toasts)
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(toast.ToXml()));
}
catch (Exception e)
{
Metrics.SendReport(new Exception("Unable to retrieve toast notifications", e));
}
}
public static async Task<InboxItem[]> GetMessages()
{
try
{
// TODO: Add backend
HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/API/FoxTube/Inbox?lang={Settings.Language}&currentVersion={Metrics.CurrentVersion}");
if (response.StatusCode == HttpStatusCode.NoContent)
return new InboxItem[0];
return JsonConvert.DeserializeObject<InboxItem[]>(await response.Content.ReadAsStringAsync());
}
catch (Exception e)
{
Metrics.SendReport(new Exception("Unable to retrieve inbox messages", e));
return new InboxItem[0];
}
}
/// <summary>
/// Fires toast notification with the last changelog content
/// </summary>
public static async void PushChangelog()
{
try
{
// TODO: Add backend
Settings.LastReviewedVersion = Metrics.CurrentVersion;
HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/API/FoxTube/Changelogs?toast=true&lang={Settings.Language}&version={Metrics.CurrentVersion}");
if (response.StatusCode == HttpStatusCode.NoContent)
return;
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification((await response.Content.ReadAsStringAsync()).ToXml()));
}
catch (Exception e)
{
Metrics.SendReport(new Exception("Unable to retrieve changelog", e));
}
}
}
}
+36
View File
@@ -0,0 +1,36 @@
using FoxTube.Models;
using System.Linq;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using System.Xml;
namespace FoxTube.Services
{
public static class Search
{
public static async Task<List<SearchSuggestion>> GetSuggestions(string term)
{
List<SearchSuggestion> suggestions = new List<SearchSuggestion>();
try
{
using HttpClient client = new HttpClient();
string results = await client.GetStringAsync($"http://suggestqueries.google.com/complete/search?ds=yt&client=toolbar&q={term}&hl={Settings.RelevanceLanguage}");
XmlDocument doc = new XmlDocument();
doc.LoadXml(results);
for (int i = 0; i < doc["toplevel"].ChildNodes.Count && i < 5; i++)
suggestions.Add(new SearchSuggestion(doc["toplevel"].ChildNodes[i]["suggestion"].GetAttribute("data")));
// Appending search history
suggestions.AddRange(History.SearchHistory.Select(i => new SearchSuggestion(i, true)));
return suggestions;
}
catch { }
return suggestions;
}
}
}
+139
View File
@@ -0,0 +1,139 @@
using FoxTube.Utils;
using System.Globalization;
using Windows.Storage;
namespace FoxTube
{
public static class Settings
{
static readonly ApplicationDataContainer settings = ApplicationData.Current.RoamingSettings;
public static string DefaultDownloadsFolder
{
get => (string)settings.Values["DefaultDownloadsFolder"] ?? "";
set => settings.Values["DefaultDownloadsFolder"] = value;
}
public static bool AskBeforeDownloading
{
get => (bool?)settings.Values["AskBeforeDownloading"] ?? true;
set => settings.Values["AskBeforeDownloading"] = value;
}
public static bool AllowAnalytics
{
get => (bool?)settings.Values["AllowAnalytics"] ?? true;
set => settings.Values["AllowAnalytics"] = value;
}
public static string DesiredVideoQuality
{
get => (string)settings.Values["DesiredVideoQuality"] ?? "auto";
set => settings.Values["DesiredVideoQuality"] = value;
}
public static string RememberedQuality
{
get => (string)settings.Values["RememberedVideoQuality"] ?? "1080p";
set => settings.Values["RememberedVideoQuality"] = value;
}
public static bool VideoNotifications
{
get => (bool?)settings.Values["NewVideosNotificationsAll"] ?? true;
set => settings.Values["NewVideosNotificationsAll"] = value;
}
public static bool DevNotifications
{
get => (bool?)settings.Values["DevelopersNewsNotifications"] ?? true;
set => settings.Values["DevelopersNewsNotifications"] = value;
}
public static bool CheckConnection
{
get => (bool?)settings.Values["WarnIfOnMeteredConnection"] ?? false;
set => settings.Values["WarnIfOnMeteredConnection"] = value;
}
public static bool Autoplay
{
get => (bool?)settings.Values["VideoAutoplay"] ?? true;
set => settings.Values["VideoAutoplay"] = value;
}
public static double Volume
{
get => (double?)settings.Values["Volume"] ?? 1;
set => settings.Values["Volume"] = value;
}
public static string Language
{
get => (string)settings.Values["InterfaceLanguage"] ?? GetDefaultLanguage();
set => settings.Values["InterfaceLanguage"] = value;
}
public static string RelevanceLanguage
{
get => (string)settings.Values["DesiredContentLanguage"] ?? CultureInfo.InstalledUICulture.TwoLetterISOLanguageName;
set => settings.Values["DesiredContentLanguage"] = value;
}
public static string Region
{
get => (string)settings.Values["Region"] ?? CultureInfo.InstalledUICulture.Name.Split('-')[1];
set => settings.Values["Region"] = value;
}
public static int SafeSearch
{
get => (int?)settings.Values["SafeSearch"] ?? 0; // Moderate
set => settings.Values["SafeSearch"] = value;
}
public static int DefaultHomeTab
{
get => (int?)settings.Values["DefaultHomeTab"] ?? 0; // Recommendations
set => settings.Values["DefaultHomeTab"] = value;
}
public static bool BlockExplicitContent
{
get => (bool?)settings.Values["BlockExplicitContent"] ?? true;
set => settings.Values["BlockExplicitContent"] = value;
}
public static bool HasAccount
{
get => (bool?)settings.Values["HasAccount"] ?? false;
set => settings.Values["HasAccount"] = value;
}
public static int Theme
{
get => (int?)settings.Values["PreferedUITheme"] ?? 2; // System
set => settings.Values["PreferedUITheme"] = value;
}
public static bool PromptReview
{
get => (bool?)settings.Values["PromptReview"] ?? Metrics.Uptime.TotalHours > 24;
set => settings.Values["PromptReview"] = value;
}
public static bool PromptFeedback
{
get => (bool?)settings.Values["PromptFeedback"] ?? Metrics.Uptime.TotalHours > 12;
set => settings.Values["PromptFeedback"] = value;
}
public static bool ProcessClipboard
{
get => (bool?)settings.Values["ProcessClipboardEntry"] ?? true;
set => settings.Values["ProcessClipboardEntry"] = value;
}
public static string LastReviewedVersion
{
get
{
if (settings.Values["LastReviewedVersion"] == null)
settings.Values["LastReviewedVersion"] = Metrics.CurrentVersion;
return (string)settings.Values["LastReviewedVersion"];
}
set => settings.Values["LastReviewedVersion"] = value;
}
static string GetDefaultLanguage()
{
if (CultureInfo.InstalledUICulture.TwoLetterISOLanguageName.Belongs("ua", "ru", "by", "kz", "kg", "md", "lv", "ee")) //Languages for Russian-speaking countries
return "ru-RU";
else
return "en-US";
}
public static void ResetSettings() =>
settings.Values.Clear();
}
}
+124
View File
@@ -0,0 +1,124 @@
using Google.Apis.Auth.OAuth2;
using Google.Apis.Oauth2.v2;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.Security.Authentication.Web;
using YouTube.Authorization;
using System.Text.RegularExpressions;
using Windows.Security.Credentials;
using FoxTube.Models;
using YouTube;
using System.Threading;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using Windows.UI.Xaml.Controls;
using Windows.UI.Popups;
using FoxTube.Utils;
namespace FoxTube
{
public static class UserManagement
{
static ExtendedYouTubeService _defaultService = new ExtendedYouTubeService(new Google.Apis.Services.BaseClientService.Initializer
{
ApplicationName = "FoxTube",
ApiKey = "AIzaSyBgHrCnrlzlVmk0cJKL8RqP9Y8x6XSuk_0"
//ApiKey = "AIzaSyD7tpbuvmYDv9h4udo9L_g3r0sLPFAnN00"
});
static string[] Scopes { get; } = new string[]
{
Oauth2Service.Scope.UserinfoProfile,
Oauth2Service.Scope.UserinfoEmail,
YouTubeService.Scope.YoutubeForceSsl
};
static ClientSecrets ClientSecrets { get; } = new ClientSecrets
{
ClientId = "349735264870-2ekqlm0a4mkg3mmrfcv90s3qp3o15dq0.apps.googleusercontent.com",
ClientSecret = "BkVZOAaCU2Zclf0Zlicg6y2_"
//ClientId = "1096685398208-u95rcpkqb4e1ijfmb8jdq3jsg37l8igv.apps.googleusercontent.com",
//ClientSecret = "IU5bbdjwvmx8ttJoXQ7e6JWd"
};
public static User CurrentUser { get; set; }
public static bool Authorized => CurrentUser != null;
public static ExtendedYouTubeService Service => CurrentUser?.Service ?? _defaultService;
public static event EventHandler<bool> UserStateUpdated;
public static event EventHandler<Subscription> SubscriptionsChanged;
public static async Task<bool> AddUser()
{
Uri requestString = AuthorizationHelpers.FormQueryString(ClientSecrets, Scopes);
WebAuthenticationResult result = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.UseTitle, requestString, AuthorizationHelpers.Endpoint);
switch (result.ResponseStatus)
{
case WebAuthenticationStatus.Success:
string successCode = new Regex(@"(?<=code=)(.*?)(?=&)").Match(result.ResponseData).Value;
UserCredential credential = await AuthorizationHelpers.ExchangeToken(ClientSecrets, successCode);
CurrentUser = new User(credential);
PasswordVault passwordVault = new PasswordVault();
passwordVault.Add(new PasswordCredential("foxtube", CurrentUser.UserInfo.Id, credential.Token.RefreshToken));
UserStateUpdated?.Invoke(null, true);
return true;
case WebAuthenticationStatus.UserCancel:
break;
case WebAuthenticationStatus.ErrorHttp:
await new ContentDialog
{
Title = "Something went wrong...",
Content = "It may be a bug or temporary server issues. Please, try again later"
}.ShowAsync();
Metrics.SendReport(new Exception("Authorization failed (HTTP Error)"), null,
("Response status", result.ResponseStatus.ToString()),
("Response data", result.ResponseData),
("Error details", result.ResponseErrorDetail.ToString()));
break;
}
return false;
}
public static async Task Initialize()
{
PasswordVault passwordVault = new PasswordVault();
IReadOnlyList<PasswordCredential> credentials;
credentials = passwordVault.RetrieveAll();
if (credentials.Count == 0)
return;
try
{
credentials[0].RetrievePassword();
UserCredential credential = await AuthorizationHelpers.RestoreUser(ClientSecrets, credentials[0].Password);
credentials[0].Password = credential.Token.RefreshToken;
CurrentUser = new User(credential);
}
catch (Exception e)
{
await new MessageDialog("It may be a bug or temporary server issues. Please, try again later", "Something went wrong...").ShowAsync();
Metrics.SendReport(new Exception("Refresh token exchange failed", e));
foreach (PasswordCredential i in credentials)
passwordVault.Remove(i);
}
}
public static async Task Logout()
{
PasswordVault passwordVault = new PasswordVault();
PasswordCredential credential = passwordVault.Retrieve("foxtube", CurrentUser.UserInfo.Id);
passwordVault.Remove(credential);
await CurrentUser.Credential.RevokeTokenAsync(CancellationToken.None);
UserStateUpdated?.Invoke(null, false);
}
}
}
+77
View File
@@ -0,0 +1,77 @@
using System;
using Microsoft.Services.Store.Engagement;
using Windows.System;
using Windows.UI.Xaml.Controls;
namespace FoxTube.Utils
{
public static class Feedback
{
public static bool HasFeedbackHub => StoreServicesFeedbackLauncher.IsSupported();
public static async void OpenFeedbackHub()
{
if (HasFeedbackHub)
await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync();
else
await Launcher.LaunchUriAsync("mailto:feedback@xfox111.net".ToUri());
}
public static async void PromptFeedback()
{
if (!HasFeedbackHub)
{
Settings.PromptFeedback = false;
return;
}
ContentDialog dialog = new ContentDialog
{
Title = "Have some thoughts?",
PrimaryButtonText = "Sure!",
SecondaryButtonText = "Don't ask me anymore",
CloseButtonText = "Maybe later",
DefaultButton = ContentDialogButton.Primary,
Content = "Would you like to share something you like or dislike in the app? Or perhaps you have some ideas? Leave feedback!"
};
ContentDialogResult result = await dialog.ShowAsync();
if (result != ContentDialogResult.None)
Settings.PromptFeedback = false;
if (result == ContentDialogResult.Primary)
OpenFeedbackHub();
}
public static async void PromptReview()
{
ContentDialog dialog = new ContentDialog
{
Title = "Like our app?",
PrimaryButtonText = "Sure!",
SecondaryButtonText = "Don't ask me anymore",
CloseButtonText = "Maybe later",
DefaultButton = ContentDialogButton.Primary,
Content = new TextBlock
{
Text = "Could you leave a feedback on Microsfot Store page? It's very important for me :)"
}
};
ContentDialogResult result = await dialog.ShowAsync();
if (result != ContentDialogResult.None)
Settings.PromptReview = false;
if (result == ContentDialogResult.Primary)
StoreInterop.RequestReview();
}
}
}
+66
View File
@@ -0,0 +1,66 @@
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using Windows.ApplicationModel;
using Windows.Storage;
namespace FoxTube.Utils
{
public static class Metrics
{
static readonly ApplicationDataContainer storage = ApplicationData.Current.RoamingSettings;
static readonly Stopwatch sw = new Stopwatch();
public static TimeSpan Uptime
{
get => (TimeSpan?)storage.Values["Metrics.SpentTime"] ?? TimeSpan.FromSeconds(0);
set => storage.Values["Metrics.SpentTime"] = value;
}
public static string CurrentVersion
{
get
{
PackageVersion v = Package.Current.Id.Version;
return $"{v.Major}.{v.Minor}.{v.Revision}.{v.Build}";
}
}
static Metrics()
{
sw.Start();
if (!Settings.AllowAnalytics)
return;
AppCenter.Start("45774462-9ea7-438a-96fc-03982666f39e", typeof(Analytics), typeof(Crashes));
AppCenter.SetCountryCode(Settings.Region);
AppCenter.LogLevel = LogLevel.Verbose;
}
public static void EndSession()
{
sw.Stop();
Uptime += sw.Elapsed;
AddEvent("Session closed",
("Duration", sw.Elapsed.ToString()),
("Spend time total", Uptime.ToString()));
}
public static void AddEvent(string eventName, params (string key, string value)[] details) =>
Analytics.TrackEvent(eventName,
details.Length < 1 ? null :
details.Select(i => new KeyValuePair<string, string>(i.key, i.value)) as Dictionary<string, string>);
public static void SendReport(Exception exception, ErrorAttachmentLog[] logs = null, params (string key, string value)[] details)
{
Crashes.TrackError(exception,
details.Length < 1 ? null :
details.Select(i => new KeyValuePair<string, string>(i.key, i.value)) as Dictionary<string, string>,
logs);
}
}
}
+61
View File
@@ -0,0 +1,61 @@
using Microsoft.Advertising.WinRT.UI;
using Microsoft.AppCenter.Crashes;
using System;
using System.Threading.Tasks;
using Windows.Services.Store;
namespace FoxTube.Utils
{
public static class StoreInterop
{
public static bool AdsDisabled { get; private set; } = true;
public static string Price { get; private set; }
private static bool UseTestAds => true;
private static string ApplicationId => UseTestAds ? "d25517cb-12d4-4699-8bdc-52040c712cab" : "9ncqqxjtdlfh";
private static string AdsId => UseTestAds ? "test" : "1100044398";
private static string ProProductId => "9NP1QK556625";
public static NativeAdsManagerV2 AdsManager => new NativeAdsManagerV2(ApplicationId, AdsId);
public static async Task UpdateStoreState()
{
StoreProductQueryResult requset = await StoreContext.GetDefault().GetAssociatedStoreProductsAsync(new[] { "Durable" });
if (requset.Products[ProProductId].IsInUserCollection)
return;
Price = requset.Products[ProProductId].Price.FormattedPrice;
AdsDisabled = false;
}
public static async Task<bool> PurchaseApp()
{
StorePurchaseResult request = await StoreContext.GetDefault().RequestPurchaseAsync(ProProductId);
switch (request.Status)
{
case StorePurchaseStatus.AlreadyPurchased:
case StorePurchaseStatus.Succeeded:
AdsDisabled = true;
return true;
default:
return false;
}
}
public static async void RequestReview()
{
StoreRateAndReviewResult result = await StoreContext.GetDefault().RequestRateAndReviewAppAsync();
if (result.Status == StoreRateAndReviewStatus.Error)
Metrics.SendReport(result.ExtendedError, new[] { ErrorAttachmentLog.AttachmentWithText(result.ExtendedJsonData, "extendedJsonData.json") },
("Status", result.Status.ToString()),
("WasReviewUpdated", result.WasUpdated.ToString()));
Metrics.AddEvent("Store review request has been recieved");
}
}
}
+38
View File
@@ -0,0 +1,38 @@
using System;
using Windows.ApplicationModel.Core;
using Windows.Security.Credentials;
namespace FoxTube.Utils
{
public static class Utils
{
/// <summary>
/// Terminates current application session
/// </summary>
public static void CloseApp() =>
CoreApplication.Exit();
/// <summary>
/// Restarts application
/// </summary>
public static void RestartApp() =>
RestartApp(null);
/// <summary>
/// Restarts application with specified parameters
/// </summary>
/// <param name="args">Parameters which will be provided to new application instance</param>
public static async void RestartApp(string args) =>
await CoreApplication.RequestRestartAsync(args ?? "");
public static void InitializeFailsafeProtocol()
{
Metrics.AddEvent("Failsafe protocol initiated");
Settings.ResetSettings();
PasswordVault passwordVault = new PasswordVault();
foreach (PasswordCredential credential in passwordVault.RetrieveAll())
passwordVault.Remove(credential);
RestartApp();
}
}
}