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