@momo-kits/foundation 0.167.1-beta.5 → 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 +67 -17
  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 -45
  18. package/Application/Navigator.ts +1 -3
  19. package/Application/StackScreen.tsx +63 -9
  20. package/Application/WidgetContainer.tsx +9 -7
  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 +5 -6
  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 +2 -11
  44. package/Text/utils.ts +1 -2
  45. package/package.json +3 -2
  46. package/Consts/designSystemFont.ts +0 -38
  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,14 +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 { useDefaultRegularFontFamily } from '../Consts/designSystemFont';
24
26
  import Localize from './Localize';
25
27
  import { Colors, defaultTheme } from '../Consts';
26
28
  import { HeaderLeft } from './Components/HeaderLeft';
@@ -28,6 +30,9 @@ import { HeaderRight } from './Components/HeaderRight';
28
30
  import { HeaderTitle } from './Components/HeaderTitle';
29
31
  import { HeaderBackground } from './Components/HeaderBackground';
30
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';
31
36
 
32
37
  const Stack = createStackNavigator();
33
38
 
@@ -73,17 +78,19 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
73
78
  frame: { x: 0, y: 0, width: 0, height: 0 },
74
79
  }}
75
80
  >
76
- <MiniAppContext.Provider value={mergedContext}>
77
- <Navigation
78
- screen={screen}
79
- theme={theme}
80
- options={options}
81
- maxApi={maxApi}
82
- setCurrentContext={setCurrentContext}
83
- initialParams={initialParams}
84
- localize={localize}
85
- />
86
- </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>
87
94
  </SafeAreaProvider>
88
95
  );
89
96
  };
