feat(app): 完成 P0 钱包解锁、下载落盘与手机号登录

This commit is contained in:
Zhou Weidong committed 2026-10-19 23:46:47 +08:00
1 parent de941c841c
commit 136ee74618
47 files changed
+3264 -70

No files matched your search

+71 -3
View File
@@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
import '../../core/data/models/download_license.dart';
import '../../core/data/models/download_policy.dart';
import '../../core/data/models/drama.dart';
import '../../core/data/services/auth_controller.dart';
import '../../core/data/services/download_controller.dart';
import '../../core/formatting/error_text.dart';
@@ -18,6 +19,7 @@ import '../../core/theme/app_theme.dart';
import '../../core/widgets/auth_gate.dart';
import '../../core/widgets/empty_state.dart';
import '../../l10n/app_localizations.dart';
import '../player/player_view.dart';
import 'downloads_binding.dart';
/// 下载授权与状态管理页;本批不落盘,只管理服务端授权。
@@ -135,7 +137,12 @@ class _DownloadsBody extends StatelessWidget {
for (final license in downloads.licenses) ...[
_LicenseTile(
license: license,
downloads: downloads,
onRemove: () => _remove(context, license.episodeId),
onPlay: () => _playOffline(context, license),
onDownload: () => unawaited(
downloads.startDownload(license.episodeId, license: license),
),
),
const SizedBox(height: 10),
],
@@ -144,6 +151,32 @@ class _DownloadsBody extends StatelessWidget {
);
}
void _playOffline(BuildContext context, DownloadLicense license) {
final drama = Drama(
id: license.dramaId,
title: license.dramaTitle,
synopsis: '',
posterUrl: '',
episodes: [
Episode(
id: license.episodeId,
number: license.episodeNo,
title: 'Episode ${license.episodeNo}',
isFree: true,
),
],
);
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => PlayerPage(
drama: drama,
initialIndex: 0,
downloads: downloads,
),
),
);
}
Future<void> _remove(BuildContext context, String episodeId) async {
unawaited(context.read<Haptics>().mediumImpact());
final ok = await downloads.remove(episodeId);
@@ -302,14 +335,35 @@ class _PolicyPanel extends StatelessWidget {
}
class _LicenseTile extends StatelessWidget {
const _LicenseTile({required this.license, required this.onRemove});
const _LicenseTile({
required this.license,
required this.downloads,
required this.onRemove,
required this.onPlay,
required this.onDownload,
});
final DownloadLicense license;
final DownloadController downloads;
final VoidCallback onRemove;
final VoidCallback onPlay;
final VoidCallback onDownload;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final downloaded = downloads.isDownloaded(license.episodeId);
final downloading = downloads.isDownloading(license.episodeId);
final progress = downloaded
? 1.0
: downloading
? downloads.progressFor(license.episodeId)
: license.progressPercent / 100;
final taskStatus = downloaded
? 'COMPLETED'
: downloading
? 'DOWNLOADING'
: license.taskStatus;
return Container(
key: ValueKey('download-license-${license.episodeId}'),
padding: const EdgeInsets.all(12),
@@ -335,6 +389,20 @@ class _LicenseTile extends StatelessWidget {
),
),
Text(l10n.episodeLabel(license.episodeNo)),
if (downloaded)
IconButton(
key: ValueKey('download-play-${license.episodeId}'),
tooltip: l10n.play,
onPressed: onPlay,
icon: const Icon(Icons.play_circle_outline_rounded),
)
else if (!downloading)
IconButton(
key: ValueKey('download-start-${license.episodeId}'),
tooltip: l10n.downloads,
onPressed: onDownload,
icon: const Icon(Icons.download_rounded),
),
IconButton(
key: ValueKey('download-remove-${license.episodeId}'),
tooltip: l10n.remove,
@@ -352,7 +420,7 @@ class _LicenseTile extends StatelessWidget {
style: const TextStyle(color: AppColors.blueSoft, fontSize: 12),
),
Text(
l10n.downloadTaskStatus(license.taskStatus),
l10n.downloadTaskStatus(taskStatus),
style: const TextStyle(color: AppColors.muted, fontSize: 12),
),
Text(
@@ -371,7 +439,7 @@ class _LicenseTile extends StatelessWidget {
borderRadius: BorderRadius.circular(AppDimens.progressRadius),
child: LinearProgressIndicator(
minHeight: AppDimens.progressHeight,
value: (license.progressPercent / 100).clamp(0.0, 1.0),
value: progress.clamp(0.0, 1.0),
backgroundColor: Colors.white24,
valueColor: const AlwaysStoppedAnimation(AppColors.blueSoft),
),