Преглед на файлове

Destroy pushed screen if back is pressed during push animation

Closes #3894
Guy Carmeli преди 6 години
родител
ревизия
156565b6ac

+ 30
- 2
lib/android/app/src/main/java/com/reactnativenavigation/anim/NavigationAnimator.java Целия файл

@@ -16,7 +16,9 @@ import com.reactnativenavigation.views.element.ElementTransitionManager;
16 16
 
17 17
 import java.util.Collection;
18 18
 import java.util.Collections;
19
+import java.util.HashMap;
19 20
 import java.util.List;
21
+import java.util.Map;
20 22
 
21 23
 import static com.reactnativenavigation.utils.CollectionUtils.merge;
22 24
 
@@ -24,6 +26,7 @@ import static com.reactnativenavigation.utils.CollectionUtils.merge;
24 26
 public class NavigationAnimator extends BaseAnimator {
25 27
 
26 28
     private final ElementTransitionManager transitionManager;
29
+    private Map<View, Animator> runningPushAnimations = new HashMap<>();
27 30
 
28 31
     public NavigationAnimator(Context context, ElementTransitionManager transitionManager) {
29 32
         super(context);
@@ -41,25 +44,50 @@ public class NavigationAnimator extends BaseAnimator {
41 44
         Collection<Animator> elementTransitions = transitionManager.createTransitions(transitions, fromElements, toElements);
42 45
         set.playTogether(merge(push.getChildAnimations(), elementTransitions));
43 46
         set.addListener(new AnimatorListenerAdapter() {
47
+            private boolean isCancelled;
48
+
44 49
             @Override
45 50
             public void onAnimationStart(Animator animation) {
46 51
                 view.setAlpha(1);
47 52
             }
48 53
 
49 54
             @Override
50
-            public void onAnimationEnd(Animator animation) {
55
+            public void onAnimationCancel(Animator animation) {
56
+                isCancelled = true;
57
+                runningPushAnimations.remove(view);
51 58
                 onAnimationEnd.run();
52 59
             }
60
+
61
+            @Override
62
+            public void onAnimationEnd(Animator animation) {
63
+                if (!isCancelled) {
64
+                    runningPushAnimations.remove(view);
65
+                    onAnimationEnd.run();
66
+                }
67
+            }
53 68
         });
69
+        runningPushAnimations.put(view, set);
54 70
         set.start();
55 71
     }
56 72
 
57 73
     public void pop(View view, NestedAnimationsOptions pop, Runnable onAnimationEnd) {
74
+        if (runningPushAnimations.containsKey(view)) {
75
+            runningPushAnimations.get(view).cancel();
76
+            onAnimationEnd.run();
77
+            return;
78
+        }
58 79
         AnimatorSet set = pop.content.getAnimation(view, getDefaultPopAnimation(view));
59 80
         set.addListener(new AnimatorListenerAdapter() {
81
+            private boolean cancelled;
82
+
83
+            @Override
84
+            public void onAnimationCancel(Animator animation) {
85
+                this.cancelled = true;
86
+            }
87
+
60 88
             @Override
61 89
             public void onAnimationEnd(Animator animation) {
62
-                onAnimationEnd.run();
90
+                if (!cancelled) onAnimationEnd.run();
63 91
             }
64 92
         });
65 93
         set.start();

+ 4
- 4
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java Целия файл

@@ -58,6 +58,10 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
58 58
     private ViewVisibilityListener viewVisibilityListener = new ViewVisibilityListenerAdapter();
59 59
     protected FabOptionsPresenter fabOptionsPresenter;
60 60
 
61
+    public boolean isDestroyed() {
62
+        return isDestroyed;
63
+    }
64
+
61 65
     public ViewController(Activity activity, String id, YellowBoxDelegate yellowBoxDelegate, Options initialOptions) {
62 66
         this.activity = activity;
63 67
         this.id = id;
@@ -241,10 +245,6 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
241 245
         }
242 246
     }
243 247
 
244
-    protected boolean isDestroyed() {
245
-        return isDestroyed;
246
-    }
247
-
248 248
     @Override
249 249
     public void onGlobalLayout() {
250 250
         if (!isShown && isViewShown()) {

+ 6
- 3
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java Целия файл

@@ -149,7 +149,9 @@ public class StackController extends ParentController<StackLayout> {
149 149
                     }));
150 150
                 } else {
151 151
                     animator.push(child.getView(), resolvedOptions.animations.push, () -> {
152
-                        getView().removeView(toRemove.getView());
152
+                        if (!toRemove.equals(peek())) {
153
+                            getView().removeView(toRemove.getView());
154
+                        }
153 155
                         listener.onSuccess(child.getId());
154 156
                     });
155 157
                 }
@@ -206,7 +208,9 @@ public class StackController extends ParentController<StackLayout> {
206 208
             appearingView.setLayoutParams(new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
207 209
             presenter.applyLayoutParamsOptions(resolvedOptions, appearingView);
208 210
         }
209
-        getView().addView(appearingView, 0);
211
+        if (appearingView.getParent() == null) {
212
+            getView().addView(appearingView, 0);
213
+        }
210 214
         presenter.onChildWillAppear(appearing.options, disappearing.options);
211 215
         if (disappearing.options.animations.pop.enable.isTrueOrUndefined()) {
212 216
             animator.pop(disappearing.getView(), resolvedOptions.animations.pop, () -> finishPopping(disappearing, listener));
@@ -216,7 +220,6 @@ public class StackController extends ParentController<StackLayout> {
216 220
     }
217 221
 
218 222
     private void finishPopping(ViewController disappearing, CommandListener listener) {
219
-        getView().removeView(disappearing.getView());
220 223
         disappearing.destroy();
221 224
         listener.onSuccess(disappearing.getId());
222 225
     }

+ 20
- 1
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.java Целия файл

@@ -30,6 +30,7 @@ import com.reactnativenavigation.viewcontrollers.topbar.TopBarController;
30 30
 import com.reactnativenavigation.views.Component;
31 31
 import com.reactnativenavigation.views.ReactComponent;
32 32
 import com.reactnativenavigation.views.StackLayout;
33
+import com.reactnativenavigation.views.element.ElementTransitionManager;
33 34
 import com.reactnativenavigation.views.topbar.TopBar;
34 35
 
35 36
 import org.assertj.core.api.iterable.Extractor;
@@ -71,9 +72,9 @@ public class StackControllerTest extends BaseTest {
71 72
     @Override
72 73
     public void beforeEach() {
73 74
         super.beforeEach();
74
-        animator = Mockito.mock(NavigationAnimator.class);
75 75
         backButtonHelper = spy(new BackButtonHelper());
76 76
         activity = newActivity();
77
+        animator = spy(new NavigationAnimator(activity, Mockito.mock(ElementTransitionManager.class)));
77 78
         childRegistry = new ChildControllersRegistry();
78 79
         presenter = spy(new StackOptionsPresenter(activity, new TitleBarReactViewCreatorMock(), new TopBarButtonCreatorMock(), ImageLoaderMock.mock(), new Options()));
79 80
         child1 = spy(new SimpleViewController(activity, childRegistry, "child1", new Options()));
@@ -168,6 +169,24 @@ public class StackControllerTest extends BaseTest {
168 169
         verify(animator, times(0)).push(eq(child1.getView()), eq(child1.options.animations.push), any());
169 170
     }
170 171
 
172
+    @Test
173
+    public void push_backPressedDuringPushAnimationDestroysPushedScreenImmediately() {
174
+        disablePushAnimation(child1);
175
+        uut.push(child1, new CommandListenerAdapter());
176
+
177
+        CommandListenerAdapter pushListener = spy(new CommandListenerAdapter());
178
+        uut.push(child2, pushListener);
179
+        CommandListenerAdapter backListener = spy(new CommandListenerAdapter());
180
+        uut.handleBack(backListener);
181
+        assertThat(uut.size()).isOne();
182
+        assertThat(child1.getView().getParent()).isEqualTo(uut.getView());
183
+        assertThat(child2.isDestroyed()).isTrue();
184
+
185
+        InOrder inOrder = inOrder(pushListener, backListener);
186
+        inOrder.verify(pushListener).onSuccess(any());
187
+        inOrder.verify(backListener).onSuccess(any());
188
+    }
189
+
171 190
     @Test
172 191
     public void animateSetRoot() {
173 192
         disablePushAnimation(child1, child2, child3);