feat(flutter): 接入分类记录收藏与下载授权

This commit is contained in:
Zhou Weidong committed 2026-10-17 14:49:16 +08:00
1 parent 00280ca0da
commit cc4d7c5a9d
52 files changed
+4440 -171

No files matched your search

@@ -0,0 +1,9 @@
// Author: fruit.com.cn
import '../../core/data/services/favorite_controller.dart';
/// 收藏页依赖装配。
class FavoritesBinding {
/// 从服务端刷新收藏列表。
Future<void> load(FavoriteController favorites) => favorites.load();
}
+148
View File
@@ -0,0 +1,148 @@
// Author: fruit.com.cn
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../core/data/models/drama.dart';
import '../../core/data/services/auth_controller.dart';
import '../../core/data/services/favorite_controller.dart';
import '../../core/formatting/error_text.dart';
import '../../core/widgets/auth_gate.dart';
import '../../core/widgets/library_drama_tile.dart';
import '../../l10n/app_localizations.dart';
import '../drama_detail/drama_detail_view.dart';
import 'favorites_binding.dart';
/// 独立我的收藏页。
class FavoritesPage extends StatefulWidget {
const FavoritesPage({super.key});
@override
State<FavoritesPage> createState() => _FavoritesPageState();
}
class _FavoritesPageState extends State<FavoritesPage> {
bool _requested = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
final signedIn = context.watch<AuthController>().isSignedIn;
if (!signedIn) {
_requested = false;
return;
}
if (_requested) return;
_requested = true;
final favorites = context.read<FavoriteController>();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) unawaited(FavoritesBinding().load(favorites));
});
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final signedIn = context.watch<AuthController>().isSignedIn;
return Scaffold(
backgroundColor: const Color(0xFF0B0C10),
appBar: AppBar(
backgroundColor: const Color(0xFF0B0C10),
foregroundColor: Colors.white,
title: Text(l10n.myFavorites),
),
body: signedIn
? _FavoritesBody(favorites: context.watch<FavoriteController>())
: const AuthGatePanel(),
);
}
}
class _FavoritesBody extends StatelessWidget {
const _FavoritesBody({required this.favorites});
final FavoriteController favorites;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
if (favorites.loading && favorites.cards.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
if (favorites.error != null && favorites.cards.isEmpty) {
return Center(
key: const Key('favorites-error'),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
errorText(l10n, favorites.error, l10n.favoritesError),
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white70),
),
const SizedBox(height: 14),
FilledButton(
key: const Key('favorites-retry'),
onPressed: () => unawaited(favorites.load()),
child: Text(l10n.retry),
),
],
),
),
);
}
if (favorites.cards.isEmpty) {
return Center(
key: const Key('favorites-empty'),
child: Text(
l10n.noFavorites,
style: const TextStyle(color: Colors.white70),
),
);
}
return RefreshIndicator(
onRefresh: favorites.load,
child: ListView.separated(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 28),
itemCount: favorites.cards.length,
separatorBuilder: (_, _) => const SizedBox(height: 10),
itemBuilder: (context, index) {
final drama = favorites.cards[index];
return LibraryDramaTile(
key: ValueKey('favorite-card-${drama.id}'),
drama: drama,
onTap: () => _open(context, drama),
trailing: IconButton(
key: ValueKey('favorite-remove-${drama.id}'),
tooltip: l10n.remove,
onPressed: () => _remove(context, drama.id),
icon: const Icon(
Icons.favorite_rounded,
color: Color(0xFFD74A7D),
),
),
);
},
),
);
}
Future<void> _open(BuildContext context, Drama drama) async {
await Navigator.of(context).push<void>(
MaterialPageRoute(builder: (_) => DramaDetailPage(drama: drama)),
);
}
Future<void> _remove(BuildContext context, String dramaId) async {
final ok = await favorites.remove(dramaId);
if (ok || !context.mounted) return;
final l10n = AppLocalizations.of(context)!;
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(l10n.favoriteToggleFailed)));
}
}