From 4cbd64396482f0776b057adaa06e5ea8fbc625a6 Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Tue, 4 Aug 2026 10:49:34 -0400 Subject: [PATCH] Address video progress review feedback --- src/view/com/composer/Composer.tsx | 55 ++++++------------- src/view/com/composer/state/video.ts | 21 +++++-- .../com/composer/state/videoProgress.test.ts | 21 ++++++- src/view/com/composer/state/videoProgress.ts | 15 ++++- 4 files changed, 65 insertions(+), 47 deletions(-) diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index fd7491c51f..5cb96bceb5 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -25,7 +25,6 @@ import {Circle as ProgressCircle} from 'react-native-progress' import Animated, { type AnimatedRef, type AnimatedStyle, - Easing, FadeIn, FadeOut, interpolateColor, @@ -37,7 +36,6 @@ import Animated, { useAnimatedStyle, useDerivedValue, useSharedValue, - withRepeat, withTiming, ZoomIn, ZoomOut, @@ -180,6 +178,7 @@ import { processVideo, type VideoState, } from './state/video' +import {videoProgressWithinPhase} from './state/videoProgress' import {type TextInputRef} from './text-input/TextInput.types' import {getVideoMetadata} from './videos/metadata' import {clearThumbnailCache} from './videos/VideoTranscodeBackdrop' @@ -1996,7 +1995,10 @@ function ComposerEmbeds({ (video.status === 'compressing' ? ( ) : video.video ? ( @@ -2672,31 +2674,8 @@ function VideoUploadToolbar({state}: {state: VideoState}) { const t = useTheme() const {t: l} = useLingui() const progress = state.progress - const processingProgress = - state.status === 'processing' ? state.jobStatus?.progress : undefined - const shouldRotate = - state.status === 'processing' && processingProgress === undefined let wheelProgress = progress - const rotate = useDerivedValue(() => { - if (shouldRotate) { - return withRepeat( - withTiming(360, { - duration: 2500, - easing: Easing.out(Easing.cubic), - }), - -1, - ) - } - return 0 - }) - - const animatedStyle = useAnimatedStyle(() => { - return { - transform: [{rotateZ: `${rotate.get()}deg`}], - } - }) - let text = '' const isGif = state.video?.mimeType === 'image/gif' @@ -2738,19 +2717,17 @@ function VideoUploadToolbar({state}: {state: VideoState}) { return ( - - - + {text} ) diff --git a/src/view/com/composer/state/video.ts b/src/view/com/composer/state/video.ts index 80276aaf06..5becb1568d 100644 --- a/src/view/com/composer/state/video.ts +++ b/src/view/com/composer/state/video.ts @@ -25,6 +25,7 @@ export type VideoAction = | { type: 'compressing_to_uploading' video: CompressedVideo + compressionSkipped: boolean signal: AbortSignal } | { @@ -103,6 +104,7 @@ type CompressingState = { type UploadingState = { status: 'uploading' progress: number + compressionSkipped: boolean abortController: AbortController asset: ImagePickerAsset video: CompressedVideo @@ -186,13 +188,13 @@ export function videoReducer( } } else if (action.type === 'update_progress') { if (state.status === 'compressing' || state.status === 'uploading') { + const phase = + state.status === 'uploading' && state.compressionSkipped + ? 'uploadingWithoutCompression' + : state.status return { ...state, - progress: advanceVideoProgress( - state.progress, - state.status, - action.progress, - ), + progress: advanceVideoProgress(state.progress, phase, action.progress), } } } else if (action.type === 'update_alt_text') { @@ -209,7 +211,13 @@ export function videoReducer( if (state.status === 'compressing') { return { status: 'uploading', - progress: videoProgressForPhase('uploading', 0), + progress: videoProgressForPhase( + action.compressionSkipped + ? 'uploadingWithoutCompression' + : 'uploading', + 0, + ), + compressionSkipped: action.compressionSkipped, abortController: state.abortController, asset: state.asset, video: action.video, @@ -323,6 +331,7 @@ export async function processVideo( dispatch({ type: 'compressing_to_uploading', video, + compressionSkipped: video.passthroughReason !== undefined, signal, }) diff --git a/src/view/com/composer/state/videoProgress.test.ts b/src/view/com/composer/state/videoProgress.test.ts index 3ca703131c..53e662c3fe 100644 --- a/src/view/com/composer/state/videoProgress.test.ts +++ b/src/view/com/composer/state/videoProgress.test.ts @@ -1,4 +1,8 @@ -import {advanceVideoProgress, videoProgressForPhase} from './videoProgress' +import { + advanceVideoProgress, + videoProgressForPhase, + videoProgressWithinPhase, +} from './videoProgress' describe('videoProgressForPhase', () => { it('maps each phase onto one continuous timeline', () => { @@ -6,6 +10,8 @@ describe('videoProgressForPhase', () => { expect(videoProgressForPhase('compressing', 1)).toBe(0.3) expect(videoProgressForPhase('uploading', 0)).toBe(0.3) expect(videoProgressForPhase('uploading', 1)).toBe(0.5) + expect(videoProgressForPhase('uploadingWithoutCompression', 0)).toBe(0) + expect(videoProgressForPhase('uploadingWithoutCompression', 1)).toBe(0.5) expect(videoProgressForPhase('processing', 0)).toBe(0.5) expect(videoProgressForPhase('processing', 0.5)).toBe(0.75) expect(videoProgressForPhase('processing', 1)).toBe(1) @@ -17,6 +23,19 @@ describe('videoProgressForPhase', () => { }) }) +describe('videoProgressWithinPhase', () => { + it('maps global progress back to phase-local progress', () => { + expect(videoProgressWithinPhase('compressing', 0)).toBe(0) + expect(videoProgressWithinPhase('compressing', 0.15)).toBe(0.5) + expect(videoProgressWithinPhase('compressing', 0.3)).toBe(1) + }) + + it('clamps progress outside the phase', () => { + expect(videoProgressWithinPhase('compressing', -1)).toBe(0) + expect(videoProgressWithinPhase('compressing', 1)).toBe(1) + }) +}) + describe('advanceVideoProgress', () => { it('does not move backwards when a transport retries or falls back', () => { const progressed = advanceVideoProgress(0.5, 'uploading', 0.8) diff --git a/src/view/com/composer/state/videoProgress.ts b/src/view/com/composer/state/videoProgress.ts index ba63bb5ef7..8d43064c2f 100644 --- a/src/view/com/composer/state/videoProgress.ts +++ b/src/view/com/composer/state/videoProgress.ts @@ -1,4 +1,8 @@ -export type VideoProgressPhase = 'compressing' | 'uploading' | 'processing' +export type VideoProgressPhase = + | 'compressing' + | 'uploading' + | 'uploadingWithoutCompression' + | 'processing' // Keep progress monotonic across the full client pipeline instead of showing // three separate 0 -> 100 cycles. Backend processing progress covers the @@ -6,6 +10,7 @@ export type VideoProgressPhase = 'compressing' | 'uploading' | 'processing' const PHASE_RANGES: Record = { compressing: [0, 0.3], uploading: [0.3, 0.5], + uploadingWithoutCompression: [0, 0.5], processing: [0.5, 1], } @@ -18,6 +23,14 @@ export function videoProgressForPhase( return start + (end - start) * clamped } +export function videoProgressWithinPhase( + phase: VideoProgressPhase, + progress: number, +): number { + const [start, end] = PHASE_RANGES[phase] + return Math.min(1, Math.max(0, (progress - start) / (end - start))) +} + export function advanceVideoProgress( currentProgress: number, phase: VideoProgressPhase,