1
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../core/media/download_manager.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../models/video_item.dart';
|
||||
|
||||
/// 已下载视频列表(我的 → 已下载):
|
||||
/// 下载中任务(进度/取消)+ 已完成列表(点击播放 / 左滑或长按删除)
|
||||
class DownloadsScreen extends ConsumerWidget {
|
||||
const DownloadsScreen({super.key});
|
||||
|
||||
static String _fmtSize(int bytes) {
|
||||
if (bytes >= 1 << 30) return '${(bytes / (1 << 30)).toStringAsFixed(2)} GB';
|
||||
if (bytes >= 1 << 20) return '${(bytes / (1 << 20)).toStringAsFixed(1)} MB';
|
||||
return '${(bytes / 1024).toStringAsFixed(0)} KB';
|
||||
}
|
||||
|
||||
static String _fmtDuration(int seconds) {
|
||||
final m = seconds ~/ 60;
|
||||
final s = seconds % 60;
|
||||
return '$m:${s.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
void _play(BuildContext context, DownloadItem item) {
|
||||
context.push('/player',
|
||||
extra: VideoItem(
|
||||
videoId: item.videoId,
|
||||
title: item.title,
|
||||
coverUrl: item.coverUrl,
|
||||
videoUrl: item.playUrl,
|
||||
duration: item.duration,
|
||||
author: VideoAuthor(userId: 0),
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> _confirmDelete(
|
||||
BuildContext context, WidgetRef ref, DownloadItem item) async {
|
||||
final ok = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('删除下载'),
|
||||
content: Text('删除「${item.title}」?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx, false),
|
||||
child: const Text('取消')),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx, true),
|
||||
child: const Text('删除', style: TextStyle(color: Colors.red))),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (ok == true) {
|
||||
ref.read(downloadManagerProvider.notifier).delete(item.videoId);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(downloadManagerProvider);
|
||||
final downloading = state.progress.entries.toList();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('已下载')),
|
||||
body: ListView(
|
||||
children: [
|
||||
// 下载中
|
||||
for (final e in downloading)
|
||||
ListTile(
|
||||
leading: const SizedBox(
|
||||
width: 56, height: 56,
|
||||
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
|
||||
),
|
||||
title: Text('视频 #${e.key}',
|
||||
maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: LinearProgressIndicator(value: e.value),
|
||||
),
|
||||
trailing: Text('${(e.value * 100).toStringAsFixed(0)}%'),
|
||||
onLongPress: () =>
|
||||
ref.read(downloadManagerProvider.notifier).cancel(e.key),
|
||||
),
|
||||
|
||||
if (downloading.isNotEmpty) const Divider(),
|
||||
|
||||
// 已完成
|
||||
if (state.items.isEmpty && downloading.isEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 120),
|
||||
child: Center(
|
||||
child: Text('暂无下载\n长按视频可下载',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: AppColors.textWeak)),
|
||||
),
|
||||
),
|
||||
for (final item in state.items)
|
||||
ListTile(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||
leading: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: SizedBox(
|
||||
width: 72, height: 48,
|
||||
child: item.coverUrl.isNotEmpty
|
||||
? CachedNetworkImage(
|
||||
imageUrl: item.coverUrl, fit: BoxFit.cover)
|
||||
: Container(
|
||||
color: Colors.grey[800],
|
||||
child: const Icon(Icons.play_circle_outline,
|
||||
color: Colors.white54),
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(item.title.isEmpty ? '视频 #${item.videoId}' : item.title,
|
||||
maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
subtitle: Text(
|
||||
'${_fmtDuration(item.duration)} · ${_fmtSize(item.sizeBytes)}',
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.delete_outline,
|
||||
color: AppColors.textWeak),
|
||||
onPressed: () => _confirmDelete(context, ref, item),
|
||||
),
|
||||
onTap: () => _play(context, item),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user