@momo-kits/foundation 0.167.1-beta.7 → 0.167.1-duo-test.1

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 (47) hide show
  1. package/Application/BottomSheet.tsx +115 -14
  2. package/Application/BottomTab/BottomTabBar.tsx +6 -3
  3. package/Application/BottomTab/DuoBarCompression.tsx +135 -0
  4. package/Application/BottomTab/DuoTabBar.tsx +462 -0
  5. package/Application/BottomTab/index.tsx +150 -134
  6. package/Application/Components/DuoGlassBackground.tsx +179 -0
  7. package/Application/Components/DuoQuickReturnHeader.tsx +42 -0
  8. package/Application/Components/HeaderExtendHeader.tsx +75 -28
  9. package/Application/Components/HeaderLeft.tsx +38 -0
  10. package/Application/Components/HeaderRight.tsx +142 -9
  11. package/Application/Components/HeaderTitle.tsx +61 -21
  12. package/Application/Components/OptionalGlassView.ts +11 -0
  13. package/Application/Components/OptionalScrollObserver.ts +12 -0
  14. package/Application/Components/SearchHeader.tsx +22 -15
  15. package/Application/Components/SideBarButton.tsx +139 -0
  16. package/Application/ModalScreen.tsx +16 -2
  17. package/Application/NavigationContainer.tsx +36 -50
  18. package/Application/Navigator.ts +1 -3
  19. package/Application/StackScreen.tsx +63 -9
  20. package/Application/WidgetContainer.tsx +9 -15
  21. package/Application/device.ts +29 -0
  22. package/Application/types.ts +27 -1
  23. package/Application/utils.tsx +255 -7
  24. package/Consts/index.ts +0 -1
  25. package/Consts/theme.ts +3 -4
  26. package/Input/Input.tsx +3 -4
  27. package/Input/InputMoney.tsx +31 -51
  28. package/Input/InputPhoneNumber.tsx +3 -4
  29. package/Input/InputSearch.tsx +3 -4
  30. package/Input/InputTextArea.tsx +3 -4
  31. package/Layout/Card.tsx +8 -26
  32. package/Layout/FloatingButton.tsx +3 -3
  33. package/Layout/Item.tsx +1 -10
  34. package/Layout/ReservedRegions.tsx +47 -0
  35. package/Layout/Screen.tsx +199 -52
  36. package/Layout/Section.tsx +5 -18
  37. package/Layout/index.ts +4 -4
  38. package/Layout/styles.ts +5 -0
  39. package/Layout/utils.ts +465 -5
  40. package/Popup/PopupNotify.tsx +33 -22
  41. package/Popup/PopupPromotion.tsx +20 -8
  42. package/Popup/utils.ts +32 -0
  43. package/Text/index.tsx +5 -15
  44. package/Text/utils.ts +1 -2
  45. package/package.json +3 -2
  46. package/Consts/designSystemFont.ts +0 -25
  47. package/Layout/GridSystem.tsx +0 -137
@@ -1,17 +1,14 @@
1
1
  import { Colors, Radius, Spacing, Styles } from '../../Consts';
2
2
  import { InputRef, InputSearch } from '../../Input';
3
- import {
4
- Dimensions,
5
- StyleSheet,
6
- TouchableOpacity,
7
- View,
8
- } from 'react-native';
3
+ import { StyleSheet, TouchableOpacity, View } from 'react-native';
9
4
  import Animated, { useAnimatedStyle } from 'react-native-reanimated';
10
5
  import React, { useContext } from 'react';
11
6
  import { SearchHeaderProps } from '../types';
12
7
  import { ApplicationContext, MiniAppContext } from '../../Context';
13
8
  import { Text } from '../../Text';
14
9
  import { useNormalizedSharedValue } from '../utils';
10
+ import { useScreenLayout } from '../../Layout';
11
+ import { DuoMetrics } from '../../Layout/utils';
15
12
 
16
13
  const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
