From 4d5d63dbdd99988d83a18b23fc9ef32387980eb2 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Wed, 18 Dec 2024 08:53:06 +0100 Subject: [PATCH 1/8] Updated Nuget --- src/BreCalClient/BreCalClient.csproj | 8 +++++--- .../PublishProfiles/ClickOnceDevelProfile.pubxml | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/BreCalClient/BreCalClient.csproj b/src/BreCalClient/BreCalClient.csproj index 1493944..5ad9573 100644 --- a/src/BreCalClient/BreCalClient.csproj +++ b/src/BreCalClient/BreCalClient.csproj @@ -118,10 +118,12 @@ - + - - + + + + diff --git a/src/BreCalClient/Properties/PublishProfiles/ClickOnceDevelProfile.pubxml b/src/BreCalClient/Properties/PublishProfiles/ClickOnceDevelProfile.pubxml index 3747c9b..a3f0105 100644 --- a/src/BreCalClient/Properties/PublishProfiles/ClickOnceDevelProfile.pubxml +++ b/src/BreCalClient/Properties/PublishProfiles/ClickOnceDevelProfile.pubxml @@ -4,8 +4,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121. --> - 2 - 1.7.0.0 + 1 + 1.7.0.1 True Debug True @@ -38,7 +38,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. Foreground True Publish.html - 1.7.0.0 + 1.7.0.1 false From f218e5f96a4dccae505c72e11df7faac36d49e76 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Wed, 18 Dec 2024 17:59:40 +0100 Subject: [PATCH 2/8] fixed missing info in notification API --- src/server/BreCal/api/notifications.py | 11 +++++------ src/server/BreCal/impl/notifications.py | 10 +++------- src/server/BreCal/schemas/model.py | 1 + 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/server/BreCal/api/notifications.py b/src/server/BreCal/api/notifications.py index 4fe4a8e..c05d52e 100644 --- a/src/server/BreCal/api/notifications.py +++ b/src/server/BreCal/api/notifications.py @@ -12,12 +12,11 @@ bp = Blueprint('notifications', __name__) @auth_guard() # no restriction by role def GetNotifications(): try: - if 'shipcall_id' in request.args: - options = {} - options["shipcall_id"] = request.args.get("shipcall_id") - return impl.notifications.GetNotifications(options) + if 'Authorization' in request.headers: + token = request.headers.get('Authorization') + return impl.notifications.GetNotifications(token) else: - return create_dynamic_exception_response(ex=None, status_code=400, message="missing argument: shipcall_id") - + return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated") + except Exception as ex: return create_dynamic_exception_response(ex=ex, status_code=400) diff --git a/src/server/BreCal/impl/notifications.py b/src/server/BreCal/impl/notifications.py index 57fe62c..3a95e0a 100644 --- a/src/server/BreCal/impl/notifications.py +++ b/src/server/BreCal/impl/notifications.py @@ -6,21 +6,17 @@ from ..schemas import model from .. import local_db from BreCal.database.sql_queries import SQLQuery -def GetNotifications(options): +def GetNotifications(token): """ - :param options: A dictionary containing all the paramters for the Operations - options["shipcall_id"]: **Id**. *Example: 42*. Id of referenced ship call. - + No parameters, gets all entries """ try: pooledConnection = local_db.getPoolConnection() commands = pydapper.using(pooledConnection) - # query = SQLQuery.get_notifications() - # data = commands.query(query, model=model.Notification.from_query_row, param={"scid" : options["shipcall_id"]}) data = commands.query("SELECT id, shipcall_id, participant_id, level, type, message, created, modified FROM notification " + - "WHERE shipcall_id = ?scid?", model=model.Notification.from_query_row, param={"scid" : options["shipcall_id"]}) + "WHERE level = 2", model=model.Notification.from_query_row) pooledConnection.close() except Exception as ex: diff --git a/src/server/BreCal/schemas/model.py b/src/server/BreCal/schemas/model.py index 0d36fc8..f6de5c8 100644 --- a/src/server/BreCal/schemas/model.py +++ b/src/server/BreCal/schemas/model.py @@ -155,6 +155,7 @@ class Notification: return { "id": self.id, "shipcall_id": self.shipcall_id, + "participant_id": self.participant_id, "level": self.level, "type": self.type.name if isinstance(self.type, IntEnum) else NotificationType(self.type).name, "message": self.message, From 880a8a2a8dd29bab5a6e585b04588d60d4d98896 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Thu, 19 Dec 2024 12:59:54 +0100 Subject: [PATCH 3/8] 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()); + } + } +} From a648cc2e71d27de53a4b3a13a095d5f84780cd11 Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Mon, 23 Dec 2024 11:23:37 +0100 Subject: [PATCH 4/8] Overview window of past notifications --- src/BreCalClient/App.config | 6 + src/BreCalClient/AppNotification.cs | 136 +++++++++++------- src/BreCalClient/BreCalClient.csproj | 2 + src/BreCalClient/HistoryDialog.xaml.cs | 2 +- src/BreCalClient/MainWindow.xaml | 18 ++- src/BreCalClient/MainWindow.xaml.cs | 23 ++- src/BreCalClient/NotificationDialog.xaml | 61 ++++++++ src/BreCalClient/NotificationDialog.xaml.cs | 44 ++++++ .../Properties/Settings.Designer.cs | 24 ++++ src/BreCalClient/Properties/Settings.settings | 6 + .../Resources/Resources.Designer.cs | 37 +++++ src/BreCalClient/Resources/Resources.de.resx | 9 ++ src/BreCalClient/Resources/Resources.resx | 12 ++ src/BreCalClient/Resources/bell3.png | Bin 0 -> 1522 bytes 14 files changed, 322 insertions(+), 58 deletions(-) create mode 100644 src/BreCalClient/NotificationDialog.xaml create mode 100644 src/BreCalClient/NotificationDialog.xaml.cs create mode 100644 src/BreCalClient/Resources/bell3.png diff --git a/src/BreCalClient/App.config b/src/BreCalClient/App.config index 335a433..31f6f0a 100644 --- a/src/BreCalClient/App.config +++ b/src/BreCalClient/App.config @@ -86,6 +86,12 @@ + + 0 + + + 0 + \ No newline at end of file diff --git a/src/BreCalClient/AppNotification.cs b/src/BreCalClient/AppNotification.cs index 7a0f075..ed66f31 100644 --- a/src/BreCalClient/AppNotification.cs +++ b/src/BreCalClient/AppNotification.cs @@ -3,34 +3,70 @@ // using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Collections.ObjectModel; using ToastNotifications.Core; +using BreCalClient.misc.Model; namespace BreCalClient { internal class AppNotification { - private static Dictionary _notifications = new (); + private static Dictionary _notifications = new(); private readonly int _id; - + private static ObservableCollection _notificationsCollection = new(); public AppNotification(int id) { _id = id; } + #region Properties + public int Id { get { return _id; } } + public string? NotificationType + { + get; private set; + } + + public string? NotificationDate + { + get; private set; + } + + public string? Ship + { + get; private set; + } + + public string? ShipcallType + { + get; private set; + } + + public string? Berth + { + get; private set; + } + + public string? ETA + { + get; private set; + } + + public static ObservableCollection AppNotifications { get { return _notificationsCollection; } } + + #endregion + #region internal statics internal static void LoadFromSettings() { _notifications.Clear(); - // preload notifications that have been processed + // load notification ids that have been processed foreach (string? notification_id in Properties.Settings.Default.Notifications) { if(Int32.TryParse(notification_id, out int result)) @@ -47,61 +83,61 @@ namespace BreCalClient 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)) + if (notification.ParticipantId.HasValue && notification.ParticipantId.Value != App.Participant.Id) // not meant for us + continue; + + if (!currentShipcalls.ContainsKey(notification.ShipcallId)) // not one of our shipcalls (maybe for another port or filtered) + continue; + + if (!_notificationsCollection.Where(x => x.Id == notification.Id).Any()) { - _notifications.Add(notification.Id, new AppNotification(notification.Id)); + AppNotification ap = new(notification.Id); + ap.NotificationType = notification.Type.ToString(); + ap.Ship = currentShipcalls[notification.ShipcallId]?.Ship?.Name; + ap.ShipcallType = currentShipcalls[notification.ShipcallId]?.Shipcall?.Type.ToString(); + ap.ETA = currentShipcalls[notification.ShipcallId]?.GetETAETD(true); + Times? agencyTimes = currentShipcalls[notification.ShipcallId]?.GetTimesForParticipantType(Extensions.ParticipantType.AGENCY); + ap.Berth = currentShipcalls[notification.ShipcallId]?.GetBerthText(agencyTimes); - // filter if the notification concerns us - if(notification.ParticipantId != null) + System.Diagnostics.Trace.WriteLine($"Notification {notification.Id} Type {notification.Type}"); + MessageOptions options = new(); + options.FontSize = 14; + options.ShowCloseButton = true; + + switch(notification.Type) // TODO: Set toast color and icon { - if(App.Participant.Id == notification.ParticipantId) - { - result = true; - } - } - else - { - // find out if this notification concerns us - if(currentShipcalls.ContainsKey(notification.ShipcallId)) - { - result = true; - } + 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; } - - if(result) + + _notificationsCollection.Add(ap); + + string toastText = $"{ap.Ship} ({ap.ShipcallType}) - {ap.ETA} - {ap.Berth}"; + + if (!_notifications.ContainsKey(notification.Id)) { - 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)}"; + _notifications.Add(notification.Id, ap); App.Current.Dispatcher.Invoke(() => { vm.ShowInformation(toastText, options); }); - } - + } } } return result; diff --git a/src/BreCalClient/BreCalClient.csproj b/src/BreCalClient/BreCalClient.csproj index 5ad9573..cd0a12e 100644 --- a/src/BreCalClient/BreCalClient.csproj +++ b/src/BreCalClient/BreCalClient.csproj @@ -26,6 +26,7 @@ + @@ -84,6 +85,7 @@ + diff --git a/src/BreCalClient/HistoryDialog.xaml.cs b/src/BreCalClient/HistoryDialog.xaml.cs index ce6cb2b..f827de0 100644 --- a/src/BreCalClient/HistoryDialog.xaml.cs +++ b/src/BreCalClient/HistoryDialog.xaml.cs @@ -1,5 +1,5 @@ // Copyright (c) 2024- schick Informatik -// Description: Window to show (complete) list of current shipcall histories +// Description: // using BreCalClient.misc.Api; diff --git a/src/BreCalClient/MainWindow.xaml b/src/BreCalClient/MainWindow.xaml index dd169f8..f7f14eb 100644 --- a/src/BreCalClient/MainWindow.xaml +++ b/src/BreCalClient/MainWindow.xaml @@ -124,6 +124,8 @@ + + @@ -157,19 +159,25 @@ + + + + - - + + - + - - + + diff --git a/src/BreCalClient/MainWindow.xaml.cs b/src/BreCalClient/MainWindow.xaml.cs index 23e109a..be19337 100644 --- a/src/BreCalClient/MainWindow.xaml.cs +++ b/src/BreCalClient/MainWindow.xaml.cs @@ -71,6 +71,7 @@ namespace BreCalClient // private bool _filterChanged = false; // private bool _sequenceChanged = false; private HistoryDialog? _historyDialog; + private NotificationDialog? _notificationDialog; #endregion @@ -453,6 +454,21 @@ namespace BreCalClient } } + private void buttonNotifications_Click(object sender, RoutedEventArgs e) + { + if (_notificationDialog == null) + { + _notificationDialog = new NotificationDialog(); + _notificationDialog.AppNotifications = AppNotification.AppNotifications; + _notificationDialog.Closed += (sender, e) => { this._notificationDialog = null; }; + _notificationDialog.Show(); + } + else + { + _notificationDialog.Activate(); + } + } + private void buttonManualRefresh_Click(object sender, RoutedEventArgs e) { _refreshImmediately = true; // set flag to avoid timer loop termination @@ -649,8 +665,11 @@ namespace BreCalClient while (true) { Thread.Sleep(CHECK_NOTIFICATIONS_INTERVAL_SECONDS * 1000); - List notifications = await _staticApi.NotificationsGetAsync(); - AppNotification.UpdateNotifications(notifications, _allShipcallsDict, _vm); + if (_loginResult?.NotifyPopup ?? false) + { + List notifications = await _staticApi.NotificationsGetAsync(); + AppNotification.UpdateNotifications(notifications, _allShipcallsDict, _vm); + } } } diff --git a/src/BreCalClient/NotificationDialog.xaml b/src/BreCalClient/NotificationDialog.xaml new file mode 100644 index 0000000..799e732 --- /dev/null +++ b/src/BreCalClient/NotificationDialog.xaml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BreCalClient/AppNotificationPart.xaml.cs b/src/BreCalClient/AppNotificationPart.xaml.cs new file mode 100644 index 0000000..b717fd7 --- /dev/null +++ b/src/BreCalClient/AppNotificationPart.xaml.cs @@ -0,0 +1,51 @@ +using BreCalClient.misc.Model; +using System.Windows; +using System.Windows.Media; +using ToastNotifications.Core; + +namespace BreCalClient +{ + /// + /// Interaction logic for NotificationPart.xaml + /// + public partial class AppNotificationPart : NotificationDisplayPart + { + public AppNotificationPart(AppNotificationMessage appNotification) + { + InitializeComponent(); + Bind(appNotification); + + if (appNotification.Options.Tag is AppNotification ap) + { + switch (ap.NotificationType) + { + case "TimeConflict": + this.ContentWrapper.Background = Brushes.Red; + break; + case "TimeConflictResolved": + this.ContentWrapper.Background = Brushes.Green; + break; + case "Assignment": + this.ContentWrapper.Background = Brushes.Blue; + break; + case "Next24h": + this.ContentWrapper.Background = Brushes.DarkOrange; + break; + case "Unassigned": + this.ContentWrapper.Background = Brushes.Gray; + break; + default: + break; + + } + } + + } + + private void OnClose(object sender, RoutedEventArgs e) + { + Notification.Close(); + } + + } +} diff --git a/src/BreCalClient/NotificationDialog.xaml b/src/BreCalClient/NotificationDialog.xaml index 799e732..6fab214 100644 --- a/src/BreCalClient/NotificationDialog.xaml +++ b/src/BreCalClient/NotificationDialog.xaml @@ -13,7 +13,7 @@ -