瀏覽代碼

Root layouts refactor (#5999)

* Hide modal and overlay containers

This commit improves support for react-native-youtube library. This library uses the native Youtube player which requires that no visible views will be laid out on top of the player. This commit set visibility of Modal and Overlay containers to GONE when they are empty.

* fix tests
Guy Carmeli 4 年之前
父節點
當前提交
2793a02272
No account linked to committer's email address

+ 8
- 13
lib/android/app/src/main/java/com/reactnativenavigation/presentation/OverlayManager.java 查看文件

@@ -4,40 +4,36 @@ import android.view.View;
4 4
 import android.view.ViewGroup;
5 5
 
6 6
 import com.reactnativenavigation.utils.CommandListener;
7
-import com.reactnativenavigation.utils.ViewUtils;
8 7
 import com.reactnativenavigation.viewcontrollers.ViewController;
9 8
 import com.reactnativenavigation.views.BehaviourDelegate;
10 9
 
11 10
 import java.util.HashMap;
12 11
 
13
-import androidx.annotation.Nullable;
14
-
15 12
 import static com.reactnativenavigation.utils.CollectionUtils.*;
16 13
 import static com.reactnativenavigation.utils.CoordinatorLayoutUtils.matchParentWithBehaviour;
17 14
 
18 15
 public class OverlayManager {
19 16
     private final HashMap<String, ViewController> overlayRegistry = new HashMap<>();
20 17
 
21
-    public void show(@Nullable ViewGroup contentLayout, ViewGroup overlaysContainer, ViewController overlay, CommandListener listener) {
22
-        if (contentLayout == null) return;
23
-        if (overlaysContainer.getParent() == null) contentLayout.addView(overlaysContainer);
18
+    public void show(ViewGroup overlaysContainer, ViewController overlay, CommandListener listener) {
19
+        overlaysContainer.setVisibility(View.VISIBLE);
24 20
         overlayRegistry.put(overlay.getId(), overlay);
25 21
         overlay.addOnAppearedListener(() -> listener.onSuccess(overlay.getId()));
26 22
         overlaysContainer.addView(overlay.getView(), matchParentWithBehaviour(new BehaviourDelegate(overlay)));
27 23
     }
28 24
 
29
-    public void dismiss(String componentId, CommandListener listener) {
25
+    public void dismiss(ViewGroup overlaysContainer, String componentId, CommandListener listener) {
30 26
         ViewController overlay = overlayRegistry.get(componentId);
31 27
         if (overlay == null) {
32 28
             listener.onError("Could not dismiss Overlay. Overlay with id " + componentId + " was not found.");
33 29
         } else {
34
-            destroyOverlay(overlay);
30
+            destroyOverlay(overlaysContainer, overlay);
35 31
             listener.onSuccess(componentId);
36 32
         }
37 33
     }
38 34
 
39
-    public void destroy() {
40
-        forEach(overlayRegistry.values(), this::destroyOverlay);
35
+    public void destroy(ViewGroup overlaysContainer) {
36
+        forEach(overlayRegistry.values(), overlay -> destroyOverlay(overlaysContainer, overlay));
41 37
     }
42 38
 
43 39
     public int size() {
@@ -48,11 +44,10 @@ public class OverlayManager {
48 44
         return overlayRegistry.get(id);
49 45
     }
50 46
 
51
-    private void destroyOverlay(ViewController overlay) {
52
-        View parent = (View) overlay.getView().getParent();
47
+    private void destroyOverlay(ViewGroup overlaysContainer, ViewController overlay) {
53 48
         overlay.destroy();
54 49
         overlayRegistry.remove(overlay.getId());
55
-        if (isEmpty()) ViewUtils.removeFromParent(parent);
50
+        if (isEmpty()) overlaysContainer.setVisibility(View.GONE);
56 51
     }
57 52
 
58 53
     private boolean isEmpty() {

+ 7
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenter.java 查看文件

@@ -2,6 +2,7 @@ package com.reactnativenavigation.viewcontrollers.modal;
2 2
 
3 3
 import android.animation.Animator;
4 4
 import android.animation.AnimatorListenerAdapter;
5
+import android.view.View;
5 6
 import android.view.ViewGroup;
6 7
 
7 8
 import com.reactnativenavigation.anim.ModalAnimator;
@@ -46,6 +47,7 @@ public class ModalPresenter {
46 47
 
47 48
         Options options = toAdd.resolveCurrentOptions(defaultOptions);
48 49
         toAdd.setWaitForRender(options.animations.showModal.waitForRender);
50
+        modalsLayout.setVisibility(View.VISIBLE);
49 51
         modalsLayout.addView(toAdd.getView(), matchParentLP());
50 52
 
51 53
         if (options.animations.showModal.enabled.isTrueOrUndefined()) {
@@ -107,5 +109,10 @@ public class ModalPresenter {
107 109
     private void onDismissEnd(ViewController toDismiss, CommandListener listener) {
108 110
         listener.onSuccess(toDismiss.getId());
109 111
         toDismiss.destroy();
112
+        if (isEmpty()) modalsLayout.setVisibility(View.GONE);
113
+    }
114
+
115
+    private boolean isEmpty() {
116
+        return modalsLayout.getChildCount() == 0;
110 117
     }
111 118
 }

+ 9
- 3
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/navigator/Navigator.java 查看文件

@@ -64,6 +64,7 @@ public class Navigator extends ParentController {
64 64
         this.contentLayout = contentLayout;
65 65
         contentLayout.addView(rootLayout);
66 66
         contentLayout.addView(modalsLayout);
67
+        contentLayout.addView(overlaysLayout);
67 68
     }
68 69
 
69 70
     public Navigator(final Activity activity, ChildControllersRegistry childRegistry, ModalStack modalStack, OverlayManager overlayManager, RootPresenter rootPresenter) {
@@ -114,7 +115,7 @@ public class Navigator extends ParentController {
114 115
 
115 116
     public void destroyViews() {
116 117
         modalStack.destroy();
117
-        overlayManager.destroy();
118
+        overlayManager.destroy(overlaysLayout);
118 119
         destroyRoot();
119 120
     }
120 121
 
@@ -198,11 +199,11 @@ public class Navigator extends ParentController {
198 199
     }
199 200
 
200 201
     public void showOverlay(ViewController overlay, CommandListener listener) {
201
-        overlayManager.show(contentLayout, overlaysLayout, overlay, listener);
202
+        overlayManager.show(overlaysLayout, overlay, listener);
202 203
     }
203 204
 
204 205
     public void dismissOverlay(final String componentId, CommandListener listener) {
205
-        overlayManager.dismiss(componentId, listener);
206
+        overlayManager.dismiss(overlaysLayout, componentId, listener);
206 207
     }
207 208
 
208 209
     @Nullable
@@ -239,4 +240,9 @@ public class Navigator extends ParentController {
239 240
     CoordinatorLayout getModalsLayout() {
240 241
         return modalsLayout;
241 242
     }
243
+
244
+    @RestrictTo(RestrictTo.Scope.TESTS)
245
+    CoordinatorLayout getOverlaysLayout() {
246
+        return overlaysLayout;
247
+    }
242 248
 }

+ 8
- 0
lib/android/app/src/test/java/com/reactnativenavigation/BaseTest.java 查看文件

@@ -121,4 +121,12 @@ public abstract class BaseTest {
121 121
         when(mock.getContext()).thenReturn(activity);
122 122
         return mock;
123 123
     }
124
+
125
+    protected void assertVisible(View view) {
126
+        assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
127
+    }
128
+
129
+    protected void assertGone(View view) {
130
+        assertThat(view.getVisibility()).isEqualTo(View.GONE);
131
+    }
124 132
 }

+ 16
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenterTest.java 查看文件

@@ -257,4 +257,20 @@ public class ModalPresenterTest extends BaseTest {
257 257
         inOrder.verify(listener).onSuccess(modal.getId());
258 258
         inOrder.verify(modal).destroy();
259 259
     }
260
+
261
+    @Test
262
+    public void dismissModal_modalsLayoutIfHiddenIsAllModalsAreDismissed() {
263
+        disableShowModalAnimation(modal1, modal2);
264
+        disableDismissModalAnimation(modal1, modal2);
265
+
266
+        uut.showModal(modal1, root, new CommandListenerAdapter());
267
+        assertVisible(modalsLayout);
268
+        uut.showModal(modal2, modal1, new CommandListenerAdapter());
269
+        assertVisible(modalsLayout);
270
+
271
+        uut.dismissModal(modal2, modal1, root, new CommandListenerAdapter());
272
+        assertVisible(modalsLayout);
273
+        uut.dismissModal(modal1, root, root, new CommandListenerAdapter());
274
+        assertGone(modalsLayout);
275
+    }
260 276
 }

+ 3
- 3
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/NavigatorTest.java 查看文件

@@ -155,11 +155,11 @@ public class NavigatorTest extends BaseTest {
155 155
     @Test
156 156
     public void setRoot_clearsSplashLayout() {
157 157
         FrameLayout content = activity.findViewById(android.R.id.content);
158
-        assertThat(content.getChildCount()).isEqualTo(3); // 2 frame layouts (root and modal containers) and the default splash layout
158
+        assertThat(content.getChildCount()).isEqualTo(4); // 3 frame layouts (root, modal and overlay containers) and the default splash layout
159 159
 
160 160
         uut.setRoot(child2, new CommandListenerAdapter(), reactInstanceManager);
161 161
 
162
-        assertThat(content.getChildCount()).isEqualTo(2);
162
+        assertThat(content.getChildCount()).isEqualTo(3);
163 163
     }
164 164
 
165 165
     @Test
@@ -658,7 +658,7 @@ public class NavigatorTest extends BaseTest {
658 658
     public void destroy_destroyOverlayManager() {
659 659
         uut.setRoot(parentController, new CommandListenerAdapter(), reactInstanceManager);
660 660
         activityController.destroy();
661
-        verify(overlayManager).destroy();
661
+        verify(overlayManager).destroy(uut.getOverlaysLayout());
662 662
     }
663 663
 
664 664
     @Test

+ 18
- 15
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/overlay/OverlayManagerTest.java 查看文件

@@ -1,6 +1,7 @@
1 1
 package com.reactnativenavigation.viewcontrollers.overlay;
2 2
 
3 3
 import android.app.Activity;
4
+import android.view.View;
4 5
 import android.widget.FrameLayout;
5 6
 
6 7
 import com.reactnativenavigation.BaseTest;
@@ -36,6 +37,7 @@ public class OverlayManagerTest extends BaseTest {
36 37
         contentLayout.layout(0, 0, 1000, 1000);
37 38
         activity.setContentView(contentLayout);
38 39
         overlayContainer = new FrameLayout(activity);
40
+        contentLayout.addView(overlayContainer);
39 41
 
40 42
         ChildControllersRegistry childRegistry = new ChildControllersRegistry();
41 43
         overlay1 = spy(new SimpleViewController(activity, childRegistry, OVERLAY_ID_1, new Options()));
@@ -45,15 +47,15 @@ public class OverlayManagerTest extends BaseTest {
45 47
 
46 48
     @Test
47 49
     public void show_attachesOverlayContainerToContentLayout() {
48
-        uut.show(contentLayout, overlayContainer, overlay1, new CommandListenerAdapter());
50
+        uut.show(overlayContainer, overlay1, new CommandListenerAdapter());
49 51
         assertThat(overlayContainer.getParent()).isEqualTo(contentLayout);
50
-        uut.show(contentLayout, overlayContainer, overlay2, new CommandListenerAdapter());
52
+        uut.show(overlayContainer, overlay2, new CommandListenerAdapter());
51 53
     }
52 54
 
53 55
     @Test
54 56
     public void show() {
55 57
         CommandListenerAdapter listener = spy(new CommandListenerAdapter());
56
-        uut.show(contentLayout, overlayContainer, overlay1, listener);
58
+        uut.show(overlayContainer, overlay1, listener);
57 59
         verify(listener, times(1)).onSuccess(OVERLAY_ID_1);
58 60
         assertThat(overlay1.getView().getParent()).isEqualTo(overlayContainer);
59 61
         assertMatchParent(overlay1.getView());
@@ -61,10 +63,10 @@ public class OverlayManagerTest extends BaseTest {
61 63
 
62 64
     @Test
63 65
     public void dismiss() {
64
-        uut.show(contentLayout, overlayContainer, overlay1, new CommandListenerAdapter());
66
+        uut.show(overlayContainer, overlay1, new CommandListenerAdapter());
65 67
         assertThat(uut.size()).isOne();
66 68
         CommandListener listener = spy(new CommandListenerAdapter());
67
-        uut.dismiss(overlay1.getId(), listener);
69
+        uut.dismiss(overlayContainer, overlay1.getId(), listener);
68 70
         assertThat(uut.size()).isZero();
69 71
         verify(listener, times(1)).onSuccess(OVERLAY_ID_1);
70 72
         verify(overlay1, times(1)).destroy();
@@ -73,28 +75,29 @@ public class OverlayManagerTest extends BaseTest {
73 75
     @Test
74 76
     public void dismiss_rejectIfOverlayNotFound() {
75 77
         CommandListener listener = spy(new CommandListenerAdapter());
76
-        uut.dismiss(overlay1.getId(), listener);
78
+        uut.dismiss(overlayContainer, overlay1.getId(), listener);
77 79
         verify(listener, times(1)).onError(any());
78 80
     }
79 81
 
80 82
     @Test
81 83
     public void dismiss_onViewReturnedToFront() {
82
-        uut.show(contentLayout, overlayContainer, overlay1, new CommandListenerAdapter());
83
-        uut.show(contentLayout, overlayContainer, overlay2, new CommandListenerAdapter());
84
+        uut.show(overlayContainer, overlay1, new CommandListenerAdapter());
85
+        uut.show(overlayContainer, overlay2, new CommandListenerAdapter());
84 86
         verify(overlay1, times(0)).onViewBroughtToFront();
85 87
 
86
-        uut.dismiss(OVERLAY_ID_2, new CommandListenerAdapter());
88
+        uut.dismiss(overlayContainer, OVERLAY_ID_2, new CommandListenerAdapter());
87 89
         verify(overlay1, times(1)).onViewBroughtToFront();
88 90
     }
89 91
 
90 92
     @Test
91
-    public void dismiss_overlayContainerIsRemovedIfAllOverlaysAreDismissed() {
92
-        uut.show(contentLayout, overlayContainer, overlay1, new CommandListenerAdapter());
93
-        uut.show(contentLayout, overlayContainer, overlay2, new CommandListenerAdapter());
93
+    public void dismiss_overlayContainerIsHiddenIfAllOverlaysAreDismissed() {
94
+        uut.show(overlayContainer, overlay1, new CommandListenerAdapter());
95
+        uut.show(overlayContainer, overlay2, new CommandListenerAdapter());
94 96
 
95
-        uut.dismiss(OVERLAY_ID_2, new CommandListenerAdapter());
97
+        uut.dismiss(overlayContainer, OVERLAY_ID_2, new CommandListenerAdapter());
98
+        assertThat(overlayContainer.getVisibility()).isEqualTo(View.VISIBLE);
96 99
         assertThat(overlayContainer.getParent()).isEqualTo(contentLayout);
97
-        uut.dismiss(OVERLAY_ID_1, new CommandListenerAdapter());
98
-        assertThat(overlayContainer.getParent()).isNull();
100
+        uut.dismiss(overlayContainer, OVERLAY_ID_1, new CommandListenerAdapter());
101
+        assertThat(overlayContainer.getVisibility()).isEqualTo(View.GONE);
99 102
     }
100 103
 }