Files
Sauci/apps/mobile/src/features/chat/components/UploadProgress.tsx
T
jjbruntonandClaude Fable 5 6bc4e31719 chore(mobile): upgrade to Expo SDK 56
Bump expo, react, react-native, and all expo-* / react-native-* peers to
the SDK 56 line via expo install --fix, adding expo-splash-screen and
expo-system-ui for config plugins the SDK split out of core. Bump
@bacons/apple-targets, react-native-view-shot, and other native modules
to versions compatible with React Native 0.85 and the new architecture.

Remove @react-navigation/* direct dependencies and route their hooks
through expo-router, which is no longer compatible with react-navigation
as of SDK 56. Fix RN 0.85's removed StyleSheet.absoluteFillObject (use
absoluteFill) and react-native-view-shot 5.x's ViewShotRef type across
call sites. Downgrade eslint-plugin-react-hooks v7's new React Compiler
era rules to warnings pending a dedicated refactor, and repair Jest
fallout from the jest-expo/reanimated/worklets/expo-media-library bumps.

Regenerate the committed ios/ and android/ native projects with
`expo prebuild --clean` (required for @bacons/apple-targets 5.x) against
the production release channel, and document that requirement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 11:11:37 +01:00

152 lines
4.6 KiB
TypeScript

import React, { useEffect } from 'react';
import { View, Text, Image, ActivityIndicator, StyleSheet } from 'react-native';
import { LinearGradient } from 'expo-linear-gradient';
import { Ionicons } from '@expo/vector-icons';
import Animated, {
FadeIn,
useSharedValue,
useAnimatedStyle,
withRepeat,
withTiming,
interpolate,
Easing,
} from 'react-native-reanimated';
import { colors, gradients, spacing, radius } from '../../../theme';
import { UploadStatus } from '../types';
export interface UploadProgressProps {
uploadStatus: UploadStatus;
}
const ACCENT = colors.premium.gold;
/**
* Upload progress skeleton component showing when media is being uploaded.
* Displays a shimmer effect with thumbnail preview for images.
*/
export function UploadProgress({ uploadStatus }: UploadProgressProps) {
const shimmer = useSharedValue(0);
useEffect(() => {
shimmer.value = withRepeat(
withTiming(1, { duration: 1500, easing: Easing.inOut(Easing.ease) }),
-1,
false
);
}, []);
const shimmerStyle = useAnimatedStyle(() => ({
opacity: interpolate(shimmer.value, [0, 0.5, 1], [0.3, 0.6, 0.3]),
}));
if (!uploadStatus) return null;
const isVideo = uploadStatus.mediaType === 'video';
const statusText = uploadStatus.status === 'compressing'
? 'Compressing video...'
: `Uploading ${isVideo ? 'video' : 'image'}...`;
return (
<Animated.View
entering={FadeIn.duration(200)}
style={styles.container}
>
<View style={styles.bubbleContainer}>
<LinearGradient
colors={gradients.primary as [string, string]}
style={styles.bubble}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
>
{/* Silk highlight */}
<LinearGradient
colors={['rgba(255, 255, 255, 0.15)', 'transparent']}
style={styles.silkHighlight}
start={{ x: 0.5, y: 0 }}
end={{ x: 0.5, y: 1 }}
/>
{/* Skeleton media placeholder */}
<Animated.View style={[styles.mediaSkeleton, shimmerStyle]}>
{uploadStatus.thumbnailUri && !isVideo ? (
<Image
source={{ uri: uploadStatus.thumbnailUri }}
style={styles.thumbnail}
blurRadius={10}
/>
) : (
<View style={styles.iconContainer}>
<Ionicons
name={isVideo ? 'videocam' : 'image'}
size={32}
color={colors.textTertiary}
/>
</View>
)}
{/* Overlay with spinner and status */}
<View style={styles.overlay}>
<ActivityIndicator color={ACCENT} size="small" />
<Text style={styles.statusText}>{statusText}</Text>
</View>
</Animated.View>
</LinearGradient>
</View>
</Animated.View>
);
}
const styles = StyleSheet.create({
container: {
alignItems: 'flex-end',
marginBottom: spacing.sm,
paddingHorizontal: spacing.md,
},
bubbleContainer: {
maxWidth: '80%',
},
bubble: {
borderRadius: radius.xl,
borderTopRightRadius: radius.sm,
overflow: 'hidden',
minWidth: 200,
},
silkHighlight: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 40,
},
mediaSkeleton: {
height: 180,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.2)',
},
thumbnail: {
...StyleSheet.absoluteFill,
},
iconContainer: {
width: 64,
height: 64,
borderRadius: 32,
backgroundColor: 'rgba(255, 255, 255, 0.1)',
justifyContent: 'center',
alignItems: 'center',
},
overlay: {
...StyleSheet.absoluteFill,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'center',
alignItems: 'center',
gap: spacing.sm,
},
statusText: {
color: colors.textSecondary,
fontSize: 13,
},
});
export default UploadProgress;