@applicaster/zapp-react-native-ui-components 13.0.0-alpha.7122212380 → 13.0.0-alpha.7196055925
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/MasterCell/dataAdapter.ts +3 -10
- package/Components/MasterCell/utils/index.ts +6 -20
- package/Components/ModalComponent/Header/index.tsx +3 -3
- package/Components/River/ComponentsMap/ComponentsMap.tsx +39 -65
- package/Components/River/ComponentsMap/hooks/useLoadingState.ts +78 -51
- package/Components/River/RiverFooter.tsx +39 -9
- package/Components/River/RiverItem.tsx +37 -2
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +148 -31
- package/Components/River/__tests__/componentsMap.test.js +3 -4
- package/Components/Screen/hooks.ts +56 -0
- package/Components/Screen/index.tsx +13 -39
- package/Components/Transitioner/Scene.tsx +52 -23
- package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +59 -43
- package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +2 -2
- package/Components/Transitioner/index.js +8 -4
- package/Components/VideoLive/PlayerLiveImageComponent.tsx +11 -5
- package/Components/default-cell-renderer/__tests__/defaultCellRenderer.test.tsx +3 -7
- package/Components/default-cell-renderer/viewTrees/tv/DefaultCell/__tests__/DefaultCell.test.tsx +1 -5
- package/package.json +5 -5
|
@@ -35,6 +35,7 @@ function retrieveData(entry, func, args) {
|
|
|
35
35
|
* @param {?[Object]} element.elements Optional array of nested elements to render within the current node
|
|
36
36
|
* @returns {Object} inflated configuration, ready to be rendered by the master cell's element mapper
|
|
37
37
|
*/
|
|
38
|
+
|
|
38
39
|
export function configInflater(
|
|
39
40
|
entry: any,
|
|
40
41
|
{
|
|
@@ -64,7 +65,7 @@ export function configInflater(
|
|
|
64
65
|
|
|
65
66
|
if (Array.isArray(elements)) {
|
|
66
67
|
adjustedElements = elements.map((element) =>
|
|
67
|
-
configInflater(entry, element)
|
|
68
|
+
configInflater(entry, element as any)
|
|
68
69
|
);
|
|
69
70
|
}
|
|
70
71
|
|
|
@@ -76,14 +77,6 @@ export function configInflater(
|
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
/**
|
|
80
|
-
* Memoized version of configInflater to cache results based on entry and masterCellConfig
|
|
81
|
-
*/
|
|
82
|
-
const memoizedConfigInflater = R.memoizeWith(
|
|
83
|
-
(entry, config) => `${entry.id}-${JSON.stringify(config)}`,
|
|
84
|
-
configInflater
|
|
85
|
-
);
|
|
86
|
-
|
|
87
80
|
/**
|
|
88
81
|
* Return the proper view tree according to entry's content type and UI state.
|
|
89
82
|
* Falls back to "default" content type or state.
|
|
@@ -104,7 +97,7 @@ function resolveElementsNode(entry, state, elements) {
|
|
|
104
97
|
export function defaultDataAdapter(elements) {
|
|
105
98
|
return function elementsBuilder({ entry, state = "default" }) {
|
|
106
99
|
return resolveElementsNode(entry, state, elements).map((element) =>
|
|
107
|
-
|
|
100
|
+
configInflater(entry, element)
|
|
108
101
|
);
|
|
109
102
|
};
|
|
110
103
|
}
|
|
@@ -78,17 +78,13 @@ export function resolveColor(entry, style) {
|
|
|
78
78
|
return style;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
if (key.includes("color") || key.includes("Color")) {
|
|
85
|
-
return { ...acc, ...resolveColorForProp(entry, acc, key) };
|
|
86
|
-
}
|
|
81
|
+
// TODO can be optimized to remove 3 O(n) loops
|
|
82
|
+
const styleKeys = Object.keys(style);
|
|
83
|
+
const colorKeys = styleKeys.filter((key) => /color/i.test(key));
|
|
87
84
|
|
|
88
|
-
|
|
85
|
+
return colorKeys.reduce((acc, value) => {
|
|
86
|
+
return { ...style, ...resolveColorForProp(entry, acc, value) };
|
|
89
87
|
}, style);
|
|
90
|
-
|
|
91
|
-
return resolvedStyle;
|
|
92
88
|
}
|
|
93
89
|
|
|
94
90
|
export function isVideoPreviewEnabled({
|
|
@@ -343,23 +339,13 @@ export const useCellState = ({
|
|
|
343
339
|
return getCellState({ focused, selected: _isSelected });
|
|
344
340
|
};
|
|
345
341
|
|
|
346
|
-
const cache = new Map<string, any>();
|
|
347
|
-
|
|
348
342
|
export const hasFocusableInsideBuilder = (elementsBuilder) => (item) => {
|
|
349
343
|
const elements = elementsBuilder({ entry: item });
|
|
350
344
|
|
|
351
|
-
|
|
352
|
-
return cache.get(item.id);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const result = R.anyPass([
|
|
345
|
+
return R.anyPass([
|
|
356
346
|
hasElementsSpecificViewType("ButtonContainerView"),
|
|
357
347
|
hasElementsSpecificViewType("FocusableView"),
|
|
358
348
|
])(elements);
|
|
359
|
-
|
|
360
|
-
cache.set(item.id, result);
|
|
361
|
-
|
|
362
|
-
return result;
|
|
363
349
|
};
|
|
364
350
|
|
|
365
351
|
export function getEntryState(state, selected) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useMemo } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { LayoutChangeEvent, StyleSheet, View } from "react-native";
|
|
3
3
|
import { Title, TitleProps } from "./Title";
|
|
4
4
|
import { CloseButton, CloseButtonProps } from "./CloseButton";
|
|
5
5
|
import { getCloseButtonProps, getTitleProps } from "./utils";
|
|
@@ -60,8 +60,8 @@ export function ModalHeader(props: Props) {
|
|
|
60
60
|
width: width - buttonsContainerWidth,
|
|
61
61
|
}}
|
|
62
62
|
>
|
|
63
|
-
{title
|
|
64
|
-
{summary
|
|
63
|
+
{title ? <Title {...titleProps} /> : null}
|
|
64
|
+
{summary ? <Title {...summaryProps} /> : null}
|
|
65
65
|
</View>
|
|
66
66
|
<View style={{ width: buttonsContainerWidth }}>
|
|
67
67
|
<CloseButton {...closeButtonProps} />
|
|
@@ -70,21 +70,6 @@ function ComponentsMapComponent(props: Props) {
|
|
|
70
70
|
|
|
71
71
|
const [flatListHeight, setFlatListHeight] = React.useState(null);
|
|
72
72
|
|
|
73
|
-
const {
|
|
74
|
-
isAnyLoading,
|
|
75
|
-
isAllLoaded,
|
|
76
|
-
waitForAllComponents,
|
|
77
|
-
onLoadFinished,
|
|
78
|
-
onLoadFailed,
|
|
79
|
-
shouldShowLoadingError,
|
|
80
|
-
isAnyLoaded,
|
|
81
|
-
arePreviousComponentsLoaded,
|
|
82
|
-
} = useLoadingState(feed?.entry?.length || components.length);
|
|
83
|
-
|
|
84
|
-
const theme = useTheme();
|
|
85
|
-
|
|
86
|
-
const logTimestamp = useProfilerLogging();
|
|
87
|
-
|
|
88
73
|
const riverComponents = React.useMemo(() => {
|
|
89
74
|
if (feed?.entry?.length < components.length) {
|
|
90
75
|
return R.slice(0, feed?.entry?.length, components);
|
|
@@ -93,55 +78,42 @@ function ComponentsMapComponent(props: Props) {
|
|
|
93
78
|
return components;
|
|
94
79
|
}, [components, feed]);
|
|
95
80
|
|
|
81
|
+
const logTimestamp = useProfilerLogging();
|
|
82
|
+
|
|
83
|
+
const onLoadDone = React.useCallback(() => {
|
|
84
|
+
logTimestamp(riverId?.toString());
|
|
85
|
+
}, [logTimestamp]);
|
|
86
|
+
|
|
87
|
+
const { loadingState, onLoadFinished, onLoadFailed, shouldShowLoadingError } =
|
|
88
|
+
useLoadingState(riverComponents.length, onLoadDone);
|
|
89
|
+
|
|
90
|
+
const theme = useTheme();
|
|
91
|
+
|
|
96
92
|
const renderRiverItem = React.useCallback(
|
|
97
93
|
({ item, index }) => {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
const riverItemProps = {
|
|
95
|
+
riverId,
|
|
96
|
+
item,
|
|
97
|
+
index,
|
|
98
|
+
isScreenWrappedInContainer,
|
|
99
|
+
feed,
|
|
100
|
+
groupId,
|
|
101
|
+
onLoadFailed,
|
|
102
|
+
onLoadFinished,
|
|
103
|
+
getStaticComponentFeed,
|
|
104
|
+
isLast: isLast(index, riverComponents.length),
|
|
105
|
+
loadingState,
|
|
102
106
|
};
|
|
103
107
|
|
|
104
108
|
return (
|
|
105
|
-
<
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
{...{
|
|
109
|
-
readyToBeDisplayed,
|
|
110
|
-
riverId,
|
|
111
|
-
item,
|
|
112
|
-
index,
|
|
113
|
-
isScreenWrappedInContainer,
|
|
114
|
-
feed,
|
|
115
|
-
groupId,
|
|
116
|
-
onLoadFailed,
|
|
117
|
-
onLoadFinished,
|
|
118
|
-
getStaticComponentFeed,
|
|
119
|
-
isLast: isLast(index, riverComponents.length),
|
|
120
|
-
}}
|
|
121
|
-
/>
|
|
122
|
-
</ScreenLoadingMeasurementsListItemWrapper>
|
|
123
|
-
</View>
|
|
109
|
+
<ScreenLoadingMeasurementsListItemWrapper index={index}>
|
|
110
|
+
<RiverItem {...riverItemProps} />
|
|
111
|
+
</ScreenLoadingMeasurementsListItemWrapper>
|
|
124
112
|
);
|
|
125
113
|
},
|
|
126
|
-
[
|
|
127
|
-
feed,
|
|
128
|
-
getStaticComponentFeed,
|
|
129
|
-
arePreviousComponentsLoaded,
|
|
130
|
-
onLoadFailed,
|
|
131
|
-
onLoadFinished,
|
|
132
|
-
]
|
|
114
|
+
[feed, getStaticComponentFeed, onLoadFailed, onLoadFinished]
|
|
133
115
|
);
|
|
134
116
|
|
|
135
|
-
const renderFooter = React.useCallback(() => {
|
|
136
|
-
return (
|
|
137
|
-
<RiverFooter
|
|
138
|
-
visible={isAnyLoading}
|
|
139
|
-
flatListHeight={flatListHeight}
|
|
140
|
-
isAnyLoaded={isAnyLoaded || waitForAllComponents}
|
|
141
|
-
/>
|
|
142
|
-
);
|
|
143
|
-
}, [flatListHeight, isAnyLoading, isAnyLoaded]);
|
|
144
|
-
|
|
145
117
|
const screenStyle = React.useMemo(
|
|
146
118
|
() => ({
|
|
147
119
|
paddingTop: ifEmptyUseFallback(
|
|
@@ -179,12 +151,6 @@ function ComponentsMapComponent(props: Props) {
|
|
|
179
151
|
|
|
180
152
|
usePipesCacheReset(riverId, riverComponents);
|
|
181
153
|
|
|
182
|
-
React.useEffect(() => {
|
|
183
|
-
if (isAllLoaded) {
|
|
184
|
-
logTimestamp(riverId?.toString());
|
|
185
|
-
}
|
|
186
|
-
}, [isAllLoaded]);
|
|
187
|
-
|
|
188
154
|
const refreshControl = React.useMemo(
|
|
189
155
|
() =>
|
|
190
156
|
pullToRefreshEnabled ? (
|
|
@@ -280,6 +246,11 @@ function ComponentsMapComponent(props: Props) {
|
|
|
280
246
|
}
|
|
281
247
|
}, []);
|
|
282
248
|
|
|
249
|
+
const contentContainerStyle = React.useMemo(
|
|
250
|
+
() => (isScreenWrappedInContainer ? {} : screenStyle),
|
|
251
|
+
[isScreenWrappedInContainer, screenStyle]
|
|
252
|
+
);
|
|
253
|
+
|
|
283
254
|
if (shouldShowLoadingError) {
|
|
284
255
|
return <RiverError />;
|
|
285
256
|
}
|
|
@@ -291,7 +262,7 @@ function ComponentsMapComponent(props: Props) {
|
|
|
291
262
|
<View style={styles.container}>
|
|
292
263
|
<ScreenLoadingMeasurements
|
|
293
264
|
riverId={riverId}
|
|
294
|
-
numberOfComponents={
|
|
265
|
+
numberOfComponents={riverComponents.length}
|
|
295
266
|
>
|
|
296
267
|
<ViewportTracker>
|
|
297
268
|
<FlatList
|
|
@@ -312,10 +283,13 @@ function ComponentsMapComponent(props: Props) {
|
|
|
312
283
|
keyExtractor={keyExtractor}
|
|
313
284
|
renderItem={renderRiverItem}
|
|
314
285
|
data={riverComponents}
|
|
315
|
-
contentContainerStyle={
|
|
316
|
-
|
|
286
|
+
contentContainerStyle={contentContainerStyle}
|
|
287
|
+
ListFooterComponent={
|
|
288
|
+
<RiverFooter
|
|
289
|
+
flatListHeight={flatListHeight}
|
|
290
|
+
loadingState={loadingState}
|
|
291
|
+
/>
|
|
317
292
|
}
|
|
318
|
-
ListFooterComponent={renderFooter}
|
|
319
293
|
refreshControl={refreshControl}
|
|
320
294
|
onScrollBeginDrag={onScrollBeginDrag}
|
|
321
295
|
onScroll={onScroll}
|
|
@@ -1,50 +1,86 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const anyFalse = R.any(R.equals(false));
|
|
6
|
-
const anyTrue = R.any(R.equals(true));
|
|
2
|
+
import { isNil, set, lensIndex, T, slice } from "ramda";
|
|
3
|
+
import { BehaviorSubject } from "rxjs";
|
|
4
|
+
import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";
|
|
7
5
|
|
|
8
6
|
const reducer = (state, { payload }) => {
|
|
9
|
-
if (!
|
|
10
|
-
return
|
|
7
|
+
if (!isNil(payload) && !state[payload]) {
|
|
8
|
+
return set(lensIndex(payload), true)(state);
|
|
11
9
|
}
|
|
12
10
|
|
|
13
11
|
return state;
|
|
14
12
|
};
|
|
15
13
|
|
|
16
|
-
type
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
type LoadingState = {
|
|
15
|
+
index: number;
|
|
16
|
+
done: boolean;
|
|
19
17
|
waitForAllComponents: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type Return = {
|
|
21
|
+
loadingState: BehaviorSubject<LoadingState>;
|
|
20
22
|
onLoadFinished: (index: number) => void;
|
|
21
23
|
onLoadFailed: ({ error, index }: { error: Error; index: number }) => void;
|
|
22
24
|
shouldShowLoadingError: boolean;
|
|
23
|
-
isAnyLoaded: boolean;
|
|
24
25
|
arePreviousComponentsLoaded: (index: number) => boolean;
|
|
25
26
|
};
|
|
26
27
|
|
|
27
|
-
type Action = { payload: { index: number } };
|
|
28
|
-
|
|
29
|
-
type Loaded = true;
|
|
30
|
-
type Loading = false;
|
|
31
|
-
|
|
32
|
-
type LoadingState = Array<Loaded | Loading>;
|
|
33
|
-
|
|
34
28
|
// TODO: Take this value from Zapp configuration, when feature is added to GeneralScreen
|
|
35
29
|
const SHOULD_FAIL_ON_COMPONENT_LOADING = false;
|
|
36
30
|
|
|
37
|
-
|
|
31
|
+
const createLoadingStateObservable = () =>
|
|
32
|
+
new BehaviorSubject<LoadingState>({
|
|
33
|
+
index: -1,
|
|
34
|
+
done: false,
|
|
35
|
+
waitForAllComponents: SHOULD_FAIL_ON_COMPONENT_LOADING,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const useLoadingState = (
|
|
39
|
+
count: number,
|
|
40
|
+
onLoadDone: () => void
|
|
41
|
+
): Return => {
|
|
42
|
+
const componentStateRef = React.useRef(new Array(count).fill(false));
|
|
38
43
|
const [loadingError, setLoadingError] = React.useState(null);
|
|
39
44
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
const loadingState = useRefWithInitialValue<BehaviorSubject<LoadingState>>(
|
|
46
|
+
createLoadingStateObservable
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const arePreviousComponentsLoaded = React.useCallback((index) => {
|
|
50
|
+
if (index === 0) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
43
53
|
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
const componentsBefore = slice(0, index, componentStateRef.current);
|
|
55
|
+
|
|
56
|
+
return componentsBefore.every(T);
|
|
46
57
|
}, []);
|
|
47
58
|
|
|
59
|
+
const dispatch = React.useCallback(({ payload }) => {
|
|
60
|
+
const newState = reducer(componentStateRef.current, { payload });
|
|
61
|
+
componentStateRef.current = newState;
|
|
62
|
+
const isDone = arePreviousComponentsLoaded(count - 1);
|
|
63
|
+
|
|
64
|
+
const state = loadingState.current.getValue();
|
|
65
|
+
|
|
66
|
+
const newLoadingState = {
|
|
67
|
+
...state,
|
|
68
|
+
index: state.index < payload ? payload : state.index,
|
|
69
|
+
done: isDone,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
loadingState.current.next(newLoadingState);
|
|
73
|
+
|
|
74
|
+
if (isDone) {
|
|
75
|
+
onLoadDone();
|
|
76
|
+
}
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
const handleComponentLoaded = React.useCallback(
|
|
80
|
+
(index) => dispatch({ payload: index }),
|
|
81
|
+
[]
|
|
82
|
+
);
|
|
83
|
+
|
|
48
84
|
const handleComponentLoadErrorWhenNeedToFail = React.useCallback(
|
|
49
85
|
({ error }) => {
|
|
50
86
|
if (error !== loadingError) {
|
|
@@ -55,35 +91,26 @@ export const useLoadingState = (count: number): Return => {
|
|
|
55
91
|
);
|
|
56
92
|
|
|
57
93
|
const handleComponentLoadErrorWhenNoNeedToFail = React.useCallback(
|
|
58
|
-
({ index }) =>
|
|
59
|
-
handleComponentLoaded(index);
|
|
60
|
-
},
|
|
94
|
+
({ index }) => handleComponentLoaded(index),
|
|
61
95
|
[]
|
|
62
96
|
);
|
|
63
97
|
|
|
64
|
-
|
|
65
|
-
(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
},
|
|
74
|
-
[
|
|
98
|
+
return React.useMemo(
|
|
99
|
+
() => ({
|
|
100
|
+
loadingState: loadingState.current,
|
|
101
|
+
onLoadFinished: handleComponentLoaded,
|
|
102
|
+
onLoadFailed: SHOULD_FAIL_ON_COMPONENT_LOADING
|
|
103
|
+
? handleComponentLoadErrorWhenNeedToFail
|
|
104
|
+
: handleComponentLoadErrorWhenNoNeedToFail,
|
|
105
|
+
shouldShowLoadingError: SHOULD_FAIL_ON_COMPONENT_LOADING && loadingError,
|
|
106
|
+
arePreviousComponentsLoaded,
|
|
107
|
+
}),
|
|
108
|
+
[
|
|
109
|
+
loadingError,
|
|
110
|
+
handleComponentLoaded,
|
|
111
|
+
handleComponentLoadErrorWhenNeedToFail,
|
|
112
|
+
handleComponentLoadErrorWhenNoNeedToFail,
|
|
113
|
+
arePreviousComponentsLoaded,
|
|
114
|
+
]
|
|
75
115
|
);
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
isAnyLoading: anyFalse(componentsState),
|
|
79
|
-
isAllLoaded: allTrue(componentsState),
|
|
80
|
-
onLoadFinished: handleComponentLoaded,
|
|
81
|
-
onLoadFailed: SHOULD_FAIL_ON_COMPONENT_LOADING
|
|
82
|
-
? handleComponentLoadErrorWhenNeedToFail
|
|
83
|
-
: handleComponentLoadErrorWhenNoNeedToFail,
|
|
84
|
-
shouldShowLoadingError: SHOULD_FAIL_ON_COMPONENT_LOADING && loadingError,
|
|
85
|
-
isAnyLoaded: anyTrue(componentsState),
|
|
86
|
-
waitForAllComponents: SHOULD_FAIL_ON_COMPONENT_LOADING,
|
|
87
|
-
arePreviousComponentsLoaded,
|
|
88
|
-
};
|
|
89
116
|
};
|
|
@@ -1,11 +1,23 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useCallback } from "react";
|
|
2
2
|
import { StyleSheet, View } from "react-native";
|
|
3
3
|
import { Spinner } from "@applicaster/zapp-react-native-ui-components/Components/Spinner";
|
|
4
|
+
import type { Subject } from "rxjs";
|
|
5
|
+
import { useStateFromSubscribe } from "@applicaster/zapp-react-native-utils/reactHooks/state/useStateFromSubscribe";
|
|
6
|
+
|
|
7
|
+
type LoadingState = {
|
|
8
|
+
waitForAllComponents: boolean;
|
|
9
|
+
index: number;
|
|
10
|
+
done: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type State = {
|
|
14
|
+
visible: boolean;
|
|
15
|
+
isAnyLoaded: boolean;
|
|
16
|
+
};
|
|
4
17
|
|
|
5
18
|
type Props = {
|
|
6
|
-
visible?: boolean;
|
|
7
19
|
flatListHeight?: number;
|
|
8
|
-
|
|
20
|
+
loadingState: Subject<LoadingState>;
|
|
9
21
|
};
|
|
10
22
|
|
|
11
23
|
const FOOTER_COMPONENT_HEIGHT = 200;
|
|
@@ -19,17 +31,35 @@ const footerStyles = StyleSheet.create({
|
|
|
19
31
|
});
|
|
20
32
|
|
|
21
33
|
function RiverFooterComponent(props: Props) {
|
|
22
|
-
const {
|
|
34
|
+
const { flatListHeight, loadingState } = props;
|
|
35
|
+
|
|
36
|
+
const { visible, isAnyLoaded } = useStateFromSubscribe<LoadingState, State>(
|
|
37
|
+
loadingState,
|
|
38
|
+
useCallback(
|
|
39
|
+
({ index, done, waitForAllComponents }, setState) =>
|
|
40
|
+
setState(() => ({
|
|
41
|
+
visible: !done,
|
|
42
|
+
isAnyLoaded: index >= 0 || waitForAllComponents,
|
|
43
|
+
})),
|
|
44
|
+
[]
|
|
45
|
+
),
|
|
46
|
+
{
|
|
47
|
+
visible: true,
|
|
48
|
+
isAnyLoaded: false,
|
|
49
|
+
}
|
|
50
|
+
);
|
|
23
51
|
|
|
24
52
|
if (!visible) return null;
|
|
25
53
|
|
|
26
54
|
return (
|
|
27
55
|
<View
|
|
28
|
-
renderToHardwareTextureAndroid
|
|
29
|
-
style={
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
56
|
+
renderToHardwareTextureAndroid
|
|
57
|
+
style={[
|
|
58
|
+
footerStyles.container,
|
|
59
|
+
{
|
|
60
|
+
height: isAnyLoaded ? FOOTER_COMPONENT_HEIGHT : flatListHeight,
|
|
61
|
+
},
|
|
62
|
+
]}
|
|
33
63
|
>
|
|
34
64
|
<Spinner size={isAnyLoaded ? "small" : "large"} />
|
|
35
65
|
</View>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
2
|
import * as R from "ramda";
|
|
3
3
|
|
|
4
4
|
import { applyDecorators } from "../../Decorators";
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { riverLogger } from "./logger";
|
|
12
12
|
import { tvPluginsWithCellRenderer } from "../../const";
|
|
13
13
|
import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
14
|
+
import type { BehaviorSubject } from "rxjs";
|
|
14
15
|
|
|
15
16
|
export type RiverItemType = {
|
|
16
17
|
item: ZappUIComponent;
|
|
@@ -24,6 +25,7 @@ export type RiverItemType = {
|
|
|
24
25
|
getStaticComponentFeed: GeneralContentScreenProps["getStaticComponentFeed"];
|
|
25
26
|
readyToBeDisplayed?: boolean;
|
|
26
27
|
isLast: boolean;
|
|
28
|
+
loadingState: BehaviorSubject<{ index: number }>;
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
function getFeedUrl(feed: ZappFeed, index: number) {
|
|
@@ -36,6 +38,33 @@ function getFeedUrl(feed: ZappFeed, index: number) {
|
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
/**
|
|
42
|
+
* useLoadingState for RiverItemComponent
|
|
43
|
+
* takes currentIndex and loadingState as arguments
|
|
44
|
+
**/
|
|
45
|
+
const useLoadingState = (
|
|
46
|
+
currentIndex: number,
|
|
47
|
+
loadingState: RiverItemType["loadingState"]
|
|
48
|
+
) => {
|
|
49
|
+
const [readyToBeDisplayed, setReadyToBeDisplayed] = React.useState(
|
|
50
|
+
loadingState.getValue().index + 1 === currentIndex
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const subscription = loadingState.subscribe(({ index }) => {
|
|
55
|
+
if (index + 1 === currentIndex) {
|
|
56
|
+
setReadyToBeDisplayed(true);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
subscription.unsubscribe();
|
|
62
|
+
};
|
|
63
|
+
}, [loadingState, currentIndex]);
|
|
64
|
+
|
|
65
|
+
return readyToBeDisplayed;
|
|
66
|
+
};
|
|
67
|
+
|
|
39
68
|
function RiverItemComponent(props: RiverItemType) {
|
|
40
69
|
const {
|
|
41
70
|
item,
|
|
@@ -47,10 +76,12 @@ function RiverItemComponent(props: RiverItemType) {
|
|
|
47
76
|
onLoadFinished,
|
|
48
77
|
onLoadFailed,
|
|
49
78
|
getStaticComponentFeed,
|
|
50
|
-
readyToBeDisplayed,
|
|
51
79
|
isLast,
|
|
80
|
+
loadingState,
|
|
52
81
|
} = props;
|
|
53
82
|
|
|
83
|
+
const readyToBeDisplayed = useLoadingState(index, loadingState);
|
|
84
|
+
|
|
54
85
|
const feedUrl = getFeedUrl(feed, index);
|
|
55
86
|
|
|
56
87
|
const Component = useComponentResolver(
|
|
@@ -90,6 +121,10 @@ function RiverItemComponent(props: RiverItemType) {
|
|
|
90
121
|
}
|
|
91
122
|
}, []);
|
|
92
123
|
|
|
124
|
+
if (!readyToBeDisplayed) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
93
128
|
if (Component === null || typeof Component === "undefined") {
|
|
94
129
|
riverLogger.warning({
|
|
95
130
|
message: `Component ${item.component_type} is null - skipping rendering`,
|