diff --git a/lib/providers/briefing_provider.dart b/lib/providers/briefing_provider.dart index 40786f1..42d76be 100644 --- a/lib/providers/briefing_provider.dart +++ b/lib/providers/briefing_provider.dart @@ -23,6 +23,24 @@ class BriefingNotifier extends AsyncNotifier { return ref.read(briefingApiProvider).getToday(); } + /// Silently fetch the latest briefing and patch state without triggering + /// AsyncLoading — existing content stays visible while the fetch is in flight. + Future silentRefresh() async { + final current = state.value; + if (current == null) return; + try { + final fresh = await ref.read(briefingApiProvider).getToday(); + final curLast = current.messages.isNotEmpty ? current.messages.last : null; + final newLast = fresh.messages.isNotEmpty ? fresh.messages.last : null; + if (fresh.messages.length != current.messages.length || + newLast?.content != curLast?.content) { + state = AsyncData(fresh); + } + } catch (_) { + // Network hiccup — silently ignore, keep existing content + } + } + /// Trigger a briefing slot (e.g. "compilation") then reload. Future refresh(String slot) async { await ref.read(briefingApiProvider).triggerSlot(slot); diff --git a/lib/screens/briefing/briefing_screen.dart b/lib/screens/briefing/briefing_screen.dart index 40ce3e5..dd36df0 100644 --- a/lib/screens/briefing/briefing_screen.dart +++ b/lib/screens/briefing/briefing_screen.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -16,15 +18,40 @@ class BriefingScreen extends ConsumerStatefulWidget { ConsumerState createState() => _BriefingScreenState(); } -class _BriefingScreenState extends ConsumerState { +class _BriefingScreenState extends ConsumerState + with WidgetsBindingObserver { final _controller = TextEditingController(); final _scrollController = ScrollController(); bool _refreshing = false; // rss_item_id -> 'up' | 'down' | null final Map _reactions = {}; + Timer? _pollTimer; + bool _appInForeground = true; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + _pollTimer = Timer.periodic(const Duration(seconds: 60), (_) => _pollSilently()); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + _appInForeground = state == AppLifecycleState.resumed; + } + + void _pollSilently() { + if (!_appInForeground || !mounted) return; + final isStreaming = ref.read(isBriefingStreamingProvider); + if (isStreaming) return; + ref.read(briefingProvider.notifier).silentRefresh(); + } + @override void dispose() { + _pollTimer?.cancel(); + WidgetsBinding.instance.removeObserver(this); _controller.dispose(); _scrollController.dispose(); super.dispose();