Created
September 18, 2025 09:37
-
-
Save aleqsio/55513bde1d1e454f3156f68a28057a39 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { ThemedText } from "@/components/themed-text"; | |
| import { BottomSheet, Button, Host, Slider } from "@expo/ui/swift-ui"; | |
| import { frame, offset } from "@expo/ui/swift-ui/modifiers"; | |
| import { Canvas, Fill, Shader, Skia, vec } from "@shopify/react-native-skia"; | |
| import { useState } from "react"; | |
| import { | |
| PlatformColor, | |
| StyleSheet, | |
| useWindowDimensions, | |
| View, | |
| } from "react-native"; | |
| // Add Reanimated imports | |
| import Animated, { | |
| Easing, | |
| useAnimatedReaction, | |
| useAnimatedStyle, | |
| useDerivedValue, | |
| useSharedValue, | |
| withTiming, | |
| } from "react-native-reanimated"; | |
| const source = Skia.RuntimeEffect.Make(` | |
| uniform float sheetAnim; | |
| uniform int type; | |
| uniform vec2 size; | |
| float rand(vec2 co){ | |
| return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); | |
| } | |
| vec4 main(vec2 pos) { | |
| vec2 normalized = pos/vec2(256); | |
| vec2 offset; | |
| float dist; | |
| offset = (pos - vec2(size.x/2, -size.y)); | |
| dist = sqrt(pow(offset.x, 2.0) + pow(offset.y, 2.0)) / sqrt(pow(size.x/2, 2.0) + pow(size.y/2, 2.0)); | |
| float mixVal = 0; | |
| vec4 colorA; | |
| float anim = 1 - sheetAnim; | |
| if(type == 0) { | |
| mixVal = anim; | |
| colorA = vec4(normalized.y, normalized.x, 1.0,1.0); | |
| } | |
| if(type == 1) { | |
| mixVal = (1-dist) - anim; | |
| colorA = vec4(normalized.y, normalized.x, 1.0,1.0); | |
| } | |
| if(type == 2) { | |
| mixVal = max(0,1.0- pow(1-dist,2.0) + pow(anim*0.7,1)); | |
| colorA = vec4(normalized.y, normalized.x, 1.0,1.0); | |
| } | |
| if(type == 3) { | |
| offset = (pos - vec2(size.x*anim, size.y*anim)); | |
| dist = sqrt(pow(offset.x, 2.0) + pow(offset.y, 2.0)) / sqrt(pow(size.x/2, 2.0) + pow(size.x/2, 2.0)) - pow(sheetAnim,1.3); | |
| mixVal = max(0.0,dist); | |
| colorA = vec4(1.0, normalized.x, normalized.y,1.0); | |
| } | |
| // float mixVal = (1-dist) - anim; | |
| // float mixVal = max(0,1.0- pow(1-dist,2.0) + pow(anim*0.7,1)); | |
| vec4 colorB = vec4(0.0, 0.0, 0.0,0.0); | |
| vec4 color = mix(colorA, colorB, mixVal+rand(pos)/(6.0)); | |
| return vec4(color); | |
| }`)!; | |
| export default function ModalScreen() { | |
| const [isOpen, setIsOpen] = useState(false); | |
| const [type, setType] = useState(0); | |
| const { width } = useWindowDimensions(); | |
| // Create shared value | |
| const sheetAnim = useSharedValue(0); | |
| // Animate sheetAnim when isOpen changes | |
| useAnimatedReaction( | |
| () => isOpen, | |
| (opened, prev) => { | |
| if (opened !== prev) { | |
| if (opened) { | |
| sheetAnim.value = withTiming(1, { | |
| duration: 2000, | |
| easing: Easing.bezier(0, 0.3, 0.7, 1), | |
| }); | |
| } else { | |
| sheetAnim.value = 0; | |
| } | |
| } | |
| }, | |
| [isOpen] | |
| ); | |
| const uniforms = useDerivedValue( | |
| () => ({ | |
| sheetAnim: sheetAnim.value, | |
| size: vec(width, 500), | |
| type: Math.round(type), | |
| }), | |
| [sheetAnim, type] | |
| ); | |
| const opacityStyle = useAnimatedStyle(() => ({ | |
| opacity: 1 - Math.abs(1 - sheetAnim.value * 2.0), | |
| })); | |
| return ( | |
| <View style={styles.container}> | |
| <Animated.View style={[opacityStyle, { position: "absolute" }]}> | |
| <Canvas | |
| style={{ | |
| width: width, | |
| height: 500, | |
| transform: [{ scale: 1.8 }], | |
| filter: "blur(10px)", | |
| }} | |
| > | |
| <Fill> | |
| <Shader source={source} uniforms={uniforms} /> | |
| </Fill> | |
| </Canvas> | |
| </Animated.View> | |
| <Host> | |
| <BottomSheet isOpened={isOpen} onIsOpenedChange={setIsOpen}> | |
| <View style={{ height: 450 }}> | |
| <Animated.View style={[opacityStyle, { position: "absolute" }]}> | |
| <Canvas style={{ width: width, height: 500 }}> | |
| <Fill> | |
| <Shader source={source} uniforms={uniforms} /> | |
| </Fill> | |
| </Canvas> | |
| </Animated.View> | |
| <View style={{ position: "absolute", top: 40, left: 20 }}> | |
| <ThemedText type="title"> | |
| {"Shaders\nGlass UI\nExpo\n\n🔥"} | |
| </ThemedText> | |
| </View> | |
| </View> | |
| </BottomSheet> | |
| </Host> | |
| <Host style={{ width: 400, height: 200 }}> | |
| <Button variant="glass" onPress={() => setIsOpen((o) => !o)}> | |
| {`Shader ${Math.round(type)}`} | |
| </Button> | |
| <Slider | |
| modifiers={[offset({ x: 0, y: 50 }), frame({ width: 200 })]} | |
| onValueChange={setType} | |
| value={type} | |
| steps={1} | |
| max={3} | |
| min={0} | |
| ></Slider> | |
| </Host> | |
| </View> | |
| ); | |
| } | |
| const styles = StyleSheet.create({ | |
| container: { | |
| flex: 1, | |
| alignItems: "center", | |
| justifyContent: "center", | |
| backgroundColor: PlatformColor("tertiarySystemBackground"), | |
| }, | |
| link: { | |
| marginTop: 15, | |
| paddingVertical: 15, | |
| }, | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment