Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
CustomShell.cs
1using Microsoft.Maui.Controls.PlatformConfiguration;
2using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
4using NeuroAccessMaui.UI.Pages.Startup; // for LoadingPage
7using ControlsVisualElement = Microsoft.Maui.Controls.VisualElement;
8using Microsoft.Maui.Devices;
9using CommunityToolkit.Maui.Core;
10using CommunityToolkit.Maui.Behaviors;
12
14{
18 public class CustomShell : ContentPage, IShellPresenter
19 {
20 private readonly Grid layout;
21 private readonly ContentView topBar;
22 private readonly ContentView navBar;
23 private readonly ContentView contentHostA;
24 private readonly ContentView contentHostB;
25 private bool isSlotAActive = true;
26 private readonly Grid popupOverlay;
27 private readonly BoxView popupBackground;
28 private readonly Grid popupHost;
29 private PopupVisualState? currentPopupState;
30 private readonly Grid toastLayer;
31 private readonly Thickness toastBasePadding = new Thickness(16, 32, 16, 16);
32 private readonly IKeyboardInsetsService keyboardInsetsService;
33 private readonly IAnimationCoordinator animationCoordinator;
34 private View? activeToast;
35 private ToastPlacement activeToastPlacement = ToastPlacement.Top;
36 private ContentView? currentScreen;
37 private double currentKeyboardInset;
38 private bool isKeyboardVisible;
39 public event EventHandler? PopupBackgroundTapped;
40 public event EventHandler? PopupBackRequested;
41
45 public CustomShell(LoadingPage loadingPage, IKeyboardInsetsService keyboardInsetsService, IAnimationCoordinator animationCoordinator)
46 {
47 ArgumentNullException.ThrowIfNull(keyboardInsetsService);
48 ArgumentNullException.ThrowIfNull(animationCoordinator);
49 this.keyboardInsetsService = keyboardInsetsService;
50 this.animationCoordinator = animationCoordinator;
51 this.currentKeyboardInset = this.keyboardInsetsService.KeyboardHeight;
52 this.isKeyboardVisible = this.keyboardInsetsService.IsKeyboardVisible;
53 this.keyboardInsetsService.KeyboardInsetChanged += this.OnKeyboardInsetChanged;
54
55 this.layout = new Grid
56 {
57 RowDefinitions =
58 {
59 new RowDefinition { Height = GridLength.Auto },
60 new RowDefinition { Height = new GridLength(1, GridUnitType.Star) },
61 new RowDefinition { Height = GridLength.Auto }
62 }
63 };
64 this.layout.SetDynamicResource(Grid.BackgroundColorProperty, "SurfaceBackgroundWL");
65
66 this.topBar = new ContentView { IsVisible = false };
67 this.navBar = new ContentView { IsVisible = false };
68 this.contentHostA = new ContentView();
69 this.contentHostB = new ContentView();
70 this.contentHostA.IsVisible = true;
71 this.contentHostB.IsVisible = false;
72
73 this.popupBackground = new BoxView { Opacity = 0, Color = Colors.Black };
74 this.popupOverlay = new Grid { IsVisible = false, InputTransparent = false };
75 this.popupOverlay.Add(this.popupBackground);
76 this.popupHost = new Grid { VerticalOptions = LayoutOptions.Fill, HorizontalOptions = LayoutOptions.Fill };
77 this.popupOverlay.Add(this.popupHost);
78 this.popupOverlay.IgnoreSafeArea = true;
79
80 TapGestureRecognizer popupBackgroundTap = new();
81 popupBackgroundTap.Tapped += this.OnPopupBackgroundTapped;
82 this.popupBackground.GestureRecognizers.Add(popupBackgroundTap);
83
84 this.toastLayer = new Grid { IsVisible = false, InputTransparent = false, Padding = this.toastBasePadding, VerticalOptions = LayoutOptions.Fill, HorizontalOptions = LayoutOptions.Fill };
85 this.toastLayer.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
86 this.toastLayer.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
87 this.toastLayer.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
88 this.toastLayer.IgnoreSafeArea = true;
89
90
91 this.layout.Add(this.topBar, 0, 0);
92 this.layout.Add(this.contentHostA, 0, 1);
93 this.layout.Add(this.contentHostB, 0, 1);
94 this.layout.Add(this.navBar, 0, 2);
95 this.layout.Add(this.popupOverlay);
96 Grid.SetRowSpan(this.popupOverlay, 3);
97 this.layout.Add(this.toastLayer);
98 Grid.SetRowSpan(this.toastLayer, 3);
99
100 this.layout.IgnoreSafeArea = true;
101 this.On<iOS>().SetUseSafeArea(false);
102 this.HideSoftInputOnTapped = true;
103
104 this.Content = this.layout;
105 this.Padding = 0;
106 this.SetDynamicResource(BackgroundColorProperty, "SurfaceBackgroundWL");
107
108 LoadingPage loadingPageInstance = ServiceHelper.GetService<LoadingPage>();
109 this.contentHostA.Content = loadingPageInstance;
110 this.contentHostB.Content = null;
111 this.currentScreen = loadingPageInstance;
112 Thickness initialSafeArea = this.ApplyInsetsToHost(this.contentHostA, loadingPageInstance, false, false);
113 this.UpdateOverlayInsets(initialSafeArea, false);
114
115 this.Dispatcher.Dispatch(async () =>
116 {
117 await loadingPage.OnInitializeAsync();
118 await loadingPage.OnAppearingAsync();
119 });
120
121#if !WINDOWS
122 AppTheme CurrentTheme = Microsoft.Maui.Controls.Application.Current?.RequestedTheme ?? AppTheme.Light;
123 StatusBarStyle StatusStyle = CurrentTheme == AppTheme.Dark ? StatusBarStyle.LightContent : StatusBarStyle.DarkContent;
124 StatusBarBehavior StatusBehaviour = new() { StatusBarColor = Colors.Transparent, StatusBarStyle = StatusStyle };
125 this.Behaviors.Add(StatusBehaviour);
126
127 if (Microsoft.Maui.Controls.Application.Current is not null)
128 {
129 Microsoft.Maui.Controls.Application.Current.RequestedThemeChanged += (s, e) =>
130 {
131 StatusBarStyle StatusStyle = CurrentTheme == AppTheme.Dark ? StatusBarStyle.LightContent : StatusBarStyle.DarkContent;
132 StatusBehaviour.StatusBarStyle = StatusStyle;
133 };
134 }
135#endif
136 this.SizeChanged += this.OnShellSizeChanged;
137 }
138
139 private void OnPopupBackgroundTapped(object? sender, EventArgs e)
140 {
141 if (this.currentPopupState is null)
142 return;
143 if (!this.currentPopupState.AllowBackgroundTap)
144 return;
145 this.PopupBackgroundTapped?.Invoke(this, EventArgs.Empty);
146 }
147
148 public BaseContentPage? CurrentPage => this.currentScreen as BaseContentPage;
149
150 public async Task ShowScreen(ContentView Screen, TransitionType Transition = TransitionType.Fade)
151 {
152 ContentView ActiveSlot = this.isSlotAActive ? this.contentHostA : this.contentHostB;
153 ContentView InactiveSlot = this.isSlotAActive ? this.contentHostB : this.contentHostA;
154 BaseContentPage? OutgoingPage = ActiveSlot.Content as BaseContentPage;
155
156 InactiveSlot.Content = Screen;
157 InactiveSlot.BindingContext = Screen.BindingContext;
158 InactiveSlot.IsVisible = true;
159 _ = this.ApplyInsetsToHost(InactiveSlot, Screen, false, false);
160 this.UpdateBars(Screen);
161
162 AnimationContextOptions ContextOptions = this.CreateAnimationContextOptions();
163 try
164 {
165 if (Transition == TransitionType.Fade)
166 {
167 await this.animationCoordinator.PlayTransitionAsync(AnimationKeys.Shell.PageCrossFade, InactiveSlot, ActiveSlot, null, ContextOptions);
168 }
169 else if (Transition == TransitionType.SwipeLeft)
170 {
171 await this.animationCoordinator.PlayTransitionAsync(AnimationKeys.Shell.PageSlideLeft, InactiveSlot, ActiveSlot, null, ContextOptions);
172 }
173 else if (Transition == TransitionType.SwipeRight)
174 {
175 await this.animationCoordinator.PlayTransitionAsync(AnimationKeys.Shell.PageSlideRight, InactiveSlot, ActiveSlot, null, ContextOptions);
176 }
177 else
178 {
179 InactiveSlot.Opacity = 1;
180 ActiveSlot.Opacity = 1;
181 }
182 }
183 finally
184 {
185 InactiveSlot.TranslationX = 0;
186 ActiveSlot.TranslationX = 0;
187 InactiveSlot.Opacity = 1;
188 ActiveSlot.Opacity = 1;
189 }
190
191 if (OutgoingPage is not null)
192 {
193 try { await OutgoingPage.OnDisappearingAsync(); }
194 catch (Exception ex) { ServiceRef.LogService.LogException(ex); }
195 }
196
197 ActiveSlot.IsVisible = false;
198 ActiveSlot.Content = null;
199 this.isSlotAActive = !this.isSlotAActive;
200 this.currentScreen = Screen;
201 ContentView NewlyActiveSlot = this.isSlotAActive ? this.contentHostA : this.contentHostB;
202 Thickness SafeAreaThickness = this.ApplyInsetsToHost(NewlyActiveSlot, Screen, false, true);
203 this.UpdateOverlayInsets(SafeAreaThickness, false);
204 }
205
206 public async Task ShowPopup(ContentView popup, PopupTransition transition, PopupVisualState visualState)
207 {
208 ArgumentNullException.ThrowIfNull(popup);
209 ArgumentNullException.ThrowIfNull(visualState);
210
211 bool overlayWasVisible = this.popupOverlay.IsVisible;
212 if (!overlayWasVisible)
213 {
214 this.popupOverlay.IsVisible = true;
215 this.popupBackground.Opacity = 0;
216 }
217
218 this.ApplyOverlayInteractionState(visualState);
219
220 double targetOpacity = visualState.OverlayOpacity;
221 double currentOpacity = this.popupBackground.Opacity;
222 if (!overlayWasVisible)
223 {
224 await this.popupBackground.FadeToAsync(targetOpacity, 150, Easing.CubicOut);
225 }
226 else if (Math.Abs(currentOpacity - targetOpacity) > 0.01)
227 {
228 await this.popupBackground.FadeToAsync(targetOpacity, 120, Easing.CubicOut);
229 }
230
231 if (popup is BasePopupView popupView)
232 {
233 popupView.BackgroundTapped -= this.OnPopupBackgroundTapped;
234 popupView.BackgroundTapped += this.OnPopupBackgroundTapped;
235 }
236
237 this.popupHost.Children.Add(popup);
238 this.NotifyKeyboardAwareTargets(popup);
239 popup.ZIndex = this.popupHost.Children.Count;
240
241 if (transition == PopupTransition.None)
242 {
243 popup.Opacity = 1;
244 popup.Scale = 1;
245 popup.TranslationX = 0;
246 popup.TranslationY = 0;
247 }
248 else
249 {
250 AnimationKey? ShowKey = transition switch
251 {
252 PopupTransition.Fade => AnimationKeys.Shell.PopupShowFade,
253 PopupTransition.Scale => AnimationKeys.Shell.PopupShowScale,
254 PopupTransition.SlideUp => AnimationKeys.Shell.PopupShowSlideUp,
255 _ => null
256 };
257
258 if (ShowKey.HasValue)
259 await this.animationCoordinator.PlayAsync(ShowKey.Value, popup, null, this.CreateAnimationContextOptions());
260 else
261 popup.Opacity = 1;
262 }
263
264 popup.TranslationX = 0;
265 popup.TranslationY = 0;
266 popup.Scale = 1;
267 popup.Opacity = 1;
268
269 this.currentPopupState = visualState;
270 }
271
272 public async Task HidePopup(ContentView popup, PopupTransition transition, PopupVisualState? nextVisualState)
273 {
274 ArgumentNullException.ThrowIfNull(popup);
275
276 if (transition == PopupTransition.None)
277 {
278 popup.Opacity = 0;
279 }
280 else
281 {
282 AnimationKey? HideKey = transition switch
283 {
284 PopupTransition.Fade => AnimationKeys.Shell.PopupHideFade,
285 PopupTransition.Scale => AnimationKeys.Shell.PopupHideScale,
286 PopupTransition.SlideUp => AnimationKeys.Shell.PopupHideSlideUp,
287 _ => null
288 };
289
290 if (HideKey.HasValue)
291 await this.animationCoordinator.PlayAsync(HideKey.Value, popup, null, this.CreateAnimationContextOptions());
292 else
293 popup.Opacity = 0;
294 }
295
296 popup.TranslationX = 0;
297 popup.TranslationY = 0;
298 popup.Scale = 1;
299 popup.Opacity = 0;
300 if (popup is BasePopupView popupView)
301 popupView.BackgroundTapped -= this.OnPopupBackgroundTapped;
302
303 this.popupHost.Children.Remove(popup);
304
305 if (nextVisualState is null)
306 {
307 await this.popupBackground.FadeToAsync(0, 150, Easing.CubicOut);
308 this.popupOverlay.IsVisible = false;
309 this.popupOverlay.InputTransparent = true;
310 this.currentPopupState = null;
311 }
312 else
313 {
314 this.ApplyOverlayInteractionState(nextVisualState);
315 double targetOpacity = nextVisualState.OverlayOpacity;
316 double currentOpacity = this.popupBackground.Opacity;
317 if (!this.popupOverlay.IsVisible)
318 this.popupOverlay.IsVisible = true;
319 if (Math.Abs(currentOpacity - targetOpacity) > 0.01)
320 await this.popupBackground.FadeToAsync(targetOpacity, 120, Easing.CubicOut);
321 this.currentPopupState = nextVisualState;
322 }
323 }
324
325 public async Task ShowToast(View Toast, ToastTransition Transition = ToastTransition.SlideFromTop, ToastPlacement Placement = ToastPlacement.Top)
326 {
327 ArgumentNullException.ThrowIfNull(Toast);
328 if (this.activeToast is not null)
329 this.toastLayer.Children.Remove(this.activeToast);
330 this.activeToast = Toast;
331 this.activeToastPlacement = Placement;
332 int TargetRow = Placement == ToastPlacement.Top ? 0 : 2;
333 Grid.SetRow(Toast, TargetRow);
334 if (!this.toastLayer.Children.Contains(Toast))
335 this.toastLayer.Children.Add(Toast);
336 if (Toast is BindableObject toastBindable)
337 this.NotifyKeyboardAwareTargets(toastBindable);
338 this.toastLayer.IsVisible = true;
339
340 AnimationKey? ShowKey = Transition switch
341 {
342 ToastTransition.Fade => AnimationKeys.Shell.ToastShowFade,
343 ToastTransition.SlideFromBottom => AnimationKeys.Shell.ToastShowSlideBottom,
344 ToastTransition.SlideFromTop when Placement == ToastPlacement.Bottom => AnimationKeys.Shell.ToastShowSlideBottom,
345 ToastTransition.SlideFromTop => AnimationKeys.Shell.ToastShowSlideTop,
346 _ => null
347 };
348
349 if (ShowKey.HasValue)
350 {
351 await this.animationCoordinator.PlayAsync(ShowKey.Value, Toast, null, this.CreateAnimationContextOptions());
352 }
353 else
354 {
355 Toast.Opacity = 1;
356 Toast.TranslationX = 0;
357 Toast.TranslationY = 0;
358 }
359
360 Toast.TranslationX = 0;
361 Toast.TranslationY = 0;
362 Toast.Opacity = 1;
363 }
364
365 public async Task HideToast(ToastTransition Transition = ToastTransition.SlideFromTop)
366 {
367 if (this.activeToast is null)
368 return;
369 View Toast = this.activeToast;
370
371 AnimationKey? HideKey = Transition switch
372 {
373 ToastTransition.Fade => AnimationKeys.Shell.ToastHideFade,
374 ToastTransition.SlideFromBottom => AnimationKeys.Shell.ToastHideSlideBottom,
375 ToastTransition.SlideFromTop when this.activeToastPlacement == ToastPlacement.Bottom => AnimationKeys.Shell.ToastHideSlideBottom,
376 ToastTransition.SlideFromTop => AnimationKeys.Shell.ToastHideSlideTop,
377 _ => null
378 };
379
380 if (HideKey.HasValue)
381 {
382 await this.animationCoordinator.PlayAsync(HideKey.Value, Toast, null, this.CreateAnimationContextOptions());
383 }
384 else
385 {
386 Toast.Opacity = 0;
387 }
388
389 this.toastLayer.Children.Remove(Toast);
390 this.activeToast = null;
391 this.activeToastPlacement = ToastPlacement.Top;
392 if (this.toastLayer.Children.Count == 0)
393 this.toastLayer.IsVisible = false;
394 }
395
396 private AnimationContextOptions CreateAnimationContextOptions()
397 {
398 double? ViewportWidth = this.Width > 0 ? this.Width : null;
399 double? ViewportHeight = this.Height > 0 ? this.Height : null;
400 return new AnimationContextOptions
401 {
402 KeyboardInset = this.currentKeyboardInset,
403 ViewportWidth = ViewportWidth,
404 ViewportHeight = ViewportHeight
405 };
406 }
407
408 private void ApplyOverlayInteractionState(PopupVisualState visualState)
409 {
410 this.popupOverlay.InputTransparent = false;
411 }
412
413 private void OnShellSizeChanged(object? sender, EventArgs e)
414 {
415 if (this.currentScreen is null)
416 return;
417
418 ContentView ActiveSlot = this.isSlotAActive ? this.contentHostA : this.contentHostB;
419 Thickness SafeArea = this.ApplyInsetsToHost(ActiveSlot, this.currentScreen, false, true);
420 this.UpdateOverlayInsets(SafeArea, false);
421 }
422
423 private Thickness ApplyInsetsToHost(ContentView host, BindableObject screen, bool animate, bool notify)
424 {
425 ArgumentNullException.ThrowIfNull(screen);
426 ArgumentNullException.ThrowIfNull(host);
427
428 Thickness SafeAreaThickness = SafeArea.ResolveInsetsFor(screen);
430 double KeyboardInsetForPadding = Mode == KeyboardInsetMode.Automatic ? this.ResolveEffectiveKeyboardInset(SafeAreaThickness) : 0;
431 Thickness TargetPadding = new Thickness(
432 SafeAreaThickness.Left,
433 SafeAreaThickness.Top,
434 SafeAreaThickness.Right,
435 SafeAreaThickness.Bottom + KeyboardInsetForPadding);
436
437 this.SetViewPadding(host, TargetPadding, animate);
438
439 if (notify)
440 this.NotifyKeyboardAwareTargets(screen);
441
442 return SafeAreaThickness;
443 }
444
445 private void UpdateOverlayInsets(Thickness safeArea, bool animate)
446 {
447 double EffectiveKeyboardInset = this.ResolveEffectiveKeyboardInset(safeArea);
448 Thickness OverlayPadding = new Thickness(safeArea.Left, safeArea.Top, safeArea.Right, safeArea.Bottom + EffectiveKeyboardInset);
449 this.SetViewPadding(this.popupOverlay, OverlayPadding, animate);
450
451 Thickness ToastPadding = new Thickness(
452 this.toastBasePadding.Left + safeArea.Left,
453 this.toastBasePadding.Top + safeArea.Top,
454 this.toastBasePadding.Right + safeArea.Right,
455 this.toastBasePadding.Bottom + safeArea.Bottom + EffectiveKeyboardInset);
456 this.SetViewPadding(this.toastLayer, ToastPadding, animate);
457
458 Thickness TopBarPadding = new Thickness(safeArea.Left, safeArea.Top, safeArea.Right, 0);
459 this.SetViewPadding(this.topBar, TopBarPadding, animate);
460
461 Thickness NavBarPadding = new Thickness(safeArea.Left, 0, safeArea.Right, safeArea.Bottom);
462 this.SetViewPadding(this.navBar, NavBarPadding, animate);
463 }
464
465 private double ResolveEffectiveKeyboardInset(Thickness safeArea)
466 {
467 double EffectiveInset = this.currentKeyboardInset;
468
469 // On iOS the reported keyboard height already includes the home-indicator safe area.
470 if (DeviceInfo.Platform == DevicePlatform.iOS)
471 {
472 EffectiveInset -= safeArea.Bottom;
473 }
474
475 if (EffectiveInset < 0)
476 return 0;
477 return EffectiveInset;
478 }
479
480 private void SetViewPadding(ControlsVisualElement view, Thickness targetPadding, bool animate)
481 {
482 if (!this.TryGetPadding(view, out Thickness currentPadding))
483 return;
484
485 if (!animate)
486 {
487 this.AssignPadding(view, targetPadding);
488 return;
489 }
490
491 if (this.AreThicknessClose(currentPadding, targetPadding))
492 {
493 this.AssignPadding(view, targetPadding);
494 return;
495 }
496
497 const uint Duration = 180;
498 Microsoft.Maui.Controls.ViewExtensions.CancelAnimations(view);
499 view.Animate(
500 "KeyboardPadding",
501 progress => this.AssignPadding(view, this.InterpolateThickness(currentPadding, targetPadding, progress)),
502 rate: 16,
503 length: Duration,
504 easing: Easing.CubicInOut,
505 finished: (v, cancelled) => this.AssignPadding(view, targetPadding));
506 }
507
508 private bool TryGetPadding(ControlsVisualElement Element, out Thickness Padding)
509 {
510 switch (Element)
511 {
512 case Layout LayoutElement:
513 Padding = LayoutElement.Padding;
514 return true;
515 case ContentView ContentViewElement:
516 Padding = ContentViewElement.Padding;
517 return true;
518 case TemplatedView TemplatedViewElement:
519 Padding = TemplatedViewElement.Padding;
520 return true;
521 case Border BorderElement:
522 Padding = BorderElement.Padding;
523 return true;
524 default:
525 Padding = new Thickness(0);
526 return false;
527 }
528 }
529
530 private void AssignPadding(ControlsVisualElement Element, Thickness Padding)
531 {
532 switch (Element)
533 {
534 case Layout LayoutElement:
535 LayoutElement.Padding = Padding;
536 break;
537 case ContentView ContentViewElement:
538 ContentViewElement.Padding = Padding;
539 break;
540 case TemplatedView TemplatedViewElement:
541 TemplatedViewElement.Padding = Padding;
542 break;
543 case Border BorderElement:
544 BorderElement.Padding = Padding;
545 break;
546 }
547 }
548
549 private Thickness InterpolateThickness(Thickness From, Thickness To, double Progress)
550 {
551 return new Thickness(
552 this.InterpolateDouble(From.Left, To.Left, Progress),
553 this.InterpolateDouble(From.Top, To.Top, Progress),
554 this.InterpolateDouble(From.Right, To.Right, Progress),
555 this.InterpolateDouble(From.Bottom, To.Bottom, Progress));
556 }
557
558 private double InterpolateDouble(double From, double To, double Progress)
559 {
560 return From + ((To - From) * Progress);
561 }
562
563 private bool AreThicknessClose(Thickness a, Thickness b)
564 {
565 return Math.Abs(a.Left - b.Left) < 0.5 &&
566 Math.Abs(a.Top - b.Top) < 0.5 &&
567 Math.Abs(a.Right - b.Right) < 0.5 &&
568 Math.Abs(a.Bottom - b.Bottom) < 0.5;
569 }
570
571 private void NotifyKeyboardAwareTargets(BindableObject Screen)
572 {
573 KeyboardInsetChangedEventArgs Args = new KeyboardInsetChangedEventArgs(this.currentKeyboardInset, this.isKeyboardVisible);
574
575 if (Screen is IKeyboardInsetAware awareView)
576 awareView.OnKeyboardInsetChanged(Args);
577
578 if (Screen.BindingContext is IKeyboardInsetAware awareContext)
579 awareContext.OnKeyboardInsetChanged(Args);
580 }
581
582 private void OnKeyboardInsetChanged(object? sender, KeyboardInsetChangedEventArgs e)
583 {
584 this.currentKeyboardInset = e.KeyboardHeight;
585 this.isKeyboardVisible = e.IsVisible;
586
587 if (this.currentScreen is not null)
588 {
589 ContentView activeSlot = this.isSlotAActive ? this.contentHostA : this.contentHostB;
590 Thickness safeArea = this.ApplyInsetsToHost(activeSlot, this.currentScreen, true, true);
591 this.UpdateOverlayInsets(safeArea, true);
592 }
593
594 foreach (Microsoft.Maui.IView Child in this.popupHost.Children)
595 {
596 if (Child is BindableObject bindableChild)
597 this.NotifyKeyboardAwareTargets(bindableChild);
598 }
599
600 if (this.activeToast is BindableObject toastBindable)
601 this.NotifyKeyboardAwareTargets(toastBindable);
602 }
603
604 public void UpdateBars(BindableObject Screen)
605 {
606 bool TopVisible = NavigationBars.GetTopBarVisible(Screen);
607 bool NavVisible = NavigationBars.GetNavBarVisible(Screen);
608 this.topBar.IsVisible = TopVisible;
609 this.navBar.IsVisible = NavVisible;
610 if (Screen is IBarContentProvider Provider)
611 {
612 this.topBar.Content = Provider.TopBarContent;
613 this.navBar.Content = Provider.NavBarContent;
614 }
615 else
616 {
617 this.topBar.Content = null;
618 this.navBar.Content = null;
619 }
620
621 if (ReferenceEquals(Screen, this.currentScreen))
622 {
623 ContentView ActiveSlot = this.isSlotAActive ? this.contentHostA : this.contentHostB;
624 Thickness SafeAreaThickness = this.ApplyInsetsToHost(ActiveSlot, Screen, false, true);
625 this.UpdateOverlayInsets(SafeAreaThickness, false);
626 }
627 }
628
629 protected override bool OnBackButtonPressed()
630 {
631 try
632 {
633 if (this.popupHost.Children.Count > 0)
634 {
635 this.PopupBackRequested?.Invoke(this, EventArgs.Empty);
636 return true;
637 }
638 if (this.activeToast is not null)
639 {
640 _ = this.Dispatcher.DispatchAsync(async () => await this.HideToast());
641 return true;
642 }
643 NavigationService? nav = ServiceRef.Provider.GetService<INavigationService>() as NavigationService;
644 if (nav is not null && nav.WouldHandleBack())
645 {
646 _ = this.Dispatcher.DispatchAsync(async () => await nav.HandleBackAsync());
647 return true;
648 }
649 }
650 catch (Exception ex)
651 {
652 ServiceRef.LogService.LogException(ex);
653 }
654 return base.OnBackButtonPressed();
655 }
656 }
657
661 public enum TransitionType
662 {
663 None,
664 Fade,
665 SwipeLeft,
666 SwipeRight
667 }
668
669 public enum PopupTransition
670 {
671 None,
672 Fade,
673 Scale,
674 SlideUp
675 }
676
677 public enum ToastTransition
678 {
679 Fade,
680 SlideFromTop,
681 SlideFromBottom
682 }
683
684 public enum ToastPlacement
685 {
686 Top,
687 Bottom
688 }
689
693 public interface IBarContentProvider
694 {
695 View TopBarContent { get; }
696 View NavBarContent { get; }
697 }
698
702 public static class NavigationBars
703 {
704 public static readonly BindableProperty TopBarVisibleProperty =
705 BindableProperty.CreateAttached(
706 "TopBarVisible",
707 typeof(bool),
708 typeof(NavigationBars),
709 false);
710
711 public static readonly BindableProperty NavBarVisibleProperty =
712 BindableProperty.CreateAttached(
713 "NavBarVisible",
714 typeof(bool),
715 typeof(NavigationBars),
716 false);
717
718 public static bool GetTopBarVisible(BindableObject View) => (bool)View.GetValue(TopBarVisibleProperty);
719 public static void SetTopBarVisible(BindableObject View, bool Value) => View.SetValue(TopBarVisibleProperty, Value);
720 public static bool GetNavBarVisible(BindableObject View) => (bool)View.GetValue(NavBarVisibleProperty);
721 public static void SetNavBarVisible(BindableObject View, bool Value) => View.SetValue(NavBarVisibleProperty, Value);
722 }
723}
Data structure used when building animation contexts.
Keys for shell-level animations.
static AnimationKey ToastShowSlideTop
Toast slide-in from top animation key.
static AnimationKey PageSlideLeft
Page slide from right to left transition key.
static AnimationKey PopupHideSlideUp
Popup slide-up hide animation key.
static AnimationKey ToastShowFade
Toast fade-in animation key.
static AnimationKey PopupShowSlideUp
Popup slide-up show animation key.
static AnimationKey PopupShowFade
Popup fade show animation key.
static AnimationKey ToastHideSlideBottom
Toast slide-out to bottom animation key.
static AnimationKey ToastHideSlideTop
Toast slide-out to top animation key.
static AnimationKey ToastHideFade
Toast fade-out animation key.
static AnimationKey ToastShowSlideBottom
Toast slide-in from bottom animation key.
static AnimationKey PageCrossFade
Page cross-fade transition key.
static AnimationKey PopupHideFade
Popup fade hide animation key.
static AnimationKey PopupShowScale
Popup scale show animation key.
static AnimationKey PageSlideRight
Page slide from left to right transition key.
static AnimationKey PopupHideScale
Popup scale hide animation key.
Provides strongly typed animation key definitions.
Definition: AnimationKeys.cs:7
Custom shell hosting app content, popups and Toast layers.
Definition: CustomShell.cs:19
CustomShell(LoadingPage loadingPage, IKeyboardInsetsService keyboardInsetsService, IAnimationCoordinator animationCoordinator)
Initializes a new instance of CustomShell.
Definition: CustomShell.cs:45
Helper class for attached properties To show/hide bars.
Definition: CustomShell.cs:703
Visual directives supplied by the popup service for presenter rendering.
Attached properties for configuring keyboard inset handling on views.
static KeyboardInsetMode GetMode(BindableObject view)
Gets the KeyboardInsetMode value for the provided view.
A base class for all pages, intended for custom navigation with explicit life-cycle events.
virtual async Task OnDisappearingAsync()
Called when the page is about to be hidden. Triggers save state, disappearing logic,...
override Task OnInitializeAsync()
Override to register event handlers, process navigation args, etc. Called ONCE per page lifetime.
override async Task OnAppearingAsync()
Called when the page is about to be shown. Triggers restore state, appearing logic,...
Flexible popup surface capable of positioning its content in different regions of the screen.
Coordinates animation execution across the application.
Optional interface for pages that provide their own bar content.
Definition: CustomShell.cs:694
Provides normalized keyboard inset information for UI components.
bool IsKeyboardVisible
Gets a value indicating whether the software keyboard is currently visible.
double KeyboardHeight
Gets the current keyboard height in device-independent units.
Presenter abstraction host for page, popup and toast transitions.
Represents a component that consumes keyboard inset updates manually.
Definition: ImplTypes.g.cs:58
TransitionType
Transition types for page navigation in CustomShell.
Definition: CustomShell.cs:662
KeyboardInsetMode
Specifies how keyboard insets should be applied to a view.
Represents a strongly typed key used for identifying animations.
Definition: AnimationKey.cs:7
string Value
Gets the raw key value.
Definition: AnimationKey.cs:24