@applicaster/zapp-react-native-ui-components 14.0.0-rc.34 → 14.0.0-rc.36
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/Focusable/FocusableiOS.tsx +1 -1
- package/Components/ScreenRevealManager/ScreenRevealManager.ts +76 -0
- package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +107 -0
- package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +96 -0
- package/Components/ScreenRevealManager/index.ts +1 -0
- package/Components/ScreenRevealManager/withScreenRevealManager.tsx +79 -0
- package/package.json +5 -5
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { makeListOf } from "@applicaster/zapp-react-native-utils/arrayUtils";
|
|
2
|
+
import { isFirstComponentGallery } from "@applicaster/zapp-react-native-utils/componentsUtils";
|
|
3
|
+
import { once } from "ramda";
|
|
4
|
+
|
|
5
|
+
const INITIAL_NUMBER_TO_LOAD = 3;
|
|
6
|
+
|
|
7
|
+
// Infer the values of COMPONENT_LOADING_STATE as a type
|
|
8
|
+
type ComponentLoadingState =
|
|
9
|
+
(typeof COMPONENT_LOADING_STATE)[keyof typeof COMPONENT_LOADING_STATE];
|
|
10
|
+
|
|
11
|
+
export const COMPONENT_LOADING_STATE = {
|
|
12
|
+
UNKNOWN: "UNKNOWN",
|
|
13
|
+
LOADED_WITH_SUCCESS: "LOADED_WITH_SUCCESS",
|
|
14
|
+
LOADED_WITH_FAILURE: "LOADED_WITH_FAILURE",
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
// Function to get the number of loaded components
|
|
18
|
+
const getNumberOfLoaded = (states: ComponentLoadingState[]): number => {
|
|
19
|
+
return states.filter((value) => value !== COMPONENT_LOADING_STATE.UNKNOWN)
|
|
20
|
+
.length;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const getNumberOfComponentsWaitToLoadBeforePresent = (
|
|
24
|
+
componentsToRender: ZappUIComponent[]
|
|
25
|
+
): number => {
|
|
26
|
+
// when Gallery is the first component, no need to wait the others
|
|
27
|
+
if (isFirstComponentGallery(componentsToRender)) {
|
|
28
|
+
return 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return Math.min(INITIAL_NUMBER_TO_LOAD, componentsToRender.length);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export class ScreenRevealManager {
|
|
35
|
+
public numberOfComponentsWaitToLoadBeforePresent: number;
|
|
36
|
+
private renderingState: Array<ComponentLoadingState>;
|
|
37
|
+
private callback: Callback;
|
|
38
|
+
|
|
39
|
+
constructor(componentsToRender: ZappUIComponent[], callback: Callback) {
|
|
40
|
+
this.numberOfComponentsWaitToLoadBeforePresent =
|
|
41
|
+
getNumberOfComponentsWaitToLoadBeforePresent(componentsToRender);
|
|
42
|
+
|
|
43
|
+
this.renderingState = makeListOf<ComponentLoadingState>(
|
|
44
|
+
COMPONENT_LOADING_STATE.UNKNOWN,
|
|
45
|
+
this.numberOfComponentsWaitToLoadBeforePresent
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
this.callback = once(callback);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
onLoadFinished = (index: number): void => {
|
|
52
|
+
this.renderingState[index] = COMPONENT_LOADING_STATE.LOADED_WITH_SUCCESS;
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
getNumberOfLoaded(this.renderingState) >=
|
|
56
|
+
this.numberOfComponentsWaitToLoadBeforePresent
|
|
57
|
+
) {
|
|
58
|
+
this.setIsReadyToShow();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
onLoadFailed = (index: number): void => {
|
|
63
|
+
this.renderingState[index] = COMPONENT_LOADING_STATE.LOADED_WITH_FAILURE;
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
getNumberOfLoaded(this.renderingState) >=
|
|
67
|
+
this.numberOfComponentsWaitToLoadBeforePresent
|
|
68
|
+
) {
|
|
69
|
+
this.setIsReadyToShow();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
setIsReadyToShow = (): void => {
|
|
74
|
+
this.callback();
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ScreenRevealManager,
|
|
3
|
+
COMPONENT_LOADING_STATE,
|
|
4
|
+
} from "../ScreenRevealManager";
|
|
5
|
+
|
|
6
|
+
describe("ScreenRevealManager", () => {
|
|
7
|
+
const mockCallback = jest.fn();
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
jest.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should initialize with the correct number of components to wait for", () => {
|
|
14
|
+
const componentsToRender: ZappUIComponent[] = [
|
|
15
|
+
{ component_type: "component1" },
|
|
16
|
+
{ component_type: "component2" },
|
|
17
|
+
{ component_type: "component3" },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const manager = new ScreenRevealManager(componentsToRender, mockCallback);
|
|
21
|
+
|
|
22
|
+
expect(manager["numberOfComponentsWaitToLoadBeforePresent"]).toBe(3);
|
|
23
|
+
|
|
24
|
+
expect(manager["renderingState"]).toEqual([
|
|
25
|
+
COMPONENT_LOADING_STATE.UNKNOWN,
|
|
26
|
+
COMPONENT_LOADING_STATE.UNKNOWN,
|
|
27
|
+
COMPONENT_LOADING_STATE.UNKNOWN,
|
|
28
|
+
]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should call the callback when the required number of components are loaded successfully", () => {
|
|
32
|
+
const componentsToRender: ZappUIComponent[] = [
|
|
33
|
+
{ component_type: "component1" },
|
|
34
|
+
{ component_type: "component2" },
|
|
35
|
+
{ component_type: "component3" },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const manager = new ScreenRevealManager(componentsToRender, mockCallback);
|
|
39
|
+
|
|
40
|
+
manager.onLoadFinished(0);
|
|
41
|
+
manager.onLoadFinished(1);
|
|
42
|
+
manager.onLoadFinished(2);
|
|
43
|
+
|
|
44
|
+
expect(mockCallback).toHaveBeenCalledTimes(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should call the callback when the required number of components fail to load", () => {
|
|
48
|
+
const componentsToRender: ZappUIComponent[] = [
|
|
49
|
+
{ component_type: "component1" },
|
|
50
|
+
{ component_type: "component2" },
|
|
51
|
+
{ component_type: "component3" },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const manager = new ScreenRevealManager(componentsToRender, mockCallback);
|
|
55
|
+
|
|
56
|
+
manager.onLoadFailed(0);
|
|
57
|
+
manager.onLoadFailed(1);
|
|
58
|
+
manager.onLoadFailed(2);
|
|
59
|
+
|
|
60
|
+
expect(mockCallback).toHaveBeenCalledTimes(1);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should call the callback when a mix of successful and failed loads meet the required number", () => {
|
|
64
|
+
const componentsToRender: ZappUIComponent[] = [
|
|
65
|
+
{ component_type: "component1" },
|
|
66
|
+
{ component_type: "component2" },
|
|
67
|
+
{ component_type: "component3" },
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const manager = new ScreenRevealManager(componentsToRender, mockCallback);
|
|
71
|
+
|
|
72
|
+
manager.onLoadFinished(0);
|
|
73
|
+
manager.onLoadFailed(1);
|
|
74
|
+
manager.onLoadFinished(2);
|
|
75
|
+
|
|
76
|
+
expect(mockCallback).toHaveBeenCalledTimes(1);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should not call the callback if the required number of components are not loaded", () => {
|
|
80
|
+
const componentsToRender: ZappUIComponent[] = [
|
|
81
|
+
{ component_type: "component1" },
|
|
82
|
+
{ component_type: "component2" },
|
|
83
|
+
{ component_type: "component3" },
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const manager = new ScreenRevealManager(componentsToRender, mockCallback);
|
|
87
|
+
|
|
88
|
+
manager.onLoadFinished(0);
|
|
89
|
+
manager.onLoadFailed(1);
|
|
90
|
+
|
|
91
|
+
expect(mockCallback).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should call the callback when the when first component is gallery and it was loaded successfully", () => {
|
|
95
|
+
const componentsToRender: ZappUIComponent[] = [
|
|
96
|
+
{ component_type: "gallery-qb" },
|
|
97
|
+
{ component_type: "component2" },
|
|
98
|
+
{ component_type: "component3" },
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
const manager = new ScreenRevealManager(componentsToRender, mockCallback);
|
|
102
|
+
|
|
103
|
+
manager.onLoadFinished(0);
|
|
104
|
+
|
|
105
|
+
expect(mockCallback).toHaveBeenCalledTimes(1);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/* eslint-disable react/prop-types */
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { render, screen, act } from "@testing-library/react-native";
|
|
5
|
+
import { View } from "react-native";
|
|
6
|
+
import {
|
|
7
|
+
withScreenRevealManager,
|
|
8
|
+
SHOWN,
|
|
9
|
+
TIMEOUT,
|
|
10
|
+
} from "../withScreenRevealManager";
|
|
11
|
+
|
|
12
|
+
jest.mock("react-native/Libraries/Animated/NativeAnimatedHelper");
|
|
13
|
+
|
|
14
|
+
const MockComponent = ({
|
|
15
|
+
initialNumberToLoad,
|
|
16
|
+
onLoadFinishedFromScreenRevealManager,
|
|
17
|
+
onLoadFailedFromScreenRevealManager,
|
|
18
|
+
}) => {
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
// Simulate loading components
|
|
21
|
+
for (let i = 0; i < initialNumberToLoad; i++) {
|
|
22
|
+
onLoadFinishedFromScreenRevealManager(i);
|
|
23
|
+
}
|
|
24
|
+
}, [initialNumberToLoad, onLoadFinishedFromScreenRevealManager]);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<View
|
|
28
|
+
testID="mock-component"
|
|
29
|
+
initialNumberToLoad={initialNumberToLoad}
|
|
30
|
+
onLoadFinishedFromScreenRevealManager={
|
|
31
|
+
onLoadFinishedFromScreenRevealManager
|
|
32
|
+
}
|
|
33
|
+
onLoadFailedFromScreenRevealManager={onLoadFailedFromScreenRevealManager}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const WrappedComponent = withScreenRevealManager(MockComponent);
|
|
39
|
+
|
|
40
|
+
describe("withScreenRevealManager", () => {
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
jest.clearAllMocks();
|
|
43
|
+
jest.useFakeTimers();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
jest.runOnlyPendingTimers();
|
|
48
|
+
jest.useRealTimers();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should render the wrapped component", () => {
|
|
52
|
+
render(
|
|
53
|
+
<WrappedComponent
|
|
54
|
+
componentsToRender={[{ id: "1" }, { id: "2" }, { id: "3" }]}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
expect(screen.getByTestId("mock-component")).toBeTruthy();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should animate opacity when ready to show", () => {
|
|
62
|
+
render(
|
|
63
|
+
<WrappedComponent
|
|
64
|
+
componentsToRender={[{ id: "1" }, { id: "2" }, { id: "3" }]}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const animatedView = screen.getByTestId("animated-component");
|
|
69
|
+
|
|
70
|
+
act(() => {
|
|
71
|
+
jest.advanceTimersByTime(TIMEOUT + 100);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(animatedView.props.style.opacity).toBe(SHOWN);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should pass initialNumberToLoad, onLoadFinishedFromScreenRevealManager, and onLoadFailedFromScreenRevealManager to the wrapped component", () => {
|
|
78
|
+
render(
|
|
79
|
+
<WrappedComponent
|
|
80
|
+
componentsToRender={[{ id: "1" }, { id: "2" }, { id: "3" }]}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const mockComponent = screen.getByTestId("mock-component");
|
|
85
|
+
|
|
86
|
+
expect(mockComponent.props.initialNumberToLoad).toBe(3);
|
|
87
|
+
|
|
88
|
+
expect(
|
|
89
|
+
mockComponent.props.onLoadFinishedFromScreenRevealManager
|
|
90
|
+
).toBeInstanceOf(Function);
|
|
91
|
+
|
|
92
|
+
expect(
|
|
93
|
+
mockComponent.props.onLoadFailedFromScreenRevealManager
|
|
94
|
+
).toBeInstanceOf(Function);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { withScreenRevealManager } from "./withScreenRevealManager";
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Animated } from "react-native";
|
|
3
|
+
import { isFirstComponentScreenPicker } from "@applicaster/zapp-react-native-utils/componentsUtils";
|
|
4
|
+
import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
5
|
+
import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";
|
|
6
|
+
|
|
7
|
+
import { ScreenRevealManager } from "./ScreenRevealManager";
|
|
8
|
+
|
|
9
|
+
const flex = platformSelect({
|
|
10
|
+
tvos: 1,
|
|
11
|
+
android_tv: 1,
|
|
12
|
+
web: undefined,
|
|
13
|
+
samsung_tv: undefined,
|
|
14
|
+
lg_tv: undefined,
|
|
15
|
+
default: undefined,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const TIMEOUT = 500; // 500 ms
|
|
19
|
+
|
|
20
|
+
const HIDDEN = 0; // opacity = 0
|
|
21
|
+
|
|
22
|
+
export const SHOWN = 1; // opacity = 1
|
|
23
|
+
|
|
24
|
+
type Props = {
|
|
25
|
+
componentsToRender: ZappUIComponent[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const withScreenRevealManager = (Component) => {
|
|
29
|
+
return function WithScreenRevealManager(props: Props) {
|
|
30
|
+
const { componentsToRender } = props;
|
|
31
|
+
|
|
32
|
+
const [isReadyToShow, setIsReadyToShow] = React.useState(false);
|
|
33
|
+
|
|
34
|
+
const handleSetIsReadyToShow = React.useCallback(() => {
|
|
35
|
+
setIsReadyToShow(true);
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
const managerRef = useRefWithInitialValue<ScreenRevealManager>(
|
|
39
|
+
() => new ScreenRevealManager(componentsToRender, handleSetIsReadyToShow)
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const opacityRef = useRefWithInitialValue<Animated.Value>(
|
|
43
|
+
() => new Animated.Value(HIDDEN)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
React.useEffect(() => {
|
|
47
|
+
if (isReadyToShow) {
|
|
48
|
+
Animated.timing(opacityRef.current, {
|
|
49
|
+
toValue: SHOWN,
|
|
50
|
+
duration: TIMEOUT,
|
|
51
|
+
useNativeDriver: true,
|
|
52
|
+
}).start();
|
|
53
|
+
}
|
|
54
|
+
}, [isReadyToShow]);
|
|
55
|
+
|
|
56
|
+
if (isFirstComponentScreenPicker(componentsToRender)) {
|
|
57
|
+
// for screen-picker with have additional internal ComponentsMap, no need to add this wrapper
|
|
58
|
+
return <Component {...props} />;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Animated.View
|
|
63
|
+
style={{ opacity: opacityRef.current, flex }}
|
|
64
|
+
testID="animated-component"
|
|
65
|
+
>
|
|
66
|
+
<Component
|
|
67
|
+
{...props}
|
|
68
|
+
initialNumberToLoad={
|
|
69
|
+
managerRef.current.numberOfComponentsWaitToLoadBeforePresent
|
|
70
|
+
}
|
|
71
|
+
onLoadFinishedFromScreenRevealManager={
|
|
72
|
+
managerRef.current.onLoadFinished
|
|
73
|
+
}
|
|
74
|
+
onLoadFailedFromScreenRevealManager={managerRef.current.onLoadFailed}
|
|
75
|
+
/>
|
|
76
|
+
</Animated.View>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-ui-components",
|
|
3
|
-
"version": "14.0.0-rc.
|
|
3
|
+
"version": "14.0.0-rc.36",
|
|
4
4
|
"description": "Applicaster Zapp React Native ui components for the Quick Brick App",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@applicaster/applicaster-types": "14.0.0-rc.
|
|
32
|
-
"@applicaster/zapp-react-native-bridge": "14.0.0-rc.
|
|
33
|
-
"@applicaster/zapp-react-native-redux": "14.0.0-rc.
|
|
34
|
-
"@applicaster/zapp-react-native-utils": "14.0.0-rc.
|
|
31
|
+
"@applicaster/applicaster-types": "14.0.0-rc.36",
|
|
32
|
+
"@applicaster/zapp-react-native-bridge": "14.0.0-rc.36",
|
|
33
|
+
"@applicaster/zapp-react-native-redux": "14.0.0-rc.36",
|
|
34
|
+
"@applicaster/zapp-react-native-utils": "14.0.0-rc.36",
|
|
35
35
|
"promise": "^8.3.0",
|
|
36
36
|
"url": "^0.11.0",
|
|
37
37
|
"uuid": "^3.3.2"
|