merge: 修正标题与观看全集跳转

This commit is contained in:
Zhou Weidong committed 2026-10-22 20:31:48 +08:00
commit 5fce65ac57
6 files changed
+82 -52

No files matched your search

+6
View File
@@ -1194,3 +1194,9 @@ $ flutter test
- 推荐流拖动使用独立 opaque 手势与 pointer 兜底,end/cancel 同时恢复浮层和轨道。
- 推荐浮层改为 AnimatedOpacity + IgnorePointer,进度条节点始终留在树中;scrub setState 均加 mounted 守卫。
- `flutter test test/player/seek_scrub_visibility_test.dart`:6 项测试全部通过。
## 2026-10-22 推荐页播放跳转修正
- 标题、剧名行和观看全集统一通过 `playDramaFromCatalog` 打开非 `homeMode` 逐集播放页。
- 仅含集数的剧集标题不再显示在标题上方,集数只保留下方进度文案。
- `flutter analyze`:No issues found。
@@ -50,38 +50,44 @@ class _HomeFeedInfoPanelState extends State<HomeFeedInfoPanel> {
final l10n = AppLocalizations.of(context)!;
final dramaTitle = localizedDramaTitle(l10n, widget.drama);
final episodeTitle = widget.episode.title.trim();
final headline = episodeTitle.isEmpty ? dramaTitle : episodeTitle;
final headline = _headline(l10n, dramaTitle, episodeTitle);
final synopsis = localizedDramaSynopsis(l10n, widget.drama).trim();
final genres = _genreValues();
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
headline,
key: const Key('home-feed-title'),
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: _headlineFont,
fontWeight: FontWeight.w700,
height: 1.2,
letterSpacing: 0,
shadows: _textShadows,
GestureDetector(
key: const Key('home-feed-title-open'),
behavior: HitTestBehavior.opaque,
onTap: widget.onOpenFullPlayer,
child: Text(
headline,
key: const Key('home-feed-title'),
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: _headlineFont,
fontWeight: FontWeight.w700,
height: 1.2,
letterSpacing: 0,
shadows: _textShadows,
),
),
),
const SizedBox(height: 8),
GestureDetector(
key: const Key('home-feed-series-name'),
behavior: HitTestBehavior.opaque,
onTap: widget.onDetail,
onTap: widget.onOpenFullPlayer,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Text(
dramaTitle,
key: const Key('home-feed-series-title'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
@@ -113,6 +119,23 @@ class _HomeFeedInfoPanelState extends State<HomeFeedInfoPanel> {
);
}
String _headline(
AppLocalizations l10n,
String dramaTitle,
String episodeTitle,
) {
if (episodeTitle.isEmpty) return dramaTitle;
final normalized = episodeTitle.toLowerCase().replaceAll(' ', '');
final localizedEpisode = l10n
.episodeLabel(widget.episode.number)
.toLowerCase()
.replaceAll(' ', '');
final episodeOnly = RegExp(r'^(第?\d+集|episode\d+)$').hasMatch(normalized);
return normalized == localizedEpisode || episodeOnly
? dramaTitle
: episodeTitle;
}
Widget _tagMetaRow(AppLocalizations l10n, List<String> genres) {
return Row(
key: const Key('home-feed-tags'),
@@ -171,11 +171,12 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
}
Future<void> _finish(double fraction, Duration duration) async {
if (widget.locked || !_dragging) return;
final startedAt = _dragStartFraction;
_dragStartFraction = null;
if (widget.locked || !_dragging || startedAt == null) return;
setState(() => _dragging = false);
_pointerMoved = false;
_pointerStart = null;
_dragStartFraction = null;
widget.thumbnailService?.cancelPending();
_previewFraction.value = null;
widget.onScrubEnd?.call();
+2 -29
View File
@@ -6,15 +6,12 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../core/data/models/drama.dart';
import '../../core/data/services/catalog_controller.dart';
import '../../core/data/services/category_controller.dart';
import '../../core/data/services/download_controller.dart';
import '../../core/data/services/home_feed_controller.dart';
import '../../core/data/services/wallet_controller.dart';
import '../../core/navigation/fade_page_route.dart';
import '../../core/theme/app_colors.dart';
import '../../core/widgets/empty_state.dart';
import '../../l10n/app_localizations.dart';
import '../player/open_player.dart';
import '../player/player_view.dart';
import 'recommend_types.dart';
import 'widgets/all_dramas_filter_grid.dart';
@@ -187,30 +184,6 @@ class _RecommendPageState extends State<RecommendPage> {
}
Future<void> _openDrama(Drama drama, {String? episodeId}) async {
final catalog = context.read<CatalogController>();
final wallet = context.read<WalletController>();
final downloads = context.read<DownloadController>();
var playable = drama;
try {
playable = await catalog.requireDetail(drama);
} catch (_) {
// 卡片预览仍可直接进入播放器。
}
if (!mounted) return;
final episodeIndex = episodeId == null
? 0
: playable.episodes.indexWhere((episode) => episode.id == episodeId);
await Navigator.of(context).push<void>(
FadePageRoute(
builder: (_) => PlayerPage(
drama: playable,
initialIndex: episodeIndex < 0 ? 0 : episodeIndex,
homeMode: true,
feedDramas: [playable],
wallet: wallet,
downloads: downloads,
),
),
);
await playDramaFromCatalog(context, drama, episodeId: episodeId);
}
}
+9 -6
View File
@@ -282,7 +282,7 @@ void main() {
await tester.pumpAndSettle();
expect(
tester.widget<PlayerPage>(find.byType(PlayerPage).last).homeMode,
isTrue,
isFalse,
);
});
@@ -321,7 +321,7 @@ void main() {
episode: const Episode(
id: 'feed-episode',
number: 2,
title: 'Ignored Episode Title',
title: 'Episode 2',
isFree: true,
),
controller: null,
@@ -353,10 +353,11 @@ void main() {
expect(
tester.widget<Text>(find.byKey(const Key('home-feed-title'))).data,
'Ignored Episode Title',
'Feed Drama',
);
final seriesTitle = tester.widget<Text>(
find.byKey(const Key('home-feed-series-title')),
);
expect(find.text('Feed Drama'), findsOneWidget);
final seriesTitle = tester.widget<Text>(find.text('Feed Drama'));
expect(seriesTitle.style?.fontSize, 19);
expect(seriesTitle.style?.fontWeight, FontWeight.w600);
expect(seriesTitle.style?.color, AppColors.textPrimary);
@@ -370,7 +371,9 @@ void main() {
expect(seriesChevron.color, AppColors.textPrimary);
expect(
tester.getTopLeft(find.byIcon(Icons.chevron_right_rounded)).dx -
tester.getTopRight(find.text('Feed Drama')).dx,
tester
.getTopRight(find.byKey(const Key('home-feed-series-title')))
.dx,
4,
);
expect(find.byKey(const Key('home-feed-info-card')), findsNothing);
+25 -1
View File
@@ -278,6 +278,30 @@ void main() {
expect(find.byKey(const Key('all-dramas-grid')), findsOneWidget);
});
testWidgets('should_open_standalone_player_from_title_and_watch_all', (
tester,
) async {
await _pumpRecommend(tester);
await tester.tap(find.byKey(const Key('home-feed-title-open')));
await tester.pumpAndSettle();
expect(
tester.widget<PlayerPage>(find.byType(PlayerPage).last).homeMode,
isFalse,
);
await tester.binding.handlePopRoute();
await tester.pumpAndSettle();
expect(find.byKey(const Key('recommend-page')), findsOneWidget);
expect(find.byKey(const Key('recommend-tab-recommend')), findsOneWidget);
await tester.tap(find.byKey(const Key('home-feed-full-series')));
await tester.pumpAndSettle();
expect(
tester.widget<PlayerPage>(find.byType(PlayerPage).last).homeMode,
isFalse,
);
});
testWidgets('should_open_player_and_search_from_recommend_page', (
tester,
) async {
@@ -290,7 +314,7 @@ void main() {
);
await tester.pumpAndSettle();
final player = tester.widget<PlayerPage>(find.byType(PlayerPage).last);
expect(player.homeMode, isTrue);
expect(player.homeMode, isFalse);
await tester.binding.handlePopRoute();
await tester.pumpAndSettle();