Skip to content

Commit

Permalink
move the particle GIF into canvas
Browse files Browse the repository at this point in the history
  • Loading branch information
tmgrask committed Sep 30, 2024
1 parent 74ff568 commit 292a763
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 19 deletions.
75 changes: 75 additions & 0 deletions src/animationHooks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// NOTE: this whole file is copied from @shopify/react-native-skia, see TODO
import { useEffect } from "react";

import {
DataSourceParam,
SkImage,
useAnimatedImage,
} from "@shopify/react-native-skia";
const DEFAULT_FRAME_DURATION = 60;

import {
FrameInfo,
SharedValue,
useFrameCallback,
useSharedValue,
} from "react-native-reanimated";

export const useAnimatedImageValue = (
source: DataSourceParam,
paused?: SharedValue<boolean>,
) => {
const defaultPaused = useSharedValue(false);
const isPaused = paused ?? defaultPaused;
const currentFrame = useSharedValue<null | SkImage>(null);
const lastTimestamp = useSharedValue(-1);
const animatedImage = useAnimatedImage(
source,
(err) => {
console.error(err);
throw new Error(
`Could not load animated image - got '${err.message}'`,
);
},
false,
);
const frameDuration =
animatedImage?.currentFrameDuration() || DEFAULT_FRAME_DURATION;

useFrameCallback((frameInfo: FrameInfo) => {
if (!animatedImage) {
currentFrame.value = null;
return;
}
if (isPaused.value && lastTimestamp.value !== -1) {
return;
}
const { timestamp } = frameInfo;
const elapsed = timestamp - lastTimestamp.value;

// Check if it's time to switch frames based on GIF frame duration
if (elapsed < frameDuration) {
return;
}

// Update the current frame
animatedImage.decodeNextFrame();
// TODO: this whole file is copied from @shopify/react-native-skia because
// the following dispose call seems to be causing flickering in the animated
// image. Will try address upstream at some point, then delete this file.
//if (currentFrame.value) {
// currentFrame.value.dispose();
//}
currentFrame.value = animatedImage.getCurrentFrame();

// Update the last timestamp
lastTimestamp.value = timestamp;
});
useEffect(() => {
return () => {
animatedImage?.dispose();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return currentFrame;
};
41 changes: 22 additions & 19 deletions src/components/ConduitOrbToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import * as Haptics from "expo-haptics";
import React from "react";
import { useTranslation } from "react-i18next";
import { Pressable, Image as RNImage, View } from "react-native";
import { Pressable, View } from "react-native";
import Animated, {
Easing,
useDerivedValue,
Expand All @@ -31,6 +31,7 @@ import Animated, {
} from "react-native-reanimated";
import { z } from "zod";

import { useAnimatedImageValue } from "@/src/animationHooks";
import { timedLog } from "@/src/common/utils";
import { ConduitConnectionLight } from "@/src/components/ConduitConnectionLight";
import { PARTICLE_VIDEO_DELAY_MS } from "@/src/constants";
Expand Down Expand Up @@ -195,20 +196,25 @@ export function ConduitOrbToggle({ size }: { size: number }) {
// Use this in initialStateDetermined state variable to coordiate the order
// of animations: first we want the intro to play, then we want to be hooked
// up to InProxyStatus changes.
const [showParticleSwirl, setShowParticleSwirl] = React.useState(false);
const particleSwirlPaused = useSharedValue(true);
const particleSwirlOpacity = useSharedValue(0);
const particleSwirlGif = useAnimatedImageValue(
require("@/assets/images/particle-swirl.gif"),
particleSwirlPaused,
);
const [initialStateDetermined, setInitialStateDetermined] =
React.useState(false);
React.useEffect(() => {
if (!initialStateDetermined) {
if (inProxyStatus === "RUNNING") {
setShowParticleSwirl(false);
animateIntro(0);
setInitialStateDetermined(true);
} else if (inProxyStatus === "STOPPED") {
setShowParticleSwirl(true);
setTimeout(
() => setShowParticleSwirl(false),
PARTICLE_VIDEO_DELAY_MS,
particleSwirlPaused.value = false;
particleSwirlOpacity.value = 1;
particleSwirlOpacity.value = withDelay(
PARTICLE_VIDEO_DELAY_MS - 200,
withTiming(0, { duration: 200 }),
);
animateIntro(PARTICLE_VIDEO_DELAY_MS);
setInitialStateDetermined(true);
Expand Down Expand Up @@ -279,18 +285,16 @@ export function ConduitOrbToggle({ size }: { size: number }) {
height: size,
}}
>
{/* intro GIF played if Conduit Station is off on start */}
{showParticleSwirl && (
<RNImage
source={require("@/assets/images/particle-swirl.gif")}
style={{
width: size,
height: size,
top: finalOrbRadius / 2,
}}
/>
)}
<Canvas style={[ss.flex]}>
<Group>
{/* Intro particle swirl animation */}
<Image
image={particleSwirlGif}
width={size}
height={size}
opacity={particleSwirlOpacity}
/>
</Group>
<Group>
{/* the red dots at top representing Psiphon Network */}
<Image
Expand Down Expand Up @@ -427,7 +431,6 @@ export function ConduitOrbToggle({ size }: { size: number }) {
duration: 200,
});
}}
disabled={showParticleSwirl}
/>
{/* Long press instructions are shown when peers are connected */}
<View style={[ss.justifyCenter, ss.alignCenter, ss.fullWidth]}>
Expand Down

0 comments on commit 292a763

Please sign in to comment.