-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathNavigationService.cs
324 lines (275 loc) · 11.3 KB
/
NavigationService.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
using Aloha.Mvvm.Maui.Pages;
using Aloha.Mvvm.Enumerations;
using Aloha.Mvvm.Services;
using Aloha.Mvvm.ViewModels;
using System.Reflection;
namespace Aloha.Mvvm.Maui.Services
{
/// <summary>
/// Interface used to couple View object to a ViewModel
/// </summary>
public interface IViewFor
{
object ViewModel { get; set; }
}
/// <summary>
/// Interface used to couple View object to a ViewModel
/// </summary>
public interface IViewFor<T> : IViewFor where T : BaseViewModel
{
new T ViewModel { get; set; }
}
/// <summary>
/// Service that facilitates ViewModel level navigation
/// </summary>
public class NavigationService : INavigationService
{
INavigation MauiNavigation
{
get
{
var tabView = Application.Current.MainPage as TabbedPage;
var flyoutView = Application.Current.MainPage as FlyoutPage;
// First, check to see if we're on a tabbed page, then master detail, finally go to overall fallback
return tabView?.CurrentPage?.Navigation ??
(flyoutView?.Detail as TabbedPage)?.CurrentPage?.Navigation ?? // Special consideration for a tabbed page inside master/detail
flyoutView?.Detail?.Navigation ??
Application.Current.MainPage.Navigation;
}
}
// View model to view lookup - making the assumption that view model to view will always be 1:1
readonly Dictionary<Type, Type> _viewModelViewDictionary = new Dictionary<Type, Type>();
#region Registration
/// <summary>
/// Automatically register all View and ViewModel relationships for lookup within the NavigationService
/// </summary>
/// <param name="asm">An assembly that contains views that implement the IViewFor interface</param>
public void AutoRegister(Assembly asm)
{
// Loop through everything in the assembly that implements IViewFor<T>
foreach (var type in asm.DefinedTypes.Where(dt => !dt.IsAbstract &&
dt.ImplementedInterfaces.Any(ii => ii == typeof(IViewFor))))
{
// Get the IViewFor<T> portion of the type that implements it
var viewForType = type.ImplementedInterfaces.FirstOrDefault(
ii => ii.IsConstructedGenericType &&
ii.GetGenericTypeDefinition() == typeof(IViewFor<>));
// Register it, using the generic (T) type as the key and the view as the value
Register(viewForType.GenericTypeArguments[0], type.AsType());
}
}
/// <summary>
/// Register a View / ViewModel relationship
/// </summary>
public void Register(Type viewModelType, Type viewType)
{
if (!_viewModelViewDictionary.ContainsKey(viewModelType))
{
_viewModelViewDictionary.Add(viewModelType, viewType);
}
}
#endregion
#region Replace
// Because we're going to do a hard switch of the page, either return
// the detail page, or if that's null, then the current main page
Page DetailPage
{
get
{
var flyoutView = Application.Current.MainPage as FlyoutPage;
return flyoutView?.Detail;
}
set
{
if (Application.Current.MainPage is FlyoutPage flyoutView)
{
flyoutView.Detail = value;
flyoutView.IsPresented = false;
}
else
{
Application.Current.MainPage = value;
}
}
}
/// <summary>
/// Set the Detail page of a FlyoutPage
/// </summary>
public async Task SetDetailAsync(BaseViewModel viewModel, bool allowSamePageSet = false)
{
if (DetailPage != null)
{
// Ensure that we're not pushing a new page if the DetailPage is already set to this type
if (!allowSamePageSet)
{
IViewFor page;
if (DetailPage is NavigationPage)
{
page = ((NavigationPage)DetailPage).RootPage as IViewFor;
}
else
{
page = DetailPage as IViewFor;
}
if (page?.ViewModel?.GetType() == viewModel.GetType())
{
var flyoutView = Application.Current.MainPage as FlyoutPage;
flyoutView.IsPresented = false;
return;
}
}
}
Page newDetailPage = await Task.Run(() =>
{
var view = InstantiateView(viewModel);
// Tab pages shouldn't go into navigation pages
if (view is TabbedPage)
{
newDetailPage = (Page)view;
}
else
{
newDetailPage = new NavigationPage((Page)view);
}
return newDetailPage;
});
DetailPage = newDetailPage;
}
/// <summary>
/// Set the Application.Current.MainPage
/// </summary>
public void SetRoot<T>(bool withNavigationEnabled = true) where T : BaseViewModel
{
SetRoot(Activator.CreateInstance<T>(), withNavigationEnabled);
}
/// <summary>
/// Set the Application.Current.MainPage
/// </summary>
public void SetRoot(BaseViewModel viewModel, bool withNavigationEnabled = true)
{
if (InstantiateView(viewModel) is Page view)
{
if (withNavigationEnabled)
{
Application.Current.MainPage = new NavigationPage(view);
}
else
{
Application.Current.MainPage = view;
}
}
}
#endregion
#region Pop
/// <summary>
/// Pop the top view off the navigation stack
/// </summary>
public Task PopAsync(bool animated = true) => MauiNavigation.PopAsync(animated);
/// <summary>
/// Pop the top view off the modal navigation stack
/// </summary>
public Task PopModalAsync(bool animated = true) => MauiNavigation.PopModalAsync(animated);
/// <summary>
/// Pop all the views, above the root, off the navigation stack
/// </summary>
public Task PopToRootAsync(bool animated = true) => MauiNavigation.PopToRootAsync(animated);
#endregion
#region Push
/// <summary>
/// Push a View, by resolving via ViewModel (T) generic, onto the navigation stack
/// </summary>
public Task PushAsync<T>(bool animated = true) where T : BaseViewModel => PushAsync(ServiceContainer.Resolve<T>(), animated);
/// <summary>
/// Push a View, by resolving via ViewModel object, onto the navigation stack
/// </summary>
public Task PushAsync(BaseViewModel viewModel, bool animated) => MauiNavigation.PushAsync((Page)InstantiateView(viewModel), animated);
public Task PushModalAsync<T>(bool nestedNavigation = false, bool animated = true) where T : BaseViewModel
{
return PushModalAsync(ServiceContainer.Resolve<T>(), nestedNavigation, animated);
}
/// <summary>
/// Push a View, by resolving via ViewModel object, onto the navigation stack
/// </summary>
public Task PushModalAsync(BaseViewModel viewModel, bool nestedNavigation = false, bool animated = true)
{
viewModel.ViewDisplay = ViewDisplayType.Modal;
var view = InstantiateView(viewModel);
Page page;
if (nestedNavigation)
{
page = new NavigationPage((Page)view);
}
else
{
page = (Page)view;
}
return MauiNavigation.PushModalAsync(page, animated);
}
#endregion
// Instantiate a View object using a ViewModel object
IViewFor InstantiateView(BaseViewModel viewModel)
{
// Figure out what type the view model is
var viewModelType = viewModel.GetType();
// Look up what type of view it corresponds to
var viewType = _viewModelViewDictionary[viewModelType];
// Instantiate it
var view = (IViewFor)Activator.CreateInstance(viewType);
if (view != null)
{
view.ViewModel = viewModel;
if (view.GetType().IsSubclassOf(typeof(BaseTabbedPage)) &&
view is BaseTabbedPage tabbedView &&
viewModel is BaseCollectionViewModel collectionViewModel)
{
foreach (var childViewModel in collectionViewModel.ViewModels)
{
if (InstantiateView(childViewModel) is BaseContentPage childView)
{
if (collectionViewModel.EnableNavigation)
{
var navPage = new NavigationPage(childView);
navPage.Title = childView.Title;
tabbedView.Children.Add(navPage);
}
else
{
tabbedView.Children.Add(childView);
}
}
}
}
else if (view.GetType().IsSubclassOf(typeof(BaseFlyoutPage)) &&
view is BaseFlyoutPage flyoutView &&
viewModel is BaseFlyoutViewModel flyoutViewModel)
{
if (InstantiateView(flyoutViewModel.Flyout) is BaseContentPage masterView)
{
flyoutView.Flyout = masterView;
if (InstantiateView(flyoutViewModel.Detail) is Page detailView)
{
if (detailView is TabbedPage)
{
flyoutView.Detail = detailView;
}
else
{
flyoutView.Detail = new NavigationPage(detailView);
}
}
else
{
throw new InvalidOperationException("Detail page cannot be null.");
}
flyoutView.IsPresented = false;
}
else
{
throw new InvalidOperationException("Flyout page must derive from BaseContentPage.");
}
}
}
return view;
}
}
}