1- import { useLatest , useMemoizedFn , useSafeState } from '@td-design/rn-hooks' ;
1+ import { useTheme } from '@shopify/restyle' ;
2+ import { useLatest , useMemoizedFn , usePrevious , useSafeState } from '@td-design/rn-hooks' ;
23import { useEffect , useMemo , useRef } from 'react' ;
3- import { Animated , BackHandler , Easing } from 'react-native' ;
4+ import { BackHandler , NativeEventSubscription } from 'react-native' ;
5+ import { Easing , runOnJS , useAnimatedStyle , useSharedValue , withTiming } from 'react-native-reanimated' ;
46import { Edge , useSafeAreaInsets } from 'react-native-safe-area-context' ;
57
68import type { ModalProps } from '.' ;
9+ import { Theme } from '../../theme' ;
710
8- export default function useModal ( { visible, onClose, position } : Pick < ModalProps , 'visible' | 'onClose' | 'position' > ) {
9- const insets = useSafeAreaInsets ( ) ;
10- const opacity = useRef ( new Animated . Value ( 0 ) ) ;
11- const [ rendered , setRendered ] = useSafeState ( false ) ;
11+ export default function useModal ( {
12+ visible,
13+ onClose,
14+ position,
15+ maskVisible,
16+ } : Pick < ModalProps , 'visible' | 'onClose' | 'position' | 'maskVisible' > ) {
17+ const theme = useTheme < Theme > ( ) ;
1218 const onCloseRef = useLatest ( onClose ) ;
19+ const insets = useSafeAreaInsets ( ) ;
20+ const opacity = useSharedValue ( 0 ) ;
21+
22+ const [ rendered , setRendered ] = useSafeState ( visible ) ;
23+ const latestVisible = useLatest ( visible ) ;
24+ const previousVisible = usePrevious ( visible ) ;
25+
26+ useEffect ( ( ) => {
27+ if ( visible && ! rendered ) {
28+ setRendered ( true ) ;
29+ }
30+ } , [ visible , rendered ] ) ;
31+
32+ useEffect ( ( ) => {
33+ if ( previousVisible !== latestVisible . current ) {
34+ if ( visible ) {
35+ showModal ( ) ;
36+ } else {
37+ hideModal ( ) ;
38+ }
39+ }
40+ } , [ visible ] ) ;
41+
42+ useEffect ( ( ) => {
43+ return removeListeners ;
44+ } , [ ] ) ;
45+
46+ /**
47+ * 处理安卓返回事件
48+ */
49+ const handleBack = useMemoizedFn ( ( ) => {
50+ hideModal ( ) ;
51+ return true ;
52+ } ) ;
53+
54+ const subscription = useRef < NativeEventSubscription | undefined > ( undefined ) ;
55+
56+ const removeListeners = ( ) => {
57+ if ( subscription . current ?. remove ) {
58+ subscription . current ?. remove ( ) ;
59+ } else {
60+ BackHandler . removeEventListener ( 'hardwareBackPress' , handleBack ) ;
61+ }
62+ } ;
1363
1464 /**
1565 * 打开弹窗
1666 */
1767 const showModal = useMemoizedFn ( ( ) => {
18- Animated . timing ( opacity . current , {
19- toValue : 1 ,
68+ subscription . current ?. remove ( ) ;
69+ subscription . current = BackHandler . addEventListener ( 'hardwareBackPress' , handleBack ) ;
70+
71+ opacity . value = withTiming ( 1 , {
2072 duration : 400 ,
21- easing : Easing . out ( Easing . cubic ) ,
22- useNativeDriver : true ,
23- } ) . start ( ) ;
73+ easing : Easing . in ( Easing . cubic ) ,
74+ } ) ;
2475 } ) ;
2576
2677 /**
2778 * 关闭弹窗
2879 */
2980 const hideModal = useMemoizedFn ( ( ) => {
30- Animated . timing ( opacity . current , {
31- toValue : 0 ,
32- duration : 400 ,
33- easing : Easing . out ( Easing . cubic ) ,
34- useNativeDriver : true ,
35- } ) . start ( finished => {
36- if ( ! finished ) return ;
81+ removeListeners ( ) ;
3782
38- if ( visible ) {
39- onCloseRef . current ?.( ) ;
40- showModal ( ) ;
41- } else {
42- setRendered ( false ) ;
83+ opacity . value = withTiming (
84+ 0 ,
85+ {
86+ duration : 400 ,
87+ easing : Easing . out ( Easing . cubic ) ,
88+ } ,
89+ finished => {
90+ runOnJS ( finishCallback ) ( finished ) ;
4391 }
44- } ) ;
92+ ) ;
4593 } ) ;
4694
95+ function finishCallback ( finished ?: boolean ) {
96+ if ( ! finished ) return ;
97+
98+ if ( visible && onCloseRef ) {
99+ onCloseRef . current ?.( ) ;
100+ }
101+
102+ if ( latestVisible . current ) {
103+ showModal ( ) ;
104+ } else {
105+ setRendered ( false ) ;
106+ }
107+ }
108+
47109 useEffect ( ( ) => {
48110 if ( visible && ! rendered ) {
49111 setRendered ( true ) ;
@@ -53,7 +115,6 @@ export default function useModal({ visible, onClose, position }: Pick<ModalProps
53115 } else if ( rendered ) {
54116 hideModal ( ) ;
55117 }
56- // eslint-disable-next-line react-hooks/exhaustive-deps
57118 } , [ visible , rendered ] ) ;
58119
59120 useEffect ( ( ) => {
@@ -93,9 +154,23 @@ export default function useModal({ visible, onClose, position }: Pick<ModalProps
93154 }
94155 } , [ insets . bottom , insets . top , position ] ) ;
95156
157+ const animatedStyle = useAnimatedStyle ( ( ) => {
158+ const style : any = {
159+ zIndex : 99 ,
160+ flex : 1 ,
161+ backgroundColor : maskVisible ? theme . colors . mask : theme . colors . transparent ,
162+ flexDirection : position === 'bottom' ? 'column-reverse' : 'column' ,
163+ opacity : opacity . value ,
164+ } ;
165+ if ( position === 'center' ) {
166+ style . justifyContent = 'center' ;
167+ }
168+ return style ;
169+ } ) ;
170+
96171 return {
97172 rendered,
98- opacity ,
173+ animatedStyle ,
99174 wrapContainer,
100175 edges,
101176 hideModal,
0 commit comments