1import { Code } from '@expo/html-elements'; 2import React, { PropsWithChildren, useCallback, useState, useRef, useEffect } from 'react'; 3import { View, StyleSheet, TouchableOpacity, Text, Animated } from 'react-native'; 4 5import Colors from '../constants/Colors'; 6 7type Props = PropsWithChildren<{ 8 /** 9 * Countdown timeout. 10 */ 11 timeout?: number; 12 13 /** 14 * Called when the countdown ended or close button is pressed. 15 */ 16 onCountdownEnded: () => void; 17}>; 18 19function MonoTextWithCountdown({ children, timeout = 8000, onCountdownEnded }: Props) { 20 const animatedValue = useRef(new Animated.Value(1)).current; 21 const [countdownInterrupted, setCountdownInterrupted] = useState(false); 22 const [valueUponPause, setValueUponPause] = useState(1); 23 24 useEffect(() => { 25 if (countdownInterrupted) { 26 animatedValue.stopAnimation((value) => { 27 setValueUponPause(value); 28 }); 29 } else { 30 Animated.timing(animatedValue, { 31 toValue: 0, 32 duration: valueUponPause * timeout, 33 useNativeDriver: false, 34 }).start(({ finished }) => { 35 if (finished) { 36 onCountdownEnded(); 37 } 38 }); 39 } 40 }, [countdownInterrupted, valueUponPause, onCountdownEnded]); 41 42 const triggerCountdownEnd = useCallback(() => { 43 onCountdownEnded(); 44 }, [onCountdownEnded]); 45 const toggleCountdown = useCallback(() => { 46 setCountdownInterrupted((previousValue) => !previousValue); 47 }, [countdownInterrupted]); 48 49 return ( 50 <View style={styles.container}> 51 <Code style={styles.monoText}>{children}</Code> 52 <View style={styles.buttonsContainer}> 53 <IconButton icon={countdownInterrupted ? '▶️' : '⏸'} onPress={toggleCountdown} /> 54 <IconButton icon="❌" onPress={triggerCountdownEnd} /> 55 </View> 56 <CountdownBar width={animatedValue} /> 57 </View> 58 ); 59} 60 61const styles = StyleSheet.create({ 62 container: { 63 borderWidth: 2, 64 borderColor: '#00AA00', 65 backgroundColor: '#fff', 66 }, 67 68 monoText: { 69 fontSize: 10, 70 padding: 6, 71 }, 72 73 countdownBar: { 74 height: 3, 75 backgroundColor: Colors.tintColor, 76 position: 'absolute', 77 top: 0, 78 left: 0, 79 right: 0, 80 }, 81 82 buttonsContainer: { 83 position: 'absolute', 84 top: 0, 85 right: 0, 86 flexDirection: 'row', 87 }, 88 buttonIcon: { 89 paddingVertical: 5, 90 paddingHorizontal: 3, 91 }, 92}); 93 94type IconButtonProps = { 95 icon: string; 96 onPress: () => void; 97}; 98 99function IconButton({ icon, onPress }: IconButtonProps) { 100 return ( 101 <TouchableOpacity onPress={onPress}> 102 <Text style={styles.buttonIcon}>{icon}</Text> 103 </TouchableOpacity> 104 ); 105} 106 107type CountdownBarProps = { 108 width: Animated.Value; 109}; 110 111function CountdownBar({ width }: CountdownBarProps) { 112 return ( 113 <Animated.View 114 style={[ 115 styles.countdownBar, 116 { 117 width: width.interpolate({ 118 inputRange: [0, 1], 119 outputRange: ['0%', '100%'], 120 }), 121 }, 122 ]} 123 /> 124 ); 125} 126 127export default MonoTextWithCountdown; 128