From 880a8a2a8dd29bab5a6e585b04588d60d4d98896 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Thu, 19 Dec 2024 12:59:54 +0100 Subject: [PATCH] Got simple toast notifications going --- misc/BreCalApi.cs | 53 +++++--- misc/BreCalApi.yaml | 14 +- src/BreCalClient/App.xaml | 90 +++++++++++++ src/BreCalClient/AppNotification.cs | 121 ++++++++++++++++++ src/BreCalClient/MainWindow.xaml.cs | 21 +++ .../Properties/Settings.Designer.cs | 55 ++++---- src/BreCalClient/Properties/Settings.settings | 3 + src/BreCalClient/ToastViewModel.cs | 105 +++++++++++++++ 8 files changed, 415 insertions(+), 47 deletions(-) create mode 100644 src/BreCalClient/AppNotification.cs create mode 100644 src/BreCalClient/ToastViewModel.cs diff --git a/misc/BreCalApi.cs b/misc/BreCalApi.cs index 5494860..9013f76 100644 --- a/misc/BreCalApi.cs +++ b/misc/BreCalApi.cs @@ -1,8 +1,8 @@ //---------------------- // -// Generated REST API Client Code Generator v1.16.0.0 on 10.12.2024 08:56:03 -// Using the tool OpenAPI Generator v7.9.0 +// Generated REST API Client Code Generator v1.17.0.0 on 19.12.2024 11:34:56 +// Using the tool OpenAPI Generator v7.10.0 // //---------------------- @@ -3829,7 +3829,7 @@ namespace BreCalClient.misc.Client if (response.Headers != null) { var filePath = string.IsNullOrEmpty(_configuration.TempFolderPath) - ? Path.GetTempPath() + ? global::System.IO.Path.GetTempPath() : _configuration.TempFolderPath; var regex = new Regex(@"Content-Disposition=.*filename=['""]?([^'""\s]+)['""]?$"); foreach (var header in response.Headers) @@ -4069,7 +4069,7 @@ namespace BreCalClient.misc.Client var bytes = ClientUtils.ReadAsBytes(file); var fileStream = file as FileStream; if (fileStream != null) - request.AddFile(fileParam.Key, bytes, Path.GetFileName(fileStream.Name)); + request.AddFile(fileParam.Key, bytes, global::System.IO.Path.GetFileName(fileStream.Name)); else request.AddFile(fileParam.Key, bytes, "no_file_name_provided"); } @@ -4139,7 +4139,7 @@ namespace BreCalClient.misc.Client var clientOptions = new RestClientOptions(baseUrl) { ClientCertificates = configuration.ClientCertificates, - MaxTimeout = configuration.Timeout, + Timeout = configuration.Timeout, Proxy = configuration.Proxy, UserAgent = configuration.UserAgent, UseDefaultCredentials = configuration.UseDefaultCredentials, @@ -4209,11 +4209,11 @@ namespace BreCalClient.misc.Client return result; } } - private RestResponse DeserializeRestResponseFromPolicy(RestClient client, RestRequest request, PolicyResult policyResult) + private async Task> DeserializeRestResponseFromPolicyAsync(RestClient client, RestRequest request, PolicyResult policyResult, CancellationToken cancellationToken = default) { if (policyResult.Outcome == OutcomeType.Successful) { - return client.Deserialize(policyResult.Result); + return await client.Deserialize(policyResult.Result, cancellationToken); } else { @@ -4243,7 +4243,7 @@ namespace BreCalClient.misc.Client { var policy = RetryConfiguration.RetryPolicy; var policyResult = policy.ExecuteAndCapture(() => client.Execute(request)); - return Task.FromResult(DeserializeRestResponseFromPolicy(client, request, policyResult)); + return DeserializeRestResponseFromPolicyAsync(client, request, policyResult); } else { @@ -4264,7 +4264,7 @@ namespace BreCalClient.misc.Client { var policy = RetryConfiguration.AsyncRetryPolicy; var policyResult = await policy.ExecuteAndCaptureAsync((ct) => client.ExecuteAsync(request, ct), cancellationToken).ConfigureAwait(false); - return DeserializeRestResponseFromPolicy(client, request, policyResult); + return await DeserializeRestResponseFromPolicyAsync(client, request, policyResult, cancellationToken); } else { @@ -5000,7 +5000,7 @@ namespace BreCalClient.misc.Client { }; // Setting Timeout has side effects (forces ApiClient creation). - Timeout = 100000; + Timeout = TimeSpan.FromSeconds(100); } /// /// Initializes a new instance of the class @@ -5072,9 +5072,9 @@ namespace BreCalClient.misc.Client /// public virtual IDictionary DefaultHeaders { get; set; } /// - /// Gets or sets the HTTP timeout (milliseconds) of ApiClient. Default to 100000 milliseconds. + /// Gets or sets the HTTP timeout of ApiClient. Defaults to 100 seconds. /// - public virtual int Timeout { get; set; } + public virtual TimeSpan Timeout { get; set; } /// /// Gets or sets the proxy /// @@ -5713,10 +5713,10 @@ namespace BreCalClient.misc.Client /// Temp folder path. string TempFolderPath { get; } /// - /// Gets the HTTP connection timeout (in milliseconds) + /// Gets the HTTP connection timeout. /// /// HTTP connection timeout. - int Timeout { get; } + TimeSpan Timeout { get; } /// /// Gets the proxy. /// @@ -7097,24 +7097,26 @@ namespace BreCalClient.misc.Model public partial class Notification : IValidatableObject { /// - /// Gets or Sets NotificationType + /// Gets or Sets Type /// - [DataMember(Name = "notification_type", EmitDefaultValue = true)] - public NotificationType? NotificationType { get; set; } + [DataMember(Name = "type", EmitDefaultValue = true)] + public NotificationType? Type { get; set; } /// /// Initializes a new instance of the class. /// /// id. /// shipcallId. - /// notificationType. + /// participantId. + /// type. /// message. /// Readonly field set by the database when notification was created. /// Readonly field set by the database when notification was last modified. - public Notification(int id = default(int), int shipcallId = default(int), NotificationType? notificationType = default(NotificationType?), string message = default(string), DateTime created = default(DateTime), DateTime? modified = default(DateTime?)) + public Notification(int id = default(int), int shipcallId = default(int), int? participantId = default(int?), NotificationType? type = default(NotificationType?), string message = default(string), DateTime created = default(DateTime), DateTime? modified = default(DateTime?)) { this.Id = id; this.ShipcallId = shipcallId; - this.NotificationType = notificationType; + this.ParticipantId = participantId; + this.Type = type; this.Message = message; this.Created = created; this.Modified = modified; @@ -7136,6 +7138,14 @@ namespace BreCalClient.misc.Model [DataMember(Name = "shipcall_id", EmitDefaultValue = true)] public int ShipcallId { get; set; } /// + /// Gets or Sets ParticipantId + /// + /* + 9 + */ + [DataMember(Name = "participant_id", EmitDefaultValue = true)] + public int? ParticipantId { get; set; } + /// /// Gets or Sets Message /// /* @@ -7171,7 +7181,8 @@ namespace BreCalClient.misc.Model sb.Append("class Notification {\n"); sb.Append(" Id: ").Append(Id).Append("\n"); sb.Append(" ShipcallId: ").Append(ShipcallId).Append("\n"); - sb.Append(" NotificationType: ").Append(NotificationType).Append("\n"); + sb.Append(" ParticipantId: ").Append(ParticipantId).Append("\n"); + sb.Append(" Type: ").Append(Type).Append("\n"); sb.Append(" Message: ").Append(Message).Append("\n"); sb.Append(" Created: ").Append(Created).Append("\n"); sb.Append(" Modified: ").Append(Modified).Append("\n"); diff --git a/misc/BreCalApi.yaml b/misc/BreCalApi.yaml index a51bd29..da638ee 100644 --- a/misc/BreCalApi.yaml +++ b/misc/BreCalApi.yaml @@ -1550,7 +1550,11 @@ components: type: integer example: 5 nullable: false - notification_type: + participant_id: + type: integer + example: 9 + nullable: true + type: $ref: '#/components/schemas/NotificationType' message: type: string @@ -1569,7 +1573,8 @@ components: example: id: 42 shipcall_id: 5 - notification_type: next24h + participant_id: 9 + type: next24h message: Shipcall may be relevant to you in the next 24 hours created: '2023-08-21T08:23:35Z' modified: '2023-08-21T08:23:35Z' @@ -1581,13 +1586,14 @@ components: example: - id: 42 shipcall_id: 5 - notification_type: time_conflict + participant_id: 9 + type: time_conflict message: Entry XY violates rule Z created: '2023-08-21T08:23:35Z' modified: '2023-08-21T08:23:35Z' - id: 43 shipcall_id: 7 - notification_type: time_conflict + type: time_conflict message: Entry AB violates rule C created: '2023-08-21T08:23:35Z' modified: '2023-08-21T08:23:35Z' diff --git a/src/BreCalClient/App.xaml b/src/BreCalClient/App.xaml index 9b54893..292becc 100644 --- a/src/BreCalClient/App.xaml +++ b/src/BreCalClient/App.xaml @@ -3,6 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:BreCalClient" xmlns:sys="clr-namespace:System;assembly=mscorlib" + xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" StartupUri="MainWindow.xaml" Exit="Application_Exit" Startup="Application_Startup" > @@ -14,6 +15,95 @@ 10 10 + #147ec9 + + + #11ad45 + + + #e60914 + + + #f5a300 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BreCalClient/AppNotification.cs b/src/BreCalClient/AppNotification.cs new file mode 100644 index 0000000..7a0f075 --- /dev/null +++ b/src/BreCalClient/AppNotification.cs @@ -0,0 +1,121 @@ +// Copyright (c) 2024- schick Informatik +// Description: Helper (static) class to handle polled API notifications +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ToastNotifications.Core; + +namespace BreCalClient +{ + internal class AppNotification + { + private static Dictionary _notifications = new (); + private readonly int _id; + + + public AppNotification(int id) + { + _id = id; + } + + public int Id { get { return _id; } } + + #region internal statics + + internal static void LoadFromSettings() + { + _notifications.Clear(); + + // preload notifications that have been processed + foreach (string? notification_id in Properties.Settings.Default.Notifications) + { + if(Int32.TryParse(notification_id, out int result)) + _notifications.Add(result, new AppNotification(result)); + } + } + + internal static void Clear() + { + _notifications.Clear(); + SaveNotifications(); + } + + internal static bool UpdateNotifications(List notifications, System.Collections.Concurrent.ConcurrentDictionary currentShipcalls, ToastViewModel vm) + { + bool result = false; + foreach (BreCalClient.misc.Model.Notification notification in notifications) + { + if(!_notifications.ContainsKey(notification.Id)) + { + _notifications.Add(notification.Id, new AppNotification(notification.Id)); + + // filter if the notification concerns us + if(notification.ParticipantId != null) + { + if(App.Participant.Id == notification.ParticipantId) + { + result = true; + } + } + else + { + // find out if this notification concerns us + if(currentShipcalls.ContainsKey(notification.ShipcallId)) + { + result = true; + } + } + + if(result) + { + System.Diagnostics.Trace.WriteLine($"Notification {notification.Id} Type {notification.Type}"); + MessageOptions options = new MessageOptions(); + options.FontSize = 14; + options.ShowCloseButton = true; + switch(notification.Type) + { + case misc.Model.NotificationType.TimeConflict: + + break; + case misc.Model.NotificationType.TimeConflictResolved: + + break; + case misc.Model.NotificationType.Assignment: + + break; + case misc.Model.NotificationType.Next24h: + + break; + case misc.Model.NotificationType.Unassigned: + + break; + } + + string toastText = $"{currentShipcalls[notification.ShipcallId]?.Ship?.Name} ({currentShipcalls[notification.ShipcallId]?.Shipcall?.Type}) - {currentShipcalls[notification.ShipcallId]?.GetETAETD(true)} - {currentShipcalls[notification.ShipcallId]?.GetBerthText(null)}"; + App.Current.Dispatcher.Invoke(() => + { + vm.ShowInformation(toastText, options); + }); + } + + } + } + return result; + } + + internal static void SaveNotifications() + { + Properties.Settings.Default.Notifications.Clear(); + foreach (int notification_id in _notifications.Keys) + { + Properties.Settings.Default.Notifications.Add(notification_id.ToString()); + } + } + + #endregion + } +} diff --git a/src/BreCalClient/MainWindow.xaml.cs b/src/BreCalClient/MainWindow.xaml.cs index cb0056b..23e109a 100644 --- a/src/BreCalClient/MainWindow.xaml.cs +++ b/src/BreCalClient/MainWindow.xaml.cs @@ -36,8 +36,11 @@ namespace BreCalClient public partial class MainWindow : Window { private readonly ILog _log = LogManager.GetLogger(typeof(MainWindow)); + private readonly ToastViewModel _vm; + private const int SHIPCALL_UPDATE_INTERVAL_SECONDS = 30; private const int SHIPS_UPDATE_INTERVAL_SECONDS = 120; + private const int CHECK_NOTIFICATIONS_INTERVAL_SECONDS = 5; private const int PROGRESS_STEPS = 50; #region Fields @@ -122,6 +125,13 @@ namespace BreCalClient RetryConfiguration.AsyncRetryPolicy = retryPolicy; this.generalProgressStatus.Maximum = PROGRESS_STEPS; + _vm = new ToastViewModel(); + this.Unloaded += MainWindow_Unloaded; + } + + private void MainWindow_Unloaded(object sender, RoutedEventArgs e) + { + _vm.OnUnloaded(); } #endregion @@ -510,6 +520,7 @@ namespace BreCalClient _ = Task.Run(() => RefreshShipcalls()); _ = Task.Run(() => RefreshShips()); + _ = Task.Run(() => CheckNotifications()); } @@ -633,6 +644,16 @@ namespace BreCalClient } } + public async Task CheckNotifications() + { + while (true) + { + Thread.Sleep(CHECK_NOTIFICATIONS_INTERVAL_SECONDS * 1000); + List notifications = await _staticApi.NotificationsGetAsync(); + AppNotification.UpdateNotifications(notifications, _allShipcallsDict, _vm); + } + } + #endregion #region basic operations diff --git a/src/BreCalClient/Properties/Settings.Designer.cs b/src/BreCalClient/Properties/Settings.Designer.cs index ece7fc6..f24308d 100644 --- a/src/BreCalClient/Properties/Settings.Designer.cs +++ b/src/BreCalClient/Properties/Settings.Designer.cs @@ -9,20 +9,20 @@ //------------------------------------------------------------------------------ namespace BreCalClient.Properties { - - + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.10.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - + public static Settings Default { get { return defaultInstance; } } - + [global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("#1D751F")] @@ -31,7 +31,7 @@ namespace BreCalClient.Properties { return ((string)(this["BG_COLOR"])); } } - + [global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("!!Bremen calling Testversion!!")] @@ -40,7 +40,7 @@ namespace BreCalClient.Properties { return ((string)(this["APP_TITLE"])); } } - + [global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("https://www.textbausteine.net/")] @@ -49,7 +49,7 @@ namespace BreCalClient.Properties { return ((string)(this["LOGO_IMAGE_URL"])); } } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("")] @@ -61,7 +61,7 @@ namespace BreCalClient.Properties { this["FilterCriteria"] = value; } } - + [global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("https://brecaldevel.bsmd-emswe.eu")] @@ -70,7 +70,7 @@ namespace BreCalClient.Properties { return ((string)(this["API_URL"])); } } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("800")] @@ -82,7 +82,7 @@ namespace BreCalClient.Properties { this["Width"] = value; } } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("450")] @@ -94,7 +94,7 @@ namespace BreCalClient.Properties { this["Height"] = value; } } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("0")] @@ -106,7 +106,7 @@ namespace BreCalClient.Properties { this["Left"] = value; } } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("0")] @@ -118,7 +118,7 @@ namespace BreCalClient.Properties { this["Top"] = value; } } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("0")] @@ -130,7 +130,7 @@ namespace BreCalClient.Properties { this["W1Left"] = value; } } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("0")] @@ -142,7 +142,7 @@ namespace BreCalClient.Properties { this["W1Top"] = value; } } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("0")] @@ -154,7 +154,7 @@ namespace BreCalClient.Properties { this["W2Left"] = value; } } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("0")] @@ -166,7 +166,7 @@ namespace BreCalClient.Properties { this["W2Top"] = value; } } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("0")] @@ -178,7 +178,7 @@ namespace BreCalClient.Properties { this["W3Left"] = value; } } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("0")] @@ -190,7 +190,7 @@ namespace BreCalClient.Properties { this["W3Top"] = value; } } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("0")] @@ -202,7 +202,7 @@ namespace BreCalClient.Properties { this["W4Left"] = value; } } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("0")] @@ -214,7 +214,7 @@ namespace BreCalClient.Properties { this["W4Top"] = value; } } - + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("")] @@ -226,5 +226,16 @@ namespace BreCalClient.Properties { this["FilterCriteriaMap"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public global::System.Collections.Specialized.StringCollection Notifications { + get { + return ((global::System.Collections.Specialized.StringCollection)(this["Notifications"])); + } + set { + this["Notifications"] = value; + } + } } } diff --git a/src/BreCalClient/Properties/Settings.settings b/src/BreCalClient/Properties/Settings.settings index b10fd5b..9f7c317 100644 --- a/src/BreCalClient/Properties/Settings.settings +++ b/src/BreCalClient/Properties/Settings.settings @@ -56,5 +56,8 @@ + + + \ No newline at end of file diff --git a/src/BreCalClient/ToastViewModel.cs b/src/BreCalClient/ToastViewModel.cs new file mode 100644 index 0000000..cf47fa3 --- /dev/null +++ b/src/BreCalClient/ToastViewModel.cs @@ -0,0 +1,105 @@ +// Copyright (c) 2024- schick Informatik +// Description: +// + +using System; +using System.ComponentModel; +using System.Windows; + +using ToastNotifications; +using ToastNotifications.Core; +using ToastNotifications.Lifetime; +using ToastNotifications.Lifetime.Clear; +using ToastNotifications.Messages; +using ToastNotifications.Position; + +namespace BreCalClient +{ + internal class ToastViewModel : INotifyPropertyChanged + { + private readonly Notifier _notifier; + + public ToastViewModel() + { + _notifier = new Notifier(cfg => + { + cfg.PositionProvider = new WindowPositionProvider( + parentWindow: Application.Current.MainWindow, + corner: Corner.BottomRight, + offsetX: 25, + offsetY: 100); + + cfg.LifetimeSupervisor = new TimeAndCountBasedLifetimeSupervisor( + notificationLifetime: TimeSpan.FromSeconds(6), + maximumNotificationCount: MaximumNotificationCount.FromCount(6)); + + cfg.Dispatcher = Application.Current.Dispatcher; + + cfg.DisplayOptions.TopMost = false; + cfg.DisplayOptions.Width = 250; + }); + + _notifier.ClearMessages(new ClearAll()); + } + + public void OnUnloaded() + { + _notifier.Dispose(); + } + + public void ShowInformation(string message) + { + _notifier.ShowInformation(message); + } + + public void ShowInformation(string message, MessageOptions opts) + { + _notifier.ShowInformation(message, opts); + } + + public void ShowSuccess(string message) + { + _notifier.ShowSuccess(message); + } + + public void ShowSuccess(string message, MessageOptions opts) + { + _notifier.ShowSuccess(message, opts); + } + + internal void ClearMessages(string msg) + { + _notifier.ClearMessages(new ClearByMessage(msg)); + } + + public void ShowWarning(string message, MessageOptions opts) + { + _notifier.ShowWarning(message, opts); + } + + public void ShowError(string message) + { + _notifier.ShowError(message); + } + + public void ShowError(string message, MessageOptions opts) + { + _notifier.ShowError(message, opts); + } + + + public event PropertyChangedEventHandler? PropertyChanged; + + protected virtual void OnPropertyChanged(string? propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public void ClearAll() + { + _notifier.ClearMessages(new ClearAll()); + } + } +}