From 16699d98a5d7c46210d787ae7fcf106642464af3 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Thu, 21 May 2026 14:31:01 +0300 Subject: [PATCH] vendor progress circle --- package.json | 1 - pnpm-lock.yaml | 14 --- src/components/Progress/Circle.tsx | 85 +++++++++++++++++++ src/components/Progress/Pie.tsx | 70 +++++++++++++++ src/components/Progress/index.tsx | 2 + src/components/Progress/shared.ts | 6 ++ src/components/ProgressGuide/Task.tsx | 4 +- src/view/com/composer/Composer.tsx | 18 ++-- .../videos/VideoTranscodeProgress.tsx | 3 +- 9 files changed, 173 insertions(+), 30 deletions(-) create mode 100644 src/components/Progress/Circle.tsx create mode 100644 src/components/Progress/Pie.tsx create mode 100644 src/components/Progress/index.tsx create mode 100644 src/components/Progress/shared.ts diff --git a/package.json b/package.json index 2fda0beb2b..068c130ab9 100644 --- a/package.json +++ b/package.json @@ -222,7 +222,6 @@ "react-native-gesture-handler": "~2.28.0", "react-native-keyboard-controller": "^1.21.7", "react-native-pager-view": "6.8.0", - "react-native-progress": "bluesky-social/react-native-progress", "react-native-qrcode-styled": "^0.3.3", "react-native-reanimated": "3.19.1", "react-native-safe-area-context": "~5.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ab086e0b70..03dd909f04 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -454,9 +454,6 @@ importers: react-native-pager-view: specifier: 6.8.0 version: 6.8.0(patch_hash=0979d6fe1e2fa43b2784cc0b5e0ff0117d53b53423e85ee5855eefdc41a00108)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-progress: - specifier: bluesky-social/react-native-progress - version: https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4(react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)) react-native-qrcode-styled: specifier: ^0.3.3 version: 0.3.3(react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -8686,12 +8683,6 @@ packages: react: '*' react-native: '*' - react-native-progress@https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4: - resolution: {gitHosted: true, integrity: sha512-gDJJirvdhK5uUuI2OkiuleTbmeCyjkf1G6kPd4HAMc1pW8pUUeA0wKyPxZaI+s9htw8IDH6mFlmBB1ob8QMB4g==, tarball: https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4} - version: 5.0.0 - peerDependencies: - react-native-svg: '*' - react-native-qrcode-styled@0.3.3: resolution: {integrity: sha512-DjxFhFAJBY3y2FEBbWSpwtPaSjsEc5RutTHejxzfRNuyTzg1f5thplGyON8yx0kbi6h0h+57s8cH4aSFGj1mQg==} engines: {node: '>= 16.0.0'} @@ -19954,11 +19945,6 @@ snapshots: react: 19.1.0 react-native: 0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) - react-native-progress@https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4(react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)): - dependencies: - prop-types: 15.8.1 - react-native-svg: 15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-qrcode-styled@0.3.3(react-native-svg@15.12.1(patch_hash=31401fa6ff01498773afb51a7b066821c471eb3e71b0764a10017938beed49e9)(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(patch_hash=2656ac6deb71b92a4df4af4593d13ace6a5740936432e5e7e8ef32bc3cd05194)(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: qrcode: 1.5.4 diff --git a/src/components/Progress/Circle.tsx b/src/components/Progress/Circle.tsx new file mode 100644 index 0000000000..3369250101 --- /dev/null +++ b/src/components/Progress/Circle.tsx @@ -0,0 +1,85 @@ +import {useEffect} from 'react' +import Animated, { + Easing, + useAnimatedProps, + useSharedValue, + withTiming, +} from 'react-native-reanimated' +import Svg, {Circle as SvgCircle} from 'react-native-svg' + +import {clamp01} from './shared' + +const AnimatedCircle = Animated.createAnimatedComponent(SvgCircle) + +export function ProgressCircle({ + progress, + size, + color, + thickness = 3, + borderWidth = 1, + borderColor, + unfilledColor, +}: { + progress: number + size: number + color: string + thickness?: number + borderWidth?: number + borderColor?: string + unfilledColor?: string +}) { + const stroke = borderColor ?? color + const r = (size - Math.max(borderWidth, thickness)) / 2 + const cx = size / 2 + const circumference = 2 * Math.PI * r + const sv = useSharedValue(clamp01(progress)) + + useEffect(() => { + sv.set( + withTiming(clamp01(progress), { + duration: 200, + easing: Easing.out(Easing.quad), + }), + ) + }, [progress, sv]) + + const animatedProps = useAnimatedProps(() => ({ + strokeDashoffset: circumference * (1 - sv.get()), + })) + + return ( + + {borderWidth > 0 && ( + + )} + {unfilledColor && ( + + )} + + + ) +} diff --git a/src/components/Progress/Pie.tsx b/src/components/Progress/Pie.tsx new file mode 100644 index 0000000000..89eda7078e --- /dev/null +++ b/src/components/Progress/Pie.tsx @@ -0,0 +1,70 @@ +import {useEffect} from 'react' +import Animated, { + Easing, + useAnimatedProps, + useSharedValue, + withTiming, +} from 'react-native-reanimated' +import Svg, {Circle as SvgCircle, Path} from 'react-native-svg' + +import {clamp01} from './shared' + +const AnimatedPath = Animated.createAnimatedComponent(Path) + +function describeWedge(cx: number, cy: number, r: number, progress: number) { + 'worklet' + // avoid degenerate path at 360deg where start and end points collide + const p = progress >= 1 ? 0.9999 : progress + if (p <= 0) return '' + const angle = p * 2 * Math.PI - Math.PI / 2 + const x = cx + r * Math.cos(angle) + const y = cy + r * Math.sin(angle) + const largeArc = p > 0.5 ? 1 : 0 + return `M ${cx} ${cy} L ${cx} ${cy - r} A ${r} ${r} 0 ${largeArc} 1 ${x} ${y} Z` +} + +export function ProgressPie({ + progress, + size, + color, + borderWidth = 1, + borderColor, +}: { + progress: number + size: number + color: string + borderWidth?: number + borderColor?: string +}) { + const stroke = borderColor ?? color + const r = (size - borderWidth) / 2 + const cx = size / 2 + const sv = useSharedValue(clamp01(progress)) + + useEffect(() => { + sv.set( + withTiming(clamp01(progress), { + duration: 200, + easing: Easing.out(Easing.quad), + }), + ) + }, [progress, sv]) + + const animatedProps = useAnimatedProps(() => ({ + d: describeWedge(cx, cx, r, sv.get()), + })) + + return ( + + + + + ) +} diff --git a/src/components/Progress/index.tsx b/src/components/Progress/index.tsx new file mode 100644 index 0000000000..09e7143aa5 --- /dev/null +++ b/src/components/Progress/index.tsx @@ -0,0 +1,2 @@ +export {ProgressCircle} from './Circle' +export {ProgressPie} from './Pie' diff --git a/src/components/Progress/shared.ts b/src/components/Progress/shared.ts new file mode 100644 index 0000000000..800cce46e3 --- /dev/null +++ b/src/components/Progress/shared.ts @@ -0,0 +1,6 @@ +export function clamp01(n: number) { + 'worklet' + if (n < 0) return 0 + if (n > 1) return 1 + return n +} diff --git a/src/components/ProgressGuide/Task.tsx b/src/components/ProgressGuide/Task.tsx index 997c777af6..88b035e60a 100644 --- a/src/components/ProgressGuide/Task.tsx +++ b/src/components/ProgressGuide/Task.tsx @@ -1,8 +1,8 @@ import {View} from 'react-native' -import * as Progress from 'react-native-progress' import {atoms as a, useTheme} from '#/alf' import {AnimatedCheck} from '../anim/AnimatedCheck' +import {ProgressCircle} from '../Progress' import {Text} from '../Typography' export function ProgressGuideTask({ @@ -25,7 +25,7 @@ export function ProgressGuideTask({ {current === total ? ( ) : ( - - + ) @@ -2413,7 +2409,7 @@ function ToolbarWrapper({ if (IS_WEB) return children return ( {children} @@ -2488,12 +2484,12 @@ function VideoUploadToolbar({state}: {state: VideoState}) { } return ( - +