merge: 修正标题与观看全集跳转
This commit is contained in:
commit
5fce65ac57
6 files changed
+82
-52
No files matched your search
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in new issue
Block a user