@applicaster/zapp-react-native-ui-components 16.0.0-alpha.9225550512 → 16.0.0-alpha.9338540010
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Components/AudioPlayer/tv/__tests__/audioPlayer.test.js +19 -0
- package/Components/AudioPlayer/tv/__tests__/index.test.tsx +120 -0
- package/Components/AudioPlayer/tv/index.tsx +5 -1
- package/Components/Cell/Cell.tsx +3 -6
- package/Components/Cell/TvOSCellComponent.tsx +0 -1
- package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +15 -0
- package/Components/GeneralContentScreen/hookAdapter/__tests__/skipExpression.test.ts +294 -0
- package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +64 -0
- package/Components/GeneralContentScreen/hookAdapter/skipExpression.ts +252 -0
- package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +18 -0
- package/Components/LinkHandler/LinkHandler.tsx +2 -7
- package/Components/MasterCell/DefaultComponents/ActionButton.tsx +5 -1
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +19 -4
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +43 -1
- package/Components/MasterCell/DefaultComponents/Button.tsx +1 -0
- package/Components/MasterCell/DefaultComponents/Image/hoc/utils/__tests__/withDimensions.test.ts +420 -125
- package/Components/MasterCell/DefaultComponents/Image/hoc/utils/index.ts +114 -19
- package/Components/MasterCell/DefaultComponents/Image/hoc/withDimensions.tsx +19 -8
- package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +2 -4
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +14 -6
- package/Components/MasterCell/DefaultComponents/Toggle.tsx +36 -14
- package/Components/MasterCell/DefaultComponents/__tests__/Toggle.test.tsx +167 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +1 -1
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +4 -2
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/factory.test.ts +32 -0
- package/Components/MasterCell/__tests__/masterCell.test.js +60 -0
- package/Components/MasterCell/index.tsx +28 -4
- package/Components/MasterCell/utils/__tests__/withoutFocusedState.test.js +20 -0
- package/Components/MasterCell/utils/index.ts +18 -0
- package/Components/ModalComponent/ActionsBottomSheet/ActionButton.tsx +56 -0
- package/Components/ModalComponent/ActionsBottomSheet/__tests__/openActionsBottomSheet.test.ts +38 -0
- package/Components/ModalComponent/ActionsBottomSheet/boundActionButton.tsx +24 -0
- package/Components/ModalComponent/ActionsBottomSheet/index.ts +9 -0
- package/Components/ModalComponent/ActionsBottomSheet/openActionsBottomSheet.ts +61 -0
- package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +87 -97
- package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +87 -126
- package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +140 -83
- package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +274 -124
- package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +205 -0
- package/Components/ModalComponent/AudioPlayer/Components/__tests__/Item.test.tsx +164 -0
- package/Components/ModalComponent/AudioPlayer/Components/__tests__/NowPlayingHeaderSection.test.tsx +36 -0
- package/Components/ModalComponent/AudioPlayer/Components/index.ts +3 -3
- package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +433 -0
- package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +563 -0
- package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
- package/Components/ModalComponent/BottomSheetModalContent.tsx +440 -66
- package/Components/ModalComponent/Button/index.tsx +5 -3
- package/Components/ModalComponent/Header/CloseButton.tsx +12 -0
- package/Components/ModalComponent/Header/__tests__/CloseButton.test.tsx +72 -0
- package/Components/ModalComponent/Header/index.tsx +20 -7
- package/Components/ModalComponent/Header/utils.ts +1 -1
- package/Components/ModalComponent/ModalBlocksStyleContext.tsx +10 -0
- package/Components/ModalComponent/SortableList.tsx +156 -0
- package/Components/ModalComponent/SortableList.web.tsx +34 -0
- package/Components/ModalComponent/TextInputContent.tsx +135 -0
- package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
- package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +343 -0
- package/Components/ModalComponent/__tests__/SortableList.web.test.tsx +16 -0
- package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +169 -0
- package/Components/ModalComponent/__tests__/showTextInput.test.ts +120 -0
- package/Components/ModalComponent/index.tsx +18 -9
- package/Components/ModalComponent/presets/__tests__/getCell.test.ts +90 -0
- package/Components/ModalComponent/presets/dynamicCollectionCell.tsx +78 -0
- package/Components/ModalComponent/presets/index.ts +27 -0
- package/Components/ModalComponent/presets/selectableCell.tsx +28 -0
- package/Components/ModalComponent/presets/standardCell.tsx +41 -0
- package/Components/ModalComponent/presets/types.ts +35 -0
- package/Components/ModalComponent/showTextInput.ts +54 -0
- package/Components/ModalComponent/utils.ts +82 -21
- package/Components/OfflineHandler/__tests__/index.test.tsx +109 -0
- package/Components/OfflineHandler/hooks.ts +0 -12
- package/Components/OfflineHandler/index.tsx +42 -4
- package/Components/PlayerContainer/PlayerContainer.tsx +19 -8
- package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -4
- package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +66 -13
- package/Components/River/ComponentsMap/ComponentsMap.tsx +89 -60
- package/Components/River/River.tsx +4 -0
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +140 -75
- package/Components/River/__tests__/componentsMap.test.js +8 -3
- package/Components/River/__tests__/resultCallback.test.tsx +47 -0
- package/Components/Screen/__tests__/Screen.test.tsx +24 -0
- package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +2 -0
- package/Components/Screen/index.tsx +5 -0
- package/Components/Screen/navigationHandler.ts +8 -8
- package/Components/ScreenRevealManager/ScreenRevealManager.ts +18 -2
- package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +36 -0
- package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +56 -0
- package/Components/ScreenRevealManager/withScreenRevealManager.tsx +21 -4
- package/Components/Transitioner/AnimationManager.js +1 -1
- package/Components/Transitioner/Scene.tsx +4 -3
- package/Components/Transitioner/Transitioner.tsx +71 -33
- package/Components/Transitioner/__tests__/transitioner.test.js +372 -3
- package/Components/VideoLive/LiveImageManager.ts +9 -3
- package/Components/VideoLive/__tests__/LiveImageManager.test.ts +59 -0
- package/Components/VideoModal/PlayerWrapper.tsx +4 -1
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
- package/Components/VideoModal/__tests__/playerWrapperStyle.test.ts +58 -0
- package/Components/VideoModal/__tests__/playerWrapperUtils.test.ts +16 -0
- package/Components/VideoModal/playerWrapperStyle.ts +21 -3
- package/Components/VideoModal/playerWrapperUtils.ts +7 -1
- package/Contexts/ModalNavigationContext/__tests__/index.test.tsx +15 -0
- package/Contexts/ModalNavigationContext/index.tsx +2 -2
- package/Decorators/ZappPipesDataConnector/__tests__/screenToolbox.test.ts +347 -0
- package/Decorators/ZappPipesDataConnector/__tests__/screenToolboxUnavailable.test.ts +53 -0
- package/Decorators/ZappPipesDataConnector/index.tsx +19 -4
- package/Decorators/ZappPipesDataConnector/screenToolbox.ts +384 -0
- package/Hooks/useEntryActionsExpander.ts +63 -0
- package/assets/empty-Info.png +0 -0
- package/package.json +6 -5
- package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +0 -332
- /package/Decorators/ZappPipesDataConnector/__tests__/{Hero.js → Hero.tsx} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { View } from "react-native";
|
|
3
|
-
import { append, compose, identity, tail,
|
|
3
|
+
import { append, compose, identity, tail, update } from "ramda";
|
|
4
4
|
|
|
5
5
|
import { AnimationManager } from "./AnimationManager";
|
|
6
6
|
import { Scene } from "./Scene";
|
|
@@ -18,11 +18,23 @@ export const NAV_ACTION_REPLACE = "REPLACE";
|
|
|
18
18
|
|
|
19
19
|
export const NAV_ACTION_BACK = "POP";
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const RIVER_SEGMENT_REGEX = /river\//g;
|
|
22
|
+
|
|
23
|
+
// Number of "river/" segments in the route, i.e. its depth in the navigation stack
|
|
24
|
+
function countRiverSegments(route?: string): number {
|
|
25
|
+
return (route?.match(RIVER_SEGMENT_REGEX) || []).length;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// The screen element rendered for the current route
|
|
29
|
+
type SceneChild = React.ReactElement<{
|
|
30
|
+
pathname: string;
|
|
31
|
+
screenData: NavigationScreenData;
|
|
32
|
+
}>;
|
|
33
|
+
|
|
34
|
+
type TransitionerScene = {
|
|
35
|
+
children: SceneChild;
|
|
36
|
+
key: string;
|
|
37
|
+
screenUniqueId: string;
|
|
26
38
|
};
|
|
27
39
|
|
|
28
40
|
/**
|
|
@@ -31,7 +43,7 @@ type SceneProps = {
|
|
|
31
43
|
* @param {Object} children
|
|
32
44
|
* @returns { children: Object, key: string }
|
|
33
45
|
*/
|
|
34
|
-
function prepareScene(children) {
|
|
46
|
+
function prepareScene(children: SceneChild): TransitionerScene {
|
|
35
47
|
return {
|
|
36
48
|
children,
|
|
37
49
|
key: children.props.pathname,
|
|
@@ -45,7 +57,10 @@ function prepareScene(children) {
|
|
|
45
57
|
* @param {any} props Component's props
|
|
46
58
|
* @param {any} state Component's state
|
|
47
59
|
*/
|
|
48
|
-
function scenesForBackTransition(
|
|
60
|
+
function scenesForBackTransition(
|
|
61
|
+
props: Pick<Props, "children">,
|
|
62
|
+
{ scenes }: Pick<State, "scenes">
|
|
63
|
+
): TransitionerScene[] {
|
|
49
64
|
return scenes.length > 1
|
|
50
65
|
? identity(scenes)
|
|
51
66
|
: [prepareScene(props.children), scenes[0]];
|
|
@@ -53,7 +68,8 @@ function scenesForBackTransition(props: SceneProps, { scenes }) {
|
|
|
53
68
|
|
|
54
69
|
type Props = {
|
|
55
70
|
transitionConfig: any;
|
|
56
|
-
|
|
71
|
+
isRTL?: boolean;
|
|
72
|
+
children: SceneChild;
|
|
57
73
|
navigator: {
|
|
58
74
|
previousAction: string;
|
|
59
75
|
currentRoute: string;
|
|
@@ -78,13 +94,12 @@ type Props = {
|
|
|
78
94
|
};
|
|
79
95
|
|
|
80
96
|
type State = {
|
|
81
|
-
animatedValue: any;
|
|
82
97
|
animating: boolean;
|
|
83
98
|
duration: number;
|
|
84
99
|
easing: any;
|
|
85
100
|
from: any;
|
|
86
101
|
to: any;
|
|
87
|
-
scenes:
|
|
102
|
+
scenes: TransitionerScene[];
|
|
88
103
|
};
|
|
89
104
|
|
|
90
105
|
const styles = {
|
|
@@ -92,11 +107,18 @@ const styles = {
|
|
|
92
107
|
} as const;
|
|
93
108
|
|
|
94
109
|
export class TransitionerComponent extends React.PureComponent<Props, State> {
|
|
110
|
+
private animationManager: AnimationManager;
|
|
111
|
+
|
|
112
|
+
private transitionId: number;
|
|
113
|
+
|
|
95
114
|
constructor(props: Props) {
|
|
96
115
|
super(props);
|
|
97
116
|
|
|
98
117
|
this.animationManager = new AnimationManager(props);
|
|
99
118
|
|
|
119
|
+
// Bumped by every animated transition; end callbacks of older ones are ignored
|
|
120
|
+
this.transitionId = 0;
|
|
121
|
+
|
|
100
122
|
this.state = {
|
|
101
123
|
scenes: [prepareScene(props.children)], // starting with index: 0
|
|
102
124
|
...this.animationManager.initialState(),
|
|
@@ -113,29 +135,36 @@ export class TransitionerComponent extends React.PureComponent<Props, State> {
|
|
|
113
135
|
* and trigger push animation with callbacks.
|
|
114
136
|
*/
|
|
115
137
|
pushTransitionStart() {
|
|
138
|
+
const transitionId = ++this.transitionId;
|
|
139
|
+
|
|
116
140
|
this.setState(
|
|
117
141
|
{
|
|
118
|
-
|
|
142
|
+
// Append right after the visible scene: stateForAction(PUSH) animates to.index + 1
|
|
143
|
+
scenes: [
|
|
144
|
+
...this.state.scenes.slice(0, this.state.to.index + 1),
|
|
145
|
+
prepareScene(this.props.children),
|
|
146
|
+
],
|
|
119
147
|
...this.animationManager.stateForAction(this.state, NAV_ACTION_PUSH),
|
|
120
148
|
},
|
|
121
149
|
() => {
|
|
122
|
-
this.animationManager.animate(this.state,
|
|
150
|
+
this.animationManager.animate(this.state, () =>
|
|
151
|
+
this.pushTransitionEnd(transitionId)
|
|
152
|
+
);
|
|
123
153
|
}
|
|
124
154
|
);
|
|
125
155
|
}
|
|
126
156
|
|
|
127
157
|
/**
|
|
128
158
|
* Trigger back animation.
|
|
129
|
-
* The
|
|
159
|
+
* The scenes after the target one are removed in the "backTransitionEnd" callback,
|
|
130
160
|
* only after the animation is complete.
|
|
131
161
|
*/
|
|
132
|
-
backTransitionStart() {
|
|
133
|
-
const
|
|
162
|
+
backTransitionStart(previousRoute: string) {
|
|
163
|
+
const transitionId = ++this.transitionId;
|
|
134
164
|
const currentRoute = this.props.navigator?.currentRoute;
|
|
135
165
|
|
|
136
166
|
const diffLengthRoutes =
|
|
137
|
-
(previousRoute
|
|
138
|
-
(currentRoute?.match(/river\//g) || []).length;
|
|
167
|
+
countRiverSegments(previousRoute) - countRiverSegments(currentRoute);
|
|
139
168
|
|
|
140
169
|
const stepsBack =
|
|
141
170
|
diffLengthRoutes && diffLengthRoutes > 0 ? diffLengthRoutes : 1;
|
|
@@ -151,30 +180,32 @@ export class TransitionerComponent extends React.PureComponent<Props, State> {
|
|
|
151
180
|
},
|
|
152
181
|
() => {
|
|
153
182
|
this.animationManager.animate(this.state, () =>
|
|
154
|
-
this.backTransitionEnd(
|
|
183
|
+
this.backTransitionEnd(transitionId)
|
|
155
184
|
);
|
|
156
185
|
}
|
|
157
186
|
);
|
|
158
187
|
}
|
|
159
188
|
|
|
160
|
-
shouldSkipAnimationForRoute(route) {
|
|
189
|
+
shouldSkipAnimationForRoute(route: string) {
|
|
161
190
|
// We are skipping animations for some routes, likes hooks & the player mainly
|
|
162
191
|
|
|
163
192
|
return route.includes("/playable") || route.includes("/hook");
|
|
164
193
|
}
|
|
165
194
|
|
|
166
|
-
shouldResetStateForRoute(route) {
|
|
195
|
+
shouldResetStateForRoute(route: string) {
|
|
167
196
|
// We are resetting the transitioner state for root routes
|
|
168
197
|
|
|
169
|
-
return (route
|
|
198
|
+
return countRiverSegments(route) === 1;
|
|
170
199
|
}
|
|
171
200
|
|
|
172
|
-
transitionWithoutAnimation(resetState) {
|
|
173
|
-
|
|
201
|
+
transitionWithoutAnimation(resetState?: boolean) {
|
|
202
|
+
// Scenes after the visible one are only leaving (back animation in flight),
|
|
203
|
+
// so the new scene replaces the one being backed to
|
|
204
|
+
const scenes = this.state.scenes.slice(0, this.state.to.index + 1);
|
|
174
205
|
const newScene = prepareScene(this.props.children);
|
|
175
206
|
const isLastSceneHook = scenes[scenes.length - 1].key.startsWith("/hook");
|
|
176
207
|
|
|
177
|
-
let newState
|
|
208
|
+
let newState: Pick<State, "scenes"> | Pick<State, "scenes" | "to">;
|
|
178
209
|
|
|
179
210
|
if (resetState) {
|
|
180
211
|
newState = {
|
|
@@ -242,7 +273,7 @@ export class TransitionerComponent extends React.PureComponent<Props, State> {
|
|
|
242
273
|
break;
|
|
243
274
|
|
|
244
275
|
case NAV_ACTION_BACK: {
|
|
245
|
-
this.backTransitionStart();
|
|
276
|
+
this.backTransitionStart(previousRoute);
|
|
246
277
|
break;
|
|
247
278
|
}
|
|
248
279
|
|
|
@@ -264,20 +295,27 @@ export class TransitionerComponent extends React.PureComponent<Props, State> {
|
|
|
264
295
|
}
|
|
265
296
|
}
|
|
266
297
|
|
|
267
|
-
// Sets state.animating to false
|
|
268
|
-
|
|
298
|
+
// Sets state.animating to false,
|
|
299
|
+
// unless a newer transition has started since this one
|
|
300
|
+
pushTransitionEnd(transitionId: number) {
|
|
301
|
+
if (transitionId !== this.transitionId) return;
|
|
302
|
+
|
|
269
303
|
this.setState({
|
|
270
304
|
animating: false,
|
|
271
305
|
});
|
|
272
306
|
}
|
|
273
307
|
|
|
274
308
|
// Sets state.animating to false
|
|
275
|
-
// and
|
|
276
|
-
|
|
277
|
-
|
|
309
|
+
// and drops every scene after the target one. Reads the latest state,
|
|
310
|
+
// so it's correct however many backs were merged into this animation.
|
|
311
|
+
// Ignored if a newer transition has started since this one
|
|
312
|
+
backTransitionEnd(transitionId: number) {
|
|
313
|
+
if (transitionId !== this.transitionId) return;
|
|
314
|
+
|
|
315
|
+
this.setState(({ scenes, to }) => ({
|
|
278
316
|
animating: false,
|
|
279
|
-
scenes:
|
|
280
|
-
});
|
|
317
|
+
scenes: scenes.slice(0, to.index + 1),
|
|
318
|
+
}));
|
|
281
319
|
}
|
|
282
320
|
|
|
283
321
|
// On initial state, both "to" and "from" point to a single scene
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Text, View } from "react-native";
|
|
2
|
+
import { Animated, Text, View } from "react-native";
|
|
3
|
+
import { v4 as uuidV4 } from "uuid";
|
|
3
4
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { AnimationManager } from "../AnimationManager";
|
|
6
|
+
import {
|
|
7
|
+
NAV_ACTION_BACK,
|
|
8
|
+
NAV_ACTION_PUSH,
|
|
9
|
+
NAV_ACTION_REPLACE,
|
|
10
|
+
TransitionerComponent,
|
|
11
|
+
} from "../Transitioner";
|
|
12
|
+
import { act, render } from "@testing-library/react-native";
|
|
6
13
|
|
|
7
14
|
jest.mock("../Scene", () => {
|
|
8
15
|
const View = jest.requireActual("react-native").View;
|
|
@@ -40,6 +47,47 @@ const transitionerComponentProps = {
|
|
|
40
47
|
|
|
41
48
|
jest.mock("uuid");
|
|
42
49
|
|
|
50
|
+
const sceneTransitionConfig = () => ({
|
|
51
|
+
push: {
|
|
52
|
+
duration: 0,
|
|
53
|
+
easing: jest.fn(),
|
|
54
|
+
from: { style: {} },
|
|
55
|
+
to: { style: {} },
|
|
56
|
+
},
|
|
57
|
+
back: {
|
|
58
|
+
duration: 0,
|
|
59
|
+
easing: jest.fn(),
|
|
60
|
+
from: { style: {} },
|
|
61
|
+
to: { style: {} },
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const sceneFor = (route) => (
|
|
66
|
+
<View pathname={route}>
|
|
67
|
+
<Text>{route}</Text>
|
|
68
|
+
</View>
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const root = "/river/root";
|
|
72
|
+
const child = `${root}/river/child`;
|
|
73
|
+
const grandchild = `${child}/river/grandchild`;
|
|
74
|
+
|
|
75
|
+
const propsFor = (route, previousAction, children = sceneFor(route)) => ({
|
|
76
|
+
navigator: {
|
|
77
|
+
previousAction,
|
|
78
|
+
currentRoute: route,
|
|
79
|
+
screenData: {},
|
|
80
|
+
},
|
|
81
|
+
transitionConfig: sceneTransitionConfig,
|
|
82
|
+
layoutData: {
|
|
83
|
+
isTablet: false,
|
|
84
|
+
isTabletPortrait: false,
|
|
85
|
+
},
|
|
86
|
+
children,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const sceneKeys = (ref) => ref.current.state.scenes.map((scene) => scene.key);
|
|
90
|
+
|
|
43
91
|
describe("<TransitionerComponent />", () => {
|
|
44
92
|
it("renders correctly", () => {
|
|
45
93
|
const wrapper = render(
|
|
@@ -50,4 +98,325 @@ describe("<TransitionerComponent />", () => {
|
|
|
50
98
|
|
|
51
99
|
expect(wrapper.toJSON()).toMatchSnapshot();
|
|
52
100
|
});
|
|
101
|
+
|
|
102
|
+
it("removes both intermediate scenes when backing out to the tab root", () => {
|
|
103
|
+
jest
|
|
104
|
+
.spyOn(AnimationManager.prototype, "animate")
|
|
105
|
+
.mockImplementation((_, done) => done());
|
|
106
|
+
|
|
107
|
+
const ref = React.createRef();
|
|
108
|
+
|
|
109
|
+
const { rerender } = render(
|
|
110
|
+
<TransitionerComponent
|
|
111
|
+
ref={ref}
|
|
112
|
+
{...propsFor(root, NAV_ACTION_REPLACE)}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
act(() => {
|
|
117
|
+
rerender(
|
|
118
|
+
<TransitionerComponent
|
|
119
|
+
ref={ref}
|
|
120
|
+
{...propsFor(child, NAV_ACTION_PUSH)}
|
|
121
|
+
/>
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
act(() => {
|
|
126
|
+
rerender(
|
|
127
|
+
<TransitionerComponent
|
|
128
|
+
ref={ref}
|
|
129
|
+
{...propsFor(grandchild, NAV_ACTION_PUSH)}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
act(() => {
|
|
135
|
+
rerender(
|
|
136
|
+
<TransitionerComponent ref={ref} {...propsFor(root, NAV_ACTION_BACK)} />
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(sceneKeys(ref)).toEqual([root]);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("with interruptible animations", () => {
|
|
144
|
+
// End callback of the animation that is currently "running"
|
|
145
|
+
let runningAnimationEnd = null;
|
|
146
|
+
|
|
147
|
+
const finishAnimation = () => {
|
|
148
|
+
act(() => {
|
|
149
|
+
const done = runningAnimationEnd;
|
|
150
|
+
runningAnimationEnd = null;
|
|
151
|
+
done?.();
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const mountTransitioner = ({
|
|
156
|
+
initialRoute = root,
|
|
157
|
+
renderScene = sceneFor,
|
|
158
|
+
} = {}) => {
|
|
159
|
+
const ref = React.createRef();
|
|
160
|
+
|
|
161
|
+
const transitioner = (route, action) => (
|
|
162
|
+
<TransitionerComponent
|
|
163
|
+
ref={ref}
|
|
164
|
+
{...propsFor(route, action, renderScene(route))}
|
|
165
|
+
/>
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const { rerender } = render(
|
|
169
|
+
transitioner(initialRoute, NAV_ACTION_REPLACE)
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const navigate = (route, action) => {
|
|
173
|
+
act(() => {
|
|
174
|
+
rerender(transitioner(route, action));
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
return { ref, navigate };
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
beforeEach(() => {
|
|
182
|
+
let sceneCount = 0;
|
|
183
|
+
uuidV4.mockImplementation(() => `scene-${++sceneCount}`);
|
|
184
|
+
|
|
185
|
+
jest
|
|
186
|
+
.spyOn(AnimationManager.prototype, "animate")
|
|
187
|
+
.mockImplementation((_, done) => {
|
|
188
|
+
runningAnimationEnd = done;
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const setValue = Animated.Value.prototype.setValue;
|
|
192
|
+
|
|
193
|
+
// RN 0.77: setValue() stops the running animation, which calls
|
|
194
|
+
// its end callback synchronously with { finished: false }
|
|
195
|
+
jest
|
|
196
|
+
.spyOn(Animated.Value.prototype, "setValue")
|
|
197
|
+
.mockImplementation(function (value) {
|
|
198
|
+
const interrupted = runningAnimationEnd;
|
|
199
|
+
runningAnimationEnd = null;
|
|
200
|
+
interrupted?.();
|
|
201
|
+
|
|
202
|
+
return setValue.call(this, value);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
afterEach(() => {
|
|
207
|
+
runningAnimationEnd = null;
|
|
208
|
+
jest.restoreAllMocks();
|
|
209
|
+
uuidV4.mockReset();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("drops interrupted scenes when going back twice during a back animation", () => {
|
|
213
|
+
const { ref, navigate } = mountTransitioner();
|
|
214
|
+
|
|
215
|
+
navigate(child, NAV_ACTION_PUSH);
|
|
216
|
+
finishAnimation();
|
|
217
|
+
navigate(grandchild, NAV_ACTION_PUSH);
|
|
218
|
+
finishAnimation();
|
|
219
|
+
|
|
220
|
+
navigate(child, NAV_ACTION_BACK);
|
|
221
|
+
navigate(root, NAV_ACTION_BACK);
|
|
222
|
+
|
|
223
|
+
expect(ref.current.state.animating).toBe(true);
|
|
224
|
+
|
|
225
|
+
finishAnimation();
|
|
226
|
+
|
|
227
|
+
expect(sceneKeys(ref)).toEqual([root]);
|
|
228
|
+
expect(ref.current.state.animating).toBe(false);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("remounts the screen when revisiting it after a quick double back", () => {
|
|
232
|
+
const mounts = {};
|
|
233
|
+
|
|
234
|
+
const MountProbe = ({ pathname }) => {
|
|
235
|
+
React.useEffect(() => {
|
|
236
|
+
mounts[pathname] = (mounts[pathname] || 0) + 1;
|
|
237
|
+
}, [pathname]);
|
|
238
|
+
|
|
239
|
+
return <Text>{pathname}</Text>;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const { ref, navigate } = mountTransitioner({
|
|
243
|
+
renderScene: (route) => <MountProbe pathname={route} />,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
navigate(child, NAV_ACTION_PUSH);
|
|
247
|
+
finishAnimation();
|
|
248
|
+
|
|
249
|
+
const originalChildId = ref.current.state.scenes[1].screenUniqueId;
|
|
250
|
+
|
|
251
|
+
navigate(grandchild, NAV_ACTION_PUSH);
|
|
252
|
+
finishAnimation();
|
|
253
|
+
navigate(child, NAV_ACTION_BACK);
|
|
254
|
+
navigate(root, NAV_ACTION_BACK);
|
|
255
|
+
finishAnimation();
|
|
256
|
+
|
|
257
|
+
navigate(child, NAV_ACTION_PUSH);
|
|
258
|
+
finishAnimation();
|
|
259
|
+
|
|
260
|
+
const { scenes, to } = ref.current.state;
|
|
261
|
+
|
|
262
|
+
expect(scenes).toHaveLength(2);
|
|
263
|
+
expect(to.index).toBe(1);
|
|
264
|
+
expect(scenes[1].screenUniqueId).not.toBe(originalChildId);
|
|
265
|
+
expect(mounts[child]).toBe(2);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("keeps a single scene when going back from the first scene", () => {
|
|
269
|
+
const { ref, navigate } = mountTransitioner({ initialRoute: child });
|
|
270
|
+
|
|
271
|
+
navigate(root, NAV_ACTION_BACK);
|
|
272
|
+
finishAnimation();
|
|
273
|
+
|
|
274
|
+
expect(sceneKeys(ref)).toEqual([root]);
|
|
275
|
+
expect(ref.current.state.to.index).toBe(0);
|
|
276
|
+
expect(ref.current.state.animating).toBe(false);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("pushes a fresh scene after a completed single back", () => {
|
|
280
|
+
const { ref, navigate } = mountTransitioner();
|
|
281
|
+
|
|
282
|
+
navigate(child, NAV_ACTION_PUSH);
|
|
283
|
+
finishAnimation();
|
|
284
|
+
|
|
285
|
+
const originalChildId = ref.current.state.scenes[1].screenUniqueId;
|
|
286
|
+
|
|
287
|
+
navigate(root, NAV_ACTION_BACK);
|
|
288
|
+
finishAnimation();
|
|
289
|
+
navigate(child, NAV_ACTION_PUSH);
|
|
290
|
+
finishAnimation();
|
|
291
|
+
|
|
292
|
+
const { scenes, to } = ref.current.state;
|
|
293
|
+
|
|
294
|
+
expect(sceneKeys(ref)).toEqual([root, child]);
|
|
295
|
+
expect(to.index).toBe(1);
|
|
296
|
+
expect(scenes[1].screenUniqueId).not.toBe(originalChildId);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("ignores the end of a back animation that a newer back superseded", () => {
|
|
300
|
+
const { ref, navigate } = mountTransitioner();
|
|
301
|
+
|
|
302
|
+
navigate(child, NAV_ACTION_PUSH);
|
|
303
|
+
finishAnimation();
|
|
304
|
+
navigate(grandchild, NAV_ACTION_PUSH);
|
|
305
|
+
finishAnimation();
|
|
306
|
+
|
|
307
|
+
navigate(child, NAV_ACTION_BACK);
|
|
308
|
+
|
|
309
|
+
const supersededEnd = runningAnimationEnd;
|
|
310
|
+
|
|
311
|
+
// Deliver the interrupted animation's end late, after the newer one started
|
|
312
|
+
Animated.Value.prototype.setValue.mockImplementation(() => {});
|
|
313
|
+
|
|
314
|
+
navigate(root, NAV_ACTION_BACK);
|
|
315
|
+
|
|
316
|
+
act(() => {
|
|
317
|
+
supersededEnd();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
expect(ref.current.state.animating).toBe(true);
|
|
321
|
+
expect(sceneKeys(ref)).toEqual([root, child, grandchild]);
|
|
322
|
+
|
|
323
|
+
finishAnimation();
|
|
324
|
+
|
|
325
|
+
expect(sceneKeys(ref)).toEqual([root]);
|
|
326
|
+
expect(ref.current.state.animating).toBe(false);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("drops the pushed scene when going back before its push animation ends", () => {
|
|
330
|
+
const { ref, navigate } = mountTransitioner();
|
|
331
|
+
|
|
332
|
+
navigate(child, NAV_ACTION_PUSH);
|
|
333
|
+
navigate(root, NAV_ACTION_BACK);
|
|
334
|
+
|
|
335
|
+
expect(ref.current.state.animating).toBe(true);
|
|
336
|
+
|
|
337
|
+
finishAnimation();
|
|
338
|
+
|
|
339
|
+
expect(sceneKeys(ref)).toEqual([root]);
|
|
340
|
+
expect(ref.current.state.animating).toBe(false);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("pushes right after the visible scene even when stale scenes are left behind", () => {
|
|
344
|
+
const { ref, navigate } = mountTransitioner();
|
|
345
|
+
|
|
346
|
+
navigate(child, NAV_ACTION_PUSH);
|
|
347
|
+
finishAnimation();
|
|
348
|
+
|
|
349
|
+
// Leftover past to.index, as some other path could leave behind
|
|
350
|
+
act(() => {
|
|
351
|
+
ref.current.setState(({ scenes }) => ({
|
|
352
|
+
scenes: [
|
|
353
|
+
...scenes,
|
|
354
|
+
{
|
|
355
|
+
...scenes[1],
|
|
356
|
+
key: `${root}/river/stale`,
|
|
357
|
+
screenUniqueId: "stale",
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
}));
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
navigate(grandchild, NAV_ACTION_PUSH);
|
|
364
|
+
finishAnimation();
|
|
365
|
+
|
|
366
|
+
const { scenes, to } = ref.current.state;
|
|
367
|
+
|
|
368
|
+
expect(sceneKeys(ref)).toEqual([root, child, grandchild]);
|
|
369
|
+
expect(scenes[to.index].key).toBe(grandchild);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("shows the replaced screen when going home during a back animation", () => {
|
|
373
|
+
const home = "/river/home";
|
|
374
|
+
const { ref, navigate } = mountTransitioner();
|
|
375
|
+
|
|
376
|
+
navigate(child, NAV_ACTION_PUSH);
|
|
377
|
+
finishAnimation();
|
|
378
|
+
|
|
379
|
+
navigate(root, NAV_ACTION_BACK);
|
|
380
|
+
navigate(home, NAV_ACTION_REPLACE);
|
|
381
|
+
|
|
382
|
+
const { scenes, to } = ref.current.state;
|
|
383
|
+
|
|
384
|
+
expect(scenes[to.index].key).toBe(home);
|
|
385
|
+
|
|
386
|
+
finishAnimation();
|
|
387
|
+
|
|
388
|
+
expect(sceneKeys(ref)).toEqual([home]);
|
|
389
|
+
expect(ref.current.state.animating).toBe(false);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("shows the pushed screen when pushing during a back animation", () => {
|
|
393
|
+
const sibling = `${root}/river/sibling`;
|
|
394
|
+
const { ref, navigate } = mountTransitioner();
|
|
395
|
+
|
|
396
|
+
navigate(child, NAV_ACTION_PUSH);
|
|
397
|
+
finishAnimation();
|
|
398
|
+
|
|
399
|
+
navigate(root, NAV_ACTION_BACK);
|
|
400
|
+
navigate(sibling, NAV_ACTION_PUSH);
|
|
401
|
+
|
|
402
|
+
const { scenes, to } = ref.current.state;
|
|
403
|
+
|
|
404
|
+
expect(scenes[to.index].key).toBe(sibling);
|
|
405
|
+
|
|
406
|
+
finishAnimation();
|
|
407
|
+
|
|
408
|
+
expect(sceneKeys(ref)).not.toContain(child);
|
|
409
|
+
expect(ref.current.state.animating).toBe(false);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it("keeps both scenes when pushing during a push animation", () => {
|
|
413
|
+
const { ref, navigate } = mountTransitioner();
|
|
414
|
+
|
|
415
|
+
navigate(child, NAV_ACTION_PUSH);
|
|
416
|
+
navigate(grandchild, NAV_ACTION_PUSH);
|
|
417
|
+
|
|
418
|
+
expect(sceneKeys(ref)).toEqual([child, grandchild]);
|
|
419
|
+
expect(ref.current.state.to.index).toBe(1);
|
|
420
|
+
});
|
|
421
|
+
});
|
|
53
422
|
});
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
|
|
6
6
|
import { setUserCellPlayerMutedPreference } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/userCellPlayerMutedPreference";
|
|
7
7
|
import { playerFactory } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerFactory";
|
|
8
|
-
import { PlayerRole } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/
|
|
8
|
+
import { PlayerRole } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/const";
|
|
9
9
|
import { loggerLiveImageManager } from "./loggerHelper";
|
|
10
10
|
import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
11
11
|
import {
|
|
@@ -213,10 +213,16 @@ export class LiveImageManager implements PlayerLifecycleListener {
|
|
|
213
213
|
log_debug(
|
|
214
214
|
`pauseItem: live-image not ready, ${item.playerId} - ${item.tag}`
|
|
215
215
|
);
|
|
216
|
-
} else {
|
|
217
|
-
item.player.pause();
|
|
218
216
|
}
|
|
219
217
|
|
|
218
|
+
// Pause even when the player is not ready yet: the underlying player may
|
|
219
|
+
// still be loading and would otherwise start playing on its own once it
|
|
220
|
+
// has buffered, leaving the live-image audible underneath the primary
|
|
221
|
+
// player. Note this is best effort - the web controller's pause() only
|
|
222
|
+
// logs while the player component is unmounted - so the player itself also
|
|
223
|
+
// refuses buffer-driven starts when it was never asked to play.
|
|
224
|
+
item.player.pause();
|
|
225
|
+
|
|
220
226
|
// Fake close event, because we unmount native view
|
|
221
227
|
item.player.onPlayerClose();
|
|
222
228
|
item.setMode?.(LiveImageType.Image);
|