|
1 |
| -/** |
2 |
| - * Sample React Native App |
3 |
| - * https://github.com/facebook/react-native |
4 |
| - * |
5 |
| - * @format |
6 |
| - */ |
7 |
| - |
8 |
| -import React from "react"; |
9 |
| -import type { PropsWithChildren } from "react"; |
| 1 | +import React, { useEffect, useMemo } from "react"; |
| 2 | +import { StyleSheet, useWindowDimensions, View } from "react-native"; |
10 | 3 | import {
|
11 |
| - SafeAreaView, |
12 |
| - ScrollView, |
13 |
| - StatusBar, |
14 |
| - StyleSheet, |
15 |
| - Text, |
16 |
| - useColorScheme, |
17 |
| - View, |
18 |
| -} from "react-native"; |
| 4 | + BlurMask, |
| 5 | + vec, |
| 6 | + Canvas, |
| 7 | + Circle, |
| 8 | + Fill, |
| 9 | + Group, |
| 10 | + polar2Canvas, |
| 11 | + mix, |
| 12 | +} from "@shopify/react-native-skia"; |
| 13 | +import type { SharedValue } from "react-native-reanimated"; |
19 | 14 | import {
|
20 |
| - Colors, |
21 |
| - DebugInstructions, |
22 |
| - Header, |
23 |
| - LearnMoreLinks, |
24 |
| - ReloadInstructions, |
25 |
| -} from "react-native/Libraries/NewAppScreen"; |
| 15 | + cancelAnimation, |
| 16 | + Easing, |
| 17 | + useDerivedValue, |
| 18 | + useSharedValue, |
| 19 | + withRepeat, |
| 20 | + withTiming, |
| 21 | +} from "react-native-reanimated"; |
| 22 | + |
| 23 | +const useLoop = ({ duration }: { duration: number }) => { |
| 24 | + const progress = useSharedValue(0); |
| 25 | + useEffect(() => { |
| 26 | + progress.value = withRepeat( |
| 27 | + withTiming(1, { duration, easing: Easing.inOut(Easing.ease) }), |
| 28 | + -1, |
| 29 | + true |
| 30 | + ); |
| 31 | + return () => { |
| 32 | + cancelAnimation(progress); |
| 33 | + }; |
| 34 | + }, [duration, progress]); |
| 35 | + return progress; |
| 36 | +}; |
| 37 | + |
| 38 | +const c1 = "#61bea2"; |
| 39 | +const c2 = "#529ca0"; |
26 | 40 |
|
27 |
| -type SectionProps = PropsWithChildren<{ |
28 |
| - title: string; |
29 |
| -}>; |
| 41 | +interface RingProps { |
| 42 | + index: number; |
| 43 | + progress: SharedValue<number>; |
| 44 | +} |
| 45 | + |
| 46 | +const Ring = ({ index, progress }: RingProps) => { |
| 47 | + const { width, height } = useWindowDimensions(); |
| 48 | + const R = width / 4; |
| 49 | + const center = useMemo( |
| 50 | + () => vec(width / 2, height / 2 - 64), |
| 51 | + [height, width] |
| 52 | + ); |
| 53 | + |
| 54 | + const theta = (index * (2 * Math.PI)) / 6; |
| 55 | + const transform = useDerivedValue(() => { |
| 56 | + const { x, y } = polar2Canvas( |
| 57 | + { theta, radius: progress.value * R }, |
| 58 | + { x: 0, y: 0 } |
| 59 | + ); |
| 60 | + const scale = mix(progress.value, 0.3, 1); |
| 61 | + return [{ translateX: x }, { translateY: y }, { scale }]; |
| 62 | + }, [progress, R]); |
30 | 63 |
|
31 |
| -function Section({ children, title }: SectionProps): React.JSX.Element { |
32 |
| - const isDarkMode = useColorScheme() === "dark"; |
33 | 64 | return (
|
34 |
| - <View style={styles.sectionContainer}> |
35 |
| - <Text |
36 |
| - style={[ |
37 |
| - styles.sectionTitle, |
38 |
| - { |
39 |
| - color: isDarkMode ? Colors.white : Colors.black, |
40 |
| - }, |
41 |
| - ]} |
42 |
| - > |
43 |
| - {title} |
44 |
| - </Text> |
45 |
| - <Text |
46 |
| - style={[ |
47 |
| - styles.sectionDescription, |
48 |
| - { |
49 |
| - color: isDarkMode ? Colors.light : Colors.dark, |
50 |
| - }, |
51 |
| - ]} |
52 |
| - > |
53 |
| - {children} |
54 |
| - </Text> |
55 |
| - </View> |
| 65 | + <Circle |
| 66 | + c={center} |
| 67 | + r={R} |
| 68 | + color={index % 2 ? c1 : c2} |
| 69 | + origin={center} |
| 70 | + transform={transform} |
| 71 | + /> |
56 | 72 | );
|
57 |
| -} |
| 73 | +}; |
58 | 74 |
|
59 |
| -function App(): React.JSX.Element { |
60 |
| - const isDarkMode = useColorScheme() === "dark"; |
| 75 | +export const Breathe = () => { |
| 76 | + const { width, height } = useWindowDimensions(); |
| 77 | + const center = useMemo( |
| 78 | + () => vec(width / 2, height / 2 - 64), |
| 79 | + [height, width] |
| 80 | + ); |
61 | 81 |
|
62 |
| - const backgroundStyle = { |
63 |
| - backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, |
64 |
| - }; |
| 82 | + const progress = useLoop({ duration: 3000 }); |
| 83 | + |
| 84 | + const transform = useDerivedValue( |
| 85 | + () => [{ rotate: mix(progress.value, -Math.PI, 0) }], |
| 86 | + [progress] |
| 87 | + ); |
65 | 88 |
|
66 | 89 | return (
|
67 |
| - <SafeAreaView style={backgroundStyle}> |
68 |
| - <StatusBar |
69 |
| - barStyle={isDarkMode ? "light-content" : "dark-content"} |
70 |
| - backgroundColor={backgroundStyle.backgroundColor} |
71 |
| - /> |
72 |
| - <ScrollView |
73 |
| - contentInsetAdjustmentBehavior="automatic" |
74 |
| - style={backgroundStyle} |
75 |
| - > |
76 |
| - <Header /> |
77 |
| - <View |
78 |
| - style={{ |
79 |
| - backgroundColor: isDarkMode ? Colors.black : Colors.white, |
80 |
| - }} |
81 |
| - > |
82 |
| - <Section title="Step One"> |
83 |
| - Edit <Text style={styles.highlight}>App.tsx</Text> to change this |
84 |
| - screen and then come back to see your edits. |
85 |
| - </Section> |
86 |
| - <Section title="See Your Changes"> |
87 |
| - <ReloadInstructions /> |
88 |
| - </Section> |
89 |
| - <Section title="Debug"> |
90 |
| - <DebugInstructions /> |
91 |
| - </Section> |
92 |
| - <Section title="Learn More"> |
93 |
| - Read the docs to discover what to do next: |
94 |
| - </Section> |
95 |
| - <LearnMoreLinks /> |
96 |
| - </View> |
97 |
| - </ScrollView> |
98 |
| - </SafeAreaView> |
| 90 | + <View style={{ flex: 1 }}> |
| 91 | + <Canvas style={styles.container}> |
| 92 | + <Fill color="rgb(36,43,56)" /> |
| 93 | + <Group origin={center} transform={transform} blendMode="screen"> |
| 94 | + <BlurMask style="solid" blur={40} /> |
| 95 | + {new Array(6).fill(0).map((_, index) => { |
| 96 | + return <Ring key={index} index={index} progress={progress} />; |
| 97 | + })} |
| 98 | + </Group> |
| 99 | + </Canvas> |
| 100 | + </View> |
99 | 101 | );
|
100 |
| -} |
| 102 | +}; |
| 103 | + |
| 104 | +// eslint-disable-next-line import/no-default-export |
| 105 | +export default Breathe; |
101 | 106 |
|
102 | 107 | const styles = StyleSheet.create({
|
103 |
| - sectionContainer: { |
104 |
| - marginTop: 32, |
105 |
| - paddingHorizontal: 24, |
106 |
| - }, |
107 |
| - sectionTitle: { |
108 |
| - fontSize: 24, |
109 |
| - fontWeight: "600", |
110 |
| - }, |
111 |
| - sectionDescription: { |
112 |
| - marginTop: 8, |
113 |
| - fontSize: 18, |
114 |
| - fontWeight: "400", |
115 |
| - }, |
116 |
| - highlight: { |
117 |
| - fontWeight: "700", |
| 108 | + container: { |
| 109 | + flex: 1, |
118 | 110 | },
|
119 | 111 | });
|
120 |
| - |
121 |
| -// eslint-disable-next-line import/no-default-export |
122 |
| -export default App; |
0 commit comments