Files
JKVideo/components/DownloadProgressBtn.tsx
Developer 463c0db058 feat: 性能优化 + Bug修复 + 搜索增强
- expo-image 替换 RN Image(VideoCard/LiveCard/BigVideoCard/CommentItem,recyclingKey)
- DanmakuList Animated.Value 对象池,减少 GC 压力
- FlatList 性能参数:windowSize=7 / maxToRenderPerBatch=6 / removeClippedSubviews
- bilibili.ts 请求去重(getVideoDetail/getPlayUrl)
- SESSDATA 迁移至 expo-secure-store(utils/secureStorage.ts,启动自动迁移)
- 主题系统扩展:新增 sheetBg/modalBg/modalText/placeholder/iconDefault/danger 等 token
- 多组件深色模式适配:DownloadSheet/LivePlayer/NativeVideoPlayer/DownloadProgressBtn/CommentItem/LoginModal
- 搜索页增强:搜索建议 + 热搜榜(hooks/useSearch.ts + app/search.tsx)
- LoginModal 修复轮询竞态(cancelled flag + try-catch)
- 修复 downloads.tsx 冗余三元表达式
2026-03-26 01:16:20 +08:00

163 lines
4.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import { View, TouchableOpacity, StyleSheet } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useDownloadStore } from '../store/downloadStore';
import { useTheme } from '../utils/theme';
const SIZE = 32; // 环外径
const RING = 3; // 环宽
const BLUE = '#00AEEC';
const INNER = SIZE - RING * 2;
interface Props {
onPress: () => void;
}
export function DownloadProgressBtn({ onPress }: Props) {
const theme = useTheme();
const tasks = useDownloadStore((s) => s.tasks);
const downloading = Object.values(tasks).filter((t) => t.status === 'downloading');
const hasDownloading = downloading.length > 0;
const avgProgress = hasDownloading
? downloading.reduce((sum, t) => sum + t.progress, 0) / downloading.length
: 0;
return (
<TouchableOpacity onPress={onPress} style={styles.btn} activeOpacity={0.7}>
{/* 进度环,绝对定位居中 */}
{/* {hasDownloading && (
<View style={styles.ringWrap} pointerEvents="none">
<RingProgress progress={avgProgress} />
</View>
)} */}
<Ionicons
name="cloud-download-outline"
size={22}
color={hasDownloading ? BLUE : theme.iconDefault}
/>
</TouchableOpacity>
);
}
/**
* 双半圆裁剪法绘制圆弧进度
*
* 两个「D 形」(实心半圆) 分别放在左/右裁剪容器中,
* 旋转 D 形就能在容器内扫出弧形,配合白色内圆变成环形。
*
* 旋转轴 = D 形 View 的默认中心 = 外圆圆心,无需 transformOrigin。
*/
function RingProgress({ progress }: { progress: number }) {
const p = Math.max(0, Math.min(1, progress));
// 右半弧:进度 0→50%,右 D 从 -180°→0°顺时针
const rightAngle = -180 + Math.min(p * 2, 1) * 180;
// 左半弧:进度 50%→100%,左 D 从 180°→0°
const leftAngle = 180 - Math.max(0, p * 2 - 1) * 180;
return (
<View style={styles.ring}>
{/* 灰色背景环 */}
<View style={styles.ringBg} />
{/* ── 右裁剪:左边缘 = 圆心,只露右半 ── */}
<View style={styles.rightClip}>
{/* left: -SIZE/2 → D 形中心落在容器左边缘 = 外圆圆心 */}
<View style={[styles.dRight, { transform: [{ rotate: `${rightAngle}deg` }] }]} />
</View>
{/* ── 左裁剪:右边缘 = 圆心,只露左半 ── */}
<View style={styles.leftClip}>
{/* left: 0 → D 形中心在容器右边缘 = 外圆圆心 */}
<View style={[styles.dLeft, { transform: [{ rotate: `${leftAngle}deg` }] }]} />
</View>
{/* 白色内圆,把实心扇区变成环形 */}
<View style={styles.inner} />
</View>
);
}
const styles = StyleSheet.create({
btn: {
width: SIZE + 8,
height: SIZE + 8,
alignItems: 'center',
justifyContent: 'center',
},
// 绝对定位居中overflow:hidden 防止 D 形溢出
ringWrap: {
position: 'absolute',
top: 4,
left: 4,
width: SIZE,
height: SIZE,
overflow: 'hidden',
},
ring: {
width: SIZE,
height: SIZE,
alignItems: 'center',
justifyContent: 'center',
},
ringBg: {
position: 'absolute',
width: SIZE,
height: SIZE,
borderRadius: SIZE / 2,
borderWidth: RING,
borderColor: '#e0e0e0',
},
// 右裁剪容器left = SIZE/2圆心处宽 SIZE/2只露右半
rightClip: {
position: 'absolute',
left: SIZE / 2,
top: 0,
width: SIZE / 2,
height: SIZE,
overflow: 'hidden',
},
// 右 D 形右半圆left = -SIZE/2 → center = (0, SIZE/2) in clip = 外圆圆心
dRight: {
position: 'absolute',
left: -SIZE / 2,
top: 0,
width: SIZE,
height: SIZE,
borderTopRightRadius: SIZE / 2,
borderBottomRightRadius: SIZE / 2,
backgroundColor: BLUE,
},
// 左裁剪容器right = SIZE/2圆心处宽 SIZE/2只露左半
leftClip: {
position: 'absolute',
left: 0,
top: 0,
width: SIZE / 2,
height: SIZE,
overflow: 'hidden',
},
// 左 D 形左半圆left = 0 → center = (SIZE/2, SIZE/2) in clip = 外圆圆心
dLeft: {
position: 'absolute',
left: 0,
top: 0,
width: SIZE,
height: SIZE,
borderTopLeftRadius: SIZE / 2,
borderBottomLeftRadius: SIZE / 2,
backgroundColor: BLUE,
},
// 白色内圆(遮住 D 形中心,留出环宽)
inner: {
width: INNER,
height: INNER,
borderRadius: INNER / 2,
backgroundColor: '#fff',
},
});