diff --git a/Directory.Packages.props b/Directory.Packages.props index e256ea4..786efe7 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -26,7 +26,7 @@ - + @@ -37,26 +37,26 @@ - - + + - - - + + + - - - + + + - + diff --git a/Firehose/App.xaml.cs b/Firehose/App.xaml.cs index a45e02e..e97dfec 100644 --- a/Firehose/App.xaml.cs +++ b/Firehose/App.xaml.cs @@ -31,9 +31,7 @@ protected override async void OnLaunched(LaunchActivatedEventArgs args) configureIOC(); Current.UnhandledException += Current_UnhandledException; - - Glob.Publications = await new API().GetPublications(); - + if (MainWindow.Content == null) { // When the navigation stack isn't restored navigate to the first page, diff --git a/Firehose/FirehoseApp.csproj b/Firehose/FirehoseApp.csproj index 3c3a516..618b387 100644 --- a/Firehose/FirehoseApp.csproj +++ b/Firehose/FirehoseApp.csproj @@ -1,11 +1,9 @@ - + net8.0-android; net8.0-ios; net8.0-maccatalyst; - net8.0-windows10.0.19041; - net8.0-desktop; Exe @@ -16,8 +14,8 @@ net.rarisma.REMNANT - 1.0 - 1 + 1.1.0 + 2 @@ -132,45 +131,60 @@ HeadlineTemplate="{StaticResource HeadlineTemplate}"/> - + + + + + + + - + - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + diff --git a/Firehose/UI/ArticleList.xaml.cs b/Firehose/UI/ArticleList.xaml.cs index 9f0bb87..d2261ea 100644 --- a/Firehose/UI/ArticleList.xaml.cs +++ b/Firehose/UI/ArticleList.xaml.cs @@ -1,6 +1,7 @@ using CommunityToolkit.Mvvm.DependencyInjection; using FirehoseApp.UI.Controls; using FirehoseApp.Viewmodels; +using HYDRANT; using HYDRANT.Definitions; using Microsoft.UI; using Microsoft.UI.Xaml.Media.Imaging; @@ -30,15 +31,12 @@ public async Task SetPublisherFilter() if (await Glob.OpenContentDialog(CD) == ContentDialogResult.Primary) { ShellVM.PublisherID = ((CD.Content as PublisherFilter).Content as ListView)!.SelectedIndex + 1; - ShellVM.FilterExtension = $"PUBLISHER_ID = {ShellVM.PublisherID}"; } else { ShellVM.PublisherID = -1; SourcesButton.Content = "Filter Publisher"; - ShellVM.FilterExtension = ""; } - ShellVM.FilterOrder = ShellVM.Filters[0].FilterOrder; ShellVM.Offset = 0; UpdateButtons(ShellVM.Filters[0]); ShellVM.LoadArticleDataCommand.Execute(null); @@ -46,10 +44,14 @@ public async Task SetPublisherFilter() public ArticleList() { + ShellVM.LoadAllDataCommand.Execute(null); ShellVM.UpdateButtonsDelegate = UpdateButtons; ShellVM.Articles = new(); + ShellVM.Offset = 0; + ShellVM.LoadMoreVisibility = Visibility.Visible; + + //Set correct filter InitializeComponent(); - ChangeFilter(ShellVM.Filters[0], new()); } private void OpenSettings(object sender, RoutedEventArgs e) @@ -78,7 +80,7 @@ public void UpdateButtons(Button Button) //Bookmarks button isn't in the filters stack panel so clear them manually. ShellVM.BookmarksButtonBackground = new SolidColorBrush(Colors.Transparent); ShellVM.BookmarksButtonForeground = Themer.SecondaryBrush; - + //Set filter by button. if (ShellVM.PublisherID != -1) { @@ -109,16 +111,6 @@ public void UpdateButtons(Button Button) Button.Foreground = Themer.MainBrush; } - private void ChangeFilter(object sender, RoutedEventArgs e) - { - ShellVM.Offset = 0; - ShellVM.LoadMoreVisibility = Visibility.Visible; - UpdateButtons((Button)sender); - - //Set correct filter - ShellVM.LoadArticleDataCommand.Execute(null); - } - private void ArticleList_OnLoaded(object sender, RoutedEventArgs e) => Glob.XamlRoot = XamlRoot; private void ShowBookmarks(object sender, RoutedEventArgs e) diff --git a/Firehose/UI/Controls/Filters.xaml b/Firehose/UI/Controls/Filters.xaml index c3596bc..6ed47f2 100644 --- a/Firehose/UI/Controls/Filters.xaml +++ b/Firehose/UI/Controls/Filters.xaml @@ -7,8 +7,6 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Background="Transparent" BorderBrush="Transparent" - MinHeight="45" MaxHeight="45" CornerRadius="4" - VerticalContentAlignment="Top" Padding="10,0" Margin="10,5" - Click="Clicked" FontSize="12"> - - + MinHeight="60" MaxHeight="60" CornerRadius="4" + VerticalContentAlignment="Bottom" Padding="10" Margin="10,0" + Click="Clicked" FontSize="20" Width="100" Content="{x:Bind DisplayName}"/> diff --git a/Firehose/UI/Controls/Filters.xaml.cs b/Firehose/UI/Controls/Filters.xaml.cs index f3a503e..08d7f1c 100644 --- a/Firehose/UI/Controls/Filters.xaml.cs +++ b/Firehose/UI/Controls/Filters.xaml.cs @@ -1,5 +1,6 @@ using CommunityToolkit.Mvvm.DependencyInjection; using FirehoseApp.Viewmodels; +using HYDRANT.Definitions; using Microsoft.UI; namespace FirehoseApp.UI.Controls; @@ -11,14 +12,6 @@ public sealed partial class Filters : AppBarButton /// Visible display name /// public string DisplayName { get; set; } - /// - /// MySQL filter that will be used to filter articles - /// - public string SQLFilter { get; set; } - /// - /// Order by - /// - public string FilterOrder { get; set; } /// /// UI button for filters @@ -26,14 +19,13 @@ public sealed partial class Filters : AppBarButton /// Visible name to the user /// MySQL filtering rule /// Order by, defaults to descending - public Filters(string name, string filter, string order = "ORDER BY PUBLISH_DATE DESC") + public Filters(Filter filter) { - DisplayName = name; - SQLFilter = filter; - FilterOrder = order; + DisplayName = filter.Name; + //FilterOrder = order; this.InitializeComponent(); } - + private void Clicked(object sender, RoutedEventArgs e) { ShellVM ShellVM = Ioc.Default.GetRequiredService(); @@ -49,16 +41,15 @@ private void Clicked(object sender, RoutedEventArgs e) Foreground = Themer.SecondaryBrush; Filters f = ShellVM.Filters.First(f => f.DisplayName == DisplayName); - if (ShellVM.FilterBy == f.SQLFilter) + if (ShellVM.CurrentFilter == f.DisplayName) { //If the same button has been clicked on load older articles ShellVM.Offset += Glob.Model.ArticleFetchLimit; } else { - ShellVM.FilterBy = f.SQLFilter; - ShellVM.FilterOrder = f.FilterOrder; ShellVM.Offset = 0; + ShellVM.CurrentFilter = DisplayName; } ShellVM.UpdateButtonsDelegate.Invoke(this); diff --git a/Firehose/UI/Dialogs/LoginFlow.xaml b/Firehose/UI/Dialogs/LoginFlow.xaml new file mode 100644 index 0000000..fe2a55d --- /dev/null +++ b/Firehose/UI/Dialogs/LoginFlow.xaml @@ -0,0 +1,20 @@ + + + + + + + public string Endpoint; - ///// - ///// Set to your API Key. - ///// - //public string API_KEY; - - /// - /// Gets Articles from API - /// - /// How many articles you want to set - /// Skip this amount of articles - /// Filter articles via SQL - /// Minimal Mode - /// List of articles if successful. - public async Task?> GetArticles(int Limit = 20, int Offset = 0, - string Filter = "ORDER BY PUBLISH_DATE DESC", bool Minimal=true) + ///// + ///// Set to your API Key. + ///// + //public string API_KEY; + + /// + /// Gets Articles from API + /// + /// How many articles you want to set + /// Skip this amount of articles + /// Used to access certain content + /// Name of the filter + /// Minimal Mode (less data is returned) + /// Only return filters from these publisher IDs + /// List of articles if successful. + public async Task?> GetArticles(int limit = 20, int Offset = 0, + string? token = null, string FilterName = "Latest", bool Minimal = false, + int PublisherID = -1) { - var endpoint = $"/Articles/GetArticles?limit={Limit}&offset={Offset}&filter={Filter}&minimal={Minimal}"; + var endpoint = $"/Articles/GetArticles?limit={limit}&offset={Offset}&" + + $"FilterName={FilterName}&minimal={Minimal}" + + $"&token={token}&publisherid={PublisherID}"; using HttpClient client = new(); - //client.DefaultRequestHeaders.Add("ApiKey", API_KEY); //Make request and check it was successful var response = await client.GetAsync(Endpoint + endpoint); @@ -106,4 +110,31 @@ public async Task GetArticleText(string URL) HttpResponseMessage Response = await client.GetAsync(url); return await Response.Content.ReadAsStringAsync(); } + + /// + /// Gets all filters available for a user + /// + /// + /// + public async Task> GetFilters(string token = null) + { + string url = Endpoint + "/Filter/GetFilters"; + using (HttpClient client = new HttpClient()) + { + var requestUrl = string.IsNullOrEmpty(token) ? url : $"{url}?Token={token}"; + + HttpResponseMessage response = await client.GetAsync(requestUrl); + + if (response.IsSuccessStatusCode) + { + + string content = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize>(content)!; + } + else + { + return null; + } + } + } } diff --git a/Hydrant/Definitions/Account.cs b/Hydrant/Definitions/Account.cs new file mode 100644 index 0000000..e662438 --- /dev/null +++ b/Hydrant/Definitions/Account.cs @@ -0,0 +1,31 @@ + +namespace HYDRANT.Definitions; +internal class Account +{ + /// + /// Account name + /// + public string Email { get; set; } + + /// + /// Account token + /// + public string Token { get; set; } + + /// + /// Internal account ID + /// + public Guid UserID { get; set; } + + /// + /// Should the user see FHN+ features + /// (Won't be accessible if the user doesn't have a FHN+ account) + /// + public bool FHNPlus; + + /// + /// Are they a part of the test group? + /// (Won't be accessible if the user isn't in the test group) + /// + public bool TestGroup; +} diff --git a/Hydrant/Definitions/Filter.cs b/Hydrant/Definitions/Filter.cs new file mode 100644 index 0000000..c63b028 --- /dev/null +++ b/Hydrant/Definitions/Filter.cs @@ -0,0 +1,33 @@ +using HYDRANT.Serializers; +using System.Text.Json; + +namespace HYDRANT.Definitions; +public class Filter +{ + + /// + /// Name of the filter + /// + public string Name { get; set; } + + /// + /// SQL that will be executed for filter + /// + public string SQL { get; set; } + + /// + /// Only returned to TestGroup users + /// + public bool Experimental { get; set; } + + /// + /// Shown to nobody + /// + public bool Hidden; + + public static List LoadFilters(string Path) + { + string content = File.ReadAllText(Path); + return JsonSerializer.Deserialize(content, FilterSerializer.Default.ListFilter); + } +} diff --git a/Hydrant/SQL.cs b/Hydrant/SQL.cs index 4458249..6f2ba52 100644 --- a/Hydrant/SQL.cs +++ b/Hydrant/SQL.cs @@ -1,7 +1,8 @@ using HYDRANT.Definitions; using MySqlConnector; + //The voice someone calls (in the labyrinth) -namespace Hydrant; +namespace HYDRANT; /// /// Access API via MySQL @@ -68,7 +69,7 @@ public SQL(string connectionString) /// list of articles. public List
GetArticles(int Limit = 0, int Offset = 0, string Filter = "ORDER BY PUBLISH_DATE DESC", bool Minimal = true, - bool AllowUserSubmittedArticles = false) + bool AllowUserSubmittedArticles = false, int PublisherID = -1) { string query; //Select queries, minimal mode is much quicker for large queries. @@ -83,15 +84,31 @@ public List
GetArticles(int Limit = 0, int Offset = 0, PAYWALL, SUMMARY, ImageURL,ARTICLE_TEXT, PUBLISHER_ID, BUSINESS_RELATED, COMPANIES_MENTIONED, AUTHOR, SECTORS"; } - - if (string.IsNullOrWhiteSpace(Filter) || Filter.Split(" ")[0] == "ORDER") - { Filter = "WHERE UserGeneratedArticle = 0 " + Filter; } - else if (Filter.Contains("ORDER")) + + //Disable UGC + if (!AllowUserSubmittedArticles) { - Filter = Filter.Replace("ORDER", "AND UserGeneratedArticle = 0 ORDER"); + if (string.IsNullOrWhiteSpace(Filter) || Filter.Split(" ")[0] == "ORDER") + { Filter = "WHERE UserGeneratedArticle = 0 " + Filter; } + else if (Filter.Contains("ORDER")) + { + Filter = Filter.Replace("ORDER", "AND UserGeneratedArticle = 0 ORDER"); + } + else { Filter += " AND UserGeneratedArticle = 0"; } } - else { Filter += " AND UserGeneratedArticle = 0"; } + //Filter by publisher if enabled + if (PublisherID != -1) + { + if (string.IsNullOrWhiteSpace(Filter) || Filter.Split(" ")[0] == "ORDER") + { Filter = "WHERE PUBLISHER_ID = 0 " + Filter; } + else if (Filter.Contains("ORDER")) + { + Filter = Filter.Replace("ORDER", "AND UserGeneratedArticle = 0 ORDER"); + } + else { Filter += " AND UserGeneratedArticle = 0"; } + } + //Add common filtering stuff query += $" FROM ARTICLES {Filter} LIMIT {Limit} OFFSET {Offset};"; diff --git a/Hydrant/Serializers/FilterSerializer.cs b/Hydrant/Serializers/FilterSerializer.cs new file mode 100644 index 0000000..967f783 --- /dev/null +++ b/Hydrant/Serializers/FilterSerializer.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; +using HYDRANT.Definitions; + +namespace HYDRANT.Serializers; +/// +/// This is required for AOT +/// +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(List))] +public partial class FilterSerializer : JsonSerializerContext +{ + +} diff --git a/global.json b/global.json index 7711a32..3eae67c 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { // To update the version of Uno please update the version of the Uno.Sdk here. See https://aka.platform.uno/upgrade-uno-packages for more information. "msbuild-sdks": { - "Uno.Sdk": "5.2.161" + "Uno.Sdk": "5.3.90" } }