瀏覽代碼

feat: central back button registry

master
Bettenbuk Zoltan 5 年之前
父節點
當前提交
0a76eebca7

+ 1
- 0
react/features/app/components/App.native.js 查看文件

16
 import { updateSettings } from '../../base/settings';
16
 import { updateSettings } from '../../base/settings';
17
 import '../../google-api';
17
 import '../../google-api';
18
 import '../../mobile/audio-mode';
18
 import '../../mobile/audio-mode';
19
+import '../../mobile/back-button';
19
 import '../../mobile/background';
20
 import '../../mobile/background';
20
 import '../../mobile/call-integration';
21
 import '../../mobile/call-integration';
21
 import '../../mobile/external-api';
22
 import '../../mobile/external-api';

+ 24
- 0
react/features/base/react/components/native/SlidingView.js 查看文件

8
     View
8
     View
9
 } from 'react-native';
9
 } from 'react-native';
10
 
10
 
11
+import { BackButtonRegistry } from '../../../../mobile/back-button';
12
+
11
 import { type StyleType } from '../../../styles';
13
 import { type StyleType } from '../../../styles';
12
 
14
 
13
 import styles from './slidingviewstyles';
15
 import styles from './slidingviewstyles';
110
         };
112
         };
111
 
113
 
112
         // Bind event handlers so they are only bound once per instance.
114
         // Bind event handlers so they are only bound once per instance.
115
+        this._onHardwareBackPress = this._onHardwareBackPress.bind(this);
113
         this._onHide = this._onHide.bind(this);
116
         this._onHide = this._onHide.bind(this);
114
     }
117
     }
115
 
118
 
119
      * @inheritdoc
122
      * @inheritdoc
120
      */
123
      */
121
     componentDidMount() {
124
     componentDidMount() {
125
+        BackButtonRegistry.addListener(this._onHardwareBackPress, true);
126
+
122
         this._mounted = true;
127
         this._mounted = true;
123
         this._setShow(this.props.show);
128
         this._setShow(this.props.show);
124
     }
129
     }
142
      * @inheritdoc
147
      * @inheritdoc
143
      */
148
      */
144
     componentWillUnmount() {
149
     componentWillUnmount() {
150
+        BackButtonRegistry.removeListener(this._onHardwareBackPress);
151
+
145
         this._mounted = false;
152
         this._mounted = false;
146
     }
153
     }
147
 
154
 
215
         return style;
222
         return style;
216
     }
223
     }
217
 
224
 
225
+    _onHardwareBackPress: () => boolean;
226
+
227
+    /**
228
+     * Callback to handle the hardware back button.
229
+     *
230
+     * @returns {boolean}
231
+     */
232
+    _onHardwareBackPress() {
233
+        const { onHide } = this.props;
234
+
235
+        if (typeof onHide === 'function') {
236
+            return onHide();
237
+        }
238
+
239
+        return false;
240
+    }
241
+
218
     _onHide: () => void;
242
     _onHide: () => void;
219
 
243
 
220
     /**
244
     /**

+ 30
- 1
react/features/chat/components/native/Chat.js 查看文件

23
  * the mobile client.
23
  * the mobile client.
24
  */
24
  */
25
 class Chat extends AbstractChat<Props> {
25
 class Chat extends AbstractChat<Props> {
26
+    /**
27
+     * Instantiates a new instance.
28
+     *
29
+     * @inheritdoc
30
+     */
31
+    constructor(props: Props) {
32
+        super(props);
33
+
34
+        this._onClose = this._onClose.bind(this);
35
+    }
36
+
26
     /**
37
     /**
27
      * Implements React's {@link Component#render()}.
38
      * Implements React's {@link Component#render()}.
28
      *
39
      *
31
     render() {
42
     render() {
32
         return (
43
         return (
33
             <SlidingView
44
             <SlidingView
45
+                onHide = { this._onClose }
34
                 position = 'bottom'
46
                 position = 'bottom'
35
                 show = { this.props._isOpen } >
47
                 show = { this.props._isOpen } >
36
                 <KeyboardAvoidingView
48
                 <KeyboardAvoidingView
38
                     style = { styles.chatContainer }>
50
                     style = { styles.chatContainer }>
39
                     <HeaderWithNavigation
51
                     <HeaderWithNavigation
40
                         headerLabelKey = 'chat.title'
52
                         headerLabelKey = 'chat.title'
41
-                        onPressBack = { this.props._onToggleChat } />
53
+                        onPressBack = { this._onClose } />
42
                     <SafeAreaView style = { styles.backdrop }>
54
                     <SafeAreaView style = { styles.backdrop }>
43
                         <MessageContainer messages = { this.props._messages } />
55
                         <MessageContainer messages = { this.props._messages } />
44
                         <ChatInputBar onSend = { this.props._onSendMessage } />
56
                         <ChatInputBar onSend = { this.props._onSendMessage } />
47
             </SlidingView>
59
             </SlidingView>
48
         );
60
         );
49
     }
61
     }
62
+
63
+    _onClose: () => boolean
64
+
65
+    /**
66
+     * Closes the chat window.
67
+     *
68
+     * @returns {boolean}
69
+     */
70
+    _onClose() {
71
+        if (this.props._isOpen) {
72
+            this.props._onToggleChat();
73
+
74
+            return true;
75
+        }
76
+
77
+        return false;
78
+    }
50
 }
79
 }
