Skip to content

Commit 1c1ce8b

Browse files
archie0732lowrt
authored andcommitted
feat(eew): let the user turn off the spoken intensity announcement
New(zh-Hant): 「更多 → 通知」可以關閉強震監視器朗讀預估震度的語音 New(en-US): the monitor's spoken intensity announcement can be switched off under More → Notifications New(ja-JP): 強震モニタの予想震度の読み上げを「その他 → 通知」でオフにできます
1 parent cf4a270 commit 1c1ce8b

29 files changed

Lines changed: 313 additions & 14 deletions
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import 'package:dpip/core/settings/setting_keys.dart';
2+
import 'package:dpip/core/settings/settings_store.dart';
3+
import 'package:flutter/foundation.dart';
4+
5+
/// Whether the visible seismic monitor speaks the estimated intensity before
6+
/// the EEW warning sound plays, persisted via [SettingsStore]. **On** by
7+
/// default: the announcement is what buys the seconds between the alert and
8+
/// the shaking, so a user who wants silence opts out rather than in.
9+
///
10+
/// Turning it off never delays a warning. The monitor drops its announcement
11+
/// controller to inactive, which releases anything the foreground gate is
12+
/// holding, so the channel's own sound plays exactly as it did before this
13+
/// feature existed.
14+
class EewSpokenAnnouncementSettings extends ChangeNotifier {
15+
EewSpokenAnnouncementSettings(this._settings);
16+
17+
final SettingsStore _settings;
18+
19+
/// Whether the foreground monitor may speak.
20+
bool get enabled =>
21+
_settings.getBool(SettingKeys.eewSpokenAnnouncement) ?? true;
22+
23+
Future<void> setEnabled(bool value) async {
24+
await _settings.setBool(SettingKeys.eewSpokenAnnouncement, value);
25+
notifyListeners();
26+
}
27+
}

lib/core/settings/setting_keys.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,13 @@ abstract final class SettingKeys {
237237
'earthquake.eewCwaOnly',
238238
);
239239

240+
/// Whether the visible seismic monitor speaks the estimated intensity before
241+
/// the EEW warning sound. Defaults to true. See
242+
/// `EewSpokenAnnouncementSettings`.
243+
static const SettingKey<bool> eewSpokenAnnouncement = SettingKey<bool>._(
244+
'earthquake.eewSpokenAnnouncement',
245+
);
246+
240247
/// Selected LB / Core API region. See `RegionSelection`.
241248
///
242249
/// Colon-form kept as-is (pre-existing storage address).

