@@ -16,15 +16,11 @@ import '../analytics/models/event_names.dart';
1616import '../features/usage/usage_event.dart' ;
1717import '../features/usage/usage_providers.dart' ;
1818import '../database/app_database.dart' ;
19- import '../database/daos/audio_item_dao.dart' ;
2019import '../database/daos/bookmark_dao.dart' ;
2120import '../database/providers.dart' ;
2221import '../l10n/app_localizations.dart' ;
2322import '../models/dict_entry.dart' ;
24- import '../models/sentence.dart' ;
25- import '../models/word_highlight_binding.dart' ;
2623import '../screens/sentence_detail_screen.dart' ;
27- import '../providers/audio_engine/audio_engine_provider.dart' ;
2824import '../providers/short_audio_player_provider.dart' ;
2925import '../providers/tts/tts_controller_provider.dart' ;
3026import '../providers/pronunciation/pronunciation_providers.dart' ;
@@ -43,10 +39,10 @@ import '../services/dictionary/ai_dictionary_source.dart';
4339import '../services/pronunciation/local_audio_clip_player.dart' ;
4440import '../services/pronunciation/local_audio_range_player.dart' ;
4541import '../services/pronunciation/source_sentence_player.dart' ;
46- import '../services/subtitle_parser.dart' ;
4742import '../router/app_router.dart' ;
4843import '../theme/app_theme.dart' ;
4944import '../widgets/favorites/sentence_recycle_bin_sheet.dart' ;
45+ import '../widgets/favorites/source_sentence_detail_navigation.dart' ;
5046import '../widgets/favorites/vocabulary_recycle_bin_sheet.dart' ;
5147import '../widgets/bookmark_review/bookmark_review_settings_sheet.dart' ;
5248import '../widgets/common/app_popup_menu.dart' ;
@@ -881,7 +877,7 @@ class _BookmarkSentenceTileState extends ConsumerState<_BookmarkSentenceTile> {
881877 sentenceIndex: bm.sentenceIndex,
882878 startTimeMs: (bm.startTime * 1000 ).round (),
883879 endTimeMs: (bm.endTime * 1000 ).round (),
884- wordHighlightSource: _audioEngineWordHighlightSource (
880+ wordHighlightSource: audioEngineWordHighlightSource (
885881 ref,
886882 widget.audioId,
887883 ),
@@ -1023,109 +1019,6 @@ class _FavoriteSourceSentenceTile extends StatelessWidget {
10231019 }
10241020}
10251021
1026- /// 解析收藏词汇来源句进入讲解页所需的位置。
1027- ///
1028- /// 新数据直接使用持久化的句序和时间;旧数据缺少其中任一字段时,改从当前
1029- /// 材料字幕按句序或文本恢复,避免把错误的时间范围传给讲解页。
1030- Future <void > _openFavoriteSourceSentenceDetail ({
1031- required BuildContext context,
1032- required WidgetRef ref,
1033- required String audioItemId,
1034- required String sentenceText,
1035- required int ? sentenceIndex,
1036- required int ? sentenceStartMs,
1037- required int ? sentenceEndMs,
1038- }) async {
1039- final audioItemDao = ref.read (audioItemDaoProvider);
1040- final audio = await audioItemDao.getById (audioItemId);
1041- if (audio == null ) return ;
1042-
1043- final storedRangeIsValid =
1044- sentenceStartMs != null &&
1045- sentenceEndMs != null &&
1046- sentenceEndMs - sentenceStartMs >= 200 ;
1047- final storedIndexIsValid = sentenceIndex != null && sentenceIndex >= 0 ;
1048- final storedLocation = storedRangeIsValid && storedIndexIsValid
1049- ? (
1050- sentenceIndex: sentenceIndex,
1051- startTimeMs: sentenceStartMs,
1052- endTimeMs: sentenceEndMs,
1053- )
1054- : null ;
1055- final location =
1056- storedLocation ??
1057- await _resolveSourceSentenceLocation (
1058- audioItemDao: audioItemDao,
1059- audioItemId: audioItemId,
1060- sentenceText: sentenceText,
1061- preferredSentenceIndex: sentenceIndex,
1062- );
1063- if (location == null || ! context.mounted) return ;
1064-
1065- AppRoutes .pushNested (
1066- context,
1067- AppRoutes .sentenceDetailSegment,
1068- extra: SentenceDetailArgs (
1069- audioItemId: audioItemId,
1070- audioName: audio.name,
1071- sentenceText: sentenceText,
1072- sentenceIndex: location.sentenceIndex,
1073- startTimeMs: location.startTimeMs,
1074- endTimeMs: location.endTimeMs,
1075- wordHighlightSource: _audioEngineWordHighlightSource (ref, audioItemId),
1076- ),
1077- );
1078- }
1079-
1080- /// 收藏详情页的句子试听由全局音频引擎播放,因此显式绑定该引擎的时间轴。
1081- WordHighlightSource _audioEngineWordHighlightSource (
1082- WidgetRef ref,
1083- String audioItemId,
1084- ) {
1085- final engine = ref.read (audioEngineProvider.notifier);
1086- return WordHighlightSource (
1087- audioItemId: audioItemId,
1088- positionStream: engine.absolutePositionStream,
1089- currentPosition: engine.absoluteCurrentPosition,
1090- playbackStatus: engine.wordHighlightPlaybackStatus,
1091- playbackStatusStream: engine.wordHighlightPlaybackStatusStream,
1092- );
1093- }
1094-
1095- /// 从材料字幕恢复来源句的位置;优先信任仍与文本一致的原句序。
1096- Future <({int sentenceIndex, int startTimeMs, int endTimeMs})?>
1097- _resolveSourceSentenceLocation ({
1098- required AudioItemDao audioItemDao,
1099- required String audioItemId,
1100- required String sentenceText,
1101- required int ? preferredSentenceIndex,
1102- }) async {
1103- final srt = await audioItemDao.getTranscriptSrt (audioItemId);
1104- if (srt == null || srt.isEmpty) return null ;
1105- final sentences = await SubtitleParser .parseSubtitleString (srt);
1106- final normalizedText = sentenceText.trim ();
1107- if (normalizedText.isEmpty) return null ;
1108-
1109- final preferred =
1110- preferredSentenceIndex != null &&
1111- preferredSentenceIndex >= 0 &&
1112- preferredSentenceIndex < sentences.length
1113- ? sentences[preferredSentenceIndex]
1114- : null ;
1115- final resolved = preferred? .text.trim () == normalizedText
1116- ? preferred
1117- : sentences.cast <Sentence ?>().firstWhere (
1118- (candidate) => candidate? .text.trim () == normalizedText,
1119- orElse: () => null ,
1120- );
1121- if (resolved == null ) return null ;
1122- return (
1123- sentenceIndex: resolved.index,
1124- startTimeMs: resolved.startTime.inMilliseconds,
1125- endTimeMs: resolved.endTime.inMilliseconds,
1126- );
1127- }
1128-
11291022// ============================================================
11301023// 单词视图
11311024// ============================================================
@@ -1561,7 +1454,7 @@ class _SavedPhraseTileState extends ConsumerState<_SavedPhraseTile> {
15611454 final audioItemId = phrase.audioItemId;
15621455 final sentenceText = phrase.sentenceText;
15631456 if (audioItemId == null || sentenceText == null ) return Future .value ();
1564- return _openFavoriteSourceSentenceDetail (
1457+ return openFavoriteSourceSentenceDetail (
15651458 context: context,
15661459 ref: ref,
15671460 audioItemId: audioItemId,
@@ -1834,7 +1727,7 @@ class _SavedWordTileState extends ConsumerState<_SavedWordTile> {
18341727 final audioItemId = word.audioItemId;
18351728 final sentenceText = word.sentenceText;
18361729 if (audioItemId == null || sentenceText == null ) return Future .value ();
1837- return _openFavoriteSourceSentenceDetail (
1730+ return openFavoriteSourceSentenceDetail (
18381731 context: context,
18391732 ref: ref,
18401733 audioItemId: audioItemId,
0 commit comments