@applicaster/zapp-react-native-ui-components 15.1.0-rc.9 → 15.1.0

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 (29) hide show
  1. package/Components/BackgroundImage/BackgroundImage.tv.android.tsx +28 -0
  2. package/Components/BackgroundImage/BackgroundImage.tv.ios.tsx +24 -0
  3. package/Components/BackgroundImage/BackgroundImage.tv.web.tsx +42 -0
  4. package/Components/BackgroundImage/index.ts +8 -0
  5. package/Components/FocusableGroup/FocusableTvOS.tsx +11 -7
  6. package/Components/Layout/FullWidthRow.tsx +38 -0
  7. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +0 -1
  8. package/Components/Layout/TV/index.tsx +2 -3
  9. package/Components/Layout/TV/index.web.tsx +2 -3
  10. package/Components/MasterCell/dataAdapter.ts +15 -5
  11. package/Components/MasterCell/index.tsx +10 -1
  12. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +169 -132
  13. package/Components/MasterCell/utils/__tests__/resolveColorForProp.test.js +92 -0
  14. package/Components/MasterCell/utils/index.ts +89 -46
  15. package/Components/PlayerContainer/PlayerContainer.tsx +9 -3
  16. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +286 -0
  17. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +0 -15
  18. package/Components/Screen/index.tsx +8 -4
  19. package/Components/ScreenRevealManager/Overlay.tsx +34 -0
  20. package/Components/ScreenRevealManager/__tests__/Overlay.test.tsx +88 -0
  21. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +8 -19
  22. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +9 -4
  23. package/Contexts/CachedDimensionsContext/index.ts +17 -2
  24. package/Helpers/ComponentCellSelectionHelper/index.js +0 -6
  25. package/Helpers/index.js +7 -40
  26. package/package.json +5 -5
  27. package/Components/Layout/TV/LayoutBackground.tsx +0 -28
  28. package/Components/Screen/utils.ts +0 -16
  29. package/Helpers/Analytics/index.js +0 -95
