@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.
- 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 +61 -21
- 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 -50
- package/Application/Navigator.ts +1 -3
- package/Application/StackScreen.tsx +63 -9
- package/Application/WidgetContainer.tsx +9 -15
- 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 +3 -4
- 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 +5 -15
- package/Text/utils.ts +1 -2
- package/package.json +3 -2
- package/Consts/designSystemFont.ts +0 -25
- 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,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 {
|
|
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
|
-
<
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
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:
|
|
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:
|
|
177
|
+
fontFamily: navigationFontFamily,
|
|
192
178
|
fontWeight: '400',
|
|
193
179
|
},
|
|
194
180
|
medium: {
|
|
195
|
-
fontFamily:
|
|
181
|
+
fontFamily: navigationFontFamily,
|
|
196
182
|
fontWeight: '400',
|
|
197
183
|
},
|
|
198
184
|
bold: {
|
|
199
|
-
fontFamily:
|
|
185
|
+
fontFamily: navigationFontFamily,
|
|
200
186
|
fontWeight: '400',
|
|
201
187
|
},
|
|
202
188
|
heavy: {
|
|
203
|
-
fontFamily:
|
|
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
|
-
|
|
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) =>
|
|
235
|
+
headerRight: (props: any) => (
|
|
236
|
+
<HeaderRight {...props} floating />
|
|
237
|
+
),
|
|
252
238
|
headerTintColor: Colors.black_17,
|
|
253
239
|
...TransitionPresets.ModalTransition,
|
|
254
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,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:
|
|
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:
|
|
121
|
+
fontFamily: navigationFontFamily,
|
|
128
122
|
fontWeight: '400',
|
|
129
123
|
},
|
|
130
124
|
medium: {
|
|
131
|
-
fontFamily:
|
|
125
|
+
fontFamily: navigationFontFamily,
|
|
132
126
|
fontWeight: '400',
|
|
133
127
|
},
|
|
134
128
|
bold: {
|
|
135
|
-
fontFamily:
|
|
129
|
+
fontFamily: navigationFontFamily,
|
|
136
130
|
fontWeight: '400',
|
|
137
131
|
},
|
|
138
132
|
heavy: {
|
|
139
|
-
fontFamily:
|
|
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 };
|