From 6fc4e6c0d2c5cafde4996809797af36f8580e013 Mon Sep 17 00:00:00 2001 From: Roberto <107645954+intcooper@users.noreply.github.com> Date: Wed, 15 May 2024 22:55:44 +0200 Subject: [PATCH 1/4] Code redesigned to adhere to the MVVM design pattern. --- Docs/Images/MMExNotifier-MainWindow.png | Bin 0 -> 18642 bytes MMExNotifier.Tests/MMExNotifier.Tests.csproj | 24 +++++ MMExNotifier.Tests/MainViewModelTests.cs | 16 ++++ MMExNotifier/App.xaml.cs | 45 ++-------- .../{Entities => DataModel}/Account.cs | 2 +- MMExNotifier/DataModel/AppConfiguration.cs | 71 +++++++++++++++ .../{Entities => DataModel}/BillDeposit.cs | 2 +- .../{Entities => DataModel}/Category.cs | 4 +- .../{Entities => DataModel}/ExpiringBill.cs | 2 +- MMExNotifier/DataModel/IAppConfiguration.cs | 11 +++ MMExNotifier/{Entities => DataModel}/Payee.cs | 4 +- .../{Entities => DataModel}/Transaction.cs | 2 +- MMExNotifier/{ => Database}/Connection.cs | 4 +- MMExNotifier/{ => Database}/DbHelper.cs | 4 +- MMExNotifier/Helpers/INotificationService.cs | 14 +++ MMExNotifier/Helpers/NotificationService.cs | 34 +++++++ .../{ => MVVM}/IsLessThanConverter.cs | 2 +- .../MVVM/RangeObservableCollection.cs | 29 ++++++ MMExNotifier/MVVM/RelayCommand.cs | 84 ++++++++++++++++++ MMExNotifier/MVVM/ViewModelBase.cs | 34 +++++++ MMExNotifier/ViewModels/MainViewModel.cs | 65 ++++++++++++++ MMExNotifier/{ => Views}/MainWindow.xaml | 22 +++-- MMExNotifier/{ => Views}/MainWindow.xaml.cs | 58 ++---------- 23 files changed, 427 insertions(+), 106 deletions(-) create mode 100644 Docs/Images/MMExNotifier-MainWindow.png create mode 100644 MMExNotifier.Tests/MMExNotifier.Tests.csproj create mode 100644 MMExNotifier.Tests/MainViewModelTests.cs rename MMExNotifier/{Entities => DataModel}/Account.cs (97%) create mode 100644 MMExNotifier/DataModel/AppConfiguration.cs rename MMExNotifier/{Entities => DataModel}/BillDeposit.cs (97%) rename MMExNotifier/{Entities => DataModel}/Category.cs (82%) rename MMExNotifier/{Entities => DataModel}/ExpiringBill.cs (93%) create mode 100644 MMExNotifier/DataModel/IAppConfiguration.cs rename MMExNotifier/{Entities => DataModel}/Payee.cs (83%) rename MMExNotifier/{Entities => DataModel}/Transaction.cs (97%) rename MMExNotifier/{ => Database}/Connection.cs (72%) rename MMExNotifier/{ => Database}/DbHelper.cs (96%) create mode 100644 MMExNotifier/Helpers/INotificationService.cs create mode 100644 MMExNotifier/Helpers/NotificationService.cs rename MMExNotifier/{ => MVVM}/IsLessThanConverter.cs (95%) create mode 100644 MMExNotifier/MVVM/RangeObservableCollection.cs create mode 100644 MMExNotifier/MVVM/RelayCommand.cs create mode 100644 MMExNotifier/MVVM/ViewModelBase.cs create mode 100644 MMExNotifier/ViewModels/MainViewModel.cs rename MMExNotifier/{ => Views}/MainWindow.xaml (90%) rename MMExNotifier/{ => Views}/MainWindow.xaml.cs (64%) diff --git a/Docs/Images/MMExNotifier-MainWindow.png b/Docs/Images/MMExNotifier-MainWindow.png new file mode 100644 index 0000000000000000000000000000000000000000..476216194655e88c0797993c0ca780b1f34d2c77 GIT binary patch literal 18642 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 + + + net6.0 + enable + enable + + false + true + + + + + + + + + + + + + + + diff --git a/MMExNotifier.Tests/MainViewModelTests.cs b/MMExNotifier.Tests/MainViewModelTests.cs new file mode 100644 index 0000000..14c3fd6 --- /dev/null +++ b/MMExNotifier.Tests/MainViewModelTests.cs @@ -0,0 +1,16 @@ +namespace MMExNotifier.Tests +{ + public class MainViewModelTests + { + [SetUp] + public void Setup() + { + } + + [Test] + public void Test1() + { + Assert.Pass(); + } + } +} \ No newline at end of file diff --git a/MMExNotifier/App.xaml.cs b/MMExNotifier/App.xaml.cs index 8bb51a3..890d8d2 100644 --- a/MMExNotifier/App.xaml.cs +++ b/MMExNotifier/App.xaml.cs @@ -1,4 +1,8 @@ using Microsoft.Toolkit.Uwp.Notifications; +using MMExNotifier.Database; +using MMExNotifier.DataModel; +using MMExNotifier.Helpers; +using MMExNotifier.ViewModels; using System.Linq; using System.Windows; using Windows.Foundation.Collections; @@ -14,43 +18,12 @@ protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); - var dbPath = MMExNotifier.Properties.Settings.Default.MMExDatabasePath; + var view = new MainWindow(); + var viewModel = new MainViewModel(new AppConfiguration(), new NotificationService()); - if (string.IsNullOrEmpty(dbPath)) - { - var mainWindow = new MainWindow(); - return; - } - - var daysAhead = MMExNotifier.Properties.Settings.Default.DaysAhead; - var expiringTransactions = DbHelper.LoadRecurringTransactions(dbPath, daysAhead); - - if ((expiringTransactions != null) && (!expiringTransactions.Any())) - { - App.Current.Shutdown(0); - } - else - { - new ToastContentBuilder() - .AddArgument("action", "viewTransactions") - .AddArgument("conversationId", 9813) - .AddText($"MMExNotifier", AdaptiveTextStyle.Header) - .AddText($"One ore more recurring transaction are about to expire.") - .SetToastScenario(ToastScenario.Reminder) - .Show(); - - // Listen to notification activation - ToastNotificationManagerCompat.OnActivated += toastArgs => - { - ToastArguments args = ToastArguments.Parse(toastArgs.Argument); - ValueSet userInput = toastArgs.UserInput; - Application.Current.Dispatcher.Invoke(delegate - { - var mainWindow = new MainWindow(); - mainWindow.ShowDialog(); - }); - }; - } + view.DataContext = viewModel; + viewModel.OnClose += (s, e) => view.Close(); + viewModel.OnOpen += (s, e) => view.ShowDialog(); } } } diff --git a/MMExNotifier/Entities/Account.cs b/MMExNotifier/DataModel/Account.cs similarity index 97% rename from MMExNotifier/Entities/Account.cs rename to MMExNotifier/DataModel/Account.cs index 52fef71..59ff585 100644 --- a/MMExNotifier/Entities/Account.cs +++ b/MMExNotifier/DataModel/Account.cs @@ -1,7 +1,7 @@ using LinqToDB.Mapping; using System; -namespace MMExNotifier.Entities +namespace MMExNotifier.DataModel { [Table(Name = "ACCOUNTLIST_V1")] internal class Account diff --git a/MMExNotifier/DataModel/AppConfiguration.cs b/MMExNotifier/DataModel/AppConfiguration.cs new file mode 100644 index 0000000..672175a --- /dev/null +++ b/MMExNotifier/DataModel/AppConfiguration.cs @@ -0,0 +1,71 @@ +using Microsoft.Win32.TaskScheduler; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace MMExNotifier.DataModel +{ + internal class AppConfiguration : IAppConfiguration + { + public string? MMExDatabasePath { get; set; } + public int DaysAhead { get; set; } + public bool RunAtLogon { get; set; } + + public AppConfiguration() + { + MMExDatabasePath = Properties.Settings.Default.MMExDatabasePath; + DaysAhead = Properties.Settings.Default.DaysAhead; + + using TaskService taskService = new(); + RunAtLogon = taskService.RootFolder.Tasks.Any(t => t.Name == "MMExNotifier"); + } + + public void Save() + { + Properties.Settings.Default.DaysAhead = DaysAhead; + Properties.Settings.Default.MMExDatabasePath = MMExDatabasePath; + + if (RunAtLogon) + { + EnableSchedulerTask(); + } + else + { + DisableSchedulerTask(); + } + + Properties.Settings.Default.Save(); + } + + private static void EnableSchedulerTask() + { + using TaskService taskService = new(); + TaskDefinition taskDefinition = taskService.NewTask(); + + // Set the task settings + taskDefinition.RegistrationInfo.Description = "MMExNotifier"; + var userId = WindowsIdentity.GetCurrent().Name; + + // Set the trigger to run on logon + LogonTrigger logonTrigger = new() { UserId = userId }; + taskDefinition.Triggers.Add(logonTrigger); + + // Set the action to run the executable that creates the task + string executablePath = Environment.ProcessPath ?? ""; + taskDefinition.Actions.Add(new ExecAction(executablePath)); + + // Register the task in the Windows Task Scheduler + taskService.RootFolder.RegisterTaskDefinition("MMExNotifier", taskDefinition); + } + + private static void DisableSchedulerTask() + { + using TaskService taskService = new(); + taskService.RootFolder.DeleteTask("MMExNotifier", false); + } + } +} diff --git a/MMExNotifier/Entities/BillDeposit.cs b/MMExNotifier/DataModel/BillDeposit.cs similarity index 97% rename from MMExNotifier/Entities/BillDeposit.cs rename to MMExNotifier/DataModel/BillDeposit.cs index 1042a30..962c961 100644 --- a/MMExNotifier/Entities/BillDeposit.cs +++ b/MMExNotifier/DataModel/BillDeposit.cs @@ -1,7 +1,7 @@ using LinqToDB.Mapping; using System; -namespace MMExNotifier.Entities +namespace MMExNotifier.DataModel { [Table(Name = "BILLSDEPOSITS_V1")] internal class BillDeposit diff --git a/MMExNotifier/Entities/Category.cs b/MMExNotifier/DataModel/Category.cs similarity index 82% rename from MMExNotifier/Entities/Category.cs rename to MMExNotifier/DataModel/Category.cs index f7bdbfc..a323a46 100644 --- a/MMExNotifier/Entities/Category.cs +++ b/MMExNotifier/DataModel/Category.cs @@ -1,8 +1,8 @@ using LinqToDB.Mapping; -namespace MMExNotifier.Entities +namespace MMExNotifier.DataModel { - [Table(Name="CATEGORY_V1")] + [Table(Name = "CATEGORY_V1")] internal class Category { [PrimaryKey] diff --git a/MMExNotifier/Entities/ExpiringBill.cs b/MMExNotifier/DataModel/ExpiringBill.cs similarity index 93% rename from MMExNotifier/Entities/ExpiringBill.cs rename to MMExNotifier/DataModel/ExpiringBill.cs index 5ea4944..8fbf123 100644 --- a/MMExNotifier/Entities/ExpiringBill.cs +++ b/MMExNotifier/DataModel/ExpiringBill.cs @@ -1,6 +1,6 @@ using System; -namespace MMExNotifier.Entities +namespace MMExNotifier.DataModel { public class ExpiringBill { diff --git a/MMExNotifier/DataModel/IAppConfiguration.cs b/MMExNotifier/DataModel/IAppConfiguration.cs new file mode 100644 index 0000000..11fffb6 --- /dev/null +++ b/MMExNotifier/DataModel/IAppConfiguration.cs @@ -0,0 +1,11 @@ +namespace MMExNotifier.DataModel +{ + internal interface IAppConfiguration + { + int DaysAhead { get; set; } + string? MMExDatabasePath { get; set; } + bool RunAtLogon { get; set; } + + void Save(); + } +} \ No newline at end of file diff --git a/MMExNotifier/Entities/Payee.cs b/MMExNotifier/DataModel/Payee.cs similarity index 83% rename from MMExNotifier/Entities/Payee.cs rename to MMExNotifier/DataModel/Payee.cs index a5a7aaf..b20ef1e 100644 --- a/MMExNotifier/Entities/Payee.cs +++ b/MMExNotifier/DataModel/Payee.cs @@ -1,8 +1,8 @@ using LinqToDB.Mapping; -namespace MMExNotifier.Entities +namespace MMExNotifier.DataModel { - [Table(Name="PAYEE_V1")] + [Table(Name = "PAYEE_V1")] internal class Payee { [PrimaryKey] diff --git a/MMExNotifier/Entities/Transaction.cs b/MMExNotifier/DataModel/Transaction.cs similarity index 97% rename from MMExNotifier/Entities/Transaction.cs rename to MMExNotifier/DataModel/Transaction.cs index 79632f2..378479b 100644 --- a/MMExNotifier/Entities/Transaction.cs +++ b/MMExNotifier/DataModel/Transaction.cs @@ -1,7 +1,7 @@ using LinqToDB.Mapping; using System; -namespace MMExNotifier.Entities +namespace MMExNotifier.DataModel { [Table(Name = "CHECKINGACCOUNT_V1")] internal class Transaction diff --git a/MMExNotifier/Connection.cs b/MMExNotifier/Database/Connection.cs similarity index 72% rename from MMExNotifier/Connection.cs rename to MMExNotifier/Database/Connection.cs index 51b194a..52920c0 100644 --- a/MMExNotifier/Connection.cs +++ b/MMExNotifier/Database/Connection.cs @@ -1,11 +1,11 @@ using LinqToDB.Data; -namespace MMExNotifier +namespace MMExNotifier.Database { internal class Connection : DataConnection { public Connection() - :base() + : base() { } diff --git a/MMExNotifier/DbHelper.cs b/MMExNotifier/Database/DbHelper.cs similarity index 96% rename from MMExNotifier/DbHelper.cs rename to MMExNotifier/Database/DbHelper.cs index 3979158..9b0156b 100644 --- a/MMExNotifier/DbHelper.cs +++ b/MMExNotifier/Database/DbHelper.cs @@ -1,10 +1,10 @@ using LinqToDB; -using MMExNotifier.Entities; +using MMExNotifier.DataModel; using System; using System.Collections.Generic; using System.Linq; -namespace MMExNotifier +namespace MMExNotifier.Database { internal static class DbHelper { diff --git a/MMExNotifier/Helpers/INotificationService.cs b/MMExNotifier/Helpers/INotificationService.cs new file mode 100644 index 0000000..aeb0cd4 --- /dev/null +++ b/MMExNotifier/Helpers/INotificationService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MMExNotifier.Helpers +{ + internal interface INotificationService + { + public void ShowToastNotification(string actionName, int conversationId, string headerText, string message, Action onToastClickAction); + public void ShowErrorNotification(string message); + } +} diff --git a/MMExNotifier/Helpers/NotificationService.cs b/MMExNotifier/Helpers/NotificationService.cs new file mode 100644 index 0000000..e6330c5 --- /dev/null +++ b/MMExNotifier/Helpers/NotificationService.cs @@ -0,0 +1,34 @@ +using Microsoft.Toolkit.Uwp.Notifications; +using System; +using System.Windows; +using Windows.Foundation.Collections; + +namespace MMExNotifier.Helpers +{ + internal class NotificationService : INotificationService + { + public void ShowErrorNotification(string message) + { + MessageBox.Show(message, "Error", MessageBoxButton.OK, MessageBoxImage.Error); + } + + public void ShowToastNotification(string actionName, int conversationId, string headerText, string message, Action onToastClickAction) + { + new ToastContentBuilder() + .AddArgument("action", "viewTransactions") + .AddArgument("conversationId", 9813) + .AddText($"MMExNotifier", AdaptiveTextStyle.Header) + .AddText($"One ore more recurring transaction are about to expire.") + .SetToastScenario(ToastScenario.Reminder) + .Show(); + + // Listen to notification activation + ToastNotificationManagerCompat.OnActivated += toastArgs => + { + ToastArguments args = ToastArguments.Parse(toastArgs.Argument); + ValueSet userInput = toastArgs.UserInput; + onToastClickAction.Invoke(); + }; + } + } +} diff --git a/MMExNotifier/IsLessThanConverter.cs b/MMExNotifier/MVVM/IsLessThanConverter.cs similarity index 95% rename from MMExNotifier/IsLessThanConverter.cs rename to MMExNotifier/MVVM/IsLessThanConverter.cs index d5d3fb9..e4c5e72 100644 --- a/MMExNotifier/IsLessThanConverter.cs +++ b/MMExNotifier/MVVM/IsLessThanConverter.cs @@ -2,7 +2,7 @@ using System.Globalization; using System.Windows.Data; -namespace MMExNotifier +namespace MMExNotifier.MVVM { internal class IsLessThanConverter : IValueConverter { diff --git a/MMExNotifier/MVVM/RangeObservableCollection.cs b/MMExNotifier/MVVM/RangeObservableCollection.cs new file mode 100644 index 0000000..2cbf9fd --- /dev/null +++ b/MMExNotifier/MVVM/RangeObservableCollection.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MMExNotifier.MVVM +{ + internal class RangeObservableCollection : ObservableCollection + { + public void AddRange(IEnumerable items) + { + using (BlockReentrancy()) + { + foreach (T item in items) + { + Items.Add(item); + } + + OnPropertyChanged(new PropertyChangedEventArgs("Count")); + OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + } + } +} diff --git a/MMExNotifier/MVVM/RelayCommand.cs b/MMExNotifier/MVVM/RelayCommand.cs new file mode 100644 index 0000000..658c07c --- /dev/null +++ b/MMExNotifier/MVVM/RelayCommand.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace MMExNotifier.MVVM +{ + internal class RelayCommand : ICommand + { + private readonly Action? _execute = null; + private readonly Func? _canExecute = null; + + public event EventHandler? CanExecuteChanged + { + add { CommandManager.RequerySuggested += value; } + remove { CommandManager.RequerySuggested -= value; } + } + + public RelayCommand(Action actionToExecute) + { + _execute = actionToExecute ?? throw new ArgumentNullException(nameof(actionToExecute)); + } + + public RelayCommand(Action actionToExecute, Func executionEvaluator) + : this(actionToExecute) + { + _canExecute = executionEvaluator ?? throw new ArgumentNullException(nameof(executionEvaluator)); + } + + public bool CanExecute(object? parameter) + { + return _canExecute == null ? true : _canExecute.Invoke(); + } + + public void Execute(object? parameter) + { + _execute?.Invoke(); + } + } + + internal class RelayCommand : ICommand + { + private readonly Action? _execute = null; + private readonly Predicate? _canExecute = null; + + public event EventHandler? CanExecuteChanged + { + add { CommandManager.RequerySuggested += value; } + remove { CommandManager.RequerySuggested -= value; } + } + + public RelayCommand(Action actionToExecute) + { + _execute = actionToExecute ?? throw new ArgumentNullException(nameof(actionToExecute)); + } + + public RelayCommand(Action actionToExecute, Predicate executionEvaluator) + : this(actionToExecute) + { + _canExecute = executionEvaluator ?? throw new ArgumentNullException(nameof(executionEvaluator)); + } + + public bool CanExecute(object? parameter) + { + if (_canExecute == null) + return true; + + if (parameter == null && typeof(T?).IsValueType) + return _canExecute.Invoke(default); + + if (parameter == null || parameter is T?) + return _canExecute.Invoke((T?)parameter); + + return false; + } + + public void Execute(object? parameter) + { + _execute?.Invoke((T?)parameter); + } + } +} diff --git a/MMExNotifier/MVVM/ViewModelBase.cs b/MMExNotifier/MVVM/ViewModelBase.cs new file mode 100644 index 0000000..32a1e84 --- /dev/null +++ b/MMExNotifier/MVVM/ViewModelBase.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using Windows.ApplicationModel.Resources.Core; + +namespace MMExNotifier.MVVM +{ + internal class ViewModelBase : INotifyPropertyChanged + { + public event EventHandler? OnClose; + public event EventHandler? OnOpen; + public event PropertyChangedEventHandler? PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected virtual void Close() + { + OnClose?.Invoke(this, EventArgs.Empty); + } + + protected virtual void Open() + { + OnOpen?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/MMExNotifier/ViewModels/MainViewModel.cs b/MMExNotifier/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..29949a7 --- /dev/null +++ b/MMExNotifier/ViewModels/MainViewModel.cs @@ -0,0 +1,65 @@ +using Microsoft.Toolkit.Uwp.Notifications; +using MMExNotifier.Database; +using MMExNotifier.DataModel; +using MMExNotifier.Helpers; +using MMExNotifier.MVVM; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using Windows.UI.ApplicationSettings; + +namespace MMExNotifier.ViewModels +{ + internal class MainViewModel : ViewModelBase + { + private readonly INotificationService _notificationService; + + public RangeObservableCollection ExpiringBills { get; set; } = new RangeObservableCollection(); + public IAppConfiguration AppSettings { get; private set; } + public RelayCommand SaveSettingsCommand; + + public MainViewModel(IAppConfiguration appSettings, INotificationService notificationService) + { + _notificationService = notificationService; + AppSettings = appSettings; + OnPropertyChanged(nameof(AppSettings)); + + SaveSettingsCommand = new(() => SaveSettings()); + + LoadRecurringTransactions(); + + if (ExpiringBills.Count == 0) + { + Close(); + } + + _notificationService.ShowToastNotification("viewTransactions", 9813, "MMExNotifier", "One ore more recurring transaction are about to expire.", () => Open()); + } + + private void LoadRecurringTransactions() + { + try + { + var expiringTransactions = DbHelper.LoadRecurringTransactions(AppSettings.MMExDatabasePath, AppSettings.DaysAhead); + ExpiringBills.AddRange(expiringTransactions); + } + catch (Exception) + { + _notificationService.ShowErrorNotification("An error has occurred while loading the recurring transactions.\nMake sure that the database version matches the supported version."); + } + } + + private void SaveSettings() + { + AppSettings.Save(); + + LoadRecurringTransactions(); + } + } +} + diff --git a/MMExNotifier/MainWindow.xaml b/MMExNotifier/Views/MainWindow.xaml similarity index 90% rename from MMExNotifier/MainWindow.xaml rename to MMExNotifier/Views/MainWindow.xaml index 9c001ba..378b40a 100644 --- a/MMExNotifier/MainWindow.xaml +++ b/MMExNotifier/Views/MainWindow.xaml @@ -4,6 +4,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:MMExNotifier" + xmlns:mvvm="clr-namespace:MMExNotifier.MVVM" mc:Ignorable="d" WindowStartupLocation="Manual" Visibility="Visible" @@ -14,7 +15,7 @@ WindowStyle="None"> - + + + @@ -168,7 +180,7 @@