From 3966d6eb4c2c93c7bf1a9cd3b874c0a0a7a83799 Mon Sep 17 00:00:00 2001 From: Aryeh Silver Date: Wed, 27 Sep 2023 10:17:20 +0100 Subject: [PATCH] Option to use random quotes with management --- TheShivisiApp.sln | 10 +- TheShivisiApp/App.xaml.cs | 139 +++++++--------- TheShivisiApp/Data/NotifyIconResources.xaml | 4 +- TheShivisiApp/GlobalUsings.cs | 9 +- TheShivisiApp/Helpers/PopTheToast.cs | 11 +- TheShivisiApp/Helpers/VersionHelper.cs | 6 + TheShivisiApp/TheShivisiApp.csproj | 31 +++- .../ViewModels/NotifyIconViewModel.cs | 29 ++-- TheShivisiApp/Views/EditQuoteWindow.xaml | 66 ++++++++ TheShivisiApp/Views/EditQuoteWindow.xaml.cs | 27 ++++ TheShivisiApp/Views/QuoteWindow.xaml | 54 +++++++ TheShivisiApp/Views/QuoteWindow.xaml.cs | 20 +++ TheShivisiApp/Views/QuotesListWindow.xaml | 121 ++++++++++++++ TheShivisiApp/Views/QuotesListWindow.xaml.cs | 59 +++++++ TheShivisiApp/Views/Settings.xaml | 117 -------------- TheShivisiApp/Views/Settings.xaml.cs | 148 ----------------- TheShivisiApp/Views/SettingsWindow.xaml | 152 ++++++++++++++++++ TheShivisiApp/Views/SettingsWindow.xaml.cs | 86 ++++++++++ 18 files changed, 712 insertions(+), 377 deletions(-) create mode 100644 TheShivisiApp/Helpers/VersionHelper.cs create mode 100644 TheShivisiApp/Views/EditQuoteWindow.xaml create mode 100644 TheShivisiApp/Views/EditQuoteWindow.xaml.cs create mode 100644 TheShivisiApp/Views/QuoteWindow.xaml create mode 100644 TheShivisiApp/Views/QuoteWindow.xaml.cs create mode 100644 TheShivisiApp/Views/QuotesListWindow.xaml create mode 100644 TheShivisiApp/Views/QuotesListWindow.xaml.cs delete mode 100644 TheShivisiApp/Views/Settings.xaml delete mode 100644 TheShivisiApp/Views/Settings.xaml.cs create mode 100644 TheShivisiApp/Views/SettingsWindow.xaml create mode 100644 TheShivisiApp/Views/SettingsWindow.xaml.cs diff --git a/TheShivisiApp.sln b/TheShivisiApp.sln index f90ce15..11bc2f7 100644 --- a/TheShivisiApp.sln +++ b/TheShivisiApp.sln @@ -1,12 +1,14 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31005.135 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34031.279 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TheShivisiApp", "TheShivisiApp\TheShivisiApp.csproj", "{BC2E0E74-F63B-4D8D-B3CD-36A87B95BC6E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotifyIcon", "NotifyIcon\NotifyIcon.csproj", "{E9749304-9102-4D3F-80BF-760D377202C0}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TheShivisiApp.Models", "TheShivisiApp.Models\TheShivisiApp.Models.csproj", "{5113FE37-B56D-447E-8AD1-1A2FCA3FE19E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {E9749304-9102-4D3F-80BF-760D377202C0}.Debug|Any CPU.Build.0 = Debug|Any CPU {E9749304-9102-4D3F-80BF-760D377202C0}.Release|Any CPU.ActiveCfg = Release|Any CPU {E9749304-9102-4D3F-80BF-760D377202C0}.Release|Any CPU.Build.0 = Release|Any CPU + {5113FE37-B56D-447E-8AD1-1A2FCA3FE19E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5113FE37-B56D-447E-8AD1-1A2FCA3FE19E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5113FE37-B56D-447E-8AD1-1A2FCA3FE19E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5113FE37-B56D-447E-8AD1-1A2FCA3FE19E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TheShivisiApp/App.xaml.cs b/TheShivisiApp/App.xaml.cs index 5caf464..a3cbaa1 100644 --- a/TheShivisiApp/App.xaml.cs +++ b/TheShivisiApp/App.xaml.cs @@ -2,91 +2,59 @@ public partial class App : Application { #region Props, Fields & consts - private static readonly string App_Folder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\The Shivisi App"; - private TaskbarIcon notifyIcon; - private Timer timer; + private AppDbContext _context; + private TaskbarIcon _notifyIcon; + private Timer _timer; public DateTime LastRead { get; set; } - public bool RunsOnStartup { get; set; } = true; - public bool SplashScreen { get; set; } = true; - public double Interval { get; set; } = 30; - public string NotifText { get; set; } = "Remember!" + Environment.NewLine + "You're not the one in charge here!"; + public Settings Settings { get; set; } = new(); #endregion protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); - //create the notifyicon (it's a resource declared in NotifyIconResources.xaml) - notifyIcon = (TaskbarIcon)FindResource("NotifyIcon"); - StyleManager.ApplicationTheme = new Windows11Theme(); - Windows11Palette.LoadPreset(Windows11Palette.ColorVariation.Dark); - ThemeEffectsHelper.IsAcrylicEnabled = false; + bool showSettings = true; - Start(); - } - - public void Start() { - if (!Directory.Exists(App_Folder)) { - Directory.CreateDirectory(App_Folder); - } - - ReadFromXml(); + // Listen to notification activation + ToastNotificationManagerCompat.OnActivated += toastArgs => { + // Obtain the arguments from the notification + ToastArguments args = ToastArguments.Parse(toastArgs.Argument); - ShowSplashScreen(); - RunTimer(); - } - - private void ReadFromXml() { - bool success; - Exception exception = new(); + // Obtain any user input (text boxes, menu selections) from the notification + //ValueSet userInput = toastArgs.UserInput; - try { - if (File.Exists(Path.Combine(App_Folder, "Settings.xml"))) { - System.Xml.XmlDocument readFile = new(); - readFile.Load(Path.Combine(App_Folder, "Settings.xml")); + // Need to dispatch to UI thread if performing UI operations + Current.Dispatcher.Invoke(delegate { + QuoteWindow quoteWindow = new(args.FirstOrDefault(a => a.Key == "Text").Value, args.FirstOrDefault(a => a.Key == "Source").Value, args.FirstOrDefault(a => a.Key == "Id").Value); + quoteWindow.Show(); + quoteWindow.IsTopmost = true; + quoteWindow.IsTopmost = false; + quoteWindow.Focus(); + }); - System.Xml.XmlNode startupNode = readFile.SelectSingleNode("/Settings/Startup"); - RunsOnStartup = startupNode.InnerText == "True"; + showSettings = false; + }; - System.Xml.XmlNode splashScreenNode = readFile.SelectSingleNode("/Settings/SplashScreen"); - SplashScreen = splashScreenNode.InnerText == "True"; + if (showSettings) { + _context = new(); + _context.Database.Migrate(); - System.Xml.XmlNode intervalNode = readFile.SelectSingleNode("/Settings/Interval"); - Interval = double.TryParse(intervalNode.InnerText, out double outInterval) ? outInterval : 30; + //create the notifyIcon (it's a resource declared in NotifyIconResources.xaml) + _notifyIcon = (TaskbarIcon)FindResource("NotifyIcon"); - System.Xml.XmlNode notifTextNode = readFile.SelectSingleNode("/Settings/NotifText"); - NotifText = notifTextNode.InnerText; - } else { - RunsOnStartup = true; - SplashScreen = true; - Interval = 30; - NotifText = "Remember!" + Environment.NewLine + "You're not the one in charge here!"; - } + StyleManager.ApplicationTheme = new Windows11Theme(); + Windows11Palette.LoadPreset(Windows11Palette.ColorVariation.Dark); + ThemeEffectsHelper.IsAcrylicEnabled = false; + Settings = _context.Settings.SingleOrDefault(); LastRead = DateTime.Now; - if (Interval <= 0) { - RadWindow.Alert(new DialogParameters { - Header = "Error", - Content = "The interval value must be greater than 0" + Environment.NewLine + "1 minute intervals will be used instead" - }); - Interval = 1; - } - success = true; - } catch (Exception ex) { - success = false; - exception = ex; - } - - if (!success) { - RadWindow.Alert(new DialogParameters { - Header = "The Shivisi App - Error", - Content = "Error reading the settings" + Environment.NewLine + exception.Message - }); + ShowSplashScreen(); + RunTimer(); } } private void ShowSplashScreen() { - if (SplashScreen) { + if (Settings.ShowSplashScreen) { SplashScreen splash = new("Data/SplashScreen.png"); splash.Show(true, true); splash.Close(TimeSpan.FromSeconds(5)); @@ -94,41 +62,42 @@ private void ShowSplashScreen() { } private void RunTimer() { - timer = new Timer { + _timer = new Timer { // [1 min = 60,000 | 5 min = 300,000 | 30 min = 1,800,000 | 1 hr = 3,600,000] - Interval = Interval * 60000 + Interval = Settings.Interval * 60000 }; - timer.Start(); - timer.Elapsed += new ElapsedEventHandler(Timer_Elapsed); + _timer.Start(); + _timer.Elapsed += new ElapsedEventHandler(Timer_Elapsed); } - private void Timer_Elapsed(object sender, ElapsedEventArgs e) { + private async void Timer_Elapsed(object sender, ElapsedEventArgs e) { // ISSUE: This will only get hit the next time the timer finishes it's elapsed time. // What if the user has chosen a smaller interval, say from 30 min down to 5, // how will the app know to change it until another 30 min passes and we hit the elapsed? // Settings need to raise an event which will be picked up here... - CheckTimeStamps(); - PopTheToast.PopIt(NotifText); + await CheckTimeStamps(); + if (Settings.UseRandomQuote) { + Random random = new(); + List quotesList = await _context.Quotes.ToListAsync(); + int rnd = random.Next(quotesList.Count); + Quote quote = quotesList.FirstOrDefault(q => q.Id == rnd); + PopTheToast.PopIt(quote.QuotedText, quote.Source, quote.Id); + } else { + PopTheToast.PopIt($"Remember!{Environment.NewLine}You're not the one in charge here!", $"Via TSA - {$"Version {VersionHelper.GetRunningVersion()}".Remove(13)}", 0); + } } - private void CheckTimeStamps() { - DateTime time = File.GetLastWriteTime(Path.Combine(App_Folder, "Settings.xml")); - if (LastRead < time) { - System.Xml.XmlDocument readFile = new(); - readFile.Load(Path.Combine(App_Folder, "Settings.xml")); - - System.Xml.XmlNode intervalNode = readFile.SelectSingleNode("/Settings/Interval"); - timer.Interval = (double.TryParse(intervalNode.InnerText, out double outInterval) ? outInterval : 30) * 60000; - - System.Xml.XmlNode notifTextNode = readFile.SelectSingleNode("/Settings/NotifText"); - NotifText = notifTextNode.InnerText; - + private async Task CheckTimeStamps() { + DateTime lastUpdated = Settings.LastUpdated; + if (LastRead < lastUpdated) { + Settings = await _context.Settings.SingleOrDefaultAsync(); + _timer.Interval = Settings.Interval * 60000; LastRead = DateTime.Now; } } protected override void OnExit(ExitEventArgs e) { - notifyIcon.Dispose(); // the icon would clean up automatically, but this is cleaner + _notifyIcon.Dispose(); // the icon would clean up automatically, but this is cleaner base.OnExit(e); } } diff --git a/TheShivisiApp/Data/NotifyIconResources.xaml b/TheShivisiApp/Data/NotifyIconResources.xaml index 02be0fe..bf2c2af 100644 --- a/TheShivisiApp/Data/NotifyIconResources.xaml +++ b/TheShivisiApp/Data/NotifyIconResources.xaml @@ -8,7 +8,9 @@ - + + + diff --git a/TheShivisiApp/GlobalUsings.cs b/TheShivisiApp/GlobalUsings.cs index 1653a1e..41da647 100644 --- a/TheShivisiApp/GlobalUsings.cs +++ b/TheShivisiApp/GlobalUsings.cs @@ -1,5 +1,9 @@ -global using NotifyIcon; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.Toolkit.Uwp.Notifications; +global using NotifyIcon; +global using Pixata.Extensions; global using System; +global using System.Collections.ObjectModel; global using System.IO; global using System.Reflection; global using System.Threading.Tasks; @@ -8,3 +12,6 @@ global using Telerik.Windows.Controls; global using Telerik.Windows.Controls.MaterialControls; global using TheShivisiApp.Helpers; +global using TheShivisiApp.Models; +global using TheShivisiApp.Views; +global using Timer = System.Timers.Timer; diff --git a/TheShivisiApp/Helpers/PopTheToast.cs b/TheShivisiApp/Helpers/PopTheToast.cs index 7f4b119..1b927fa 100644 --- a/TheShivisiApp/Helpers/PopTheToast.cs +++ b/TheShivisiApp/Helpers/PopTheToast.cs @@ -1,14 +1,15 @@ -using Microsoft.Toolkit.Uwp.Notifications; - -namespace TheShivisiApp.Helpers; +namespace TheShivisiApp.Helpers; public class PopTheToast { - public static void PopIt(string notifText) => + public static void PopIt(string notifText, string source, int id) => new ToastContentBuilder() .AddText("The Shivisi App") .AddText(!string.IsNullOrWhiteSpace(notifText) ? notifText : "Remember!" + Environment.NewLine + "You're not the one in charge here!") //.AddHeroImage(new Uri("file:///")) .AddAppLogoOverride(new Uri("file:///" + Path.GetFullPath("Data/Logo.png")), ToastGenericAppLogoCrop.Circle) - .AddAttributionText("Via TSA") + .AddAttributionText(source) + .AddArgument("Text", notifText) + .AddArgument("Source", source) + .AddArgument("Id", id) .Show(toast => toast.ExpirationTime = DateTime.Now.AddMinutes(1)); } diff --git a/TheShivisiApp/Helpers/VersionHelper.cs b/TheShivisiApp/Helpers/VersionHelper.cs new file mode 100644 index 0000000..7dec574 --- /dev/null +++ b/TheShivisiApp/Helpers/VersionHelper.cs @@ -0,0 +1,6 @@ +namespace TheShivisiApp.Helpers; + +public static class VersionHelper { + public static Version GetRunningVersion() => + Assembly.GetExecutingAssembly().GetName().Version; +} diff --git a/TheShivisiApp/TheShivisiApp.csproj b/TheShivisiApp/TheShivisiApp.csproj index 14f7380..1e4f87a 100644 --- a/TheShivisiApp/TheShivisiApp.csproj +++ b/TheShivisiApp/TheShivisiApp.csproj @@ -3,19 +3,26 @@ WinExe net6.0-windows10.0.17763.0 true + enable TheShivisiApp False Data\SNI The Shivisi App - 3.2.2.0 - 3.2.2.0 - 3.2.2 + 4.1.2.0 + 4.1.2.0 + 4.1.2 + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + @@ -26,7 +33,16 @@ MSBuild:Compile - + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + MSBuild:Compile @@ -36,8 +52,12 @@ + - + + + + @@ -59,6 +79,7 @@ Never + Never diff --git a/TheShivisiApp/ViewModels/NotifyIconViewModel.cs b/TheShivisiApp/ViewModels/NotifyIconViewModel.cs index 39518b7..56f7785 100644 --- a/TheShivisiApp/ViewModels/NotifyIconViewModel.cs +++ b/TheShivisiApp/ViewModels/NotifyIconViewModel.cs @@ -1,5 +1,4 @@ using System.Windows.Input; -using TheShivisiApp.Views; namespace TheShivisiApp.ViewModels; @@ -9,16 +8,24 @@ namespace TheShivisiApp.ViewModels; /// in App.xaml.cs could have created this view model, and assigned it to the NotifyIcon. /// public class NotifyIconViewModel { - /// - /// Shows a window, if none is already open. - /// public ICommand ShowWindowCommand => new DelegateCommand { - CanExecuteFunc = () => Application.Current.MainWindow == null, + //CanExecuteFunc = () => Application.Current.MainWindow == null, CommandAction = () => { - Settings settings = new Settings(); - if (settings.Success) { - settings.Show(); - } + new SettingsWindow().Show(); + } + }; + + public ICommand ShowQuotesCommand => new DelegateCommand { + //CanExecuteFunc = () => Application.Current.MainWindow == null, + CommandAction = () => { + new QuotesListWindow().Show(); + } + }; + + public ICommand NewQuoteWindowCommand => new DelegateCommand { + //CanExecuteFunc = () => Application.Current.MainWindow == null, + CommandAction = () => { + new EditQuoteWindow(new()).Show(); } }; @@ -37,10 +44,6 @@ public class NotifyIconViewModel { new DelegateCommand { CommandAction = () => Application.Current.Shutdown() }; } - -/// -/// Simplistic delegate command for the demo. -/// public class DelegateCommand : ICommand { public Action CommandAction { get; set; } public Func CanExecuteFunc { get; set; } diff --git a/TheShivisiApp/Views/EditQuoteWindow.xaml b/TheShivisiApp/Views/EditQuoteWindow.xaml new file mode 100644 index 0000000..3289855 --- /dev/null +++ b/TheShivisiApp/Views/EditQuoteWindow.xaml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/TheShivisiApp/Views/EditQuoteWindow.xaml.cs b/TheShivisiApp/Views/EditQuoteWindow.xaml.cs new file mode 100644 index 0000000..6b55d6f --- /dev/null +++ b/TheShivisiApp/Views/EditQuoteWindow.xaml.cs @@ -0,0 +1,27 @@ +namespace TheShivisiApp.Views; + +public partial class EditQuoteWindow : RadWindow { + private AppDbContext _context; + public Quote Quote { get; set; } = new(); + + public EditQuoteWindow(Quote quote) { + InitializeComponent(); + _context = new(); + Quote = quote; + window.Header = quote.Id > 0 ? "Edit quote" : "New quote"; + quoteText.Text = quote.QuotedText; + source.Text = quote.Source; + quoteText.WatermarkContent = $"Remember!{Environment.NewLine}You're not the one in charge here!"; + saveButton.Focus(); + } + + private async void Save_Click(object sender, RoutedEventArgs e) { + Quote.QuotedText = quoteText.Text; + Quote.Source = source.Text; + _context.Quotes.Update(Quote); + await _context.SaveChangesAsync(); + } + + private void Cancel_Click(object sender, RoutedEventArgs e) => + Close(); +} diff --git a/TheShivisiApp/Views/QuoteWindow.xaml b/TheShivisiApp/Views/QuoteWindow.xaml new file mode 100644 index 0000000..c98c165 --- /dev/null +++ b/TheShivisiApp/Views/QuoteWindow.xaml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + diff --git a/TheShivisiApp/Views/QuoteWindow.xaml.cs b/TheShivisiApp/Views/QuoteWindow.xaml.cs new file mode 100644 index 0000000..db6f0ec --- /dev/null +++ b/TheShivisiApp/Views/QuoteWindow.xaml.cs @@ -0,0 +1,20 @@ +namespace TheShivisiApp.Views; + +public partial class QuoteWindow : RadWindow { + private int Id; + + public QuoteWindow(string text, string source, string id) { + InitializeComponent(); + textBlock.Text = text; + sourceBlock.Text = source; + if (int.TryParse(id, out Id) && Id > 0) { + edit.Visibility = Visibility.Visible; + } + } + + private async void Edit_Click(object sender, RoutedEventArgs e) { + using AppDbContext context = new(); + new EditQuoteWindow(await context.Quotes.FirstOrDefaultAsync(q => q.Id == Id)).Show(); + Close(); + } +} diff --git a/TheShivisiApp/Views/QuotesListWindow.xaml b/TheShivisiApp/Views/QuotesListWindow.xaml new file mode 100644 index 0000000..c519a5f --- /dev/null +++ b/TheShivisiApp/Views/QuotesListWindow.xaml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TheShivisiApp/Views/QuotesListWindow.xaml.cs b/TheShivisiApp/Views/QuotesListWindow.xaml.cs new file mode 100644 index 0000000..eca7a94 --- /dev/null +++ b/TheShivisiApp/Views/QuotesListWindow.xaml.cs @@ -0,0 +1,59 @@ +using System.Windows.Controls; +using System.Windows.Input; + +namespace TheShivisiApp.Views; + +public partial class QuotesListWindow : RadWindow { + private AppDbContext _context; + private Quote Quote { get; set; } = new(); + private ObservableCollection Quotes { get; set; } = new(); + private ObservableCollection SearchedQuotes { get; set; } = new(); + + public QuotesListWindow() { + InitializeComponent(); + _context = new(); + Quotes = _context.Quotes.ToObservableCollection(); + list.ItemsSource = Quotes; + search.Focus(); + } + + private void New_Click(object sender, RoutedEventArgs e) => + new EditQuoteWindow(new()).Show(); + + private void Delete_Click(object sender, RoutedEventArgs e) => + Confirm(new DialogParameters { + Content = "Are you sure you want to delete this quote?", + Header = "Confirm delete", + Closed = OnClosed + }); + + private void Edit_Click(object sender, RoutedEventArgs e) => + new EditQuoteWindow(Quote).Show(); + + private async void OnClosed(object sender, WindowClosedEventArgs e) { + bool? result = e.DialogResult; + if (result == true) { + _context.Quotes.Remove(Quote); + await _context.SaveChangesAsync(); + Quotes.Remove(Quote); + } + } + + private void ListSelected_Changed(object sender, SelectionChangedEventArgs e) => + Quote = list.SelectedItem as Quote; + + private void On_Focus(object sender, KeyboardFocusChangedEventArgs e) => + (sender as RadListBoxItem).IsSelected = true; + + private void SearchText_Changed(object sender, TextChangedEventArgs e) { + string searchText = (sender as TextBox).Text; + SearchedQuotes = Quotes.Where(q => q.QuotedText.ToLower().Contains(searchText.ToLower())).ToObservableCollection(); + list.ItemsSource = SearchedQuotes; + } + + private async void Text_Changed(object sender, TextChangedEventArgs e) { + //Quote.QuotedText = (sender as TextBox).Text; + //_context.Quotes.Update(Quote); + //await _context.SaveChangesAsync(); + } +} diff --git a/TheShivisiApp/Views/Settings.xaml b/TheShivisiApp/Views/Settings.xaml deleted file mode 100644 index 8f0a003..0000000 --- a/TheShivisiApp/Views/Settings.xaml +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - -