Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 30 additions & 51 deletions apps/example/src/Examples/Wallet/Wallet.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,35 @@
import React, { useMemo } from "react";
import { View, StyleSheet, useWindowDimensions } from "react-native";
import {
Canvas,
Path,
Group,
LinearGradient,
vec,
} from "@shopify/react-native-skia";
import { Canvas, Group, LinearGradient, Path, vec } from '@shopify/react-native-skia';
import React, { useMemo } from 'react';
import { StyleSheet, useWindowDimensions, View } from 'react-native';
import { GestureDetector, ScrollView } from 'react-native-gesture-handler';
import Animated, {
useAnimatedStyle,
useDerivedValue,
useSharedValue,
} from "react-native-reanimated";
import { GestureDetector, ScrollView } from "react-native-gesture-handler";
} from 'react-native-reanimated';

import { PADDING, COLORS, getGraph } from "./Model";
import { getYForX } from "./Math";
import { Cursor } from "./components/Cursor";
import { Selection } from "./components/Selection";
import { List } from "./components/List";
import { Header } from "./components/Header";
import { Label } from "./components/Label";
import { useGraphTouchHandler } from "./components/useGraphTouchHandler";
import { getYForX } from './Math';
import { COLORS, getGraph, PADDING } from './Model';
import { Cursor } from './components/Cursor';
import { Header } from './components/Header';
import { Label } from './components/Label';
import { List } from './components/List';
import { Selection } from './components/Selection';
import { useGraphTouchHandler } from './components/useGraphTouchHandler';

const touchableCursorSize = 80;

const styles = StyleSheet.create({
container: {
backgroundColor: "#1F1D2B",
backgroundColor: '#1F1D2B',
},
});

export const Wallet = () => {
const window = useWindowDimensions();
const { width } = window;
const height = Math.min(window.width, window.height) / 2;
const chartHeight = 2 * height + 30;
const translateY = height + PADDING;
const graphs = useMemo(() => getGraph(width, height), [width, height]);
// animation value to transition from one graph to the next
Expand All @@ -57,7 +52,7 @@ export const Wallet = () => {
const gesture = useGraphTouchHandler(x, width);
const style = useAnimatedStyle(() => {
return {
position: "absolute",
position: 'absolute',
width: touchableCursorSize,
height: touchableCursorSize,
left: x.value - touchableCursorSize / 2,
Expand All @@ -67,36 +62,20 @@ export const Wallet = () => {
return (
<ScrollView style={styles.container}>
<Header />
<View>
<Canvas style={{ width, height: 2 * height + 30 }}>
<Label
state={state}
y={y}
graphs={graphs}
width={width}
height={height}
/>
<Group transform={[{ translateY }]}>
<Path
style="stroke"
path={path}
strokeWidth={4}
strokeJoin="round"
strokeCap="round"
>
<LinearGradient
start={vec(0, 0)}
end={vec(width, 0)}
colors={COLORS}
/>
</Path>
<Cursor x={x} y={y} width={width} />
</Group>
</Canvas>
<GestureDetector gesture={gesture}>
<Animated.View style={style} />
</GestureDetector>
</View>
<GestureDetector gesture={gesture}>
<View style={{ width, height: chartHeight }}>
<Canvas style={{ width, height: chartHeight }}>
<Label state={state} y={y} graphs={graphs} width={width} height={height} />
<Group transform={[{ translateY }]}>
<Path style="stroke" path={path} strokeWidth={4} strokeJoin="round" strokeCap="round">
<LinearGradient start={vec(0, 0)} end={vec(width, 0)} colors={COLORS} />
</Path>
<Cursor x={x} y={y} width={width} />
</Group>
</Canvas>
<Animated.View pointerEvents="none" style={style} />
</View>
</GestureDetector>
<Selection state={state} transition={transition} graphs={graphs} />
<List />
</ScrollView>
Expand Down
51 changes: 26 additions & 25 deletions apps/example/src/Examples/Wallet/components/Cursor.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import { Circle, Group, Paint } from "@shopify/react-native-skia";
import React from "react";
import type { SharedValue } from "react-native-reanimated";
import {
interpolateColor,
useDerivedValue,
// In react-native-reanimated <= 3.1.0, convertToRGBA is not exported yet
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
convertToRGBA,
} from "react-native-reanimated";
import { Circle, Group, Paint } from '@shopify/react-native-skia';
import React from 'react';
import type { SharedValue } from 'react-native-reanimated';
import { interpolateColor, useDerivedValue } from 'react-native-reanimated';

import { COLORS } from "../Model";
import { COLORS } from '../Model';

const COLOR_STOPS =
COLORS.length <= 1 ? [0] : COLORS.map((_, index) => index / (COLORS.length - 1));

interface CursorProps {
x: SharedValue<number>;
Expand All @@ -19,19 +15,24 @@ interface CursorProps {
}

export const Cursor = ({ x, y, width }: CursorProps) => {
const color = useDerivedValue(() =>
convertToRGBA(
interpolateColor(
x.value / width,
COLORS.map((_, i) => i / COLORS.length),
COLORS
)
)
);
const transform = useDerivedValue(() => [
{ translateX: x.value },
{ translateY: y.value },
]);
const color = useDerivedValue(() => {
'worklet';
const ratio = width === 0 ? 0 : x.value / width;
const clamped = Math.min(Math.max(ratio, 0), 1);
const interpolated = interpolateColor(clamped, COLOR_STOPS, COLORS);
if (typeof interpolated === 'string') {
return interpolated;
}
const r = (interpolated & 0xff0000) >> 16;
const g = (interpolated & 0x00ff00) >> 8;
const b = interpolated & 0x0000ff;
const a = ((interpolated >>> 24) & 0xff) / 255;
return `rgba(${r}, ${g}, ${b}, ${a})`;
});
const transform = useDerivedValue(() => {
'worklet';
return [{ translateX: x.value }, { translateY: y.value }];
});
return (
<Group transform={transform}>
<Circle cx={0} cy={0} r={27} color={color} opacity={0.15} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import type { SharedValue } from "react-native-reanimated";
import { withDecay } from "react-native-reanimated";
import { Gesture } from "react-native-gesture-handler";
import { useMemo } from "react";
import { useMemo } from 'react';
import { Gesture } from 'react-native-gesture-handler';
import type { SharedValue } from 'react-native-reanimated';

const clamp = (value: number, lower: number, upper: number) => {
'worklet';
return Math.min(Math.max(value, lower), upper);
};

export const useGraphTouchHandler = (x: SharedValue<number>, width: number) => {
const gesture = useMemo(
() =>
Gesture.Pan()
.onChange((pos) => {
x.value += pos.x;
.minDistance(0)
.onStart((event) => {
'worklet';
x.value = clamp(event.x, 0, width);
})
.onChange((event) => {
'worklet';
x.value = clamp(event.x, 0, width);
})
.onEnd(({ velocityX }) => {
x.value = withDecay({ velocity: velocityX, clamp: [0, width] });
.onEnd((event) => {
'worklet';
// Snap to the last touched point instead of letting decay push it further.
x.value = clamp(event.x, 0, width);
}),
[width, x]
);
Expand Down