Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at cope-settings-sync 182 lines 4.6 kB view raw
1import { 2 forwardRef, 3 useCallback, 4 useImperativeHandle, 5 useMemo, 6 useRef, 7 useState, 8} from 'react' 9import {Pressable, useWindowDimensions, View} from 'react-native' 10import Animated, { 11 Easing, 12 runOnJS, 13 useAnimatedStyle, 14 useSharedValue, 15 withTiming, 16} from 'react-native-reanimated' 17import {useSafeAreaInsets} from 'react-native-safe-area-context' 18import {msg} from '@lingui/core/macro' 19import {useLingui} from '@lingui/react' 20 21import {atoms as a, useTheme} from '#/alf' 22import {Portal} from '#/components/Portal' 23import {IS_WEB} from '#/env' 24import {AnimatedCheck, type AnimatedCheckRef} from '../anim/AnimatedCheck' 25import {Text} from '../Typography' 26 27export interface ProgressGuideToastRef { 28 open(): void 29 close(): void 30} 31 32export interface ProgressGuideToastProps { 33 title: string 34 subtitle?: string 35 visibleDuration?: number // default 5s 36} 37 38export const ProgressGuideToast = forwardRef< 39 ProgressGuideToastRef, 40 ProgressGuideToastProps 41>(function ProgressGuideToast({title, subtitle, visibleDuration}, ref) { 42 const t = useTheme() 43 const {_} = useLingui() 44 const insets = useSafeAreaInsets() 45 const [isOpen, setIsOpen] = useState(false) 46 const translateY = useSharedValue(0) 47 const opacity = useSharedValue(0) 48 const animatedCheckRef = useRef<AnimatedCheckRef | null>(null) 49 const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined) 50 const winDim = useWindowDimensions() 51 52 /** 53 * Methods 54 */ 55 56 const close = useCallback(() => { 57 // clear the timeout, in case this was called imperatively 58 if (timeoutRef.current) { 59 clearTimeout(timeoutRef.current) 60 timeoutRef.current = undefined 61 } 62 63 // animate the opacity then set isOpen to false when done 64 const setIsntOpen = () => setIsOpen(false) 65 opacity.set(() => 66 withTiming( 67 0, 68 { 69 duration: 400, 70 easing: Easing.out(Easing.cubic), 71 }, 72 () => runOnJS(setIsntOpen)(), 73 ), 74 ) 75 }, [setIsOpen, opacity]) 76 77 const open = useCallback(() => { 78 // set isOpen=true to render 79 setIsOpen(true) 80 81 // animate the vertical translation, the opacity, and the checkmark 82 const playCheckmark = () => animatedCheckRef.current?.play() 83 opacity.set(0) 84 opacity.set(() => 85 withTiming( 86 1, 87 { 88 duration: 100, 89 easing: Easing.out(Easing.cubic), 90 }, 91 () => runOnJS(playCheckmark)(), 92 ), 93 ) 94 translateY.set(0) 95 translateY.set(() => 96 withTiming(insets.top + 10, { 97 duration: 500, 98 easing: Easing.out(Easing.cubic), 99 }), 100 ) 101 102 // start the countdown timer to autoclose 103 timeoutRef.current = setTimeout(close, visibleDuration || 5e3) 104 }, [setIsOpen, translateY, opacity, insets, close, visibleDuration]) 105 106 useImperativeHandle( 107 ref, 108 () => ({ 109 open, 110 close, 111 }), 112 [open, close], 113 ) 114 115 const containerStyle = useMemo(() => { 116 let left = 10 117 let right = 10 118 if (IS_WEB && winDim.width > 400) { 119 left = right = (winDim.width - 380) / 2 120 } 121 return { 122 position: IS_WEB ? 'fixed' : 'absolute', 123 top: 0, 124 left, 125 right, 126 } 127 }, [winDim.width]) 128 129 const animatedStyle = useAnimatedStyle(() => ({ 130 transform: [{translateY: translateY.get()}], 131 opacity: opacity.get(), 132 })) 133 134 return ( 135 isOpen && ( 136 <Portal> 137 <Animated.View 138 style={[ 139 // @ts-ignore position: fixed is web only 140 containerStyle, 141 animatedStyle, 142 ]}> 143 <Pressable 144 style={[ 145 t.atoms.bg, 146 a.flex_row, 147 a.align_center, 148 a.gap_md, 149 a.border, 150 t.atoms.border_contrast_high, 151 a.rounded_md, 152 a.px_lg, 153 a.py_md, 154 a.shadow_sm, 155 { 156 shadowRadius: 8, 157 shadowOpacity: 0.1, 158 shadowOffset: {width: 0, height: 2}, 159 elevation: 8, 160 }, 161 ]} 162 onPress={close} 163 accessibilityLabel={_(msg`Tap to dismiss`)} 164 accessibilityHint=""> 165 <AnimatedCheck 166 fill={t.palette.primary_500} 167 ref={animatedCheckRef} 168 /> 169 <View> 170 <Text style={[a.text_md, a.font_semi_bold]}>{title}</Text> 171 {subtitle && ( 172 <Text style={[a.text_sm, t.atoms.text_contrast_medium]}> 173 {subtitle} 174 </Text> 175 )} 176 </View> 177 </Pressable> 178 </Animated.View> 179 </Portal> 180 ) 181 ) 182})