0f57bd1c95ddcaa2137cfced076f7d0380dabab2
[platform/core/csapi/xsf.git] / Xamarin.Forms / Xamarin.Forms.Platform.Tizen / Renderers / NavigationPageRenderer.cs
1 using System;
2 using System.Linq;
3 using System.Threading.Tasks;
4 using System.Collections.Generic;
5 using Xamarin.Forms.Internals;
6 using Xamarin.Forms.PlatformConfiguration.TizenSpecific;
7 using ElmSharp;
8 using EButton = ElmSharp.Button;
9 using EToolbar = ElmSharp.Toolbar;
10 using EToolbarItem = ElmSharp.ToolbarItem;
11 using Specific = Xamarin.Forms.PlatformConfiguration.TizenSpecific.NavigationPage;
12 using SpecificPage = Xamarin.Forms.PlatformConfiguration.TizenSpecific.Page;
13
14 namespace Xamarin.Forms.Platform.Tizen
15 {
16         public class NavigationPageRenderer : VisualElementRenderer<NavigationPage>
17         {
18                 enum ToolbarButtonPosition
19                 {
20                         Left,
21                         Right
22                 };
23
24                 const string PartTitle = "default";
25                 const string PartBackButton = "elm.swallow.prev_btn";
26                 const string PartLeftToolbar = "title_left_btn";
27                 const string PartRightToolbar = "title_right_btn";
28                 const string PartNavigationBar = "navigationbar";
29                 const string StyleBackButton = "naviframe/back_btn/default";
30                 const string StyleNavigationBar = "navigationbar";
31
32                 readonly List<Widget> _naviItemContentPartList = new List<Widget>();
33                 Naviframe _naviFrame = null;
34                 Page _previousPage = null;
35                 TaskCompletionSource<bool> _currentTaskSource = null;
36                 ToolbarTracker _toolbarTracker = null;
37                 IDictionary<Page, NaviItem> _naviItemMap;
38
39                 Page CurrentPage => Element.CurrentPage;
40                 Page PreviousPage => Element.Navigation.NavigationStack.Count > 1 ? Element.Navigation.NavigationStack[Element.Navigation.NavigationStack.Count - 2] : null;
41                 NaviItem CurrentNaviItem => _naviFrame.NavigationStack.Count > 0 ? _naviFrame.NavigationStack.Last() : null;
42                 NaviItem PreviousNaviItem => _naviFrame.NavigationStack.Count > 1 ? _naviFrame.NavigationStack[_naviFrame.NavigationStack.Count - 2] : null;
43
44                 protected override void Dispose(bool disposing)
45                 {
46                         if (disposing)
47                         {
48                                 if (_naviFrame != null)
49                                 {
50                                         _naviFrame.AnimationFinished -= OnAnimationFinished;
51                                 }
52                                 if (_toolbarTracker != null)
53                                 {
54                                         _toolbarTracker.CollectionChanged -= OnToolbarCollectionChanged;
55                                 }
56                         }
57                         base.Dispose(disposing);
58                 }
59
60                 protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
61                 {
62                         if (_naviFrame == null)
63                         {
64                                 _naviFrame = new Naviframe(Forms.NativeParent);
65                                 _naviFrame.PreserveContentOnPop = true;
66                                 _naviFrame.DefaultBackButtonEnabled = false;
67                                 _naviFrame.AnimationFinished += OnAnimationFinished;
68
69                                 SetNativeView(_naviFrame);
70                                 _naviItemMap = new Dictionary<Page, NaviItem>();
71                         }
72
73                         if (_toolbarTracker == null)
74                         {
75                                 _toolbarTracker = new ToolbarTracker();
76                                 _toolbarTracker.CollectionChanged += OnToolbarCollectionChanged;
77                         }
78
79                         if (e.OldElement != null)
80                         {
81                                 var navigation = e.OldElement as INavigationPageController;
82                                 navigation.PopRequested -= OnPopRequested;
83                                 navigation.PopToRootRequested -= OnPopToRootRequested;
84                                 navigation.PushRequested -= OnPushRequested;
85                                 navigation.RemovePageRequested -= OnRemovePageRequested;
86                                 navigation.InsertPageBeforeRequested -= OnInsertPageBeforeRequested;
87
88                                 var pageController = e.OldElement as IPageController;
89                                 pageController.InternalChildren.CollectionChanged -= OnPageCollectionChanged;
90                         }
91
92                         if (e.NewElement != null)
93                         {
94                                 var navigation = e.NewElement as INavigationPageController;
95                                 navigation.PopRequested += OnPopRequested;
96                                 navigation.PopToRootRequested += OnPopToRootRequested;
97                                 navigation.PushRequested += OnPushRequested;
98                                 navigation.RemovePageRequested += OnRemovePageRequested;
99                                 navigation.InsertPageBeforeRequested += OnInsertPageBeforeRequested;
100
101                                 _toolbarTracker.Target = e.NewElement;
102                                 _previousPage = e.NewElement.CurrentPage;
103                         }
104                         base.OnElementChanged(e);
105                 }
106
107                 protected override void OnElementReady()
108                 {
109                         base.OnElementReady();
110                         var pageController = Element as IPageController;
111                         pageController.InternalChildren.CollectionChanged += OnPageCollectionChanged;
112
113                         foreach (Page page in pageController.InternalChildren)
114                         {
115                                 _naviItemMap[page] = _naviFrame.Push(CreateNavItem(page), SpanTitle(page.Title));
116                                 page.PropertyChanged += NavigationBarPropertyChangedHandler;
117
118                                 UpdateHasNavigationBar(page);
119                         }
120                 }
121
122                 protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
123                 {
124                         base.OnElementPropertyChanged(sender, e);
125
126                         if (e.PropertyName == NavigationPage.CurrentPageProperty.PropertyName)
127                         {
128                                 Device.BeginInvokeOnMainThread(() =>
129                                 {
130                                         (_previousPage as IPageController)?.SendDisappearing();
131                                         _previousPage = Element.CurrentPage;
132                                         (_previousPage as IPageController)?.SendAppearing();
133                                 });
134                         }
135                         else if (e.PropertyName == NavigationPage.BarTextColorProperty.PropertyName)
136                                 UpdateTitle(CurrentPage);
137                         // Tizen does not support 'Tint', but only 'BarBackgroundColor'
138                         else if (e.PropertyName == NavigationPage.BarBackgroundColorProperty.PropertyName)
139                                 UpdateBarBackgroundColor(CurrentNaviItem);
140                         else if (e.PropertyName == Specific.HasBreadCrumbsBarProperty.PropertyName)
141                                 UpdateBreadCrumbsBar(CurrentNaviItem);
142
143                 }
144
145                 void OnPageCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
146                 {
147                         if (e.OldItems != null)
148                                 foreach (Page page in e.OldItems)
149                                         page.PropertyChanged -= NavigationBarPropertyChangedHandler;
150                         if (e.NewItems != null)
151                                 foreach (Page page in e.NewItems)
152                                         page.PropertyChanged += NavigationBarPropertyChangedHandler;
153                 }
154
155                 void OnToolbarCollectionChanged(object sender, EventArgs eventArgs)
156                 {
157                         UpdateToolbarItem(Element.CurrentPage);
158                 }
159
160                 void NavigationBarPropertyChangedHandler(object sender, System.ComponentModel.PropertyChangedEventArgs e)
161                 {
162                         // this handler is invoked only for child pages (contained on a navigation stack)
163                         if (e.PropertyName == NavigationPage.HasNavigationBarProperty.PropertyName)
164                                 UpdateHasNavigationBar(sender as Page);
165                         else if (e.PropertyName == NavigationPage.HasBackButtonProperty.PropertyName ||
166                                 e.PropertyName == NavigationPage.BackButtonTitleProperty.PropertyName)
167                                 UpdateHasBackButton(sender as Page);
168                         else if (e.PropertyName == Page.TitleProperty.PropertyName)
169                                 UpdateTitle(sender as Page);
170                         else if (e.PropertyName == SpecificPage.BreadCrumbProperty.PropertyName)
171                                 UpdateBreadCrumbsBar(GetNaviItemForPage(sender as Page));
172                 }
173
174                 void UpdateHasNavigationBar(Page page)
175                 {
176                         NaviItem item = GetNaviItemForPage(page);
177                         if (NavigationPage.GetTitleView(page) != null)
178                         {
179                                 item.TitleBarVisible = false;
180                                 Native.TitleViewPage tvPage = item.Content as Native.TitleViewPage;
181                                 if (tvPage != null)
182                                 {
183                                         tvPage.HasNavigationBar = (bool)page.GetValue(NavigationPage.HasNavigationBarProperty);
184                                 }
185                                 return;
186                         }
187                         item.TitleBarVisible = (bool)page.GetValue(NavigationPage.HasNavigationBarProperty);
188                         UpdateToolbarItem(page, item);
189                         UpdateBarBackgroundColor(item);
190                         UpdateBreadCrumbsBar(item);
191                 }
192
193                 void UpdateToolbarItem(Page page, NaviItem item = null)
194                 {
195                         if (item == null)
196                                 item = GetNaviItemForPage(page);
197
198                         if (_naviFrame.NavigationStack.Count == 0 || item == null || item != _naviFrame.NavigationStack.Last())
199                                 return;
200
201                         Native.Button rightButton = GetToolbarButton(ToolbarButtonPosition.Right);
202                         item.SetPartContent(PartRightToolbar, rightButton);
203
204                         Native.Button leftButton = GetToolbarButton(ToolbarButtonPosition.Left);
205                         item.SetPartContent(PartLeftToolbar, leftButton);
206                         UpdateHasBackButton(page, item);
207                 }
208
209                 void UpdateHasBackButton(Page page, NaviItem item = null)
210                 {
211                         if (item == null)
212                                 item = GetNaviItemForPage(page);
213
214                         EButton button = null;
215
216                         if ((bool)page.GetValue(NavigationPage.HasBackButtonProperty) && _naviFrame.NavigationStack.Count > 1)
217                         {
218                                 button = CreateNavigationButton((string)page.GetValue(NavigationPage.BackButtonTitleProperty));
219                         }
220                         item.SetPartContent(PartBackButton, button);
221                 }
222
223                 void UpdateTitle(Page page, NaviItem item = null)
224                 {
225                         if (item == null)
226                                 item = GetNaviItemForPage(page);
227
228                         item.SetPartText(PartTitle, SpanTitle(page.Title));
229                 }
230
231                 string SpanTitle(string Title)
232                 {
233                         Native.Span span = new Native.Span
234                         {
235                                 Text = Title,
236                                 HorizontalTextAlignment = Native.TextAlignment.Center,
237                                 ForegroundColor = Element.BarTextColor.ToNative()
238                         };
239                         return span.GetMarkupText();
240                 }
241
242                 void UpdateBarBackgroundColor(NaviItem item)
243                 {
244                         item.TitleBarBackgroundColor = Element.BarBackgroundColor.ToNative();
245                 }
246
247                 void UpdateNavigationBar(Page page, NaviItem item = null)
248                 {
249                         if (item == null)
250                                 item = GetNaviItemForPage(page);
251
252                         UpdateTitle(page, item);
253                         UpdateBarBackgroundColor(item);
254                 }
255
256                 void UpdateBreadCrumbsBar(NaviItem item)
257                 {
258                         if (Element.OnThisPlatform().HasBreadCrumbsBar())
259                         {
260                                 item.Style = StyleNavigationBar;
261                                 item.SetPartContent(PartNavigationBar, GetBreadCrumbsBar());
262                         }
263                         else
264                         {
265                                 item.SetPartContent(PartNavigationBar, null, false);
266                         }
267                 }
268
269                 EButton CreateNavigationButton(string text)
270                 {
271                         EButton button = new EButton(Forms.NativeParent);
272                         button.Clicked += (sender, e) =>
273                         {
274                                 if (!Element.SendBackButtonPressed())
275                                         Forms.Context.Exit();
276                         };
277
278                         button.Style = StyleBackButton;
279                         button.Text = text;
280
281                         _naviItemContentPartList.Add(button);
282                         button.Deleted += NaviItemPartContentDeletedHandler;
283
284                         return button;
285                 }
286
287                 void NaviItemPartContentDeletedHandler(object sender, EventArgs e)
288                 {
289                         _naviItemContentPartList.Remove(sender as Widget);
290                 }
291
292                 NaviItem GetNaviItemForPage(Page page)
293                 {
294                         NaviItem item;
295                         if (_naviItemMap.TryGetValue(page, out item))
296                         {
297                                 return item;
298                         }
299                         return null;
300                 }
301
302                 Native.Button GetToolbarButton(ToolbarButtonPosition position)
303                 {
304                         ToolbarItem item = _toolbarTracker.ToolbarItems.Where(
305                                 i => (position == ToolbarButtonPosition.Right && i.Order <= ToolbarItemOrder.Primary)
306                                 || (position == ToolbarButtonPosition.Left && i.Order == ToolbarItemOrder.Secondary))
307                                 .OrderBy(i => i.Priority).FirstOrDefault();
308
309                         if (item == default(ToolbarItem))
310                                 return null;
311
312                         Native.ToolbarItemButton button = new Native.ToolbarItemButton(item);
313                         return button;
314                 }
315
316                 EToolbar GetBreadCrumbsBar()
317                 {
318                         EToolbar toolbar = new EToolbar(Forms.NativeParent)
319                         {
320                                 Style = StyleNavigationBar,
321                                 ItemAlignment = 0,
322                                 Homogeneous = false,
323                                 ShrinkMode = ToolbarShrinkMode.Scroll
324                         };
325
326                         foreach (var p in Element.Navigation.NavigationStack)
327                         {
328                                 string breadCrumb = p.OnThisPlatform().GetBreadCrumb();
329                                 if (!string.IsNullOrEmpty(breadCrumb))
330                                 {
331                                         EToolbarItem toolbarItem = toolbar.Append(breadCrumb);
332                                         toolbarItem.Selected += (s, e) =>
333                                         {
334                                                 var copyOfStack = Element.Navigation.NavigationStack.Reverse().Skip(1);
335                                                 foreach (var lp in copyOfStack)
336                                                 {
337                                                         if (lp == p) break;
338                                                         Element.Navigation.RemovePage(lp);
339                                                 }
340                                                 if (Element.Navigation.NavigationStack.Last() != p)
341                                                         Element.Navigation.PopAsync();
342                                         };
343                                 }
344                         }
345
346                         return toolbar;
347                 }
348
349                 void OnPopRequested(object sender, NavigationRequestedEventArgs nre)
350                 {
351                         if ((Element as IPageController).InternalChildren.Count == _naviFrame.NavigationStack.Count)
352                         {
353                                 nre.Page?.SendDisappearing();
354                                 UpdateNavigationBar(PreviousPage, PreviousNaviItem);
355
356                                 if (nre.Animated)
357                                 {
358                                         _naviFrame.Pop();
359
360                                         _currentTaskSource = new TaskCompletionSource<bool>();
361                                         nre.Task = _currentTaskSource.Task;
362
363                                         // There is no TransitionFinished (AnimationFinished) event after Pop the last page
364                                         if (_naviFrame.NavigationStack.Count == 0)
365                                                 CompleteCurrentNavigationTask();
366                                 }
367                                 else
368                                 {
369                                         CurrentNaviItem?.Delete();
370                                 }
371
372                                 if (_naviItemMap.ContainsKey(nre.Page))
373                                         _naviItemMap.Remove(nre.Page);
374                         }
375                 }
376
377                 void OnPopToRootRequested(object sender, NavigationRequestedEventArgs nre)
378                 {
379                         List<NaviItem> copyOfStack = new List<NaviItem>(_naviFrame.NavigationStack);
380                         NaviItem rootItem = copyOfStack.FirstOrDefault();
381                         NaviItem topItem = copyOfStack.LastOrDefault();
382
383                         foreach (NaviItem naviItem in copyOfStack)
384                                 if (naviItem != rootItem && naviItem != topItem)
385                                         naviItem.Delete();
386
387                         if (topItem != rootItem)
388                         {
389                                 UpdateNavigationBar(Element.Navigation.NavigationStack.Last(), rootItem);
390                                 if (nre.Animated)
391                                 {
392                                         _naviFrame.Pop();
393
394                                         _currentTaskSource = new TaskCompletionSource<bool>();
395                                         nre.Task = _currentTaskSource.Task;
396                                 }
397                                 else
398                                         topItem?.Delete();
399                         }
400
401                         _naviItemMap.Clear();
402                         _naviItemMap[Element.Navigation.NavigationStack.Last()] = rootItem;
403                 }
404
405                 void OnPushRequested(object sender, NavigationRequestedEventArgs nre)
406                 {
407                         if (nre.Animated || _naviFrame.NavigationStack.Count == 0)
408                         {
409                                 _naviItemMap[nre.Page] = _naviFrame.Push(CreateNavItem(nre.Page), SpanTitle(nre.Page.Title));
410                                 _currentTaskSource = new TaskCompletionSource<bool>();
411                                 nre.Task = _currentTaskSource.Task;
412
413                                 // There is no TransitionFinished (AnimationFinished) event after the first Push
414                                 if (_naviFrame.NavigationStack.Count == 1)
415                                         CompleteCurrentNavigationTask();
416                         }
417                         else
418                         {
419                                 _naviItemMap[nre.Page] = _naviFrame.InsertAfter(_naviFrame.NavigationStack.Last(), CreateNavItem(nre.Page), SpanTitle(nre.Page.Title));
420                         }
421                         UpdateHasNavigationBar(nre.Page);
422                 }
423
424                 void OnRemovePageRequested(object sender, NavigationRequestedEventArgs nre)
425                 {
426                         GetNaviItemForPage(nre.Page).Delete();
427                         if (_naviItemMap.ContainsKey(nre.Page))
428                                 _naviItemMap.Remove(nre.Page);
429                 }
430
431                 void OnInsertPageBeforeRequested(object sender, NavigationRequestedEventArgs nre)
432                 {
433                         if (nre.BeforePage == null)
434                                 throw new ArgumentException("BeforePage is null");
435                         if (nre.Page == null)
436                                 throw new ArgumentException("Page is null");
437
438                         _naviItemMap[nre.Page] = _naviFrame.InsertBefore(GetNaviItemForPage(nre.BeforePage), CreateNavItem(nre.Page), SpanTitle(nre.Page.Title));
439                         UpdateHasNavigationBar(nre.Page);
440                 }
441
442                 void OnAnimationFinished(object sender, EventArgs e)
443                 {
444                         CompleteCurrentNavigationTask();
445                 }
446
447                 void CompleteCurrentNavigationTask()
448                 {
449                         if (_currentTaskSource != null)
450                         {
451                                 var tmp = _currentTaskSource;
452                                 _currentTaskSource = null;
453                                 tmp.SetResult(true);
454                         }
455                 }
456
457                 EvasObject CreateNavItem(Page page)
458                 {
459                         View titleView = NavigationPage.GetTitleView(page);
460                         EvasObject nativeView = null;
461                         if (titleView != null)
462                         {
463                                 titleView.Parent = this.Element;
464                                 nativeView = new Native.TitleViewPage(Forms.NativeParent, page, titleView);
465                                 nativeView.Show();
466                         }
467                         else
468                         {
469                                 nativeView = Platform.GetOrCreateRenderer(page).NativeView;
470
471                         }
472                         return nativeView;
473                 }
474         }
475 }