From b7926d64a5419fd58c7964e4bdae680cf6e755ff Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Tue, 20 Oct 2026 13:35:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(flutter):=20=E9=A6=96=E9=A1=B5=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E6=B2=89=E6=B5=B8=E5=BC=8F=20feed=20=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/home/home_view.dart | 19 ++- test/home/home_nav_ui_test.dart | 4 +- test/home/home_preview_layout_test.dart | 49 +----- test/home/k28_a_navigation_test.dart | 206 ++++-------------------- test/widget_test.dart | 17 +- 5 files changed, 55 insertions(+), 240 deletions(-) diff --git a/lib/pages/home/home_view.dart b/lib/pages/home/home_view.dart index 7ec90e3..d1e878a 100644 --- a/lib/pages/home/home_view.dart +++ b/lib/pages/home/home_view.dart @@ -16,10 +16,10 @@ import '../discover/discover_types.dart'; import '../discover/discover_view.dart'; import '../create/create_view.dart'; import '../mine/mine_types.dart'; +import '../player/player_view.dart'; import '../splash/splash_page.dart'; import '../mine/mine_view.dart'; import 'home_binding.dart'; -import 'home_browse_view.dart'; import 'home_logic.dart'; class HomePage extends StatelessWidget { @@ -158,15 +158,18 @@ class _HomeTabsState extends State<_HomeTabs> { } Widget _home(BuildContext context) { - return HomeBrowseView( + return PlayerPage( + homeMode: true, + embedded: true, active: logic.tab == 0, + drama: widget.dramas.first, + initialIndex: 0, + feedDramas: widget.dramas, + onDramaChanged: logic.onDramaChanged, + onImmersiveChanged: logic.onImmersiveChanged, onSearch: () => logic.select(1, search: true), - onProfile: logic.openProfile, - onTheater: () => logic.select(1), - onHistory: () => logic.openHistory(context), - onOpenDrama: (drama) => logic.playDrama(context, drama), - onOpenContinue: (drama, record) => - logic.playDrama(context, drama, episodeId: record.episodeId), + onDiscover: () => logic.select(1), + onMy: () => logic.select(3), ); } diff --git a/test/home/home_nav_ui_test.dart b/test/home/home_nav_ui_test.dart index b6ed587..40b220d 100644 --- a/test/home/home_nav_ui_test.dart +++ b/test/home/home_nav_ui_test.dart @@ -140,7 +140,7 @@ void main() { expect(find.byKey(const Key('tab-theater')), findsOneWidget); expect(find.byKey(const Key('tab-create')), findsOneWidget); expect(find.byKey(const Key('tab-mine')), findsOneWidget); - expect(find.byKey(const Key('home-browse-page')), findsOneWidget); + expect(find.byKey(const Key('home-feed-title')), findsOneWidget); await tester.tap(find.byKey(const Key('tab-theater'))); await tester.pumpAndSettle(); @@ -159,7 +159,7 @@ void main() { await tester.tap(find.byKey(const Key('tab-home'))); await tester.pumpAndSettle(); - expect(find.byKey(const Key('home-browse-page')), findsOneWidget); + expect(find.byKey(const Key('home-feed-title')), findsOneWidget); }); testWidgets('should_not_haptic_when_ordinary_list_item_is_opened', ( diff --git a/test/home/home_preview_layout_test.dart b/test/home/home_preview_layout_test.dart index c706c7a..37e111b 100644 --- a/test/home/home_preview_layout_test.dart +++ b/test/home/home_preview_layout_test.dart @@ -23,9 +23,7 @@ import '../support/fake_notice_repository.dart'; import '../support/widget_player.dart'; void main() { - testWidgets('should_render_browse_home_with_header_channels_and_cards', ( - tester, - ) async { + testWidgets('should_render_immersive_feed_on_home', (tester) async { SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty(); tester.view.physicalSize = const Size(390, 844); @@ -61,50 +59,13 @@ void main() { await tester.tap(find.byKey(const Key('tab-home'))); await tester.pumpAndSettle(); - expect(find.byKey(const Key('home-search')), findsOneWidget); - expect(find.byKey(const Key('home-profile')), findsOneWidget); - expect(find.byKey(const Key('home-notifications')), findsOneWidget); - for (final channel in ['hot', 'free', 'newReleases', 'ranking']) { - expect(find.byKey(Key('home-channel-$channel')), findsOneWidget); - } - expect(find.byKey(const Key('home-hero-banner')), findsOneWidget); - expect( - find.byKey(const Key('home-drama-card-the-signal-demo')), - findsOneWidget, - ); - expect(find.byKey(const Key('home-feed-info-card')), findsNothing); + expect(find.byKey(const Key('home-feed-title')), findsOneWidget); + expect(find.byKey(const Key('home-feed-info-card')), findsOneWidget); + expect(find.byType(PageView), findsOneWidget); + expect(find.byKey(const Key('home-browse-page')), findsNothing); expect(tester.takeException(), isNull); }); - testWidgets('should_open_notices_when_bell_is_tapped', (tester) async { - SharedPreferencesAsyncPlatform.instance = - InMemorySharedPreferencesAsync.empty(); - tester.view.physicalSize = const Size(390, 844); - tester.view.devicePixelRatio = 1; - addTearDown(tester.view.resetPhysicalSize); - addTearDown(tester.view.resetDevicePixelRatio); - final catalog = FakeCatalogRepository(); - await tester.pumpWidget( - LumaDramaApp( - playerFactory: createWidgetPlayer, - catalogRepository: catalog, - categoryRepository: FakeCategoryRepository(catalog: catalog), - watchHistoryRepository: InMemoryWatchHistoryRepository(), - favoriteRepository: InMemoryFavoriteRepository(cards: catalog.dramas), - playbackRepository: FakePlaybackRepository(), - commentRepository: FakeCommentRepository(), - interactionRepository: FakeInteractionRepository(), - noticeRepository: FakeNoticeRepository(), - haptics: FakeHaptics(), - ), - ); - await tester.pumpAndSettle(); - - await tester.tap(find.byKey(const Key('home-notifications'))); - await tester.pumpAndSettle(); - expect(find.byKey(const Key('notices-page')), findsOneWidget); - }); - testWidgets('should_use_episode_title_then_drama_title_as_fallback', ( tester, ) async { diff --git a/test/home/k28_a_navigation_test.dart b/test/home/k28_a_navigation_test.dart index 8429c79..6e0a8c4 100644 --- a/test/home/k28_a_navigation_test.dart +++ b/test/home/k28_a_navigation_test.dart @@ -3,13 +3,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/app.dart'; -import 'package:fruitshorts/core/data/models/drama.dart'; -import 'package:fruitshorts/core/data/models/home_feed.dart'; -import 'package:fruitshorts/core/data/models/watch_progress.dart'; import 'package:fruitshorts/core/data/repositories/favorite_repository.dart'; 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_colors.dart'; import 'package:fruitshorts/pages/player/player_view.dart'; import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; @@ -50,98 +46,48 @@ void main() { expect(find.text('Coming soon'), findsOneWidget); }); - testWidgets('should_switch_home_channel_when_underline_tab_is_tapped', ( - tester, - ) async { + testWidgets('should_start_home_on_immersive_feed', (tester) async { await _pumpApp(tester); - // 选中态用主题色,未选中保留白色文字与透明占位。 - expect(_underlineColor(tester, 'hot'), AppColors.blue); - expect(_underlineColor(tester, 'free'), Colors.transparent); - expect(_labelStyle(tester, 'hot').fontWeight, FontWeight.w800); - expect(_labelStyle(tester, 'free').fontWeight, FontWeight.w600); - expect(_labelStyle(tester, 'hot').color, AppColors.blue); - expect(_labelStyle(tester, 'free').color, Colors.white70); + expect(find.byKey(const Key('home-feed-title')), findsOneWidget); + expect(find.byKey(const Key('home-feed-info-card')), findsOneWidget); + expect(find.byType(PageView), findsOneWidget); + expect(find.byKey(const Key('home-browse-page')), findsNothing); + expect(tester.widget(find.byType(PlayerPage)).homeMode, isTrue); + }); - await tester.tap(find.byKey(const Key('home-channel-free'))); + testWidgets('should_switch_feed_drama_when_swiped_up', (tester) async { + await _pumpApp(tester); + final before = tester + .widget(find.byKey(const Key('home-feed-title'))) + .data; + + await tester.drag(find.byType(PageView).first, const Offset(0, -600)); await tester.pumpAndSettle(); - expect(_underlineColor(tester, 'hot'), Colors.transparent); - expect(_underlineColor(tester, 'free'), AppColors.blue); - expect(_labelStyle(tester, 'free').color, AppColors.blue); - expect(_labelStyle(tester, 'hot').color, Colors.white70); - expect(_labelStyle(tester, 'free').fontWeight, FontWeight.w800); + final after = tester + .widget(find.byKey(const Key('home-feed-title'))) + .data; + expect(after, isNot(before)); }); - testWidgets('should_render_flush_hero_with_channels_overlaid', ( - tester, - ) async { + testWidgets('should_hide_feed_when_switching_to_theater', (tester) async { await _pumpApp(tester); + expect(find.byKey(const Key('home-feed-title')), findsOneWidget); - final hero = tester.getRect(find.byKey(const Key('home-hero-banner'))); - expect(hero.width, 390); - expect(hero.left, 0); - final card = tester.widget( - find - .descendant( - of: find.byKey(const Key('home-hero-banner')), - matching: find.byType(ClipRRect), - ) - .first, - ); - expect(card.borderRadius, BorderRadius.zero); - - // 频道行是固定层,不在可滚动列表里,初始仍压在轮播顶部。 - expect( - find.ancestor( - of: find.byKey(const Key('home-channels')), - matching: find.byKey(const Key('home-browse-page')), - ), - findsNothing, - ); - final channels = tester.getRect(find.byKey(const Key('home-channels'))); - expect(channels.top, closeTo(hero.top, 1)); - expect(find.byKey(const Key('home-channels-pinned')), findsNothing); - }); - - testWidgets('should_pin_home_channels_when_scrolling', (tester) async { - // 多放几部剧,保证内容高度足够滚过吸顶阈值。 - await _pumpApp( - tester, - withBanner: false, - dramas: [ - for (var index = 0; index < 10; index++) - Drama( - id: 'scroll-$index', - title: 'Scroll Drama $index', - synopsis: 'synopsis', - posterUrl: '/files/scroll-$index.png', - genre: 'Drama', - totalEpisodes: 10, - freeEpisodes: 10, - ), - ], - ); - final before = tester.getRect(find.byKey(const Key('home-channel-hot'))); - expect(find.byKey(const Key('home-channels-pinned')), findsNothing); - - await tester.drag( - find.byKey(const Key('home-browse-page')), - const Offset(0, -300), - ); + await tester.tap(find.byKey(const Key('tab-theater'))); await tester.pumpAndSettle(); - expect(find.byKey(const Key('home-channels-pinned')), findsOneWidget); - final after = tester.getRect(find.byKey(const Key('home-channel-hot'))); - expect(after.top, closeTo(before.top, 0.5)); + expect(find.byKey(const Key('home-feed-title')), findsNothing); + expect(find.byKey(const Key('discover-search')), findsOneWidget); }); - testWidgets('should_open_theater_search_when_home_search_is_tapped', ( + testWidgets('should_open_theater_search_when_feed_search_is_tapped', ( tester, ) async { await _pumpApp(tester); - await tester.tap(find.byKey(const Key('home-search'))); + await tester.tap(find.byKey(const Key('home-feed-search'))); await tester.pumpAndSettle(); final field = tester.widget( @@ -165,36 +111,6 @@ void main() { ); }); - testWidgets('should_open_fullscreen_player_when_home_card_is_tapped', ( - tester, - ) async { - await _pumpApp(tester); - - await tester.ensureVisible( - find.byKey(const Key('home-drama-card-the-signal-demo')), - ); - await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key('home-drama-card-the-signal-demo'))); - await tester.pumpAndSettle(); - - final player = tester.widget(find.byType(PlayerPage)); - expect(player.homeMode, isFalse); - }); - - testWidgets('should_use_first_drama_cover_when_home_has_no_banner', ( - tester, - ) async { - await _pumpApp(tester, withBanner: false); - - expect(find.byKey(const Key('home-hero-banner')), findsOneWidget); - expect(find.byKey(const Key('home-hero-title-0')), findsOneWidget); - await tester.tap(find.byKey(const Key('feed-banner-0'))); - await tester.pumpAndSettle(); - - final player = tester.widget(find.byType(PlayerPage)); - expect(player.homeMode, isFalse); - }); - testWidgets('should_open_fullscreen_player_when_theater_card_is_tapped', ( tester, ) async { @@ -218,44 +134,14 @@ void main() { await tester.tap(card); await tester.pumpAndSettle(); - final player = tester.widget(find.byType(PlayerPage)); + final player = tester.widget(find.byType(PlayerPage).last); expect(player.homeMode, isFalse); }); - testWidgets('should_resume_continue_watching_at_saved_episode', ( - tester, - ) async { - await _pumpApp( - tester, - history: InMemoryWatchHistoryRepository( - seed: const [ - WatchProgress( - dramaId: 'the-signal-demo', - episodeId: 'the-signal-demo-3', - position: Duration(minutes: 2), - duration: Duration(minutes: 10), - ), - ], - ), - ); - - expect(find.byKey(const Key('home-continue-watching')), findsOneWidget); - expect(find.text('Episode 3 · 8 min left'), findsOneWidget); - await tester.tap( - find.byKey(const Key('home-continue-play-the-signal-demo')), - ); - await tester.pumpAndSettle(); - - final player = tester.widget(find.byType(PlayerPage)); - expect(player.homeMode, isFalse); - expect(player.initialIndex, 2); - }); - - testWidgets('should_fit_home_and_theater_on_320_width', (tester) async { + testWidgets('should_fit_home_feed_and_theater_on_320_width', (tester) async { await _pumpApp(tester, size: const Size(320, 568)); - expect(find.byKey(const Key('home-search')), findsOneWidget); - expect(find.byKey(const Key('home-hero-title-0')), findsOneWidget); + expect(find.byKey(const Key('home-feed-title')), findsOneWidget); expect(tester.takeException(), isNull); await tester.tap(find.byKey(const Key('tab-theater'))); @@ -273,53 +159,21 @@ const _tabKeys = [ Key('tab-mine'), ]; -Color? _underlineColor(WidgetTester tester, String name) { - final underline = tester.widget( - find.byKey(Key('home-channel-underline-$name')), - ); - return (underline.decoration! as BoxDecoration).color; -} - -TextStyle _labelStyle(WidgetTester tester, String name) { - final label = tester.widget( - find.descendant( - of: find.byKey(Key('home-channel-$name')), - matching: find.byType(Text), - ), - ); - return label.style!; -} - Future _pumpApp( WidgetTester tester, { - WatchHistoryRepository? history, Size size = const Size(390, 844), - bool withBanner = true, - List? dramas, }) async { tester.view.physicalSize = size; tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); - final catalog = FakeCatalogRepository( - dramas: dramas, - banners: withBanner - ? const [ - FeedBanner( - id: 'banner-1', - imageUrl: '/files/banner.jpg', - linkType: 'DRAMA', - linkValue: 'the-signal-demo', - ), - ] - : const [], - ); + final catalog = FakeCatalogRepository(); await tester.pumpWidget( LumaDramaApp( playerFactory: createWidgetPlayer, catalogRepository: catalog, categoryRepository: FakeCategoryRepository(catalog: catalog), - watchHistoryRepository: history ?? InMemoryWatchHistoryRepository(), + watchHistoryRepository: InMemoryWatchHistoryRepository(), favoriteRepository: InMemoryFavoriteRepository(cards: catalog.dramas), playbackRepository: FakePlaybackRepository(), commentRepository: FakeCommentRepository(), diff --git a/test/widget_test.dart b/test/widget_test.dart index bcfc416..5b910bc 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -64,11 +64,13 @@ void main() { ), ); await tester.pumpAndSettle(); + // 首页 feed 启动会创建首个播放器,先清掉再统计剧场播放器。 + created.clear(); await _openTheaterPlayer(tester); expect(created, hasLength(2)); expect(find.byType(CircularProgressIndicator), findsNothing); - await tester.drag(find.byType(PageView), const Offset(0, -550)); + await tester.drag(find.byType(PageView).last, const Offset(0, -550)); await tester.pumpAndSettle(); expect( @@ -79,19 +81,14 @@ void main() { expect(created.toSet(), hasLength(3)); }); - testWidgets('should_open_fullscreen_player_when_home_card_is_tapped', ( + testWidgets('should_open_fullscreen_player_when_theater_card_is_tapped', ( tester, ) async { await tester.pumpWidget(appUnderTest()); await tester.pumpAndSettle(); - await tester.ensureVisible( - find.byKey(const Key('home-drama-card-the-signal-demo')), - ); - await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key('home-drama-card-the-signal-demo'))); - await tester.pumpAndSettle(); + await _openTheaterPlayer(tester); - final player = tester.widget(find.byType(PlayerPage)); + final player = tester.widget(find.byType(PlayerPage).last); expect(player.homeMode, isFalse); expect(find.byKey(const Key('player-controls-top')), findsOneWidget); }); @@ -121,7 +118,7 @@ void main() { await tester.binding.handlePopRoute(); await tester.pumpAndSettle(); - expect(find.byKey(const Key('home-browse-page')), findsOneWidget); + expect(find.byKey(const Key('home-feed-title')), findsOneWidget); await tester.tap(find.byKey(const Key('tab-create'))); await tester.pumpAndSettle();