ソースを参照

Modals support BackHandler

closes #2840
Guy Carmeli 6 年 前
コミット
00cf5419f0

+ 55
- 0
e2e/BackHandler.test.js ファイルの表示

@@ -0,0 +1,55 @@
1
+const Utils = require('./Utils');
2
+const testIDs = require('../playground/src/testIDs');
3
+const Android = require('./AndroidUtils');
4
+
5
+const { elementByLabel, elementById, sleep } = Utils;
6
+
7
+describe(':android: screen stack', () => {
8
+  beforeEach(async () => {
9
+    await device.relaunchApp();
10
+  });
11
+
12
+  it('override hardware back button', async () => {
13
+    await elementByLabel('BACK HANDLER').tap();
14
+    await expect(elementByLabel('Back Handler Screen')).toBeVisible();
15
+
16
+    await elementByLabel('ADD BACK HANDLER').tap();
17
+    Android.pressBack();
18
+    await sleep(100);
19
+    await expect(elementByLabel('Back Handler Screen')).toBeVisible();
20
+
21
+    await elementByLabel('REMOVE BACK HANDLER').tap();
22
+    Android.pressBack();
23
+    await sleep(100);
24
+    await expect(elementByLabel('React Native Navigation!')).toBeVisible();
25
+  });
26
+
27
+  it('override hardware back button in modal with stack', async () => {
28
+    await elementByLabel('BACK HANDLER').tap();
29
+    await expect(elementByLabel('Back Handler Screen')).toBeVisible();
30
+
31
+    await elementByLabel('SHOW MODAL WITH STACK').tap();
32
+    await elementByLabel('ADD BACK HANDLER').tap();
33
+
34
+    // Back is handled in Js
35
+    Android.pressBack();
36
+    await sleep(100);
37
+    await expect(elementByLabel('Back button pressed!')).toBeVisible();
38
+
39
+    // pop
40
+    await elementByLabel('REMOVE BACK HANDLER').tap();
41
+    Android.pressBack();
42
+    await sleep(100);
43
+    await expect(elementByLabel('Back Handler Screen')).toBeVisible();
44
+
45
+    // modal dismissed
46
+    Android.pressBack();
47
+    await sleep(100);
48
+    await expect(elementByLabel('Back Handler Screen')).toBeVisible();
49
+
50
+    // main
51
+    Android.pressBack();
52
+    await sleep(100);
53
+    await expect(elementByLabel('React Native Navigation!')).toBeVisible();
54
+  });
55
+});

+ 0
- 15
e2e/ScreenStack.test.js ファイルの表示

@@ -74,19 +74,4 @@ describe('screen stack', () => {
74 74
     await expect(elementById('TestLabel')).toBeVisible();
75 75
     await expect(elementById(testIDs.TOP_BAR_ELEMENT)).toBeVisible();
76 76
   });
77
-
78
-  it(':android: override hardware back button', async () => {
79
-    await elementByLabel('BACK HANDLER').tap();
80
-    await expect(elementByLabel('Back Handler Screen')).toBeVisible();
81
-
82
-    await elementByLabel('ADD BACK HANDLER').tap();
83
-    Android.pressBack();
84
-    await sleep(100);
85
-    await expect(elementByLabel('Back Handler Screen')).toBeVisible();
86
-
87
-    await elementByLabel('REMOVE BACK HANDLER').tap();
88
-    Android.pressBack();
89
-    await sleep(100);
90
-    await expect(elementByLabel('React Native Navigation!')).toBeVisible();
91
-  });
92 77
 });

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

@@ -93,4 +93,8 @@ class ModalStack implements ModalListener {
93 93
     private void performOnModal(@Nullable Modal modal, Task<Modal> task) {
94 94
         if (modal != null) task.run(modal);
95 95
     }
96
+
97
+    public boolean handleBack() {
98
+        return !modals.isEmpty() && peek().handleBack();
99
+    }
96 100
 }

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