17
14
  (
@@ -26,19 +23,31 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
26
23
  ) => {
27
24
  const { theme, navigator } = useContext(ApplicationContext);
28
25
  const context = useContext<any>(MiniAppContext);
29
- const BACK_WIDTH = 28;
30
- const { width: screenWidth } = Dimensions.get('window');
26
+ const layout = useScreenLayout();
27
+ // the iPhone Duo status bar row keeps a side bar sized back button
28
+ const BACK_WIDTH = layout.statusBarClearance ? DuoMetrics.controlSize : 28;
31
29
 
32
30
  const sharedValue = useNormalizedSharedValue(animatedValue);
33
31
  const leftPosition = props?.leftPosition ?? BACK_WIDTH + 20;
34
32
  const headerRightWidth = props?.headerRightWidth ?? 73;
35
33
 
34
+ // iPhone Duo side bar holds back: the search shares the content width with the buttons only
35
+ let searchMaxWidth = layout.width - leftPosition - 12 - headerRightWidth;
36
+ if (layout.sideBar) {
37
+ searchMaxWidth = layout.contentWidth - Spacing.M * 2 - headerRightWidth;
38
+ }
39
+ if (layout.statusBarClearance) {
40
+ searchMaxWidth -=
41
+ layout.statusBarClearance.left + layout.statusBarClearance.right;
42
+ }
43
+
36
44
  const backgroundStyle = useAnimatedStyle(() => {
37
45
  const t = Math.min(Math.max(sharedValue.value / 100, 0), 1);
38
46
  return {
39
- backgroundColor: t === 0
40
- ? theme.colors.background.surface
41
- : theme.colors.background.default,
47
+ backgroundColor:
48
+ t === 0
49
+ ? theme.colors.background.surface
50
+ : theme.colors.background.default,
42
51
  };
43
52
  });
44
53
 
@@ -77,13 +86,11 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
77
86
  style={[
78
87
  Styles.flex,
79
88
  renderButtons && {
80
- maxWidth: screenWidth - leftPosition - 12 - headerRightWidth,
89
+ maxWidth: searchMaxWidth,
81
90
  },
82
91
  ]}
83
92
  >
84
- <Animated.View
85
- style={[{ borderRadius: Radius.XL }, backgroundStyle]}
86
- >
93
+ <Animated.View style={[{ borderRadius: Radius.XL }, backgroundStyle]}>
87
94
  <InputSearch
88
95
  ref={ref}
89
96
  {...props}
@@ -0,0 +1,139 @@
1
+ import React, { ReactNode, useContext } from 'react';
2
+ import {
3
+ StyleProp,
4
+ StyleSheet,
5
+ TouchableOpacity,
6
+ View,
7
+ ViewStyle,
8
+ } from 'react-native';
9
+ import { NavigationButtonProps } from '../types';
10
+ import { ApplicationContext } from '../../Context';
11
+ import { Colors, Radius, Shadow, Spacing } from '../../Consts';
12
+ import { Icon } from '../../Icon';
13
+ import { BadgeDot } from '../../Badge';
14
+ import { DuoMetrics } from '../../Layout/utils';
15
+
16
+ const SIDE_BAR_ICON_SIZE = 24;
17
+
18
+ /**
19
+ * glass material for side bar controls: dark glass under light icons (banner headers),
20
+ * light glass otherwise
21
+ */
22
+ const getGlassStyle = (tintColor?: string) =>
23
+ tintColor === Colors.black_01 || tintColor === 'white'
24
+ ? styles.glassDark
25
+ : styles.glassLight;
26
+
27
+ /**
28
+ * 48pt glass circle used for a single control in the iPhone Duo side bar
29
+ */
30
+ const SideBarButton: React.FC<NavigationButtonProps> = ({
31
+ icon,
32
+ tintColor,
33
+ showBadge = false,
34
+ style,
35
+ ...props
36
+ }) => {
37
+ const { theme } = useContext(ApplicationContext);
38
+
39
+ return (
40
+ <View>
41
+ <TouchableOpacity
42
+ {...props}
43
+ style={[styles.control, getGlassStyle(tintColor), style]}
44
+ >
45
+ <Icon
46
+ source={icon}
47
+ color={tintColor ?? theme.colors.text.default}
48
+ size={SIDE_BAR_ICON_SIZE}
49
+ />
50
+ </TouchableOpacity>
51
+ {showBadge && <BadgeDot size={'small'} style={styles.badgeDot} />}
52
+ </View>
53
+ );
54
+ };
55
+
56
+ /**
57
+ * 48pt glass capsule grouping related controls, one item per 50pt, laid out vertically
58
+ * in the side bar or horizontally on a header row
59
+ */
60
+ const SideBarGroup: React.FC<{
61
+ tintColor?: string;
62
+ horizontal?: boolean;
63
+ style?: StyleProp<ViewStyle>;
64
+ children: ReactNode;
65
+ }> = ({ tintColor, horizontal = false, style, children }) => {
66
+ const count = React.Children.toArray(children).length;
67
+ const length =
68
+ count > 1 ? count * DuoMetrics.itemPitch : DuoMetrics.controlSize;
69
+
70
+ return (
71
+ <View
72
+ style={[
73
+ styles.control,
74
+ getGlassStyle(tintColor),
75
+ horizontal
76
+ ? { width: length, flexDirection: 'row' }
77
+ : { height: length },
78
+ style,
79
+ ]}
80
+ >
81
+ {children}
82
+ </View>
83
+ );
84
+ };
85
+
86
+ /**
87
+ * icon item inside a SideBarGroup
88
+ */
89
+ const SideBarGroupItem: React.FC<NavigationButtonProps> = ({
90
+ icon,
91
+ tintColor,
92
+ ...props
93
+ }) => {
94
+ const { theme } = useContext(ApplicationContext);
95
+
96
+ return (
97
+ <TouchableOpacity {...props} style={styles.groupItem}>
98
+ <Icon
99
+ source={icon}
100
+ color={tintColor ?? theme.colors.text.default}
101
+ size={SIDE_BAR_ICON_SIZE}
102
+ />
103
+ </TouchableOpacity>
104
+ );
105
+ };
106
+
107
+ const styles = StyleSheet.create({
108
+ control: {
109
+ width: DuoMetrics.controlSize,
110
+ height: DuoMetrics.controlSize,
111
+ borderRadius: Radius.XL,
112
+ justifyContent: 'center',
113
+ alignItems: 'center',
114
+ },
115
+ glassLight: {
116
+ backgroundColor: Colors.black_01 + 'D9',
117
+ borderWidth: StyleSheet.hairlineWidth,
118
+ borderColor: Colors.black_20 + '14',
119
+ ...Shadow.Light,
120
+ },
121
+ glassDark: {
122
+ backgroundColor: Colors.black_20 + '80',
123
+ borderWidth: StyleSheet.hairlineWidth,
124
+ borderColor: Colors.black_01 + '33',
125
+ },
126
+ groupItem: {
127
+ flex: 1,
128
+ alignSelf: 'stretch',
129
+ justifyContent: 'center',
130
+ alignItems: 'center',
131
+ },
132
+ badgeDot: {
133
+ position: 'absolute',
134
+ top: Spacing.XXS,
135
+ right: Spacing.XXS,
136
+ },
137
+ });
138
+
139
+ export { SideBarButton, SideBarGroup, SideBarGroupItem };
@@ -8,15 +8,18 @@ import {
8
8
  Platform,
9
9
  Pressable,
10
10
  StyleSheet,
11
+ useWindowDimensions,
11
12
  View,
12
13
  } from 'react-native';
14
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
13
15
 
14
16
  import { ApplicationContext, MiniAppContext } from '../Context';
15
17
  import { Styles } from '../Consts';
16
18
  import Navigation from './Navigation';
17
19
  import { ModalParams } from './types';
18
20
  import BottomSheet from './BottomSheet';
19
- import { runOnJS } from 'react-native-reanimated';
21
+ import { getDuoTopInset, isIPhoneDuo } from './device';
22
+ import { scheduleOnRN } from 'react-native-worklets';
20
23
 
21
24
  const ModalScreen: React.FC<any> = props => {
22
25
  if (props.route?.params?.isBottomSheet) {
@@ -37,6 +40,8 @@ const Modal: React.FC<ModalParams> = props => {
37
40
  title,
38
41
  } = props.route.params;
39
42
  const Component = useRef(screen).current;
43
+ const insets = useSafeAreaInsets();
44
+ const windowSize = useWindowDimensions();
40
45
 
41
46
  /**
42
47
  * Fire auto_popup_displayed tracking event on mount
@@ -106,7 +111,7 @@ const Modal: React.FC<ModalParams> = props => {
106
111
  }),
107
112
  ]).start(() => {
108
113
  navigator?.pop();
109
- runOnJS(callback)();
114
+ scheduleOnRN(callback);
110
115
  });
111
116
  },
112
117
  [navigator, opacity, scale],
@@ -147,6 +152,15 @@ const Modal: React.FC<ModalParams> = props => {
147
152
  opacity,
148
153
  transform: [{ scale }],
149
154
  },
155
+ // center below the side status bar and above the home indicator
156
+ isIPhoneDuo() && {
157
+ marginTop: getDuoTopInset(
158
+ insets,
159
+ windowSize.width,
160
+ windowSize.height,
161
+ ),
162
+ marginBottom: insets.bottom,
163
+ },
150
164
  modalStyle,
151
165
  ]}
152
166
  >
@@ -2,7 +2,6 @@ import React, { useContext, useEffect, useRef, useState } from 'react';
2
2
  import {
3
3
  SafeAreaInsetsContext,
4
4
  SafeAreaProvider,
5
- useSafeAreaInsets,
6
5
  } from 'react-native-safe-area-context';
7
6
  import {
8
7
  NavigationContainer as ReactNavigationContainer,
@@ -13,17 +12,17 @@ import {
13
12
  StackNavigationOptions,
14
13
  TransitionPresets,
15
14
  } from '@react-navigation/stack';
16
- import { DeviceEventEmitter } from 'react-native';
17
15
  import StackScreen from './StackScreen';
18
16
  import ModalScreen from './ModalScreen';
19
17
  import Navigator from './Navigator';
20
- import { getModalOptions, getStackOptions, useNativeCanGoBack } from './utils';
18
+ import {
19
+ getModalOptions,
20
+ getStackOptions,
21
+ isWhiteListDesignSystem,
22
+ useNativeCanGoBack,
23
+ } from './utils';
21
24
  import { NavigationContainerProps } from './types';
22
25
  import { ApplicationContext, MiniAppContext } from '../Context';
23
- import {
24
- useDefaultRegularFontFamily,
25
- useResolvedFontFamily,
26
- } from '../Consts/designSystemFont';
27
26
  import Localize from './Localize';
28
27
  import { Colors, defaultTheme } from '../Consts';
29
28
  import { HeaderLeft } from './Components/HeaderLeft';
@@ -31,6 +30,9 @@ import { HeaderRight } from './Components/HeaderRight';
31
30
  import { HeaderTitle } from './Components/HeaderTitle';
32
31
  import { HeaderBackground } from './Components/HeaderBackground';
33
32
  import { type EdgeInsets } from 'react-native-safe-area-context';
33
+ import { useScreenLayout } from '../Layout';
34
+ import { getHeaderRowOptions } from '../Layout/utils';
35
+ import { ReservedRegionsProvider } from '../Layout/ReservedRegions';
34
36
 
35
37
  const Stack = createStackNavigator();
36
38
 
@@ -76,17 +78,19 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
76
78
  frame: { x: 0, y: 0, width: 0, height: 0 },
77
79
  }}
78
80
  >
79
- <MiniAppContext.Provider value={mergedContext}>
80
- <Navigation
81
- screen={screen}
82
- theme={theme}
83
- options={options}
84
- maxApi={maxApi}
85
- setCurrentContext={setCurrentContext}
86
- initialParams={initialParams}
87
- localize={localize}
88
- />
89
- </MiniAppContext.Provider>
81
+ <ReservedRegionsProvider>
82
+ <MiniAppContext.Provider value={mergedContext}>
83
+ <Navigation
84
+ screen={screen}
85
+ theme={theme}
86
+ options={options}
87
+ maxApi={maxApi}
88
+ setCurrentContext={setCurrentContext}
89
+ initialParams={initialParams}
90
+ localize={localize}
91
+ />
92
+ </MiniAppContext.Provider>
93
+ </ReservedRegionsProvider>
90
94
  </SafeAreaProvider>
91
95
  );
92
96
  };
@@ -104,11 +108,11 @@ const Navigation: React.FC<any> = ({
104
108
  const navigationRef = useRef<any>(null);
105
109
  const isReady = useRef(false);
106
110
  const navigator = useRef(new Navigator(navigationRef, isReady));
107
- const [showGrid, setShowGrid] = useState(false);
108
- const insets = useSafeAreaInsets();
109
- const defaultRegularFontFamily = useDefaultRegularFontFamily();
110
- const themeFont = useResolvedFontFamily(theme.font);
111
+ const headerRowOptions = getHeaderRowOptions(useScreenLayout());
111
112
  const syncCanGoBack = useNativeCanGoBack();
113
+ const isWhiteList = isWhiteListDesignSystem(context);
114
+ const fontFamily = isWhiteList ? 'MomoTrustSans' : 'SFProText';
115
+ const navigationFontFamily = isWhiteList ? 'MomoTrustSans-Regular' : 'SFProText';
112
116
  let headerBackground = context?.designSystemConfig?.config?.headerBar;
113
117
  let headerGradient = theme.colors?.gradient;
114
118
 
@@ -124,23 +128,6 @@ const Navigation: React.FC<any> = ({
124
128
  */
125
129
  navigator.current.maxApi = maxApi;
126
130
 
127
- /**
128
- * handle mini language & listen change
129
- * engine only shake to enable grid view
130
- */
131
- useEffect(() => {
132
- const subscription = DeviceEventEmitter.addListener(
133
- 'onChangeGrid',
134
- enable => {
135
- setShowGrid(!!enable);
136
- },
137
- );
138
-
139
- return () => {
140
- subscription?.remove?.();
141
- };
142
- }, []);
143
-
144
131
  /**
145
132
  * translate context method
146
133
  * @param data
@@ -160,7 +147,7 @@ const Navigation: React.FC<any> = ({
160
147
  navigator: navigator.current,
161
148
  theme: {
162
149
  ...theme,
163
- font: themeFont,
150
+ font: fontFamily,
164
151
  colors: {
165
152
  ...theme.colors,
166
153
  gradient: headerGradient,
@@ -170,7 +157,6 @@ const Navigation: React.FC<any> = ({
170
157
  headerBackground,
171
158
  },
172
159
  },
173
- showGrid,
174
160
  translate,
175
161
  }}
176
162
  >
@@ -188,19 +174,19 @@ const Navigation: React.FC<any> = ({
188
174
  },
189
175
  fonts: {
190
176
  regular: {
191
- fontFamily: defaultRegularFontFamily,
177
+ fontFamily: navigationFontFamily,
192
178
  fontWeight: '400',
193
179
  },
194
180
  medium: {
195
- fontFamily: defaultRegularFontFamily,
181
+ fontFamily: navigationFontFamily,
196
182
  fontWeight: '400',
197
183
  },
198
184
  bold: {
199
- fontFamily: defaultRegularFontFamily,
185
+ fontFamily: navigationFontFamily,
200
186
  fontWeight: '400',
201
187
  },
202
188
  heavy: {
203
- fontFamily: defaultRegularFontFamily,
189
+ fontFamily: navigationFontFamily,
204
190
  fontWeight: '400',
205
191
  },
206
192
  },
@@ -219,9 +205,7 @@ const Navigation: React.FC<any> = ({
219
205
  gestureEnabled: false,
220
206
  headerTitleAlign: 'center',
221
207
  headerTintColor: theme.colors.text.default,
222
- headerStyle: {
223
- height: 52 + insets.top,
224
- },
208
+ ...headerRowOptions,
225
209
  }}
226
210
  >
227
211
  <Stack.Screen
@@ -244,11 +228,13 @@ const Navigation: React.FC<any> = ({
244
228
  cardStyle: {
245
229
  backgroundColor: theme.colors.background.default,
246
230
  },
247
- headerLeft: (props: any) => <HeaderLeft {...props} />,
231
+ headerLeft: (props: any) => <HeaderLeft {...props} floating />,
248
232
  headerBackground: (props: any) => (
249
233
  <HeaderBackground {...props} />
250
234
  ),
251
- headerRight: (props: any) => <HeaderRight {...props} />,
235
+ headerRight: (props: any) => (
236
+ <HeaderRight {...props} floating />
237
+ ),
252
238
  headerTintColor: Colors.black_17,
253
239
  ...TransitionPresets.ModalTransition,
254
240
  }}
@@ -118,9 +118,7 @@ class Navigator {
118
118
  navigate = (name: string) => {
119
119
  if (this.isReady.current) {
120
120
  this.ref.current?.dispatch?.(
121
- CommonActions.navigate({
122
- name,
123
- })
121
+ CommonActions.navigate(name)
124
122
  );
125
123
  }
126
124
  };
@@ -1,12 +1,27 @@
1
- import React, { useContext, useEffect, useLayoutEffect, useRef } from 'react';
2
- import { Alert, InteractionManager } from 'react-native';
1
+ import React, {
2
+ useCallback,
3
+ useContext,
4
+ useEffect,
5
+ useLayoutEffect,
6
+ useRef,
7
+ useState,
8
+ } from 'react';
9
+ import {
10
+ Alert,
11
+ InteractionManager,
12
+ Platform,
13
+ StyleSheet,
14
+ View,
15
+ } from 'react-native';
16
+ import Animated from 'react-native-reanimated';
3
17
  import { useHeaderHeight } from '@react-navigation/elements';
4
18
  import { ScreenTrackingParams } from './types';
5
19
  import Navigation from './Navigation';
6
20
  import { ApplicationContext, MiniAppContext, ScreenContext } from '../Context';
7
- import { GridSystem } from '../Layout';
8
21
  import { version } from '../package.json';
9
- import { SwipeBackHaptic, useAppState } from './utils';
22
+ import { SwipeBackHaptic, useAppState, useHeaderScrollState } from './utils';
23
+ import { ScrollObserverView } from './Components/OptionalScrollObserver';
24
+ import { useScreenLayout } from '../Layout/utils';
10
25
  import { TooltipPortalHost, TooltipPortalProvider } from './TooltipPortal';
11
26
 
12
27
  const runAfterInteractions = InteractionManager.runAfterInteractions;
@@ -17,7 +32,7 @@ const runAfterInteractions = InteractionManager.runAfterInteractions;
17
32
  * @constructor
18
33
  */
19
34
  const StackScreen: React.FC<any> = props => {
20
- const { showGrid, navigator } = useContext(ApplicationContext);
35
+ const { navigator } = useContext(ApplicationContext);
21
36
  const tracking = useRef<any>({
22
37
  mounted: false,
23
38
  timeoutLoad: undefined,
@@ -42,6 +57,7 @@ const StackScreen: React.FC<any> = props => {
42
57
  });
43
58
  const widgets = useRef<any>([]);
44
59
  const context = useContext<any>(MiniAppContext);
60
+
45
61
  const {
46
62
  screen: Component,
47
63
  options,
@@ -51,6 +67,24 @@ const StackScreen: React.FC<any> = props => {
51
67
  const navigation = useRef(new Navigation(props.navigation, context)).current;
52
68
  const heightHeader = useHeaderHeight();
53
69
  const { isBackgroundToForeground } = useAppState();
70
+ const layout = useScreenLayout();
71
+
72
+ /**
73
+ * iPhone Duo: a screen without <Screen> keeps clear of the side bar and, with the
74
+ * native scroll observer, gets the quick return header, so mini apps need no change
75
+ */
76
+ const canAutoLayout = Platform.OS === 'ios' && !bottomTab;
77
+ const Observer: React.ComponentType<any> = ScrollObserverView ?? View;
78
+ const headerClaimed = useRef(false);
79
+ const [autoHeader, setAutoHeader] = useState(false);
80
+ const syncHeader = useCallback(() => {
81
+ setAutoHeader(
82
+ canAutoLayout && layout.isIPhoneDuo && !headerClaimed.current,
83
+ );
84
+ }, [canAutoLayout, layout.isIPhoneDuo]);
85
+ // parent effects run after the screen's, so a claim made on mount is already in
86
+ useEffect(syncHeader, [syncHeader]);
87
+ const headerScroll = useHeaderScrollState({ navigation }, autoHeader);
54
88
 
55
89
  const data = {
56
90
  ...initialParams,
@@ -407,18 +441,38 @@ const StackScreen: React.FC<any> = props => {
407
441
  onSetParams: (item: ScreenTrackingParams) => {
408
442
  tracking.current.params = item;
409
443
  },
444
+ claimHeader: () => {
445
+ headerClaimed.current = true;
446
+ },
447
+ syncHeader,
410
448
  }}
411
449
  >
412
450
  <TooltipPortalProvider>
413
- <Component heightHeader={heightHeader} {...data} />
414
- {showGrid && <GridSystem />}
451
+ {canAutoLayout ? (
452
+ // the tree stays the same whether auto layout is on, so the screen never remounts
453
+ <Observer
454
+ enabled={autoHeader}
455
+ pointerEvents={'box-none'}
456
+ style={styles.flex}
457
+ onScrollChange={headerScroll.onScrollChange}
458
+ >
459
+ {headerScroll.background}
460
+ <Animated.View style={[styles.flex, headerScroll.contentStyle]}>
461
+ <Component heightHeader={heightHeader} {...data} />
462
+ </Animated.View>
463
+ </Observer>
464
+ ) : (
465
+ <Component heightHeader={heightHeader} {...data} />
466
+ )}
415
467
  <TooltipPortalHost />
416
- {/* a leaf on purpose: it subscribes to CardAnimationContext, which changes on every
417
- Card render, and must not pull this screen into those renders */}
418
468
  <SwipeBackHaptic />
419
469
  </TooltipPortalProvider>
420
470
  </ScreenContext.Provider>
421
471
  );
422
472
  };
423
473
 
474
+ const styles = StyleSheet.create({
475
+ flex: { flex: 1 },
476
+ });
477
+
424
478
  export default StackScreen;
@@ -3,10 +3,6 @@ import { SafeAreaProvider } from 'react-native-safe-area-context';
3
3
  import Navigator from './Navigator';
4
4
  import { WidgetContainerProps } from './types';
5
5
  import { ApplicationContext, MiniAppContext } from '../Context';
6
- import {
7
- resolveFontFamily,
8
- useDefaultRegularFontFamily,
9
- } from '../Consts/designSystemFont';
10
6
  import Localize from './Localize';
11
7
  import { defaultTheme } from '../Consts';
12
8
  import {
@@ -14,7 +10,7 @@ import {
14
10
  NavigationContainerRef,
15
11
  NavigationIndependentTree,
16
12
  } from '@react-navigation/native';
17
- import { getModalOptions } from './utils';
13
+ import { getModalOptions, isWhiteListDesignSystem } from './utils';
18
14
  import { createStackNavigator } from '@react-navigation/stack';
19
15
  import ModalScreen from './ModalScreen';
20
16
  import { View } from 'react-native';
@@ -46,7 +42,6 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
46
42
  const isReady = useRef(false);
47
43
  const navigator = useRef(new Navigator(navigationRef, isReady, true));
48
44
  const [currentContext, setCurrentContext] = useState({});
49
- const defaultRegularFontFamily = useDefaultRegularFontFamily();
50
45
 
51
46
  useEffect(() => {
52
47
  const sub = maxApi?.observer?.('font_scale_config', (data: any) => {
@@ -83,6 +78,9 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
83
78
  ...context,
84
79
  ...currentContext,
85
80
  };
81
+ const isWhiteList = isWhiteListDesignSystem(mergedContext);
82
+ const fontFamily = isWhiteList ? 'MomoTrustSans' : 'SFProText';
83
+ const navigationFontFamily = isWhiteList ? 'MomoTrustSans-Regular' : 'SFProText';
86
84
 
87
85
  return (
88
86
  <View style={{ height: height ?? '100%', minHeight: 1 }}>
@@ -93,11 +91,7 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
93
91
  navigator: navigator.current,
94
92
  theme: {
95
93
  ...theme,
96
- font: resolveFontFamily(
97
- theme.font,
98
- mergedContext?.designSystemConfig?.isWhiteListDesignSystem ===
99
- true,
100
- ),
94
+ font: fontFamily,
101
95
  colors: {
102
96
  ...theme.colors,
103
97
  gradient: headerGradient,
@@ -124,19 +118,19 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
124
118
  },
125
119
  fonts: {
126
120
  regular: {
127
- fontFamily: defaultRegularFontFamily,
121
+ fontFamily: navigationFontFamily,
128
122
  fontWeight: '400',
129
123
  },
130
124
  medium: {
131
- fontFamily: defaultRegularFontFamily,
125
+ fontFamily: navigationFontFamily,
132
126
  fontWeight: '400',
133
127
  },
134
128
  bold: {
135
- fontFamily: defaultRegularFontFamily,
129
+ fontFamily: navigationFontFamily,
136
130
  fontWeight: '400',
137
131
  },
138
132
  heavy: {
139
- fontFamily: defaultRegularFontFamily,
133
+ fontFamily: navigationFontFamily,
140
134
  fontWeight: '400',
141
135
  },
142
136
  },
@@ -0,0 +1,29 @@
1
+ import { Dimensions, Platform, ScaledSize } from 'react-native';
2
+ import type { EdgeInsets } from 'react-native-safe-area-context';
3
+
4
+ // widest short side of any single-display iPhone
5
+ const IPHONE_MAX_SHORT_SIDE = 440;
6
+
7
+ // iPhone Duo reports idiom "phone"; 'screen' stays on the outer display, so it
8
+ // still matches when Split View narrows the window
9
+ const isIPhoneDuo = () => {
10
+ if (Platform.OS !== 'ios' || Platform.isPad) {
11
+ return false;
12
+ }
13
+ const shortSide = (size: ScaledSize) => Math.min(size.width, size.height);
14
+ return (
15
+ Math.max(
16
+ shortSide(Dimensions.get('screen')),
17
+ shortSide(Dimensions.get('window')),
18
+ ) > IPHONE_MAX_SHORT_SIDE
19
+ );
20
+ };
21
+
22
+ // portrait side status bar is about twice as tall as its inset is wide
23
+ const getDuoTopInset = (insets: EdgeInsets, width: number, height: number) =>
24
+ Math.max(
25
+ insets.top,
26
+ height > width ? Math.max(insets.left, insets.right) * 2 : 0,
27
+ );
28
+
29
+ export { isIPhoneDuo, getDuoTopInset };