51
 
80
 
52
 export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat));
81
 export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat));

+ 4
- 3
react/features/conference/components/native/Conference.js 查看文件

1
 // @flow
1
 // @flow
2
 
2
 
3
 import React from 'react';
3
 import React from 'react';
4
-import { BackHandler, NativeModules, SafeAreaView, StatusBar, View } from 'react-native';
4
+import { NativeModules, SafeAreaView, StatusBar, View } from 'react-native';
5
 
5
 
6
 import { appNavigate } from '../../../app';
6
 import { appNavigate } from '../../../app';
7
 import { PIP_ENABLED, getFeatureFlag } from '../../../base/flags';
7
 import { PIP_ENABLED, getFeatureFlag } from '../../../base/flags';
23
     TileView
23
     TileView
24
 } from '../../../filmstrip';
24
 } from '../../../filmstrip';
25
 import { LargeVideo } from '../../../large-video';
25
 import { LargeVideo } from '../../../large-video';
26
+import { BackButtonRegistry } from '../../../mobile/back-button';
26
 import { AddPeopleDialog, CalleeInfoContainer } from '../../../invite';
27
 import { AddPeopleDialog, CalleeInfoContainer } from '../../../invite';
27
 import { Captions } from '../../../subtitles';
28
 import { Captions } from '../../../subtitles';
28
 import { setToolboxVisible, Toolbox } from '../../../toolbox';
29
 import { setToolboxVisible, Toolbox } from '../../../toolbox';
144
      * @returns {void}
145
      * @returns {void}
145
      */
146
      */
146
     componentDidMount() {
147
     componentDidMount() {
147
-        BackHandler.addEventListener('hardwareBackPress', this._onHardwareBackPress);
148
+        BackButtonRegistry.addListener(this._onHardwareBackPress);
148
 
149
 
149
         // Show the toolbox if we are the only participant; otherwise, the whole
150
         // Show the toolbox if we are the only participant; otherwise, the whole
150
         // UI looks too unpopulated the LargeVideo visible.
151
         // UI looks too unpopulated the LargeVideo visible.
186
      */
187
      */
187
     componentWillUnmount() {
188
     componentWillUnmount() {
188
         // Tear handling any hardware button presses for back navigation down.
189
         // Tear handling any hardware button presses for back navigation down.
189
-        BackHandler.removeEventListener('hardwareBackPress', this._onHardwareBackPress);
190
+        BackButtonRegistry.removeListener(this._onHardwareBackPress);
190
     }
191
     }
191
 
192
 
192
     /**
193
     /**

+ 10
- 3
react/features/invite/components/add-people-dialog/native/AddPeopleDialog.js 查看文件

150
 
150
 
151
         return (
151
         return (
152
             <SlidingView
152
             <SlidingView
153
+                onHide = { this._onCloseAddPeopleDialog }
153
                 position = 'bottom'
154
                 position = 'bottom'
154
                 show = { this.props._isVisible } >
155
                 show = { this.props._isVisible } >
155
                 <HeaderWithNavigation
156
                 <HeaderWithNavigation
242
         this._onTypeQuery('');
243
         this._onTypeQuery('');
243
     }
244
     }
244
 
245
 
245
-    _onCloseAddPeopleDialog: () => void
246
+    _onCloseAddPeopleDialog: () => boolean
246
 
247
 
247
     /**
248
     /**
248
      * Closes the dialog.
249
      * Closes the dialog.
249
      *
250
      *
250
-     * @returns {void}
251
+     * @returns {boolean}
251
      */
252
      */
252
     _onCloseAddPeopleDialog() {
253
     _onCloseAddPeopleDialog() {
253
-        this.props.dispatch(setAddPeopleDialogVisible(false));
254
+        if (this.props._isVisible) {
255
+            this.props.dispatch(setAddPeopleDialogVisible(false));
256
+
257
+            return true;
258
+        }
259
+
260
+        return false;
254
     }
261
     }
255
 
262
 
256
     _onInvite: () => void
263
     _onInvite: () => void

+ 10
- 3
react/features/invite/components/dial-in-summary/native/DialInSummary.js 查看文件

59
 
59
 
60
         return (
60
         return (
61
             <SlidingView
61
             <SlidingView
62
+                onHide = { this._onCloseView }
62
                 position = 'bottom'
63
                 position = 'bottom'
63
                 show = { Boolean(_summaryUrl) } >
64
                 show = { Boolean(_summaryUrl) } >
64
                 <View style = { styles.webViewWrapper }>
65
                 <View style = { styles.webViewWrapper }>
77
         );
78
         );
78
     }
79
     }
