This repository has been archived by the owner on May 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
Shell.cs
1358 lines (1083 loc) · 46.2 KB
/
Shell.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xamarin.Forms.Internals;
using Xamarin.Forms.Xaml.Diagnostics;
namespace Xamarin.Forms
{
[ContentProperty(nameof(Items))]
public class Shell : Page, IShellController, IPropertyPropagationController, IPageContainer<Page>
{
public Page CurrentPage => (CurrentSection as IShellSectionController)?.PresentedPage;
public static readonly BindableProperty BackButtonBehaviorProperty =
BindableProperty.CreateAttached("BackButtonBehavior", typeof(BackButtonBehavior), typeof(Shell), null, BindingMode.OneTime,
propertyChanged: OnBackButonBehaviorPropertyChanged);
static void OnBackButonBehaviorPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (oldValue is BackButtonBehavior oldHandlerBehavior)
SetInheritedBindingContext(oldHandlerBehavior, null);
if (newValue is BackButtonBehavior newHandlerBehavior)
SetInheritedBindingContext(newHandlerBehavior, bindable.BindingContext);
}
public static readonly BindableProperty PresentationModeProperty = BindableProperty.CreateAttached("PresentationMode", typeof(PresentationMode), typeof(Shell), PresentationMode.Animated);
public static readonly BindableProperty FlyoutBehaviorProperty =
BindableProperty.CreateAttached("FlyoutBehavior", typeof(FlyoutBehavior), typeof(Shell), FlyoutBehavior.Flyout,
propertyChanged: OnFlyoutBehaviorChanged);
public static readonly BindableProperty NavBarIsVisibleProperty =
BindableProperty.CreateAttached("NavBarIsVisible", typeof(bool), typeof(Shell), true);
public static readonly BindableProperty NavBarHasShadowProperty =
BindableProperty.CreateAttached("NavBarHasShadow", typeof(bool), typeof(Shell), default(bool),
defaultValueCreator: (b) => Device.RuntimePlatform == Device.Android);
public static readonly BindableProperty SearchHandlerProperty =
BindableProperty.CreateAttached("SearchHandler", typeof(SearchHandler), typeof(Shell), null, BindingMode.OneTime,
propertyChanged: OnSearchHandlerPropertyChanged);
static void OnSearchHandlerPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (oldValue is SearchHandler oldHandler)
SetInheritedBindingContext(oldHandler, null);
if (newValue is SearchHandler newHandler)
SetInheritedBindingContext(newHandler, bindable.BindingContext);
}
public static readonly BindableProperty FlyoutItemIsVisibleProperty =
BindableProperty.CreateAttached(nameof(IsVisible), typeof(bool), typeof(Shell), true, propertyChanged: OnFlyoutItemIsVisibleChanged);
public static bool GetFlyoutItemIsVisible(BindableObject obj) => (bool)obj.GetValue(FlyoutItemIsVisibleProperty);
public static void SetFlyoutItemIsVisible(BindableObject obj, bool isVisible) => obj.SetValue(FlyoutItemIsVisibleProperty, isVisible);
static void OnFlyoutItemIsVisibleChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is Element element)
element
.FindParentOfType<Shell>()
?.SendFlyoutItemsChanged();
}
public static readonly BindableProperty TabBarIsVisibleProperty =
BindableProperty.CreateAttached("TabBarIsVisible", typeof(bool), typeof(Shell), true);
public static readonly BindableProperty TitleViewProperty =
BindableProperty.CreateAttached("TitleView", typeof(View), typeof(Shell), null, propertyChanged: OnTitleViewChanged);
public static readonly BindableProperty MenuItemTemplateProperty =
BindableProperty.CreateAttached(nameof(MenuItemTemplate), typeof(DataTemplate), typeof(Shell), null, BindingMode.OneTime);
public static DataTemplate GetMenuItemTemplate(BindableObject obj) => (DataTemplate)obj.GetValue(MenuItemTemplateProperty);
public static void SetMenuItemTemplate(BindableObject obj, DataTemplate menuItemTemplate) => obj.SetValue(MenuItemTemplateProperty, menuItemTemplate);
public static readonly BindableProperty ItemTemplateProperty =
BindableProperty.CreateAttached(nameof(ItemTemplate), typeof(DataTemplate), typeof(Shell), null, BindingMode.OneTime);
public static DataTemplate GetItemTemplate(BindableObject obj) => (DataTemplate)obj.GetValue(ItemTemplateProperty);
public static void SetItemTemplate(BindableObject obj, DataTemplate itemTemplate) => obj.SetValue(ItemTemplateProperty, itemTemplate);
public static BackButtonBehavior GetBackButtonBehavior(BindableObject obj) => (BackButtonBehavior)obj.GetValue(BackButtonBehaviorProperty);
public static void SetBackButtonBehavior(BindableObject obj, BackButtonBehavior behavior) => obj.SetValue(BackButtonBehaviorProperty, behavior);
public static PresentationMode GetPresentationMode(BindableObject obj) => (PresentationMode)obj.GetValue(PresentationModeProperty);
public static void SetPresentationMode(BindableObject obj, PresentationMode presentationMode) => obj.SetValue(PresentationModeProperty, presentationMode);
public static FlyoutBehavior GetFlyoutBehavior(BindableObject obj) => (FlyoutBehavior)obj.GetValue(FlyoutBehaviorProperty);
public static void SetFlyoutBehavior(BindableObject obj, FlyoutBehavior value) => obj.SetValue(FlyoutBehaviorProperty, value);
public static double GetFlyoutWidth(BindableObject obj) => (double)obj.GetValue(FlyoutWidthProperty);
public static void SetFlyoutWidth(BindableObject obj, double value) => obj.SetValue(FlyoutWidthProperty, value);
public static double GetFlyoutHeight(BindableObject obj) => (double)obj.GetValue(FlyoutHeightProperty);
public static void SetFlyoutHeight(BindableObject obj, double value) => obj.SetValue(FlyoutHeightProperty, value);
public static bool GetNavBarIsVisible(BindableObject obj) => (bool)obj.GetValue(NavBarIsVisibleProperty);
public static void SetNavBarIsVisible(BindableObject obj, bool value) => obj.SetValue(NavBarIsVisibleProperty, value);
public static bool GetNavBarHasShadow(BindableObject obj) => (bool)obj.GetValue(NavBarHasShadowProperty);
public static void SetNavBarHasShadow(BindableObject obj, bool value) => obj.SetValue(NavBarHasShadowProperty, value);
public static SearchHandler GetSearchHandler(BindableObject obj) => (SearchHandler)obj.GetValue(SearchHandlerProperty);
public static void SetSearchHandler(BindableObject obj, SearchHandler handler) => obj.SetValue(SearchHandlerProperty, handler);
public static bool GetTabBarIsVisible(BindableObject obj) => (bool)obj.GetValue(TabBarIsVisibleProperty);
public static void SetTabBarIsVisible(BindableObject obj, bool value) => obj.SetValue(TabBarIsVisibleProperty, value);
public static View GetTitleView(BindableObject obj) => (View)obj.GetValue(TitleViewProperty);
public static void SetTitleView(BindableObject obj, View value) => obj.SetValue(TitleViewProperty, value);
static void OnFlyoutBehaviorChanged(BindableObject bindable, object oldValue, object newValue)
{
var element = (Element)bindable;
while (!Application.IsApplicationOrNull(element))
{
if (element is Shell shell)
shell.NotifyFlyoutBehaviorObservers();
element = element.Parent;
}
}
public static readonly new BindableProperty BackgroundColorProperty =
BindableProperty.CreateAttached("BackgroundColor", typeof(Color), typeof(Shell), Color.Default,
propertyChanged: OnShellAppearanceValueChanged);
public static readonly BindableProperty DisabledColorProperty =
BindableProperty.CreateAttached("DisabledColor", typeof(Color), typeof(Shell), Color.Default,
propertyChanged: OnShellAppearanceValueChanged);
public static readonly BindableProperty ForegroundColorProperty =
BindableProperty.CreateAttached("ForegroundColor", typeof(Color), typeof(Shell), Color.Default,
propertyChanged: OnShellAppearanceValueChanged);
public static readonly BindableProperty TabBarBackgroundColorProperty =
BindableProperty.CreateAttached("TabBarBackgroundColor", typeof(Color), typeof(Shell), Color.Default,
propertyChanged: OnShellAppearanceValueChanged);
public static readonly BindableProperty TabBarDisabledColorProperty =
BindableProperty.CreateAttached("TabBarDisabledColor", typeof(Color), typeof(Shell), Color.Default,
propertyChanged: OnShellAppearanceValueChanged);
public static readonly BindableProperty TabBarForegroundColorProperty =
BindableProperty.CreateAttached("TabBarForegroundColor", typeof(Color), typeof(Shell), Color.Default,
propertyChanged: OnShellAppearanceValueChanged);
public static readonly BindableProperty TabBarTitleColorProperty =
BindableProperty.CreateAttached("TabBarTitleColor", typeof(Color), typeof(Shell), Color.Default,
propertyChanged: OnShellAppearanceValueChanged);
public static readonly BindableProperty TabBarUnselectedColorProperty =
BindableProperty.CreateAttached("TabBarUnselectedColor", typeof(Color), typeof(Shell), Color.Default,
propertyChanged: OnShellAppearanceValueChanged);
public static readonly BindableProperty TitleColorProperty =
BindableProperty.CreateAttached("TitleColor", typeof(Color), typeof(Shell), Color.Default,
propertyChanged: OnShellAppearanceValueChanged);
public static readonly BindableProperty UnselectedColorProperty =
BindableProperty.CreateAttached("UnselectedColor", typeof(Color), typeof(Shell), Color.Default,
propertyChanged: OnShellAppearanceValueChanged);
public static readonly BindableProperty FlyoutBackdropProperty =
BindableProperty.CreateAttached("FlyoutBackdrop", typeof(Brush), typeof(Shell), Brush.Default,
propertyChanged: OnShellAppearanceValueChanged);
public static readonly BindableProperty FlyoutWidthProperty =
BindableProperty.CreateAttached("FlyoutWidth", typeof(double), typeof(Shell), -1d,
propertyChanged: OnShellAppearanceValueChanged);
public static readonly BindableProperty FlyoutHeightProperty =
BindableProperty.CreateAttached("FlyoutHeight", typeof(double), typeof(Shell), -1d,
propertyChanged: OnShellAppearanceValueChanged);
public static Color GetBackgroundColor(BindableObject obj) => (Color)obj.GetValue(BackgroundColorProperty);
public static void SetBackgroundColor(BindableObject obj, Color value) => obj.SetValue(BackgroundColorProperty, value);
public static Color GetDisabledColor(BindableObject obj) => (Color)obj.GetValue(DisabledColorProperty);
public static void SetDisabledColor(BindableObject obj, Color value) => obj.SetValue(DisabledColorProperty, value);
public static Color GetForegroundColor(BindableObject obj) => (Color)obj.GetValue(ForegroundColorProperty);
public static void SetForegroundColor(BindableObject obj, Color value) => obj.SetValue(ForegroundColorProperty, value);
public static Color GetTabBarBackgroundColor(BindableObject obj) => (Color)obj.GetValue(TabBarBackgroundColorProperty);
public static void SetTabBarBackgroundColor(BindableObject obj, Color value) => obj.SetValue(TabBarBackgroundColorProperty, value);
public static Color GetTabBarDisabledColor(BindableObject obj) => (Color)obj.GetValue(TabBarDisabledColorProperty);
public static void SetTabBarDisabledColor(BindableObject obj, Color value) => obj.SetValue(TabBarDisabledColorProperty, value);
public static Color GetTabBarForegroundColor(BindableObject obj) => (Color)obj.GetValue(TabBarForegroundColorProperty);
public static void SetTabBarForegroundColor(BindableObject obj, Color value) => obj.SetValue(TabBarForegroundColorProperty, value);
public static Color GetTabBarTitleColor(BindableObject obj) => (Color)obj.GetValue(TabBarTitleColorProperty);
public static void SetTabBarTitleColor(BindableObject obj, Color value) => obj.SetValue(TabBarTitleColorProperty, value);
public static Color GetTabBarUnselectedColor(BindableObject obj) => (Color)obj.GetValue(TabBarUnselectedColorProperty);
public static void SetTabBarUnselectedColor(BindableObject obj, Color value) => obj.SetValue(TabBarUnselectedColorProperty, value);
public static Color GetTitleColor(BindableObject obj) => (Color)obj.GetValue(TitleColorProperty);
public static void SetTitleColor(BindableObject obj, Color value) => obj.SetValue(TitleColorProperty, value);
public static Color GetUnselectedColor(BindableObject obj) => (Color)obj.GetValue(UnselectedColorProperty);
public static void SetUnselectedColor(BindableObject obj, Color value) => obj.SetValue(UnselectedColorProperty, value);
public static Brush GetFlyoutBackdrop(BindableObject obj) => (Brush)obj.GetValue(FlyoutBackdropProperty);
public static void SetFlyoutBackdrop(BindableObject obj, Brush value) => obj.SetValue(FlyoutBackdropProperty, value);
static void OnShellAppearanceValueChanged(BindableObject bindable, object oldValue, object newValue)
{
var item = (Element)bindable;
var source = item;
while (!Application.IsApplicationOrNull(item))
{
if (item is IShellController shell)
{
shell.AppearanceChanged(source, true);
return;
}
item = item.Parent;
}
}
static readonly BindablePropertyKey CurrentStatePropertyKey =
BindableProperty.CreateReadOnly(nameof(CurrentState), typeof(ShellNavigationState), typeof(Shell), null);
static readonly BindablePropertyKey ItemsPropertyKey = BindableProperty.CreateReadOnly(nameof(Items), typeof(ShellItemCollection), typeof(Shell), null,
defaultValueCreator: bo => new ShellItemCollection { Inner = new ElementCollection<ShellItem>(((Shell)bo).InternalChildren) });
List<(IAppearanceObserver Observer, Element Pivot)> _appearanceObservers = new List<(IAppearanceObserver Observer, Element Pivot)>();
List<IFlyoutBehaviorObserver> _flyoutBehaviorObservers = new List<IFlyoutBehaviorObserver>();
internal static BindableObject GetBindableObjectWithFlyoutItemTemplate(BindableObject bo)
{
if (bo is IMenuItemController)
{
if (bo is MenuItem mi && mi.Parent != null && mi.Parent.IsSet(MenuItemTemplateProperty))
return mi.Parent;
else if (bo is MenuShellItem msi && msi.MenuItem != null && msi.MenuItem.IsSet(MenuItemTemplateProperty))
return msi.MenuItem;
}
return bo;
}
DataTemplate IShellController.GetFlyoutItemDataTemplate(BindableObject bo)
{
BindableProperty bp = null;
string textBinding;
string iconBinding;
var bindableObjectWithTemplate = GetBindableObjectWithFlyoutItemTemplate(bo);
if (bo is IMenuItemController)
{
bp = MenuItemTemplateProperty;
textBinding = "Text";
iconBinding = "Icon";
}
else
{
bp = ItemTemplateProperty;
textBinding = "Title";
iconBinding = "FlyoutIcon";
}
if (bindableObjectWithTemplate.IsSet(bp))
{
return (DataTemplate)bindableObjectWithTemplate.GetValue(bp);
}
if (IsSet(bp))
{
return (DataTemplate)GetValue(bp);
}
return BaseShellItem.CreateDefaultFlyoutItemCell(textBinding, iconBinding);
}
event EventHandler IShellController.StructureChanged
{
add { _structureChanged += value; }
remove { _structureChanged -= value; }
}
event EventHandler _structureChanged;
event EventHandler IShellController.FlyoutItemsChanged
{
add { _flyoutManager.FlyoutItemsChanged += value; }
remove { _flyoutManager.FlyoutItemsChanged -= value; }
}
View IShellController.FlyoutHeader => FlyoutHeaderView;
View IShellController.FlyoutFooter => FlyoutFooterView;
View IShellController.FlyoutContent => FlyoutContentView;
IShellController ShellController => this;
void IShellController.AddAppearanceObserver(IAppearanceObserver observer, Element pivot)
{
_appearanceObservers.Add((observer, pivot));
observer.OnAppearanceChanged(GetAppearanceForPivot(pivot));
}
void IShellController.AddFlyoutBehaviorObserver(IFlyoutBehaviorObserver observer)
{
_flyoutBehaviorObservers.Add(observer);
// We need to wait until the visible page has been created before we try to calculate
// the flyout behavior
if (GetVisiblePage() != null)
observer.OnFlyoutBehaviorChanged(GetEffectiveFlyoutBehavior());
}
void IShellController.AppearanceChanged(Element source, bool appearanceSet)
{
if (!appearanceSet)
{
// This bubbles up whenever there is an kind of structure/page change
// So its also quite useful for checking the FlyoutBehavior conditions
NotifyFlyoutBehaviorObservers();
}
// here we wish to notify every element whose "pivot line" contains the source
// To do that we first need to find the leaf node in the line, and then walk up
// to see if we find the source on the way up.
// If this is not an appearanceSet event but just a structural change, then we only
// need walk up from the source to look for the pivot as items below the change
// can't be affected by it
foreach (var (Observer, Pivot) in _appearanceObservers)
{
var observer = Observer;
var pivot = Pivot;
Element target;
Element leaf;
if (appearanceSet)
{
leaf = WalkToPage(pivot);
target = source;
}
else
{
leaf = source;
target = pivot;
}
while (!Application.IsApplicationOrNull(leaf))
{
if (leaf == target)
{
observer.OnAppearanceChanged(GetAppearanceForPivot(pivot));
break;
}
leaf = leaf.Parent;
}
}
}
ShellNavigationState IShellController.GetNavigationState(ShellItem shellItem, ShellSection shellSection, ShellContent shellContent, bool includeStack)
=> ShellNavigationManager.GetNavigationState(shellItem, shellSection, shellContent, includeStack ? shellSection.Stack.ToList() : null, includeStack ? shellSection.Navigation.ModalStack.ToList() : null);
async void IShellController.OnFlyoutItemSelected(Element element)
{
await (this as IShellController).OnFlyoutItemSelectedAsync(element);
}
async Task IShellController.OnFlyoutItemSelectedAsync(Element element)
{
ShellItem shellItem = null;
ShellSection shellSection = null;
ShellContent shellContent = null;
switch (element)
{
case MenuShellItem menuShellItem:
((IMenuItemController)menuShellItem.MenuItem).Activate();
break;
case ShellItem i:
shellItem = i;
break;
case ShellSection s:
shellItem = s.Parent as ShellItem;
shellSection = s;
break;
case ShellContent c:
shellItem = c.Parent.Parent as ShellItem;
shellSection = c.Parent as ShellSection;
shellContent = c;
break;
case MenuItem m:
((IMenuItemController)m).Activate();
break;
}
if (shellItem == null || !shellItem.IsEnabled)
return;
shellSection = shellSection ?? shellItem.CurrentItem;
shellContent = shellContent ?? shellSection?.CurrentItem;
var state = ShellNavigationManager.GetNavigationState(shellItem, shellSection, shellContent, null, null);
if (FlyoutIsPresented && GetEffectiveFlyoutBehavior() != FlyoutBehavior.Locked)
SetValueFromRenderer(FlyoutIsPresentedProperty, false);
if (shellSection == null)
shellItem.PropertyChanged += OnShellItemPropertyChanged;
else if (shellContent == null)
shellSection.PropertyChanged += OnShellItemPropertyChanged;
else
await GoToAsync(state).ConfigureAwait(false);
}
void OnShellItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == CurrentItemProperty.PropertyName)
{
(sender as BindableObject).PropertyChanged -= OnShellItemPropertyChanged;
if (sender is ShellItem item)
((IShellController)this).OnFlyoutItemSelected(item);
else if (sender is ShellSection section)
((IShellController)this).OnFlyoutItemSelected(section.Parent);
}
}
bool IShellController.ProposeNavigation(ShellNavigationSource source, ShellItem shellItem, ShellSection shellSection, ShellContent shellContent, IReadOnlyList<Page> stack, bool canCancel)
{
return _navigationManager.ProposeNavigationOutsideGotoAsync(source, shellItem, shellSection, shellContent, stack, canCancel, true);
}
bool IShellController.RemoveAppearanceObserver(IAppearanceObserver observer)
{
for (int i = 0; i < _appearanceObservers.Count; i++)
{
if (_appearanceObservers[i].Observer == observer)
{
_appearanceObservers.RemoveAt(i);
return true;
}
}
return false;
}
bool IShellController.RemoveFlyoutBehaviorObserver(IFlyoutBehaviorObserver observer)
=> _flyoutBehaviorObservers.Remove(observer);
void IShellController.UpdateCurrentState(ShellNavigationSource source)
{
var oldState = CurrentState;
var shellItem = CurrentItem;
var shellSection = shellItem?.CurrentItem;
var shellContent = shellSection?.CurrentItem;
var stack = shellSection?.Stack;
var modalStack = shellSection?.Navigation?.ModalStack;
var result = ShellNavigationManager.GetNavigationState(shellItem, shellSection, shellContent, stack, modalStack);
if (result?.Location != oldState?.Location)
{
SetValueFromRenderer(CurrentStatePropertyKey, result);
_navigationManager.HandleNavigated(new ShellNavigatedEventArgs(oldState, CurrentState, source));
}
}
ReadOnlyCollection<ShellItem> IShellController.GetItems() =>
new ReadOnlyCollection<ShellItem>(((ShellItemCollection)Items).VisibleItemsReadOnly.ToList());
event NotifyCollectionChangedEventHandler IShellController.ItemsCollectionChanged
{
add { ((ShellItemCollection)Items).VisibleItemsChanged += value; }
remove { ((ShellItemCollection)Items).VisibleItemsChanged -= value; }
}
public static Shell Current => Application.Current?.MainPage as Shell;
internal ShellNavigationManager NavigationManager => _navigationManager;
public Task GoToAsync(ShellNavigationState state)
{
return _navigationManager.GoToAsync(state, null, false);
}
public Task GoToAsync(ShellNavigationState state, bool animate)
{
return _navigationManager.GoToAsync(state, animate, false);
}
public void AddLogicalChild(Element element)
{
if (element == null)
{
return;
}
if (_logicalChildren.Contains(element))
return;
_logicalChildren.Add(element);
element.Parent = this;
OnChildAdded(element);
VisualDiagnostics.OnChildAdded(this, element);
}
public void RemoveLogicalChild(Element element)
{
if (element == null)
{
return;
}
element.Parent = null;
if (!_logicalChildren.Contains(element))
return;
var oldLogicalIndex = _logicalChildren.IndexOf(element);
_logicalChildren.Remove(element);
OnChildRemoved(element, oldLogicalIndex);
VisualDiagnostics.OnChildRemoved(this, element, oldLogicalIndex);
}
public static readonly BindableProperty CurrentItemProperty =
BindableProperty.Create(nameof(CurrentItem), typeof(ShellItem), typeof(Shell), null, BindingMode.TwoWay,
propertyChanging: OnCurrentItemChanging,
propertyChanged: OnCurrentItemChanged);
public static readonly BindableProperty CurrentStateProperty = CurrentStatePropertyKey.BindableProperty;
public static readonly BindableProperty FlyoutBackgroundImageProperty =
BindableProperty.Create(nameof(FlyoutBackgroundImage), typeof(ImageSource), typeof(Shell), default(ImageSource), BindingMode.OneTime);
public static readonly BindableProperty FlyoutBackgroundImageAspectProperty =
BindableProperty.Create(nameof(FlyoutBackgroundImageAspect), typeof(Aspect), typeof(Shell), default(Aspect), BindingMode.OneTime);
public static readonly BindableProperty FlyoutBackgroundColorProperty =
BindableProperty.Create(nameof(FlyoutBackgroundColor), typeof(Color), typeof(Shell), Color.Default, BindingMode.OneTime);
public static readonly BindableProperty FlyoutBackgroundProperty =
BindableProperty.Create(nameof(FlyoutBackground), typeof(Brush), typeof(Shell), Brush.Default, BindingMode.OneTime);
public static readonly BindableProperty FlyoutHeaderBehaviorProperty =
BindableProperty.Create(nameof(FlyoutHeaderBehavior), typeof(FlyoutHeaderBehavior), typeof(Shell), FlyoutHeaderBehavior.Default, BindingMode.OneTime);
public static readonly BindableProperty FlyoutHeaderProperty =
BindableProperty.Create(nameof(FlyoutHeader), typeof(object), typeof(Shell), null, BindingMode.OneTime,
propertyChanging: OnFlyoutHeaderChanging);
public static readonly BindableProperty FlyoutFooterProperty =
BindableProperty.Create(nameof(FlyoutFooter), typeof(object), typeof(Shell), null, BindingMode.OneTime,
propertyChanging: OnFlyoutFooterChanging);
public static readonly BindableProperty FlyoutHeaderTemplateProperty =
BindableProperty.Create(nameof(FlyoutHeaderTemplate), typeof(DataTemplate), typeof(Shell), null, BindingMode.OneTime,
propertyChanging: OnFlyoutHeaderTemplateChanging);
public static readonly BindableProperty FlyoutFooterTemplateProperty =
BindableProperty.Create(nameof(FlyoutFooterTemplate), typeof(DataTemplate), typeof(Shell), null, BindingMode.OneTime,
propertyChanging: OnFlyoutFooterTemplateChanging);
public static readonly BindableProperty FlyoutIsPresentedProperty =
BindableProperty.Create(nameof(FlyoutIsPresented), typeof(bool), typeof(Shell), false, BindingMode.TwoWay);
public static readonly BindableProperty ItemsProperty = ItemsPropertyKey.BindableProperty;
public static readonly BindableProperty FlyoutIconProperty =
BindableProperty.Create(nameof(FlyoutIcon), typeof(ImageSource), typeof(Shell), null);
public static readonly BindableProperty FlyoutVerticalScrollModeProperty =
BindableProperty.Create(nameof(FlyoutVerticalScrollMode), typeof(ScrollMode), typeof(Shell), ScrollMode.Auto);
View _flyoutHeaderView;
View _flyoutFooterView;
ShellNavigationManager _navigationManager;
ShellFlyoutItemsManager _flyoutManager;
ObservableCollection<Element> _logicalChildren = new ObservableCollection<Element>();
internal override ReadOnlyCollection<Element> LogicalChildrenInternal =>
new ReadOnlyCollection<Element>(_logicalChildren);
public Shell()
{
_navigationManager = new ShellNavigationManager(this);
_navigationManager.Navigated += (_, args) =>
{
OnNavigated(args);
Navigated?.Invoke(this, args);
};
_navigationManager.Navigating += (_, args) =>
{
Navigating?.Invoke(this, args);
OnNavigating(args);
};
_flyoutManager = new ShellFlyoutItemsManager(this);
Navigation = new NavigationImpl(this);
Route = Routing.GenerateImplicitRoute("shell");
Initialize();
InternalChildren.CollectionChanged += OnInternalChildrenCollectionChanged;
}
void Initialize()
{
if (CurrentItem != null)
SetCurrentItem();
((ShellElementCollection)Items).VisibleItemsChangedInternal += (s, e) =>
{
SetCurrentItem();
SendStructureChanged();
SendFlyoutItemsChanged();
};
async void SetCurrentItem()
{
var shellItems = ShellController.GetItems();
if (CurrentItem != null && shellItems.Contains(CurrentItem))
return;
ShellItem shellItem = null;
// If shell item has been removed try to renavigate to current location
// Just in case the item was replaced. This is mainly relevant for hot reload
if (CurrentItem != null)
{
try
{
var location = CurrentState.Location;
var navRequest = ShellUriHandler.GetNavigationRequest(this, ((ShellNavigationState)location).FullLocation, false, false);
if (navRequest != null)
{
var item = navRequest.Request.Item;
var section = navRequest.Request.Section;
var Content = navRequest.Request.Content;
if (IsValidRoute(item) && IsValidRoute(section) && IsValidRoute(Content))
{
await GoToAsync(location, false);
return;
}
bool IsValidRoute(BaseShellItem baseShellItem)
{
if (baseShellItem == null)
return true;
if (!baseShellItem.IsVisible)
return false;
return baseShellItem.IsPartOfVisibleTree();
}
}
}
catch (Exception exc)
{
Log.Warning(nameof(Shell), $"If you're using hot reload add a route to everything in your shell file: {exc}");
}
}
if (shellItem == null)
{
foreach (var item in shellItems)
{
if (item is ShellItem && ValidDefaultShellItem(item))
{
shellItem = item;
break;
}
}
}
if (shellItem != null)
ShellController.OnFlyoutItemSelected(shellItem);
}
}
public ScrollMode FlyoutVerticalScrollMode
{
get => (ScrollMode)GetValue(FlyoutVerticalScrollModeProperty);
set => SetValue(FlyoutVerticalScrollModeProperty, value);
}
public event EventHandler<ShellNavigatedEventArgs> Navigated;
public event EventHandler<ShellNavigatingEventArgs> Navigating;
public ImageSource FlyoutIcon
{
get => (ImageSource)GetValue(FlyoutIconProperty);
set => SetValue(FlyoutIconProperty, value);
}
public ShellItem CurrentItem
{
get => (ShellItem)GetValue(CurrentItemProperty);
set => SetValue(CurrentItemProperty, value);
}
internal ShellContent CurrentContent => CurrentItem?.CurrentItem?.CurrentItem;
internal ShellSection CurrentSection => CurrentItem?.CurrentItem;
public ShellNavigationState CurrentState => (ShellNavigationState)GetValue(CurrentStateProperty);
[TypeConverter(typeof(ImageSourceConverter))]
public ImageSource FlyoutBackgroundImage
{
get => (ImageSource)GetValue(FlyoutBackgroundImageProperty);
set => SetValue(FlyoutBackgroundImageProperty, value);
}
public Aspect FlyoutBackgroundImageAspect
{
get => (Aspect)GetValue(FlyoutBackgroundImageAspectProperty);
set => SetValue(FlyoutBackgroundImageAspectProperty, value);
}
public Color FlyoutBackgroundColor
{
get => (Color)GetValue(FlyoutBackgroundColorProperty);
set => SetValue(FlyoutBackgroundColorProperty, value);
}
public Brush FlyoutBackground
{
get => (Brush)GetValue(FlyoutBackgroundProperty);
set => SetValue(FlyoutBackgroundProperty, value);
}
public Brush FlyoutBackdrop
{
get => (Brush)GetValue(FlyoutBackdropProperty);
set => SetValue(FlyoutBackdropProperty, value);
}
public double FlyoutWidth
{
get => (double)GetValue(FlyoutWidthProperty);
set => SetValue(FlyoutWidthProperty, value);
}
public double FlyoutHeight
{
get => (double)GetValue(FlyoutHeightProperty);
set => SetValue(FlyoutHeightProperty, value);
}
public FlyoutBehavior FlyoutBehavior
{
get => (FlyoutBehavior)GetValue(FlyoutBehaviorProperty);
set => SetValue(FlyoutBehaviorProperty, value);
}
public object FlyoutHeader
{
get => GetValue(FlyoutHeaderProperty);
set => SetValue(FlyoutHeaderProperty, value);
}
public object FlyoutFooter
{
get => GetValue(FlyoutFooterProperty);
set => SetValue(FlyoutFooterProperty, value);
}
public FlyoutHeaderBehavior FlyoutHeaderBehavior
{
get => (FlyoutHeaderBehavior)GetValue(FlyoutHeaderBehaviorProperty);
set => SetValue(FlyoutHeaderBehaviorProperty, value);
}
public DataTemplate FlyoutHeaderTemplate
{
get => (DataTemplate)GetValue(FlyoutHeaderTemplateProperty);
set => SetValue(FlyoutHeaderTemplateProperty, value);
}
public DataTemplate FlyoutFooterTemplate
{
get => (DataTemplate)GetValue(FlyoutFooterTemplateProperty);
set => SetValue(FlyoutFooterTemplateProperty, value);
}
public bool FlyoutIsPresented
{
get => (bool)GetValue(FlyoutIsPresentedProperty);
set => SetValue(FlyoutIsPresentedProperty, value);
}
public IList<ShellItem> Items => (IList<ShellItem>)GetValue(ItemsProperty);
public DataTemplate ItemTemplate
{
get => GetItemTemplate(this);
set => SetItemTemplate(this, value);
}
public DataTemplate MenuItemTemplate
{
get => GetMenuItemTemplate(this);
set => SetMenuItemTemplate(this, value);
}
internal string Route
{
get => Routing.GetRoute(this);
set => Routing.SetRoute(this, value);
}
internal string RouteHost { get; set; } = "shell";
internal string RouteScheme { get; set; } = "app";
View FlyoutHeaderView
{
get => _flyoutHeaderView;
}
View FlyoutFooterView
{
get => _flyoutFooterView;
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (FlyoutHeaderView != null)
SetInheritedBindingContext(FlyoutHeaderView, BindingContext);
if (FlyoutFooterView != null)
SetInheritedBindingContext(FlyoutFooterView, BindingContext);
if (FlyoutContentView != null)
SetInheritedBindingContext(FlyoutContentView, BindingContext);
}
internal void SendFlyoutItemsChanged() => _flyoutManager.CheckIfFlyoutItemsChanged();
public IEnumerable FlyoutItems => _flyoutManager.FlyoutItems;
List<List<Element>> IShellController.GenerateFlyoutGrouping() =>
_flyoutManager.GenerateFlyoutGrouping();
internal void SendStructureChanged()
{
UpdateChecked(this);
_structureChanged?.Invoke(this, EventArgs.Empty);
}
protected override bool OnBackButtonPressed()
{
if (GetVisiblePage() is Page page && page.SendBackButtonPressed())
return true;
var currentContent = CurrentItem?.CurrentItem;
if (currentContent != null && currentContent.Stack.Count > 1)
{
NavigationPop();
return true;
}
var args = new ShellNavigatingEventArgs(this.CurrentState, new ShellNavigationState(""), ShellNavigationSource.Pop, true);
_navigationManager.HandleNavigating(args);
return args.Cancelled;
async void NavigationPop()
{
try
{
await currentContent.Navigation.PopAsync();
}
catch (Exception exc)
{
Internals.Log.Warning(nameof(Shell), $"Failed to Navigate Back: {exc}");
}
}
}
bool ValidDefaultShellItem(Element child) => !(child is MenuShellItem);
protected virtual void OnNavigated(ShellNavigatedEventArgs args)
{
}
protected virtual void OnNavigating(ShellNavigatingEventArgs args)
{
}
static void OnCurrentItemChanged(BindableObject bindable, object oldValue, object newValue)
{
if (oldValue is ShellItem oldShellItem)
oldShellItem.SendDisappearing();
if (newValue == null)
return;
if (newValue is ShellItem newShellItem)
newShellItem.SendAppearing();
var shell = (Shell)bindable;
UpdateChecked(shell);
shell.ShellController.AppearanceChanged(shell, false);
shell.ShellController.UpdateCurrentState(ShellNavigationSource.ShellItemChanged);
}
static void OnCurrentItemChanging(BindableObject bindable, object oldValue, object newValue)
{
var shell = (Shell)bindable;
var shellItem = (ShellItem)newValue;
if (!shell.Items.Contains(shellItem))
shell.Items.Add(shellItem);
var shellSection = shellItem.CurrentItem;
var shellContent = shellSection.CurrentItem;
var stack = shellSection.Stack;
shell._navigationManager.ProposeNavigationOutsideGotoAsync(ShellNavigationSource.ShellItemChanged, shellItem, shellSection, shellContent, stack, false, true);
}
static void UpdateChecked(Element root, bool isChecked = true)
{
if (root is BaseShellItem baseItem)
{
if (!isChecked && !baseItem.IsChecked)
return;
baseItem.SetValue(BaseShellItem.IsCheckedPropertyKey, isChecked);
}
if (root is Shell shell)
{
ShellItem currentItem = shell.CurrentItem;
var items = shell.ShellController.GetItems();
var count = items.Count;
for (int i = 0; i < count; i++)
{
ShellItem item = items[i];
UpdateChecked(item, isChecked && item == currentItem);
}
}
else if (root is ShellItem shellItem)
{
var currentItem = shellItem.CurrentItem;
var items = (shellItem as IShellItemController).GetItems();
var count = items.Count;
for (int i = 0; i < count; i++)
{
ShellSection item = items[i];
UpdateChecked(item, isChecked && item == currentItem);
}
}
else if (root is ShellSection shellSection)
{
var currentItem = shellSection.CurrentItem;
var items = (shellSection as IShellSectionController).GetItems();
var count = items.Count;
for (int i = 0; i < count; i++)
{
ShellContent item = items[i];
UpdateChecked(item, isChecked && item == currentItem);
}
}
}
static void OnFlyoutHeaderChanging(BindableObject bindable, object oldValue, object newValue)
{
var shell = (Shell)bindable;
shell.OnFlyoutHeaderChanged(oldValue, newValue);
}
static void OnFlyoutFooterChanging(BindableObject bindable, object oldValue, object newValue)
{
var shell = (Shell)bindable;
shell.OnFlyoutFooterChanged(oldValue, newValue);
}
static void OnFlyoutHeaderTemplateChanging(BindableObject bindable, object oldValue, object newValue)
{
var shell = (Shell)bindable;
shell.OnFlyoutHeaderTemplateChanged((DataTemplate)oldValue, (DataTemplate)newValue);
}
static void OnFlyoutFooterTemplateChanging(BindableObject bindable, object oldValue, object newValue)