ソースを参照

Set stack root refactor (#4460)

This PR adds support for setting stack root with multiple children, which is to be expected as stack can be initialised with multiple children.
Closes #4441
Guy Carmeli 5 年 前
コミット
2365e0211b
No account linked to committer's email address

+ 9
- 4
lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java ファイルの表示

@@ -9,6 +9,7 @@ import com.facebook.react.bridge.Promise;
9 9
 import com.facebook.react.bridge.ReactApplicationContext;
10 10
 import com.facebook.react.bridge.ReactContextBaseJavaModule;
11 11
 import com.facebook.react.bridge.ReactMethod;
12
+import com.facebook.react.bridge.ReadableArray;
12 13
 import com.facebook.react.bridge.ReadableMap;
13 14
 import com.facebook.react.bridge.WritableMap;
14 15
 import com.reactnativenavigation.NavigationActivity;
@@ -27,6 +28,7 @@ import com.reactnativenavigation.viewcontrollers.ViewController;
27 28
 import com.reactnativenavigation.viewcontrollers.externalcomponent.ExternalComponentCreator;
28 29
 import com.reactnativenavigation.viewcontrollers.navigator.Navigator;
29 30
 
31
+import java.util.ArrayList;
30 32
 import java.util.Map;
31 33
 
32 34
 public class NavigationModule extends ReactContextBaseJavaModule {
@@ -89,11 +91,14 @@ public class NavigationModule extends ReactContextBaseJavaModule {
89 91
 	}
90 92
 
91 93
     @ReactMethod
92
-    public void setStackRoot(String commandId, String onComponentId, ReadableMap rawLayoutTree, Promise promise) {
93
-        final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree));
94
+    public void setStackRoot(String commandId, String onComponentId, ReadableArray children, Promise promise) {
94 95
         handle(() -> {
95
-            final ViewController viewController = newLayoutFactory().create(layoutTree);
96
-            navigator().setStackRoot(onComponentId, viewController, new NativeCommandListener(commandId, promise, eventEmitter, now));
96
+            ArrayList<ViewController> _children = new ArrayList();
97
+            for (int i = 0; i < children.size(); i++) {
98
+                final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(children.getMap(i)));
99
+                _children.add(newLayoutFactory().create(layoutTree));
100
+            }
101
+            navigator().setStackRoot(onComponentId, _children, new NativeCommandListener(commandId, promise, eventEmitter, now));
97 102
         });
98 103
     }
99 104
 

+ 8
- 0
lib/android/app/src/main/java/com/reactnativenavigation/utils/CollectionUtils.java ファイルの表示

@@ -83,6 +83,14 @@ public class CollectionUtils {
83 83
         return null;
84 84
     }
85 85
 
86
+    public static <T> T last(@Nullable List<T> items) {
87
+        return CollectionUtils.isNullOrEmpty(items) ? null : items.get(items.size() - 1);
88
+    }
89
+
90
+    public static <T> T removeLast(@NonNull List<T> items) {
91
+        return items.remove(items.size() - 1);
92
+    }
93
+
86 94
     private static @NonNull <T> Collection<T> get(@Nullable Collection<T> t) {
87 95
         return t == null ? Collections.EMPTY_LIST : t;
88 96
     }

+ 25
- 7
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/IdStack.java ファイルの表示

@@ -4,33 +4,42 @@ import android.support.annotation.NonNull;
4 4
 
5 5
 import com.reactnativenavigation.utils.StringUtils;
6 6
 
7
-import java.util.ArrayDeque;
7
+import java.util.ArrayList;
8 8
 import java.util.Collection;
9 9
 import java.util.HashMap;
10 10
 import java.util.Iterator;
11
+import java.util.Map;
12
+
13
+import static com.reactnativenavigation.utils.CollectionUtils.last;
14
+import static com.reactnativenavigation.utils.CollectionUtils.removeLast;
11 15
 
