@momo-kits/foundation 0.161.2-test.1 → 0.162.1-beta.2
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 +0 -2
- package/Application/BottomTab/index.tsx +0 -2
- package/Application/Components/HeaderAnimated.tsx +5 -8
- package/Application/Components/HeaderBackground.tsx +4 -4
- package/Application/Components/HeaderExtendHeader.tsx +14 -13
- package/Application/Components/HeaderLeft.tsx +1 -1
- package/Application/Components/HeaderTitle.tsx +9 -5
- package/Application/Components/SearchHeader.tsx +4 -7
- package/Application/NavigationContainer.tsx +44 -2
- package/Application/ScaleSizeProvider.tsx +5 -3
- package/Application/WidgetContainer.tsx +1 -1
- package/Application/types.ts +6 -4
- package/Application/utils.tsx +40 -1
- package/Context/index.ts +6 -3
- package/Layout/FloatingButton.tsx +10 -5
- package/Layout/Screen.tsx +111 -67
- package/Text/utils.ts +10 -5
- package/package.json +1 -1
|
@@ -37,10 +37,8 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
|
|
|
37
37
|
const action = useRef<undefined | string>(undefined);
|
|
38
38
|
const insets = useSafeAreaInsets();
|
|
39
39
|
const heightHeader = useHeaderHeight();
|
|
40
|
-
// AI-GENERATED START: iOS caps at 21pt (home indicator), Android uses full nav bar height
|
|
41
40
|
const bottomInset = Platform.OS === 'ios' ? Math.min(insets.bottom, 21) : insets.bottom;
|
|
42
41
|
const keyboardOffset = heightHeader - bottomInset;
|
|
43
|
-
// AI-GENERATED END
|
|
44
42
|
|
|
45
43
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
46
44
|
|
|
@@ -296,9 +296,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
|
|
|
296
296
|
activeTintColor={theme.colors.primary}
|
|
297
297
|
style={[
|
|
298
298
|
{
|
|
299
|
-
// AI-GENERATED START: iOS caps at 21pt (home indicator), Android uses full nav bar height
|
|
300
299
|
height: 64 + (Platform.OS === 'ios' ? Math.min(insets.bottom, 21) : insets.bottom),
|
|
301
|
-
// AI-GENERATED END
|
|
302
300
|
},
|
|
303
301
|
]}
|
|
304
302
|
/>
|
|
@@ -8,6 +8,7 @@ import { type HeaderAnimatedProps } from '../types';
|
|
|
8
8
|
import React from 'react';
|
|
9
9
|
import { Image } from '../../Image';
|
|
10
10
|
import { Colors } from '../../Consts';
|
|
11
|
+
import { useNormalizedSharedValue } from '../utils';
|
|
11
12
|
|
|
12
13
|
const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
|
|
13
14
|
animatedValue,
|
|
@@ -15,23 +16,19 @@ const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
|
|
|
15
16
|
useScale = true,
|
|
16
17
|
children,
|
|
17
18
|
}) => {
|
|
19
|
+
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
18
20
|
const animatedStyle = useAnimatedStyle(() => {
|
|
19
21
|
const scale = useScale
|
|
20
|
-
? interpolate(
|
|
21
|
-
animatedValue.value,
|
|
22
|
-
[-300, 0, 300],
|
|
23
|
-
[4, 1, 1],
|
|
24
|
-
Extrapolation.CLAMP,
|
|
25
|
-
)
|
|
22
|
+
? interpolate(sharedValue.value, [-300, 0, 300], [4, 1, 1], Extrapolation.CLAMP)
|
|
26
23
|
: 1;
|
|
27
24
|
const opacity = interpolate(
|
|
28
|
-
|
|
25
|
+
sharedValue.value,
|
|
29
26
|
[0, 150, 300],
|
|
30
27
|
[1, 0.5, 0],
|
|
31
28
|
Extrapolation.CLAMP,
|
|
32
29
|
);
|
|
33
30
|
const translateY = interpolate(
|
|
34
|
-
|
|
31
|
+
sharedValue.value,
|
|
35
32
|
[-300, 0],
|
|
36
33
|
[-80, -2],
|
|
37
34
|
Extrapolation.CLAMP,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import LinearGradient from 'react-native-linear-gradient';
|
|
2
2
|
import { StyleSheet, View } from 'react-native';
|
|
3
3
|
import React, { useContext } from 'react';
|
|
4
|
-
import Animated
|
|
4
|
+
import Animated from 'react-native-reanimated';
|
|
5
5
|
import { HeaderBackgroundProps } from '../types';
|
|
6
6
|
import { ApplicationContext, MiniAppContext } from '../../Context';
|
|
7
7
|
import { Colors, Styles } from '../../Consts';
|
|
8
8
|
import { Image } from '../../Image';
|
|
9
9
|
import BackgroundImageView from './BackgroundImageView';
|
|
10
|
+
import { useNormalizedSharedValue } from '../utils';
|
|
10
11
|
|
|
11
12
|
const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
|
|
12
13
|
animatedValue,
|
|
@@ -19,8 +20,7 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
|
|
|
19
20
|
const context = useContext<any>(MiniAppContext);
|
|
20
21
|
const gradientColor = customGradientColor ?? theme.colors.gradient;
|
|
21
22
|
const headerImage = headerBackground ?? theme.assets?.headerBackground;
|
|
22
|
-
const
|
|
23
|
-
const sv = animatedValue ?? fallback;
|
|
23
|
+
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
24
24
|
|
|
25
25
|
const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
|
|
26
26
|
|
|
@@ -29,7 +29,7 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
|
|
|
29
29
|
<BackgroundImageView
|
|
30
30
|
useShadowHeader={useShadowHeader}
|
|
31
31
|
heightHeader={'100%'}
|
|
32
|
-
animatedValue={
|
|
32
|
+
animatedValue={sharedValue}
|
|
33
33
|
headerBackground={headerImage}
|
|
34
34
|
/>
|
|
35
35
|
<View style={styles.gradientContainer}>
|
|
@@ -5,8 +5,6 @@ import Animated, {
|
|
|
5
5
|
Extrapolation,
|
|
6
6
|
interpolate,
|
|
7
7
|
useAnimatedStyle,
|
|
8
|
-
useSharedValue,
|
|
9
|
-
type SharedValue,
|
|
10
8
|
} from 'react-native-reanimated';
|
|
11
9
|
import { ApplicationContext, MiniAppContext } from '../../Context';
|
|
12
10
|
import { HeaderType } from '../../Layout/types';
|
|
@@ -16,6 +14,10 @@ import { Colors, Radius, Spacing } from '../../Consts';
|
|
|
16
14
|
import { Image } from '../../Image';
|
|
17
15
|
import { SearchHeaderProps } from '../types';
|
|
18
16
|
import BackgroundImageView from './BackgroundImageView';
|
|
17
|
+
import {
|
|
18
|
+
AnimatedCompatValue,
|
|
19
|
+
useNormalizedSharedValue,
|
|
20
|
+
} from '../utils';
|
|
19
21
|
|
|
20
22
|
const SCREEN_PADDING = 12;
|
|
21
23
|
const BACK_WIDTH = 28;
|
|
@@ -24,7 +26,7 @@ const { width: screenWidth } = Dimensions.get('window');
|
|
|
24
26
|
|
|
25
27
|
const HeaderExtendHeader: React.FC<{
|
|
26
28
|
headerType?: HeaderType;
|
|
27
|
-
animatedValue:
|
|
29
|
+
animatedValue: AnimatedCompatValue;
|
|
28
30
|
heightHeader: number;
|
|
29
31
|
headerRightWidth: number;
|
|
30
32
|
inputSearchProps?: SearchHeaderProps;
|
|
@@ -46,8 +48,7 @@ const HeaderExtendHeader: React.FC<{
|
|
|
46
48
|
}) => {
|
|
47
49
|
const { theme } = useContext(ApplicationContext);
|
|
48
50
|
const context = useContext<any>(MiniAppContext);
|
|
49
|
-
const
|
|
50
|
-
const sv = animatedValue ?? fallback;
|
|
51
|
+
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
51
52
|
const gradientColor = customGradientColor ?? theme.colors.gradient;
|
|
52
53
|
const headerBackground = customBackground ?? theme.assets?.headerBackground;
|
|
53
54
|
const leftPosition = inputSearchProps?.leftPosition || BACK_WIDTH + 20;
|
|
@@ -61,7 +62,7 @@ const HeaderExtendHeader: React.FC<{
|
|
|
61
62
|
|
|
62
63
|
const heightStyle = useAnimatedStyle(() => ({
|
|
63
64
|
height: interpolate(
|
|
64
|
-
|
|
65
|
+
sharedValue.value,
|
|
65
66
|
[0, 100],
|
|
66
67
|
[heightHeader + 52, heightHeader],
|
|
67
68
|
Extrapolation.CLAMP,
|
|
@@ -69,14 +70,14 @@ const HeaderExtendHeader: React.FC<{
|
|
|
69
70
|
}));
|
|
70
71
|
|
|
71
72
|
const gradientOpacityStyle = useAnimatedStyle(() => ({
|
|
72
|
-
opacity: interpolate(
|
|
73
|
+
opacity: interpolate(sharedValue.value, [0, 52], [1, 0], Extrapolation.CLAMP),
|
|
73
74
|
}));
|
|
74
75
|
|
|
75
76
|
const searchTranslateStyle = useAnimatedStyle(() => ({
|
|
76
77
|
transform: [
|
|
77
78
|
{
|
|
78
79
|
translateX: interpolate(
|
|
79
|
-
|
|
80
|
+
sharedValue.value,
|
|
80
81
|
[0, 100],
|
|
81
82
|
[SCREEN_PADDING, leftPosition],
|
|
82
83
|
Extrapolation.CLAMP,
|
|
@@ -84,7 +85,7 @@ const HeaderExtendHeader: React.FC<{
|
|
|
84
85
|
},
|
|
85
86
|
],
|
|
86
87
|
width: interpolate(
|
|
87
|
-
|
|
88
|
+
sharedValue.value,
|
|
88
89
|
[0, 100],
|
|
89
90
|
[
|
|
90
91
|
screenWidth - SCREEN_PADDING * 2,
|
|
@@ -95,7 +96,7 @@ const HeaderExtendHeader: React.FC<{
|
|
|
95
96
|
}));
|
|
96
97
|
|
|
97
98
|
const searchBackgroundStyle = useAnimatedStyle(() => {
|
|
98
|
-
const t = Math.min(Math.max(
|
|
99
|
+
const t = Math.min(Math.max(sharedValue.value / 100, 0), 1);
|
|
99
100
|
return { backgroundColor: t === 0
|
|
100
101
|
? theme.colors.background.surface
|
|
101
102
|
: theme.colors.background.default };
|
|
@@ -103,7 +104,7 @@ const HeaderExtendHeader: React.FC<{
|
|
|
103
104
|
|
|
104
105
|
const extendedHeightStyle = useAnimatedStyle(() => ({
|
|
105
106
|
height: interpolate(
|
|
106
|
-
|
|
107
|
+
sharedValue.value,
|
|
107
108
|
[0, 100],
|
|
108
109
|
[heightHeader + 52, heightHeader],
|
|
109
110
|
Extrapolation.CLAMP,
|
|
@@ -117,7 +118,7 @@ const HeaderExtendHeader: React.FC<{
|
|
|
117
118
|
<BackgroundImageView
|
|
118
119
|
useShadowHeader={useShadowHeader}
|
|
119
120
|
heightHeader={heightHeader}
|
|
120
|
-
animatedValue={
|
|
121
|
+
animatedValue={sharedValue}
|
|
121
122
|
headerBackground={headerBackground}
|
|
122
123
|
/>
|
|
123
124
|
<Animated.View
|
|
@@ -185,7 +186,7 @@ const HeaderExtendHeader: React.FC<{
|
|
|
185
186
|
<BackgroundImageView
|
|
186
187
|
useShadowHeader={useShadowHeader}
|
|
187
188
|
heightHeader={heightHeader}
|
|
188
|
-
animatedValue={
|
|
189
|
+
animatedValue={sharedValue}
|
|
189
190
|
headerBackground={headerBackground}
|
|
190
191
|
/>
|
|
191
192
|
{!!gradientColor && (
|
|
@@ -9,7 +9,6 @@ import Animated, {
|
|
|
9
9
|
Extrapolation,
|
|
10
10
|
interpolate,
|
|
11
11
|
useAnimatedStyle,
|
|
12
|
-
type SharedValue,
|
|
13
12
|
} from 'react-native-reanimated';
|
|
14
13
|
import { ApplicationContext, MiniAppContext } from '../../Context';
|
|
15
14
|
import { exportFontFamily, Text, useScaleSize } from '../../Text';
|
|
@@ -22,6 +21,10 @@ import {
|
|
|
22
21
|
import { Image } from '../../Image';
|
|
23
22
|
import { Icon } from '../../Icon';
|
|
24
23
|
import { Skeleton } from '../../Skeleton';
|
|
24
|
+
import {
|
|
25
|
+
AnimatedCompatValue,
|
|
26
|
+
useNormalizedSharedValue,
|
|
27
|
+
} from '../utils';
|
|
25
28
|
|
|
26
29
|
type HeaderTitleInterpolate = {
|
|
27
30
|
inputRange: number[];
|
|
@@ -29,7 +32,7 @@ type HeaderTitleInterpolate = {
|
|
|
29
32
|
};
|
|
30
33
|
|
|
31
34
|
type HeaderTitleExtraProps = {
|
|
32
|
-
animatedValue?:
|
|
35
|
+
animatedValue?: AnimatedCompatValue;
|
|
33
36
|
interpolate?: HeaderTitleInterpolate;
|
|
34
37
|
tintColor?: string;
|
|
35
38
|
children?: React.ReactNode;
|
|
@@ -47,15 +50,16 @@ const HeaderTitle: React.FC<HeaderTitleExtraProps & { [key: string]: any }> = pr
|
|
|
47
50
|
inputRange: [0, 200],
|
|
48
51
|
outputRange: [0, 1],
|
|
49
52
|
};
|
|
50
|
-
const
|
|
53
|
+
const hasAnimated = props.animatedValue != null;
|
|
54
|
+
const sharedValue = useNormalizedSharedValue(props.animatedValue);
|
|
51
55
|
|
|
52
56
|
const animatedStyle = useAnimatedStyle(() => {
|
|
53
|
-
if (!
|
|
57
|
+
if (!hasAnimated) {
|
|
54
58
|
return { opacity: 1 };
|
|
55
59
|
}
|
|
56
60
|
return {
|
|
57
61
|
opacity: interpolate(
|
|
58
|
-
|
|
62
|
+
sharedValue.value,
|
|
59
63
|
interpolateConfig.inputRange,
|
|
60
64
|
interpolateConfig.outputRange,
|
|
61
65
|
Extrapolation.CLAMP,
|
|
@@ -6,14 +6,12 @@ import {
|
|
|
6
6
|
TouchableOpacity,
|
|
7
7
|
View,
|
|
8
8
|
} from 'react-native';
|
|
9
|
-
import Animated, {
|
|
10
|
-
useAnimatedStyle,
|
|
11
|
-
useSharedValue,
|
|
12
|
-
} from 'react-native-reanimated';
|
|
9
|
+
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
|
|
13
10
|
import React, { useContext } from 'react';
|
|
14
11
|
import { SearchHeaderProps } from '../types';
|
|
15
12
|
import { ApplicationContext, MiniAppContext } from '../../Context';
|
|
16
13
|
import { Text } from '../../Text';
|
|
14
|
+
import { useNormalizedSharedValue } from '../utils';
|
|
17
15
|
|
|
18
16
|
const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
19
17
|
(
|
|
@@ -31,13 +29,12 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
|
|
|
31
29
|
const BACK_WIDTH = 28;
|
|
32
30
|
const { width: screenWidth } = Dimensions.get('window');
|
|
33
31
|
|
|
34
|
-
const
|
|
35
|
-
const sv = animatedValue ?? fallback;
|
|
32
|
+
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
36
33
|
const leftPosition = props?.leftPosition ?? BACK_WIDTH + 20;
|
|
37
34
|
const headerRightWidth = props?.headerRightWidth ?? 73;
|
|
38
35
|
|
|
39
36
|
const backgroundStyle = useAnimatedStyle(() => {
|
|
40
|
-
const t = Math.min(Math.max(
|
|
37
|
+
const t = Math.min(Math.max(sharedValue.value / 100, 0), 1);
|
|
41
38
|
return {
|
|
42
39
|
backgroundColor: t === 0
|
|
43
40
|
? theme.colors.background.surface
|
|
@@ -19,7 +19,7 @@ import Navigator from './Navigator';
|
|
|
19
19
|
import ScaleSizeProvider from './ScaleSizeProvider';
|
|
20
20
|
import { getModalOptions, getStackOptions } from './utils';
|
|
21
21
|
import { NavigationContainerProps } from './types';
|
|
22
|
-
import { ApplicationContext, MiniAppContext } from '../Context';
|
|
22
|
+
import { ApplicationContext, FontScaleConfig, MiniAppContext } from '../Context';
|
|
23
23
|
import Localize from './Localize';
|
|
24
24
|
import { Colors, defaultTheme } from '../Consts';
|
|
25
25
|
import { HeaderLeft } from './Components/HeaderLeft';
|
|
@@ -29,6 +29,29 @@ import { HeaderBackground } from './Components/HeaderBackground';
|
|
|
29
29
|
|
|
30
30
|
const Stack = createStackNavigator();
|
|
31
31
|
|
|
32
|
+
const FONT_SCALE_OBSERVER_KEY = 'font_scale_config';
|
|
33
|
+
|
|
34
|
+
// AI-GENERATED START: parse the font_scale_config first-frame seed read straight from async storage (string | {response} | object)
|
|
35
|
+
const parseSeedFontScaleConfig = (raw: any): FontScaleConfig | undefined => {
|
|
36
|
+
let data = raw;
|
|
37
|
+
if (typeof data === 'string') {
|
|
38
|
+
try {
|
|
39
|
+
data = JSON.parse(data);
|
|
40
|
+
} catch {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
data = data?.response ?? data;
|
|
45
|
+
if (!data || (data.useOSFontScale === undefined && data.userScaleRate === undefined)) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
useOSFontScale: data.useOSFontScale,
|
|
50
|
+
userScaleRate: data.userScaleRate,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
// AI-GENERATED END: parse the font_scale_config first-frame seed read straight from async storage (string | {response} | object)
|
|
54
|
+
|
|
32
55
|
const NavigationContainer: React.FC<NavigationContainerProps> = ({
|
|
33
56
|
screen,
|
|
34
57
|
theme = defaultTheme,
|
|
@@ -45,6 +68,11 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
|
|
|
45
68
|
}) => {
|
|
46
69
|
const context = useContext<any>(MiniAppContext);
|
|
47
70
|
const [currentContext, setCurrentContext] = useState<any>({});
|
|
71
|
+
// AI-GENERATED START: self-seed first-frame font scale by reading font_scale_config straight from async storage
|
|
72
|
+
const [observerFontScaleConfig, setObserverFontScaleConfig] = useState<
|
|
73
|
+
FontScaleConfig | undefined
|
|
74
|
+
>(() => parseSeedFontScaleConfig(maxApi?.getItem?.(FONT_SCALE_OBSERVER_KEY)) ?? context?.fontScaleConfig);
|
|
75
|
+
// AI-GENERATED END: self-seed first-frame font scale by reading font_scale_config straight from async storage
|
|
48
76
|
|
|
49
77
|
const mergedContext = {
|
|
50
78
|
...context,
|
|
@@ -55,10 +83,24 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
|
|
|
55
83
|
},
|
|
56
84
|
};
|
|
57
85
|
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
const sub = maxApi?.observer?.(
|
|
88
|
+
FONT_SCALE_OBSERVER_KEY,
|
|
89
|
+
(data: FontScaleConfig) => {
|
|
90
|
+
setObserverFontScaleConfig(data ?? undefined);
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
return () => {
|
|
94
|
+
sub?.remove?.();
|
|
95
|
+
};
|
|
96
|
+
}, [maxApi]);
|
|
97
|
+
|
|
58
98
|
return (
|
|
59
99
|
<SafeAreaProvider>
|
|
60
100
|
<MiniAppContext.Provider value={mergedContext}>
|
|
61
|
-
<ScaleSizeProvider
|
|
101
|
+
<ScaleSizeProvider
|
|
102
|
+
fontScaleConfig={observerFontScaleConfig}
|
|
103
|
+
>
|
|
62
104
|
<Navigation
|
|
63
105
|
screen={screen}
|
|
64
106
|
theme={theme}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import React, { FC } from 'react';
|
|
1
|
+
import React, { FC, useContext } from 'react';
|
|
2
2
|
import { ScaleSizeContext } from '../Context';
|
|
3
3
|
import { ScaleSizeProviderProps } from './types';
|
|
4
4
|
|
|
5
5
|
const ScaleSizeProvider: FC<ScaleSizeProviderProps> = ({
|
|
6
|
-
|
|
6
|
+
fontScaleConfig,
|
|
7
7
|
children,
|
|
8
8
|
}) => {
|
|
9
|
+
// Inherit the config from an upper layer (e.g. app root) when the host doesn't set it via MiniAppContext.
|
|
10
|
+
const parent = useContext(ScaleSizeContext);
|
|
9
11
|
return (
|
|
10
|
-
<ScaleSizeContext.Provider value={
|
|
12
|
+
<ScaleSizeContext.Provider value={fontScaleConfig ?? parent}>
|
|
11
13
|
{children}
|
|
12
14
|
</ScaleSizeContext.Provider>
|
|
13
15
|
);
|
|
@@ -78,7 +78,7 @@ const WidgetContainer: React.FC<WidgetContainerProps> = ({
|
|
|
78
78
|
<SafeAreaProvider>
|
|
79
79
|
<MiniAppContext.Provider value={mergedContext}>
|
|
80
80
|
<ScaleSizeProvider
|
|
81
|
-
|
|
81
|
+
fontScaleConfig={mergedContext?.fontScaleConfig}
|
|
82
82
|
>
|
|
83
83
|
<ApplicationContext.Provider
|
|
84
84
|
value={{
|
package/Application/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { EventArg } from '@react-navigation/core';
|
|
|
2
2
|
import { StackNavigationOptions } from '@react-navigation/stack';
|
|
3
3
|
import React, { ReactNode } from 'react';
|
|
4
4
|
import {
|
|
5
|
+
Animated,
|
|
5
6
|
TouchableOpacityProps,
|
|
6
7
|
ViewProps,
|
|
7
8
|
ViewStyle,
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
import type { SharedValue } from 'react-native-reanimated';
|
|
10
11
|
import { PopupNotifyProps } from '../Popup/types';
|
|
11
12
|
import { InputRef, InputSearchProps } from '../Input';
|
|
13
|
+
import { FontScaleConfig } from '../Context';
|
|
12
14
|
import Navigation from './Navigation';
|
|
13
15
|
|
|
14
16
|
export type NavigationProps = {
|
|
@@ -108,7 +110,7 @@ export type Theme = {
|
|
|
108
110
|
};
|
|
109
111
|
|
|
110
112
|
export type ScaleSizeProviderProps = {
|
|
111
|
-
|
|
113
|
+
fontScaleConfig?: FontScaleConfig;
|
|
112
114
|
children: ViewProps['children'];
|
|
113
115
|
};
|
|
114
116
|
|
|
@@ -258,7 +260,7 @@ export interface HeaderBackProps extends NavigationButtonProps {
|
|
|
258
260
|
}
|
|
259
261
|
|
|
260
262
|
export type HeaderBackgroundProps = {
|
|
261
|
-
animatedValue?: SharedValue<number>;
|
|
263
|
+
animatedValue?: Animated.Value | SharedValue<number>;
|
|
262
264
|
useGradient?: boolean;
|
|
263
265
|
useShadowHeader?: boolean;
|
|
264
266
|
backgroundColor?: string;
|
|
@@ -298,7 +300,7 @@ export type TitleJourneyProps = {
|
|
|
298
300
|
};
|
|
299
301
|
|
|
300
302
|
export interface HeaderAnimatedProps extends ViewProps {
|
|
301
|
-
animatedValue: SharedValue<number>;
|
|
303
|
+
animatedValue: Animated.Value | SharedValue<number>;
|
|
302
304
|
image: string;
|
|
303
305
|
useScale?: boolean;
|
|
304
306
|
}
|
|
@@ -342,7 +344,7 @@ export type AnimatedHeader = {
|
|
|
342
344
|
|
|
343
345
|
export interface SearchHeaderProps extends InputSearchProps {
|
|
344
346
|
ref?: React.RefObject<InputRef>;
|
|
345
|
-
animatedValue?: SharedValue<number>;
|
|
347
|
+
animatedValue?: Animated.Value | SharedValue<number>;
|
|
346
348
|
headerRightWidth?: 0 | 74 | 110 | number;
|
|
347
349
|
leftPosition?: 12 | 48 | number;
|
|
348
350
|
renderButtons?: () => ReactNode;
|
package/Application/utils.tsx
CHANGED
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
} from '@react-navigation/stack';
|
|
6
6
|
import type { HeaderTitleProps, NavigationOptions } from './types';
|
|
7
7
|
import { Colors, Spacing } from '../Consts';
|
|
8
|
-
import { AppState, Platform } from 'react-native';
|
|
8
|
+
import { Animated, AppState, Platform } from 'react-native';
|
|
9
9
|
import type { SharedValue } from 'react-native-reanimated';
|
|
10
|
+
import { useSharedValue } from 'react-native-reanimated';
|
|
10
11
|
import {
|
|
11
12
|
MiniAppContext,
|
|
12
13
|
ScreenContext,
|
|
@@ -229,6 +230,43 @@ const useAppState = () => {
|
|
|
229
230
|
};
|
|
230
231
|
};
|
|
231
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Union of the two animation runtimes an internal foundation component might
|
|
235
|
+
* receive. Public Screen props still accept React Native Animated.Value only;
|
|
236
|
+
* this type keeps lower-level header/floating components compatible while they
|
|
237
|
+
* render with reanimated.
|
|
238
|
+
*/
|
|
239
|
+
export type AnimatedCompatValue = Animated.Value | SharedValue<number>;
|
|
240
|
+
|
|
241
|
+
const isRNAnimatedValue = (v: unknown): v is Animated.Value =>
|
|
242
|
+
!!v &&
|
|
243
|
+
typeof (v as Animated.Value).setValue === 'function' &&
|
|
244
|
+
typeof (v as Animated.Value).interpolate === 'function';
|
|
245
|
+
|
|
246
|
+
const isSharedValue = (v: unknown): v is SharedValue<number> =>
|
|
247
|
+
!!v && !isRNAnimatedValue(v) && 'value' in (v as object);
|
|
248
|
+
|
|
249
|
+
const useNormalizedSharedValue = (
|
|
250
|
+
input?: AnimatedCompatValue,
|
|
251
|
+
): SharedValue<number> => {
|
|
252
|
+
const fallback = useSharedValue(0);
|
|
253
|
+
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
if (!isRNAnimatedValue(input)) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const id = input.addListener(({ value }) => {
|
|
259
|
+
fallback.value = value;
|
|
260
|
+
});
|
|
261
|
+
return () => input.removeListener(id);
|
|
262
|
+
}, [input, fallback]);
|
|
263
|
+
|
|
264
|
+
if (isSharedValue(input)) {
|
|
265
|
+
return input;
|
|
266
|
+
}
|
|
267
|
+
return fallback;
|
|
268
|
+
};
|
|
269
|
+
|
|
232
270
|
export {
|
|
233
271
|
getStackOptions,
|
|
234
272
|
getModalOptions,
|
|
@@ -237,4 +275,5 @@ export {
|
|
|
237
275
|
setAutomationID,
|
|
238
276
|
useComponentId,
|
|
239
277
|
useAppState,
|
|
278
|
+
useNormalizedSharedValue,
|
|
240
279
|
};
|
package/Context/index.ts
CHANGED
|
@@ -9,9 +9,12 @@ const MiniAppContext = (Platform as any).MiniAppContext ?? Context;
|
|
|
9
9
|
const ScreenContext = (Platform as any).ScreenContext ?? Context;
|
|
10
10
|
const ComponentContext = (Platform as any).ComponentContext ?? Context;
|
|
11
11
|
const SkeletonContext = createContext({ loading: false });
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
// Single source of truth: holds both the OS flag and the user-adjusted rate.
|
|
13
|
+
export type FontScaleConfig = {
|
|
14
|
+
useOSFontScale?: boolean;
|
|
15
|
+
userScaleRate?: number;
|
|
16
|
+
};
|
|
17
|
+
const ScaleSizeContext = createContext<FontScaleConfig>({});
|
|
15
18
|
const TrackingScopeContext = createContext<{ scopeName?: string }>({
|
|
16
19
|
scopeName: undefined,
|
|
17
20
|
});
|
|
@@ -11,11 +11,14 @@ import Animated, {
|
|
|
11
11
|
useSharedValue,
|
|
12
12
|
withTiming,
|
|
13
13
|
runOnJS,
|
|
14
|
-
type SharedValue,
|
|
15
14
|
} from 'react-native-reanimated';
|
|
16
15
|
import { ApplicationContext } from '../Context';
|
|
17
16
|
import { Icon } from '../Icon';
|
|
18
17
|
import { useScaleSize } from '../Text';
|
|
18
|
+
import {
|
|
19
|
+
AnimatedCompatValue,
|
|
20
|
+
useNormalizedSharedValue,
|
|
21
|
+
} from '../Application/utils';
|
|
19
22
|
|
|
20
23
|
export interface FloatingButtonProps {
|
|
21
24
|
label?: string;
|
|
@@ -24,7 +27,7 @@ export interface FloatingButtonProps {
|
|
|
24
27
|
icon?: string;
|
|
25
28
|
iconColor?: string;
|
|
26
29
|
size?: 'small' | 'large';
|
|
27
|
-
animatedValue?:
|
|
30
|
+
animatedValue?: AnimatedCompatValue;
|
|
28
31
|
bottom?: number;
|
|
29
32
|
renderComponent?: () => React.ReactNode;
|
|
30
33
|
}
|
|
@@ -41,6 +44,8 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
41
44
|
bottom = 12,
|
|
42
45
|
}: FloatingButtonProps) => {
|
|
43
46
|
const { theme } = useContext(ApplicationContext);
|
|
47
|
+
const enabled = animatedValue != null;
|
|
48
|
+
const sharedValue = useNormalizedSharedValue(animatedValue);
|
|
44
49
|
const scaledFontSize = useScaleSize(16);
|
|
45
50
|
const scaledLineHeight = useScaleSize(22);
|
|
46
51
|
const maxWidth = useSharedValue(0);
|
|
@@ -57,10 +62,10 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
57
62
|
}, [label, opacityAnimated]);
|
|
58
63
|
|
|
59
64
|
useAnimatedReaction(
|
|
60
|
-
() =>
|
|
65
|
+
() => sharedValue.value,
|
|
61
66
|
(value) => {
|
|
62
67
|
'worklet';
|
|
63
|
-
if (!label || !
|
|
68
|
+
if (!label || !enabled) return;
|
|
64
69
|
if (value !== lastOffset.value && value > 0) {
|
|
65
70
|
const direction = value > lastOffset.value ? 'down' : 'up';
|
|
66
71
|
lastOffset.value = value;
|
|
@@ -88,7 +93,7 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
|
|
|
88
93
|
}
|
|
89
94
|
}
|
|
90
95
|
},
|
|
91
|
-
[label,
|
|
96
|
+
[label, enabled, minWidth],
|
|
92
97
|
);
|
|
93
98
|
|
|
94
99
|
const containerStyle = useAnimatedStyle(() => ({
|
package/Layout/Screen.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import React, {
|
|
|
12
12
|
useRef,
|
|
13
13
|
} from 'react';
|
|
14
14
|
import {
|
|
15
|
+
Animated,
|
|
15
16
|
KeyboardAvoidingView,
|
|
16
17
|
NativeScrollEvent,
|
|
17
18
|
NativeSyntheticEvent,
|
|
@@ -24,12 +25,9 @@ import {
|
|
|
24
25
|
View,
|
|
25
26
|
ViewProps,
|
|
26
27
|
} from 'react-native';
|
|
27
|
-
import
|
|
28
|
-
runOnJS,
|
|
29
|
-
useAnimatedScrollHandler,
|
|
28
|
+
import {
|
|
30
29
|
useSharedValue,
|
|
31
30
|
withTiming,
|
|
32
|
-
type SharedValue,
|
|
33
31
|
} from 'react-native-reanimated';
|
|
34
32
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
35
33
|
import { ApplicationContext, ScreenContext } from '../Context';
|
|
@@ -142,9 +140,10 @@ export interface ScreenProps extends ViewProps {
|
|
|
142
140
|
inputSearchRef?: Ref<InputRef>;
|
|
143
141
|
|
|
144
142
|
/**
|
|
145
|
-
* Optional.
|
|
143
|
+
* Optional. React Native animated value for header.
|
|
144
|
+
* Internally, Screen mirrors it into a reanimated shared value.
|
|
146
145
|
*/
|
|
147
|
-
animatedValue?:
|
|
146
|
+
animatedValue?: Animated.Value;
|
|
148
147
|
|
|
149
148
|
/**
|
|
150
149
|
* Optional. If `true`, use shadow header.
|
|
@@ -201,20 +200,47 @@ const Screen = forwardRef(
|
|
|
201
200
|
const screen: any = useContext(ScreenContext);
|
|
202
201
|
const insets = useSafeAreaInsets();
|
|
203
202
|
const heightHeader = useHeaderHeight();
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Dual animation source:
|
|
206
|
+
* - `animatedValue` is a react-native `Animated.Value` driven natively by the
|
|
207
|
+
* scroll event. It is what legacy consumers receive (`animatedValue`).
|
|
208
|
+
* - `sharedValue` is a reanimated `SharedValue` consumed by every internal
|
|
209
|
+
* foundation component (and offered to modern consumers). It is kept in
|
|
210
|
+
* sync from the scroll `listener` (see below), which fires on the JS
|
|
211
|
+
* thread even when the scroll uses the native driver.
|
|
212
|
+
* If a legacy value is driven outside of Screen, its listener also
|
|
213
|
+
* mirrors JS-driven updates into the shared value.
|
|
214
|
+
* Consumers pass only `Animated.Value`; the reanimated shared value stays
|
|
215
|
+
* an internal implementation detail.
|
|
216
|
+
*/
|
|
217
|
+
const animatedValue = useRef<Animated.Value>(
|
|
218
|
+
customAnimatedValue ?? new Animated.Value(0),
|
|
219
|
+
);
|
|
220
|
+
const internalShared = useSharedValue(0);
|
|
221
|
+
const sharedValue = internalShared;
|
|
222
|
+
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
if (!customAnimatedValue) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const listenerId = customAnimatedValue.addListener(({ value }) => {
|
|
229
|
+
internalShared.value = value;
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
return () => {
|
|
233
|
+
customAnimatedValue.removeListener(listenerId);
|
|
234
|
+
};
|
|
235
|
+
}, [customAnimatedValue, internalShared]);
|
|
236
|
+
|
|
206
237
|
const currentTint = useRef<string | undefined>(undefined);
|
|
207
238
|
const isTab = navigation?.instance?.getState?.()?.type === 'tab';
|
|
208
239
|
|
|
209
|
-
let handleScroll
|
|
240
|
+
let handleScroll;
|
|
210
241
|
let Component: any = View;
|
|
211
242
|
|
|
212
|
-
// AI-GENERATED START
|
|
213
|
-
// On iOS the home indicator is ~34pt but visual safe area needs only ~21pt cap.
|
|
214
|
-
// On Android 15+ edge-to-edge the nav bar can be 48dp — no cap needed.
|
|
215
243
|
const bottomInset = Platform.OS === 'ios' ? Math.min(insets.bottom, 21) : insets.bottom;
|
|
216
|
-
// AI-GENERATED END
|
|
217
|
-
|
|
218
244
|
let keyboardOffset = heightHeader - bottomInset;
|
|
219
245
|
if (headerType === 'extended' || animatedHeader || inputSearchProps) {
|
|
220
246
|
keyboardOffset = -bottomInset;
|
|
@@ -260,7 +286,7 @@ const Screen = forwardRef(
|
|
|
260
286
|
inputRange: [0, 50],
|
|
261
287
|
outputRange: [1, 0],
|
|
262
288
|
}}
|
|
263
|
-
animatedValue={
|
|
289
|
+
animatedValue={sharedValue}
|
|
264
290
|
/>
|
|
265
291
|
),
|
|
266
292
|
};
|
|
@@ -275,7 +301,7 @@ const Screen = forwardRef(
|
|
|
275
301
|
headerBackground: (props: any) => (
|
|
276
302
|
<HeaderBackground
|
|
277
303
|
{...props}
|
|
278
|
-
animatedValue={
|
|
304
|
+
animatedValue={sharedValue}
|
|
279
305
|
useShadowHeader={useShadowHeader}
|
|
280
306
|
headerBackground={headerBackground}
|
|
281
307
|
gradientColor={gradientColor}
|
|
@@ -295,7 +321,7 @@ const Screen = forwardRef(
|
|
|
295
321
|
inputRange: [0, 50],
|
|
296
322
|
outputRange: [1, 0],
|
|
297
323
|
}}
|
|
298
|
-
animatedValue={
|
|
324
|
+
animatedValue={sharedValue}
|
|
299
325
|
/>
|
|
300
326
|
),
|
|
301
327
|
};
|
|
@@ -309,6 +335,7 @@ const Screen = forwardRef(
|
|
|
309
335
|
headerBackground,
|
|
310
336
|
inputSearchProps,
|
|
311
337
|
navigation?.instance,
|
|
338
|
+
sharedValue,
|
|
312
339
|
useShadowHeader,
|
|
313
340
|
],
|
|
314
341
|
);
|
|
@@ -330,7 +357,7 @@ const Screen = forwardRef(
|
|
|
330
357
|
headerBackground: (props: any) => (
|
|
331
358
|
<HeaderBackground
|
|
332
359
|
{...props}
|
|
333
|
-
animatedValue={
|
|
360
|
+
animatedValue={sharedValue}
|
|
334
361
|
useGradient={false}
|
|
335
362
|
useShadowHeader={useShadowHeader}
|
|
336
363
|
headerBackground={headerBackground}
|
|
@@ -351,7 +378,13 @@ const Screen = forwardRef(
|
|
|
351
378
|
|
|
352
379
|
navigation?.instance?.setOptions(options);
|
|
353
380
|
},
|
|
354
|
-
[
|
|
381
|
+
[
|
|
382
|
+
gradientColor,
|
|
383
|
+
headerBackground,
|
|
384
|
+
navigation?.instance,
|
|
385
|
+
sharedValue,
|
|
386
|
+
useShadowHeader,
|
|
387
|
+
],
|
|
355
388
|
);
|
|
356
389
|
|
|
357
390
|
/**
|
|
@@ -369,13 +402,13 @@ const Screen = forwardRef(
|
|
|
369
402
|
inputRange: [0, 50],
|
|
370
403
|
outputRange: [1, 0],
|
|
371
404
|
}}
|
|
372
|
-
animatedValue={
|
|
405
|
+
animatedValue={sharedValue}
|
|
373
406
|
/>
|
|
374
407
|
),
|
|
375
408
|
};
|
|
376
409
|
|
|
377
410
|
navigation?.instance?.setOptions(options);
|
|
378
|
-
}, [navigation?.instance]);
|
|
411
|
+
}, [navigation?.instance, sharedValue]);
|
|
379
412
|
|
|
380
413
|
/**
|
|
381
414
|
* export search header
|
|
@@ -398,7 +431,7 @@ const Screen = forwardRef(
|
|
|
398
431
|
headerLeft: (props: any) =>
|
|
399
432
|
params?.hiddenBack ? null : <HeaderLeft {...props} />,
|
|
400
433
|
headerTitle: () => (
|
|
401
|
-
<SearchHeader {...params} animatedValue={
|
|
434
|
+
<SearchHeader {...params} animatedValue={sharedValue} />
|
|
402
435
|
),
|
|
403
436
|
};
|
|
404
437
|
|
|
@@ -449,51 +482,47 @@ const Screen = forwardRef(
|
|
|
449
482
|
});
|
|
450
483
|
});
|
|
451
484
|
|
|
452
|
-
const onTintColorChange = useCallback(
|
|
453
|
-
(offsetY: number) => {
|
|
454
|
-
if (!animatedHeader) return;
|
|
455
|
-
let color = animatedHeader?.headerTintColor ?? Colors.black_17;
|
|
456
|
-
if (offsetY > 50) {
|
|
457
|
-
color = Colors.black_17;
|
|
458
|
-
}
|
|
459
|
-
if (color !== currentTint.current) {
|
|
460
|
-
currentTint.current = color;
|
|
461
|
-
navigation?.setOptions({
|
|
462
|
-
headerTintColor: color,
|
|
463
|
-
});
|
|
464
|
-
let barStyle: StatusBarStyle = 'dark-content';
|
|
465
|
-
if (currentTint.current === Colors.black_01) {
|
|
466
|
-
barStyle = 'light-content';
|
|
467
|
-
}
|
|
468
|
-
StatusBar.setBarStyle(barStyle, true);
|
|
469
|
-
}
|
|
470
|
-
},
|
|
471
|
-
[animatedHeader, navigation],
|
|
472
|
-
);
|
|
473
|
-
|
|
474
|
-
const emitOnScroll = useCallback(
|
|
475
|
-
(offsetY: number) => {
|
|
476
|
-
scrollViewProps?.onScroll?.({
|
|
477
|
-
nativeEvent: { contentOffset: { x: 0, y: offsetY } },
|
|
478
|
-
} as NativeSyntheticEvent<NativeScrollEvent>);
|
|
479
|
-
},
|
|
480
|
-
[scrollViewProps],
|
|
481
|
-
);
|
|
482
|
-
|
|
483
|
-
const scrollHandler = useAnimatedScrollHandler({
|
|
484
|
-
onScroll: (event) => {
|
|
485
|
-
animatedValue.value = event.contentOffset.y;
|
|
486
|
-
runOnJS(emitOnScroll)(event.contentOffset.y);
|
|
487
|
-
runOnJS(onTintColorChange)(event.contentOffset.y);
|
|
488
|
-
},
|
|
489
|
-
});
|
|
490
|
-
|
|
491
485
|
/**
|
|
492
486
|
* animated when use scroll && animated value
|
|
493
487
|
*/
|
|
494
488
|
if (scrollable) {
|
|
495
489
|
Component = Animated.ScrollView;
|
|
496
|
-
handleScroll =
|
|
490
|
+
handleScroll = Animated.event(
|
|
491
|
+
[
|
|
492
|
+
{
|
|
493
|
+
nativeEvent: {
|
|
494
|
+
contentOffset: { y: animatedValue.current as Animated.Value },
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
],
|
|
498
|
+
{
|
|
499
|
+
useNativeDriver: true,
|
|
500
|
+
listener: (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
501
|
+
const offsetY = e.nativeEvent.contentOffset.y;
|
|
502
|
+
// keep the reanimated source in sync for the internal components.
|
|
503
|
+
sharedValue.value = offsetY;
|
|
504
|
+
scrollViewProps?.onScroll?.(e);
|
|
505
|
+
if (animatedHeader) {
|
|
506
|
+
let color = animatedHeader?.headerTintColor ?? Colors.black_17;
|
|
507
|
+
if (offsetY > 50) {
|
|
508
|
+
color = Colors.black_17;
|
|
509
|
+
}
|
|
510
|
+
if (color !== currentTint.current) {
|
|
511
|
+
currentTint.current = color;
|
|
512
|
+
navigation?.setOptions({
|
|
513
|
+
headerTintColor: color,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
let barStyle: StatusBarStyle = 'dark-content';
|
|
517
|
+
if (currentTint.current === Colors.black_01) {
|
|
518
|
+
barStyle = 'light-content';
|
|
519
|
+
}
|
|
520
|
+
StatusBar.setBarStyle(barStyle, true);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
);
|
|
497
526
|
}
|
|
498
527
|
|
|
499
528
|
/**
|
|
@@ -503,7 +532,12 @@ const Screen = forwardRef(
|
|
|
503
532
|
const handleScrollEnd = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
504
533
|
const offsetY = e.nativeEvent.contentOffset.y;
|
|
505
534
|
if (inputSearchProps && offsetY < 100 && offsetY > 0) {
|
|
506
|
-
animatedValue.
|
|
535
|
+
Animated.timing(animatedValue.current, {
|
|
536
|
+
toValue: 0,
|
|
537
|
+
useNativeDriver: true,
|
|
538
|
+
duration: 300,
|
|
539
|
+
}).start();
|
|
540
|
+
sharedValue.value = withTiming(0, { duration: 300 });
|
|
507
541
|
ref?.scrollTo?.({ y: 0, animated: true });
|
|
508
542
|
}
|
|
509
543
|
scrollViewProps?.onScrollEndDrag?.(e);
|
|
@@ -519,7 +553,10 @@ const Screen = forwardRef(
|
|
|
519
553
|
style={[styles.screenBanner, { maxHeight: 210 + layoutOffset }]}
|
|
520
554
|
>
|
|
521
555
|
{animatedHeader?.component({
|
|
522
|
-
animatedValue
|
|
556
|
+
// legacy consumers read `animatedValue` (Animated.Value),
|
|
557
|
+
// modern consumers read `sharedValue` (SharedValue).
|
|
558
|
+
animatedValue: animatedValue.current,
|
|
559
|
+
sharedValue: sharedValue,
|
|
523
560
|
})}
|
|
524
561
|
</View>
|
|
525
562
|
);
|
|
@@ -586,7 +623,7 @@ const Screen = forwardRef(
|
|
|
586
623
|
headerType={headerType}
|
|
587
624
|
heightHeader={heightHeader}
|
|
588
625
|
headerRightWidth={headerRightWidth}
|
|
589
|
-
animatedValue={
|
|
626
|
+
animatedValue={sharedValue}
|
|
590
627
|
inputSearchProps={inputSearchProps}
|
|
591
628
|
navigation={navigation}
|
|
592
629
|
inputSearchRef={inputSearchRef}
|
|
@@ -613,7 +650,14 @@ const Screen = forwardRef(
|
|
|
613
650
|
onScroll={handleScroll}
|
|
614
651
|
onScrollEndDrag={handleScrollEnd}
|
|
615
652
|
scrollEventThrottle={16}
|
|
616
|
-
style={
|
|
653
|
+
style={[
|
|
654
|
+
Styles.flex,
|
|
655
|
+
!scrollable && !Footer && !isTab && { paddingBottom: bottomInset },
|
|
656
|
+
]}
|
|
657
|
+
contentContainerStyle={[
|
|
658
|
+
scrollable && !Footer && !isTab && { paddingBottom: bottomInset },
|
|
659
|
+
scrollViewProps?.contentContainerStyle,
|
|
660
|
+
]}
|
|
617
661
|
>
|
|
618
662
|
{renderAnimatedHeader()}
|
|
619
663
|
|
|
@@ -624,7 +668,7 @@ const Screen = forwardRef(
|
|
|
624
668
|
<View>
|
|
625
669
|
<FloatingButton
|
|
626
670
|
{...floatingButtonProps}
|
|
627
|
-
animatedValue={
|
|
671
|
+
animatedValue={sharedValue}
|
|
628
672
|
bottom={
|
|
629
673
|
Footer || isTab ? 12 : bottomInset + Spacing.S
|
|
630
674
|
}
|
package/Text/utils.ts
CHANGED
|
@@ -7,13 +7,18 @@ const DEFAULT_SCREEN_SIZE = 375;
|
|
|
7
7
|
const MAX_FONT_SCALE = 1.5;
|
|
8
8
|
const MAX_DEVICE_SCALE = 5;
|
|
9
9
|
|
|
10
|
-
const useScaleSize = (size: number,
|
|
11
|
-
const {
|
|
10
|
+
const useScaleSize = (size: number, userScaleRate?: number) => {
|
|
11
|
+
const { useOSFontScale = true, userScaleRate: ctxRate } =
|
|
12
|
+
useContext(ScaleSizeContext);
|
|
12
13
|
const fontScale = PixelRatio.getFontScale();
|
|
13
14
|
const { width } = useWindowDimensions();
|
|
14
15
|
const deviceScale = width / DEFAULT_SCREEN_SIZE;
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
+
const customRate = userScaleRate ?? ctxRate ?? 1;
|
|
18
|
+
|
|
19
|
+
if (!useOSFontScale) {
|
|
20
|
+
return customRate > 1 ? customRate * size : size;
|
|
21
|
+
}
|
|
17
22
|
|
|
18
23
|
let fontSizeDeviceScale = size;
|
|
19
24
|
let fontSizeOSScale = size;
|
|
@@ -27,8 +32,8 @@ const useScaleSize = (size: number, scaleRate?: number) => {
|
|
|
27
32
|
|
|
28
33
|
if (fontScale > 1) {
|
|
29
34
|
fontSizeOSScale = Math.min(
|
|
30
|
-
|
|
31
|
-
fontSizeOSScale *
|
|
35
|
+
fontScale * fontSizeOSScale,
|
|
36
|
+
fontSizeOSScale * MAX_FONT_SCALE,
|
|
32
37
|
);
|
|
33
38
|
}
|
|
34
39
|
|