diff --git a/FoxTube.Core/Extensions.cs b/FoxTube.Core/Extensions.cs index f6d6005..e9b36ee 100644 --- a/FoxTube.Core/Extensions.cs +++ b/FoxTube.Core/Extensions.cs @@ -1,4 +1,5 @@ -using FoxTube.Utils; +using FoxTube.Services; +using FoxTube.Utils; using Google.Apis.YouTube.v3.Data; using SQLitePCL; using System; @@ -131,7 +132,7 @@ namespace FoxTube public static async Task GetChannel(string channelId, string part) { - var request = UserManagement.Service.Channels.List(part); + var request = UserService.Service.Channels.List(part); request.Id = channelId; request.MaxResults = 1; diff --git a/FoxTube.Core/FoxTube.Core.csproj b/FoxTube.Core/FoxTube.Core.csproj index 9968c80..9f76189 100644 --- a/FoxTube.Core/FoxTube.Core.csproj +++ b/FoxTube.Core/FoxTube.Core.csproj @@ -130,17 +130,20 @@ PackageReference + + - + - + - + - - - + + + + @@ -168,6 +171,9 @@ 1.45.0.1929 + + 10.1811.22001 + 3.2.1 @@ -191,13 +197,14 @@ + + Microsoft Advertising SDK for XAML + Microsoft Engagement Framework - - - + 14.0 diff --git a/FoxTube.Core/Models/Collections/InboxCollection.cs b/FoxTube.Core/Models/Collections/InboxCollection.cs new file mode 100644 index 0000000..c3a5089 --- /dev/null +++ b/FoxTube.Core/Models/Collections/InboxCollection.cs @@ -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 + { + private int _pageNumber = 0; + private HttpClient _httpClient = new HttpClient(); + + public override async Task LoadItems() + { + // TODO: Add backend + HttpResponseMessage response = await _httpClient.GetAsync($"https://xfox111.net/API/FoxTube/Inbox?" + + $"lang={Storage.GetValue(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(await response.Content.ReadAsStringAsync()); + foreach (InboxItem item in newItems) + Items.Add(item); + + _pageNumber++; + + return new LoadMoreItemsResult + { + Count = (uint)newItems.Length + }; + } + } +} diff --git a/FoxTube.Core/Models/Collections/ViewCollection.cs b/FoxTube.Core/Models/Collections/ViewCollection.cs new file mode 100644 index 0000000..1ee2372 --- /dev/null +++ b/FoxTube.Core/Models/Collections/ViewCollection.cs @@ -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 : ObservableCollection, ISupportIncrementalLoading + { + public int ItemsPerRequest { get; set; } + public bool HasMoreItems { get; protected set; } = true; + + public IAsyncOperation LoadMoreItemsAsync(uint count) => + AsyncInfo.Run((c) => LoadItems()); + + public abstract Task LoadItems(); + } +} \ No newline at end of file diff --git a/FoxTube.Core/Models/DownloadItem.cs b/FoxTube.Core/Models/DownloadItem.cs index 1a5bdda..c290e67 100644 --- a/FoxTube.Core/Models/DownloadItem.cs +++ b/FoxTube.Core/Models/DownloadItem.cs @@ -1,4 +1,5 @@ -using System; +using FoxTube.Services; +using System; using System.Threading; using System.Threading.Tasks; using Windows.Storage; @@ -20,7 +21,7 @@ namespace FoxTube.Models public async Task CommenceDownload(IStreamInfo stream, IStorageFile destination) { Path = destination.Path; - YoutubeClient client = new YoutubeClient(UserManagement.Service.HttpClient); + YoutubeClient client = new YoutubeClient(UserService.Service.HttpClient); State = DownloadState.Downloading; Task task = client.Videos.Streams.DownloadAsync(stream, Path, DownloadPercentage, CTS.Token); diff --git a/FoxTube.Core/Models/User.cs b/FoxTube.Core/Models/User.cs index 0c386b2..11f0850 100644 --- a/FoxTube.Core/Models/User.cs +++ b/FoxTube.Core/Models/User.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using Windows.Storage; using YouTube; using YoutubeExplode; +using FoxTube.Services; namespace FoxTube.Models { @@ -36,10 +37,10 @@ namespace FoxTube.Models catch (Exception e) { Metrics.SendReport(new Exception("Failed to unsubscribe", e)); - return true; + return true; } - UserManagement.SubscriptionsChangedInvoker(this, subscription); + UserService.SubscriptionsChangedInvoker(this, subscription); Subscriptions.Remove(subscription); SaveSubscriptions(); @@ -77,7 +78,7 @@ namespace FoxTube.Models }; Subscriptions.Add(subscription); - UserManagement.SubscriptionsChangedInvoker(this, subscription); + UserService.SubscriptionsChangedInvoker(this, subscription); SaveSubscriptions(); return true; @@ -86,7 +87,7 @@ namespace FoxTube.Models private void SaveSubscriptions() { - Dictionary subs = Subscriptions.Select(i => + Dictionary subs = Subscriptions.Select(i => new KeyValuePair(i.ChannelId, i.Avatar.Default__?.Url)) as Dictionary; diff --git a/FoxTube.Core/Models/VideoItem.cs b/FoxTube.Core/Models/VideoItem.cs index 4849a6e..a86caff 100644 --- a/FoxTube.Core/Models/VideoItem.cs +++ b/FoxTube.Core/Models/VideoItem.cs @@ -1,4 +1,5 @@ -using Google.Apis.YouTube.v3.Data; +using FoxTube.Services; +using Google.Apis.YouTube.v3.Data; using System; using YoutubeExplode; @@ -36,7 +37,7 @@ namespace FoxTube.Models 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); ChannelMeta = await client.Channels.GetByVideoAsync(Meta.Id); diff --git a/FoxTube.Core/Services/DownloadsCenter.cs b/FoxTube.Core/Services/DownloadsService.cs similarity index 68% rename from FoxTube.Core/Services/DownloadsCenter.cs rename to FoxTube.Core/Services/DownloadsService.cs index aa9d924..221b434 100644 --- a/FoxTube.Core/Services/DownloadsCenter.cs +++ b/FoxTube.Core/Services/DownloadsService.cs @@ -9,23 +9,25 @@ using YoutubeExplode.Videos; using YoutubeExplode.Videos.Streams; using FoxTube.Utils; using FoxTube.Models; +using Windows.Storage.Pickers; namespace FoxTube.Services { - public static class DownloadsCenter + public static class DownloadsService { - public static List History { get; private set; } + public static List History { get; } = new List(); public static List Queue { get; } = new List(); - static DownloadsCenter() => + static DownloadsService() => 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 { - History = JsonConvert.DeserializeObject>(File.ReadAllText(file.Path) ?? "") ?? new List(); + List savedVideos = JsonConvert.DeserializeObject>(File.ReadAllText(file.Path) ?? "") ?? new List(); + History.AddRange(savedVideos); foreach (SavedVideo i in History) try { i.IsPathValid = await StorageApplicationPermissions.MostRecentlyUsedList.GetFileAsync(i.AccessToken) != null; } @@ -33,7 +35,6 @@ namespace FoxTube.Services } catch (Exception e) { - History = new List(); await file.DeleteAsync(StorageDeleteOption.PermanentDelete); StorageApplicationPermissions.MostRecentlyUsedList.Clear(); Metrics.SendReport(new Exception("Failed to load downloads history", e)); @@ -64,12 +65,12 @@ namespace FoxTube.Services { await item.CommenceDownload(streamInfo, destination); - SavedVideo savedItem = item as SavedVideo; + SavedVideo savedItem = item; savedItem.AccessToken = StorageApplicationPermissions.MostRecentlyUsedList.Add(destination); 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)); } catch (OperationCanceledException) { } @@ -111,10 +112,11 @@ namespace FoxTube.Services public static async Task GetDefaultDownloadsFolder() { - if (string.IsNullOrWhiteSpace(Settings.DefaultDownloadsFolder)) - return await KnownFolders.VideosLibrary.CreateFolderAsync("FoxTube", CreationCollisionOption.OpenIfExists); + if (Storage.GetValue(Storage.Settings.DefaultDownloadsFolder) is string token && !string.IsNullOrWhiteSpace(token)) + return await StorageApplicationPermissions.FutureAccessList.GetFolderAsync(token) ?? + await KnownFolders.VideosLibrary.CreateFolderAsync("FoxTube", CreationCollisionOption.OpenIfExists); else - return await StorageApplicationPermissions.FutureAccessList.GetFolderAsync(Settings.DefaultDownloadsFolder); + return await KnownFolders.VideosLibrary.CreateFolderAsync("FoxTube", CreationCollisionOption.OpenIfExists); } public static async Task CancelAll() @@ -123,5 +125,28 @@ namespace FoxTube.Services while (Queue.Count > 0) await Task.Delay(500); } + + public static async Task ChangeDefaultFolder() + { + FolderPicker picker = new FolderPicker + { + SuggestedStartLocation = PickerLocationId.Downloads + }; + + StorageFolder folder = await picker.PickSingleFolderAsync(); + + if (folder != null) + { + if (Storage.GetValue(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; + } } } \ No newline at end of file diff --git a/FoxTube.Core/Services/Inbox.cs b/FoxTube.Core/Services/InboxService.cs similarity index 53% rename from FoxTube.Core/Services/Inbox.cs rename to FoxTube.Core/Services/InboxService.cs index 8ba75b4..ef3cbc4 100644 --- a/FoxTube.Core/Services/Inbox.cs +++ b/FoxTube.Core/Services/InboxService.cs @@ -1,27 +1,36 @@ -using FoxTube.Models; +using FoxTube.Models.Collections; 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 + public static class InboxService { - private static readonly HttpClient client = new HttpClient(); - private static readonly ApplicationDataContainer storage = ApplicationData.Current.RoamingSettings; + public const string lastChangelogVersionKey = "Inbox.lastChangelogVersion"; + 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 { // 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}"); - storage.Values["Inbox.lastCheck"] = DateTime.UtcNow.Ticks; + HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/FoxTube/Inbox?" + + $"toast=true&" + + $"publishedAfter={Storage.Registry.Values[lastCheckKey]}&" + + $"lang={Storage.GetValue(Storage.Settings.UILanguage)}&" + + $"appVersion={Metrics.CurrentVersion}"); + + Storage.Registry.Values[lastCheckKey] = DateTime.UtcNow.Ticks; if (response.StatusCode == HttpStatusCode.NoContent) return; @@ -37,26 +46,6 @@ namespace FoxTube.Services } } - public static async Task 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(await response.Content.ReadAsStringAsync()); - } - catch (Exception e) - { - Metrics.SendReport(new Exception("Unable to retrieve inbox messages", e)); - - return new InboxItem[0]; - } - } - /// /// Fires toast notification with the last changelog content /// @@ -65,9 +54,14 @@ namespace FoxTube.Services try { // 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(Storage.Settings.UILanguage)}&" + + $"version={Metrics.CurrentVersion}"); if (response.StatusCode == HttpStatusCode.NoContent) return; diff --git a/FoxTube.Core/Services/Search.cs b/FoxTube.Core/Services/Search.cs index a80f8fe..17f3d00 100644 --- a/FoxTube.Core/Services/Search.cs +++ b/FoxTube.Core/Services/Search.cs @@ -16,7 +16,7 @@ namespace FoxTube.Services 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}"); + string results = await client.GetStringAsync($"http://suggestqueries.google.com/complete/search?ds=yt&client=toolbar&q={term}&hl={Storage.GetValue(Storage.Settings.RelevanceLanguage)}"); XmlDocument doc = new XmlDocument(); doc.LoadXml(results); diff --git a/FoxTube.Core/Services/Storage.cs b/FoxTube.Core/Services/Storage.cs new file mode 100644 index 0000000..3f373e6 --- /dev/null +++ b/FoxTube.Core/Services/Storage.cs @@ -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 SettingsChanged; + + public static StorageFolder Folder => ApplicationData.Current.RoamingFolder; + public static ApplicationDataContainer Registry { get; } = ApplicationData.Current.RoamingSettings; + + private static readonly Dictionary _defaultSettings = new Dictionary + { + { Settings.Theme, ElementTheme.Default }, + { Settings.UILanguage, GetDefaultLanguage() }, + { Settings.RelevanceLanguage, GetDefaultLanguage() }, + { Settings.PromptFeedback, true }, + { Settings.PromptReview, true }, + { Settings.AllowAnalytics, true } + }; + + public enum Settings + { + /// + /// ElementTheme + /// + Theme, + /// + /// string + /// + UILanguage, + /// + /// string + /// + RelevanceLanguage, + /// + /// string + /// + DefaultDownloadsFolder, + /// + /// bool + /// + PromptFeedback, + /// + /// bool + /// + PromptReview, + /// + /// bool + /// + AllowAnalytics, + /// + /// string + /// + Region + } + + public enum Metrics + { + /// + /// TimeSpan + /// + 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(Settings key) => + (T)(Registry.Values[$"{key.GetType().Name}.{key}"] ?? (_defaultSettings.ContainsKey(key) ? _defaultSettings[key] : null)); + public static T GetValue(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(); + } + } +} \ No newline at end of file diff --git a/FoxTube.Core/UserManagement.cs b/FoxTube.Core/Services/UserService.cs similarity index 79% rename from FoxTube.Core/UserManagement.cs rename to FoxTube.Core/Services/UserService.cs index 0dd61d1..d0b4aec 100644 --- a/FoxTube.Core/UserManagement.cs +++ b/FoxTube.Core/Services/UserService.cs @@ -14,25 +14,24 @@ using FoxTube.Utils; using YoutubeExplode; using System.Linq; using Newtonsoft.Json; -using Windows.Storage; 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; #region Private members - private static readonly ApplicationDataContainer storage = ApplicationData.Current.LocalSettings; - private static readonly ExtendedYouTubeService _defaultService = new ExtendedYouTubeService(new Google.Apis.Services.BaseClientService.Initializer { ApplicationName = "FoxTube", - ApiKey = "AIzaSyBgHrCnrlzlVmk0cJKL8RqP9Y8x6XSuk_0", - //ApiKey = "AIzaSyD7tpbuvmYDv9h4udo9L_g3r0sLPFAnN00" + ApiKey = SecretConstants.YoutubeApiKey, // TODO: Replace with an actual API key }); - private static readonly YoutubeClient _defaultYteClient = new YoutubeClient(); + private static readonly YoutubeClient _defaultYtClient = new YoutubeClient(); private static string[] Scopes { get; } = new string[] { @@ -41,14 +40,7 @@ namespace FoxTube YouTubeService.Scope.YoutubeForceSsl }; - private static ClientSecrets[] ClientSecrets { get; } = new ClientSecrets[MaxUsersCount] - { - new ClientSecrets - { - ClientId = "349735264870-2ekqlm0a4mkg3mmrfcv90s3qp3o15dq0.apps.googleusercontent.com", - ClientSecret = "BkVZOAaCU2Zclf0Zlicg6y2_" - } - }; + private static ClientSecrets[] ClientSecrets { get; } = SecretConstants.ClientSecrets; #endregion 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 User CurrentUser { get; set; } public static bool Authorized => CurrentUser != null; - public static ExtendedYouTubeService Service => IncognitoMode ? _defaultService : (CurrentUser?.Service ?? _defaultService); - public static YoutubeClient YoutubeClient => IncognitoMode ? _defaultYteClient : (CurrentUser?.Client ?? _defaultYteClient); + public static ExtendedYouTubeService Service + { + 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 UserStateUpdated; public static event EventHandler SubscriptionsChanged; + static UserService() => + Initialize(); + public static async Task AddUser() { int queueIndex = Users.ToList().FindIndex(i => i == null); @@ -100,14 +113,14 @@ namespace FoxTube ("Error details", result.ResponseErrorDetail.ToString())); break; } - + return false; } - public static async Task Initialize() + public static async void Initialize() { - Users = JsonConvert.DeserializeObject(storage.Values["UserManagement.Users"] as string ?? "") ?? new Userinfoplus[MaxUsersCount]; - int? lastUserIndex = storage.Values["UserManagement.LastUser"] as int?; + Users = JsonConvert.DeserializeObject(Storage.Registry.Values[UsersStorageKey] as string ?? "") ?? new Userinfoplus[MaxUsersCount]; + int? lastUserIndex = Storage.Registry.Values[LastUserInfoKey] as int?; if (lastUserIndex.HasValue && Users[lastUserIndex.Value] != null || (lastUserIndex = Users.ToList().FindIndex(i => i != null)) > -1) @@ -133,12 +146,12 @@ namespace FoxTube await CurrentUser.Credential.RevokeTokenAsync(CancellationToken.None); - storage.Values.Remove($"Subscriptions.{CurrentUser.UserInfo.Id}"); + Storage.Registry.Values.Remove($"Subscriptions.{CurrentUser.UserInfo.Id}"); CurrentUser = null; Users[Users.ToList().FindIndex(i => i.Id == userId)] = null; - storage.Values["UserManagement.Users"] = JsonConvert.SerializeObject(Users); - storage.Values["UserManagement.LastUser"] = null; + Storage.Registry.Values[UsersStorageKey] = JsonConvert.SerializeObject(Users); + Storage.Registry.Values[LastUserInfoKey] = null; if (Users.Any(i => i != null)) await SwitchUser(Users.ToList().FindIndex(i => i != null)); @@ -189,8 +202,8 @@ namespace FoxTube CurrentUser = await User.GetUser(credential); Users[userIndex] = CurrentUser.UserInfo; - storage.Values["UserManagement.Users"] = JsonConvert.SerializeObject(Users); - storage.Values["UserManagement.LastUser"] = userIndex; + Storage.Registry.Values[UsersStorageKey] = JsonConvert.SerializeObject(Users); + Storage.Registry.Values[LastUserInfoKey] = userIndex; credential.RefreshTokenUpdated += (s, e) => UpdateToken(CurrentUser.UserInfo.Id, credential.Token.RefreshToken); UpdateToken(CurrentUser.UserInfo.Id, credential.Token.RefreshToken); diff --git a/FoxTube.Core/Settings.cs b/FoxTube.Core/Settings.cs deleted file mode 100644 index e0c14b0..0000000 --- a/FoxTube.Core/Settings.cs +++ /dev/null @@ -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(); - } - } -} diff --git a/FoxTube.Core/Utils/StoreInterop.cs b/FoxTube.Core/Utils/AddonsInterop.cs similarity index 52% rename from FoxTube.Core/Utils/StoreInterop.cs rename to FoxTube.Core/Utils/AddonsInterop.cs index fa52ec3..66a13d1 100644 --- a/FoxTube.Core/Utils/StoreInterop.cs +++ b/FoxTube.Core/Utils/AddonsInterop.cs @@ -1,23 +1,27 @@ -using Microsoft.AppCenter.Crashes; +using Microsoft.Advertising.WinRT.UI; using System; using System.Threading.Tasks; using Windows.Services.Store; namespace FoxTube.Utils { - public static class StoreInterop + public static class AddonsInterop { public static bool AdsDisabled { get; private set; } = true; 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 string AdsId => UseTestAds ? "test" : "1100044398"; - private static string ProProductId => "9NP1QK556625"; + public static NativeAdsManagerV2 AdsManager => new NativeAdsManagerV2(ApplicationId, AdsId); - public static async Task UpdateStoreState() + static AddonsInterop() => + UpdateStoreState(); + + public static async void UpdateStoreState() { StoreProductQueryResult requset = await StoreContext.GetDefault().GetAssociatedStoreProductsAsync(new[] { "Durable" }); @@ -42,17 +46,5 @@ namespace FoxTube.Utils 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"); - } } } diff --git a/FoxTube.Core/Utils/Feedback.cs b/FoxTube.Core/Utils/FeedbackIterop.cs similarity index 62% rename from FoxTube.Core/Utils/Feedback.cs rename to FoxTube.Core/Utils/FeedbackIterop.cs index 44e0f52..8820bc9 100644 --- a/FoxTube.Core/Utils/Feedback.cs +++ b/FoxTube.Core/Utils/FeedbackIterop.cs @@ -1,11 +1,13 @@ using System; +using FoxTube.Services; +using Microsoft.AppCenter.Crashes; using Microsoft.Services.Store.Engagement; -using Windows.System; +using Windows.Services.Store; using Windows.UI.Xaml.Controls; namespace FoxTube.Utils { - public static class Feedback + public static class FeedbackInterop { public static bool HasFeedbackHub => StoreServicesFeedbackLauncher.IsSupported(); @@ -13,15 +15,25 @@ namespace FoxTube.Utils { if (HasFeedbackHub) 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() { if (!HasFeedbackHub) { - Settings.PromptFeedback = false; + Storage.SetValue(Storage.Settings.PromptFeedback, false); return; } @@ -41,7 +53,7 @@ namespace FoxTube.Utils ContentDialogResult result = await dialog.ShowAsync(); if (result != ContentDialogResult.None) - Settings.PromptFeedback = false; + Storage.SetValue(Storage.Settings.PromptFeedback, false); if (result == ContentDialogResult.Primary) OpenFeedbackHub(); @@ -61,17 +73,17 @@ namespace FoxTube.Utils 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(); if (result != ContentDialogResult.None) - Settings.PromptReview = false; + Storage.SetValue(Storage.Settings.PromptReview, false); if (result == ContentDialogResult.Primary) - StoreInterop.RequestReview(); + RequestStoreReview(); } } } diff --git a/FoxTube.Core/Utils/Metrics.cs b/FoxTube.Core/Utils/Metrics.cs index 7d45269..8e296cd 100644 --- a/FoxTube.Core/Utils/Metrics.cs +++ b/FoxTube.Core/Utils/Metrics.cs @@ -6,19 +6,17 @@ using System.Linq; using System.Collections.Generic; using System.Diagnostics; using Windows.ApplicationModel; -using Windows.Storage; +using FoxTube.Services; 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; + get => Storage.GetValue(Storage.Metrics.Uptime) ?? TimeSpan.FromSeconds(0); + set => Storage.SetValue(Storage.Metrics.Uptime, value); } public static string CurrentVersion { @@ -32,11 +30,11 @@ namespace FoxTube.Utils static Metrics() { sw.Start(); - if (!Settings.AllowAnalytics) + if (!Storage.GetValue(Storage.Settings.AllowAnalytics)) return; - AppCenter.Start("45774462-9ea7-438a-96fc-03982666f39e", typeof(Analytics), typeof(Crashes)); - AppCenter.SetCountryCode(Settings.Region); + AppCenter.Start(SecretConstants.MetricsId, typeof(Analytics), typeof(Crashes)); + AppCenter.SetCountryCode(Storage.GetValue(Storage.Settings.Region)); AppCenter.LogLevel = LogLevel.Verbose; } @@ -45,23 +43,30 @@ namespace FoxTube.Utils sw.Stop(); Uptime += sw.Elapsed; - AddEvent("Session closed", - ("Duration", sw.Elapsed.ToString()), - ("Spend time total", Uptime.ToString())); + if (Storage.GetValue(Storage.Settings.AllowAnalytics)) + 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(i.key, i.value)) as Dictionary); + public static void AddEvent(string eventName, params (string key, string value)[] details) + { + if (Storage.GetValue(Storage.Settings.AllowAnalytics)) + Analytics.TrackEvent(eventName, + details.Length < 1 ? null : + details.Select(i => new KeyValuePair(i.key, i.value)) as Dictionary); + } public static void SendReport(Exception exception, ErrorAttachmentLog[] logs = null, params (string key, string value)[] details) { - logs ??= new ErrorAttachmentLog[0]; - Crashes.TrackError(exception, - details.Length < 1 ? null : - details.Select(i => new KeyValuePair(i.key, i.value)) as Dictionary, - logs); + if (Storage.GetValue(Storage.Settings.AllowAnalytics)) + { + logs ??= new ErrorAttachmentLog[0]; + Crashes.TrackError(exception ?? new Exception("Unknown exception"), + details.Length < 1 ? null : + details.Select(i => new KeyValuePair(i.key, i.value)) as Dictionary, + logs); + } } } } \ No newline at end of file diff --git a/FoxTube.Core/Utils/SecretConstants.cs b/FoxTube.Core/Utils/SecretConstants.cs new file mode 100644 index 0000000..b3d3b39 --- /dev/null +++ b/FoxTube.Core/Utils/SecretConstants.cs @@ -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_" + } + }; + } +} diff --git a/FoxTube.Core/Utils/Utils.cs b/FoxTube.Core/Utils/Utils.cs index ca9d1ef..c3de3c6 100644 --- a/FoxTube.Core/Utils/Utils.cs +++ b/FoxTube.Core/Utils/Utils.cs @@ -1,4 +1,5 @@ -using System; +using FoxTube.Services; +using System; using Windows.ApplicationModel.Core; using Windows.Security.Credentials; @@ -25,10 +26,10 @@ namespace FoxTube.Utils public static async void RestartApp(string args) => await CoreApplication.RequestRestartAsync(args ?? ""); - public static void InitializeFailsafeProtocol() + public static async void InitializeFailsafeProtocol() { Metrics.AddEvent("Failsafe protocol initiated"); - Settings.ResetSettings(); + await Storage.ResetStorage(); PasswordVault passwordVault = new PasswordVault(); foreach (PasswordCredential credential in passwordVault.RetrieveAll()) passwordVault.Remove(credential);