lib/features/earthquake/earthquake_providers.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:dpip/core/build/demo_flags.dart';
22
import 'package:dpip/core/di/shared_deps.dart';
33
import 'package:dpip/core/settings/eew_cwa_only_settings.dart';
4+
import 'package:dpip/core/settings/eew_spoken_announcement_settings.dart';
45
import 'package:dpip/core/realtime/elapsed.dart';
56
import 'package:dpip/core/realtime/realtime_channel.dart';
67
import 'package:dpip/core/realtime/realtime_config.dart';
@@ -37,6 +38,7 @@ import 'package:provider/single_child_widget.dart';
3738
List<SingleChildWidget> earthquakeProviders(SharedDeps deps) {
3839
final api = EarthquakeApi(deps.apiClient);
3940
final eewCwaOnly = EewCwaOnlySettings(deps.settings);
41+
final eewSpokenAnnouncement = EewSpokenAnnouncementSettings(deps.settings);
4042
final repository = EewRepositoryImpl(api, cwaOnly: () => eewCwaOnly.enabled);
4143
final reports = ReportRepositoryImpl(api);
4244
final tremStations = TremStationRepositoryImpl(deps.apiClient);
@@ -92,6 +94,9 @@ List<SingleChildWidget> earthquakeProviders(SharedDeps deps) {
9294
return [
9395
Provider<EewRepository>.value(value: repository),
9496
ChangeNotifierProvider<EewCwaOnlySettings>.value(value: eewCwaOnly),
97+
ChangeNotifierProvider<EewSpokenAnnouncementSettings>.value(
98+
value: eewSpokenAnnouncement,
99+
),
95100
Provider<ReportRepository>.value(value: reports),
96101
ChangeNotifierProvider<EewRealtimeController>.value(value: eewController),
97102
ChangeNotifierProvider<RtsRealtimeController>.value(value: rtsController),

lib/features/map/presentation/widgets/rts_monitor_panel.dart

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import 'package:dpip/features/earthquake/domain/eew.dart';
2121
import 'package:dpip/features/earthquake/domain/eew_local_estimate.dart';
2222
import 'package:dpip/features/earthquake/domain/rts.dart';
2323
import 'package:dpip/features/map/presentation/pages/map_page.dart';
24+
import 'package:dpip/core/settings/eew_spoken_announcement_settings.dart';
2425
import 'package:dpip/features/map/presentation/monitor_eew_announcement_controller.dart';
2526
import 'package:dpip/features/map/presentation/widgets/monitor_eew_card.dart';
2627
import 'package:dpip/l10n/gen/app_localizations.dart';
@@ -75,6 +76,10 @@ class _RtsMonitorPanelState extends State<RtsMonitorPanel>
7576
bool _visible = true;
7677
VisibleTab? _visibleTab;
7778
MonitorEewAnnouncementController? _announcement;
79+
80+
/// The user's on/off switch for the announcement, watched so flipping it
81+
/// takes effect on the alert already on screen rather than the next one.
82+
EewSpokenAnnouncementSettings? _speechSettings;
7883
AppLocalizations? _l10n;
7984
String _languageTag = 'zh-TW';
8085
AppLifecycleState? _lifecycleState;
@@ -126,6 +131,12 @@ class _RtsMonitorPanelState extends State<RtsMonitorPanel>
126131
visibleTab?.addListener(_syncVisibility);
127132
_syncVisibility();
128133
}
134+
final speechSettings = context.read<EewSpokenAnnouncementSettings?>();
135+
if (!identical(speechSettings, _speechSettings)) {
136+
_speechSettings?.removeListener(_syncAnnouncement);
137+
_speechSettings = speechSettings;
138+
speechSettings?.addListener(_syncAnnouncement);
139+
}
129140
_syncAnnouncement();
130141
}
131142

@@ -180,7 +191,12 @@ class _RtsMonitorPanelState extends State<RtsMonitorPanel>
180191
if (controller == null || l10n == null) return;
181192
final foreground =
182193
_lifecycleState == null || _lifecycleState == AppLifecycleState.resumed;
183-
controller.setActive(_isMonitorOnScreen && foreground);
194+
// Absent provider means a test that supplied neither — announce, matching
195+
// the default. Switching off deactivates the controller, which stops any
196+
// phrase in flight and releases the notification the gate was holding, so
197+
// the warning sound is never delayed by a setting the user just turned off.
198+
final speechEnabled = _speechSettings?.enabled ?? true;
199+
controller.setActive(speechEnabled && _isMonitorOnScreen && foreground);
184200
controller.update(
185201
widget.eew.state,
186202
languageTag: _languageTag,
@@ -229,6 +245,7 @@ class _RtsMonitorPanelState extends State<RtsMonitorPanel>
229245
widget.eew.removeListener(_onData);
230246
widget.eewIndex.removeListener(_onData);
231247
_visibleTab?.removeListener(_syncVisibility);
248+
_speechSettings?.removeListener(_syncAnnouncement);
232249
WidgetsBinding.instance.removeObserver(this);
233250
_announcement?.dispose();
234251
super.dispose();

lib/features/more/presentation/pages/more_page.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:dpip/features/bug_tracker/bug_tracker_counter.dart';
1111
import 'package:dpip/core/network/endpoint_health.dart';
1212
import 'package:dpip/core/settings/default_map_layer_controller.dart';
1313
import 'package:dpip/core/settings/eew_cwa_only_settings.dart';
14+
import 'package:dpip/core/settings/eew_spoken_announcement_settings.dart';
1415
import 'package:dpip/core/settings/experimental_settings.dart';
1516
import 'package:dpip/core/settings/region_store.dart';
1617
import 'package:dpip/core/version/app_build.dart';
@@ -86,6 +87,12 @@ class MorePage extends StatelessWidget {
8687
),
8788
onTap: () => context.pushNamed(AppRoutes.permissions),
8889
),
90+
// In the notification group rather than under 顯示: what this
91+
// switches is the order of two *sounds*, not anything drawn.
92+
// Below 權限檢查, which has to stay next to the notification
93+
// settings — it is the row people reach for when an alert did
94+
// not arrive.
95+
const _SpokenAnnouncementTile(),
8996
// What the system says actually went out — a status page, kept
9097
// in the notification group because that is where you look when
9198
// an alert did not arrive.
@@ -473,6 +480,35 @@ class _MoreTile extends StatelessWidget {
473480
}
474481
}
475482

