@applicaster/zapp-react-native-ui-components 12.0.0-alpha.9004455836 → 12.0.0-alpha.9233494544

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.
Files changed (101) hide show
  1. package/Components/AudioPlayer/AudioPlayer.tsx +9 -1
  2. package/Components/AudioPlayer/__tests__/__snapshots__/audioPlayer.test.js.snap +5 -0
  3. package/Components/AudioPlayer/helpers.tsx +1 -0
  4. package/Components/BaseFocusable/index.tsx +1 -1
  5. package/Components/Cell/Cell.tsx +7 -3
  6. package/Components/Cell/CellWithFocusable.tsx +38 -19
  7. package/Components/Cell/TvOSCellComponent.tsx +2 -2
  8. package/Components/ErrorScreen/index.tsx +1 -1
  9. package/Components/FocusableCell/index.tsx +7 -7
  10. package/Components/FocusableGroup/index.tsx +1 -0
  11. package/Components/FocusableList/FocusableChild.ts +24 -14
  12. package/Components/FocusableList/FocusableItem.tsx +1 -1
  13. package/Components/FocusableList/FocusableListItemWrapper.tsx +76 -0
  14. package/Components/FocusableList/hooks/useCellState.android.ts +3 -1
  15. package/Components/FocusableList/index.tsx +26 -25
  16. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +6 -3
  17. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +13 -6
  18. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +1 -1
  19. package/Components/HandlePlayable/HandlePlayable.tsx +3 -0
  20. package/Components/HookRenderer/index.tsx +1 -6
  21. package/Components/Image/Image.android.tsx +3 -25
  22. package/Components/Image/Image.tsx +18 -2
  23. package/Components/Image/utils/index.ts +39 -0
  24. package/Components/Layout/TV/ScreenLayoutContextProvider.tsx +2 -2
  25. package/Components/Layout/TV/__tests__/index.test.tsx +1 -34
  26. package/Components/Layout/TV/index.tsx +141 -58
  27. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +9 -26
  28. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +5 -3
  29. package/Components/MasterCell/DefaultComponents/Image/hooks/useImageSize.ts +65 -0
  30. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +17 -2
  31. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +3 -2
  32. package/Components/MasterCell/DefaultComponents/Toggle.tsx +5 -1
  33. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +1 -1
  34. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +8 -14
  35. package/Components/MasterCell/utils/index.ts +16 -5
  36. package/Components/ModalComponent/BottomSheetModalContent.tsx +121 -0
  37. package/Components/ModalComponent/Button/Item.tsx +82 -0
  38. package/Components/ModalComponent/Button/ItemIcon.tsx +60 -0
  39. package/Components/ModalComponent/Button/ItemLabel.tsx +101 -0
  40. package/Components/ModalComponent/Button/assets.ts +4 -0
  41. package/Components/ModalComponent/Button/index.tsx +111 -0
  42. package/Components/ModalComponent/Header/CloseButton.tsx +59 -0
  43. package/Components/ModalComponent/Header/Title.tsx +70 -0
  44. package/Components/ModalComponent/Header/assets.ts +4 -0
  45. package/Components/ModalComponent/Header/index.tsx +71 -0
  46. package/Components/ModalComponent/Header/utils.ts +79 -0
  47. package/Components/ModalComponent/index.tsx +38 -0
  48. package/Components/ModalComponent/utils.ts +126 -0
  49. package/Components/PlayerContainer/PlayerContainer.tsx +63 -31
  50. package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -1
  51. package/Components/PlayerImageBackground/index.tsx +34 -8
  52. package/Components/River/ComponentsMap/ComponentsMap.tsx +90 -2
  53. package/Components/River/TV/River.tsx +2 -3
  54. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +1 -1
  55. package/Components/River/__tests__/componentsMap.test.js +7 -0
  56. package/Components/River/useScreenConfiguration.ts +2 -2
  57. package/Components/RouteManager/__tests__/__snapshots__/routeManager.test.js.snap +1 -0
  58. package/Components/RouteManager/__tests__/routeManager.test.js +9 -2
  59. package/Components/RouteManager/index.tsx +5 -3
  60. package/Components/Screen/TV/index.web.tsx +169 -0
  61. package/Components/Screen/__tests__/Screen.test.tsx +88 -74
  62. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +24 -6
  63. package/Components/Screen/__tests__/navigationHandler.test.ts +1 -178
  64. package/Components/Screen/index.tsx +103 -43
  65. package/Components/Screen/navigationHandler.ts +0 -82
  66. package/Components/ScreenResolver/index.tsx +4 -2
  67. package/Components/Tabs/TV/styles.ts +1 -1
  68. package/Components/VideoLive/LiveImageManager.ts +15 -5
  69. package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -28
  70. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +45 -3
  71. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +18 -28
  72. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +78 -0
  73. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +342 -0
  74. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +151 -0
  75. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +438 -0
  76. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +143 -0
  77. package/Components/VideoModal/ModalAnimation/index.ts +15 -0
  78. package/Components/VideoModal/ModalAnimation/utils.ts +299 -0
  79. package/Components/VideoModal/PlayerDetails.tsx +3 -0
  80. package/Components/VideoModal/PlayerWrapper.tsx +64 -41
  81. package/Components/VideoModal/VideoModal.tsx +8 -25
  82. package/Components/VideoModal/__tests__/PlayerDetails.test.tsx +0 -2
  83. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +5 -1
  84. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +246 -166
  85. package/Components/VideoModal/utils.ts +3 -3
  86. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +26 -21
  87. package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +1 -1
  88. package/Components/Viewport/ViewportTracker/__tests__/viewportTracker.test.js +4 -5
  89. package/Components/Viewport/ViewportTracker/index.tsx +1 -0
  90. package/Components/Viewport/utils.js +1 -1
  91. package/Components/ZappUIComponent/index.tsx +22 -12
  92. package/Components/index.js +2 -0
  93. package/Contexts/MenuToggleContext/{index.js → index.ts} +16 -0
  94. package/Contexts/ScreenContext/__tests__/__snapshots__/index.test.tsx.snap +3 -0
  95. package/Contexts/ScreenContext/__tests__/index.test.tsx +27 -0
  96. package/Contexts/ScreenContext/index.tsx +95 -29
  97. package/Contexts/ZappPipesContext/ZappPipesEntryContext.ts +1 -1
  98. package/Decorators/ComponentsMapWrapper/withComponentsMapProvider.tsx +7 -2
  99. package/package.json +5 -5
  100. package/Components/VideoModal/hooks/usePosition.ts +0 -50
  101. /package/Contexts/{index.js → index.ts} +0 -0