@@ -101,10 +108,11 @@ const Navigation: React.FC<any> = ({
101
108
  const navigationRef = useRef<any>(null);
102
109
  const isReady = useRef(false);
103
110
  const navigator = useRef(new Navigator(navigationRef, isReady));
104
- const [showGrid, setShowGrid] = useState(false);
105
- const insets = useSafeAreaInsets();
106
- const defaultRegularFontFamily = useDefaultRegularFontFamily();
111
+ const headerRowOptions = getHeaderRowOptions(useScreenLayout());
107
112
  const syncCanGoBack = useNativeCanGoBack();
113
+ const isWhiteList = isWhiteListDesignSystem(context);
114
+ const fontFamily = isWhiteList ? 'MomoTrustSans' : 'SFProText';
115
+ const navigationFontFamily = isWhiteList ? 'MomoTrustSans-Regular' : 'SFProText';
108
116
  let headerBackground = context?.designSystemConfig?.config?.headerBar;
109
117
  let headerGradient = theme.colors?.gradient;
110
118
 
@@ -120,23 +128,6 @@ const Navigation: React.FC<any> = ({
120
128
  */
121
129
  navigator.current.maxApi = maxApi;
122
130
 
123
- /**
124
- * handle mini language & listen change
125
- * engine only shake to enable grid view
126
- */
127
- useEffect(() => {
128
- const subscription = DeviceEventEmitter.addListener(
129
- 'onChangeGrid',
130
- enable => {
131
- setShowGrid(!!enable);
132
- },
133
- );
134
-
135
- return () => {
136
- subscription?.remove?.();
137
- };
138
- }, []);
139
-
140
131
  /**
141
132
  * translate context method
142
133
  * @param data
@@ -156,6 +147,7 @@ const Navigation: React.FC<any> = ({
156
147
  navigator: navigator.current,
157
148
  theme: {
158
149
  ...theme,
150
+ font: fontFamily,
159
151
  colors: {
160
152
  ...theme.colors,
161
153
  gradient: headerGradient,
@@ -165,7 +157,6 @@ const Navigation: React.FC<any> = ({
165
157
  headerBackground,
166
158
  },
167
159
  },
168
- showGrid,
169
160
  translate,
170
161
  }}
171
162
  >
@@ -183,19 +174,19 @@ const Navigation: React.FC<any> = ({
183
174
  },
184
175
  fonts: {
185
176
  regular: {
186
- fontFamily: defaultRegularFontFamily,
177
+ fontFamily: navigationFontFamily,
187
178
  fontWeight: '400',
188
179
  },
189
180
  medium: {
190
- fontFamily: defaultRegularFontFamily,
181
+ fontFamily: navigationFontFamily,
191
182
  fontWeight: '400',
192
183
  },
193
184
  bold: {
194
- fontFamily: defaultRegularFontFamily,
185
+ fontFamily: navigationFontFamily,
195
186
  fontWeight: '400',
196
187
  },
197
188
  heavy: {
198
- fontFamily: defaultRegularFontFamily,
189
+ fontFamily: navigationFontFamily,
199
190
  fontWeight: '400',
200
191
  },
201
192
  },
@@ -214,9 +205,7 @@ const Navigation: React.FC<any> = ({
214
205
  gestureEnabled: false,
215
206
  headerTitleAlign: 'center',
216
207
  headerTintColor: theme.colors.text.default,
217
- headerStyle: {
218
- height: 52 + insets.top,
219
- },
208
+ ...headerRowOptions,
220
209
  }}
221
210
  >
222
211
  <Stack.Screen
@@ -239,11 +228,13 @@ const Navigation: React.FC<any> = ({
239
228
  cardStyle: {
240
229
  backgroundColor: theme.colors.background.default,
241
230
  },
242
- headerLeft: (props: any) => <HeaderLeft {...props} />,
231
+ headerLeft: (props: any) => <HeaderLeft {...props} floating />,
243
232
  headerBackground: (props: any) => (
244
233
  <HeaderBackground {...props} />
245
234
  ),
246
- headerRight: (props: any) => <HeaderRight {...props} />,
235
+ headerRight: (props: any) => (
236
+ <HeaderRight {...props} floating />
237
+ ),
247
238
  headerTintColor: Colors.black_17,
248
239
  ...TransitionPresets.ModalTransition,
249
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,7 +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 { useDefaultRegularFontFamily } from '../Consts/designSystemFont';
7
6
  import Localize from './Localize';
8
7
  import { defaultTheme } from '../Consts';
9
8
  import {
@@ -11,7 +10,7 @@ import {
11
10
  NavigationContainerRef,
12
11
  NavigationIndependentTree,
13
12
  } from '@react-navigation/native';
14
- import { getModalOptions } from './utils';
13
+ import { getModalOptions, isWhiteListDesignSystem } from './utils';
15
14
  import { createStackNavigator } from '@react-navigation/stack';
16
15
  import ModalScreen from './ModalScreen';
17
16
  import { View } from 'react-native';
@@ -43,7 +42,6 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
43
42
  const isReady = useRef(false);
44
43
  const navigator = useRef(new Navigator(navigationRef, isReady, true));
45
44
  const [currentContext, setCurrentContext] = useState({});
46
- const defaultRegularFontFamily = useDefaultRegularFontFamily();
47
45
 
48
46
  useEffect(() => {
49
47
  const sub = maxApi?.observer?.('font_scale_config', (data: any) => {
@@ -80,6 +78,9 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
80
78
  ...context,
81
79
  ...currentContext,
82
80
  };
81
+ const isWhiteList = isWhiteListDesignSystem(mergedContext);
82
+ const fontFamily = isWhiteList ? 'MomoTrustSans' : 'SFProText';
83
+ const navigationFontFamily = isWhiteList ? 'MomoTrustSans-Regular' : 'SFProText';
83
84
 
84
85
  return (
85
86
  <View style={{ height: height ?? '100%', minHeight: 1 }}>
@@ -90,6 +91,7 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
90
91
  navigator: navigator.current,
91
92
  theme: {
92
93
  ...theme,
94
+ font: fontFamily,
93
95
  colors: {
94
96
  ...theme.colors,
95
97
  gradient: headerGradient,
@@ -116,19 +118,19 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
116
118
  },
117
119
  fonts: {
118
120
  regular: {
119
- fontFamily: defaultRegularFontFamily,
121
+ fontFamily: navigationFontFamily,
120
122
  fontWeight: '400',
121
123
  },
122
124
  medium: {
123
- fontFamily: defaultRegularFontFamily,
125
+ fontFamily: navigationFontFamily,
124
126
  fontWeight: '400',
125
127
  },
126
128
  bold: {
127
- fontFamily: defaultRegularFontFamily,
129
+ fontFamily: navigationFontFamily,
128
130
  fontWeight: '400',
129
131
  },
130
132
  heavy: {
131
- fontFamily: defaultRegularFontFamily,
133
+ fontFamily: navigationFontFamily,
132
134
  fontWeight: '400',
133
135
  },
134
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 };