mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-19 04:06:43 +02:00
110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
import { Pressable, View } from 'react-native';
|
|
import { Text } from '@/components/Text';
|
|
import { spacing } from '@/theme';
|
|
import { useRipple } from '@/theme/ripple';
|
|
import { createThemedStyles, useColors } from '@/theme/themed';
|
|
import { useLibraryStore } from '@/stores/libraryStore';
|
|
|
|
/** Thin accent bar + caption shown under the library header while scanning. */
|
|
export function ScanProgress() {
|
|
const styles = useStyles();
|
|
const colors = useColors();
|
|
const ripple = useRipple();
|
|
const isScanning = useLibraryStore((s) => s.isScanning);
|
|
const isCancelling = useLibraryStore((s) => s.isCancelling);
|
|
const progress = useLibraryStore((s) => s.scanProgress);
|
|
const cancelScan = useLibraryStore((s) => s.cancelScan);
|
|
|
|
if (!isScanning) return null;
|
|
|
|
const label =
|
|
progress.phase === 'analyzing'
|
|
? `Analyzing audio… ${progress.processed}/${progress.total}`
|
|
: progress.phase === 'extracting'
|
|
? `Scanning ${progress.folderName ?? ''}… ${progress.processed}/${progress.total}`
|
|
: progress.total > 0
|
|
? `Found ${progress.total} files in ${progress.folderName ?? ''}…`
|
|
: `Looking for music${progress.folderName ? ` in ${progress.folderName}` : ''}…`;
|
|
|
|
const fraction =
|
|
(progress.phase === 'extracting' || progress.phase === 'analyzing') && progress.total > 0
|
|
? progress.processed / progress.total
|
|
: 0;
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<View style={styles.labelRow}>
|
|
<Text
|
|
variant="caption"
|
|
color={colors.textSecondary}
|
|
numberOfLines={1}
|
|
style={styles.label}
|
|
>
|
|
{label}
|
|
</Text>
|
|
<Pressable
|
|
android_ripple={ripple.bounded}
|
|
disabled={isCancelling}
|
|
onPress={cancelScan}
|
|
accessibilityRole="button"
|
|
accessibilityLabel={isCancelling ? 'Cancelling library scan' : 'Cancel library scan'}
|
|
accessibilityState={{ disabled: isCancelling, busy: isCancelling }}
|
|
hitSlop={6}
|
|
style={styles.cancelButton}
|
|
>
|
|
<Text
|
|
variant="caption"
|
|
color={isCancelling ? colors.textTertiary : colors.warning}
|
|
>
|
|
{isCancelling ? 'Cancelling…' : 'Cancel'}
|
|
</Text>
|
|
</Pressable>
|
|
</View>
|
|
<View style={styles.track}>
|
|
<View
|
|
style={[
|
|
styles.fill,
|
|
// Indeterminate discovery phase shows a faint full-width bar.
|
|
fraction > 0 ? { width: `${fraction * 100}%` } : styles.fillIndeterminate,
|
|
]}
|
|
/>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const useStyles = createThemedStyles((colors) => ({
|
|
container: {
|
|
gap: spacing.xs,
|
|
marginBottom: spacing.md,
|
|
},
|
|
labelRow: {
|
|
minHeight: 32,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: spacing.sm,
|
|
},
|
|
label: {
|
|
flex: 1,
|
|
},
|
|
cancelButton: {
|
|
minHeight: 32,
|
|
justifyContent: 'center',
|
|
paddingHorizontal: spacing.xs,
|
|
},
|
|
track: {
|
|
height: 2,
|
|
backgroundColor: colors.glassBorder,
|
|
borderRadius: 1,
|
|
overflow: 'hidden',
|
|
},
|
|
fill: {
|
|
height: 2,
|
|
backgroundColor: colors.accent,
|
|
},
|
|
fillIndeterminate: {
|
|
width: '100%',
|
|
opacity: 0.35,
|
|
},
|
|
}));
|