Archived
1
0

Core update. Base core features are done (main app doesn't compile)

Related Work Items: #416, #422, #423, #424
This commit is contained in:
Michael Gordeev
2020-06-11 21:17:18 +03:00
parent b3212738e8
commit c58d846057
18 changed files with 386 additions and 281 deletions
+3 -2
View File
@@ -1,4 +1,5 @@
using FoxTube.Utils; using FoxTube.Services;
using FoxTube.Utils;
using Google.Apis.YouTube.v3.Data; using Google.Apis.YouTube.v3.Data;
using SQLitePCL; using SQLitePCL;
using System; using System;
@@ -131,7 +132,7 @@ namespace FoxTube
public static async Task<Channel> GetChannel(string channelId, string part) public static async Task<Channel> GetChannel(string channelId, string part)
{ {
var request = UserManagement.Service.Channels.List(part); var request = UserService.Service.Channels.List(part);
request.Id = channelId; request.Id = channelId;
request.MaxResults = 1; request.MaxResults = 1;
+16 -9
View File
@@ -130,17 +130,20 @@
<RestoreProjectStyle>PackageReference</RestoreProjectStyle> <RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Models\Collections\InboxCollection.cs" />
<Compile Include="Models\Collections\ViewCollection.cs" />
<Compile Include="Models\Subscription.cs" /> <Compile Include="Models\Subscription.cs" />
<Compile Include="Services\DownloadsCenter.cs" /> <Compile Include="Services\DownloadsService.cs" />
<Compile Include="Extensions.cs" /> <Compile Include="Extensions.cs" />
<Compile Include="Utils\Feedback.cs" /> <Compile Include="Utils\FeedbackIterop.cs" />
<Compile Include="Services\History.cs" /> <Compile Include="Services\History.cs" />
<Compile Include="Services\Inbox.cs" /> <Compile Include="Services\InboxService.cs" />
<Compile Include="Utils\Metrics.cs" /> <Compile Include="Utils\Metrics.cs" />
<Compile Include="Services\Search.cs" /> <Compile Include="Services\Search.cs" />
<Compile Include="Settings.cs" /> <Compile Include="Services\Storage.cs" />
<Compile Include="Utils\StoreInterop.cs" /> <Compile Include="Utils\AddonsInterop.cs" />
<Compile Include="UserManagement.cs" /> <Compile Include="Services\UserService.cs" />
<Compile Include="Utils\SecretConstants.cs" />
<Compile Include="Utils\Utils.cs" /> <Compile Include="Utils\Utils.cs" />
<Compile Include="Models\DownloadItem.cs" /> <Compile Include="Models\DownloadItem.cs" />
<Compile Include="Models\InboxItem.cs" /> <Compile Include="Models\InboxItem.cs" />
@@ -168,6 +171,9 @@
<PackageReference Include="Google.Apis.YouTube.v3"> <PackageReference Include="Google.Apis.YouTube.v3">
<Version>1.45.0.1929</Version> <Version>1.45.0.1929</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Advertising.XAML">
<Version>10.1811.22001</Version>
</PackageReference>
<PackageReference Include="Microsoft.AppCenter.Analytics"> <PackageReference Include="Microsoft.AppCenter.Analytics">
<Version>3.2.1</Version> <Version>3.2.1</Version>
</PackageReference> </PackageReference>
@@ -191,13 +197,14 @@
<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>
</ItemGroup> </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>
@@ -0,0 +1,45 @@
using Newtonsoft.Json;
using System.Threading.Tasks;
using System.Net.Http;
using Windows.UI.Xaml.Data;
using FoxTube.Services;
using FoxTube.Utils;
namespace FoxTube.Models.Collections
{
public class InboxCollection : ViewCollection<InboxItem>
{
private int _pageNumber = 0;
private HttpClient _httpClient = new HttpClient();
public override async Task<LoadMoreItemsResult> LoadItems()
{
// TODO: Add backend
HttpResponseMessage response = await _httpClient.GetAsync($"https://xfox111.net/API/FoxTube/Inbox?" +
$"lang={Storage.GetValue<string>(Storage.Settings.UILanguage)}&" +
$"currentVersion={Metrics.CurrentVersion}&" +
$"itemsCount={ItemsPerRequest}&" +
$"iteration={_pageNumber}");
if (!response.IsSuccessStatusCode || response.StatusCode == System.Net.HttpStatusCode.NoContent)
{
HasMoreItems = false;
return new LoadMoreItemsResult
{
Count = 0
};
}
InboxItem[] newItems = JsonConvert.DeserializeObject<InboxItem[]>(await response.Content.ReadAsStringAsync());
foreach (InboxItem item in newItems)
Items.Add(item);
_pageNumber++;
return new LoadMoreItemsResult
{
Count = (uint)newItems.Length
};
}
}
}
@@ -0,0 +1,19 @@
using System.Collections.ObjectModel;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI.Xaml.Data;
namespace FoxTube.Models.Collections
{
public abstract class ViewCollection<T> : ObservableCollection<T>, ISupportIncrementalLoading
{
public int ItemsPerRequest { get; set; }
public bool HasMoreItems { get; protected set; } = true;
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count) =>
AsyncInfo.Run((c) => LoadItems());
public abstract Task<LoadMoreItemsResult> LoadItems();
}
}
+3 -2
View File
@@ -1,4 +1,5 @@
using System; using FoxTube.Services;
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Windows.Storage; using Windows.Storage;
@@ -20,7 +21,7 @@ namespace FoxTube.Models
public async Task CommenceDownload(IStreamInfo stream, IStorageFile destination) public async Task CommenceDownload(IStreamInfo stream, IStorageFile destination)
{ {
Path = destination.Path; Path = destination.Path;
YoutubeClient client = new YoutubeClient(UserManagement.Service.HttpClient); YoutubeClient client = new YoutubeClient(UserService.Service.HttpClient);
State = DownloadState.Downloading; State = DownloadState.Downloading;
Task task = client.Videos.Streams.DownloadAsync(stream, Path, DownloadPercentage, CTS.Token); Task task = client.Videos.Streams.DownloadAsync(stream, Path, DownloadPercentage, CTS.Token);
+3 -2
View File
@@ -13,6 +13,7 @@ using System.Threading.Tasks;
using Windows.Storage; using Windows.Storage;
using YouTube; using YouTube;
using YoutubeExplode; using YoutubeExplode;
using FoxTube.Services;
namespace FoxTube.Models namespace FoxTube.Models
{ {
@@ -39,7 +40,7 @@ namespace FoxTube.Models
return true; return true;
} }
UserManagement.SubscriptionsChangedInvoker(this, subscription); UserService.SubscriptionsChangedInvoker(this, subscription);
Subscriptions.Remove(subscription); Subscriptions.Remove(subscription);
SaveSubscriptions(); SaveSubscriptions();
@@ -77,7 +78,7 @@ namespace FoxTube.Models
}; };
Subscriptions.Add(subscription); Subscriptions.Add(subscription);
UserManagement.SubscriptionsChangedInvoker(this, subscription); UserService.SubscriptionsChangedInvoker(this, subscription);
SaveSubscriptions(); SaveSubscriptions();
return true; return true;
+3 -2
View File
@@ -1,4 +1,5 @@
using Google.Apis.YouTube.v3.Data; using FoxTube.Services;
using Google.Apis.YouTube.v3.Data;
using System; using System;
using YoutubeExplode; using YoutubeExplode;
@@ -36,7 +37,7 @@ namespace FoxTube.Models
private async void LoadInfo() private async void LoadInfo()
{ {
YoutubeClient client = new YoutubeClient(UserManagement.Service.HttpClient); YoutubeClient client = new YoutubeClient(UserService.Service.HttpClient);
AdditionalMeta = await client.Videos.GetAsync(Meta.Id); AdditionalMeta = await client.Videos.GetAsync(Meta.Id);
ChannelMeta = await client.Channels.GetByVideoAsync(Meta.Id); ChannelMeta = await client.Channels.GetByVideoAsync(Meta.Id);
@@ -9,23 +9,25 @@ using YoutubeExplode.Videos;
using YoutubeExplode.Videos.Streams; using YoutubeExplode.Videos.Streams;
using FoxTube.Utils; using FoxTube.Utils;
using FoxTube.Models; using FoxTube.Models;
using Windows.Storage.Pickers;
namespace FoxTube.Services namespace FoxTube.Services
{ {
public static class DownloadsCenter public static class DownloadsService
{ {
public static List<SavedVideo> History { get; private set; } public static List<SavedVideo> History { get; } = new List<SavedVideo>();
public static List<DownloadItem> Queue { get; } = new List<DownloadItem>(); public static List<DownloadItem> Queue { get; } = new List<DownloadItem>();
static DownloadsCenter() => static DownloadsService() =>
Initialize(); Initialize();
private static async void Initialize() private static async void Initialize()
{ {
StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync("DownloadHistory.json", CreationCollisionOption.OpenIfExists); StorageFile file = await Storage.Folder.CreateFileAsync("DownloadHistory.json", CreationCollisionOption.OpenIfExists);
try try
{ {
History = JsonConvert.DeserializeObject<List<SavedVideo>>(File.ReadAllText(file.Path) ?? "") ?? new List<SavedVideo>(); List<SavedVideo> savedVideos = JsonConvert.DeserializeObject<List<SavedVideo>>(File.ReadAllText(file.Path) ?? "") ?? new List<SavedVideo>();
History.AddRange(savedVideos);
foreach (SavedVideo i in History) foreach (SavedVideo i in History)
try { i.IsPathValid = await StorageApplicationPermissions.MostRecentlyUsedList.GetFileAsync(i.AccessToken) != null; } try { i.IsPathValid = await StorageApplicationPermissions.MostRecentlyUsedList.GetFileAsync(i.AccessToken) != null; }
@@ -33,7 +35,6 @@ namespace FoxTube.Services
} }
catch (Exception e) catch (Exception e)
{ {
History = new List<SavedVideo>();
await file.DeleteAsync(StorageDeleteOption.PermanentDelete); await file.DeleteAsync(StorageDeleteOption.PermanentDelete);
StorageApplicationPermissions.MostRecentlyUsedList.Clear(); StorageApplicationPermissions.MostRecentlyUsedList.Clear();
Metrics.SendReport(new Exception("Failed to load downloads history", e)); Metrics.SendReport(new Exception("Failed to load downloads history", e));
@@ -64,12 +65,12 @@ namespace FoxTube.Services
{ {
await item.CommenceDownload(streamInfo, destination); await item.CommenceDownload(streamInfo, destination);
SavedVideo savedItem = item as SavedVideo; SavedVideo savedItem = item;
savedItem.AccessToken = StorageApplicationPermissions.MostRecentlyUsedList.Add(destination); savedItem.AccessToken = StorageApplicationPermissions.MostRecentlyUsedList.Add(destination);
History.Add(savedItem); History.Add(savedItem);
StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync("DownloadHistory.json", CreationCollisionOption.OpenIfExists); StorageFile file = await Storage.Folder.CreateFileAsync("DownloadHistory.json", CreationCollisionOption.OpenIfExists);
File.WriteAllText(file.Path, JsonConvert.SerializeObject(History)); File.WriteAllText(file.Path, JsonConvert.SerializeObject(History));
} }
catch (OperationCanceledException) { } catch (OperationCanceledException) { }
@@ -111,10 +112,11 @@ namespace FoxTube.Services
public static async Task<StorageFolder> GetDefaultDownloadsFolder() public static async Task<StorageFolder> GetDefaultDownloadsFolder()
{ {
if (string.IsNullOrWhiteSpace(Settings.DefaultDownloadsFolder)) if (Storage.GetValue<string>(Storage.Settings.DefaultDownloadsFolder) is string token && !string.IsNullOrWhiteSpace(token))
return await KnownFolders.VideosLibrary.CreateFolderAsync("FoxTube", CreationCollisionOption.OpenIfExists); return await StorageApplicationPermissions.FutureAccessList.GetFolderAsync(token) ??
await KnownFolders.VideosLibrary.CreateFolderAsync("FoxTube", CreationCollisionOption.OpenIfExists);
else else
return await StorageApplicationPermissions.FutureAccessList.GetFolderAsync(Settings.DefaultDownloadsFolder); return await KnownFolders.VideosLibrary.CreateFolderAsync("FoxTube", CreationCollisionOption.OpenIfExists);
} }
public static async Task CancelAll() public static async Task CancelAll()
@@ -123,5 +125,28 @@ namespace FoxTube.Services
while (Queue.Count > 0) while (Queue.Count > 0)
await Task.Delay(500); await Task.Delay(500);
} }
public static async Task<StorageFolder> ChangeDefaultFolder()
{
FolderPicker picker = new FolderPicker
{
SuggestedStartLocation = PickerLocationId.Downloads
};
StorageFolder folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
if (Storage.GetValue<string>(Storage.Settings.DefaultDownloadsFolder) is string token && !string.IsNullOrWhiteSpace(token))
StorageApplicationPermissions.FutureAccessList.AddOrReplace(token, folder);
else
{
token = StorageApplicationPermissions.FutureAccessList.Add(folder);
Storage.SetValue(Storage.Settings.DefaultDownloadsFolder, token);
}
}
return folder;
}
} }
} }
@@ -1,27 +1,36 @@
using FoxTube.Models; using FoxTube.Models.Collections;
using FoxTube.Utils; using FoxTube.Utils;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Windows.Storage;
using Windows.UI.Notifications; using Windows.UI.Notifications;
namespace FoxTube.Services namespace FoxTube.Services
{ {
public static class Inbox public static class InboxService
{ {
private static readonly HttpClient client = new HttpClient(); public const string lastChangelogVersionKey = "Inbox.lastChangelogVersion";
private static readonly ApplicationDataContainer storage = ApplicationData.Current.RoamingSettings; public const string lastCheckKey = "Inbox.lastChangelogVersion";
public static async void PushNew() private static readonly HttpClient client = new HttpClient();
public static InboxCollection GetInboxCollection() =>
new InboxCollection();
public static async Task PushNew()
{ {
try try
{ {
// TODO: Add backend // TODO: Add backend
HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/FoxTube/Inbox?toast=true&publishedAfter={storage.Values["Inbox.lastCheck"]}&lang={Settings.Language}&appVersion={Metrics.CurrentVersion}"); HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/FoxTube/Inbox?" +
storage.Values["Inbox.lastCheck"] = DateTime.UtcNow.Ticks; $"toast=true&" +
$"publishedAfter={Storage.Registry.Values[lastCheckKey]}&" +
$"lang={Storage.GetValue<string>(Storage.Settings.UILanguage)}&" +
$"appVersion={Metrics.CurrentVersion}");
Storage.Registry.Values[lastCheckKey] = DateTime.UtcNow.Ticks;
if (response.StatusCode == HttpStatusCode.NoContent) if (response.StatusCode == HttpStatusCode.NoContent)
return; return;
@@ -37,26 +46,6 @@ namespace FoxTube.Services
} }
} }
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> /// <summary>
/// Fires toast notification with the last changelog content /// Fires toast notification with the last changelog content
/// </summary> /// </summary>
@@ -65,9 +54,14 @@ namespace FoxTube.Services
try try
{ {
// TODO: Add backend // TODO: Add backend
Settings.LastReviewedVersion = Metrics.CurrentVersion; if ((string)Storage.Registry.Values[lastChangelogVersionKey] == Metrics.CurrentVersion)
return;
HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/API/FoxTube/Changelog?lang={Settings.Language}&version={Metrics.CurrentVersion}"); Storage.Registry.Values[lastChangelogVersionKey] = Metrics.CurrentVersion;
HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/API/FoxTube/Changelog?" +
$"lang={Storage.GetValue<string>(Storage.Settings.UILanguage)}&" +
$"version={Metrics.CurrentVersion}");
if (response.StatusCode == HttpStatusCode.NoContent) if (response.StatusCode == HttpStatusCode.NoContent)
return; return;
+1 -1
View File
@@ -16,7 +16,7 @@ namespace FoxTube.Services
try try
{ {
using HttpClient client = new HttpClient(); 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}"); string results = await client.GetStringAsync($"http://suggestqueries.google.com/complete/search?ds=yt&client=toolbar&q={term}&hl={Storage.GetValue<string>(Storage.Settings.RelevanceLanguage)}");
XmlDocument doc = new XmlDocument(); XmlDocument doc = new XmlDocument();
doc.LoadXml(results); doc.LoadXml(results);
+101
View File
@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.UI.Xaml;
namespace FoxTube.Services
{
public static class Storage
{
public static event EventHandler<Settings> SettingsChanged;
public static StorageFolder Folder => ApplicationData.Current.RoamingFolder;
public static ApplicationDataContainer Registry { get; } = ApplicationData.Current.RoamingSettings;
private static readonly Dictionary<Settings, object> _defaultSettings = new Dictionary<Settings, object>
{
{ Settings.Theme, ElementTheme.Default },
{ Settings.UILanguage, GetDefaultLanguage() },
{ Settings.RelevanceLanguage, GetDefaultLanguage() },
{ Settings.PromptFeedback, true },
{ Settings.PromptReview, true },
{ Settings.AllowAnalytics, true }
};
public enum Settings
{
/// <summary>
/// ElementTheme
/// </summary>
Theme,
/// <summary>
/// string
/// </summary>
UILanguage,
/// <summary>
/// string
/// </summary>
RelevanceLanguage,
/// <summary>
/// string
/// </summary>
DefaultDownloadsFolder,
/// <summary>
/// bool
/// </summary>
PromptFeedback,
/// <summary>
/// bool
/// </summary>
PromptReview,
/// <summary>
/// bool
/// </summary>
AllowAnalytics,
/// <summary>
/// string
/// </summary>
Region
}
public enum Metrics
{
/// <summary>
/// TimeSpan
/// </summary>
Uptime
}
public static void SetValue(Settings key, object value)
{
Registry.Values[$"{key.GetType().Name}.{key}"] = value;
SettingsChanged?.Invoke(value, key);
}
public static void SetValue(Metrics key, object value) =>
Registry.Values[$"{key.GetType().Name}.{key}"] = value;
public static T GetValue<T>(Settings key) =>
(T)(Registry.Values[$"{key.GetType().Name}.{key}"] ?? (_defaultSettings.ContainsKey(key) ? _defaultSettings[key] : null));
public static T GetValue<T>(Metrics key) =>
(T)Registry.Values[$"{key.GetType().Name}.{key}"];
private 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 async Task ResetStorage()
{
Registry.Values.Clear();
foreach (IStorageItem i in await Folder.GetItemsAsync())
await i.DeleteAsync();
}
}
}
@@ -14,25 +14,24 @@ using FoxTube.Utils;
using YoutubeExplode; using YoutubeExplode;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Windows.Storage;
using Google.Apis.Oauth2.v2.Data; using Google.Apis.Oauth2.v2.Data;
namespace FoxTube namespace FoxTube.Services
{ {
public static class UserManagement public static class UserService
{ {
public const string UsersStorageKey = "UserService.Users";
public const string LastUserInfoKey = "UserService.LastUser";
public const int MaxUsersCount = 1; public const int MaxUsersCount = 1;
#region Private members #region Private members
private static readonly ApplicationDataContainer storage = ApplicationData.Current.LocalSettings;
private static readonly ExtendedYouTubeService _defaultService = new ExtendedYouTubeService(new Google.Apis.Services.BaseClientService.Initializer private static readonly ExtendedYouTubeService _defaultService = new ExtendedYouTubeService(new Google.Apis.Services.BaseClientService.Initializer
{ {
ApplicationName = "FoxTube", ApplicationName = "FoxTube",
ApiKey = "AIzaSyBgHrCnrlzlVmk0cJKL8RqP9Y8x6XSuk_0", ApiKey = SecretConstants.YoutubeApiKey, // TODO: Replace with an actual API key
//ApiKey = "AIzaSyD7tpbuvmYDv9h4udo9L_g3r0sLPFAnN00"
}); });
private static readonly YoutubeClient _defaultYteClient = new YoutubeClient(); private static readonly YoutubeClient _defaultYtClient = new YoutubeClient();
private static string[] Scopes { get; } = new string[] private static string[] Scopes { get; } = new string[]
{ {
@@ -41,14 +40,7 @@ namespace FoxTube
YouTubeService.Scope.YoutubeForceSsl YouTubeService.Scope.YoutubeForceSsl
}; };
private static ClientSecrets[] ClientSecrets { get; } = new ClientSecrets[MaxUsersCount] private static ClientSecrets[] ClientSecrets { get; } = SecretConstants.ClientSecrets;
{
new ClientSecrets
{
ClientId = "349735264870-2ekqlm0a4mkg3mmrfcv90s3qp3o15dq0.apps.googleusercontent.com",
ClientSecret = "BkVZOAaCU2Zclf0Zlicg6y2_"
}
};
#endregion #endregion
public static Userinfoplus[] Users { get; private set; } = new Userinfoplus[MaxUsersCount]; public static Userinfoplus[] Users { get; private set; } = new Userinfoplus[MaxUsersCount];
@@ -58,12 +50,33 @@ namespace FoxTube
public static bool CanAddAccounts => Users.Any(i => i == null); public static bool CanAddAccounts => Users.Any(i => i == null);
public static User CurrentUser { get; set; } public static User CurrentUser { get; set; }
public static bool Authorized => CurrentUser != null; public static bool Authorized => CurrentUser != null;
public static ExtendedYouTubeService Service => IncognitoMode ? _defaultService : (CurrentUser?.Service ?? _defaultService); public static ExtendedYouTubeService Service
public static YoutubeClient YoutubeClient => IncognitoMode ? _defaultYteClient : (CurrentUser?.Client ?? _defaultYteClient); {
get
{
if (IncognitoMode || CurrentUser == null)
return _defaultService;
else
return CurrentUser.Service;
}
}
public static YoutubeClient YouTubeClient
{
get
{
if (IncognitoMode || CurrentUser == null)
return _defaultYtClient;
else
return CurrentUser.Client;
}
}
public static event EventHandler<bool> UserStateUpdated; public static event EventHandler<bool> UserStateUpdated;
public static event EventHandler<Subscription> SubscriptionsChanged; public static event EventHandler<Subscription> SubscriptionsChanged;
static UserService() =>
Initialize();
public static async Task<bool> AddUser() public static async Task<bool> AddUser()
{ {
int queueIndex = Users.ToList().FindIndex(i => i == null); int queueIndex = Users.ToList().FindIndex(i => i == null);
@@ -104,10 +117,10 @@ namespace FoxTube
return false; return false;
} }
public static async Task Initialize() public static async void Initialize()
{ {
Users = JsonConvert.DeserializeObject<Userinfoplus[]>(storage.Values["UserManagement.Users"] as string ?? "") ?? new Userinfoplus[MaxUsersCount]; Users = JsonConvert.DeserializeObject<Userinfoplus[]>(Storage.Registry.Values[UsersStorageKey] as string ?? "") ?? new Userinfoplus[MaxUsersCount];
int? lastUserIndex = storage.Values["UserManagement.LastUser"] as int?; int? lastUserIndex = Storage.Registry.Values[LastUserInfoKey] as int?;
if (lastUserIndex.HasValue && Users[lastUserIndex.Value] != null || if (lastUserIndex.HasValue && Users[lastUserIndex.Value] != null ||
(lastUserIndex = Users.ToList().FindIndex(i => i != null)) > -1) (lastUserIndex = Users.ToList().FindIndex(i => i != null)) > -1)
@@ -133,12 +146,12 @@ namespace FoxTube
await CurrentUser.Credential.RevokeTokenAsync(CancellationToken.None); await CurrentUser.Credential.RevokeTokenAsync(CancellationToken.None);
storage.Values.Remove($"Subscriptions.{CurrentUser.UserInfo.Id}"); Storage.Registry.Values.Remove($"Subscriptions.{CurrentUser.UserInfo.Id}");
CurrentUser = null; CurrentUser = null;
Users[Users.ToList().FindIndex(i => i.Id == userId)] = null; Users[Users.ToList().FindIndex(i => i.Id == userId)] = null;
storage.Values["UserManagement.Users"] = JsonConvert.SerializeObject(Users); Storage.Registry.Values[UsersStorageKey] = JsonConvert.SerializeObject(Users);
storage.Values["UserManagement.LastUser"] = null; Storage.Registry.Values[LastUserInfoKey] = null;
if (Users.Any(i => i != null)) if (Users.Any(i => i != null))
await SwitchUser(Users.ToList().FindIndex(i => i != null)); await SwitchUser(Users.ToList().FindIndex(i => i != null));
@@ -189,8 +202,8 @@ namespace FoxTube
CurrentUser = await User.GetUser(credential); CurrentUser = await User.GetUser(credential);
Users[userIndex] = CurrentUser.UserInfo; Users[userIndex] = CurrentUser.UserInfo;
storage.Values["UserManagement.Users"] = JsonConvert.SerializeObject(Users); Storage.Registry.Values[UsersStorageKey] = JsonConvert.SerializeObject(Users);
storage.Values["UserManagement.LastUser"] = userIndex; Storage.Registry.Values[LastUserInfoKey] = userIndex;
credential.RefreshTokenUpdated += (s, e) => UpdateToken(CurrentUser.UserInfo.Id, credential.Token.RefreshToken); credential.RefreshTokenUpdated += (s, e) => UpdateToken(CurrentUser.UserInfo.Id, credential.Token.RefreshToken);
UpdateToken(CurrentUser.UserInfo.Id, credential.Token.RefreshToken); UpdateToken(CurrentUser.UserInfo.Id, credential.Token.RefreshToken);
-142
View File
@@ -1,142 +0,0 @@
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();
ApplicationData.Current.LocalSettings.Values.Clear();
}
}
}
@@ -1,23 +1,27 @@
using Microsoft.AppCenter.Crashes; using Microsoft.Advertising.WinRT.UI;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Windows.Services.Store; using Windows.Services.Store;
namespace FoxTube.Utils namespace FoxTube.Utils
{ {
public static class StoreInterop public static class AddonsInterop
{ {
public static bool AdsDisabled { get; private set; } = true; public static bool AdsDisabled { get; private set; } = true;
public static string Price { get; private set; } public static string Price { get; private set; }
private const bool UseTestAds = true;
private static bool UseTestAds => true; public static string ApplicationId => UseTestAds ? "d25517cb-12d4-4699-8bdc-52040c712cab" : SecretConstants.ApplicationProductId;
public static string AdsId => UseTestAds ? "test" : SecretConstants.AdsUnitId;
private const string ProProductId = SecretConstants.ProAddonId;
public static string ApplicationId => UseTestAds ? "d25517cb-12d4-4699-8bdc-52040c712cab" : "9ncqqxjtdlfh"; public static NativeAdsManagerV2 AdsManager => new NativeAdsManagerV2(ApplicationId, AdsId);
public static string AdsId => UseTestAds ? "test" : "1100044398";
private static string ProProductId => "9NP1QK556625";
public static async Task UpdateStoreState() static AddonsInterop() =>
UpdateStoreState();
public static async void UpdateStoreState()
{ {
StoreProductQueryResult requset = await StoreContext.GetDefault().GetAssociatedStoreProductsAsync(new[] { "Durable" }); StoreProductQueryResult requset = await StoreContext.GetDefault().GetAssociatedStoreProductsAsync(new[] { "Durable" });
@@ -42,17 +46,5 @@ namespace FoxTube.Utils
return false; 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");
}
} }
} }
@@ -1,11 +1,13 @@
using System; using System;
using FoxTube.Services;
using Microsoft.AppCenter.Crashes;
using Microsoft.Services.Store.Engagement; using Microsoft.Services.Store.Engagement;
using Windows.System; using Windows.Services.Store;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
namespace FoxTube.Utils namespace FoxTube.Utils
{ {
public static class Feedback public static class FeedbackInterop
{ {
public static bool HasFeedbackHub => StoreServicesFeedbackLauncher.IsSupported(); public static bool HasFeedbackHub => StoreServicesFeedbackLauncher.IsSupported();
@@ -13,15 +15,25 @@ namespace FoxTube.Utils
{ {
if (HasFeedbackHub) if (HasFeedbackHub)
await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync(); await StoreServicesFeedbackLauncher.GetDefault().LaunchAsync();
else }
await Launcher.LaunchUriAsync("mailto:feedback@xfox111.net".ToUri());
public static async void RequestStoreReview()
{
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 received");
} }
public static async void PromptFeedback() public static async void PromptFeedback()
{ {
if (!HasFeedbackHub) if (!HasFeedbackHub)
{ {
Settings.PromptFeedback = false; Storage.SetValue(Storage.Settings.PromptFeedback, false);
return; return;
} }
@@ -41,7 +53,7 @@ namespace FoxTube.Utils
ContentDialogResult result = await dialog.ShowAsync(); ContentDialogResult result = await dialog.ShowAsync();
if (result != ContentDialogResult.None) if (result != ContentDialogResult.None)
Settings.PromptFeedback = false; Storage.SetValue(Storage.Settings.PromptFeedback, false);
if (result == ContentDialogResult.Primary) if (result == ContentDialogResult.Primary)
OpenFeedbackHub(); OpenFeedbackHub();
@@ -61,17 +73,17 @@ namespace FoxTube.Utils
Content = new TextBlock Content = new TextBlock
{ {
Text = "Could you leave a feedback on Microsfot Store page? It's very important for me :)" Text = "Could you leave a feedback on Microsoft Store page? It's very important for me :)"
} }
}; };
ContentDialogResult result = await dialog.ShowAsync(); ContentDialogResult result = await dialog.ShowAsync();
if (result != ContentDialogResult.None) if (result != ContentDialogResult.None)
Settings.PromptReview = false; Storage.SetValue(Storage.Settings.PromptReview, false);
if (result == ContentDialogResult.Primary) if (result == ContentDialogResult.Primary)
StoreInterop.RequestReview(); RequestStoreReview();
} }
} }
} }
+15 -10
View File
@@ -6,19 +6,17 @@ using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using Windows.ApplicationModel; using Windows.ApplicationModel;
using Windows.Storage; using FoxTube.Services;
namespace FoxTube.Utils namespace FoxTube.Utils
{ {
public static class Metrics public static class Metrics
{ {
static readonly ApplicationDataContainer storage = ApplicationData.Current.RoamingSettings;
static readonly Stopwatch sw = new Stopwatch(); static readonly Stopwatch sw = new Stopwatch();
public static TimeSpan Uptime public static TimeSpan Uptime
{ {
get => (TimeSpan?)storage.Values["Metrics.SpentTime"] ?? TimeSpan.FromSeconds(0); get => Storage.GetValue<TimeSpan?>(Storage.Metrics.Uptime) ?? TimeSpan.FromSeconds(0);
set => storage.Values["Metrics.SpentTime"] = value; set => Storage.SetValue(Storage.Metrics.Uptime, value);
} }
public static string CurrentVersion public static string CurrentVersion
{ {
@@ -32,11 +30,11 @@ namespace FoxTube.Utils
static Metrics() static Metrics()
{ {
sw.Start(); sw.Start();
if (!Settings.AllowAnalytics) if (!Storage.GetValue<bool>(Storage.Settings.AllowAnalytics))
return; return;
AppCenter.Start("45774462-9ea7-438a-96fc-03982666f39e", typeof(Analytics), typeof(Crashes)); AppCenter.Start(SecretConstants.MetricsId, typeof(Analytics), typeof(Crashes));
AppCenter.SetCountryCode(Settings.Region); AppCenter.SetCountryCode(Storage.GetValue<string>(Storage.Settings.Region));
AppCenter.LogLevel = LogLevel.Verbose; AppCenter.LogLevel = LogLevel.Verbose;
} }
@@ -45,23 +43,30 @@ namespace FoxTube.Utils
sw.Stop(); sw.Stop();
Uptime += sw.Elapsed; Uptime += sw.Elapsed;
if (Storage.GetValue<bool>(Storage.Settings.AllowAnalytics))
AddEvent("Session closed", AddEvent("Session closed",
("Duration", sw.Elapsed.ToString()), ("Duration", sw.Elapsed.ToString()),
("Spend time total", Uptime.ToString())); ("Spend time total", Uptime.ToString()));
} }
public static void AddEvent(string eventName, params (string key, string value)[] details) => public static void AddEvent(string eventName, params (string key, string value)[] details)
{
if (Storage.GetValue<bool>(Storage.Settings.AllowAnalytics))
Analytics.TrackEvent(eventName, Analytics.TrackEvent(eventName,
details.Length < 1 ? null : details.Length < 1 ? null :
details.Select(i => new KeyValuePair<string, string>(i.key, i.value)) as Dictionary<string, string>); 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) public static void SendReport(Exception exception, ErrorAttachmentLog[] logs = null, params (string key, string value)[] details)
{
if (Storage.GetValue<bool>(Storage.Settings.AllowAnalytics))
{ {
logs ??= new ErrorAttachmentLog[0]; logs ??= new ErrorAttachmentLog[0];
Crashes.TrackError(exception, Crashes.TrackError(exception ?? new Exception("Unknown exception"),
details.Length < 1 ? null : details.Length < 1 ? null :
details.Select(i => new KeyValuePair<string, string>(i.key, i.value)) as Dictionary<string, string>, details.Select(i => new KeyValuePair<string, string>(i.key, i.value)) as Dictionary<string, string>,
logs); logs);
} }
} }
}
} }
+29
View File
@@ -0,0 +1,29 @@
using Google.Apis.Auth.OAuth2;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FoxTube.Services;
namespace FoxTube.Utils
{
public static class SecretConstants
{
public const string YoutubeApiKey = "AIzaSyBgHrCnrlzlVmk0cJKL8RqP9Y8x6XSuk_0";
public const string MetricsId = "45774462-9ea7-438a-96fc-03982666f39e";
public const string ProAddonId = "9NP1QK556625";
public const string AdsUnitId = "1100044398";
public const string ApplicationProductId = "9ncqqxjtdlfh";
public static ClientSecrets[] ClientSecrets { get; } = new ClientSecrets[UserService.MaxUsersCount]
{
// TODO: Replace with actual secrets
new ClientSecrets
{
ClientId = "349735264870-2ekqlm0a4mkg3mmrfcv90s3qp3o15dq0.apps.googleusercontent.com",
ClientSecret = "BkVZOAaCU2Zclf0Zlicg6y2_"
}
};
}
}
+4 -3
View File
@@ -1,4 +1,5 @@
using System; using FoxTube.Services;
using System;
using Windows.ApplicationModel.Core; using Windows.ApplicationModel.Core;
using Windows.Security.Credentials; using Windows.Security.Credentials;
@@ -25,10 +26,10 @@ namespace FoxTube.Utils
public static async void RestartApp(string args) => public static async void RestartApp(string args) =>
await CoreApplication.RequestRestartAsync(args ?? ""); await CoreApplication.RequestRestartAsync(args ?? "");
public static void InitializeFailsafeProtocol() public static async void InitializeFailsafeProtocol()
{ {
Metrics.AddEvent("Failsafe protocol initiated"); Metrics.AddEvent("Failsafe protocol initiated");
Settings.ResetSettings(); await Storage.ResetStorage();
PasswordVault passwordVault = new PasswordVault(); PasswordVault passwordVault = new PasswordVault();
foreach (PasswordCredential credential in passwordVault.RetrieveAll()) foreach (PasswordCredential credential in passwordVault.RetrieveAll())
passwordVault.Remove(credential); passwordVault.Remove(credential);