@@ -46,7 +46,7 @@ public class Navigator extends ParentController implements ModalListener {
46 46
 
47 47
     @Override
48 48
     public boolean handleBack() {
49
-        return root != null && root.handleBack();
49
+        return modalStack.isEmpty() ? root.handleBack() : modalStack.handleBack();
50 50
     }
51 51
 
52 52
     @Override

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

@@ -52,11 +52,9 @@ public class Modal implements DialogInterface.OnKeyListener, DialogInterface.OnD
52 52
     public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
53 53
         if (keyCode == KeyEvent.KEYCODE_BACK) {
54 54
             if (event.getAction() == KeyEvent.ACTION_UP) {
55
-                if (viewController.handleBack()) {
56
-                    return true;
57
-                }
58
-                dialog.dismiss();
55
+                viewController.getActivity().onBackPressed();
59 56
             }
57
+            return true;
60 58
         }
61 59
         return false;
62 60
     }
@@ -73,4 +71,11 @@ public class Modal implements DialogInterface.OnKeyListener, DialogInterface.OnD
73 71
     public void onShow(DialogInterface dialog) {
74 72
         modalListener.onModalDisplay(this);
75 73
     }
74
+
75
+    public boolean handleBack() {
76
+        if (!viewController.handleBack()) {
77
+            dialog.dismiss();
78
+        }
79
+        return true;
80
+    }
76 81
 }

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

