UNPKG

7.02 kBJavaScriptView Raw
1// @flow
2import * as React from "react";
3import * as R from "ramda";
4
5import {
6 sendQuickBrickEvent,
7 QUICK_BRICK_EVENTS,
8} from "@applicaster/zapp-react-native-bridge/QuickBrick";
9
10import {
11 debounce,
12 noop,
13} from "@applicaster/zapp-react-native-utils/functionUtils";
14
15import {
16 keyCode,
17 KEYS,
18 ARROW_KEYS,
19 focusManager,
20 playerManager,
21} from "@applicaster/zapp-react-native-utils/appUtils";
22
23import {
24 DisplayStateContext,
25 DISPLAY_STATES,
26} from "@applicaster/zapp-react-native-ui-components/Contexts/DisplayStateContext";
27
28import { PlayerContentContext } from "@applicaster/zapp-react-dom-ui-components/Contexts";
29
30import { withNavigator } from "@applicaster/zapp-react-native-ui-components/Decorators/Navigator";
31
32type Props = {
33 displayState: string,
34 setDisplayState: string => void,
35 setPlayerContent: ({} | null) => void,
36 navigator: {
37 goBack: () => void,
38 canGoBack: () => boolean,
39 replace: ({}) => void,
40 currentRoute: string,
41 },
42 rivers: {},
43};
44
45type KeyCode = { code: string, keyCode: number | string };
46
47const DEFAULT_TRANSPORT_CONTROLS_TIMEOUT = 5 * 1000;
48const LONG_KEY_PRESS_TIMEOUT = 2 * 1000;
49
50export function withInteractionManager(
51 Component: React.ComponentType<any>
52): React.ComponentType<any> {
53 class InteractionManager extends React.Component<Props> {
54 onKeyDown: KeyCode => void;
55 onKeyUp: KeyCode => void;
56 onKeyDownListener: KeyCode => void;
57 onKeyUpListener: KeyCode => void;
58
59 longKeyPressTimeout: any | null;
60 timeout: any | null;
61
62 constructor(props) {
63 super(props);
64 this.onKeyDown = this.onKeyDown.bind(this);
65 this.onKeyDownListener = debounce({ fn: this.onKeyDown, context: this });
66 this.onKeyUpListener = debounce({ fn: this.onKeyUp, context: this });
67
68 this.longKeyPressTimeout = null;
69 }
70
71 componentDidMount() {
72 window.addEventListener("keydown", this.onKeyDownListener);
73 window.addEventListener("keyup", this.onKeyUpListener);
74 }
75
76 componentWillUnmount() {
77 window.removeEventListener("keydown", this.onKeyDownListener);
78 window.removeEventListener("keyup", this.onKeyUpListener);
79
80 if (this.timeout) {
81 this.clearTimeout();
82 }
83 }
84
85 isHomeScreen() {
86 const { navigator } = this.props;
87 const { rivers } = this.props;
88
89 const homeId = R.compose(
90 R.prop("id"),
91 R.find(R.prop("home")),
92 R.values
93 )(rivers);
94
95 const homePath = `/river/${homeId}`;
96
97 return homePath === navigator.currentRoute;
98 }
99
100 goToHome() {
101 const { navigator } = this.props;
102 const { rivers } = this.props;
103 const homeRiver = R.find(R.propEq("home", true), R.values(rivers));
104
105 navigator.replace(homeRiver);
106 }
107
108 resetHudTimeout() {
109 const { setDisplayState } = this.props;
110
111 if (this.timeout) {
112 clearTimeout(this.timeout);
113 }
114
115 this.timeout = setTimeout(() => {
116 setDisplayState(DISPLAY_STATES.PLAYER);
117 this.timeout = null;
118 }, DEFAULT_TRANSPORT_CONTROLS_TIMEOUT);
119 }
120
121 clearTimeout() {
122 if (this.timeout) {
123 clearTimeout(this.timeout);
124 this.timeout = null;
125 }
126 }
127
128 onKeyUp(event) {
129 this.clearLongPressTimeout();
130 }
131
132 clearLongPressTimeout() {
133 if (this.longKeyPressTimeout) {
134 clearTimeout(this.longKeyPressTimeout);
135 this.longKeyPressTimeout = null;
136 }
137 }
138
139 runIfLongPress(action, delay = LONG_KEY_PRESS_TIMEOUT) {
140 this.clearLongPressTimeout();
141
142 this.longKeyPressTimeout = setTimeout(() => {
143 (action || noop)();
144 this.clearLongPressTimeout();
145 }, delay);
146 }
147
148 onKeyDown(event) {
149 const {
150 displayState,
151 setDisplayState,
152 setPlayerContent,
153 navigator,
154 } = this.props;
155
156 if (keyCode(event).matches(KEYS.Exit)) {
157 this.runIfLongPress(
158 sendQuickBrickEvent(QUICK_BRICK_EVENTS.MOVE_APP_TO_BACKGROUND)
159 );
160 }
161
162 /**
163 When the player is displayed, we listen to Native Play / Pause / Stop
164 buttons. Any other key press will display the transport controls
165 */
166 if (
167 displayState === DISPLAY_STATES.PLAYER ||
168 displayState === DISPLAY_STATES.HUD
169 ) {
170 this.resetHudTimeout();
171
172 /**
173 registering native keys for playback
174 */
175 if (keyCode(event).matches(KEYS.TogglePlayPause)) {
176 const { playing } = playerManager.getState() || {};
177 if (!playing) {
178 return playerManager.togglePlayPause();
179 }
180 }
181
182 if (keyCode(event).matches(KEYS.Play)) {
183 return playerManager.play();
184 }
185
186 if (keyCode(event).matches(KEYS.Pause)) {
187 setDisplayState(DISPLAY_STATES.HUD);
188 return playerManager.pause();
189 }
190
191 if (keyCode(event).matches(KEYS.Stop)) {
192 setDisplayState(DISPLAY_STATES.DEFAULT);
193 setPlayerContent(null);
194 navigator.goBack();
195 return;
196 }
197
198 if (keyCode(event).matches(KEYS.Back)) {
199 setDisplayState(DISPLAY_STATES.DEFAULT);
200 setPlayerContent(null);
201 navigator.goBack();
202 return;
203 }
204
205 if (keyCode(event).matches(KEYS.Forward)) {
206 setDisplayState(DISPLAY_STATES.HUD);
207 return playerManager.forward();
208 }
209
210 if (keyCode(event).matches(KEYS.Rewind)) {
211 setDisplayState(DISPLAY_STATES.HUD);
212 return playerManager.rewind();
213 }
214
215 if (displayState === DISPLAY_STATES.PLAYER) {
216 return setDisplayState(DISPLAY_STATES.HUD);
217 }
218 }
219
220 /**
221 When pressing backspace or the Native "Back" button:
222 if on the transport controls: we show the player
223 if on the player : we show the UI
224 if on the UI : we exit the app
225 */
226
227 if (keyCode(event).matches(KEYS.Back)) {
228 switch (displayState) {
229 case DISPLAY_STATES.HUD:
230 setDisplayState(DISPLAY_STATES.PLAYER);
231 break;
232
233 case DISPLAY_STATES.PLAYER:
234 setDisplayState(DISPLAY_STATES.DEFAULT);
235 break;
236
237 case DISPLAY_STATES.DEFAULT:
238 if (navigator.canGoBack()) {
239 navigator.goBack();
240 } else if (this.isHomeScreen()) {
241 sendQuickBrickEvent(QUICK_BRICK_EVENTS.MOVE_APP_TO_BACKGROUND);
242 } else {
243 this.goToHome();
244 }
245 break;
246
247 default:
248 break;
249 }
250 }
251
252 if (keyCode(event).matches(KEYS.Enter)) {
253 focusManager.press();
254 return true;
255 }
256
257 if (keyCode(event).matchesAny(...ARROW_KEYS)) {
258 focusManager.moveFocus(keyCode(event).direction());
259 return true;
260 }
261 }
262
263 render() {
264 return <Component {...this.props} />;
265 }
266 }
267
268 return R.compose(
269 withNavigator,
270 PlayerContentContext.withConsumer,
271 DisplayStateContext.withConsumer
272 )(InteractionManager);
273}
274
\No newline at end of file