12 16
 public class IdStack<E> implements Iterable<String> {
13 17
 
14
-	private final ArrayDeque<String> deque = new ArrayDeque<>();
15
-	private final HashMap<String, E> map = new HashMap<>();
18
+	private final ArrayList<String> deque = new ArrayList();
19
+	private final Map<String, E> map = new HashMap<>();
16 20
 
17 21
 	public void push(String id, E item) {
18
-		deque.push(id);
22
+		deque.add(id);
19 23
 		map.put(id, item);
20 24
 	}
21 25
 
26
+    public void set(String id, E item, int index) {
27
+        deque.add(index, id);
28
+        map.put(id, item);
29
+    }
30
+
22 31
 	public E peek() {
23 32
 		if (isEmpty()) {
24 33
 			return null;
25 34
 		}
26
-		return map.get(deque.peek());
35
+		return map.get(last(deque));
27 36
 	}
28 37
 
29 38
 	public E pop() {
30 39
 		if (isEmpty()) {
31 40
 			return null;
32 41
 		}
33
-		return map.remove(deque.pop());
42
+		return map.remove(removeLast(deque));
34 43
 	}
35 44
 
36 45
 	public boolean isEmpty() {
@@ -42,7 +51,7 @@ public class IdStack<E> implements Iterable<String> {
42 51
 	}
43 52
 
44 53
 	public String peekId() {
45
-		return deque.peek();
54
+		return last(deque);
46 55
 	}
47 56
 
48 57
 	public void clear() {
@@ -54,6 +63,10 @@ public class IdStack<E> implements Iterable<String> {
54 63
 		return map.get(id);
55 64
 	}
56 65
 
66
+	public E get(final int index) {
67
+        return map.get(deque.get(index));
68
+    }
69
+
57 70
 	public boolean containsId(final String id) {
58 71
 		return deque.contains(id);
59 72
 	}
@@ -80,4 +93,9 @@ public class IdStack<E> implements Iterable<String> {
80 93
 	public Collection<E> values() {
81 94
 		return map.values();
82 95
 	}
96
+
97
+    public void remove(Iterator<String> iterator, String id) {
98
+        iterator.remove();
99
+        map.remove(id);
100
+    }
83 101
 }

+ 4
- 3
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/navigator/Navigator.java ファイルの表示

@@ -8,8 +8,8 @@ import android.view.ViewGroup;
8 8
 import android.widget.FrameLayout;
9 9
 
10 10
 import com.reactnativenavigation.parse.Options;
11
-import com.reactnativenavigation.presentation.Presenter;
12 11
 import com.reactnativenavigation.presentation.OverlayManager;
12
+import com.reactnativenavigation.presentation.Presenter;
13 13
 import com.reactnativenavigation.react.EventEmitter;
14 14
 import com.reactnativenavigation.utils.CommandListener;
15 15
 import com.reactnativenavigation.utils.CompatUtils;
@@ -22,6 +22,7 @@ import com.reactnativenavigation.viewcontrollers.stack.StackController;
22 22
 
23 23
 import java.util.Collection;
24 24
 import java.util.Collections;
25
+import java.util.List;
25 26
 
26 27
 public class Navigator extends ParentController {
27 28
 
@@ -148,8 +149,8 @@ public class Navigator extends ParentController {
148 149
         applyOnStack(id, listener, stack -> stack.push(viewController, listener));
149 150
     }
150 151
 
151
-    public void setStackRoot(String id, ViewController viewController, CommandListener listener) {
152
-        applyOnStack(id, listener, stack -> stack.setRoot(viewController, listener));
152
+    public void setStackRoot(String id, List<ViewController> children, CommandListener listener) {
153
+        applyOnStack(id, listener, stack -> stack.setRoot(children, listener));
153 154
     }
154 155
 
155 156
     public void pop(String id, Options mergeOptions, CommandListener listener) {

+ 44
- 19
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java ファイルの表示

@@ -14,6 +14,7 @@ import com.reactnativenavigation.parse.Options;
14 14
 import com.reactnativenavigation.presentation.Presenter;
15 15
 import com.reactnativenavigation.presentation.StackPresenter;
16 16
 import com.reactnativenavigation.react.Constants;
17
+import com.reactnativenavigation.utils.CollectionUtils;
17 18
 import com.reactnativenavigation.utils.CommandListener;
18 19
 import com.reactnativenavigation.utils.CommandListenerAdapter;
19 20
 import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
@@ -183,15 +184,34 @@ public class StackController extends ParentController<StackLayout> {
183 184
         getView().addView(view, getView().getChildCount() - 1);
184 185
     }
185 186
 
186
-    public void setRoot(ViewController child, CommandListener listener) {
187
-        backButtonHelper.clear(child);
188
-        push(child, new CommandListenerAdapter() {
189
-            @Override
190
-            public void onSuccess(String childId) {
191
-                removeChildrenBellowTop();
192
-                listener.onSuccess(childId);
193
-            }
194
-        });
187
+    public void setRoot(List<ViewController> children, CommandListener listener) {
188
+        if (children.size() == 1) {
189
+            backButtonHelper.clear(CollectionUtils.last(children));
190
+            push(CollectionUtils.last(children), new CommandListenerAdapter() {
191
+                @Override
192
+                public void onSuccess(String childId) {
193
+                    removeChildrenBellowTop();
194
+                    listener.onSuccess(childId);
195
+                }
196
+            });
197
+        } else {
198
+            push(CollectionUtils.last(children), new CommandListenerAdapter() {
199
+                @Override
200
+                public void onSuccess(String childId) {
201
+                    removeChildrenBellowTop();
202
+                    for (int i = 0; i < children.size() - 1; i++) {
203
+                        stack.set(children.get(i).getId(), children.get(i), i);
204
+                        children.get(i).setParentController(StackController.this);
205
+                        if (i == 0) {
206
+                            backButtonHelper.clear(children.get(i));
207
+                        } else {
208
+                            backButtonHelper.addToPushedChild(children.get(i));
209
+                        }
210
+                    }
211
+                    listener.onSuccess(childId);
212
+                }
213
+            });
214
+        }
195 215
     }
196 216
 
197 217
     private void removeChildrenBellowTop() {
@@ -199,7 +219,8 @@ public class StackController extends ParentController<StackLayout> {
199 219
         while (stack.size() > 1) {
200 220
             ViewController controller = stack.get(iterator.next());
201 221
             if (!stack.isTop(controller.getId())) {
202
-                removeAndDestroyController(controller);
222
+                stack.remove(iterator, controller.getId());
223
+                controller.destroy();
203 224
             }
204 225
         }
205 226
     }
@@ -247,15 +268,17 @@ public class StackController extends ParentController<StackLayout> {
247 268
             return;
248 269
         }
249 270
 
250
-        Iterator<String> iterator = stack.iterator();
251
-        String currentControlId = iterator.next();
252
-        while (!viewController.getId().equals(currentControlId)) {
253
-            if (stack.isTop(currentControlId)) {
254
-                currentControlId = iterator.next();
255
-                continue;
271
+
272
+        String currentControlId;
273
+        for (int i = stack.size() - 2; i >= 0; i--) {
274
+            currentControlId = stack.get(i).getId();
275
+            if (currentControlId.equals(viewController.getId())) {
276
+                break;
256 277
             }
257
-            removeAndDestroyController(stack.get(currentControlId));
258
-            currentControlId = iterator.next();
278
+
279
+            ViewController controller = stack.get(currentControlId);
280
+            stack.remove(controller.getId());
281
+            controller.destroy();
259 282
         }
260 283
 
261 284
         pop(mergeOptions, listener);
@@ -268,10 +291,12 @@ public class StackController extends ParentController<StackLayout> {
268 291
         }
269 292
 
270 293
         Iterator<String> iterator = stack.iterator();
294
+        iterator.next();
271 295
         while (stack.size() > 2) {
272 296
             ViewController controller = stack.get(iterator.next());
273 297
             if (!stack.isTop(controller.getId())) {
274
-                removeAndDestroyController(controller);
298
+                stack.remove(iterator, controller.getId());
299
+                controller.destroy();
275 300
             }
276 301
         }
277 302
 

+ 13
- 13
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/IdStackTest.java ファイルの表示

@@ -17,21 +17,21 @@ public class IdStackTest extends BaseTest {
17 17
     }
18 18
 
19 19
     @Test
20
-    public void isEmpty() throws Exception {
20
+    public void isEmpty() {
21 21
         assertThat(uut.isEmpty()).isTrue();
22 22
         uut.push("123", 123);
23 23
         assertThat(uut.isEmpty()).isFalse();
24 24
     }
25 25
 
26 26
     @Test
27
-    public void size() throws Exception {
27
+    public void size() {
28 28
         assertThat(uut.size()).isEqualTo(0);
29 29
         uut.push("123", 123);
30 30
         assertThat(uut.size()).isEqualTo(1);
31 31
     }
32 32
 
33 33
     @Test
34
-    public void peek() throws Exception {
34
+    public void peek() {
35 35
         assertThat(uut.peek()).isNull();
36 36
         uut.push("123", 123);
37 37
         uut.push("456", 456);
@@ -39,7 +39,7 @@ public class IdStackTest extends BaseTest {
39 39
     }
40 40
 
41 41
     @Test
42
-    public void pop() throws Exception {
42
+    public void pop() {
43 43
         assertThat(uut.pop()).isNull();
44 44
         uut.push("123", 123);
45 45
         uut.push("456", 456);
@@ -47,14 +47,14 @@ public class IdStackTest extends BaseTest {
47 47
     }
48 48
 
49 49
     @Test
50
-    public void peekId() throws Exception {
50
+    public void peekId() {
51 51
         assertThat(uut.peekId()).isNull();
52 52
         uut.push("123", 123);
53 53
         assertThat(uut.peekId()).isEqualTo("123");
54 54
     }
55 55
 
56 56
     @Test
57
-    public void clear() throws Exception {
57
+    public void clear() {
58 58
         uut.push("123", 123);
59 59
         uut.push("456", 456);
60 60
         uut.clear();
@@ -62,7 +62,7 @@ public class IdStackTest extends BaseTest {
62 62
     }
63 63
 
64 64
     @Test
65
-    public void getById() throws Exception {
65
+    public void getById() {
66 66
         assertThat(uut.get("123")).isNull();
67 67
         uut.push("123", 123);
68 68
         uut.push("456", 456);
@@ -70,14 +70,14 @@ public class IdStackTest extends BaseTest {
70 70
     }
71 71
 
72 72
     @Test
73
-    public void containsId() throws Exception {
73
+    public void containsId() {
74 74
         assertThat(uut.containsId("123")).isFalse();
75 75
         uut.push("123", 123);
76 76
         assertThat(uut.containsId("123")).isTrue();
77 77
     }
78 78
 
79 79
     @Test
80
-    public void remove() throws Exception {
80
+    public void remove() {
81 81
         assertThat(uut.remove("123")).isNull();
82 82
 
83 83
         uut.push("123", 123);
@@ -87,16 +87,16 @@ public class IdStackTest extends BaseTest {
87 87
     }
88 88
 
89 89
     @Test
90
-    public void iterableIds() throws Exception {
90
+    public void iterableIds() {
91 91
         assertThat(uut).isInstanceOf(Iterable.class);
92 92
         assertThat(uut).isEmpty();
93 93
         uut.push("123", 123);
94 94
         uut.push("456", 456);
95
-        assertThat(uut).containsExactly("456", "123");
95
+        assertThat(uut).containsExactly("123", "456");
96 96
     }
97 97
 
98 98
     @Test
99
-    public void isTop() throws Exception {
99
+    public void isTop() {
100 100
         assertThat(uut.isTop("123")).isFalse();
101 101
         uut.push("123", 123);
102 102
         assertThat(uut.isTop("123")).isTrue();
@@ -105,7 +105,7 @@ public class IdStackTest extends BaseTest {
105 105
     }
106 106
 
107 107
     @Test
108
-    public void values() throws Exception {
108
+    public void values() {
109 109
         assertThat(uut.values()).isNotNull().isEmpty();
110 110
         uut.push("123", 123);
111 111
         uut.push("456", 456);

+ 2
- 1
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/NavigatorTest.java ファイルの表示

@@ -38,6 +38,7 @@ import org.robolectric.android.controller.ActivityController;
38 38
 import org.robolectric.annotation.Config;
39 39
 
40 40
 import java.util.Arrays;
41
+import java.util.Collections;
41 42
 import java.util.List;
42 43
 
43 44
 import static org.assertj.core.api.Java6Assertions.assertThat;
@@ -301,7 +302,7 @@ public class NavigatorTest extends BaseTest {
301 302
 
302 303
         stack.push(child1, new CommandListenerAdapter());
303 304
         stack.push(child2, new CommandListenerAdapter());
304
-        stack.setRoot(child3, new CommandListenerAdapter());
305
+        stack.setRoot(Collections.singletonList(child3), new CommandListenerAdapter());
305 306
 
306 307
         assertThat(stack.getChildControllers()).containsOnly(child3);
307 308
     }

+ 30
- 4
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.java ファイルの表示

@@ -100,7 +100,7 @@ public class StackControllerTest extends BaseTest {
100 100
     public void childrenAreAssignedParent() {
101 101
         StackController uut = createStack(Arrays.asList(child1, child2));
102 102
         for (ViewController child : uut.getChildControllers()) {
103
-            assertThat(child.getParentController().equals(uut));
103
+            assertThat(child.getParentController().equals(uut)).isTrue();
104 104
         }
105 105
     }
106 106
 
@@ -204,7 +204,7 @@ public class StackControllerTest extends BaseTest {
204 204
         assertThat(uut.isEmpty()).isTrue();
205 205
         uut.push(child1, new CommandListenerAdapter());
206 206
         uut.push(child2, new CommandListenerAdapter());
207
-        uut.setRoot(child3, new CommandListenerAdapter() {
207
+        uut.setRoot(Collections.singletonList(child3), new CommandListenerAdapter() {
208 208
             @Override
209 209
             public void onSuccess(String childId) {
210 210
                 assertContainsOnlyId(child3.getId());
@@ -213,7 +213,7 @@ public class StackControllerTest extends BaseTest {
213 213
     }
214 214
 
215 215
     @Test
216
-    public void setRoot() {
216
+    public void setRoot_singleChild() {
217 217
         activity.setContentView(uut.getView());
218 218
         disablePushAnimation(child1, child2, child3);
219 219
 
@@ -221,7 +221,7 @@ public class StackControllerTest extends BaseTest {
221 221
         uut.push(child1, new CommandListenerAdapter());
222 222
         uut.push(child2, new CommandListenerAdapter());
223 223
         assertThat(uut.getTopBar().getTitleBar().getNavigationIcon()).isNotNull();
224
-        uut.setRoot(child3, new CommandListenerAdapter() {
224
+        uut.setRoot(Collections.singletonList(child3), new CommandListenerAdapter() {
225 225
             @Override
226 226
             public void onSuccess(String childId) {
227 227
                 assertContainsOnlyId(child3.getId());
@@ -230,6 +230,32 @@ public class StackControllerTest extends BaseTest {
230 230
         });
231 231
     }
232 232
 
233
+    @Test
234
+    public void setRoot_multipleChildren() {
235
+        activity.setContentView(uut.getView());
236
+        disablePushAnimation(child1, child2, child3, child4);
237
+        disablePopAnimation(child4);
238
+
239
+        assertThat(uut.isEmpty()).isTrue();
240
+        uut.push(child1, new CommandListenerAdapter());
241
+        uut.push(child2, new CommandListenerAdapter());
242
+        assertThat(uut.getTopBar().getTitleBar().getNavigationIcon()).isNotNull();
243
+        uut.setRoot(Arrays.asList(child3, child4), new CommandListenerAdapter() {
244
+            @Override
245
+            public void onSuccess(String childId) {
246
+                assertContainsOnlyId(child3.getId(), child4.getId());
247
+                assertThat(uut.getTopBar().getTitleBar().getNavigationIcon()).isNotNull();
248
+                assertThat(child4.isViewShown()).isTrue();
249
+                assertThat(child3.isViewShown()).isFalse();
250
+
251
+                assertThat(uut.getCurrentChild()).isEqualTo(child4);
252
+                uut.pop(Options.EMPTY, new CommandListenerAdapter());
253
+                assertThat(uut.getTopBar().getTitleBar().getNavigationIcon()).isNull();
254
+                assertThat(uut.getCurrentChild()).isEqualTo(child3);
255
+            }
256
+        });
257
+    }
258
+
233 259
     @Test
234 260
     public synchronized void pop() {
235 261
         disablePushAnimation(child1, child2);

+ 2
- 2
lib/ios/RNNBridgeModule.m ファイルの表示

@@ -49,8 +49,8 @@ RCT_EXPORT_METHOD(pop:(NSString*)commandId componentId:(NSString*)componentId me
49 49
 	} rejection:reject];
50 50
 }
51 51
 
52
-RCT_EXPORT_METHOD(setStackRoot:(NSString*)commandId componentId:(NSString*)componentId layout:(NSDictionary*)layout resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
53
-	[_commandsHandler setStackRoot:componentId layout:layout completion:^{
52
+RCT_EXPORT_METHOD(setStackRoot:(NSString*)commandId componentId:(NSString*)componentId children:(NSArray*)children resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
53
+	[_commandsHandler setStackRoot:componentId children:children completion:^{
54 54
 		resolve(componentId);
55 55
 	} rejection:reject];
56 56
 }

+ 1
- 1
lib/ios/RNNCommandsHandler.h ファイルの表示

@@ -25,7 +25,7 @@
25 25
 
26 26
 - (void)popToRoot:(NSString*)componentId mergeOptions:(NSDictionary*)options completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection;
27 27
 
28
-- (void)setStackRoot:(NSString*)componentId layout:(NSDictionary*)layout completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection;
28
+- (void)setStackRoot:(NSString*)componentId children:(NSArray*)children completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection;
29 29
 
30 30
 - (void)showModal:(NSDictionary*)layout completion:(RNNTransitionWithComponentIdCompletionBlock)completion;
31 31
 

+ 4
- 4
lib/ios/RNNCommandsHandler.m ファイルの表示

@@ -150,14 +150,14 @@ static NSString* const setDefaultOptions	= @"setDefaultOptions";
150 150
 	}
151 151
 }
152 152
 
153
-- (void)setStackRoot:(NSString*)componentId layout:(NSDictionary*)layout completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection {
153
+- (void)setStackRoot:(NSString*)componentId children:(NSArray*)children completion:(RNNTransitionCompletionBlock)completion rejection:(RCTPromiseRejectBlock)rejection {
154 154
 	[self assertReady];
155 155
 	
156
-	UIViewController<RNNParentProtocol> *newVC = [_controllerFactory createLayout:layout saveToStore:_store];
157
-	RNNNavigationOptions* options = [newVC getCurrentChild].resolveOptions;
156
+ 	NSArray<RNNLayoutProtocol> *childViewControllers = [_controllerFactory createChildrenLayout:children saveToStore:_store];
157
+	RNNNavigationOptions* options = [childViewControllers.lastObject getCurrentChild].resolveOptions;
158 158
 	UIViewController *fromVC = [_store findComponentForId:componentId];
159 159
 	__weak typeof(RNNEventEmitter*) weakEventEmitter = _eventEmitter;
160
-	[_stackManager setStackRoot:newVC fromViewController:fromVC animated:options.animations.setStackRoot.enable completion:^{
160
+	[_stackManager setStackChildren:childViewControllers fromViewController:fromVC animated:options.animations.setStackRoot.enable completion:^{
161 161
 		[weakEventEmitter sendOnNavigationCommandCompletion:setStackRoot params:@{@"componentId": componentId}];
162 162
 		completion();
163 163
 	} rejection:rejection];

+ 2
- 0
lib/ios/RNNControllerFactory.h ファイルの表示

@@ -14,6 +14,8 @@
14 14
 
15 15
 - (UIViewController<RNNParentProtocol> *)createLayout:(NSDictionary*)layout saveToStore:(RNNStore *)store;
16 16
 
17
+- (NSArray<RNNLayoutProtocol> *)createChildrenLayout:(NSArray*)children saveToStore:(RNNStore *)store;
18
+
17 19
 @property (nonatomic, strong) RNNEventEmitter *eventEmitter;
18 20
 
19 21
 @property (nonatomic, strong) RNNNavigationOptions* defaultOptions;

+ 10
- 0
lib/ios/RNNControllerFactory.m ファイルの表示

@@ -44,6 +44,16 @@
44 44
 	return layoutViewController;
45 45
 }
46 46
 
47
+- (NSArray<RNNLayoutProtocol> *)createChildrenLayout:(NSArray*)children saveToStore:(RNNStore *)store {
48
+	_store = store;
49
+	NSMutableArray<RNNLayoutProtocol>* childViewControllers = [NSMutableArray<RNNLayoutProtocol> new];
50
+	for (NSDictionary* layout in children) {
51
+		[childViewControllers addObject:[self fromTree:layout]];
52
+	}
53
+	_store = nil;
54
+	return childViewControllers;
55
+}
56
+
47 57
 # pragma mark private
48 58
 
49 59
 - (UIViewController<RNNParentProtocol> *)fromTree:(NSDictionary*)json {

+ 1
- 1
lib/ios/RNNNavigationStackManager.h ファイルの表示

@@ -15,6 +15,6 @@ typedef void (^RNNTransitionRejectionBlock)(NSString *code, NSString *message, N
15 15
 
16 16
 - (void)popToRoot:(UIViewController*)viewController animated:(BOOL)animated completion:(RNNPopCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection;
17 17
 
18
-- (void)setStackRoot:(UIViewController *)newRoot fromViewController:(UIViewController *)fromViewController animated:(BOOL)animated completion:(RNNTransitionCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection;
18
+- (void)setStackChildren:(NSArray<UIViewController *> *)children fromViewController:(UIViewController *)fromViewController animated:(BOOL)animated completion:(RNNTransitionCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection;
19 19
 
20 20
 @end

+ 2
- 2
lib/ios/RNNNavigationStackManager.m ファイルの表示

@@ -62,11 +62,11 @@ typedef void (^RNNAnimationBlock)(void);
62 62
 	}];
63 63
 }
64 64
 
65
-- (void)setStackRoot:(UIViewController *)newRoot fromViewController:(UIViewController *)fromViewController animated:(BOOL)animated completion:(RNNTransitionCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection {
65
+- (void)setStackChildren:(NSArray<UIViewController *> *)children fromViewController:(UIViewController *)fromViewController animated:(BOOL)animated completion:(RNNTransitionCompletionBlock)completion rejection:(RNNTransitionRejectionBlock)rejection {
66 66
 	UINavigationController* nvc = fromViewController.navigationController;
67 67
 	
68 68
 	[self performAnimationBlock:^{
69
-		[nvc setViewControllers:@[newRoot] animated:animated];
69
+		[nvc setViewControllers:children animated:animated];
70 70
 	} completion:completion];
71 71
 }
72 72
 

+ 24
- 0
lib/ios/ReactNativeNavigationTests/RNNCommandsHandlerTest.m ファイルの表示

@@ -310,4 +310,28 @@
310 310
 	[self.store verify];
311 311
 }
312 312
 
313
+- (void)testSetStackRoot_resetStackWithSingleComponent {
314
+	OCMStub([self.controllerFactory createChildrenLayout:[OCMArg any] saveToStore:self.store]).andReturn(@[self.vc2]);
315
+	[self.store setReadyToReceiveCommands:true];
316
+	[self.uut setStackRoot:@"vc1" children:nil completion:^{
317
+		
318
+	} rejection:^(NSString *code, NSString *message, NSError *error) {
319
+		
320
+	}];
321
+	XCTAssertEqual(_nvc.viewControllers.firstObject, self.vc2);
322
+	XCTAssertEqual(_nvc.viewControllers.count, 1);
323
+}
324
+
325
+- (void)testSetStackRoot_setMultipleChildren {
326
+	NSArray* newViewControllers = @[_vc1, _vc3];
327
+	OCMStub([self.controllerFactory createChildrenLayout:[OCMArg any] saveToStore:self.store]).andReturn(newViewControllers);
328
+	[self.store setReadyToReceiveCommands:true];
329
+	[self.uut setStackRoot:@"vc1" children:nil completion:^{
330
+		
331
+	} rejection:^(NSString *code, NSString *message, NSError *error) {
332
+		
333
+	}];
334
+	XCTAssertTrue([_nvc.viewControllers isEqual:newViewControllers]);
335
+}
336
+
313 337
 @end

+ 1
- 1
lib/ios/ReactNativeNavigationTests/RNNNavigationStackManagerTest.m ファイルの表示

@@ -65,7 +65,7 @@
65 65
 
66 66
 - (void)testStackRoot_shouldUpdateNavigationControllerChildrenViewControllers {
67 67
 	XCTestExpectation *expectation = [self expectationWithDescription:@"Testing Async Method"];
68
-	[_stackManager setStackRoot:self.vc2 fromViewController:self.vc1 animated:NO completion:^{
68
+	[_stackManager setStackChildren:@[self.vc2] fromViewController:self.vc1 animated:NO completion:^{
69 69
 		XCTAssertTrue(self.nvc.childViewControllers.count == 1);
70 70
 		XCTAssertTrue([self.nvc.topViewController isEqual:self.vc2]);
71 71
 		[expectation fulfill];

+ 4
- 2
lib/src/Navigation.ts ファイルの表示

@@ -1,3 +1,4 @@
1
+import { isArray } from 'lodash';
1 2
 import { NativeCommandsSender } from './adapters/NativeCommandsSender';
2 3
 import { NativeEventsReceiver } from './adapters/NativeEventsReceiver';
3 4
 import { UniqueIdProvider } from './adapters/UniqueIdProvider';
@@ -146,8 +147,9 @@ export class NavigationRoot {
146 147
   /**
147 148
    * Sets new root component to stack.
148 149
    */
149
-  public setStackRoot(componentId: string, layout: Layout): Promise<any> {
150
-    return this.commands.setStackRoot(componentId, layout);
150
+  public setStackRoot(componentId: string, layout: Layout | Layout[]): Promise<any> {
151
+    const children: Layout[] = isArray(layout) ? layout : [layout];
152
+    return this.commands.setStackRoot(componentId, children);
151 153
   }
152 154
 
153 155
   /**

+ 15
- 13
lib/src/commands/Commands.test.ts ファイルの表示

@@ -289,17 +289,19 @@ describe('Commands', () => {
289 289
 
290 290
   describe('setStackRoot', () => {
291 291
     it('parses into correct layout node and sends to native', () => {
292
-      uut.setStackRoot('theComponentId', { component: { name: 'com.example.MyScreen' } });
293
-      verify(mockedNativeCommandsSender.setStackRoot('setStackRoot+UNIQUE_ID', 'theComponentId', deepEqual({
294
-        type: 'Component',
295
-        id: 'Component+UNIQUE_ID',
296
-        data: {
297
-          name: 'com.example.MyScreen',
298
-          options: {},
299
-          passProps: undefined
300
-        },
301
-        children: []
302
-      }))).called();
292
+      uut.setStackRoot('theComponentId', [{ component: { name: 'com.example.MyScreen' } }]);
293
+      verify(mockedNativeCommandsSender.setStackRoot('setStackRoot+UNIQUE_ID', 'theComponentId', deepEqual([
294
+        {
295
+          type: 'Component',
296
+          id: 'Component+UNIQUE_ID',
297
+          data: {
298
+            name: 'com.example.MyScreen',
299
+            options: {},
300
+            passProps: undefined
301
+          },
302
+          children: []
303
+        }
304
+      ]))).called();
303 305
     });
304 306
   });
305 307
 
@@ -396,7 +398,7 @@ describe('Commands', () => {
396 398
         pop: ['id', {}],
397 399
         popTo: ['id', {}],
398 400
         popToRoot: ['id', {}],
399
-        setStackRoot: ['id', {}],
401
+        setStackRoot: ['id', [{}]],
400 402
         showOverlay: [{}],
401 403
         dismissOverlay: ['id'],
402 404
         getLaunchArgs: ['id']
@@ -412,7 +414,7 @@ describe('Commands', () => {
412 414
         pop: { commandId: 'pop+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
413 415
         popTo: { commandId: 'popTo+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
414 416
         popToRoot: { commandId: 'popToRoot+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
415
-        setStackRoot: { commandId: 'setStackRoot+UNIQUE_ID', componentId: 'id', layout: 'parsed' },
417
+        setStackRoot: { commandId: 'setStackRoot+UNIQUE_ID', componentId: 'id', layout: ['parsed'] },
416 418
         showOverlay: { commandId: 'showOverlay+UNIQUE_ID', layout: 'parsed' },
417 419
         dismissOverlay: { commandId: 'dismissOverlay+UNIQUE_ID', componentId: 'id' },
418 420
         getLaunchArgs: { commandId: 'getLaunchArgs+UNIQUE_ID' },

+ 8
- 8
lib/src/commands/Commands.ts ファイルの表示

@@ -113,15 +113,15 @@ export class Commands {
113 113
     return result;
114 114
   }
115 115
 
116
-  public setStackRoot(componentId: string, simpleApi: Layout) {
117
-    const input = _.cloneDeep(simpleApi);
118
-
119
-    const layout = this.layoutTreeParser.parse(input);
120
-    this.layoutTreeCrawler.crawl(layout);
121
-
116
+  public setStackRoot(componentId: string, children: Layout[]) {
117
+    const input = _.map(_.cloneDeep(children), (simpleApi) => {
118
+      const layout = this.layoutTreeParser.parse(simpleApi);
119
+      this.layoutTreeCrawler.crawl(layout);
120
+      return layout;
121
+    });
122 122
     const commandId = this.uniqueIdProvider.generate('setStackRoot');
123
-    const result = this.nativeCommandsSender.setStackRoot(commandId, componentId, layout);
124
-    this.commandsObserver.notify('setStackRoot', { commandId, componentId, layout });
123
+    const result = this.nativeCommandsSender.setStackRoot(commandId, componentId, input);
124
+    this.commandsObserver.notify('setStackRoot', { commandId, componentId, layout: input });
125 125
     return result;
126 126
   }
127 127
 

+ 38
- 15
playground/src/screens/PushedScreen.js ファイルの表示

@@ -168,27 +168,50 @@ class PushedScreen extends Component {
168 168
   }
169 169
 
170 170
   async onClickSetStackRoot() {
171
-    await Navigation.setStackRoot(this.props.componentId, {
172
-      component: {
173
-        name: 'navigation.playground.PushedScreen',
174
-        passProps: {
175
-          stackPosition: this.getStackPosition() + 1,
176
-          previousScreenIds: _.concat([], this.props.previousScreenIds || [], this.props.componentId)
177
-        },
178
-        options: {
179
-          animations: {
180
-            setStackRoot: {
181
-              enabled: false
171
+    await Navigation.setStackRoot(this.props.componentId, [
172
+      {
173
+        component: {
174
+          name: 'navigation.playground.PushedScreen',
175
+          passProps: {
176
+            stackPosition: this.getStackPosition() + 1,
177
+            previousScreenIds: _.concat([], this.props.previousScreenIds || [], this.props.componentId)
178
+          },
179
+          options: {
180
+            animations: {
181
+              setStackRoot: {
182
+                enabled: false
183
+              }
184
+            },
185
+            topBar: {
186
+              title: {
187
+                text: `Pushed ${this.getStackPosition() + 1} a`
188
+              }
182 189
             }
190
+          }
191
+        }
192
+      },
193
+      {
194
+        component: {
195
+          name: 'navigation.playground.PushedScreen',
196
+          passProps: {
197
+            stackPosition: this.getStackPosition() + 1,
198
+            previousScreenIds: _.concat([], this.props.previousScreenIds || [], this.props.componentId)
183 199
           },
184
-          topBar: {
185
-            title: {
186
-              text: `Pushed ${this.getStackPosition() + 1}`
200
+          options: {
201
+            animations: {
202
+              setStackRoot: {
203
+                enabled: false
204
+              }
205
+            },
206
+            topBar: {
207
+              title: {
208
+                text: `Pushed ${this.getStackPosition() + 1} b`
209
+              }
187 210
             }
188 211
           }
189 212
         }
190 213
       }
191
-    });
214
+    ]);
192 215
   }
193 216
 
194 217
   onClickPushWaitForRender = async () => {