483+
/// The monitor's spoken-intensity switch.
484+
///
485+
/// A row that acts rather than navigates, so it carries its own trailing
486+
/// [Switch] instead of `_MoreTile`'s chevron, and the whole row toggles — a
487+
/// switch you can only hit by aiming at the switch is a smaller target than the
488+
/// row it sits in.
489+
class _SpokenAnnouncementTile extends StatelessWidget {
490+
const _SpokenAnnouncementTile();
491+
492+
@override
493+
Widget build(BuildContext context) {
494+
final l10n = AppLocalizations.of(context);
495+
final settings = context.watch<EewSpokenAnnouncementSettings>();
496+
final enabled = settings.enabled;
497+
return _MoreTile(
498+
icon: enabled
499+
? Icons.record_voice_over_outlined
500+
: Icons.voice_over_off_outlined,
501+
title: l10n.eewSpokenAnnouncementTitle,
502+
subtitle: l10n.eewSpokenAnnouncementDescription,
503+
trailing: Switch(
504+
value: enabled,
505+
onChanged: (value) => settings.setEnabled(value),
506+
),
507+
onTap: () => settings.setEnabled(!enabled),
508+
);
509+
}
510+
}
511+
476512
class _SavedRegionsTile extends StatefulWidget {
477513
const _SavedRegionsTile();
478514

lib/l10n/app_en.arb

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3962,11 +3962,27 @@
39623962
"eewSpokenLocalIntensity": "Estimated intensity at your location: {intensity}.",
39633963
"@eewSpokenLocalIntensity": {
39643964
"description": "Short foreground TTS phrase before an EEW warning sound",
3965-
"placeholders": {"intensity": {"type": "String"}}
3965+
"placeholders": {
3966+
"intensity": {
3967+
"type": "String"
3968+
}
3969+
}
39663970
},
39673971
"eewSpokenMaxIntensity": "Estimated maximum intensity: {intensity}.",
39683972
"@eewSpokenMaxIntensity": {
39693973
"description": "TTS fallback when the device location is unavailable",
3970-
"placeholders": {"intensity": {"type": "String"}}
3974+
"placeholders": {
3975+
"intensity": {
3976+
"type": "String"
3977+
}
3978+
}
3979+
},
3980+
"eewSpokenAnnouncementTitle": "Speak estimated intensity",
3981+
"eewSpokenAnnouncementDescription": "When the seismic monitor is open, the estimated intensity is read aloud before the warning sound plays.",
3982+
"@eewSpokenAnnouncementTitle": {
3983+
"description": "Settings row: toggles the monitor's spoken intensity announcement"
3984+
},
3985+
"@eewSpokenAnnouncementDescription": {
3986+
"description": "Explains what the spoken-announcement toggle does and when it speaks"
39713987
}
39723988
}

lib/l10n/app_fil.arb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1984,5 +1984,7 @@
19841984
"bugTrackerSortMostDiscussed": "Pinakamaraming talakayan",
19851985
"bugTrackerStaff": "Kawani",
19861986
"eewSpokenLocalIntensity": "Tinatayang intensidad sa iyong lokasyon: {intensity}.",
1987-
"eewSpokenMaxIntensity": "Tinatayang pinakamataas na intensidad: {intensity}."
1987+
"eewSpokenMaxIntensity": "Tinatayang pinakamataas na intensidad: {intensity}.",
1988+
"eewSpokenAnnouncementTitle": "Basahin ang tinatayang intensidad",
1989+
"eewSpokenAnnouncementDescription": "Kapag bukas ang seismic monitor, binabasa nang malakas ang tinatayang intensidad bago tumunog ang babala."
19881990
}

lib/l10n/app_id.arb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1984,5 +1984,7 @@
19841984
"bugTrackerSortMostDiscussed": "Paling banyak dibahas",
19851985
"bugTrackerStaff": "Staf",
19861986
"eewSpokenLocalIntensity": "Perkiraan intensitas di lokasi Anda: {intensity}.",
1987-
"eewSpokenMaxIntensity": "Perkiraan intensitas maksimum: {intensity}."
1987+
"eewSpokenMaxIntensity": "Perkiraan intensitas maksimum: {intensity}.",
1988+
"eewSpokenAnnouncementTitle": "Bacakan intensitas perkiraan",
1989+
"eewSpokenAnnouncementDescription": "Saat monitor gempa terbuka, intensitas perkiraan dibacakan sebelum suara peringatan diputar."
19881990
}

lib/l10n/app_ja.arb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1984,5 +1984,7 @@
19841984
"bugTrackerSortMostDiscussed": "返信が多い順",
19851985
"bugTrackerStaff": "スタッフ",
19861986
"eewSpokenLocalIntensity": "現在地の予想震度、{intensity}。",
1987-
"eewSpokenMaxIntensity": "予想最大震度、{intensity}。"
1987+
"eewSpokenMaxIntensity": "予想最大震度、{intensity}。",
1988+
"eewSpokenAnnouncementTitle": "予想震度を読み上げる",
1989+
"eewSpokenAnnouncementDescription": "強震モニタを開いているとき、警報音の前に予想震度を音声で読み上げます。"
19881990
}

lib/l10n/app_ko.arb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1984,5 +1984,7 @@
19841984
"bugTrackerSortMostDiscussed": "답글 많은 순",
19851985
"bugTrackerStaff": "스태프",
19861986
"eewSpokenLocalIntensity": "현재 위치 예상 진도, {intensity}.",
1987-
"eewSpokenMaxIntensity": "예상 최대 진도, {intensity}."
1987+
"eewSpokenMaxIntensity": "예상 최대 진도, {intensity}.",
1988+
"eewSpokenAnnouncementTitle": "예상 진도 음성 안내",
1989+
"eewSpokenAnnouncementDescription": "지진 모니터를 열었을 때 경보음보다 먼저 예상 진도를 음성으로 안내합니다."
19881990
}

0 commit comments

Comments
 (0)