@@ -206,12 +206,19 @@ public class NavigatorTest extends BaseTest {
206 206
 
207 207
     @Test
208 208
     public void handleBack_DelegatesToRoot() throws Exception {
209
-        assertThat(uut.handleBack()).isFalse();
210
-        ViewController spy = spy(child1);
211
-        uut.setRoot(spy, new MockPromise());
212
-        when(spy.handleBack()).thenReturn(true);
209
+        ViewController root = spy(child1);
210
+        uut.setRoot(root, new MockPromise());
211
+        when(root.handleBack()).thenReturn(true);
213 212
         assertThat(uut.handleBack()).isTrue();
214
-        verify(spy, times(1)).handleBack();
213
+        verify(root, times(1)).handleBack();
214
+    }
215
+
216
+    @Test
217
+    public void handleBack_modalTakePrecedenceOverRoot() throws Exception {
218
+        ViewController root = spy(child1);
219
+        uut.setRoot(root, new MockPromise());
220
+        uut.showModal(child2, new MockPromise());
221
+        verify(root, times(0)).handleBack();
215 222
     }
216 223
 
217 224
     @Test

+ 71
- 0
playground/src/screens/BackHandlerModalScreen.js ファイルの表示

@@ -0,0 +1,71 @@
1
+const React = require('react');
2
+const { Component } = require('react');
3
+
4
+const { View, Text, Button, BackHandler } = require('react-native');
5
+
6
+class BackHandlerModalScreen extends Component {
7
+  static get options() {
8
+    return {
9
+      topBar: {
10
+        title: 'Back Handler'
11
+      }
12
+    };
13
+  }
14
+
15
+  constructor(props) {
16
+    super(props);
17
+    this.backHandler = () => {
18
+      this.setState({
19
+        backPress: 'Back button pressed!'
20
+      });
21
+      return true;
22
+    };
23
+    this.state = {
24
+      backPress: ''
25
+    };
26
+  }
27
+
28
+  render() {
29
+    return (
30
+      <View style={styles.root}>
31
+        <Text style={styles.h1}>{`Back Handler Screen`}</Text>
32
+        <Text style={styles.h2}>{this.state.backPress}</Text>
33
+        <Button title='add back handler' onPress={() => this.addBackHandler()} />
34
+        <Button title='remove back handler' onPress={() => this.removeBackHandler()} />
35
+      </View>
36
+    );
37
+  }
38
+
39
+  addBackHandler() {
40
+    BackHandler.addEventListener('hardwareBackPress', this.backHandler);
41
+  }
42
+
43
+  removeBackHandler() {
44
+    BackHandler.removeEventListener('hardwareBackPress', this.backHandler);
45
+  }
46
+
47
+  componentWillUnmount() {
48
+    BackHandler.removeEventListener('hardwareBackPress', this.backHandler);
49
+  }
50
+}
51
+
52
+const styles = {
53
+  root: {
54
+    flex: 1,
55
+    backgroundColor: 'white',
56
+    justifyContent: 'center',
57
+    alignItems: 'center'
58
+  },
59
+  h2: {
60
+    fontSize: 12,
61
+    textAlign: 'center',
62
+    margin: 10
63
+  },
64
+  footer: {
65
+    fontSize: 10,
66
+    color: '#888',
67
+    marginTop: 10
68
+  }
69
+};
70
+
71
+module.exports = BackHandlerModalScreen;

+ 32
- 5
playground/src/screens/BackHandlerScreen.js ファイルの表示

@@ -1,6 +1,6 @@
1 1
 const React = require('react');
2 2
 const { Component } = require('react');
3
-
3
+const { Navigation } = require('react-native-navigation');
4 4
 const { View, Text, Button, BackHandler } = require('react-native');
5 5
 
6 6
 class BackHandlerScreen extends Component {
@@ -16,8 +16,6 @@ class BackHandlerScreen extends Component {
16 16
 
17 17
   constructor(props) {
18 18
     super(props);
19
-    this.addBackHandler = this.addBackHandler.bind(this);
20
-    this.removeBackHandler = this.removeBackHandler.bind(this);
21 19
     this.backHandler = () => {
22 20
       this.setState({
23 21
         backPress: 'Back button pressed!'
@@ -34,8 +32,10 @@ class BackHandlerScreen extends Component {
34 32
       <View style={styles.root}>
35 33
         <Text style={styles.h1}>{`Back Handler Screen`}</Text>
36 34
         <Text style={styles.h2}>{this.state.backPress}</Text>
37
-        <Button title='add back handler' onPress={this.addBackHandler} />
38
-        <Button title='remove back handler' onPress={this.removeBackHandler} />
35
+        <Button title='add back handler' onPress={() => this.addBackHandler()} />
36
+        <Button title='remove back handler' onPress={() => this.removeBackHandler()} />
37
+        <Button title='show single component modal' onPress={() => this.showModal()} />
38
+        <Button title='show modal with stack' onPress={() => this.showModalWitchStack()} />
39 39
       </View>
40 40
     );
41 41
   }
@@ -48,6 +48,33 @@ class BackHandlerScreen extends Component {
48 48
     BackHandler.removeEventListener('hardwareBackPress', this.backHandler);
49 49
   }
50 50
 
51
+  showModal() {
52
+    Navigation.showModal({
53
+      component: {
54
+        name: 'navigation.playground.BackHandlerModalScreen'
55
+      }
56
+    });
57
+  }
58
+
59
+  showModalWitchStack() {
60
+    Navigation.showModal({
61
+      stack: {
62
+        children: [
63
+          {
64
+            component: {
65
+              name: 'navigation.playground.BackHandlerModalScreen'
66
+            }
67
+          },
68
+          {
69
+            component: {
70
+              name: 'navigation.playground.BackHandlerModalScreen'
71
+            }
72
+          }
73
+        ]
74
+      }
75
+    });
76
+  }
77
+
51 78
   componentWillUnmount() {
52 79
     BackHandler.removeEventListener('hardwareBackPress', this.backHandler);
53 80
   }

+ 2
- 0
playground/src/screens/index.js ファイルの表示

@@ -18,6 +18,7 @@ const TopTabScreen = require('./TopTabScreen');
18 18
 const TopTabOptionsScreen = require('./TopTabOptionsScreen');
19 19
 const CustomTopBar = require('./CustomTopBar');
20 20
 const Alert = require('./Alert');
21
+const BackHandlerModalScreen = require('./BackHandlerModalScreen');
21 22
 
22 23
 function registerScreens() {
23 24
   Navigation.registerComponent(`navigation.playground.CustomTransitionDestination`, () => CustomTransitionDestination);
@@ -39,6 +40,7 @@ function registerScreens() {
39 40
   Navigation.registerComponent('navigation.playground.TopTabOptionsScreen', () => TopTabOptionsScreen);
40 41
   Navigation.registerComponent('navigation.playground.CustomTopBar', () => CustomTopBar);
41 42
   Navigation.registerComponent('navigation.playground.alert', () => Alert);
43
+  Navigation.registerComponent('navigation.playground.BackHandlerModalScreen', () => BackHandlerModalScreen);
42 44
 }
43 45
 
44 46
 module.exports = {