merge: 支持横向切换三个内容页
This commit is contained in:
commit
14885d9efa
3 files changed
+121
-17
No files matched your search
@@ -1200,3 +1200,9 @@ $ flutter test
|
|||||||
- 标题、剧名行和观看全集统一通过 `playDramaFromCatalog` 打开非 `homeMode` 逐集播放页。
|
- 标题、剧名行和观看全集统一通过 `playDramaFromCatalog` 打开非 `homeMode` 逐集播放页。
|
||||||
- 仅含集数的剧集标题不再显示在标题上方,集数只保留下方进度文案。
|
- 仅含集数的剧集标题不再显示在标题上方,集数只保留下方进度文案。
|
||||||
- `flutter analyze`:No issues found。
|
- `flutter analyze`:No issues found。
|
||||||
|
|
||||||
|
## 2026-10-22 推荐页横向切换
|
||||||
|
|
||||||
|
- 推荐、剧单、全部短剧改为共享 PageController 的横向 PageView,点击与滑动均驱动同一选中态。
|
||||||
|
- 三页使用 AutomaticKeepAliveClientMixin 保留视频、滚动位置和筛选状态;顶部导航固定在外层。
|
||||||
|
- `flutter analyze`:No issues found。
|
||||||
@@ -38,8 +38,15 @@ class RecommendPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _RecommendPageState extends State<RecommendPage> {
|
class _RecommendPageState extends State<RecommendPage> {
|
||||||
RecommendInnerTab _tab = RecommendInnerTab.feed;
|
RecommendInnerTab _tab = RecommendInnerTab.feed;
|
||||||
|
final PageController _pageController = PageController();
|
||||||
int _scrubCancelRequest = 0;
|
int _scrubCancelRequest = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_pageController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final categories = context.read<CategoryController>();
|
final categories = context.read<CategoryController>();
|
||||||
@@ -51,23 +58,35 @@ class _RecommendPageState extends State<RecommendPage> {
|
|||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: ListenableBuilder(
|
child: ListenableBuilder(
|
||||||
listenable: Listenable.merge([widget.feedController, categories]),
|
listenable: Listenable.merge([widget.feedController, categories]),
|
||||||
builder: (context, _) => IndexedStack(
|
builder: (context, _) => PageView(
|
||||||
index: _tab.index,
|
key: const Key('recommend-tab-page-view'),
|
||||||
|
controller: _pageController,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
onPageChanged: _onPageChanged,
|
||||||
children: [
|
children: [
|
||||||
_recommendFeed(context),
|
_KeepAliveTab(
|
||||||
_withTopNavSpace(
|
key: const PageStorageKey('recommend-tab-feed'),
|
||||||
RecommendSectionsView(
|
child: _recommendFeed(context),
|
||||||
feedController: widget.feedController,
|
),
|
||||||
categories: categories,
|
_KeepAliveTab(
|
||||||
onOpenDrama: _openDrama,
|
key: const PageStorageKey('recommend-tab-playlists'),
|
||||||
onMore: _handleMore,
|
child: _withTopNavSpace(
|
||||||
|
RecommendSectionsView(
|
||||||
|
feedController: widget.feedController,
|
||||||
|
categories: categories,
|
||||||
|
onOpenDrama: _openDrama,
|
||||||
|
onMore: _handleMore,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_withTopNavSpace(
|
_KeepAliveTab(
|
||||||
AllDramasFilterGrid(
|
key: const PageStorageKey('recommend-tab-all'),
|
||||||
feedController: widget.feedController,
|
child: _withTopNavSpace(
|
||||||
onOpenDrama: _openDrama,
|
AllDramasFilterGrid(
|
||||||
onOpenFilter: widget.onOpenFilter,
|
feedController: widget.feedController,
|
||||||
|
onOpenDrama: _openDrama,
|
||||||
|
onOpenFilter: widget.onOpenFilter,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -162,6 +181,24 @@ class _RecommendPageState extends State<RecommendPage> {
|
|||||||
_tab = tab;
|
_tab = tab;
|
||||||
_scrubCancelRequest++;
|
_scrubCancelRequest++;
|
||||||
});
|
});
|
||||||
|
if (_pageController.hasClients) {
|
||||||
|
unawaited(
|
||||||
|
_pageController.animateToPage(
|
||||||
|
tab.index,
|
||||||
|
duration: const Duration(milliseconds: 220),
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onPageChanged(int index) {
|
||||||
|
final next = RecommendInnerTab.values[index];
|
||||||
|
if (next == _tab) return;
|
||||||
|
setState(() {
|
||||||
|
_tab = next;
|
||||||
|
_scrubCancelRequest++;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSearch() {
|
void _handleSearch() {
|
||||||
@@ -180,10 +217,31 @@ class _RecommendPageState extends State<RecommendPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleMore(RecommendSectionKind section) {
|
void _handleMore(RecommendSectionKind section) {
|
||||||
setState(() => _tab = RecommendInnerTab.allDramas);
|
_selectTab(RecommendInnerTab.allDramas);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _openDrama(Drama drama, {String? episodeId}) async {
|
Future<void> _openDrama(Drama drama, {String? episodeId}) async {
|
||||||
await playDramaFromCatalog(context, drama, episodeId: episodeId);
|
await playDramaFromCatalog(context, drama, episodeId: episodeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _KeepAliveTab extends StatefulWidget {
|
||||||
|
const _KeepAliveTab({super.key, required this.child});
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_KeepAliveTab> createState() => _KeepAliveTabState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _KeepAliveTabState extends State<_KeepAliveTab>
|
||||||
|
with AutomaticKeepAliveClientMixin<_KeepAliveTab> {
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
|
return widget.child;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,7 +91,7 @@ void main() {
|
|||||||
expect(player.homeMode, isTrue);
|
expect(player.homeMode, isTrue);
|
||||||
expect(player.embedded, isTrue);
|
expect(player.embedded, isTrue);
|
||||||
expect(player.showHomeFeedHeader, isFalse);
|
expect(player.showHomeFeedHeader, isFalse);
|
||||||
expect(find.byType(PageView), findsOneWidget);
|
expect(find.byType(PageView), findsNWidgets(2));
|
||||||
expect(find.byKey(const Key('recommend-section-hits')), findsNothing);
|
expect(find.byKey(const Key('recommend-section-hits')), findsNothing);
|
||||||
expect(find.byType(AppBottomNav), findsOneWidget);
|
expect(find.byType(AppBottomNav), findsOneWidget);
|
||||||
|
|
||||||
@@ -269,7 +269,7 @@ void main() {
|
|||||||
await tester.tap(find.byKey(const Key('recommend-tab-playlist')));
|
await tester.tap(find.byKey(const Key('recommend-tab-playlist')));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(find.byKey(const Key('recommend-section-hits')), findsOneWidget);
|
expect(find.byKey(const Key('recommend-section-hits')), findsOneWidget);
|
||||||
expect(find.byKey(const Key('recommend-feed-player')), findsNothing);
|
expect(find.byKey(const Key('recommend-feed-player')), findsOneWidget);
|
||||||
expect(find.byType(AppBottomNav), findsOneWidget);
|
expect(find.byType(AppBottomNav), findsOneWidget);
|
||||||
|
|
||||||
await tester.tap(find.byKey(const Key('recommend-tab-all')));
|
await tester.tap(find.byKey(const Key('recommend-tab-all')));
|
||||||
@@ -278,6 +278,46 @@ void main() {
|
|||||||
expect(find.byKey(const Key('all-dramas-grid')), findsOneWidget);
|
expect(find.byKey(const Key('all-dramas-grid')), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('should_switch_inner_tabs_with_horizontal_page_swipes', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await _pumpRecommend(tester);
|
||||||
|
final pages = find.byKey(const Key('recommend-tab-page-view'));
|
||||||
|
final topNavBefore = tester.getRect(
|
||||||
|
find.byKey(const Key('recommend-top-nav')),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.drag(pages, const Offset(-320, 0));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byKey(const Key('recommend-section-hits')), findsOneWidget);
|
||||||
|
expect(find.byKey(const Key('recommend-feed-player')), findsOneWidget);
|
||||||
|
expect(
|
||||||
|
tester
|
||||||
|
.widget<Text>(
|
||||||
|
find.descendant(
|
||||||
|
of: find.byKey(const Key('recommend-tab-playlist')),
|
||||||
|
matching: find.text('Playlists'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.style
|
||||||
|
?.color,
|
||||||
|
AppColors.textPrimary,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
tester.getRect(find.byKey(const Key('recommend-top-nav'))),
|
||||||
|
topNavBefore,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.drag(pages, const Offset(-320, 0));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byKey(const Key('all-dramas-grid')), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.drag(pages, const Offset(640, 0));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byKey(const Key('recommend-feed-player')), findsOneWidget);
|
||||||
|
expect(find.byKey(const Key('recommend-tab-recommend')), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('should_open_standalone_player_from_title_and_watch_all', (
|
testWidgets('should_open_standalone_player_from_title_and_watch_all', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
|
|||||||
Reference in new issue
Block a user