[NUI] Fix Navigator not to make ContentPage.Content hide
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / Navigation / Navigator.cs
1 /*
2  * Copyright(c) 2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 using System;
19 using System.Collections.Generic;
20 using System.ComponentModel;
21 using Tizen.NUI.BaseComponents;
22 using Tizen.NUI.Binding;
23
24 namespace Tizen.NUI.Components
25 {
26     /// <summary>
27     /// PoppedEventArgs is a class to record <see cref="Navigator.Popped"/> event arguments which will be sent to user.
28     /// </summary>
29     /// <since_tizen> 9 </since_tizen>
30     public class PoppedEventArgs : EventArgs
31     {
32         /// <summary>
33         /// Page popped by Navigator.
34         /// </summary>
35         /// <since_tizen> 9 </since_tizen>
36         public Page Page { get; internal set; }
37     }
38
39     /// <summary>
40     /// The Navigator is a class which navigates pages with stack methods such as Push and Pop.
41     /// </summary>
42     /// <remarks>
43     /// With Transition class, Navigator supports smooth transition of View pair between two Pages
44     /// by using <see cref="PushWithTransition(Page)"/> and <see cref="PopWithTransition()"/> methods.
45     /// If current top Page and next top Page have <see cref="View"/>s those have same TransitionTag,
46     /// Navigator creates smooth transition motion for them.
47     /// Navigator.Transition property can be used to set properties of the Transition such as TimePeriod and AlphaFunction.
48     /// When all transitions are finished, Navigator calls a callback methods those connected on the "TransitionFinished" event.
49     /// </remarks>
50     /// <example>
51     /// <code>
52     /// Navigator navigator = new Navigator()
53     /// {
54     ///     TimePeriod = new TimePeriod(500),
55     ///     AlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseInOutSine)
56     /// };
57     ///
58     /// View view = new View()
59     /// {
60     ///     TransitionOptions = new TransitionOptions()
61     ///     {
62     ///         /* Set properties for the transition of this View */
63     ///     }
64     /// };
65     ///
66     /// ContentPage newPage = new ContentPage()
67     /// {
68     ///     Content = view,
69     /// };
70     ///
71     /// Navigator.PushWithTransition(newPage);
72     /// </code>
73     /// </example>
74     /// <since_tizen> 9 </since_tizen>
75     public class Navigator : Control
76     {
77         /// <summary>
78         /// TransitionProperty
79         /// </summary>
80         [EditorBrowsable(EditorBrowsableState.Never)]
81         public static readonly BindableProperty TransitionProperty = BindableProperty.Create(nameof(Transition), typeof(Transition), typeof(Navigator), null, propertyChanged: (bindable, oldValue, newValue) =>
82         {
83             var instance = (Navigator)bindable;
84             if (newValue != null)
85             {
86                 instance.InternalTransition = newValue as Transition;
87             }
88         },
89         defaultValueCreator: (bindable) =>
90         {
91             var instance = (Navigator)bindable;
92             return instance.InternalTransition;
93         });
94
95         private const int DefaultTransitionDuration = 500;
96
97         //This will be replaced with view transition class instance.
98         private Animation curAnimation = null;
99
100         //This will be replaced with view transition class instance.
101         private Animation newAnimation = null;
102
103         private TransitionSet transitionSet = null;
104
105         private Transition transition = new Transition()
106         {
107             TimePeriod = new TimePeriod(DefaultTransitionDuration),
108             AlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Default),
109         };
110
111         private bool transitionFinished = true;
112
113         //TODO: Needs to consider how to remove disposed window from dictionary.
114         //Two dictionaries are required to remove disposed navigator from dictionary.
115         private static Dictionary<Window, Navigator> windowNavigator = new Dictionary<Window, Navigator>();
116         private static Dictionary<Navigator, Window> navigatorWindow = new Dictionary<Navigator, Window>();
117
118         private List<Page> navigationPages = new List<Page>();
119
120         /// <summary>
121         /// Creates a new instance of a Navigator.
122         /// </summary>
123         /// <since_tizen> 9 </since_tizen>
124         public Navigator() : base()
125         {
126             Layout = new AbsoluteLayout();
127         }
128
129         /// <inheritdoc/>
130         [EditorBrowsable(EditorBrowsableState.Never)]
131         public override void OnInitialize()
132         {
133             base.OnInitialize();
134
135             AccessibilityRole = Role.PageTabList;
136         }
137
138         /// <summary>
139         /// An event fired when Transition has been finished.
140         /// </summary>
141         /// <since_tizen> 9 </since_tizen>
142         public event EventHandler<EventArgs> TransitionFinished;
143
144         /// <summary>
145         /// An event fired when Pop of a page has been finished.
146         /// </summary>
147         /// <remarks>
148         /// When you free resources in the Popped event handler, please make sure if the popped page is the page you find.
149         /// </remarks>
150         /// <since_tizen> 9 </since_tizen>
151         public event EventHandler<PoppedEventArgs> Popped;
152
153         /// <summary>
154         /// Returns the count of pages in Navigator.
155         /// </summary>
156         /// <since_tizen> 9 </since_tizen>
157         public int PageCount => navigationPages.Count;
158
159         /// <summary>
160         /// Transition properties for the transition of View pair having same transition tag.
161         /// </summary>
162         /// <since_tizen> 9 </since_tizen>
163         public Transition Transition
164         {
165             get
166             {
167                 return GetValue(TransitionProperty) as Transition;
168             }
169             set
170             {
171                 SetValue(TransitionProperty, value);
172                 NotifyPropertyChanged();
173             }
174         }
175         private Transition InternalTransition
176         {
177             set
178             {
179                 transition = value;
180             }
181             get
182             {
183                 return transition;
184             }
185         }
186
187         /// <summary>
188         /// Pushes a page to Navigator.
189         /// If the page is already in Navigator, then it is not pushed.
190         /// </summary>
191         /// <param name="page">The page to push to Navigator.</param>
192         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
193         /// <since_tizen> 9 </since_tizen>
194         public void PushWithTransition(Page page)
195         {
196             if (!transitionFinished)
197             {
198                 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
199                 return;
200             }
201
202             if (page == null)
203             {
204                 throw new ArgumentNullException(nameof(page), "page should not be null.");
205             }
206
207             //Duplicate page is not pushed.
208             if (navigationPages.Contains(page)) return;
209
210             var topPage = Peek();
211
212             if (!topPage)
213             {
214                 Insert(0, page);
215                 return;
216             }
217
218             navigationPages.Add(page);
219             Add(page);
220             page.Navigator = this;
221
222             //Invoke Page events
223             page.InvokeAppearing();
224             topPage.InvokeDisappearing();
225
226             transitionSet = CreateTransitions(topPage, page, true);
227             transitionSet.Finished += (object sender, EventArgs e) =>
228             {
229                 if (page is DialogPage == false)
230                 {
231                     topPage.SetVisible(false);
232                 }
233
234                 // Need to update Content of the new page
235                 ShowContentOfPage(page);
236
237                 //Invoke Page events
238                 page.InvokeAppeared();
239                 topPage.InvokeDisappeared();
240                 NotifyAccessibilityStatesChangeOfPages(topPage, page);
241             };
242             transitionFinished = false;
243         }
244
245         /// <summary>
246         /// Pops the top page from Navigator.
247         /// </summary>
248         /// <returns>The popped page.</returns>
249         /// <exception cref="InvalidOperationException">Thrown when there is no page in Navigator.</exception>
250         /// <since_tizen> 9 </since_tizen>
251         public Page PopWithTransition()
252         {
253             if (!transitionFinished)
254             {
255                 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
256                 return null;
257             }
258
259             if (navigationPages.Count == 0)
260             {
261                 throw new InvalidOperationException("There is no page in Navigator.");
262             }
263
264             var topPage = Peek();
265
266             if (navigationPages.Count == 1)
267             {
268                 Remove(topPage);
269
270                 //Invoke Popped event
271                 Popped?.Invoke(this, new PoppedEventArgs() { Page = topPage });
272
273                 return topPage;
274             }
275             var newTopPage = navigationPages[navigationPages.Count - 2];
276
277             //Invoke Page events
278             newTopPage.InvokeAppearing();
279             topPage.InvokeDisappearing();
280
281             transitionSet = CreateTransitions(topPage, newTopPage, false);
282             transitionSet.Finished += (object sender, EventArgs e) =>
283             {
284                 Remove(topPage);
285                 topPage.SetVisible(true);
286
287                 // Need to update Content of the new page
288                 ShowContentOfPage(newTopPage);
289
290                 //Invoke Page events
291                 newTopPage.InvokeAppeared();
292                 topPage.InvokeDisappeared();
293
294                 //Invoke Popped event
295                 Popped?.Invoke(this, new PoppedEventArgs() { Page = topPage });
296             };
297             transitionFinished = false;
298
299             return topPage;
300         }
301
302         /// <summary>
303         /// Pushes a page to Navigator.
304         /// If the page is already in Navigator, then it is not pushed.
305         /// </summary>
306         /// <param name="page">The page to push to Navigator.</param>
307         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
308         /// <since_tizen> 9 </since_tizen>
309         public void Push(Page page)
310         {
311             if (!transitionFinished)
312             {
313                 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
314                 return;
315             }
316
317             if (page == null)
318             {
319                 throw new ArgumentNullException(nameof(page), "page should not be null.");
320             }
321
322             //Duplicate page is not pushed.
323             if (navigationPages.Contains(page)) return;
324
325             var curTop = Peek();
326
327             if (!curTop)
328             {
329                 Insert(0, page);
330                 return;
331             }
332
333             navigationPages.Add(page);
334             Add(page);
335             page.Navigator = this;
336
337             //Invoke Page events
338             page.InvokeAppearing();
339             curTop.InvokeDisappearing();
340
341             curTop.SaveKeyFocus();
342
343             //TODO: The following transition codes will be replaced with view transition.
344             InitializeAnimation();
345
346             if (page is DialogPage == false)
347             {
348                 curAnimation = new Animation(1000);
349                 curAnimation.AnimateTo(curTop, "Opacity", 1.0f, 0, 1000);
350                 curAnimation.EndAction = Animation.EndActions.StopFinal;
351                 curAnimation.Finished += (object sender, EventArgs args) =>
352                 {
353                     curTop.SetVisible(false);
354
355                     //Invoke Page events
356                     curTop.InvokeDisappeared();
357                 };
358                 curAnimation.Play();
359
360                 page.Opacity = 0.0f;
361                 page.SetVisible(true);
362                 // Set Content visible because it was hidden by HideContentOfPage.
363                 (page as ContentPage).Content?.SetVisible(true);
364
365                 newAnimation = new Animation(1000);
366                 newAnimation.AnimateTo(page, "Opacity", 1.0f, 0, 1000);
367                 newAnimation.EndAction = Animation.EndActions.StopFinal;
368                 newAnimation.Finished += (object sender, EventArgs e) =>
369                 {
370                     // Need to update Content of the new page
371                     ShowContentOfPage(page);
372
373                     //Invoke Page events
374                     page.InvokeAppeared();
375                     NotifyAccessibilityStatesChangeOfPages(curTop, page);
376
377                     page.RestoreKeyFocus();
378                 };
379                 newAnimation.Play();
380             }
381             else
382             {
383                 ShowContentOfPage(page);
384                 page.RestoreKeyFocus();
385             }
386         }
387
388         /// <summary>
389         /// Pops the top page from Navigator.
390         /// </summary>
391         /// <returns>The popped page.</returns>
392         /// <exception cref="InvalidOperationException">Thrown when there is no page in Navigator.</exception>
393         /// <since_tizen> 9 </since_tizen>
394         public Page Pop()
395         {
396             if (!transitionFinished)
397             {
398                 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
399                 return null;
400             }
401
402             if (navigationPages.Count == 0)
403             {
404                 throw new InvalidOperationException("There is no page in Navigator.");
405             }
406
407             var curTop = Peek();
408
409             if (navigationPages.Count == 1)
410             {
411                 Remove(curTop);
412
413                 //Invoke Popped event
414                 Popped?.Invoke(this, new PoppedEventArgs() { Page = curTop });
415
416                 return curTop;
417             }
418
419             var newTop = navigationPages[navigationPages.Count - 2];
420
421             //Invoke Page events
422             newTop.InvokeAppearing();
423             curTop.InvokeDisappearing();
424             curTop.SaveKeyFocus();
425
426             //TODO: The following transition codes will be replaced with view transition.
427             InitializeAnimation();
428
429             if (curTop is DialogPage == false)
430             {
431                 curAnimation = new Animation(1000);
432                 curAnimation.AnimateTo(curTop, "Opacity", 0.0f, 0, 1000);
433                 curAnimation.EndAction = Animation.EndActions.StopFinal;
434                 curAnimation.Finished += (object sender, EventArgs e) =>
435                 {
436                     //Removes the current top page after transition is finished.
437                     Remove(curTop);
438                     curTop.Opacity = 1.0f;
439
440                     //Invoke Page events
441                     curTop.InvokeDisappeared();
442
443                     //Invoke Popped event
444                     Popped?.Invoke(this, new PoppedEventArgs() { Page = curTop });
445                 };
446                 curAnimation.Play();
447
448                 newTop.Opacity = 1.0f;
449                 newTop.SetVisible(true);
450                 // Set Content visible because it was hidden by HideContentOfPage.
451                 (newTop as ContentPage).Content?.SetVisible(true);
452
453                 newAnimation = new Animation(1000);
454                 newAnimation.AnimateTo(newTop, "Opacity", 1.0f, 0, 1000);
455                 newAnimation.EndAction = Animation.EndActions.StopFinal;
456                 newAnimation.Finished += (object sender, EventArgs e) =>
457                 {
458                     // Need to update Content of the new page
459                     ShowContentOfPage(newTop);
460
461                     //Invoke Page events
462                     newTop.InvokeAppeared();
463
464                     newTop.RestoreKeyFocus();
465                 };
466                 newAnimation.Play();
467             }
468             else
469             {
470                 Remove(curTop);
471             }
472
473             return curTop;
474         }
475
476         /// <summary>
477         /// Returns the page of the given index in Navigator.
478         /// The indices of pages in Navigator are basically the order of pushing or inserting to Navigator.
479         /// So a page's index in Navigator can be changed whenever push/insert or pop/remove occurs.
480         /// </summary>
481         /// <param name="index">The index of a page in Navigator.</param>
482         /// <returns>The page of the given index in Navigator.</returns>
483         /// <exception cref="ArgumentOutOfRangeException">Thrown when the argument index is less than 0, or greater than the number of pages.</exception>
484         public Page GetPage(int index)
485         {
486             if ((index < 0) || (index > navigationPages.Count))
487             {
488                 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than or equal to the number of pages.");
489             }
490
491             return navigationPages[index];
492         }
493
494         /// <summary>
495         /// Returns the current index of the given page in Navigator.
496         /// The indices of pages in Navigator are basically the order of pushing or inserting to Navigator.
497         /// So a page's index in Navigator can be changed whenever push/insert or pop/remove occurs.
498         /// </summary>
499         /// <param name="page">The page in Navigator.</param>
500         /// <returns>The index of the given page in Navigator. If the given page is not in the Navigator, then -1 is returned.</returns>
501         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
502         /// <since_tizen> 9 </since_tizen>
503         public int IndexOf(Page page)
504         {
505             if (page == null)
506             {
507                 throw new ArgumentNullException(nameof(page), "page should not be null.");
508             }
509
510             for (int i = 0; i < navigationPages.Count; i++)
511             {
512                 if (navigationPages[i] == page)
513                 {
514                     return i;
515                 }
516             }
517
518             return -1;
519         }
520
521         /// <summary>
522         /// Inserts a page at the specified index of Navigator.
523         /// The indices of pages in Navigator are basically the order of pushing or inserting to Navigator.
524         /// So a page's index in Navigator can be changed whenever push/insert or pop/remove occurs.
525         /// To find the current index of a page in Navigator, please use IndexOf(page).
526         /// If the page is already in Navigator, then it is not inserted.
527         /// </summary>
528         /// <param name="index">The index of a page in Navigator where the page will be inserted.</param>
529         /// <param name="page">The page to insert to Navigator.</param>
530         /// <exception cref="ArgumentOutOfRangeException">Thrown when the argument index is less than 0, or greater than the number of pages.</exception>
531         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
532         /// <since_tizen> 9 </since_tizen>
533         public void Insert(int index, Page page)
534         {
535             if ((index < 0) || (index > navigationPages.Count))
536             {
537                 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than or equal to the number of pages.");
538             }
539
540             if (page == null)
541             {
542                 throw new ArgumentNullException(nameof(page), "page should not be null.");
543             }
544
545             //Duplicate page is not pushed.
546             if (navigationPages.Contains(page)) return;
547
548             //TODO: The following transition codes will be replaced with view transition.
549             InitializeAnimation();
550
551             ShowContentOfPage(page);
552
553             if (index == PageCount)
554             {
555                 page.Opacity = 1.0f;
556                 page.SetVisible(true);
557             }
558             else
559             {
560                 page.SetVisible(false);
561                 page.Opacity = 0.0f;
562             }
563
564             navigationPages.Insert(index, page);
565             Add(page);
566             page.Navigator = this;
567             if (index == PageCount - 1)
568             {
569                 if (PageCount > 1)
570                 {
571                     NotifyAccessibilityStatesChangeOfPages(navigationPages[PageCount - 2], page);
572                 }
573                 else
574                 {
575                     NotifyAccessibilityStatesChangeOfPages(null, page);
576                 }
577             }
578         }
579
580         /// <summary>
581         /// Inserts a page to Navigator before an existing page.
582         /// If the page is already in Navigator, then it is not inserted.
583         /// </summary>
584         /// <param name="before">The existing page, before which a page will be inserted.</param>
585         /// <param name="page">The page to insert to Navigator.</param>
586         /// <exception cref="ArgumentNullException">Thrown when the argument before is null.</exception>
587         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
588         /// <exception cref="ArgumentException">Thrown when the argument before does not exist in Navigator.</exception>
589         /// <since_tizen> 9 </since_tizen>
590         public void InsertBefore(Page before, Page page)
591         {
592             if (before == null)
593             {
594                 throw new ArgumentNullException(nameof(before), "before should not be null.");
595             }
596
597             if (page == null)
598             {
599                 throw new ArgumentNullException(nameof(page), "page should not be null.");
600             }
601
602             //Find the index of before page.
603             int beforeIndex = navigationPages.FindIndex(x => x == before);
604
605             //before does not exist in Navigator.
606             if (beforeIndex == -1)
607             {
608                 throw new ArgumentException("before does not exist in Navigator.", nameof(before));
609             }
610
611             Insert(beforeIndex, page);
612         }
613
614         /// <summary>
615         /// Removes a page from Navigator.
616         /// </summary>
617         /// <param name="page">The page to remove from Navigator.</param>
618         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
619         /// <since_tizen> 9 </since_tizen>
620         public void Remove(Page page)
621         {
622             if (page == null)
623             {
624                 throw new ArgumentNullException(nameof(page), "page should not be null.");
625             }
626
627             //TODO: The following transition codes will be replaced with view transition.
628             InitializeAnimation();
629
630             HideContentOfPage(page);
631
632             if (page == Peek())
633             {
634                 if (PageCount >= 2)
635                 {
636                     navigationPages[PageCount - 2].Opacity = 1.0f;
637                     navigationPages[PageCount - 2].SetVisible(true);
638                     NotifyAccessibilityStatesChangeOfPages(page, navigationPages[PageCount - 2]);
639                 }
640                 else if (PageCount == 1)
641                 {
642                     NotifyAccessibilityStatesChangeOfPages(page, null);
643                 }
644             }
645             page.Navigator = null;
646             navigationPages.Remove(page);
647             base.Remove(page);
648         }
649
650         /// <summary>
651         /// Removes a page at the specified index of Navigator.
652         /// The indices of pages in Navigator are basically the order of pushing or inserting to Navigator.
653         /// So a page's index in Navigator can be changed whenever push/insert or pop/remove occurs.
654         /// To find the current index of a page in Navigator, please use IndexOf(page).
655         /// </summary>
656         /// <param name="index">The index of a page in Navigator where the page will be removed.</param>
657         /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is less than 0, or greater than or equal to the number of pages.</exception>
658         /// <since_tizen> 9 </since_tizen>
659         public void RemoveAt(int index)
660         {
661             if ((index < 0) || (index >= navigationPages.Count))
662             {
663                 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than the number of pages.");
664             }
665
666             Remove(navigationPages[index]);
667         }
668
669         /// <summary>
670         /// Returns the page at the top of Navigator.
671         /// </summary>
672         /// <returns>The page at the top of Navigator.</returns>
673         /// <since_tizen> 9 </since_tizen>
674         public Page Peek()
675         {
676             if (navigationPages.Count == 0) return null;
677
678             return navigationPages[navigationPages.Count - 1];
679         }
680
681         /// <summary>
682         /// Disposes Navigator and all children on it.
683         /// </summary>
684         /// <param name="type">Dispose type.</param>
685         [EditorBrowsable(EditorBrowsableState.Never)]
686         protected override void Dispose(DisposeTypes type)
687         {
688             if (disposed)
689             {
690                 return;
691             }
692
693             if (type == DisposeTypes.Explicit)
694             {
695                 foreach (Page page in navigationPages)
696                 {
697                     Utility.Dispose(page);
698                 }
699                 navigationPages.Clear();
700
701                 Window window;
702
703                 if (navigatorWindow.TryGetValue(this, out window) == true)
704                 {
705                     navigatorWindow.Remove(this);
706                     windowNavigator.Remove(window);
707                 }
708             }
709
710             base.Dispose(type);
711         }
712
713         /// <summary>
714         /// Returns the default navigator of the given window.
715         /// </summary>
716         /// <returns>The default navigator of the given window.</returns>
717         /// <exception cref="ArgumentNullException">Thrown when the argument window is null.</exception>
718         /// <since_tizen> 9 </since_tizen>
719         public static Navigator GetDefaultNavigator(Window window)
720         {
721             if (window == null)
722             {
723                 throw new ArgumentNullException(nameof(window), "window should not be null.");
724             }
725
726             if (windowNavigator.ContainsKey(window) == true)
727             {
728                 return windowNavigator[window];
729             }
730
731             var defaultNavigator = new Navigator();
732             defaultNavigator.WidthResizePolicy = ResizePolicyType.FillToParent;
733             defaultNavigator.HeightResizePolicy = ResizePolicyType.FillToParent;
734             window.Add(defaultNavigator);
735             windowNavigator.Add(window, defaultNavigator);
736             navigatorWindow.Add(defaultNavigator, window);
737
738             return defaultNavigator;
739         }
740
741         /// <summary>
742         /// Create Transitions between currentTopPage and newTopPage
743         /// </summary>
744         /// <param name="currentTopPage">The top page of Navigator.</param>
745         /// <param name="newTopPage">The new top page after transition.</param>
746         /// <param name="pushTransition">True if this transition is for push new page</param>
747         private TransitionSet CreateTransitions(Page currentTopPage, Page newTopPage, bool pushTransition)
748         {
749             currentTopPage.SetVisible(true);
750             // Set Content visible because it was hidden by HideContentOfPage.
751             (currentTopPage as ContentPage).Content?.SetVisible(true);
752
753             newTopPage.SetVisible(true);
754             // Set Content visible because it was hidden by HideContentOfPage.
755             (newTopPage as ContentPage).Content?.SetVisible(true);
756
757             List<View> taggedViewsInNewTopPage = new List<View>();
758             RetrieveTaggedViews(taggedViewsInNewTopPage, newTopPage, true);
759             List<View> taggedViewsInCurrentTopPage = new List<View>();
760             RetrieveTaggedViews(taggedViewsInCurrentTopPage, currentTopPage, true);
761
762             List<KeyValuePair<View, View>> sameTaggedViewPair = new List<KeyValuePair<View, View>>();
763             foreach (View currentTopPageView in taggedViewsInCurrentTopPage)
764             {
765                 bool findPair = false;
766                 foreach (View newTopPageView in taggedViewsInNewTopPage)
767                 {
768                     if ((currentTopPageView.TransitionOptions != null) && (newTopPageView.TransitionOptions != null) &&
769                         currentTopPageView.TransitionOptions?.TransitionTag == newTopPageView.TransitionOptions?.TransitionTag)
770                     {
771                         sameTaggedViewPair.Add(new KeyValuePair<View, View>(currentTopPageView, newTopPageView));
772                         findPair = true;
773                         break;
774                     }
775                 }
776                 if (findPair)
777                 {
778                     taggedViewsInNewTopPage.Remove(sameTaggedViewPair[sameTaggedViewPair.Count - 1].Value);
779                 }
780             }
781             foreach (KeyValuePair<View, View> pair in sameTaggedViewPair)
782             {
783                 taggedViewsInCurrentTopPage.Remove(pair.Key);
784             }
785
786             TransitionSet newTransitionSet = new TransitionSet();
787             foreach (KeyValuePair<View, View> pair in sameTaggedViewPair)
788             {
789                 TransitionItem pairTransition = transition.CreateTransition(pair.Key, pair.Value, pushTransition);
790                 if (pair.Value.TransitionOptions?.TransitionWithChild ?? false)
791                 {
792                     pairTransition.TransitionWithChild = true;
793                 }
794                 newTransitionSet.AddTransition(pairTransition);
795             }
796
797             newTransitionSet.Finished += (object sender, EventArgs e) =>
798             {
799                 if (newTopPage.Layout != null)
800                 {
801                     newTopPage.Layout.RequestLayout();
802                 }
803                 if (currentTopPage.Layout != null)
804                 {
805                     currentTopPage.Layout.RequestLayout();
806                 }
807                 transitionFinished = true;
808                 InvokeTransitionFinished();
809                 transitionSet.Dispose();
810                 currentTopPage.Opacity = 1.0f;
811             };
812
813             if (!pushTransition || newTopPage is DialogPage == false)
814             {
815                 View transitionView = (currentTopPage is ContentPage) ? (currentTopPage as ContentPage).Content : (currentTopPage as DialogPage).Content;
816                 if (currentTopPage.DisappearingTransition != null && transitionView != null)
817                 {
818                     TransitionItemBase disappearingTransition = currentTopPage.DisappearingTransition.CreateTransition(transitionView, false);
819                     disappearingTransition.TransitionWithChild = true;
820                     newTransitionSet.AddTransition(disappearingTransition);
821                 }
822                 else
823                 {
824                     currentTopPage.SetVisible(false);
825                 }
826             }
827             if (pushTransition || currentTopPage is DialogPage == false)
828             {
829                 View transitionView = (newTopPage is ContentPage) ? (newTopPage as ContentPage).Content : (newTopPage as DialogPage).Content;
830                 if (newTopPage.AppearingTransition != null && transitionView != null)
831                 {
832                     TransitionItemBase appearingTransition = newTopPage.AppearingTransition.CreateTransition(transitionView, true);
833                     appearingTransition.TransitionWithChild = true;
834                     newTransitionSet.AddTransition(appearingTransition);
835                 }
836             }
837
838             newTransitionSet.Play();
839
840             return newTransitionSet;
841         }
842
843         /// <summary>
844         /// Retrieve Tagged Views in the view tree.
845         /// </summary>
846         /// <param name="taggedViews">Returned tagged view list..</param>
847         /// <param name="view">Root View to get tagged child View.</param>
848         /// <param name="isRoot">Flag to check current View is page or not</param>
849         private void RetrieveTaggedViews(List<View> taggedViews, View view, bool isRoot)
850         {
851             if (!isRoot && view.TransitionOptions != null)
852             {
853                 if (!string.IsNullOrEmpty(view.TransitionOptions?.TransitionTag))
854                 {
855                     taggedViews.Add((view as View));
856                     if (view.TransitionOptions.TransitionWithChild)
857                     {
858                         return;
859                     }
860                 }
861
862             }
863
864             foreach (View child in view.Children)
865             {
866                 RetrieveTaggedViews(taggedViews, child, false);
867             }
868         }
869
870         /// <summary>
871         /// Notify accessibility states change of pages.
872         /// </summary>
873         /// <param name="disappearedPage">Disappeared page</param>
874         /// <param name="appearedPage">Appeared page</param>
875         private void NotifyAccessibilityStatesChangeOfPages(Page disappearedPage, Page appearedPage)
876         {
877             if (disappearedPage != null)
878             {
879                 disappearedPage.UnregisterDefaultLabel();
880                 //We can call disappearedPage.NotifyAccessibilityStatesChange
881                 //To reduce accessibility events, we are using currently highlighted view instead
882                 View curHighlightedView = Accessibility.Accessibility.GetCurrentlyHighlightedView();
883                 if (curHighlightedView != null)
884                 {
885                     curHighlightedView.NotifyAccessibilityStatesChange(new AccessibilityStates(AccessibilityState.Visible, AccessibilityState.Showing), AccessibilityStatesNotifyMode.Single);
886                 }
887             }
888
889             if (appearedPage != null)
890             {
891                 appearedPage.RegisterDefaultLabel();
892                 appearedPage.NotifyAccessibilityStatesChange(new AccessibilityStates(AccessibilityState.Visible, AccessibilityState.Showing), AccessibilityStatesNotifyMode.Single);
893             }
894         }
895
896         internal void InvokeTransitionFinished()
897         {
898             TransitionFinished?.Invoke(this, new EventArgs());
899         }
900
901         //TODO: The following transition codes will be replaced with view transition.
902         private void InitializeAnimation()
903         {
904             if (curAnimation != null)
905             {
906                 curAnimation.Stop();
907                 curAnimation.Clear();
908                 curAnimation = null;
909             }
910
911             if (newAnimation != null)
912             {
913                 newAnimation.Stop();
914                 newAnimation.Clear();
915                 newAnimation = null;
916             }
917         }
918
919         // Show and Register Content of Page to Accessibility bridge
920         private void ShowContentOfPage(Page page)
921         {
922             View content = (page is DialogPage) ? (page as DialogPage)?.Content : (page as ContentPage)?.Content;
923             if (content != null)
924             {
925                 content.Show(); // Calls RegisterDefaultLabel()
926             }
927         }
928
929         // Hide and Remove Content of Page from Accessibility bridge
930         private void HideContentOfPage(Page page)
931         {
932             View content = (page is DialogPage) ? (page as DialogPage)?.Content : (page as ContentPage)?.Content;
933             if (content != null)
934             {
935                 content.Hide(); // Calls UnregisterDefaultLabel()
936             }
937         }
938     }
939 }