79
 
80
 
80
-    _onCloseView: () => void;
81
+    _onCloseView: () => boolean;
81
 
82
 
82
     /**
83
     /**
83
      * Closes the view.
84
      * Closes the view.
84
      *
85
      *
85
-     * @returns {void}
86
+     * @returns {boolean}
86
      */
87
      */
87
     _onCloseView() {
88
     _onCloseView() {
88
-        this.props.dispatch(hideDialInSummary());
89
+        if (this.props._summaryUrl) {
90
+            this.props.dispatch(hideDialInSummary());
91
+
92
+            return true;
93
+        }
94
+
95
+        return false;
89
     }
96
     }
90
 
97
 
91
     _onError: () => void;
98
     _onError: () => void;

+ 66
- 0
react/features/mobile/back-button/BackButtonRegistry.js 查看文件

1
+// @flow
2
+
3
+/**
4
+ * An registry that dispatches hardware back button events for subscribers with a custom logic.
5
+ */
6
+class BackButtonRegistry {
7
+    _listeners: Array<Function>;
8
+
9
+    /**
10
+     * Instantiates a new instance of the registry.
11
+     */
12
+    constructor() {
13
+        this._listeners = [];
14
+    }
15
+
16
+    /**
17
+     * Adds a listener to the registry.
18
+     *
19
+     * NOTE: Due to the different order of component mounts, we allow a component to register
20
+     * its listener to the top of the list, so then that will be invoked before other, 'non-top'
21
+     * listeners. For example a 'non-top' listener can be the one that puts the app into PiP mode,
22
+     * while a 'top' listener is the one that closes a modal in a conference.
23
+     *
24
+     * @param {Function} listener - The listener function.
25
+     * @param {boolean?} top - If true, the listener will be put on the top (eg for modal-like components).
26
+     * @returns {void}
27
+     */
28
+    addListener(listener: Function, top: boolean = false) {
29
+        if (top) {
30
+            this._listeners.splice(0, 0, listener);
31
+        } else {
32
+            this._listeners.push(listener);
33
+        }
34
+    }
35
+
36
+    /**
37
+     * Removes a listener from the registry.
38
+     *
39
+     * @param {Function} listener - The listener to remove.
40
+     * @returns {void}
41
+     */
42
+    removeListener(listener: Function) {
43
+        this._listeners = this._listeners.filter(f => f !== listener);
44
+    }
45
+
46
+    onHardwareBackPress: () => boolean
47
+
48
+    /**
49
+     * Callback for the back button press event.
50
+     *
51
+     * @returns {boolean}
52
+     */
53
+    onHardwareBackPress() {
54
+        for (const listener of this._listeners) {
55
+            const result = listener();
56
+
57
+            if (result === true) {
58
+                return true;
59
+            }
60
+        }
61
+
62
+        return false;
63
+    }
64
+}
65
+
66
+export default new BackButtonRegistry();

+ 5
- 0
react/features/mobile/back-button/index.js 查看文件

1
+// @flow
2
+
3
+export { default as BackButtonRegistry } from './BackButtonRegistry';
4
+
5
+import './middleware';

+ 36
- 0
react/features/mobile/back-button/middleware.js 查看文件

