@applicaster/zapp-react-native-ui-components 12.0.0-rc.51 → 12.0.0-rc.53

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.
@@ -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
  );
@@ -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
  });
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import * as R from "ramda";
3
- import { View, FlatList, StyleSheet, Platform } from "react-native";
3
+ import { View, StyleSheet, Platform, Animated, FlatList } from "react-native";
4
4
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
5
5
  import { RiverItem } from "../RiverItem";
6
6
  import { RiverFooter } from "../RiverFooter";
@@ -21,7 +21,8 @@ import {
21
21
  import { useScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenData";
22
22
  import { isLast } from "@applicaster/zapp-react-native-utils/arrayUtils";
23
23
  import { withComponentsMapProvider } from "@applicaster/zapp-react-native-ui-components/Decorators/ComponentsMapWrapper";
24
- import { useScreenScrollStore } from "@applicaster/zapp-react-native-utils/screenState";
24
+ import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
25
+ import { useShallow } from "zustand/react/shallow";
25
26
 
26
27
  type Props = {
27
28
  feed: ZappFeed;
@@ -41,8 +42,10 @@ const styles = StyleSheet.create({
41
42
  },
42
43
  });
43
44
 
44
- const scrollIndicatorInsets = { right: 1 };
45
+ const getNearestValue = (value, optionA, optionB) =>
46
+ Math.abs(value - optionA) < Math.abs(value - optionB) ? optionA : optionB;
45
47
 
48
+ const scrollIndicatorInsets = { right: 1 };
46
49
  const keyExtractor = R.compose(String, R.prop("id"));
47
50
 
48
51
  function ComponentsMapComponent(props: Props) {
@@ -60,6 +63,7 @@ function ComponentsMapComponent(props: Props) {
60
63
  refreshingPipesV1,
61
64
  } = props;
62
65
 
66
+ const flatListRef = React.useRef<FlatList | null>(null);
63
67
  const screenConfig = useScreenConfiguration(riverId);
64
68
  const screenData = useScreenData(riverId);
65
69
  const pullToRefreshEnabled = screenData?.rules?.pull_to_refresh_enabled;
@@ -194,26 +198,67 @@ function ComponentsMapComponent(props: Props) {
194
198
  []
195
199
  );
196
200
 
197
- const onScrollFlatList = React.useCallback(
198
- ({ nativeEvent }, isScrollingDone = false) => {
199
- const {
200
- contentOffset: { y },
201
- } = nativeEvent;
201
+ const navBarStore = useScreenContextV2()._navBarStore;
202
202
 
203
- useScreenScrollStore.setState({
204
- scrollingVelocity: isScrollingDone ? 0 : 1,
205
- scrollPosition: y,
206
- });
207
- },
208
- []
203
+ const scrollY = navBarStore(
204
+ useShallow(({ scrollYAnimated }) => scrollYAnimated)
209
205
  );
210
206
 
211
- const onMomentumScrollEnd = React.useCallback(
212
- ({ nativeEvent }) => {
213
- onScrollFlatList({ nativeEvent }, true);
214
- },
215
- [onScrollFlatList]
216
- );
207
+ const canMomentum = React.useRef(false);
208
+
209
+ const onScrollBeginDrag = React.useCallback(() => {
210
+ canMomentum.current = true;
211
+ }, []);
212
+
213
+ /* onMomentumScrollEnd: Handling snap to expanded/collapsed state ( for navbar ) */
214
+ const _onMomentumScrollEnd = React.useCallback(({ nativeEvent }) => {
215
+ if (!canMomentum.current) return;
216
+ canMomentum.current = false;
217
+
218
+ const { height: headerHeight, scrollState } = navBarStore.getState();
219
+
220
+ const offsetY = nativeEvent.contentOffset.y;
221
+
222
+ if (
223
+ !(scrollState === 0 || scrollState === -headerHeight) &&
224
+ flatListRef.current
225
+ ) {
226
+ const offset =
227
+ getNearestValue(Math.round(scrollState), -headerHeight, 0) ===
228
+ -headerHeight
229
+ ? Math.round(offsetY + scrollState + headerHeight)
230
+ : Math.round(offsetY + scrollState);
231
+
232
+ flatListRef.current.scrollToOffset({
233
+ animated: true,
234
+ offset,
235
+ });
236
+ }
237
+ }, []);
238
+
239
+ /* onScrollEnd: Handling snap to expanded/collapsed state ( for navbar ) */
240
+ const _onScrollEndDrag = React.useCallback(({ nativeEvent }) => {
241
+ const { height: headerHeight, scrollState } = navBarStore.getState();
242
+
243
+ const offsetY = nativeEvent.contentOffset.y;
244
+
245
+ if (
246
+ !(scrollState === 0 || scrollState === -headerHeight) &&
247
+ flatListRef.current &&
248
+ nativeEvent.velocity.y === 0
249
+ ) {
250
+ const offset =
251
+ getNearestValue(Math.round(scrollState), -headerHeight, 0) ===
252
+ -headerHeight
253
+ ? Math.round(offsetY + scrollState + headerHeight)
254
+ : Math.round(offsetY + scrollState);
255
+
256
+ flatListRef.current.scrollToOffset({
257
+ animated: true,
258
+ offset,
259
+ });
260
+ }
261
+ }, []);
217
262
 
218
263
  if (shouldShowLoadingError) {
219
264
  return <RiverError />;
@@ -230,6 +275,9 @@ function ComponentsMapComponent(props: Props) {
230
275
  >
231
276
  <ViewportTracker>
232
277
  <FlatList
278
+ ref={(ref) => {
279
+ flatListRef.current = ref;
280
+ }}
233
281
  // Fix for WebView rerender crashes on Android API 28+
234
282
  // https://github.com/react-native-webview/react-native-webview/issues/1915#issuecomment-964035468
235
283
  overScrollMode={Platform.OS === "android" ? "never" : "auto"}
@@ -249,8 +297,23 @@ function ComponentsMapComponent(props: Props) {
249
297
  }
250
298
  ListFooterComponent={renderFooter}
251
299
  refreshControl={refreshControl}
252
- onScroll={onScrollFlatList}
253
- onMomentumScrollEnd={onMomentumScrollEnd}
300
+ onScrollBeginDrag={onScrollBeginDrag}
301
+ onScroll={Animated.event(
302
+ [
303
+ {
304
+ nativeEvent: {
305
+ contentOffset: {
306
+ y: scrollY,
307
+ },
308
+ },
309
+ },
310
+ ],
311
+ {
312
+ useNativeDriver: false,
313
+ }
314
+ )}
315
+ onMomentumScrollEnd={_onMomentumScrollEnd}
316
+ onScrollEndDrag={_onScrollEndDrag}
254
317
  scrollEventThrottle={16}
255
318
  {...scrollViewExtraProps}
256
319
  />
@@ -11,7 +11,9 @@ exports[`<Screen Component /> when the navbar should be hidden renders correctly
11
11
  }
12
12
  >
13
13
  <View
14
+ hasMenu={false}
14
15
  id="/river/testId"
16
+ pathname="/river/testId"
15
17
  selected="testId"
16
18
  testID="navBar"
17
19
  title=""
@@ -41,7 +43,9 @@ exports[`<Screen Component /> when the navbar should show renders correctly 1`]
41
43
  }
42
44
  >
43
45
  <View
46
+ hasMenu={false}
44
47
  id="/river/testId"
48
+ pathname="/river/testId"
45
49
  selected="testId"
46
50
  testID="navBar"
47
51
  title=""
@@ -6,7 +6,10 @@ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
6
6
 
7
7
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
8
8
  import { getComponentModule } from "@applicaster/zapp-react-native-utils/pluginUtils";
9
- import { getNavigationPropsV2 } from "@applicaster/zapp-react-native-utils/navigationUtils";
9
+ import {
10
+ getNavigationPropsV2,
11
+ resolveNavigationPlugin,
12
+ } from "@applicaster/zapp-react-native-utils/navigationUtils";
10
13
  import { useSafeAreaInsets } from "react-native-safe-area-context";
11
14
  import {
12
15
  useDimensions,
@@ -52,11 +55,11 @@ function isValidColor(string) {
52
55
  }
53
56
 
54
57
  type Props = {
55
- setToggleMenu: () => void;
56
- setCloseMenu: () => void;
58
+ pathname: unknown;
59
+ screenData: unknown;
57
60
  };
58
61
 
59
- export function Screen({ setToggleMenu, setCloseMenu }: Props) {
62
+ export function Screen(_props: Props) {
60
63
  const theme = useTheme<BaseThemePropertiesMobile>();
61
64
  const navigation = useNavigation();
62
65
  const { plugins } = usePickFromState(["plugins"]);
@@ -70,26 +73,38 @@ export function Screen({ setToggleMenu, setCloseMenu }: Props) {
70
73
  fullDimensions: true,
71
74
  });
72
75
 
73
- const routeData = useRoute();
76
+ const { screenData, pathname } = useRoute();
74
77
 
75
78
  const currentRiver = useScreenData(
76
- getScreenId(routeData.screenData, navigation.activeRiver)
79
+ getScreenId(screenData, navigation.activeRiver)
77
80
  );
78
81
 
79
82
  const { title } = useNavbarState();
80
83
 
81
- const navBarProps = React.useMemo(
82
- () => ({
83
- ...getNavigationPropsV2({
84
- currentRiver,
85
- title,
86
- category: "nav_bar",
87
- }),
88
- setToggleMenu,
89
- setCloseMenu,
90
- }),
91
- [currentRiver, setCloseMenu, setToggleMenu]
92
- );
84
+ const hasMenu =
85
+ resolveNavigationPlugin({
86
+ category: "menu",
87
+ navigations: currentRiver.navigations,
88
+ plugins,
89
+ })?.identifier === "quick_brick_side_menu";
90
+
91
+ const navBarProps = React.useMemo<MobileNavBarPluginProps | null>(() => {
92
+ const props = getNavigationPropsV2({
93
+ currentRiver,
94
+ title,
95
+ category: "nav_bar",
96
+ });
97
+
98
+ if (props) {
99
+ return {
100
+ ...props,
101
+ id: pathname,
102
+ pathname: pathname,
103
+ };
104
+ }
105
+
106
+ return null;
107
+ }, [currentRiver, pathname]);
93
108
 
94
109
  const NavBar = React.useMemo(
95
110
  () =>
@@ -97,12 +112,10 @@ export function Screen({ setToggleMenu, setCloseMenu }: Props) {
97
112
  "nav_bar",
98
113
  currentRiver,
99
114
  plugins
100
- ) as React.ComponentType<MobileNavBarPluginProps>,
115
+ ) as React.ComponentType<MobileNavBarPluginProps & { hasMenu: boolean }>,
101
116
  []
102
117
  );
103
118
 
104
- const { screenData, pathname } = routeData;
105
-
106
119
  const offlinePlugin = getComponentModule({
107
120
  plugins,
108
121
  components: {},
@@ -133,9 +146,7 @@ export function Screen({ setToggleMenu, setCloseMenu }: Props) {
133
146
  return (
134
147
  <ScreenContextProvider pathname={pathname}>
135
148
  <View style={style}>
136
- <NavBar
137
- {...{ ...(navBarProps as MobileNavBarPluginProps), id: pathname }}
138
- />
149
+ {navBarProps && <NavBar {...navBarProps} hasMenu={hasMenu} />}
139
150
 
140
151
  <OfflineFallbackScreen screenData={targetScreenData}>
141
152
  {/* @TODO RouteManager doesn't use props, can they be removed ? */}
@@ -30,11 +30,6 @@ exports[`PlayerLiveImageComponent should render correctly with default props 1`]
30
30
  playerId="player1"
31
31
  resizeMode="cover"
32
32
  screenConfig={Object {}}
33
- source={
34
- Object {
35
- "uri": "",
36
- }
37
- }
38
33
  style={Object {}}
39
34
  />
40
35
  </View>
@@ -15,7 +15,7 @@ export function ViewportTracker(props) {
15
15
 
16
16
  const assignRef = React.useCallback(
17
17
  (ref) => {
18
- callOnChildren(children, "ref", ref);
18
+ children?.ref?.(ref);
19
19
  setNodeHandle(findNodeHandle(ref));
20
20
  },
21
21
  [children]
@@ -6,6 +6,7 @@ import { ViewportEventContext, ViewportEvents } from "../ViewportEvents";
6
6
  type Props = {
7
7
  viewportEventsManager?: any;
8
8
  children: React.ReactNode;
9
+ flatListRef?: any; // this ref is used to bind to children component
9
10
  };
10
11
 
11
12
  export function ViewportTracker(props: Props) {
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
 
3
3
  export function callOnChildren(children, method, ...args) {
4
4
  const childMethod = React.Children.only(children).props[method];
5
- childMethod && childMethod(...args);
5
+ typeof childMethod === "function" && childMethod?.(...args);
6
6
  }
7
7
 
8
8
  export function isInViewport(
@@ -1,4 +1,5 @@
1
1
  import { createContext } from "@applicaster/zapp-react-native-utils/reactUtils/createContext";
2
+ import React from "react";
2
3
 
3
4
  const initialContext = { toggleMenu: {}, closeMenu: {}, openMenu: {} };
4
5
 
@@ -16,3 +17,18 @@ const stateValidator = ({ property }) =>
16
17
  property === "openMenu";
17
18
 
18
19
  export const MenuToggleContext = createContext(initialContext, stateValidator);
20
+
21
+ export type MenuToggleContextType = {
22
+ toggleMenu: any;
23
+ setToggleMenu: () => void;
24
+ openMenu: any;
25
+ setOpenMenu: () => void;
26
+ closeMenu: any;
27
+ setCloseMenu: () => void;
28
+ };
29
+
30
+ export const useMenuToggleContext = () => {
31
+ return React.useContext<MenuToggleContextType>(
32
+ MenuToggleContext.ReactContext
33
+ );
34
+ };
@@ -0,0 +1,3 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`ScreenContext withScreenContext should return the context 1`] = `null`;
@@ -0,0 +1,27 @@
1
+ import { render } from "@testing-library/react-native";
2
+ import { ScreenContext, ScreenContextProvider, withScreenContext } from "../";
3
+ import React from "react";
4
+
5
+ describe("ScreenContext", () => {
6
+ describe("ScreneContext context", () => {
7
+ it("should return the context", () => {
8
+ expect(ScreenContext).toBeDefined();
9
+ });
10
+ });
11
+
12
+ describe("ScreenContextProvider", () => {
13
+ it("should return the provider", () => {
14
+ expect(ScreenContextProvider).toBeDefined();
15
+ });
16
+ });
17
+
18
+ describe("withScreenContext", () => {
19
+ it("should return the context", () => {
20
+ const Component = () => null;
21
+ const WrappedComponent = withScreenContext(Component);
22
+ const { toJSON } = render(<WrappedComponent />);
23
+
24
+ expect(toJSON()).toMatchSnapshot();
25
+ });
26
+ });
27
+ });
@@ -1,4 +1,10 @@
1
- import React, { createContext, ReactNode, useMemo, useState } from "react";
1
+ import React, {
2
+ createContext,
3
+ ReactNode,
4
+ useCallback,
5
+ useMemo,
6
+ useRef,
7
+ } from "react";
2
8
  import {
3
9
  useCurrentScreenData,
4
10
  useNavigation,
@@ -7,27 +13,67 @@ import {
7
13
  } from "@applicaster/zapp-react-native-utils/reactHooks";
8
14
  import { useModalNavigationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ModalNavigationContext";
9
15
  import { useNestedNavigationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/NestedNavigationContext";
16
+ import { create, StoreApi, UseBoundStore } from "zustand";
17
+ import { useShallow } from "zustand/react/shallow";
18
+ import { Animated } from "react-native";
19
+
20
+ interface NavBarStoreState {
21
+ visible: boolean;
22
+ title: string;
23
+ summary: string;
24
+ setVisible: (state: boolean) => void;
25
+ setTitle: (title: string) => void;
26
+ setSummary: (subtitle: string) => void;
27
+ height: number;
28
+ scrollState: number;
29
+ isOnBottomNavBar: boolean;
30
+ scrollYAnimated: Animated.Value;
31
+ }
32
+
33
+ interface NavBarState {
34
+ visible: boolean;
35
+ title: string;
36
+ summary: string;
37
+ setVisible: (state: boolean) => void;
38
+ setTitle: (title: string) => void;
39
+ setSummary: (subtitle: string) => void;
40
+ }
10
41
 
11
42
  type ScreenContextType = {
12
- navBar: {
13
- visible: boolean;
14
- title: string;
15
- summary: string;
16
- setVisible: (state: boolean) => void;
17
- setTitle: (title: string) => void;
18
- setSummary: (subtitle: string) => void;
19
- };
43
+ _navBarStore: UseBoundStore<StoreApi<NavBarStoreState>>;
44
+ navBar: NavBarState;
20
45
  legacyFormatScreenData: LegacyNavigationScreenData | null;
21
46
  };
22
47
 
48
+ const createStore = () =>
49
+ create<NavBarStoreState>((set) => ({
50
+ title: "",
51
+ summary: "",
52
+ visible: true,
53
+ height: 0,
54
+ scrollState: 0,
55
+ isOnBottomNavBar: true,
56
+ scrollYAnimated: new Animated.Value(0),
57
+ setTitle(title) {
58
+ set({ title });
59
+ },
60
+ setSummary(summary) {
61
+ set({ summary });
62
+ },
63
+ setVisible(visible) {
64
+ set({ visible });
65
+ },
66
+ }));
67
+
23
68
  export const ScreenContext = createContext<ScreenContextType>({
69
+ _navBarStore: createStore(),
24
70
  navBar: {
25
71
  visible: true,
26
72
  title: "",
27
73
  summary: "",
28
- setVisible: (_state) => {},
29
- setTitle: (_title) => {},
30
- setSummary: (_subtitle) => {},
74
+ setVisible: (_state) => void 0,
75
+ setTitle: (_title) => void 0,
76
+ setSummary: (_subtitle) => void 0,
31
77
  },
32
78
  legacyFormatScreenData: {} as LegacyNavigationScreenData,
33
79
  });
@@ -53,9 +99,30 @@ export function ScreenContextProvider({
53
99
  const isNested = useNestedNavigationContext();
54
100
  const currentScreenScreenData = useCurrentScreenData();
55
101
 
56
- const [navBarTitle, setNavBarTitle] = useState("");
57
- const [navBarSummary, setNavBarSummary] = useState("");
58
- const [navBarVisible, setNavBarVisible] = useState(true);
102
+ const screenNavBarStateRef =
103
+ useRef<null | ReturnType<typeof createStore>>(null);
104
+
105
+ const getScreenNavBarState = useCallback(() => {
106
+ if (screenNavBarStateRef.current !== null) {
107
+ return screenNavBarStateRef.current;
108
+ }
109
+
110
+ const navBarState = createStore();
111
+ screenNavBarStateRef.current = navBarState;
112
+
113
+ return navBarState;
114
+ }, []);
115
+
116
+ const screenNavBarState = getScreenNavBarState()(
117
+ useShallow((state) => ({
118
+ visible: state.visible,
119
+ title: state.title,
120
+ summary: state.summary,
121
+ setTitle: state.setTitle,
122
+ setSummary: state.setSummary,
123
+ setVisible: state.setVisible,
124
+ }))
125
+ );
59
126
 
60
127
  const isHook = pathname.includes("hook");
61
128
 
@@ -73,26 +140,28 @@ export function ScreenContextProvider({
73
140
  const isVisible = isNavBarVisible(
74
141
  pathname,
75
142
  currentScreenScreenData, // what fallback should be if currentScreenData is not available?
76
- navBarVisible,
143
+ screenNavBarState.visible,
77
144
  canGoBack(isHook, pathname),
78
145
  videoModalState
79
146
  );
80
147
 
81
- const navBarState = useMemo(() => {
82
- return {
83
- title: navBarTitle,
84
- summary: navBarSummary,
148
+ const navBarState = useMemo(
149
+ () => ({
150
+ title: screenNavBarState.title,
151
+ summary: screenNavBarState.summary,
85
152
  visible: isVisible,
86
- setTitle: setNavBarTitle,
87
- setSummary: setNavBarSummary,
88
- setVisible: setNavBarVisible,
89
- };
90
- }, [isVisible, navBarTitle, navBarSummary]);
153
+ setTitle: screenNavBarState.setTitle,
154
+ setSummary: screenNavBarState.setSummary,
155
+ setVisible: screenNavBarState.setVisible,
156
+ }),
157
+ [isVisible, screenNavBarState.title, screenNavBarState.summary]
158
+ );
91
159
 
92
160
  return (
93
161
  <ScreenContext.Provider
94
162
  value={useMemo(
95
163
  () => ({
164
+ _navBarStore: getScreenNavBarState(),
96
165
  navBar: navBarState,
97
166
  legacyFormatScreenData: routeScreenData,
98
167
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "12.0.0-rc.51",
3
+ "version": "12.0.0-rc.53",
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",
@@ -34,10 +34,10 @@
34
34
  "redux-mock-store": "^1.5.3"
35
35
  },
36
36
  "dependencies": {
37
- "@applicaster/applicaster-types": "12.0.0-rc.51",
38
- "@applicaster/zapp-react-native-bridge": "12.0.0-rc.51",
39
- "@applicaster/zapp-react-native-redux": "12.0.0-rc.51",
40
- "@applicaster/zapp-react-native-utils": "12.0.0-rc.51",
37
+ "@applicaster/applicaster-types": "12.0.0-rc.53",
38
+ "@applicaster/zapp-react-native-bridge": "12.0.0-rc.53",
39
+ "@applicaster/zapp-react-native-redux": "12.0.0-rc.53",
40
+ "@applicaster/zapp-react-native-utils": "12.0.0-rc.53",
41
41
  "promise": "^8.3.0",
42
42
  "react-router-native": "^5.1.2",
43
43
  "url": "^0.11.0",
File without changes