feat(app): 客户端展示多语言并支持筛选分页

This commit is contained in:
Zhou Weidong committed 2026-10-24 17:20:24 +08:00
1 parent 8481611372
commit 8b50b5bd06
68 files changed
+2742 -1120

No files matched your search

@@ -4,17 +4,21 @@ import 'package:flutter/material.dart';
import '../../../core/data/models/content_category.dart';
import '../../../core/data/models/drama.dart';
import '../../../core/data/models/playlist_detail.dart';
import '../../../core/data/services/category_controller.dart';
import '../../../core/data/services/home_feed_controller.dart';
import '../../../core/data/services/playlist_controller.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../core/widgets/app_pull_to_refresh.dart';
import '../../../core/widgets/empty_state.dart';
import '../../../l10n/app_localizations.dart';
import '../../home/widgets/home_drama_classification.dart';
import 'recommend_section_card.dart';
const List<String> _costumeNames = ['古装', 'costume', 'kostum'];
const int _sectionPreviewCount = 10;
const double _loadMoreThreshold = 200;
enum RecommendSectionKind { hits, newReleases, costume }
@@ -25,17 +29,51 @@ class RecommendSectionsView extends StatelessWidget {
required this.categories,
required this.onOpenDrama,
required this.onMore,
this.playlists,
this.onOpenPlaylist,
});
final HomeFeedController feedController;
final CategoryController categories;
final ValueChanged<Drama> onOpenDrama;
final ValueChanged<RecommendSectionKind> onMore;
final PlaylistController? playlists;
final ValueChanged<PlaylistDetail>? onOpenPlaylist;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final source = feedController.dramas;
final remote = playlists;
return AppPullToRefresh(
key: const Key('recommend-sections-refresh'),
onRefresh: () async {
await Future.wait([
feedController.refreshCurrent(),
categories.load(),
if (remote != null) remote.load(),
]);
},
child: NotificationListener<ScrollNotification>(
onNotification: (notification) =>
remote == null ? false : _onPlaylistScroll(notification, remote),
child: ListView(
key: const Key('recommend-sections-scroll'),
physics: AppPullToRefresh.physics,
padding: const EdgeInsets.only(bottom: AppDimens.spaceXl),
children: remote == null
? _localSections(context, l10n, source)
: _remoteSections(context, l10n, remote),
),
),
);
}
List<Widget> _localSections(
BuildContext context,
AppLocalizations l10n,
List<Drama> source,
) {
final hits = recommendSectionDramas(
kind: RecommendSectionKind.hits,
source: source,
@@ -44,16 +82,7 @@ class RecommendSectionsView extends StatelessWidget {
kind: RecommendSectionKind.newReleases,
source: source,
);
return AppPullToRefresh(
key: const Key('recommend-sections-refresh'),
onRefresh: () async {
await Future.wait([feedController.refreshCurrent(), categories.load()]);
},
child: ListView(
key: const Key('recommend-sections-scroll'),
physics: AppPullToRefresh.physics,
padding: const EdgeInsets.only(bottom: AppDimens.spaceXl),
children: [
return [
_section(
context,
l10n,
@@ -87,9 +116,97 @@ class RecommendSectionsView extends StatelessWidget {
).take(_sectionPreviewCount).toList(),
onMore: () => onMore(RecommendSectionKind.costume),
),
],
),
);
];
}
List<Widget> _remoteSections(
BuildContext context,
AppLocalizations l10n,
PlaylistController remote,
) {
if (!remote.loaded && remote.loading) {
return const [
SizedBox(
height: 240,
child: Center(
child: CircularProgressIndicator(
key: Key('recommend-playlists-loading'),
),
),
),
];
}
if (remote.items.isEmpty) {
return [
EmptyState(
key: const Key('recommend-playlists-empty'),
icon: Icons.video_library_outlined,
title: remote.error == null ? l10n.emptyContent : l10n.networkError,
actionLabel: l10n.retry,
onAction: remote.load,
),
];
}
final sections = <Widget>[];
for (final playlist in remote.items) {
if (sections.isNotEmpty) {
sections.add(const SizedBox(height: AppDimens.spaceXl));
}
sections.add(
_section(
context,
l10n,
key: playlist.id,
title: playlist.localizedName(
Localizations.localeOf(context).languageCode,
),
subtitle: playlist.localizedDescription(
Localizations.localeOf(context).languageCode,
),
dramas: playlist.dramas.take(_sectionPreviewCount).toList(),
onMore: () => onOpenPlaylist?.call(playlist),
),
);
}
if (remote.loadingMore) {
sections.add(
const Padding(
padding: EdgeInsets.symmetric(vertical: AppDimens.spaceMd),
child: Center(
child: CircularProgressIndicator(
key: Key('recommend-playlists-more'),
),
),
),
);
} else if (!remote.hasMore) {
sections.add(
Padding(
key: const Key('paged-list-no-more'),
padding: const EdgeInsets.symmetric(vertical: 16),
child: Text(
l10n.noMoreItems,
textAlign: TextAlign.center,
style: const TextStyle(color: AppColors.textHint, fontSize: 12),
),
),
);
}
return sections;
}
bool _onPlaylistScroll(
ScrollNotification notification,
PlaylistController remote,
) {
if (notification.depth != 0) return false;
if (notification.metrics.extentAfter > _loadMoreThreshold) return false;
if (notification is! ScrollUpdateNotification &&
notification is! ScrollEndNotification) {
return false;
}
remote.loadMore();
return false;
}
Widget _section(