1
+// @flow
2
+
3
+import { BackHandler } from 'react-native';
4
+
5
+import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app';
6
+import { MiddlewareRegistry } from '../../base/redux';
7
+
8
+import BackButtonRegistry from './BackButtonRegistry';
9
+
10
+// Binding function to the proper instance, so then the event emitter won't replace the
11
+// underlying instance.
12
+BackButtonRegistry.onHardwareBackPress = BackButtonRegistry.onHardwareBackPress.bind(BackButtonRegistry);
13
+
14
+/**
15
+ * Middleware that captures App lifetime actions and subscribes to application
16
+ * state changes. When the application state changes it will fire the action
17
+ * required to mute or unmute the local video in case the application goes to
18
+ * the background or comes back from it.
19
+ *
20
+ * @param {Store} store - The redux store.
21
+ * @returns {Function}
22
+ * @see {@link https://facebook.github.io/react-native/docs/appstate.html}
23
+ */
24
+MiddlewareRegistry.register(() => next => action => {
25
+    switch (action.type) {
26
+    case APP_WILL_MOUNT:
27
+        BackHandler.addEventListener('hardwareBackPress', BackButtonRegistry.onHardwareBackPress);
28
+        break;
29
+
30
+    case APP_WILL_UNMOUNT:
31
+        BackHandler.removeEventListener('hardwareBackPress', BackButtonRegistry.onHardwareBackPress);
32
+        break;
33
+    }
34
+
35
+    return next(action);
36
+});

+ 23
- 11
react/features/remote-video-menu/components/native/RemoteVideoMenu.js 查看文件

5
 
5
 
6
 import { Avatar } from '../../../base/avatar';
6
 import { Avatar } from '../../../base/avatar';
7
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
7
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
8
-import {
9
-    BottomSheet
10
-} from '../../../base/dialog';
8
+import { BottomSheet, isDialogOpen } from '../../../base/dialog';
11
 import { getParticipantDisplayName } from '../../../base/participants';
9
 import { getParticipantDisplayName } from '../../../base/participants';
12
 import { connect } from '../../../base/redux';
10
 import { connect } from '../../../base/redux';
13
 import { StyleType } from '../../../base/styles';
11
 import { StyleType } from '../../../base/styles';
41
      */
39
      */
42
     _bottomSheetStyles: StyleType,
40
     _bottomSheetStyles: StyleType,
43
 
41
 
42
+    /**
43
+     * True if the menu is currently open, false otherwise.
44
+     */
45
+    _isOpen: boolean,
46
+
44
     /**
47
     /**
45
      * Display name of the participant retreived from Redux.
48
      * Display name of the participant retreived from Redux.
46
      */
49
      */
47
     _participantDisplayName: string
50
     _participantDisplayName: string
48
 }
51
 }
49
 
52
 
53
+// eslint-disable-next-line prefer-const
54
+let RemoteVideoMenu_;
55
+
50
 /**
56
 /**
51
  * Class to implement a popup menu that opens upon long pressing a thumbnail.
57
  * Class to implement a popup menu that opens upon long pressing a thumbnail.
52
  */
58
  */
93
         );
99
         );
94
     }
100
     }
95
 
101
 
96
-    _onCancel: () => void;
102
+    _onCancel: () => boolean;
97
 
103
 
98
     /**
104
     /**
99
      * Callback to hide the {@code RemoteVideoMenu}.
105
      * Callback to hide the {@code RemoteVideoMenu}.
100
      *
106
      *
101
      * @private
107
      * @private
102
-     * @returns {void}
108
+     * @returns {boolean}
103
      */
109
      */
104
     _onCancel() {
110
     _onCancel() {
105
-        this.props.dispatch(hideRemoteVideoMenu());
111
+        if (this.props._isOpen) {
112
+            this.props.dispatch(hideRemoteVideoMenu());
113
+
114
+            return true;
115
+        }
116
+
117
+        return false;
106
     }
118
     }
107
 }
119
 }
108
 
120
 
112
  * @param {Object} state - Redux state.
124
  * @param {Object} state - Redux state.
113
  * @param {Object} ownProps - Properties of component.
125
  * @param {Object} ownProps - Properties of component.
114
  * @private
126
  * @private
115
- * @returns {{
116
- *      _bottomSheetStyles: StyleType,
117
- *      _participantDisplayName: string
118
- *  }}
127
+ * @returns {Props}
119
  */
128
  */
