Refactored core
UI navigation framework Related Work Items: #408, #414, #416
This commit is contained in:
@@ -132,26 +132,28 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="Models\Collections\InboxCollection.cs" />
|
||||
<Compile Include="Models\Collections\ViewCollection.cs" />
|
||||
<Compile Include="Models\SearchParameters.cs" />
|
||||
<Compile Include="Models\Subscription.cs" />
|
||||
<Compile Include="Services\DownloadsService.cs" />
|
||||
<Compile Include="Extensions.cs" />
|
||||
<Compile Include="Services\SettingsService.cs" />
|
||||
<Compile Include="Utils\FeedbackIterop.cs" />
|
||||
<Compile Include="Services\History.cs" />
|
||||
<Compile Include="Services\InboxService.cs" />
|
||||
<Compile Include="Utils\Metrics.cs" />
|
||||
<Compile Include="Services\Search.cs" />
|
||||
<Compile Include="Services\Storage.cs" />
|
||||
<Compile Include="Services\StorageService.cs" />
|
||||
<Compile Include="Utils\AddonsInterop.cs" />
|
||||
<Compile Include="Services\UserService.cs" />
|
||||
<Compile Include="Utils\SecretConstants.cs" />
|
||||
<Compile Include="Utils\BackgroundManager.cs" />
|
||||
<Compile Include="Utils\ToastTemplates.cs" />
|
||||
<Compile Include="Utils\Utils.cs" />
|
||||
<Compile Include="Models\DownloadItem.cs" />
|
||||
<Compile Include="Models\InboxItem.cs" />
|
||||
<Compile Include="Models\Attributes.cs" />
|
||||
<Compile Include="Models\SavedVideo.cs" />
|
||||
<Compile Include="Models\SearchSuggestion.cs" />
|
||||
<Compile Include="Models\User.cs" />
|
||||
<Compile Include="Models\VideoItem.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<EmbeddedResource Include="Properties\FoxTube.Core.rd.xml" />
|
||||
</ItemGroup>
|
||||
@@ -163,22 +165,25 @@
|
||||
<Version>1.0.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Apis.Auth">
|
||||
<Version>1.45.0</Version>
|
||||
<Version>1.46.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Apis.Blogger.v3">
|
||||
<Version>1.46.0.1986</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Apis.Oauth2.v2">
|
||||
<Version>1.44.1.1869</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Google.Apis.YouTube.v3">
|
||||
<Version>1.45.0.1929</Version>
|
||||
<Version>1.46.0.1987</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Advertising.XAML">
|
||||
<Version>10.1811.22001</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AppCenter.Analytics">
|
||||
<Version>3.2.1</Version>
|
||||
<Version>3.2.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AppCenter.Crashes">
|
||||
<Version>3.2.1</Version>
|
||||
<Version>3.2.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
||||
<Version>6.2.10</Version>
|
||||
@@ -186,11 +191,14 @@
|
||||
<PackageReference Include="Microsoft.Services.Store.Engagement">
|
||||
<Version>10.1901.28001</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.UI.Xaml">
|
||||
<Version>2.4.0</Version>
|
||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications">
|
||||
<Version>6.0.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="QueryString.NET">
|
||||
<Version>1.0.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="YoutubeExplode">
|
||||
<Version>5.0.4</Version>
|
||||
<Version>5.0.5</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,45 +1,49 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using FoxTube.Services;
|
||||
using FoxTube.Utils;
|
||||
using Google.Apis.Blogger.v3;
|
||||
using Google.Apis.Blogger.v3.Data;
|
||||
|
||||
namespace FoxTube.Models.Collections
|
||||
{
|
||||
public class InboxCollection : ViewCollection<InboxItem>
|
||||
public class InboxCollection : ViewCollection<Post>
|
||||
{
|
||||
private int _pageNumber = 0;
|
||||
private HttpClient _httpClient = new HttpClient();
|
||||
private string nextPageToken;
|
||||
|
||||
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)
|
||||
try
|
||||
{
|
||||
PostsResource.ListRequest request = InboxService.Service.Posts.List(SecretConstants.BlogId);
|
||||
request.FetchImages = false;
|
||||
request.PageToken = nextPageToken;
|
||||
request.Labels = "FoxTube";
|
||||
request.MaxResults = ItemsPerRequest;
|
||||
request.OrderBy = PostsResource.ListRequest.OrderByEnum.UPDATED;
|
||||
|
||||
PostList response = await request.ExecuteAsync();
|
||||
|
||||
foreach (Post post in response.Items)
|
||||
Items.Add(post);
|
||||
|
||||
HasMoreItems = !string.IsNullOrWhiteSpace(nextPageToken = response.NextPageToken);
|
||||
|
||||
return new LoadMoreItemsResult
|
||||
{
|
||||
Count = (uint)response.Items.Count
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Metrics.SendReport(new Exception("Unable to load inbox", e));
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace FoxTube.Models.Collections
|
||||
{
|
||||
public abstract class ViewCollection<T> : ObservableCollection<T>, ISupportIncrementalLoading
|
||||
{
|
||||
public int ItemsPerRequest { get; set; }
|
||||
public int ItemsPerRequest { get; set; } = 25;
|
||||
public bool HasMoreItems { get; protected set; } = true;
|
||||
|
||||
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count) =>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
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()}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FoxTube.Models
|
||||
{
|
||||
public class SearchParameters
|
||||
{
|
||||
public SearchParameters(string query)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,10 @@ using Google.Apis.Oauth2.v2.Data;
|
||||
using Google.Apis.Services;
|
||||
using Google.Apis.YouTube.v3;
|
||||
using Google.Apis.YouTube.v3.Data;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using YouTube;
|
||||
using YoutubeExplode;
|
||||
using FoxTube.Services;
|
||||
@@ -42,8 +40,6 @@ namespace FoxTube.Models
|
||||
|
||||
UserService.SubscriptionsChangedInvoker(this, subscription);
|
||||
Subscriptions.Remove(subscription);
|
||||
|
||||
SaveSubscriptions();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@@ -79,21 +75,10 @@ namespace FoxTube.Models
|
||||
Subscriptions.Add(subscription);
|
||||
|
||||
UserService.SubscriptionsChangedInvoker(this, subscription);
|
||||
|
||||
SaveSubscriptions();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveSubscriptions()
|
||||
{
|
||||
Dictionary<string, string> subs = Subscriptions.Select(i =>
|
||||
new KeyValuePair<string, string>(i.ChannelId, i.Avatar.Default__?.Url))
|
||||
as Dictionary<string, string>;
|
||||
|
||||
ApplicationData.Current.RoamingSettings.Values[$"Subscriptions.{UserInfo.Id}"] = JsonConvert.SerializeObject(subs);
|
||||
}
|
||||
|
||||
public static async Task<User> GetUser(UserCredential credential)
|
||||
{
|
||||
User user = new User
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
using FoxTube.Services;
|
||||
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(UserService.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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,9 @@ namespace FoxTube.Services
|
||||
public static List<SavedVideo> History { get; } = new List<SavedVideo>();
|
||||
public static List<DownloadItem> Queue { get; } = new List<DownloadItem>();
|
||||
|
||||
static DownloadsService() =>
|
||||
Initialize();
|
||||
|
||||
private static async void Initialize()
|
||||
public static async Task Initialize()
|
||||
{
|
||||
StorageFile file = await Storage.Folder.CreateFileAsync("DownloadHistory.json", CreationCollisionOption.OpenIfExists);
|
||||
StorageFile file = await StorageService.Folder.CreateFileAsync("DownloadHistory.json", CreationCollisionOption.OpenIfExists);
|
||||
try
|
||||
{
|
||||
List<SavedVideo> savedVideos = JsonConvert.DeserializeObject<List<SavedVideo>>(File.ReadAllText(file.Path) ?? "") ?? new List<SavedVideo>();
|
||||
@@ -70,7 +67,7 @@ namespace FoxTube.Services
|
||||
|
||||
History.Add(savedItem);
|
||||
|
||||
StorageFile file = await Storage.Folder.CreateFileAsync("DownloadHistory.json", CreationCollisionOption.OpenIfExists);
|
||||
StorageFile file = await StorageService.Folder.CreateFileAsync("DownloadHistory.json", CreationCollisionOption.OpenIfExists);
|
||||
File.WriteAllText(file.Path, JsonConvert.SerializeObject(History));
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
@@ -112,7 +109,7 @@ namespace FoxTube.Services
|
||||
|
||||
public static async Task<StorageFolder> GetDefaultDownloadsFolder()
|
||||
{
|
||||
if (Storage.GetValue<string>(Storage.Settings.DefaultDownloadsFolder) is string token && !string.IsNullOrWhiteSpace(token))
|
||||
if (SettingsService.DefaultDownloadsFolder is string token && !string.IsNullOrWhiteSpace(token))
|
||||
return await StorageApplicationPermissions.FutureAccessList.GetFolderAsync(token) ??
|
||||
await KnownFolders.VideosLibrary.CreateFolderAsync("FoxTube", CreationCollisionOption.OpenIfExists);
|
||||
else
|
||||
@@ -137,12 +134,12 @@ namespace FoxTube.Services
|
||||
|
||||
if (folder != null)
|
||||
{
|
||||
if (Storage.GetValue<string>(Storage.Settings.DefaultDownloadsFolder) is string token && !string.IsNullOrWhiteSpace(token))
|
||||
if (SettingsService.DefaultDownloadsFolder is string token && !string.IsNullOrWhiteSpace(token))
|
||||
StorageApplicationPermissions.FutureAccessList.AddOrReplace(token, folder);
|
||||
else
|
||||
{
|
||||
token = StorageApplicationPermissions.FutureAccessList.Add(folder);
|
||||
Storage.SetValue(Storage.Settings.DefaultDownloadsFolder, token);
|
||||
SettingsService.DefaultDownloadsFolder = token;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
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.UI.Notifications;
|
||||
using Google.Apis.Blogger.v3;
|
||||
using Google.Apis.Blogger.v3.Data;
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
using Windows.Foundation.Metadata;
|
||||
|
||||
namespace FoxTube.Services
|
||||
{
|
||||
public static class InboxService
|
||||
{
|
||||
public const string lastChangelogVersionKey = "Inbox.lastChangelogVersion";
|
||||
public const string lastCheckKey = "Inbox.lastChangelogVersion";
|
||||
public static BloggerService Service { get; } = new BloggerService(new Google.Apis.Services.BaseClientService.Initializer
|
||||
{
|
||||
ApplicationName = "FoxTube",
|
||||
ApiKey = SecretConstants.BloggerApiKey
|
||||
});
|
||||
|
||||
private static readonly HttpClient client = new HttpClient();
|
||||
|
||||
@@ -23,22 +29,19 @@ namespace FoxTube.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: Add backend
|
||||
HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/FoxTube/Inbox?" +
|
||||
$"toast=true&" +
|
||||
$"publishedAfter={Storage.Registry.Values[lastCheckKey]}&" +
|
||||
$"lang={Storage.GetValue<string>(Storage.Settings.UILanguage)}&" +
|
||||
$"appVersion={Metrics.CurrentVersion}");
|
||||
PostsResource.ListRequest request = Service.Posts.List(SecretConstants.BlogId);
|
||||
request.FetchImages = false;
|
||||
request.Labels = "FoxTube";
|
||||
request.MaxResults = 500;
|
||||
request.StartDate = StorageService.LastInboxCheck.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK", DateTimeFormatInfo.InvariantInfo);
|
||||
request.OrderBy = PostsResource.ListRequest.OrderByEnum.UPDATED;
|
||||
|
||||
Storage.Registry.Values[lastCheckKey] = DateTime.UtcNow.Ticks;
|
||||
PostList response = await request.ExecuteAsync();
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.NoContent)
|
||||
return;
|
||||
foreach(Post post in response.Items.Where(i => !i.Labels.Contains("Changelog")))
|
||||
ToastNotificationManager.CreateToastNotifier().Show(ToastTemplates.GetBlogpostToast(post));
|
||||
|
||||
string[] toasts = JsonConvert.DeserializeObject<string[]>(await response.Content.ReadAsStringAsync());
|
||||
|
||||
foreach (string toast in toasts)
|
||||
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(toast.ToXml()));
|
||||
StorageService.LastInboxCheck = DateTime.UtcNow;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -46,27 +49,33 @@ namespace FoxTube.Services
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add changelog retrieval
|
||||
/// <summary>
|
||||
/// Fires toast notification with the last changelog content
|
||||
/// </summary>
|
||||
[Deprecated("In future versions it will be replaced with splash screen full size message", DeprecationType.Deprecate, 1)]
|
||||
public static async void PushChangelog()
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: Add backend
|
||||
if ((string)Storage.Registry.Values[lastChangelogVersionKey] == Metrics.CurrentVersion)
|
||||
if (string.IsNullOrWhiteSpace(StorageService.LastOpenedVersion))
|
||||
StorageService.LastOpenedVersion = Metrics.CurrentVersion;
|
||||
|
||||
string lastVersion = StorageService.LastOpenedVersion;
|
||||
if (string.IsNullOrWhiteSpace(lastVersion) || lastVersion == Metrics.CurrentVersion)
|
||||
return;
|
||||
|
||||
Storage.Registry.Values[lastChangelogVersionKey] = Metrics.CurrentVersion;
|
||||
PostsResource.ListRequest request = Service.Posts.List(SecretConstants.BlogId);
|
||||
request.FetchImages = false;
|
||||
request.Labels = $"FoxTube,Changelog,v{Metrics.CurrentVersion}";
|
||||
request.MaxResults = 1;
|
||||
|
||||
HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/API/FoxTube/Changelog?" +
|
||||
$"lang={Storage.GetValue<string>(Storage.Settings.UILanguage)}&" +
|
||||
$"version={Metrics.CurrentVersion}");
|
||||
PostList response = await request.ExecuteAsync();
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.NoContent)
|
||||
return;
|
||||
if (response.Items.Count > 0)
|
||||
ToastNotificationManager.CreateToastNotifier().Show(ToastTemplates.GetBlogpostToast(response.Items.First()));
|
||||
|
||||
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification((await response.Content.ReadAsStringAsync()).ToXml()));
|
||||
StorageService.LastOpenedVersion = Metrics.CurrentVersion;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -16,7 +16,12 @@ 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={Storage.GetValue<string>(Storage.Settings.RelevanceLanguage)}");
|
||||
string results = await client.GetStringAsync($"http://suggestqueries.google.com/complete/search?" +
|
||||
$"ds=yt&" +
|
||||
$"client=toolbar&" +
|
||||
$"q={term}&" +
|
||||
$"hl={SettingsService.RelevanceLanguage}");
|
||||
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.LoadXml(results);
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
using Windows.Storage;
|
||||
using System.Globalization;
|
||||
using System.Diagnostics;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace FoxTube.Services
|
||||
{
|
||||
public class SettingsService
|
||||
{
|
||||
private static readonly ApplicationDataContainer storage = ApplicationData.Current.RoamingSettings;
|
||||
|
||||
public static void ResetSettings() =>
|
||||
storage.Values.Clear();
|
||||
|
||||
public static ElementTheme Theme
|
||||
{
|
||||
get => (ElementTheme)GetValue(ElementTheme.Default);
|
||||
set => SetValue((int)value);
|
||||
}
|
||||
|
||||
public static string RelevanceLanguage
|
||||
{
|
||||
get => GetValue(GetDefaultLanguage());
|
||||
set => SetValue(value);
|
||||
}
|
||||
|
||||
public static bool AllowAnalytics
|
||||
{
|
||||
get => GetValue(true);
|
||||
set => SetValue(value);
|
||||
}
|
||||
|
||||
public static bool AskEveryDownload
|
||||
{
|
||||
get => GetValue(true);
|
||||
set => SetValue(value);
|
||||
}
|
||||
|
||||
public static string DefaultDownloadsFolder
|
||||
{
|
||||
get => GetValue(null);
|
||||
set => SetValue(value);
|
||||
}
|
||||
|
||||
#region Service methods
|
||||
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";
|
||||
}
|
||||
|
||||
private static dynamic GetValue(object defaultValue)
|
||||
{
|
||||
string propName = (new StackTrace()).GetFrame(1).GetMethod().Name.Substring(4);
|
||||
return storage.Values[$"Settings.{propName}"] ?? defaultValue;
|
||||
}
|
||||
|
||||
private static void SetValue(object value)
|
||||
{
|
||||
string propName = (new StackTrace()).GetFrame(1).GetMethod().Name.Substring(4);
|
||||
storage.Values[$"Settings.{propName}"] = value;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace FoxTube.Services
|
||||
{
|
||||
public static class StorageService
|
||||
{
|
||||
private static readonly ApplicationDataContainer storage = ApplicationData.Current.RoamingSettings;
|
||||
|
||||
public static StorageFolder Folder => ApplicationData.Current.RoamingFolder;
|
||||
|
||||
public static bool PromptFeedback
|
||||
{
|
||||
get => GetValue(Uptime.TotalHours > 12);
|
||||
set => SetValue(value);
|
||||
}
|
||||
|
||||
public static bool PromptReview
|
||||
{
|
||||
get => GetValue(Uptime.TotalHours > 24);
|
||||
set => SetValue(value);
|
||||
}
|
||||
|
||||
public static TimeSpan Uptime
|
||||
{
|
||||
get => GetValue(TimeSpan.FromSeconds(0));
|
||||
set => SetValue(value);
|
||||
}
|
||||
|
||||
public static DateTime LastInboxCheck
|
||||
{
|
||||
get => GetValue(DateTime.UtcNow);
|
||||
set => SetValue(value);
|
||||
}
|
||||
|
||||
public static string LastOpenedVersion
|
||||
{
|
||||
get => GetValue(null);
|
||||
set => SetValue(value);
|
||||
}
|
||||
|
||||
public static int? LastUserIndex
|
||||
{
|
||||
get => GetValue(null);
|
||||
set => SetValue(value);
|
||||
}
|
||||
|
||||
public static string UserInfos
|
||||
{
|
||||
get => GetValue(null);
|
||||
set => SetValue(value);
|
||||
}
|
||||
|
||||
public static async Task ClearStorage()
|
||||
{
|
||||
storage.Values.Clear();
|
||||
foreach (IStorageItem i in await Folder.GetItemsAsync())
|
||||
await i.DeleteAsync();
|
||||
}
|
||||
|
||||
#region Service methods
|
||||
private static dynamic GetValue(object defaultValue)
|
||||
{
|
||||
string propName = (new StackTrace()).GetFrame(1).GetMethod().Name.Substring(4);
|
||||
return storage.Values[$"Storage.{propName}"] ?? defaultValue;
|
||||
}
|
||||
|
||||
private static void SetValue(object value)
|
||||
{
|
||||
string propName = (new StackTrace()).GetFrame(1).GetMethod().Name.Substring(4);
|
||||
storage.Values[$"Storage.{propName}"] = value;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,6 @@ namespace FoxTube.Services
|
||||
{
|
||||
public static class UserService
|
||||
{
|
||||
public const string UsersStorageKey = "UserService.Users";
|
||||
public const string LastUserInfoKey = "UserService.LastUser";
|
||||
|
||||
public const int MaxUsersCount = 1;
|
||||
|
||||
#region Private members
|
||||
@@ -74,9 +71,6 @@ namespace FoxTube.Services
|
||||
public static event EventHandler<bool> UserStateUpdated;
|
||||
public static event EventHandler<Subscription> SubscriptionsChanged;
|
||||
|
||||
static UserService() =>
|
||||
Initialize();
|
||||
|
||||
public static async Task<bool> AddUser()
|
||||
{
|
||||
int queueIndex = Users.ToList().FindIndex(i => i == null);
|
||||
@@ -117,10 +111,10 @@ namespace FoxTube.Services
|
||||
return false;
|
||||
}
|
||||
|
||||
public static async void Initialize()
|
||||
public static async Task Initialize()
|
||||
{
|
||||
Users = JsonConvert.DeserializeObject<Userinfoplus[]>(Storage.Registry.Values[UsersStorageKey] as string ?? "") ?? new Userinfoplus[MaxUsersCount];
|
||||
int? lastUserIndex = Storage.Registry.Values[LastUserInfoKey] as int?;
|
||||
Users = JsonConvert.DeserializeObject<Userinfoplus[]>(StorageService.UserInfos ?? "") ?? new Userinfoplus[MaxUsersCount];
|
||||
int? lastUserIndex = StorageService.LastUserIndex;
|
||||
|
||||
if (lastUserIndex.HasValue && Users[lastUserIndex.Value] != null ||
|
||||
(lastUserIndex = Users.ToList().FindIndex(i => i != null)) > -1)
|
||||
@@ -146,12 +140,11 @@ namespace FoxTube.Services
|
||||
|
||||
await CurrentUser.Credential.RevokeTokenAsync(CancellationToken.None);
|
||||
|
||||
Storage.Registry.Values.Remove($"Subscriptions.{CurrentUser.UserInfo.Id}");
|
||||
CurrentUser = null;
|
||||
Users[Users.ToList().FindIndex(i => i.Id == userId)] = null;
|
||||
|
||||
Storage.Registry.Values[UsersStorageKey] = JsonConvert.SerializeObject(Users);
|
||||
Storage.Registry.Values[LastUserInfoKey] = null;
|
||||
StorageService.UserInfos = JsonConvert.SerializeObject(Users);
|
||||
StorageService.LastUserIndex = null;
|
||||
|
||||
if (Users.Any(i => i != null))
|
||||
await SwitchUser(Users.ToList().FindIndex(i => i != null));
|
||||
@@ -202,8 +195,8 @@ namespace FoxTube.Services
|
||||
CurrentUser = await User.GetUser(credential);
|
||||
Users[userIndex] = CurrentUser.UserInfo;
|
||||
|
||||
Storage.Registry.Values[UsersStorageKey] = JsonConvert.SerializeObject(Users);
|
||||
Storage.Registry.Values[LastUserInfoKey] = userIndex;
|
||||
StorageService.UserInfos = JsonConvert.SerializeObject(Users);
|
||||
StorageService.LastUserIndex = userIndex;
|
||||
|
||||
credential.RefreshTokenUpdated += (s, e) => UpdateToken(CurrentUser.UserInfo.Id, credential.Token.RefreshToken);
|
||||
UpdateToken(CurrentUser.UserInfo.Id, credential.Token.RefreshToken);
|
||||
|
||||
@@ -18,18 +18,15 @@ namespace FoxTube.Utils
|
||||
|
||||
public static NativeAdsManagerV2 AdsManager => new NativeAdsManagerV2(ApplicationId, AdsId);
|
||||
|
||||
static AddonsInterop() =>
|
||||
UpdateStoreState();
|
||||
|
||||
public static async void UpdateStoreState()
|
||||
public static async Task<bool> UpdateProPurchasedState()
|
||||
{
|
||||
StoreProductQueryResult requset = await StoreContext.GetDefault().GetAssociatedStoreProductsAsync(new[] { "Durable" });
|
||||
|
||||
if (requset.Products[ProProductId].IsInUserCollection)
|
||||
return;
|
||||
return AdsDisabled = true;
|
||||
|
||||
Price = requset.Products[ProProductId].Price.FormattedPrice;
|
||||
AdsDisabled = false;
|
||||
return AdsDisabled = false;
|
||||
}
|
||||
|
||||
public static async Task<bool> PurchaseApp()
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Windows.ApplicationModel.Background;
|
||||
using Windows.System.Power;
|
||||
using Windows.UI.Notifications;
|
||||
|
||||
namespace FoxTube.Utils
|
||||
{
|
||||
// TODO: Complete class
|
||||
public static class BackgroundManager
|
||||
{
|
||||
public static async Task PerformBackgroundTask(BackgroundActivatedEventArgs args)
|
||||
{
|
||||
|
||||
if (args.TaskInstance.TriggerDetails is ToastNotificationActionTriggerDetail details)
|
||||
{
|
||||
ProcessBackgroundAction(details.Argument);
|
||||
// TODO: Process toast parameters
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Restore user
|
||||
// TODO: Update subscriptions
|
||||
// TODO: Update homepage cache
|
||||
}
|
||||
|
||||
//var saverRequest = PowerManager.EnergySaverStatus;
|
||||
}
|
||||
|
||||
public static async Task ProcessBackgroundAction(string uri)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static async void RegisterBackgroundTasks()
|
||||
{
|
||||
var backgroundRequest = await BackgroundExecutionManager.RequestAccessAsync();
|
||||
if (backgroundRequest == BackgroundAccessStatus.DeniedBySystemPolicy || backgroundRequest == BackgroundAccessStatus.DeniedByUser)
|
||||
return;
|
||||
|
||||
RegisterBackgoundTask("FoxtubeToastBackground", new ToastNotificationActionTrigger());
|
||||
RegisterBackgoundTask("FoxtubeBackground", new TimeTrigger(15, false));
|
||||
}
|
||||
|
||||
private static void RegisterBackgoundTask(string taskName, IBackgroundTrigger trigger)
|
||||
{
|
||||
if (BackgroundTaskRegistration.AllTasks.Any(i => i.Value.Name.Equals(taskName)))
|
||||
return;
|
||||
|
||||
BackgroundTaskBuilder builder = new BackgroundTaskBuilder
|
||||
{
|
||||
Name = taskName,
|
||||
IsNetworkRequested = true
|
||||
};
|
||||
builder.SetTrigger(trigger);
|
||||
|
||||
BackgroundTaskRegistration registration = builder.Register();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ namespace FoxTube.Utils
|
||||
{
|
||||
if (!HasFeedbackHub)
|
||||
{
|
||||
Storage.SetValue(Storage.Settings.PromptFeedback, false);
|
||||
StorageService.PromptFeedback = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace FoxTube.Utils
|
||||
ContentDialogResult result = await dialog.ShowAsync();
|
||||
|
||||
if (result != ContentDialogResult.None)
|
||||
Storage.SetValue(Storage.Settings.PromptFeedback, false);
|
||||
StorageService.PromptFeedback = false;
|
||||
|
||||
if (result == ContentDialogResult.Primary)
|
||||
OpenFeedbackHub();
|
||||
@@ -80,7 +80,7 @@ namespace FoxTube.Utils
|
||||
ContentDialogResult result = await dialog.ShowAsync();
|
||||
|
||||
if (result != ContentDialogResult.None)
|
||||
Storage.SetValue(Storage.Settings.PromptReview, false);
|
||||
StorageService.PromptFeedback = false;
|
||||
|
||||
if (result == ContentDialogResult.Primary)
|
||||
RequestStoreReview();
|
||||
|
||||
@@ -5,8 +5,8 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Windows.ApplicationModel;
|
||||
using FoxTube.Services;
|
||||
using System.Globalization;
|
||||
|
||||
namespace FoxTube.Utils
|
||||
{
|
||||
@@ -15,26 +15,29 @@ namespace FoxTube.Utils
|
||||
static readonly Stopwatch sw = new Stopwatch();
|
||||
public static TimeSpan Uptime
|
||||
{
|
||||
get => Storage.GetValue<TimeSpan?>(Storage.Metrics.Uptime) ?? TimeSpan.FromSeconds(0);
|
||||
set => Storage.SetValue(Storage.Metrics.Uptime, value);
|
||||
get => StorageService.Uptime;
|
||||
set => StorageService.Uptime = value;
|
||||
}
|
||||
|
||||
public static string CurrentVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
PackageVersion v = Package.Current.Id.Version;
|
||||
return $"{v.Major}.{v.Minor}.{v.Revision}.{v.Build}";
|
||||
// TODO: Remove on release
|
||||
/*PackageVersion v = Package.Current.Id.Version;
|
||||
return $"{v.Major}.{v.Minor}.{v.Revision}.{v.Build}";*/
|
||||
return "2.0 Preview 1";
|
||||
}
|
||||
}
|
||||
|
||||
static Metrics()
|
||||
{
|
||||
sw.Start();
|
||||
if (!Storage.GetValue<bool>(Storage.Settings.AllowAnalytics))
|
||||
if (!SettingsService.AllowAnalytics)
|
||||
return;
|
||||
|
||||
AppCenter.Start(SecretConstants.MetricsId, typeof(Analytics), typeof(Crashes));
|
||||
AppCenter.SetCountryCode(Storage.GetValue<string>(Storage.Settings.Region));
|
||||
AppCenter.SetCountryCode(RegionInfo.CurrentRegion.TwoLetterISORegionName);
|
||||
AppCenter.LogLevel = LogLevel.Verbose;
|
||||
}
|
||||
|
||||
@@ -43,7 +46,7 @@ namespace FoxTube.Utils
|
||||
sw.Stop();
|
||||
Uptime += sw.Elapsed;
|
||||
|
||||
if (Storage.GetValue<bool>(Storage.Settings.AllowAnalytics))
|
||||
if (SettingsService.AllowAnalytics)
|
||||
AddEvent("Session closed",
|
||||
("Duration", sw.Elapsed.ToString()),
|
||||
("Spend time total", Uptime.ToString()));
|
||||
@@ -51,7 +54,7 @@ namespace FoxTube.Utils
|
||||
|
||||
public static void AddEvent(string eventName, params (string key, string value)[] details)
|
||||
{
|
||||
if (Storage.GetValue<bool>(Storage.Settings.AllowAnalytics))
|
||||
if (SettingsService.AllowAnalytics)
|
||||
Analytics.TrackEvent(eventName,
|
||||
details.Length < 1 ? null :
|
||||
details.Select(i => new KeyValuePair<string, string>(i.key, i.value)) as Dictionary<string, string>);
|
||||
@@ -59,7 +62,7 @@ namespace FoxTube.Utils
|
||||
|
||||
public static void SendReport(Exception exception, ErrorAttachmentLog[] logs = null, params (string key, string value)[] details)
|
||||
{
|
||||
if (Storage.GetValue<bool>(Storage.Settings.AllowAnalytics))
|
||||
if (SettingsService.AllowAnalytics)
|
||||
{
|
||||
logs ??= new ErrorAttachmentLog[0];
|
||||
Crashes.TrackError(exception ?? new Exception("Unknown exception"),
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace FoxTube.Utils
|
||||
public static class SecretConstants
|
||||
{
|
||||
public const string YoutubeApiKey = "AIzaSyBgHrCnrlzlVmk0cJKL8RqP9Y8x6XSuk_0";
|
||||
public const string BloggerApiKey = "AIzaSyD7tpbuvmYDv9h4udo9L_g3r0sLPFAnN00";
|
||||
public const string BlogId = "8566398713922921363";
|
||||
public const string MetricsId = "45774462-9ea7-438a-96fc-03982666f39e";
|
||||
public const string ProAddonId = "9NP1QK556625";
|
||||
public const string AdsUnitId = "1100044398";
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Linq;
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
using Google.Apis.Blogger.v3.Data;
|
||||
using Windows.UI.Notifications;
|
||||
using AngleSharp.Html.Parser;
|
||||
|
||||
namespace FoxTube.Utils
|
||||
{
|
||||
public static class ToastTemplates
|
||||
{
|
||||
public static ToastNotification GetBlogpostToast(Post post)
|
||||
{
|
||||
ToastContent toastContent = new ToastContent
|
||||
{
|
||||
Visual = new ToastVisual
|
||||
{
|
||||
BindingGeneric = new ToastBindingGeneric
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new AdaptiveText
|
||||
{
|
||||
Text = new BindableString(post.Title)
|
||||
},
|
||||
new AdaptiveText()
|
||||
{
|
||||
Text = new BindableString(new HtmlParser().ParseDocument(post.Content).QuerySelector("p").TextContent),
|
||||
HintMaxLines = 2
|
||||
}
|
||||
},
|
||||
AppLogoOverride = new ToastGenericAppLogo
|
||||
{
|
||||
Source = post.Author.Image.Url,
|
||||
HintCrop = ToastGenericAppLogoCrop.Circle
|
||||
}
|
||||
}
|
||||
},
|
||||
Launch = $"Settings/Inbox/{post.Id}",
|
||||
ActivationType = ToastActivationType.Foreground
|
||||
};
|
||||
|
||||
if (post.Images.Count > 0)
|
||||
toastContent.Visual.BindingGeneric.HeroImage = new ToastGenericHeroImage
|
||||
{
|
||||
Source = post.Images.FirstOrDefault()?.Url
|
||||
};
|
||||
|
||||
return new ToastNotification(toastContent.GetXml());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
using System;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Security.Credentials;
|
||||
using Windows.UI;
|
||||
using Windows.UI.ViewManagement;
|
||||
|
||||
namespace FoxTube.Utils
|
||||
{
|
||||
@@ -29,11 +31,36 @@ namespace FoxTube.Utils
|
||||
public static async void InitializeFailsafeProtocol()
|
||||
{
|
||||
Metrics.AddEvent("Failsafe protocol initiated");
|
||||
await Storage.ResetStorage();
|
||||
await StorageService.ClearStorage();
|
||||
PasswordVault passwordVault = new PasswordVault();
|
||||
foreach (PasswordCredential credential in passwordVault.RetrieveAll())
|
||||
passwordVault.Remove(credential);
|
||||
RestartApp();
|
||||
}
|
||||
|
||||
public static void UpdateTitleBarTheme(bool isDark)
|
||||
{
|
||||
ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar;
|
||||
titleBar.ButtonBackgroundColor = Colors.Transparent;
|
||||
titleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
|
||||
titleBar.ButtonInactiveForegroundColor = Colors.Gray;
|
||||
|
||||
if (isDark)
|
||||
{
|
||||
titleBar.ButtonForegroundColor =
|
||||
titleBar.ButtonHoverForegroundColor =
|
||||
titleBar.ButtonPressedForegroundColor = Colors.White;
|
||||
titleBar.ButtonHoverBackgroundColor = Color.FromArgb(50, 255, 255, 255);
|
||||
titleBar.ButtonPressedBackgroundColor = Color.FromArgb(30, 255, 255, 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
titleBar.ButtonForegroundColor =
|
||||
titleBar.ButtonHoverForegroundColor =
|
||||
titleBar.ButtonPressedForegroundColor = Colors.Black;
|
||||
titleBar.ButtonHoverBackgroundColor = Color.FromArgb(50, 0, 0, 0);
|
||||
titleBar.ButtonPressedBackgroundColor = Color.FromArgb(70, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user