@@ -343,11 +343,15 @@ const PlayerContainerComponent = (props: Props) => {
343
343
  setIsLoadingNextVideo(false);
344
344
 
345
345
  const resumeTime = Number(item?.extensions?.resumeTime);
346
+ const activePlayer = playerManager.getActivePlayer();
346
347
 
347
- // Сhecking that the player itself knows where to start playing, and there is no need to call seekTo after returning from the Сhromecast
348
+ // Сhecking that the player itself knows where to start playing, and there is no need to call seekTo after returning from the Сhromecast.
349
+ // Live streams and a missing/zero resume position are skipped: seeking to 0 on
350
+ // a live stream pulls playback off the live edge into a buffer-starvation seek loop.
348
351
  if (
349
- !playerManager.getActivePlayer()?.hasResumePosition() &&
350
- !isNaN(resumeTime)
352
+ !activePlayer?.hasResumePosition() &&
353
+ resumeTime > 0 &&
354
+ !activePlayer?.isLive()
351
355
  ) {
352
356
  player?.seekTo(resumeTime);
353
357
  }
@@ -465,6 +469,8 @@ const PlayerContainerComponent = (props: Props) => {
465
469
  if (isModal && mode === VideoModalMode.MAXIMIZED) {
466
470
  if (disableMiniPlayer) {
467
471
  navigator.closeVideoModal();
472
+ } else {
473
+ navigator.minimiseVideoModal();
468
474
  }
469
475
  }
470
476
 
@@ -0,0 +1,286 @@
1
+ import * as React from "react";
2
+ import { render } from "@testing-library/react-native";
3
+
4
+ jest.mock(
5
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager",
6
+ () => ({
7
+ playerManager: {
8
+ invokeHandler: jest.fn(),
9
+ getPluginConfiguration: jest.fn(() => ({})),
10
+ isRegistered: jest.fn(() => true),
11
+ on: jest.fn().mockReturnThis(),
12
+ removeHandler: jest.fn().mockReturnThis(),
13
+ getActivePlayer: jest.fn(() => null),
14
+ closeNativePlayer: jest.fn(),
15
+ },
16
+ })
17
+ );
18
+
19
+ jest.mock("@applicaster/zapp-react-native-utils/reactUtils", () => ({
20
+ isTV: jest.fn(() => false),
21
+ isAndroidTVPlatform: jest.fn(() => false),
22
+ isTvOSPlatform: jest.fn(() => false),
23
+ platformSelect: jest.fn(({ native }) => native),
24
+ }));
25
+
26
+ jest.mock("@applicaster/zapp-react-native-utils/playerUtils", () => ({
27
+ isAudioItem: jest.fn(() => false),
28
+ isInlineTV: jest.fn(() => false),
29
+ }));
30
+
31
+ jest.mock(
32
+ "@applicaster/zapp-react-native-tvos-ui-components/Components/TVEventHandlerComponent",
33
+ () => ({
34
+ TVEventHandlerComponent: ({ children }) => children,
35
+ })
36
+ );
37
+
38
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/utils", () => ({
39
+ usePrevious: jest.fn(),
40
+ }));
41
+
42
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks", () => {
43
+ const navigator = {
44
+ closeVideoModal: jest.fn(),
45
+ minimiseVideoModal: jest.fn(),
46
+ maximiseVideoModal: jest.fn(),
47
+ fullscreenVideoModal: jest.fn(),
48
+ isVideoModalDocked: jest.fn(() => false),
49
+ goBack: jest.fn(),
50
+ replaceTop: jest.fn(),
51
+ push: jest.fn(),
52
+ setPlayNextOverlay: jest.fn(),
53
+ currentRoute: "/playable/entry-1",
54
+ };
55
+
56
+ const backHandlerRef: { current: (() => boolean) | null } = {
57
+ current: null,
58
+ };
59
+
60
+ return {
61
+ useBackHandler: jest.fn((cb) => {
62
+ backHandlerRef.current = cb;
63
+ }),
64
+ useNavigation: jest.fn(() => navigator),
65
+ __navigator: navigator,
66
+ __backHandlerRef: backHandlerRef,
67
+ };
68
+ });
69
+
70
+ jest.mock("@applicaster/zapp-react-native-bridge/QuickBrick", () => ({
71
+ QUICK_BRICK_EVENTS: {
72
+ IDLE_TIMER_DISABLED: "IDLE_TIMER_DISABLED",
73
+ MOVE_APP_TO_BACKGROUND: "MOVE_APP_TO_BACKGROUND",
74
+ },
75
+ sendQuickBrickEvent: jest.fn(),
76
+ }));
77
+
78
+ jest.mock("../ProgramInfo", () => ({
79
+ ProgramInfo: () => null,
80
+ }));
81
+
82
+ jest.mock("../../AudioPlayer", () => ({
83
+ AudioPlayer: () => null,
84
+ }));
85
+
86
+ jest.mock("../logger", () => ({
87
+ log_debug: jest.fn(),
88
+ log_info: jest.fn(),
89
+ log_warning: jest.fn(),
90
+ playerContainerLogger: { error: jest.fn() },
91
+ }));
92
+
93
+ jest.mock(
94
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer",
95
+ () => {
96
+ const player = {
97
+ addListener: jest.fn(),
98
+ removeListener: jest.fn(),
99
+ isPaused: jest.fn(() => false),
100
+ isAd: jest.fn(() => false),
101
+ seekTo: jest.fn(),
102
+ getPluginConfiguration: jest.fn(() => ({})),
103
+ getOverlayObservable: jest.fn(() => ({ getPlayNextEntry: () => null })),
104
+ };
105
+
106
+ return {
107
+ usePlayer: jest.fn(() => player),
108
+ __player: player,
109
+ };
110
+ }
111
+ );
112
+
113
+ jest.mock(
114
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayNextOverlay",
115
+ () => ({
116
+ usePlayNextOverlay: jest.fn(() => null),
117
+ })
118
+ );
119
+
120
+ jest.mock(
121
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerNativeCommand",
122
+ () => ({
123
+ PlayerNativeCommandTypes: { clearPlayerData: "clearPlayerData" },
124
+ PlayerNativeSendCommand: jest.fn(),
125
+ })
126
+ );
127
+
128
+ jest.mock(
129
+ "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext",
130
+ () => ({
131
+ useSetNavbarState: jest.fn(() => ({ setVisible: jest.fn() })),
132
+ })
133
+ );
134
+
135
+ jest.mock(
136
+ "@applicaster/zapp-react-native-utils/reactHooks/screen/useTargetScreenData",
137
+ () => ({
138
+ useTargetScreenData: jest.fn(() => ({ id: "screen-id", styles: {} })),
139
+ })
140
+ );
141
+
142
+ jest.mock("../PlayerContainerContext", () => {
143
+ const ReactActual = require("react");
144
+
145
+ return {
146
+ PlayerContainerContext: ReactActual.createContext({
147
+ isLanguageOverlayVisible: false,
148
+ bottomFocusableId: "bottom",
149
+ showComponentsContainer: false,
150
+ refs: null,
151
+ }),
152
+ PlayerContainerContextProvider: ({ children }) => children,
153
+ };
154
+ });
155
+
156
+ jest.mock(
157
+ "@applicaster/zapp-react-native-ui-components/Components/FocusableGroup",
158
+ () => ({
159
+ FocusableGroup: ({ children }) => children,
160
+ })
161
+ );
162
+
163
+ jest.mock("../WappersView/PlayerFocusableWrapperView", () => ({
164
+ PlayerFocusableWrapperView: ({ children }) => children,
165
+ }));
166
+
167
+ jest.mock("../WappersView/ComponentFocusableWrapperView", () => ({
168
+ ComponentFocusableWrapperView: () => null,
169
+ }));
170
+
171
+ jest.mock("../index", () => ({
172
+ FocusableGroupMainContainerId: "player-container-general",
173
+ }));
174
+
175
+ jest.mock(
176
+ "@applicaster/zapp-react-native-utils/navigationUtils/itemTypeMatchers",
177
+ () => ({
178
+ isPlayable: jest.fn(() => true),
179
+ })
180
+ );
181
+
182
+ jest.mock("../../GeneralContentScreen", () => ({
183
+ GeneralContentScreen: () => null,
184
+ }));
185
+
186
+ jest.mock("@applicaster/zapp-react-native-redux/hooks", () => ({
187
+ usePickFromState: jest.fn(() => ({
188
+ appData: { isTabletPortrait: false },
189
+ })),
190
+ }));
191
+
192
+ import { PlayerContainer, VideoModalMode } from "../PlayerContainer";
193
+
194
+ const { __navigator: mockNavigator, __backHandlerRef: mockBackHandlerRef } =
195
+ jest.requireMock("@applicaster/zapp-react-native-utils/reactHooks");
196
+
197
+ const { playerManager: mockPlayerManager } = jest.requireMock(
198
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager"
199
+ );
200
+
201
+ const { __player: mockPlayer } = jest.requireMock(
202
+ "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer"
203
+ );
204
+
205
+ const Player = React.forwardRef(() => null);
206
+
207
+ const item = {
208
+ id: "entry-1",
209
+ title: "Test entry",
210
+ summary: "",
211
+ content: { src: "https://example.com/video.m3u8" },
212
+ extensions: {},
213
+ } as any;
214
+
215
+ const renderPlayerContainer = (props = {}) =>
216
+ render(
217
+ <PlayerContainer
218
+ Player={Player}
219
+ item={item}
220
+ style={{}}
221
+ loadPipesData={jest.fn()}
222
+ isModal={true}
223
+ mode={VideoModalMode.MAXIMIZED}
224
+ {...props}
225
+ />
226
+ );
227
+
228
+ const pressHardwareBack = () => {
229
+ if (!mockBackHandlerRef.current) {
230
+ throw new Error("back handler was not registered via useBackHandler");
231
+ }
232
+
233
+ return mockBackHandlerRef.current();
234
+ };
235
+
236
+ describe("PlayerContainer hardware back handling", () => {
237
+ beforeEach(() => {
238
+ jest.clearAllMocks();
239
+ mockBackHandlerRef.current = null;
240
+ mockPlayer.getPluginConfiguration.mockReturnValue({});
241
+ mockPlayer.isPaused.mockReturnValue(false);
242
+ mockPlayer.isAd.mockReturnValue(false);
243
+
244
+ mockPlayer.getOverlayObservable.mockReturnValue({
245
+ getPlayNextEntry: () => null,
246
+ });
247
+
248
+ mockPlayerManager.getPluginConfiguration.mockReturnValue({});
249
+ mockPlayerManager.on.mockReturnThis();
250
+ mockPlayerManager.removeHandler.mockReturnThis();
251
+ });
252
+
253
+ it("minimises the video modal on back press when mini player is enabled", () => {
254
+ renderPlayerContainer();
255
+
256
+ const handled = pressHardwareBack();
257
+
258
+ expect(mockNavigator.minimiseVideoModal).toHaveBeenCalled();
259
+ expect(mockNavigator.closeVideoModal).not.toHaveBeenCalled();
260
+ expect(handled).toBe(true);
261
+ });
262
+
263
+ it("closes the video modal on back press when mini player is disabled", () => {
264
+ mockPlayer.getPluginConfiguration.mockReturnValue({
265
+ disable_mini_player_when_inline: true,
266
+ });
267
+
268
+ renderPlayerContainer();
269
+
270
+ const handled = pressHardwareBack();
271
+
272
+ expect(mockNavigator.closeVideoModal).toHaveBeenCalled();
273
+ expect(mockNavigator.minimiseVideoModal).not.toHaveBeenCalled();
274
+ expect(handled).toBe(true);
275
+ });
276
+
277
+ it("does not handle back press when the player is already minimised", () => {
278
+ renderPlayerContainer({ mode: VideoModalMode.MINIMIZED });
279
+
280
+ const handled = pressHardwareBack();
281
+
282
+ expect(handled).toBe(false);
283
+ expect(mockNavigator.minimiseVideoModal).not.toHaveBeenCalled();
284
+ expect(mockNavigator.closeVideoModal).not.toHaveBeenCalled();
285
+ });
286
+ });
@@ -10,11 +10,6 @@ exports[`componentsMap renders renders components map correctly 1`] = `
10
10
  >
11
11
  <View
12
12
  onLayout={[Function]}
13
- style={
14
- {
15
- "flex": 1,
16
- }
17
- }
18
13
  >
19
14
  <RCTScrollView
20
15
  ListFooterComponent={
@@ -166,11 +161,6 @@ exports[`componentsMap renders renders components map correctly 1`] = `
166
161
  >
