feat(home): 首页按内容类型分频道 Feed 并支持分页

This commit is contained in:
Zhou Weidong committed 2026-10-21 17:33:52 +08:00
1 parent 4d0be47ee3
commit ae61d9ec4c
22 files changed
+1001 -71

No files matched your search

@@ -2,6 +2,7 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/models/drama.dart';
import 'package:fruitshorts/core/data/models/feed_page.dart';
import 'package:fruitshorts/core/data/models/home_feed.dart';
import 'package:fruitshorts/core/data/repositories/catalog_repository.dart';
import 'package:fruitshorts/core/data/services/catalog_controller.dart';
@@ -59,6 +60,13 @@ class _FakeCatalog implements CatalogRepository {
@override
Future<HomeFeed> fetchDiscoverFeed() async => const HomeFeed();
@override
Future<FeedPage> fetchFeed({
List<String> types = const [],
String? cursor,
int size = 10,
}) async => const FeedPage();
@override
Future<Drama> fetchDrama(String dramaId) async => _drama;
}
+54
View File
@@ -0,0 +1,54 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/theme/app_theme.dart';
import 'package:fruitshorts/l10n/app_localizations.dart';
import 'package:fruitshorts/pages/home/home_feed_channel.dart';
import 'package:fruitshorts/pages/player/widgets/home_feed_header.dart';
void main() {
testWidgets('should_emit_channel_callbacks_and_render_selected_channel', (
tester,
) async {
final selected = <HomeFeedChannel>[];
await tester.pumpWidget(
MaterialApp(
theme: AppTheme.light(),
locale: const Locale('en'),
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: AppLocalizations.localizationsDelegates,
home: Builder(
builder: (context) => Scaffold(
body: HomeFeedHeader(
l10n: AppLocalizations.of(context)!,
channel: HomeFeedChannel.anime,
onChannelSelected: selected.add,
onRefresh: () async => true,
onSearch: () {},
onMore: () {},
),
),
),
),
);
expect(find.byKey(const Key('home-feed-tab-anime')), findsOneWidget);
expect(find.byKey(const Key('home-feed-tab-real')), findsOneWidget);
expect(find.byKey(const Key('home-feed-tab-recommend')), findsOneWidget);
expect(
tester.widget<Text>(find.text('Anime')).style?.fontWeight,
FontWeight.w800,
);
await tester.tap(find.byKey(const Key('home-feed-tab-real')));
await tester.tap(find.byKey(const Key('home-feed-tab-recommend')));
await tester.tap(find.byKey(const Key('home-feed-tab-anime')));
expect(selected, [
HomeFeedChannel.live,
HomeFeedChannel.recommend,
HomeFeedChannel.anime,
]);
});
}
+3
View File
@@ -15,6 +15,7 @@ import 'package:fruitshorts/core/data/repositories/playback_repository.dart';
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
import 'package:fruitshorts/core/theme/app_theme.dart';
import 'package:fruitshorts/l10n/app_localizations.dart';
import 'package:fruitshorts/pages/home/home_feed_channel.dart';
import 'package:fruitshorts/pages/player/widgets/home_feed_header.dart';
import 'package:fruitshorts/pages/player/widgets/home_feed_overlay.dart';
import 'package:video_player/video_player.dart';
@@ -439,6 +440,8 @@ void main() {
builder: (context) => Scaffold(
body: HomeFeedHeader(
l10n: AppLocalizations.of(context)!,
channel: HomeFeedChannel.recommend,
onChannelSelected: (_) {},
onRefresh: () {
calls++;
return completer.future;
+15
View File
@@ -12,6 +12,7 @@ import 'package:shared_preferences_platform_interface/shared_preferences_async_p
import 'package:fruitshorts/app.dart';
import 'package:fruitshorts/core/config/app_config.dart';
import 'package:fruitshorts/core/data/models/drama.dart';
import 'package:fruitshorts/core/data/models/feed_page.dart';
import 'package:fruitshorts/core/data/models/home_feed.dart';
import 'package:fruitshorts/core/data/repositories/catalog_repository.dart';
import 'package:fruitshorts/core/data/repositories/favorite_repository.dart';
@@ -158,6 +159,13 @@ class _EmptyCatalogRepository implements CatalogRepository {
@override
Future<HomeFeed> fetchDiscoverFeed() async => const HomeFeed();
@override
Future<FeedPage> fetchFeed({
List<String> types = const [],
String? cursor,
int size = 10,
}) async => const FeedPage();
@override
Future<Drama> fetchDrama(String dramaId) async => _feed.dramas.first;
}
@@ -177,6 +185,13 @@ class _PendingCatalogRepository implements CatalogRepository {
@override
Future<HomeFeed> fetchDiscoverFeed() async => _feed;
@override
Future<FeedPage> fetchFeed({
List<String> types = const [],
String? cursor,
int size = 10,
}) async => FeedPage(dramas: _feed.dramas);
@override
Future<Drama> fetchDrama(String dramaId) async => _feed.dramas.first;
}
+9 -3
View File
@@ -116,12 +116,18 @@ void main() {
expect(player.homeMode, isFalse);
});
testWidgets('should_show_coming_soon_for_anime_tab', (tester) async {
testWidgets('should_switch_to_anime_channel', (tester) async {
await _pumpApp(tester);
await tester.tap(find.byKey(const Key('home-feed-tab-anime')));
await tester.pump();
expect(find.text('Coming soon'), findsOneWidget);
await tester.pumpAndSettle();
expect(find.text('Coming soon'), findsNothing);
expect(
tester.widget<Text>(find.text('Anime')).style?.fontWeight,
FontWeight.w800,
);
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
});
testWidgets('should_refresh_feed_without_blank_loading', (tester) async {