From d4c6cdb013e3117af7d304a6c96ed77fb6e9ab10 Mon Sep 17 00:00:00 2001 From: virtus Date: Mon, 27 Apr 2026 00:58:51 +0700 Subject: [PATCH] feat: Add native TTS snapshot reconciliation and lifecycle management --- .../reader/presentation/reader_screen.dart | 32 ++++++++++++++++++- lib/features/reader/tts/tts_service.dart | 16 ++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/features/reader/presentation/reader_screen.dart b/lib/features/reader/presentation/reader_screen.dart index c512545..e161267 100644 --- a/lib/features/reader/presentation/reader_screen.dart +++ b/lib/features/reader/presentation/reader_screen.dart @@ -26,7 +26,8 @@ class ReaderScreen extends ConsumerStatefulWidget { ConsumerState createState() => _ReaderScreenState(); } -class _ReaderScreenState extends ConsumerState { +class _ReaderScreenState extends ConsumerState + with WidgetsBindingObserver { static const List _backgroundColorChoices = [ Color(0xFFFFFEF8), Color(0xFFF6EAD7), @@ -260,11 +261,39 @@ class _ReaderScreenState extends ConsumerState { return partiallyVisibleIndex ?? 0; } + Future _reconcileChapterWithNativeTts() async { + if (defaultTargetPlatform != TargetPlatform.android) return; + + final notifier = ref.read(ttsProvider.notifier); + await notifier.refreshNativeSnapshot(); + if (!mounted) return; + + final tts = ref.read(ttsProvider); + final targetChapterId = tts.contentKey; + if (targetChapterId == null || targetChapterId.isEmpty) return; + if (targetChapterId == widget.chapterId) return; + if (tts.status != TtsStatus.playing && tts.status != TtsStatus.paused) return; + + context.pushReplacement(RouteNames.readerChapter(targetChapterId)); + } + @override void initState() { super.initState(); + WidgetsBinding.instance.addObserver(this); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); _scrollCtrl.addListener(_onScroll); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + unawaited(_reconcileChapterWithNativeTts()); + }); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + unawaited(_reconcileChapterWithNativeTts()); + } } /// Handle TTS state transitions that require navigation or restarts. @@ -336,6 +365,7 @@ class _ReaderScreenState extends ConsumerState { @override void dispose() { + WidgetsBinding.instance.removeObserver(this); _uiAutoHideTimer?.cancel(); _scrollCtrl.removeListener(_onScroll); _scrollCtrl.dispose(); diff --git a/lib/features/reader/tts/tts_service.dart b/lib/features/reader/tts/tts_service.dart index 0b74570..a62bcd3 100644 --- a/lib/features/reader/tts/tts_service.dart +++ b/lib/features/reader/tts/tts_service.dart @@ -261,6 +261,22 @@ class TtsNotifier extends StateNotifier { await _mediaChannel.invokeMethod('openNotificationSettings'); } + Future refreshNativeSnapshot() async { + if (!_useNativeAndroidMediaService) return; + + if (!_initialized) { + await (_initFuture ?? _init()); + return; + } + + try { + final snapshot = await _mediaChannel.invokeMethod('getSnapshot'); + _applyAndroidSnapshot(snapshot); + } catch (_) { + // Ignore snapshot pull errors; event stream updates will continue. + } + } + Future _configureVietnameseVoiceWithFlutterTts() async { final dynamic voicesRaw = await _tts.getVoices;