120
 function _mapStateToProps(state, ownProps) {
129
 function _mapStateToProps(state, ownProps) {
121
     const { participant } = ownProps;
130
     const { participant } = ownProps;
123
     return {
132
     return {
124
         _bottomSheetStyles:
133
         _bottomSheetStyles:
125
             ColorSchemeRegistry.get(state, 'BottomSheet'),
134
             ColorSchemeRegistry.get(state, 'BottomSheet'),
135
+        _isOpen: isDialogOpen(state, RemoteVideoMenu_),
126
         _participantDisplayName: getParticipantDisplayName(
136
         _participantDisplayName: getParticipantDisplayName(
127
             state, participant.id)
137
             state, participant.id)
128
     };
138
     };
129
 }
139
 }
130
 
140
 
131
-export default connect(_mapStateToProps)(RemoteVideoMenu);
141
+RemoteVideoMenu_ = connect(_mapStateToProps)(RemoteVideoMenu);
142
+
143
+export default RemoteVideoMenu_;

+ 17
- 9
react/features/toolbox/components/native/OverflowMenu.js 查看文件

4
 import { Platform } from 'react-native';
4
 import { Platform } from 'react-native';
5
 
5
 
6
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
6
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
7
-import { BottomSheet, hideDialog } from '../../../base/dialog';
7
+import { BottomSheet, hideDialog, isDialogOpen } from '../../../base/dialog';
8
 import { CHAT_ENABLED, IOS_RECORDING_ENABLED, getFeatureFlag } from '../../../base/flags';
8
 import { CHAT_ENABLED, IOS_RECORDING_ENABLED, getFeatureFlag } from '../../../base/flags';
9
 import { connect } from '../../../base/redux';
9
 import { connect } from '../../../base/redux';
10
 import { StyleType } from '../../../base/styles';
10
 import { StyleType } from '../../../base/styles';
34
      */
34
      */
35
     _chatEnabled: boolean,
35
     _chatEnabled: boolean,
36
 
36
 
37
+    /**
38
+     * True if the overflow menu is currently visible, false otherwise.
39
+     */
40
+    _isOpen: boolean,
41
+
37
     /**
42
     /**
38
      * Whether the recoding button should be enabled or not.
43
      * Whether the recoding button should be enabled or not.
39
      */
44
      */
107
         );
112
         );
108
     }
113
     }
109
 
114
 
110
-    _onCancel: () => void;
115
+    _onCancel: () => boolean;
111
 
116
 
112
     /**
117
     /**
113
      * Hides this {@code OverflowMenu}.
118
      * Hides this {@code OverflowMenu}.
114
      *
119
      *
115
      * @private
120
      * @private
116
-     * @returns {void}
121
+     * @returns {boolean}
117
      */
122
      */
118
     _onCancel() {
123
     _onCancel() {
119
-        this.props.dispatch(hideDialog(OverflowMenu_));
124
+        if (this.props._isOpen) {
125
+            this.props.dispatch(hideDialog(OverflowMenu_));
126
+
127
+            return true;
128
+        }
129
+
130
+        return false;
120
     }
131
     }
121
 }
132
 }
122
 
133
 
125
  *
136
  *
126
  * @param {Object} state - Redux state.
137
  * @param {Object} state - Redux state.
127
  * @private
138
  * @private
128
- * @returns {{
129
- *      _bottomSheetStyles: StyleType,
130
- *      _chatEnabled: boolean,
131
- *      _recordingEnabled: boolean
132
- *  }}
139
+ * @returns {Props}
133
  */
140
  */
134
 function _mapStateToProps(state) {
141
 function _mapStateToProps(state) {
135
     return {
142
     return {
136
         _bottomSheetStyles:
143
         _bottomSheetStyles:
137
             ColorSchemeRegistry.get(state, 'BottomSheet'),
144
             ColorSchemeRegistry.get(state, 'BottomSheet'),
138
         _chatEnabled: getFeatureFlag(state, CHAT_ENABLED, true),
145
         _chatEnabled: getFeatureFlag(state, CHAT_ENABLED, true),
146
+        _isOpen: isDialogOpen(state, OverflowMenu_),
139
         _recordingEnabled: Platform.OS !== 'ios' || getFeatureFlag(state, IOS_RECORDING_ENABLED)
147
         _recordingEnabled: Platform.OS !== 'ios' || getFeatureFlag(state, IOS_RECORDING_ENABLED)
140
     };
148
     };
141
 }
149
 }

Loading…
取消
儲存