Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
UiService.cs
1//#define PROFILING
2
3using IdApp.Cv;
10using SkiaSharp;
11using Svg.Skia;
12using System.Collections.Concurrent;
13using System.Collections.ObjectModel;
14using System.Diagnostics;
15using System.Diagnostics.CodeAnalysis;
17using Waher.Events;
19
20#if PROFILING
22#endif
23
25{
27 [Singleton]
29 {
30 private readonly Dictionary<string, NavigationArgs> navigationArgsMap = [];
31 private readonly ConcurrentQueue<UiTask> taskQueue = new();
32 private NavigationArgs? latestArguments = null;
33 private bool isExecutingUiTasks = false;
34 private bool isNavigating = false;
35
39 public UiService()
40 {
41 }
42
43 #region UI-tasks
44
45 private void AddTask(UiTask Task)
46 {
47 this.taskQueue.Enqueue(Task);
48
49 if (!this.isExecutingUiTasks)
50 {
51 this.isExecutingUiTasks = true;
52
53 MainThread.BeginInvokeOnMainThread(async () =>
54 {
55 await this.ProcessAllTasks();
56 });
57 }
58 }
59
60 private async Task ProcessAllTasks()
61 {
62 try
63 {
64 do
65 {
66 if (this.taskQueue.TryDequeue(out UiTask? Task))
67 await Task.Execute();
68 }
69 while (!this.taskQueue.IsEmpty);
70 }
71 finally
72 {
73 this.isExecutingUiTasks = false;
74 }
75 }
76
77 #endregion
78
79 #region DisplayAlert
80
82 public Task<bool> DisplayAlert(string Title, string Message, string? Accept = null, string? Cancel = null)
83 {
84 DisplayAlert Task = new(Title, Message, Accept, Cancel);
85 this.AddTask(Task);
86 return Task.CompletionSource.Task;
87 }
88
90 public Task DisplayException(Exception Exception, string? Title = null)
91 {
92 Exception = Log.UnnestException(Exception);
93
94 return this.DisplayAlert(
95 Title ?? ServiceRef.Localizer[nameof(AppResources.SomethingWentWrong)], GetUserFriendlyMessage(Exception),
97 }
98
99 private static string GetUserFriendlyMessage(Exception? Exception)
100 {
101 if (Exception is null)
103
104 Exception? Current = Exception;
105
106 while (Current is not null)
107 {
108 if (Current is MissingNetworkException)
110
111 if (Current is TimeoutException)
113
114 if (Current is TaskCanceledException || Current is OperationCanceledException)
116
117 if (Current is RecipientUnavailableException)
119
120 if (Current is NotAuthorizedException || Current is UnauthorizedAccessException)
122
123 if (Current is NotAllowedException)
125
126 if (Current is InternalServerErrorException)
128
129 Current = Current.InnerException;
130 }
131
133 }
134
135 #endregion
136
137 #region DisplayPrompt
138
140 public Task<string?> DisplayPrompt(string Title, string Message, string? Accept = null, string? Cancel = null)
141 {
142 DisplayPrompt Task = new(Title, Message, Accept, Cancel);
143 this.AddTask(Task);
144 return Task.CompletionSource.Task;
145 }
146
147 #endregion
148
149 #region Screen shots
150
155 public async Task<ImageSource?> TakeBlurredScreenshotAsync()
156 {
157 try
158 {
159#if PROFILING
160 Profiler Profiler = new("Blur", ProfilerThreadType.Sequential);
161
162 Profiler.Start();
163 Profiler.NewState("Capture");
164#endif
165 IScreenshotResult? Screen = await Screenshot.CaptureAsync();
166 if (Screen is null)
167 return null;
168
169#if PROFILING
170 Profiler.NewState("PNG");
171#endif
172 //Read screenshot
173 using Stream PngStream = await Screen.OpenReadAsync(ScreenshotFormat.Png, 20);
174
175#if PROFILING
176 Profiler.NewState("SKBitmap");
177#endif
178 // Original SKBitmap from PNG stream
179 SKBitmap OriginalBitmap = SKBitmap.FromImage(SKImage.FromEncodedData(PngStream));
180
181#if PROFILING
182 Profiler.NewState("Scale");
183#endif
184 // Desired width and height for the downscaled image
185 int DesiredWidth = OriginalBitmap.Width / 4; //Reduce the width by a quarter
186 int DesiredHeight = OriginalBitmap.Height / 4; //Reduce the height by a quarter
187
188 // Create an SKImageInfo with the desired width, height, and color type of the original
189 SKImageInfo ResizedInfo = new(DesiredWidth, DesiredHeight, SKColorType.Gray8);
190
191 // Create a new SKBitmap for the downscaled image
192 SKBitmap ResizedBitmap = OriginalBitmap.Resize(ResizedInfo, SKFilterQuality.Medium);
193
194#if PROFILING
195 Profiler.NewState("Prepare");
196#endif
197 //Blur image
198 IMatrix RezisedMatrix = Bitmaps.FromBitmap(ResizedBitmap);
199 IMatrix GreyChannelMatrix = RezisedMatrix.GrayScale();
200
201#if PROFILING
202 Profiler.NewState("Blur 5x5");
203#endif
204 IMatrix NewMatrix = IdApp.Cv.Transformations.Convolutions.ConvolutionOperations.Blur(GreyChannelMatrix, 5);
205
206#if PROFILING
207 Profiler.NewState("Blur2");
208#endif
210
211#if PROFILING
212 Profiler.NewState("Encode");
213#endif
214 // Continue with the blurring and encoding to PNG as before
215 byte[] Blurred = Bitmaps.EncodeAsPng(NewMatrix);
216 ImageSource BlurredScreen = ImageSource.FromStream(() => new MemoryStream(Blurred));
217
218#if PROFILING
219 Profiler.Stop();
220
221 string TimingUml = Profiler.ExportPlantUml(TimeUnit.MilliSeconds);
222
223 await App.SendAlert("```uml\r\n" + TimingUml + "\r\n```", "text/markdown");
224#endif
225 return BlurredScreen;
226 }
227 catch (Exception ex)
228 {
229 ServiceRef.LogService.LogException(ex);
230 return null;
231 }
232 }
233
238 public async Task<ImageSource?> TakeScreenshotAsync()
239 {
240 try
241 {
242 IScreenshotResult? Result = await Screenshot.CaptureAsync();
243 if (Result is null)
244 return null;
245
246 // Read the stream into a memory stream or byte array
247 using Stream Stream = await Result.OpenReadAsync();
248 MemoryStream MemoryStream = new();
249 await Stream.CopyToAsync(MemoryStream);
250 byte[] Bytes = MemoryStream.ToArray();
251
252 // Return a new MemoryStream based on the byte array for each invocation
253 return ImageSource.FromStream(() => new MemoryStream(Bytes));
254 }
255 catch (Exception ex)
256 {
257 ServiceRef.LogService.LogException(ex);
258 return null;
259 }
260 }
261
262 #endregion
263
264 #region Navigation
265
266
267
269 public Page CurrentPage => Shell.Current.CurrentPage;
270
272 public override Task Load(bool IsResuming, CancellationToken CancellationToken)
273 {
274 if (this.BeginLoad(IsResuming, CancellationToken))
275 {
276 try
277 {
278 Application? Application = Application.Current;
279
280 if (Application is not null)
281 {
282 Application.PropertyChanging += this.OnApplicationPropertyChanging;
283 Application.PropertyChanged += this.OnApplicationPropertyChanged;
284 }
285
286 this.SubscribeToShellNavigatingIfNecessary(Application);
287
288 this.EndLoad(true);
289 }
290 catch (Exception ex)
291 {
292 ServiceRef.LogService.LogException(ex);
293 this.EndLoad(false);
294 }
295 }
296
297
298 return Task.CompletedTask;
299 }
300
302 public override Task Unload()
303 {
304 if (this.BeginUnload())
305 {
306 try
307 {
308 Application? Application = Application.Current;
309 if (Application is not null)
310 {
311 this.UnsubscribeFromShellNavigatingIfNecessary(Application);
312 Application.PropertyChanged -= this.OnApplicationPropertyChanged;
313 Application.PropertyChanging -= this.OnApplicationPropertyChanging;
314 }
315 }
316 catch (Exception ex)
317 {
318 ServiceRef.LogService.LogException(ex);
319 }
320
321 this.EndUnload();
322 }
323
324 return Task.CompletedTask;
325 }
326
327
329 public Task GoToAsync(string Route, BackMethod BackMethod = BackMethod.Inherited, string? UniqueId = null)
330 {
331 // No args navigation will create a default navigation arguments
332 return this.GoToAsync<NavigationArgs>(Route, null, BackMethod, UniqueId);
333 }
334
336 public async Task GoToAsync<TArgs>(string Route, TArgs? Args, BackMethod BackMethod = BackMethod.Inherited, string? UniqueId = null) where TArgs : NavigationArgs, new()
337 {
338 await MainThread.InvokeOnMainThreadAsync(async () =>
339 {
341
342 // Get the parent's navigation arguments
343 NavigationArgs? ParentArgs = this.GetCurrentNavigationArgs();
344
345 // Create a default navigation arguments if Args are null
346 NavigationArgs NavigationArgs = Args ?? new();
347
348 NavigationArgs.SetBackArguments(ParentArgs, BackMethod, UniqueId);
349 this.PushArgs(Route, NavigationArgs);
350
351 if (!string.IsNullOrEmpty(UniqueId))
352 Route += "?UniqueId=" + UniqueId;
353
354 try
355 {
356 this.isNavigating = true;
357 await Shell.Current.GoToAsync(Route, NavigationArgs.Animated);
358 }
359 catch (Exception ex)
360 {
361 ex = Log.UnnestException(ex);
362 ServiceRef.LogService.LogException(ex);
363 string ExtraInfo = Environment.NewLine + ex.Message;
364
365 await ServiceRef.UiService.DisplayAlert(
367 ServiceRef.Localizer[nameof(AppResources.FailedToNavigateToPage), Route, ExtraInfo]);
368 }
369 finally
370 {
371 this.isNavigating = false;
373 }
374 });
375 }
376
378 public async Task GoBackAsync(bool Animate = true)
379 {
380 try
381 {
382 NavigationArgs? NavigationArgs = this.GetCurrentNavigationArgs();
383
384 if (NavigationArgs is not null) // the main view?
385 {
386 string BackRoute = NavigationArgs.GetBackRoute();
387
388 this.isNavigating = true;
389 await Shell.Current.GoToAsync(BackRoute, Animate);
390 }
391 else
392 {
393 ShellNavigationState State = Shell.Current.CurrentState;
394 if (Uri.TryCreate(State.Location, "..", out Uri? BackLocation))
395 await Shell.Current.GoToAsync(BackLocation);
396 else
397 await Shell.Current.GoToAsync(Constants.Pages.MainPage);
398 }
399 }
400 catch (Exception ex)
401 {
402 ServiceRef.LogService.LogException(ex);
403
404 await ServiceRef.UiService.DisplayAlert(
407 }
408 finally
409 {
410 this.isNavigating = false;
411 }
412 }
413
419 public TArgs? PopLatestArgs<TArgs>()
420 where TArgs : NavigationArgs, new()
421 {
422 if (this.latestArguments is TArgs Result)
423 {
424 this.latestArguments = null;
425 return Result;
426 }
427 else
428 return null;
429 }
430
436 public TArgs? TryGetArgs<TArgs>(string? UniqueId = null)
437 where TArgs : NavigationArgs, new()
438 {
439 if (this.TryGetArgs(out TArgs? Result, UniqueId))
440 return Result;
441 else
442 return null;
443 }
444
450 public bool TryGetArgs<TArgs>([NotNullWhen(true)] out TArgs? Args, string? UniqueId = null)
451 where TArgs : NavigationArgs, new()
452 {
454
455 if (this.CurrentPage is Page Page)
456 {
457 NavigationArgs = this.TryGetArgs(Page.GetType().Name, UniqueId);
458 string Route = Routing.GetRoute(Page);
459 NavigationArgs ??= this.TryGetArgs(Route, UniqueId);
460 /*
461 if ((NavigationArgs is null) && (UniqueId is null) &&
462 (Page is BaseContentPage BasePage) && (BasePage.UniqueId is not null))
463 {
464 return this.TryGetArgs(out Args, BasePage.UniqueId);
465 }
466 */
467 }
468
469 if (NavigationArgs is TArgs TArgsArgs)
470 Args = TArgsArgs;
471 else
472 Args = null;
473
474 return (Args is not null);
475 }
476
477 private NavigationArgs? GetCurrentNavigationArgs()
478 {
479 this.TryGetArgs(out NavigationArgs? Args);
480 return Args;
481 }
482
483 private void OnApplicationPropertyChanged(object? Sender, System.ComponentModel.PropertyChangedEventArgs Args)
484 {
485 if (Args.PropertyName == nameof(Application.MainPage))
486 this.SubscribeToShellNavigatingIfNecessary((Application?)Sender);
487 }
488
489 private void OnApplicationPropertyChanging(object? Sender, PropertyChangingEventArgs Args)
490 {
491 if (Args.PropertyName == nameof(Application.MainPage))
492 this.UnsubscribeFromShellNavigatingIfNecessary((Application?)Sender);
493 }
494
495 private void SubscribeToShellNavigatingIfNecessary(Application? Application)
496 {
497 if (Application?.MainPage is Shell Shell)
498 Shell.Navigating += this.Shell_Navigating;
499 }
500
501 private void UnsubscribeFromShellNavigatingIfNecessary(Application? Application)
502 {
503 if (Application?.MainPage is Shell Shell)
504 Shell.Navigating -= this.Shell_Navigating;
505 }
506
507 private void Shell_Navigating(object? Sender, ShellNavigatingEventArgs e)
508 {
509 try
510 {
511 if ((e.Source == ShellNavigationSource.Pop) && e.CanCancel && !this.isNavigating)
512 {
513 e.Cancel();
514
515 MainThread.BeginInvokeOnMainThread(async () =>
516 {
517 await this.GoBackAsync();
518 });
519 }
520 }
521 catch (Exception ex)
522 {
523 ServiceRef.LogService.LogException(ex);
524 }
525 }
526
527 private static bool TryGetPageName(string Route, [NotNullWhen(true)] out string? PageName)
528 {
529 PageName = null;
530
531 if (!string.IsNullOrWhiteSpace(Route))
532 {
533 PageName = Route.TrimStart('.', '/');
534 return !string.IsNullOrWhiteSpace(PageName);
535 }
536
537 return false;
538 }
539
540 private void PushArgs(string Route, NavigationArgs Args)
541 {
542 this.latestArguments = Args;
543
544 if (TryGetPageName(Route, out string? PageName))
545 {
546 if (Args is not null)
547 {
548 string? UniqueId = Args.UniqueId;
549
550 if (!string.IsNullOrEmpty(UniqueId))
551 PageName += "?UniqueId=" + UniqueId;
552
553 this.navigationArgsMap[PageName] = Args;
554 }
555 else
556 this.navigationArgsMap.Remove(PageName);
557 }
558 }
559
560 private NavigationArgs? TryGetArgs(string Route, string? UniqueId)
561 {
562 if (!string.IsNullOrEmpty(UniqueId))
563 Route += "?UniqueId=" + UniqueId;
564
565 if (TryGetPageName(Route, out string? PageName) &&
566 this.navigationArgsMap.TryGetValue(PageName, out NavigationArgs? Args))
567 {
568 return Args;
569 }
570
571 return null;
572 }
573
574 #endregion
575 #region Image
577 public async Task<ImageSource?> ConvertSvgUriToImageSource(string svgUri)
578 {
579 try
580 {
581 //Fetch image
582 using HttpClient HttpClient = new();
583 using HttpResponseMessage Response = await HttpClient.GetAsync(svgUri);
584 if (!Response.IsSuccessStatusCode)
585 return null;
586
587 // Load SVG image
588 byte[] ContentBytes = await Response.Content.ReadAsByteArrayAsync();
589 SKSvg Svg = new();
590 using (MemoryStream Stream = new(ContentBytes))
591 {
592 Svg.Load(Stream);
593 }
594
595 //Check that the svg was parsed correct
596 if (Svg.Picture is null)
597 return null;
598
599 using (MemoryStream Stream = new())
600 {
601 if (Svg.Picture.ToImage(Stream, SKColor.Parse("#00FFFFFF"), SKEncodedImageFormat.Png, 100, 1, 1, SKColorType.Rgba8888, SKAlphaType.Premul, SKColorSpace.CreateSrgb()))
602 return ImageSource.FromStream(() => new MemoryStream(Stream.ToArray()));
603 return null;
604 }
605 }
606 catch (Exception ex)
607 {
608 ServiceRef.LogService.LogException(ex);
609 return null;
610 }
611
612 }
613 #endregion
614 }
615}
Static methods managing conversion to and from bitmap representations.
Definition: Bitmaps.cs:12
static byte[] EncodeAsPng(IMatrix M)
Encodes an image in a matrix using PNG.
Definition: Bitmaps.cs:318
static IMatrix FromBitmap(SKBitmap Bmp)
Craetes a matrix from a bitmap.
Definition: Bitmaps.cs:66
Static class for Convolution Operations, implemented as extensions.
Definition: Blur.cs:9
static Matrix< float > Blur(this Matrix< float > M)
Blurs an image.
Definition: Blur.cs:15
Represents an instance of the Neuro-Access app.
Definition: App.xaml.cs:125
Absolute paths to important pages.
Definition: Constants.cs:875
const string MainPage
Path to main page.
Definition: Constants.cs:879
A set of never changing property constants and helpful values.
Definition: Constants.cs:24
A strongly-typed resource class, for looking up localized strings, etc.
static string RequestTimedOut
Looks up a localized string similar to The request timed out.
static string ThereIsNoNetwork
Looks up a localized string similar to There is no network.
static string NotAuthorized
Looks up a localized string similar to You're not authorized to perform this action....
static string RequestWasCancelled
Looks up a localized string similar to The request was cancelled.
static string FailedToNavigateToPage
Looks up a localized string similar to Failed to navigate to page {0} {1}.
static string ActionNotAllowed
Looks up a localized string similar to This action isn't allowed..
static string Ok
Looks up a localized string similar to OK.
static string FailedToClosePage
Looks up a localized string similar to Failed to close page.
static string SomethingWentWrong
Looks up a localized string similar to Something went wrong.
static string ServiceUnavailable
Looks up a localized string similar to The service is currently unavailable. Please try again later....
static string ErrorTitle
Looks up a localized string similar to An error has occurred.
bool BeginLoad(bool IsResuming, CancellationToken CancellationToken)
Sets the IsLoading flag if the service isn't already loading.
void EndLoad(bool isLoaded)
Sets the IsLoading and IsLoaded flags and fires an event representing the current load state of the s...
bool IsResuming
If App is resuming service.
bool BeginUnload()
Sets the IsLoading flag if the service isn't already unloading.
void EndUnload()
Sets the IsLoading and IsLoaded flags and fires an event representing the current load state of the s...
Base class that references services in the app.
Definition: ServiceRef.cs:43
static ILogService LogService
Log service.
Definition: ServiceRef.cs:214
static IUiService UiService
Service serializing and managing UI-related tasks.
Definition: ServiceRef.cs:130
static IReportingStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:370
static IPlatformSpecific PlatformSpecific
Localization service
Definition: ServiceRef.cs:383
An base class holding page specific navigation parameters.
string? UniqueId
An unique view identifier used to search the args of similar view types.
readonly TaskCompletionSource< bool > NavigationCompletionSource
The completion source for the navigation task. Will return true when the navigation and transitions a...
void SetBackArguments(NavigationArgs? ParentArgs, BackMethod BackMethod=BackMethod.Inherited, string? UniqueId=null)
Sets the reference to the main parent's NavigationArgs.
string GetBackRoute()
Get the route used for the IUiService.GoBackAsync method.
bool Animated
Is the navigation animated
Abstract base class for UI tasks.
Definition: UiTask.cs:7
abstract Task Execute()
Executes the task.
UiService()
Creates a new instance of the UiService class.
Definition: UiService.cs:39
override Task Unload()
Unloads the specified service.
Definition: UiService.cs:302
async Task GoToAsync< TArgs >(string Route, TArgs? Args, BackMethod BackMethod=BackMethod.Inherited, string? UniqueId=null)
Navigates the AppShell to the specified route, with page arguments to match.
Definition: UiService.cs:336
TArgs? TryGetArgs< TArgs >(string? UniqueId=null)
Returns the page's arguments from the (one-level) deep navigation stack.
Definition: UiService.cs:436
Task< bool > DisplayAlert(string Title, string Message, string? Accept=null, string? Cancel=null)
Displays an alert/message box to the user. If Accept or Cancel was pressed
Definition: UiService.cs:82
TArgs? PopLatestArgs< TArgs >()
Pops the latests navigation arguments. Can only be used once to get the navigation arguments....
Definition: UiService.cs:419
Task DisplayException(Exception Exception, string? Title=null)
Displays an alert/message box to the user.
Definition: UiService.cs:90
override Task Load(bool IsResuming, CancellationToken CancellationToken)
Loads the specified service.
Definition: UiService.cs:272
Task GoToAsync(string Route, BackMethod BackMethod=BackMethod.Inherited, string? UniqueId=null)
Navigates the AppShell to the specified route, with page arguments to match.
Definition: UiService.cs:329
async Task< ImageSource?> ConvertSvgUriToImageSource(string svgUri)
Fetches a SVG and converts it to a PNG image source. An image Source representing the SVG file or nul...
Definition: UiService.cs:577
async Task GoBackAsync(bool Animate=true)
Returns to the previous page/route.
Definition: UiService.cs:378
async Task< ImageSource?> TakeScreenshotAsync()
Takes a screen-shot.
Definition: UiService.cs:238
async Task< ImageSource?> TakeBlurredScreenshotAsync()
Takes a blurred screen shot
Definition: UiService.cs:155
Task< string?> DisplayPrompt(string Title, string Message, string? Accept=null, string? Cancel=null)
Prompts the user for some input. User input
Definition: UiService.cs:140
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:828
The server has experienced a misconfiguration or other internal error that prevents it from processin...
The recipient or server does not allow any entity to perform the action (e.g., sending to entities at...
The sender needs to provide credentials before being allowed to perform the action,...
The intended recipient is temporarily unavailable, undergoing maintenance, etc.; the associated error...
Class that keeps track of events and timing.
Definition: Profiler.cs:68
void Stop()
Stops measuring time.
Definition: Profiler.cs:227
void NewState(string State)
Main Thread changes state.
Definition: Profiler.cs:267
string ExportPlantUml(TimeUnit TimeUnit)
Exports events to PlantUML.
Definition: Profiler.cs:530
void Start()
Starts measuring time.
Definition: Profiler.cs:217
Interface for matrices.
Definition: IMatrix.cs:9
void HideKeyboard()
Force hide the keyboard
Service serializing and managing UI-related tasks.
Definition: IUiService.cs:12
Definition: Abs.cs:2
Definition: Abs.cs:2
BackMethod
Navigation Back Method
Definition: BackMethod.cs:7
TimeUnit
Options for presenting time in reports.
Definition: Profiler.cs:17
ProfilerThreadType
Type of profiler thread.