feat(app): 客户端展示多语言并支持筛选分页
This commit is contained in:
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(
|
||||
|
||||
Reference in new issue
Block a user