Core refactoring (app doesn't work)
This commit is contained in:
@@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)' < '14.0' ">
|
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' ">
|
||||||
<VisualStudioVersion>14.0</VisualStudioVersion>
|
<VisualStudioVersion>14.0</VisualStudioVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace FoxTube.Core.Helpers
|
|
||||||
{
|
|
||||||
public static class Constants
|
|
||||||
{
|
|
||||||
public static string ApplicationId => "9ncqqxjtdlfh";
|
|
||||||
public static string AdsId => "1100044398";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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}¤tVersion={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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FoxTube.Attributes
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class RefreshableAttribute : Attribute { }
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace FoxTube.Core.Models
|
|
||||||
{
|
|
||||||
public interface IRefreshable
|
|
||||||
{
|
|
||||||
void RefreshPage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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() { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,4 +1,4 @@
|
|||||||
namespace FoxTube.Core.Models
|
namespace FoxTube.Models
|
||||||
{
|
{
|
||||||
public class SearchSuggestion
|
public class SearchSuggestion
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}¤tVersion={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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user