@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.
- package/Application/BottomSheet.tsx +115 -14
- package/Application/BottomTab/BottomTabBar.tsx +6 -3
- package/Application/BottomTab/DuoBarCompression.tsx +135 -0
- package/Application/BottomTab/DuoTabBar.tsx +462 -0
- package/Application/BottomTab/index.tsx +150 -134
- package/Application/Components/DuoGlassBackground.tsx +179 -0
- package/Application/Components/DuoQuickReturnHeader.tsx +42 -0
- package/Application/Components/HeaderExtendHeader.tsx +75 -28
- package/Application/Components/HeaderLeft.tsx +38 -0
- package/Application/Components/HeaderRight.tsx +142 -9
- package/Application/Components/HeaderTitle.tsx +67 -17
- package/Application/Components/OptionalGlassView.ts +11 -0
- package/Application/Components/OptionalScrollObserver.ts +12 -0
- package/Application/Components/SearchHeader.tsx +22 -15
- package/Application/Components/SideBarButton.tsx +139 -0
- package/Application/ModalScreen.tsx +16 -2
- package/Application/NavigationContainer.tsx +36 -45
- package/Application/Navigator.ts +1 -3
- package/Application/StackScreen.tsx +63 -9
- package/Application/WidgetContainer.tsx +9 -7
- package/Application/device.ts +29 -0
- package/Application/types.ts +27 -1
- package/Application/utils.tsx +255 -7
- package/Consts/index.ts +0 -1
- package/Consts/theme.ts +5 -6
- package/Input/Input.tsx +3 -4
- package/Input/InputMoney.tsx +31 -51
- package/Input/InputPhoneNumber.tsx +3 -4
- package/Input/InputSearch.tsx +3 -4
- package/Input/InputTextArea.tsx +3 -4
- package/Layout/Card.tsx +8 -26
- package/Layout/FloatingButton.tsx +3 -3
- package/Layout/Item.tsx +1 -10
- package/Layout/ReservedRegions.tsx +47 -0
- package/Layout/Screen.tsx +199 -52
- package/Layout/Section.tsx +5 -18
- package/Layout/index.ts +4 -4
- package/Layout/styles.ts +5 -0
- package/Layout/utils.ts +465 -5
- package/Popup/PopupNotify.tsx +33 -22
- package/Popup/PopupPromotion.tsx +20 -8
- package/Popup/utils.ts +32 -0
- package/Text/index.tsx +2 -11
- package/Text/utils.ts +1 -2
- package/package.json +3 -2
- package/Consts/designSystemFont.ts +0 -38
- 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
|
|
30
|
-
|
|
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:
|
|
40
|
-
|
|
41
|
-
|
|
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:
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
<
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
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:
|
|
177
|
+
fontFamily: navigationFontFamily,
|
|
187
178
|
fontWeight: '400',
|
|
188
179
|
},
|
|
189
180
|
medium: {
|
|
190
|
-
fontFamily:
|
|
181
|
+
fontFamily: navigationFontFamily,
|
|
191
182
|
fontWeight: '400',
|
|
192
183
|
},
|
|
193
184
|
bold: {
|
|
194
|
-
fontFamily:
|
|
185
|
+
fontFamily: navigationFontFamily,
|
|
195
186
|
fontWeight: '400',
|
|
196
187
|
},
|
|
197
188
|
heavy: {
|
|
198
|
-
fontFamily:
|
|
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
|
-
|
|
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) =>
|
|
235
|
+
headerRight: (props: any) => (
|
|
236
|
+
<HeaderRight {...props} floating />
|
|
237
|
+
),
|
|
247
238
|
headerTintColor: Colors.black_17,
|
|
248
239
|
...TransitionPresets.ModalTransition,
|
|
249
240
|
}}
|
package/Application/Navigator.ts
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
|
|
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 {
|
|
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
|
-
|
|
414
|
-
|
|
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:
|
|
121
|
+
fontFamily: navigationFontFamily,
|
|
120
122
|
fontWeight: '400',
|
|
121
123
|
},
|
|
122
124
|
medium: {
|
|
123
|
-
fontFamily:
|
|
125
|
+
fontFamily: navigationFontFamily,
|
|
124
126
|
fontWeight: '400',
|
|
125
127
|
},
|
|
126
128
|
bold: {
|
|
127
|
-
fontFamily:
|
|
129
|
+
fontFamily: navigationFontFamily,
|
|
128
130
|
fontWeight: '400',
|
|
129
131
|
},
|
|
130
132
|
heavy: {
|
|
131
|
-
fontFamily:
|
|
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 };
|