11import 'package:dpip/core/error/result.dart' ;
2+ import 'package:dpip/core/geo/location_service.dart' ;
3+ import 'package:dpip/core/geo/location_status.dart' ;
24import 'package:dpip/core/geo/town.dart' ;
35import 'package:dpip/core/geo/town_directory.dart' ;
46import 'package:dpip/core/realtime/clock.dart' ;
@@ -11,12 +13,14 @@ import 'package:dpip/core/realtime/ticker.dart';
1113import 'package:dpip/core/settings/settings_store.dart' ;
1214import 'package:dpip/core/settings/region_store.dart' ;
1315import 'package:dpip/features/earthquake/domain/eew.dart' ;
16+ import 'package:dpip/features/earthquake/domain/seismic_travel_time.dart' ;
1417import 'package:dpip/features/events/domain/event.dart' ;
1518import 'package:dpip/features/events/domain/event_repository.dart' ;
1619import 'package:dpip/features/home/presentation/home_active_events_controller.dart' ;
1720import 'package:dpip/features/home/presentation/home_weather_controller.dart' ;
1821import 'package:dpip/features/home/presentation/widgets/home_active_events_section.dart' ;
1922import 'package:dpip/features/home/presentation/widgets/home_content.dart' ;
23+ import 'package:dpip/features/home/presentation/widgets/home_eew_section.dart' ;
2024import 'package:dpip/features/home/presentation/widgets/home_forecast_section.dart' ;
2125import 'package:dpip/features/home/presentation/widgets/home_rain_trend_section.dart' ;
2226import 'package:dpip/features/home/presentation/widgets/home_sheet_header.dart' ;
@@ -114,11 +118,43 @@ class _StaticEewSource extends RealtimeSource<List<Eew>> {
114118 bool sameData (List <Eew >? a, List <Eew >? b) => listEquals (a, b);
115119}
116120
121+ /// A refreshed EEW channel carrying one live alert, ready to hand to [_wrap] .
122+ Future <RealtimeNotifier <List <Eew >>> _liveEew () async {
123+ final channel = RealtimeChannel <List <Eew >>(
124+ source: _StaticEewSource ([
125+ Eew (
126+ agency: 'CWA' ,
127+ id: 'test' ,
128+ serial: 34 ,
129+ status: 0 ,
130+ isFinal: false ,
131+ info: const EewInfo (
132+ time: 1786362600000 ,
133+ longitude: 121.5 ,
134+ latitude: 23.5 ,
135+ depth: 10 ,
136+ magnitude: 7.5 ,
137+ location: '臺東縣' ,
138+ max: 6 ,
139+ ),
140+ ),
141+ ]),
142+ clock: _FakeClock (),
143+ elapsed: _FakeElapsed (),
144+ ticker: _FakeTicker (),
145+ config: RealtimeConfig .eew,
146+ label: 'test-eew' ,
147+ );
148+ await channel.refreshNow ();
149+ return RealtimeNotifier <List <Eew >>(channel);
150+ }
151+
117152/// Pumps [HomeContent] with everything it reads: a [RegionStore] to switch on
118153/// and localizations for the body.
119154Widget _wrap (
120155 RegionStore store, {
121156 bool expanded = false ,
157+ RealtimeNotifier <List <Eew >>? eew,
122158 double topInset = 0 ,
123159 double textScale = 1 ,
124160 ScrollController ? controller,
@@ -147,18 +183,36 @@ Widget _wrap(
147183 ChangeNotifierProvider <HomeActiveEventsController >(
148184 create: (_) => HomeActiveEventsController (events, store),
149185 ),
150- ChangeNotifierProvider <RealtimeNotifier <List <Eew >>>(
151- create: (_) => RealtimeNotifier <List <Eew >>(
152- RealtimeChannel <List <Eew >>(
153- source: _StaticEewSource (const []),
154- clock: _FakeClock (),
155- elapsed: _FakeElapsed (),
156- ticker: _FakeTicker (),
157- config: RealtimeConfig .eew,
158- label: 'test-eew' ,
159- ),
186+ // Read by the EEW alert card for its 所在地預估 countdown.
187+ Provider <Future <SeismicTravelTimeTable >>.value (
188+ value: Future <SeismicTravelTimeTable >.value (
189+ const SeismicTravelTimeTable ({}),
160190 ),
161191 ),
192+ Provider <LocationService >.value (
193+ value: LocationService (
194+ directory,
195+ isAvailable: () async => false ,
196+ fix: () async => null ,
197+ lastKnown: () async => null ,
198+ status: () async => LocationStatus .denied,
199+ ),
200+ ),
201+ if (eew != null )
202+ ChangeNotifierProvider <RealtimeNotifier <List <Eew >>>.value (value: eew)
203+ else
204+ ChangeNotifierProvider <RealtimeNotifier <List <Eew >>>(
205+ create: (_) => RealtimeNotifier <List <Eew >>(
206+ RealtimeChannel <List <Eew >>(
207+ source: _StaticEewSource (const []),
208+ clock: _FakeClock (),
209+ elapsed: _FakeElapsed (),
210+ ticker: _FakeTicker (),
211+ config: RealtimeConfig .eew,
212+ label: 'test-eew' ,
213+ ),
214+ ),
215+ ),
162216 ],
163217 child: Builder (
164218 builder: (context) => MediaQuery (
@@ -364,6 +418,33 @@ void main() {
364418 expect (find.byType (HomeActiveEventsSection ), findsOneWidget);
365419 });
366420
421+ testWidgets ('the EEW card is full-screen only, never at rest' , (
422+ tester,
423+ ) async {
424+ final store = await _store ();
425+ store
426+ ..select (1 )
427+ ..setCurrentCode ('100' );
428+
429+ // At rest `HomeMonitorBanner` is still on screen with the same alert, so
430+ // the in-sheet card would be a second copy of one warning.
431+ await tester.pumpWidget (_wrap (store, eew: await _liveEew ()));
432+ await tester.pumpAndSettle ();
433+ expect (find.byType (HomeEewSection ), findsNothing);
434+ expect (find.text ('臺東縣' ), findsNothing);
435+
436+ // Full-screen the banner has slid away, so the card carries the alert.
437+ await tester.pumpWidget (
438+ _wrap (store, expanded: true , eew: await _liveEew ()),
439+ );
440+ await tester.pumpAndSettle ();
441+ expect (find.byType (HomeEewSection ), findsOneWidget);
442+ expect (find.text ('臺東縣' ), findsOneWidget);
443+
444+ // Tear down so the card's countdown timer is cancelled.
445+ await tester.pumpWidget (const SizedBox ());
446+ });
447+
367448 testWidgets ('a dry hour hides the rain trend and raises a compact forecast' , (
368449 tester,
369450 ) async {
0 commit comments