167
162
  <View
168
163
  onLayout={[Function]}
169
- style={
170
- {
171
- "flex": 1,
172
- }
173
- }
174
164
  >
175
165
  <View />
176
166
  </View>
@@ -182,11 +172,6 @@ exports[`componentsMap renders renders components map correctly 1`] = `
182
172
  >
183
173
  <View
184
174
  onLayout={[Function]}
185
- style={
186
- {
187
- "flex": 1,
188
- }
189
- }
190
175
  />
191
176
  </View>
192
177
  <View
@@ -19,10 +19,13 @@ import {
19
19
  useScreenData,
20
20
  } from "@applicaster/zapp-react-native-utils/reactHooks";
21
21
  import { getNavigationPluginModule } from "@applicaster/zapp-react-native-app/App/Layout/layoutHelpers";
22
+ import {
23
+ isValidColor,
24
+ isTransparentColor,
25
+ } from "@applicaster/zapp-react-native-utils/colorUtils";
22
26
 
23
27
  import { RouteManager } from "../RouteManager";
24
28
  import { useScreenConfiguration } from "../River/useScreenConfiguration";
25
- import { isValidColor } from "./utils";
26
29
  import { useWaitForValidOrientation } from "./hooks";
27
30
 
28
31
  const screenStyles = {
@@ -83,9 +86,10 @@ export function Screen(_props: Props) {
83
86
  const style = React.useMemo(
84
87
  () => ({
85
88
  ...screenStyles,
86
- backgroundColor: isValidColor(backgroundColor)
87
- ? backgroundColor
88
- : theme.app_background_color,
89
+ backgroundColor:
90
+ isValidColor(backgroundColor) && !isTransparentColor(backgroundColor)
91
+ ? backgroundColor
92
+ : theme.app_background_color,
89
93
  }),
90
94
  [theme.app_background_color, backgroundColor]
91
95
  );
@@ -0,0 +1,34 @@
1
+ import * as React from "react";
2
+ import { Animated, StyleSheet } from "react-native";
3
+
4
+ import { BackgroundImage } from "@applicaster/zapp-react-native-ui-components/Components/BackgroundImage";
5
+
6
+ const styles = StyleSheet.create({
7
+ container: {
8
+ ...StyleSheet.absoluteFillObject,
9
+ position: "absolute",
10
+ },
11
+ });
12
+
13
+ export const Overlay = ({
14
+ opacity,
15
+ backgroundColor,
16
+ }: {
17
+ opacity: Animated.Value;
18
+ backgroundColor: string;
19
+ }) => {
20
+ return (
21
+ <Animated.View
22
+ style={[
23
+ styles.container,
24
+ {
25
+ opacity,
26
+ backgroundColor,
27
+ },
28
+ ]}
29
+ testID="animated-component"
30
+ >
31
+ <BackgroundImage />
32
+ </Animated.View>
33
+ );
34
+ };
@@ -0,0 +1,88 @@
1
+ import React from "react";
2
+ import { Animated } from "react-native";
3
+ import { render } from "@testing-library/react-native";
4
+
5
+ jest.mock(
6
+ "@applicaster/zapp-react-native-ui-components/Components/BackgroundImage",
7
+ () => {
8
+ const { Text } = require("react-native");
9
+
10
+ return {
11
+ BackgroundImage: () => (
12
+ <Text testID="background-image">BackgroundImage</Text>
13
+ ),
14
+ };
15
+ }
16
+ );
17
+
18
+ import { Overlay } from "../Overlay";
19
+
20
+ const flattenStyle = (style: unknown): Record<string, unknown> => {
21
+ if (!style) {
22
+ return {};
23
+ }
24
+
25
+ if (Array.isArray(style)) {
26
+ return style.reduce<Record<string, unknown>>(
27
+ (acc, item) => ({ ...acc, ...flattenStyle(item) }),
28
+ {}
29
+ );
30
+ }
31
+
32
+ if (typeof style === "object") {
33
+ return style as Record<string, unknown>;
34
+ }
35
+
36
+ return {};
37
+ };
38
+
39
+ describe("Overlay", () => {
40
+ it("renders the animated overlay container", () => {
41
+ const opacity = new Animated.Value(1);
42
+
43
+ const { getByTestId } = render(
44
+ <Overlay opacity={opacity} backgroundColor="#000000" />
45
+ );
46
+
47
+ expect(getByTestId("animated-component")).toBeTruthy();
48
+ });
49
+
50
+ it("applies opacity and backgroundColor to the overlay", () => {
51
+ const opacity = new Animated.Value(0.5);
52
+
53
+ const { getByTestId } = render(
54
+ <Overlay opacity={opacity} backgroundColor="#ff0000" />
55
+ );
56
+
57
+ const style = flattenStyle(getByTestId("animated-component").props.style);
58
+
59
+ expect(style.backgroundColor).toBe("#ff0000");
60
+ expect(style.opacity).toBe(0.5);
61
+ });
62
+
63
+ it("positions the overlay with absolute fill layout", () => {
64
+ const opacity = new Animated.Value(1);
65
+
66
+ const { getByTestId } = render(
67
+ <Overlay opacity={opacity} backgroundColor="#000000" />
68
+ );
69
+
70
+ const style = flattenStyle(getByTestId("animated-component").props.style);
71
+
72
+ expect(style.position).toBe("absolute");
73
+ expect(style.top).toBe(0);
74
+ expect(style.left).toBe(0);
75
+ expect(style.right).toBe(0);
76
+ expect(style.bottom).toBe(0);
77
+ });
78
+
79
+ it("renders BackgroundImage inside the overlay", () => {
80
+ const opacity = new Animated.Value(1);
81
+
82
+ const { getByTestId } = render(
83
+ <Overlay opacity={opacity} backgroundColor="#000000" />
84
+ );
85
+
86
+ expect(getByTestId("background-image")).toBeTruthy();
87
+ });
88
+ });
@@ -1,8 +1,10 @@
1
1
  import * as React from "react";
2
- import { Animated, StyleSheet } from "react-native";
2
+ import { Animated } from "react-native";
3
+
3
4
  import { isFirstComponentScreenPicker } from "@applicaster/zapp-react-native-utils/componentsUtils";
4
5
  import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";
5
6
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
7
+ import { Overlay } from "./Overlay";
6
8
 
7
9
  import { ScreenRevealManager } from "./ScreenRevealManager";
8
10
  import {
@@ -10,13 +12,6 @@ import {
10
12
  emitScreenRevealManagerIsNotReadyToShow,
11
13
  } from "./utils";
12
14
 
13
- const styles = StyleSheet.create({
14
- container: {
15
- ...StyleSheet.absoluteFillObject,
16
- position: "absolute",
17
- },
18
- });
19
-
20
15
  export const TIMEOUT = 300; // 300 ms
21
16
 
22
17
  const HIDDEN = 0; // opacity = 0
@@ -93,17 +88,11 @@ export const withScreenRevealManager = (Component) => {
93
88
  onLoadFailedFromScreenRevealManager={managerRef.current.onLoadFailed}
94
89
  />
95
90
  {isShowOverlay ? (
96
- <Animated.View
97
- style={[
98
- styles.container,
99
- {
100
- opacity: opacityRef.current,
101
- // TODO: we should support background image as well, but for now we will use background color from theme
102
- backgroundColor:
103
- props.backgroundColor ?? theme.app_background_color,
104
- },
105
- ]}
106
- testID="animated-component"
91
+ <Overlay
92
+ opacity={opacityRef.current}
93
+ backgroundColor={
94
+ props.backgroundColor ?? theme.app_background_color
95
+ }
107
96
  />
108
97
  ) : null}
109
98
  </>
@@ -1,11 +1,11 @@
1
1
  import React from "react";
2
- import { StyleSheet } from "react-native";
2
+ import { StyleSheet, SafeAreaView as RNSafeAreaView } from "react-native";
3
3
  import { SafeAreaView } from "react-native-safe-area-context";
4
-
5
4
  import { useIsRTL } from "@applicaster/zapp-react-native-utils/localizationUtils";
6
5
  import CloseButton from "./Buttons/CloseButton";
7
6
  import BackButton from "./Buttons/BackButton";
8
7
  import BarTitle from "./BarTitle";
8
+ import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
9
9
 
10
10
  type Props = {
11
11
  screenStyles: {
@@ -24,6 +24,11 @@ const defaultState: NavBarState = {
24
24
  title: "",
25
25
  };
26
26
 
27
+ const SafeAreaViewPlatform = platformSelect({
28
+ ios: RNSafeAreaView,
29
+ android: SafeAreaView,
30
+ });
31
+
27
32
  const BarView = ({ screenStyles, barState = defaultState }: Props) => {
28
33
  const isRTL = useIsRTL();
29
34
 
@@ -45,7 +50,7 @@ const BarView = ({ screenStyles, barState = defaultState }: Props) => {
45
50
  );
46
51
 
47
52
  return (
48
- <SafeAreaView
53
+ <SafeAreaViewPlatform
49
54
  edges={["top", "left", "right"]}
50
55
  style={[style.view, isRTL ? style.rtlStyle : {}]}
51
56
  testID="BarView-safeAreaView"
@@ -53,7 +58,7 @@ const BarView = ({ screenStyles, barState = defaultState }: Props) => {
53
58
  {backButton}
54
59
  <BarTitle title={barState.title} screenStyles={screenStyles} />
55
60
  {closeButton}
56
- </SafeAreaView>
61
+ </SafeAreaViewPlatform>
57
62
  );
58
63
  };
59
64
 
@@ -1,6 +1,7 @@
1
1
  import { create, useStore } from "zustand";
2
2
  import { produce } from "immer";
3
3
  import { useCallback, useMemo } from "react";
4
+ import { last } from "@applicaster/zapp-react-native-utils/utils";
4
5
 
5
6
  type Dimensions = {
6
7
  width: Option<number>;
@@ -10,14 +11,17 @@ type Dimensions = {
10
11
  type DimensionsState = {
11
12
  dimensions: Record<string, Dimensions>;
12
13
  setCachedDimensions: (id: string, dimensions: Dimensions) => void;
14
+ ids: string[];
13
15
  };
14
16
 
15
17
  const dimensionsStore = create<DimensionsState>((set) => ({
16
18
  dimensions: {},
19
+ ids: [],
17
20
  setCachedDimensions: (id, dimensions) =>
18
21
  set(
19
22
  produce((draft) => {
20
23
  draft.dimensions[id] = dimensions;
24
+ draft.ids.push(id);
21
25
  })
22
26
  ),
23
27
  }));
@@ -28,7 +32,7 @@ const initialDimensions = () => ({
28
32
  });
29
33
 
30
34
  export const useCachedDimensions = (id: string) => {
31
- const { dimensions, setCachedDimensions } = useStore(
35
+ const { dimensions, setCachedDimensions, ids } = useStore(
32
36
  dimensionsStore
33
37
  ) as DimensionsState;
34
38
 
@@ -40,11 +44,22 @@ export const useCachedDimensions = (id: string) => {
40
44
  setCachedDimensions(id, newDimensions);
41
45
  }, []);
42
46
 
47
+ const getLastCachedDimensions = useCallback(() => {
48
+ const previousId = last(ids);
49
+
50
+ if (!previousId) {
51
+ return undefined;
52
+ }
53
+
54
+ return dimensions[previousId];
55
+ }, [dimensions, ids]);
56
+
43
57
  return useMemo(
44
58
  () => ({
45
59
  getCachedDimensions,
46
60
  setCachedDimensions: handleSetCachedDimensions,
61
+ getLastCachedDimensions,
47
62
  }),
48
- [getCachedDimensions, handleSetCachedDimensions]
63
+ [getCachedDimensions, handleSetCachedDimensions, getLastCachedDimensions]
49
64
  );
50
65
  };
@@ -1,4 +1,3 @@
1
- import { sendTapCellEvent, sendTapMenuItem } from "../Analytics";
2
1
  import { getItemType } from "@applicaster/zapp-react-native-utils/navigationUtils";
3
2
  import { SCREEN_TYPES } from "@applicaster/zapp-react-native-utils/navigationUtils/itemTypes";
4
3
 
@@ -12,7 +11,6 @@ export function onCellPress(
12
11
  itemIndex,
13
12
  layoutVersion = "v1"
14
13
  ) {
15
- sendTapCellEvent(entry, component, headerTitle, itemIndex);
16
14
  const componentScreenType = screenTypeFromComponent(component);
17
15
  const entryScreenType = screenTypeFromEntry(entry);
18
16
  const itemType = getItemType(entry, layoutVersion);
@@ -37,7 +35,3 @@ function screenTypeFromComponent(component) {
37
35
  function screenTypeFromEntry(entry) {
38
36
  return entry && entry.screen_type;
39
37
  }
40
-
41
- export function onMenuItemFocus({ item, index, isHome }) {
42
- sendTapMenuItem({ item, index, isHome });
43
- }