UI rework and fixes

This commit is contained in:
2023-04-12 17:13:43 -04:00
parent 242663c3e5
commit 68aec56824
12 changed files with 207 additions and 175 deletions

View File

@@ -88,17 +88,10 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Button Content="{x:Static my:Resources.OkayButton}" <Button Content="{x:Static my:Resources.CloseButton}"
Height="23"
IsDefault="True"
Width="75"
Grid.Column="1"
Click="HandleOkayButtonClick" />
<Button Content="{x:Static my:Resources.CancelButton}"
Height="23" Height="23"
IsCancel="True" IsCancel="True"
Width="75" Width="75"
Margin="6,0,0,0"
Grid.Column="2" /> Grid.Column="2" />
</Grid> </Grid>
</Grid> </Grid>

View File

@@ -1,30 +1,29 @@
using ChrisKaczor.InstalledBrowsers; using System.ComponentModel;
using FeedCenter.Data; using System.Linq;
using FeedCenter.Options; using System.Threading.Tasks;
using FeedCenter.Properties;
using System.ComponentModel;
using System.Windows; using System.Windows;
using System.Windows.Data; using System.Windows.Data;
using System.Windows.Input; using System.Windows.Input;
using ChrisKaczor.InstalledBrowsers;
using FeedCenter.Data;
using FeedCenter.Options;
using FeedCenter.Properties;
namespace FeedCenter namespace FeedCenter
{ {
public partial class FeedErrorWindow public partial class FeedErrorWindow
{ {
private CollectionViewSource _collectionViewSource;
public FeedErrorWindow() public FeedErrorWindow()
{ {
InitializeComponent(); InitializeComponent();
} }
private FeedCenterEntities _database; public void Display(Window owner)
private CollectionViewSource _collectionViewSource;
public bool? Display(Window owner)
{ {
_database = Database.Entities;
// Create a view and sort it by name // Create a view and sort it by name
_collectionViewSource = new CollectionViewSource { Source = _database.Feeds }; _collectionViewSource = new CollectionViewSource { Source = Database.Entities.Feeds };
_collectionViewSource.Filter += HandleCollectionViewSourceFilter; _collectionViewSource.Filter += HandleCollectionViewSourceFilter;
_collectionViewSource.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending)); _collectionViewSource.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
@@ -36,10 +35,10 @@ namespace FeedCenter
Owner = owner; Owner = owner;
// Show the dialog and result the result // Show the dialog and result the result
return ShowDialog(); ShowDialog();
} }
private void HandleCollectionViewSourceFilter(object sender, FilterEventArgs e) private static void HandleCollectionViewSourceFilter(object sender, FilterEventArgs e)
{ {
var feed = (Feed) e.Item; var feed = (Feed) e.Item;
@@ -70,9 +69,12 @@ namespace FeedCenter
private void DeleteSelectedFeed() private void DeleteSelectedFeed()
{ {
if (MessageBox.Show(this, Properties.Resources.ConfirmDeleteFeed, Properties.Resources.ConfirmDeleteTitle, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.No)
return;
var feed = (Feed) FeedDataGrid.SelectedItem; var feed = (Feed) FeedDataGrid.SelectedItem;
_database.Feeds.Remove(feed); Database.Entities.SaveChanges(() => Database.Entities.Feeds.Remove(feed));
SetFeedButtonStates(); SetFeedButtonStates();
} }
@@ -98,24 +100,23 @@ namespace FeedCenter
InstalledBrowser.OpenLink(Settings.Default.Browser, feed.Source); InstalledBrowser.OpenLink(Settings.Default.Browser, feed.Source);
} }
private void HandleOkayButtonClick(object sender, RoutedEventArgs e) private async void HandleRefreshCurrentButtonClick(object sender, RoutedEventArgs e)
{
// Save the actual settings
_database.SaveChanges(() => { });
DialogResult = true;
Close();
}
private void HandleRefreshCurrentButtonClick(object sender, RoutedEventArgs e)
{ {
IsEnabled = false; IsEnabled = false;
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
var feed = (Feed) FeedDataGrid.SelectedItem; var feedId = ((Feed) FeedDataGrid.SelectedItem).Id;
_database.SaveChanges(() => feed.Read(true)); await Task.Run(() =>
{
var entities = new FeedCenterEntities();
var feed = entities.Feeds.First(f => f.Id == feedId);
entities.SaveChanges(() => feed.Read(true));
});
Database.Entities.Refresh();
var selectedIndex = FeedDataGrid.SelectedIndex; var selectedIndex = FeedDataGrid.SelectedIndex;

View File

@@ -0,0 +1,8 @@
namespace FeedCenter.FeedParsers
{
internal enum FeedParseError
{
Unknown = 0,
InvalidXml = 1
}
}

View File

@@ -0,0 +1,14 @@
using System;
namespace FeedCenter.FeedParsers
{
internal class FeedParseException : ApplicationException
{
public FeedParseException(FeedParseError feedParseError)
{
ParseError = feedParseError;
}
public FeedParseError ParseError { get; set; }
}
}

View File

@@ -142,11 +142,17 @@ namespace FeedCenter.FeedParsers
// No clue! // No clue!
return FeedType.Unknown; return FeedType.Unknown;
} }
catch (XmlException xmlException)
{
Log.Logger.Error(xmlException, "Exception: {0}", feedText);
throw new FeedParseException(FeedParseError.InvalidXml);
}
catch (Exception exception) catch (Exception exception)
{ {
Log.Logger.Error(exception, "Exception: {0}", feedText); Log.Logger.Error(exception, "Exception: {0}", feedText);
return FeedType.Unknown; throw new FeedParseException(FeedParseError.InvalidXml);
} }
} }

View File

@@ -1,12 +1,4 @@
using ChrisKaczor.ApplicationUpdate; using System;
using FeedCenter.Data;
using FeedCenter.FeedParsers;
using FeedCenter.Properties;
using FeedCenter.Xml;
using JetBrains.Annotations;
using Realms;
using Serilog;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
@@ -14,9 +6,17 @@ using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Net.Sockets;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Resources = FeedCenter.Properties.Resources; using ChrisKaczor.ApplicationUpdate;
using FeedCenter.Data;
using FeedCenter.FeedParsers;
using FeedCenter.Properties;
using FeedCenter.Xml;
using JetBrains.Annotations;
using Realms;
using Serilog;
namespace FeedCenter namespace FeedCenter
{ {
@@ -49,30 +49,30 @@ namespace FeedCenter
NotFound, NotFound,
Timeout, Timeout,
ConnectionFailed, ConnectionFailed,
ServerError ServerError,
Moved
} }
#endregion #endregion
public partial class Feed : RealmObject public partial class Feed : RealmObject
{ {
private static HttpClient _httpClient;
public bool Authenticate { get; set; }
public Guid CategoryId { get; set; }
public int CheckInterval { get; set; } = 60;
public string Description { get; set; }
public bool Enabled { get; set; } = true;
[PrimaryKey] [PrimaryKey]
[MapTo("ID")]
public Guid Id { get; set; } public Guid Id { get; set; }
public string Name { get; set; } [UsedImplicitly]
public string Title { get; set; } public IList<FeedItem> Items { get; }
public string Source { get; set; }
public string Link { get; set; }
public string Description { get; set; }
public DateTimeOffset LastChecked { get; set; }
public int CheckInterval { get; set; } = 60;
public bool Enabled { get; set; } = true;
public bool Authenticate { get; set; }
public string Username { get; set; }
public string Password { get; set; }
private string LastReadResultRaw { get; set; } public DateTimeOffset LastChecked { get; set; }
public FeedReadResult LastReadResult public FeedReadResult LastReadResult
{ {
@@ -80,22 +80,6 @@ namespace FeedCenter
set => LastReadResultRaw = value.ToString(); set => LastReadResultRaw = value.ToString();
} }
public DateTimeOffset LastUpdated { get; set; }
[MapTo("CategoryID")]
public Guid CategoryId { get; set; }
private string MultipleOpenActionRaw { get; set; }
public MultipleOpenAction MultipleOpenAction
{
get => Enum.TryParse(MultipleOpenActionRaw, out MultipleOpenAction result) ? result : MultipleOpenAction.IndividualPages;
set => MultipleOpenActionRaw = value.ToString();
}
[UsedImplicitly]
public IList<FeedItem> Items { get; }
// ReSharper disable once UnusedMember.Global // ReSharper disable once UnusedMember.Global
public string LastReadResultDescription public string LastReadResultDescription
{ {
@@ -115,7 +99,24 @@ namespace FeedCenter
} }
} }
private static HttpClient _httpClient; private string LastReadResultRaw { get; set; }
public DateTimeOffset LastUpdated { get; set; }
public string Link { get; set; }
public MultipleOpenAction MultipleOpenAction
{
get => Enum.TryParse(MultipleOpenActionRaw, out MultipleOpenAction result) ? result : MultipleOpenAction.IndividualPages;
set => MultipleOpenActionRaw = value.ToString();
}
private string MultipleOpenActionRaw { get; set; }
public string Name { get; set; }
public string Password { get; set; }
public string Source { get; set; }
public string Title { get; set; }
public string Username { get; set; }
public static Feed Create() public static Feed Create()
{ {
@@ -175,11 +176,14 @@ namespace FeedCenter
// Create and configure the HTTP client if needed // Create and configure the HTTP client if needed
if (_httpClient == null) if (_httpClient == null)
{ {
_httpClient = new HttpClient(new HttpClientHandler var clientHandler = new HttpClientHandler
{ {
// Set that we'll accept compressed data // Set that we'll accept compressed data
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
}); AllowAutoRedirect = true
};
_httpClient = new HttpClient(clientHandler);
// Set a user agent string // Set a user agent string
var userAgent = string.IsNullOrWhiteSpace(Settings.Default.DefaultUserAgent) ? "FeedCenter/" + UpdateCheck.LocalVersion : Settings.Default.DefaultUserAgent; var userAgent = string.IsNullOrWhiteSpace(Settings.Default.DefaultUserAgent) ? "FeedCenter/" + UpdateCheck.LocalVersion : Settings.Default.DefaultUserAgent;
@@ -222,33 +226,52 @@ namespace FeedCenter
return Tuple.Create(FeedReadResult.ConnectionFailed, string.Empty); return Tuple.Create(FeedReadResult.ConnectionFailed, string.Empty);
} }
catch (AggregateException aggregateException)
{
Log.Logger.Error(aggregateException, "Exception");
var innerException = aggregateException.InnerException;
if (innerException is not HttpRequestException httpRequestException)
return Tuple.Create(FeedReadResult.UnknownError, string.Empty);
switch (httpRequestException.StatusCode)
{
case HttpStatusCode.InternalServerError:
return Tuple.Create(FeedReadResult.ServerError, string.Empty);
case HttpStatusCode.NotModified:
return Tuple.Create(FeedReadResult.NotModified, string.Empty);
case HttpStatusCode.NotFound:
return Tuple.Create(FeedReadResult.NotFound, string.Empty);
case HttpStatusCode.Unauthorized:
case HttpStatusCode.Forbidden:
return Tuple.Create(FeedReadResult.Unauthorized, string.Empty);
case HttpStatusCode.Moved:
return Tuple.Create(FeedReadResult.Moved, string.Empty);
}
if (httpRequestException.InnerException is not SocketException socketException)
return Tuple.Create(FeedReadResult.UnknownError, string.Empty);
switch (socketException.SocketErrorCode)
{
case SocketError.NoData:
return Tuple.Create(FeedReadResult.NoResponse, string.Empty);
case SocketError.HostNotFound:
return Tuple.Create(FeedReadResult.NotFound, string.Empty);
}
return Tuple.Create(FeedReadResult.UnknownError, string.Empty);
}
catch (WebException webException) catch (WebException webException)
{ {
var result = FeedReadResult.UnknownError; var result = FeedReadResult.UnknownError;
if (webException.Response is HttpWebResponse errorResponse)
{
switch (errorResponse.StatusCode)
{
case HttpStatusCode.InternalServerError:
return Tuple.Create(FeedReadResult.ServerError, string.Empty);
case HttpStatusCode.NotModified:
return Tuple.Create(FeedReadResult.NotModified, string.Empty);
case HttpStatusCode.NotFound:
return Tuple.Create(FeedReadResult.NotFound, string.Empty);
case HttpStatusCode.Unauthorized:
case HttpStatusCode.Forbidden:
return Tuple.Create(FeedReadResult.Unauthorized, string.Empty);
}
}
switch (webException.Status) switch (webException.Status)
{ {
case WebExceptionStatus.ConnectFailure: case WebExceptionStatus.ConnectFailure:
@@ -337,6 +360,12 @@ namespace FeedCenter
return FeedReadResult.Success; return FeedReadResult.Success;
} }
catch (FeedParseException feedParseException)
{
Log.Logger.Error(feedParseException, "Exception");
return FeedReadResult.InvalidXml;
}
catch (InvalidFeedFormatException exception) catch (InvalidFeedFormatException exception)
{ {
Log.Logger.Error(exception, "Exception"); Log.Logger.Error(exception, "Exception");

View File

@@ -1,30 +1,30 @@
using FeedCenter.Options; using System;
using Realms;
using System;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using FeedCenter.Options;
using Realms;
namespace FeedCenter namespace FeedCenter
{ {
public partial class FeedItem : RealmObject public partial class FeedItem : RealmObject
{ {
[PrimaryKey]
[MapTo("ID")]
public Guid Id { get; set; }
[MapTo("FeedID")]
public Guid FeedId { get; set; }
public string Title { get; set; }
public string Link { get; set; }
public string Description { get; set; }
public bool BeenRead { get; set; } public bool BeenRead { get; set; }
public DateTimeOffset LastFound { get; set; } public string Description { get; set; }
public bool New { get; set; }
public string Guid { get; set; }
public int Sequence { get; set; }
public Feed Feed { get; set; } public Feed Feed { get; set; }
public Guid FeedId { get; set; }
public string Guid { get; set; }
[PrimaryKey]
public Guid Id { get; set; }
public DateTimeOffset LastFound { get; set; }
public string Link { get; set; }
public bool New { get; set; }
public int Sequence { get; set; }
public string Title { get; set; }
public static FeedItem Create() public static FeedItem Create()
{ {
return new FeedItem { Id = System.Guid.NewGuid() }; return new FeedItem { Id = System.Guid.NewGuid() };
@@ -59,6 +59,8 @@ namespace FeedCenter
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
title ??= string.Empty;
// Condense multiple spaces to one space // Condense multiple spaces to one space
title = MultipleSpaceRegex().Replace(title, " "); title = MultipleSpaceRegex().Replace(title, " ");

View File

@@ -146,7 +146,7 @@ namespace FeedCenter
private void ResetDatabase() private void ResetDatabase()
{ {
// Get the ID of the current feed // Get the ID of the current feed
var currentId = _currentFeed?.Id ?? Guid.Empty; var currentId = _currentFeed?.IsValid ?? false ? _currentFeed.Id : Guid.Empty;
// Create a new database object // Create a new database object
_database.Refresh(); _database.Refresh();

View File

@@ -1,12 +1,12 @@
using ChrisKaczor.InstalledBrowsers; using System.IO;
using FeedCenter.Options;
using FeedCenter.Properties;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Web.UI; using System.Web.UI;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using ChrisKaczor.InstalledBrowsers;
using FeedCenter.Options;
using FeedCenter.Properties;
namespace FeedCenter namespace FeedCenter
{ {
@@ -59,18 +59,16 @@ namespace FeedCenter
// Create the options form // Create the options form
var optionsWindow = new OptionsWindow { Owner = this }; var optionsWindow = new OptionsWindow { Owner = this };
// Show the options form and get the result // Show the options window
var result = optionsWindow.ShowDialog(); optionsWindow.ShowDialog();
// If okay was selected // Refresh the database to current settings
if (result.HasValue && result.Value) ResetDatabase();
{
// Refresh the database to current settings
ResetDatabase();
// Re-initialize the feed display // Re-initialize the feed display
DisplayFeed(); DisplayFeed();
}
UpdateErrorLink();
} }
private void HandleMarkReadToolbarButtonClick(object sender, RoutedEventArgs e) private void HandleMarkReadToolbarButtonClick(object sender, RoutedEventArgs e)
@@ -84,19 +82,15 @@ namespace FeedCenter
var feedErrorWindow = new FeedErrorWindow(); var feedErrorWindow = new FeedErrorWindow();
// Display the window // Display the window
var result = feedErrorWindow.Display(this); feedErrorWindow.Display(this);
// If okay was selected // Refresh the database to current settings
if (result.GetValueOrDefault()) ResetDatabase();
{
// Refresh the database to current settings
ResetDatabase();
// Re-initialize the feed display // Re-initialize the feed display
DisplayFeed(); DisplayFeed();
UpdateErrorLink(); UpdateErrorLink();
}
} }
private void HandleRefreshMenuItemClick(object sender, RoutedEventArgs e) private void HandleRefreshMenuItemClick(object sender, RoutedEventArgs e)

View File

@@ -6,7 +6,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d:DataContext="{d:DesignInstance Type=feedCenter:Feed}" d:DataContext="{d:DesignInstance Type=feedCenter:Feed}"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:validation="clr-namespace:ChrisKaczor.Wpf.Validation;assembly=ChrisKaczor.Wpf.Validation"
mc:Ignorable="d" mc:Ignorable="d"
Title="FeedWindow" Title="FeedWindow"
Height="300" Height="300"
@@ -38,15 +37,8 @@
<TextBox Name="UrlTextBox" <TextBox Name="UrlTextBox"
Grid.Row="0" Grid.Row="0"
Grid.Column="1" Grid.Column="1"
Margin="6"> Margin="6"
<TextBox.Text> Text="{Binding Path=Source, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
<Binding Path="Source" UpdateSourceTrigger="Explicit" ValidatesOnExceptions="True">
<Binding.ValidationRules>
<validation:RequiredValidationRule></validation:RequiredValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Label Content="{x:Static properties:Resources.feedNameLabel}" <Label Content="{x:Static properties:Resources.feedNameLabel}"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
Target="{Binding ElementName=NameTextBox}" Target="{Binding ElementName=NameTextBox}"
@@ -57,15 +49,8 @@
<TextBox Name="NameTextBox" <TextBox Name="NameTextBox"
Grid.Column="1" Grid.Column="1"
Grid.Row="1" Grid.Row="1"
Margin="6"> Margin="6"
<TextBox.Text> Text="{Binding Path=Name, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
<Binding Path="Name" UpdateSourceTrigger="Explicit" ValidatesOnExceptions="True">
<Binding.ValidationRules>
<validation:RequiredValidationRule></validation:RequiredValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Label Content="{x:Static properties:Resources.feedCategoryLabel}" <Label Content="{x:Static properties:Resources.feedCategoryLabel}"
Target="{Binding ElementName=CategoryComboBox}" Target="{Binding ElementName=CategoryComboBox}"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
@@ -165,15 +150,8 @@
Grid.Column="1" Grid.Column="1"
IsEnabled="{Binding ElementName=RequiresAuthenticationCheckBox, Path=IsChecked}" IsEnabled="{Binding ElementName=RequiresAuthenticationCheckBox, Path=IsChecked}"
Grid.Row="1" Grid.Row="1"
Margin="6"> Margin="6"
<TextBox.Text> Text="{Binding Path=Username, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
<Binding Path="Username" UpdateSourceTrigger="Explicit" ValidatesOnExceptions="True">
<Binding.ValidationRules>
<validation:RequiredValidationRule></validation:RequiredValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Label Content="{x:Static properties:Resources.authenticationPasswordLabel}" <Label Content="{x:Static properties:Resources.authenticationPasswordLabel}"
Target="{Binding ElementName=AuthenticationPasswordTextBox}" Target="{Binding ElementName=AuthenticationPasswordTextBox}"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"

View File

@@ -35,6 +35,8 @@ namespace FeedCenter.Options
private void HandleOkayButtonClick(object sender, RoutedEventArgs e) private void HandleOkayButtonClick(object sender, RoutedEventArgs e)
{ {
var transaction = Database.Entities.BeginTransaction();
var feed = (Feed) DataContext; var feed = (Feed) DataContext;
// Get a list of all framework elements and explicit binding expressions // Get a list of all framework elements and explicit binding expressions
@@ -73,12 +75,17 @@ namespace FeedCenter.Options
// Set focus // Set focus
firstErrorElement.Focus(); firstErrorElement.Focus();
transaction.Rollback();
return; return;
} }
if (RequiresAuthenticationCheckBox.IsChecked.GetValueOrDefault(false)) if (RequiresAuthenticationCheckBox.IsChecked.GetValueOrDefault(false))
feed.Password = AuthenticationPasswordTextBox.Password; feed.Password = AuthenticationPasswordTextBox.Password;
transaction.Commit();
Database.Entities.Refresh();
// Dialog is good // Dialog is good
DialogResult = true; DialogResult = true;

View File

@@ -57,7 +57,7 @@ namespace FeedCenter.Options
if (!result.HasValue || !result.Value) if (!result.HasValue || !result.Value)
return; return;
Database.Entities.Feeds.Add(feed); Database.Entities.SaveChanges(() => Database.Entities.Feeds.Add(feed));
FeedListBox.SelectedItem = feed; FeedListBox.SelectedItem = feed;