@@ -3,32 +3,10 @@ import FastImage, {
3
3
  FastImageProps,
4
4
  } from "@applicaster/react-native-fast-image";
5
5
 
6
- export const QBImage = (props: FastImageProps) => {
7
- const source = React.useMemo(() => {
8
- if (typeof props.source === "number") {
9
- return props.source;
10
- }
6
+ import { useImageSource } from "./utils";
11
7
 
12
- return {
13
- uri: props.source.uri,
14
- headers: props.source.headers,
15
- priority: props.source.priority,
16
- cache: props.source.cache,
17
- maxWidth: props.source.maxWidth,
18
- maxHeight: props.source.maxHeight,
19
- };
20
- }, [
21
- ...(typeof props.source === "object"
22
- ? [
23
- props.source?.uri,
24
- props.source?.priority,
25
- props.source?.cache,
26
- props.source?.headers,
27
- props.source?.maxWidth,
28
- props.source?.maxHeight,
29
- ]
30
- : []),
31
- ]);
8
+ export const QBImage = (props: FastImageProps) => {
9
+ const source = useImageSource(props.source);
32
10
 
33
11
  return <FastImage {...props} source={source} />;
34
12
  };
@@ -1,8 +1,24 @@
1
- import { Image, ImageProps, ImageStyle, StyleProp } from "react-native";
1
+ import React from "react";
2
+ import {
3
+ Image,
4
+ ImageBackground,
5
+ ImageProps,
6
+ ImageStyle,
7
+ StyleProp,
8
+ } from "react-native";
2
9
 
3
10
  export interface Props extends ImageProps {
4
11
  style?: StyleProp<ImageStyle> | undefined;
5
12
  }
6
13
 
7
14
  // TODO - React.FC<Props> QBImage should process incoming url and apply any transformations if needed
8
- export const QBImage = Image;
15
+ export const QBImage = ({
16
+ children,
17
+ ...props
18
+ }: Props & { children?: React.ReactNode }) => {
19
+ if (children) {
20
+ return <ImageBackground {...props}>{children}</ImageBackground>;
21
+ }
22
+
23
+ return <Image {...props} />;
24
+ };
@@ -0,0 +1,39 @@
1
+ import FastImage, { Source } from "@applicaster/react-native-fast-image";
2
+ import { useMemo } from "react";
3
+ import { ImageRequireSource } from "react-native";
4
+
5
+ function getSourceCache(cache: Source["cache"]) {
6
+ return cache || FastImage.cacheControl.web;
7
+ }
8
+
9
+ export const useImageSource = (source?: Source | ImageRequireSource) => {
10
+ return useMemo(() => {
11
+ if (source) {
12
+ if (typeof source === "number") {
13
+ return source;
14
+ }
15
+
16
+ return {
17
+ uri: source.uri,
18
+ headers: source.headers,
19
+ priority: source.priority,
20
+ cache: getSourceCache(source?.cache),
21
+ maxWidth: source.maxWidth,
22
+ maxHeight: source.maxHeight,
23
+ };
24
+ }
25
+
26
+ return undefined;
27
+ }, [
28
+ ...(typeof source === "object"
29
+ ? [
30
+ source?.uri,
31
+ source?.priority,
32
+ source?.cache,
33
+ source?.headers,
34
+ source?.maxWidth,
35
+ source?.maxHeight,
36
+ ]
37
+ : []),
38
+ ]);
39
+ };
@@ -60,7 +60,7 @@ export function ScreenLayoutContextProvider(props: {
60
60
  initialState
61
61
  );
62
62
 
63
- const screeenLayoutValue = React.useMemo(
63
+ const screenLayoutValue = React.useMemo(
64
64
  () => ({
65
65
  ...screenLayout,
66
66
  setScreenLayout,
@@ -69,7 +69,7 @@ export function ScreenLayoutContextProvider(props: {
69
69
  );
70
70
 
71
71
  return (
72
- <ScreenLayoutContext.Provider value={screeenLayoutValue}>
72
+ <ScreenLayoutContext.Provider value={screenLayoutValue}>
73
73
  {props.children}
74
74
  </ScreenLayoutContext.Provider>
75
75
  );
@@ -12,19 +12,7 @@ jest.mock("@applicaster/quick-brick-core/App/ActionSetters", () => ({
12
12
  useRiverInitialState: () => ({}),
13
13
  }));
14
14
 
15
- jest.mock("@applicaster/zapp-react-native-utils/reactHooks/screen", () => ({
16
- useCurrentScreenData: jest.fn(() => ({
17
- navigations: [
18
- {
19
- category: "menu",
20
- navigation_type: "menu",
21
- styles: {
22
- hide_secondary_level_menu: false,
23
- },
24
- },
25
- ],
26
- })),
27
- }));
15
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/screen");
28
16
 
29
17
  const Layout = require("..").default;
30
18
 
@@ -125,25 +113,4 @@ describe("Layout TV", () => {
125
113
  expect(bgComponent.props.testProp).toBe(true);
126
114
  });
127
115
  });
128
-
129
- describe("props passed to navbar component", () => {
130
- it("passes correct props", () => {
131
- const { getByTestId } = render(
132
- <Layout
133
- Components={Components}
134
- ComponentsExtraProps={{
135
- NavBar: { testProp: true },
136
- }}
137
- >
138
- <Screen />
139
- </Layout>,
140
- { wrapper }
141
- );
142
-
143
- const navbarComponent = getByTestId("nav-bar-component");
144
- expect(navbarComponent.props.isVisible).toBe(isNavBarVisible);
145
- expect(navbarComponent.props.menuPlugin).toBe(plugins[0]);
146
- expect(navbarComponent.props.testProp).toBe(true);
147
- });
148
- });
149
116
  });
@@ -1,14 +1,15 @@
1
1
  import * as React from "react";
2
2
  import * as R from "ramda";
3
+ import { View, StyleSheet } from "react-native";
3
4
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
4
- import {
5
- useIsNavBarVisible,
6
- useNavigation,
7
- } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
5
+ import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
8
6
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
7
+ import { useNavbarState } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
8
+ import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
9
9
 
10
10
  import {
11
11
  getNavigationProps,
12
+ getPathAttributes,
12
13
  resolveNavigationPlugin,
13
14
  routeIsPlayerScreen,
14
15
  } from "@applicaster/zapp-react-native-utils/navigationUtils";
@@ -20,9 +21,25 @@ import { ScreenContainer } from "./ScreenContainer";
20
21
  import { TransitionOverlay } from "./TransitionOverlay";
21
22
 
22
23
  import { ScreenLayoutContextProvider } from "./ScreenLayoutContextProvider";
23
- import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
24
24
  import { PathnameContext } from "../../../Contexts/PathnameContext";
25
- import { useNavbarState } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
25
+
26
+ import { FocusableGroup } from "../../FocusableGroup";
27
+ import { Screen } from "../../Screen/TV/index.web";
28
+
29
+ export const ScreenDataContext = React.createContext(null);
30
+
31
+ const styles = StyleSheet.create({
32
+ container: {
33
+ position: "absolute",
34
+ width: 1920,
35
+ height: 1080,
36
+ backgroundColor: "black",
37
+ // opacity: 0.5,
38
+
39
+ borderWidth: 4,
40
+ borderColor: "magenta",
41
+ },
42
+ });
26
43
 
27
44
  type Components = {
28
45
  NavBar: React.ComponentType<any>;
@@ -30,20 +47,77 @@ type Components = {
30
47
  };
31
48
 
32
49
  type ComponentsExtraProps = {
33
- Background?: Record<string, any>;
34
50
  NavBar?: Record<string, any>;
51
+ Background?: Record<string, any>;
35
52
  };
36
53
 
37
54
  type Props = {
38
55
  Components: Components;
39
- ComponentsExtraProps?: ComponentsExtraProps;
40
- children: React.ReactNode;
56
+ componentsExtraProps?: ComponentsExtraProps;
57
+ };
58
+
59
+ const StackNavigator = ({ Components }: Props) => {
60
+ // @ts-ignore
61
+ const { mainStack, currentRoute } = useNavigation();
62
+
63
+ // console.log("debug_2", "DEBUG", { mainStack });
64
+
65
+ const renderScreen = ({ route, action, key, state }, index) => {
66
+ const pathAttributes = getPathAttributes({ pathname: route });
67
+
68
+ if (R.isEmpty(pathAttributes)) {
69
+ return null;
70
+ }
71
+
72
+ // console.log("debug_2", "StackNavigator - renderScreen", {
73
+ // index,
74
+ // route,
75
+ // state,
76
+ // key,
77
+ // action,
78
+ // });
79
+
80
+ const id = `${route}`;
81
+
82
+ return (
83
+ <FocusableGroup
84
+ id={id}
85
+ preferredFocus={route === currentRoute}
86
+ excludeFromFocusSearching
87
+ key={id}
88
+ >
89
+ <View style={styles.container}>
90
+ <ScreenDataContext.Provider value={state}>
91
+ <PathnameContext.Provider value={route}>
92
+ <Screen route={route} Components={Components} />
93
+ </PathnameContext.Provider>
94
+ </ScreenDataContext.Provider>
95
+ </View>
96
+ </FocusableGroup>
97
+ );
98
+ };
99
+
100
+ return mainStack.map(renderScreen);
41
101
  };
42
102
 
103
+ const TransitionerTV = ({ componentsExtraProps, Components }: Props) => {
104
+ return (
105
+ <>
106
+ <ScreenLayoutContextProvider>
107
+ <StackNavigator
108
+ componentsExtraProps={componentsExtraProps}
109
+ Components={Components}
110
+ />
111
+ </ScreenLayoutContextProvider>
112
+ </>
113
+ );
114
+ };
115
+
116
+ // @ts-ignore
43
117
  const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
44
118
  const navigator = useNavigation();
45
119
  const theme = useTheme();
46
- const { title } = useNavbarState();
120
+ const { title, visible } = useNavbarState();
47
121
  const { activeRiver, currentRoute } = navigator;
48
122
 
49
123
  const {
@@ -80,66 +154,75 @@ const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
80
154
  []
81
155
  );
82
156
 
83
- const navBarVisible = useIsNavBarVisible();
84
-
85
157
  const [navBarReady, setNavBarReady] = React.useState(false);
86
158
 
159
+ const navBarContainer = (
160
+ <NavBarContainer isVisible={visible} onReady={setNavBarReady}>
161
+ <NavBar
162
+ ref={ComponentsExtraProps?.NavBar?.ref}
163
+ isVisible={visible && !routeIsPlayerScreen(currentRoute)}
164
+ menuPlugin={menuPlugin}
165
+ navigationProps={navigationProps}
166
+ {...R.omit(["ref"])(ComponentsExtraProps?.NavBar)}
167
+ />
168
+ </NavBarContainer>
169
+ );
170
+
171
+ const isTVOS = isApplePlatform();
172
+
87
173
  if (!appReady) {
88
174
  return null;
89
175
  }
90
176
 
91
177
  return (
92
- <LayoutContainer>
93
- <ScreenLayoutContextProvider>
94
- <Components.Background
95
- backgroundColor={backgroundColor}
96
- backgroundImageUrl={backgroundImageUrl}
97
- {...R.omit(["ref"])(ComponentsExtraProps?.Background)}
98
- >
99
- {!isApplePlatform() ? (
100
- <NavBarContainer isVisible={navBarVisible} onReady={setNavBarReady}>
101
- <NavBar
102
- ref={ComponentsExtraProps?.NavBar?.ref}
103
- isVisible={navBarVisible && !routeIsPlayerScreen(currentRoute)}
104
- menuPlugin={menuPlugin}
105
- navigationProps={navigationProps}
106
- {...R.omit(["ref"])(ComponentsExtraProps?.NavBar)}
107
- />
108
- </NavBarContainer>
109
- ) : null}
110
-
111
- {navBarReady ? (
112
- /**
178
+ // @ts-ignore
179
+ <TransitionerTV
180
+ componentsExtraProps={ComponentsExtraProps}
181
+ Components={Components}
182
+ >
183
+ <LayoutContainer>
184
+ <ScreenLayoutContextProvider>
185
+ <Components.Background
186
+ backgroundColor={backgroundColor}
187
+ backgroundImageUrl={backgroundImageUrl}
188
+ {...R.omit(["ref"])(ComponentsExtraProps?.Background)}
189
+ >
190
+ {!isTVOS ? navBarContainer : null}
191
+ {navBarReady ? (
192
+ /**
113
193
  Need to re-mount this component on each navigation event
114
194
  It's a hack to work around the fact that we don't support
115
195
  correct screen rendering in our navigator (shared currentRoute state)
116
196
  **/
117
- <PathnameContext.Provider value={navigator.currentRoute}>
118
- <ScreenContainer>{children}</ScreenContainer>
119
- </PathnameContext.Provider>
120
- ) : null}
121
-
122
- {isApplePlatform() ? (
123
- /**
124
- Render NavBarContainer over ScreenContainer to let tvOS native
125
- focus manager works correctly to focus nav bar items first
126
- **/
127
- <NavBarContainer isVisible={navBarVisible} onReady={setNavBarReady}>
128
- <NavBar
129
- ref={ComponentsExtraProps?.NavBar?.ref}
130
- isVisible={navBarVisible && !routeIsPlayerScreen(currentRoute)}
131
- menuPlugin={menuPlugin}
132
- navigationProps={navigationProps}
133
- {...R.omit(["ref"])(ComponentsExtraProps?.NavBar)}
134
- />
135
- </NavBarContainer>
136
- ) : null}
137
-
138
- <TransitionOverlay />
139
- </Components.Background>
140
- </ScreenLayoutContextProvider>
141
- </LayoutContainer>
197
+ <PathnameContext.Provider value={navigator.currentRoute}>
198
+ <ScreenContainer>{children}</ScreenContainer>
199
+ </PathnameContext.Provider>
200
+ ) : null}
201
+ {/** Render NavBarContainer over ScreenContainer to let tvOS native
202
+ focus manager works correctly to focus nav bar items first **/}
203
+ {isTVOS ? navBarContainer : null}
204
+ <TransitionOverlay />
205
+ </Components.Background>
206
+ </ScreenLayoutContextProvider>
207
+ </LayoutContainer>
208
+ </TransitionerTV>
142
209
  );
143
210
  };
144
211
 
145
212
  export default Layout;
213
+
214
+ /// Transitioner:
215
+ /*
216
+ Component: Transitioner
217
+ Responsibilities:
218
+ - Transitione between screens.
219
+ Tehere is no transition on Tv so no need for transitioner.
220
+
221
+ Component: StackNavigator
222
+ Responsibilities:
223
+ - Render the screens in the stack.
224
+ - Render the NavBar? I guess navbar should be part of the screen
225
+ - Render the screen.
226
+
227
+ Q
228
+ */
@@ -1,11 +1,11 @@
1
1
  import * as React from "react";
2
2
  import { ImageStyle, View } from "react-native";
3
3
  import * as R from "ramda";
4
- import ImageComponent from "@applicaster/zapp-react-native-ui-components/Components/FastImage";
4
+ import { QBImage } from "@applicaster/zapp-react-native-ui-components/Components/Image";
5
5
  import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
6
- import { toFiniteNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
7
6
 
8
7
  import { useImageSourceWithDefault, useCheckFailedUri } from "./hooks";
8
+ import useImageSize from "./hooks/useImageSize";
9
9
 
10
10
  type Source = {
11
11
  uri: string;
@@ -19,32 +19,13 @@ type Props = Record<string, any> & {
19
19
  withDimensions: (source: Source) => Source;
20
20
  };
21
21
 
22
- const MemoizedImage = React.memo(ImageComponent, R.equals);
23
-
24
- const normalize = Math.ceil;
22
+ const MemoizedImage = React.memo(QBImage, R.equals);
25
23
 
26
24
  const isValidExternalSource = (source): boolean =>
27
25
  source?.uri?.startsWith("http") ||
28
26
  source?.uri?.startsWith("data:image") ||
29
27
  source?.uri?.startsWith("file://");
30
28
 
31
- const isValidSize = (size: unknown): boolean =>
32
- toFiniteNumberWithDefault(0, size) > 0;
33
-
34
- const getSourceWithMaxSizes = (applyMaxSize, style, source) => {
35
- const { width, height } = style;
36
-
37
- if (!applyMaxSize || !isValidSize(width) || !isValidSize(height)) {
38
- return source;
39
- }
40
-
41
- return {
42
- ...source,
43
- maxWidth: normalize(width),
44
- maxHeight: normalize(height),
45
- };
46
- };
47
-
48
29
  export default function Image({
49
30
  style,
50
31
  uri,
@@ -82,10 +63,12 @@ export default function Image({
82
63
 
83
64
  const applyMaxSize = toBooleanWithDefaultFalse(otherProps?.applyMaxSize);
84
65
 
85
- const sourceWithMaxSizes = React.useMemo(
86
- () => withDimensions(getSourceWithMaxSizes(applyMaxSize, style, source)),
87
- [applyMaxSize, style?.height, style?.width, source?.uri, withDimensions]
88
- );
66
+ const sourceWithMaxSizes = useImageSize({
67
+ style,
68
+ applyMaxSize,
69
+ source,
70
+ withDimensions,
71
+ });
89
72
 
90
73
  if (isFailedToLoadUri(uri) || !isValidExternalSource(source)) {
91
74
  return <View style={style} />;
@@ -43,14 +43,16 @@ function Image({
43
43
  [source, error]
44
44
  );
45
45
 
46
+ const _source = shouldRenderImg
47
+ ? withDimensions(source)
48
+ : defaultPlaceHolderImage;
49
+
46
50
  return (
47
51
  // @ts-ignore
48
52
  <MemoizedImage
49
53
  style={style as ImageStyle}
50
54
  onError={() => setErrorState(true)}
51
- source={
52
- shouldRenderImg ? withDimensions(source) : defaultPlaceHolderImage
53
- }
55
+ source={_source?.uri ? _source : undefined}
54
56
  {...R.omit(["source"], otherProps)}
55
57
  />
56
58
  );
@@ -0,0 +1,65 @@
1
+ import * as React from "react";
2
+ import { ImageStyle } from "react-native";
3
+ import { toFiniteNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
4
+
5
+ type Source = {
6
+ uri: string;
7
+ };
8
+
9
+ type Props = Record<string, any> & {
10
+ style: ImageStyle;
11
+ uri?: string;
12
+ placeholderImage?: string;
13
+ entry?: ZappEntry | ZappFeed;
14
+ withDimensions: (source: Source) => Source;
15
+ };
16
+
17
+ const normalize = Math.ceil;
18
+
19
+ const isValidSize = (size: unknown): boolean =>
20
+ toFiniteNumberWithDefault(0, size) > 0;
21
+
22
+ type WidthDimensions = { aspectRatio?: number; width: number; height?: number };
23
+ type HeightDimensions = {
24
+ aspectRatio?: number;
25
+ width?: number;
26
+ height: number;
27
+ };
28
+ type FullDimensions = { aspectRatio?: number; width: number; height: number };
29
+ type Dimensions = WidthDimensions | HeightDimensions | FullDimensions;
30
+
31
+ const calculateDimensions = ({ aspectRatio, width, height }: Dimensions) => {
32
+ if (aspectRatio) {
33
+ return {
34
+ width: width || height * aspectRatio,
35
+ height: height || width / aspectRatio,
36
+ };
37
+ }
38
+
39
+ return { width, height };
40
+ };
41
+
42
+ const getSourceWithMaxSizes = (applyMaxSize, style, source) => {
43
+ const { width, height } = calculateDimensions(style);
44
+
45
+ if (!applyMaxSize || !isValidSize(width) || !isValidSize(height)) {
46
+ return source;
47
+ }
48
+
49
+ return {
50
+ ...source,
51
+ maxWidth: normalize(width),
52
+ maxHeight: normalize(height),
53
+ };
54
+ };
55
+
56
+ export default function useImageSize(props: Props) {
57
+ const { style, applyMaxSize, source, withDimensions } = props;
58
+
59
+ const sourceWithMaxSizes = React.useMemo(
60
+ () => withDimensions(getSourceWithMaxSizes(applyMaxSize, style, source)),
61
+ [applyMaxSize, style?.height, style?.width, source?.uri, withDimensions]
62
+ );
63
+
64
+ return sourceWithMaxSizes;
65
+ }
@@ -18,6 +18,7 @@ type LiveImageProps = {
18
18
  imageKey?: string;
19
19
  state: "focused" | "unfocused" | "selected";
20
20
  audio_muted_by_default?: boolean;
21
+ uri: string; // cover image url
21
22
  };
22
23
 
23
24
  const DEFAULT_PLAYER_IDENTIFIER = "QuickBrickPlayerPlugin";
@@ -176,10 +177,23 @@ const getPlayerConfig = (player_screen_id, actionIdentifier) => {
176
177
  const positionAbsolute: ViewStyle = { position: "absolute" };
177
178
 
178
179
  const LiveImageComponent = (props: LiveImageProps) => {
179
- const { item, style, cellUUID, imageKey, actionIdentifier, state } = props;
180
+ const {
181
+ item,
182
+ style: componentStyle,
183
+ cellUUID,
184
+ imageKey,
185
+ actionIdentifier,
186
+ state,
187
+ } = props;
180
188
 
181
189
  const component = useUIComponentContext();
182
190
 
191
+ // Fix for blinking on state change
192
+ const style = React.useMemo(
193
+ () => ({ ...componentStyle, backgroundColor: "transparent" }),
194
+ [componentStyle]
195
+ );
196
+
183
197
  const componentType = React.useMemo(
184
198
  () => getPropComponentType(component),
185
199
  [component?.id]
@@ -209,10 +223,11 @@ const LiveImageComponent = (props: LiveImageProps) => {
209
223
 
210
224
  return (
211
225
  <>
212
- <Image {...props} />
226
+ <Image {...props} applyMaxSize />
213
227
  {isShouldRender && (
214
228
  <View style={[style, positionAbsolute]}>
215
229
  <PlayerLiveImage
230
+ uri={props.uri}
216
231
  playerId={cellUUID}
217
232
  playerPluginId={playerPluginId}
218
233
  screenConfig={screenConfig}
@@ -1,11 +1,12 @@
1
1
  import * as React from "react";
2
2
  import { isNil } from "ramda";
3
- import { Image as RNImage, ImageStyle, View } from "react-native";
3
+ import { ImageStyle, View } from "react-native";
4
4
  import {
5
5
  FIT_POSITION,
6
6
  IMAGE_SIZING_FIT,
7
7
  IMAGE_SIZING_FILL,
8
8
  } from "@applicaster/zapp-react-native-utils/manifestUtils/secondaryImage";
9
+ import { QBImage as Image } from "@applicaster/zapp-react-native-ui-components/Components/Image";
9
10
 
10
11
  import { useGetImageDimensions } from "./hooks";
11
12
  import {
@@ -59,7 +60,7 @@ const SecondaryImageComponent = (props: Props) => {
59
60
 
60
61
  return (
61
62
  <View style={style} onLayout={onAsyncRender}>
62
- <RNImage
63
+ <Image
63
64
  {...props}
64
65
  source={{ uri }}
65
66
  style={{
@@ -23,7 +23,11 @@ export const Toggle = (props: Props) => {
23
23
  iosBackgroundColor,
24
24
  } = style;
25
25
 
26
- const isSelected = React.useMemo(() => (cellState === "selected"), [item?.id, cellState]);
26
+ const isSelected = React.useMemo(
27
+ () => cellState === "selected",
28
+ [item?.id, cellState]
29
+ );
30
+
27
31
  const actionContext = useActions(action?.identifier);
28
32
 
29
33
  const [actionState, setActionState] = useState(
@@ -39,6 +39,6 @@ describe("image with no source", () => {
39
39
  );
40
40
 
41
41
  const testInstance = testRenderer.root;
42
- expect(testInstance.findByType(Image).props.source).toEqual({ uri: "" });
42
+ expect(testInstance.findByType(Image).props.source).toBeUndefined();
43
43
  });
44
44
  });