diff --git a/.crowdin/strings.pot b/.crowdin/strings.pot
index 0c93b9b6f..aafc7d610 100644
--- a/.crowdin/strings.pot
+++ b/.crowdin/strings.pot
@@ -1,338 +1,338 @@
-#: ./lib/core/service.dart:291
+#: ./lib/core/service.dart:285
msgid "正在更新位置"
msgstr ""
-#: ./lib/core/service.dart:292
+#: ./lib/core/service.dart:286
msgid "取得 GPS 位置中..."
msgstr ""
-#: ./lib/app/settings/notify/page.dart:51
+#: ./lib/app/settings/notify/page.dart:56
msgid "接收全部"
msgstr ""
-#: ./lib/app/settings/notify/page.dart:50
+#: ./lib/app/settings/notify/page.dart:65
msgid "關閉"
msgstr ""
-#: ./lib/app/settings/notify/_widgets/eew_notify_section.dart:51
+#: ./lib/app/settings/notify/_widgets/eew_notify_section.dart:70
msgid "接收類別"
msgstr ""
-#: ./lib/app/settings/notify/page.dart:35
+#: ./lib/app/settings/notify/page.dart:55
msgid "所在地震度1以上"
msgstr ""
-#: ./lib/app/settings/notify/page.dart:46
+#: ./lib/app/settings/notify/page.dart:61
msgid "海嘯消息、海嘯警報"
msgstr ""
-#: ./lib/app/settings/notify/page.dart:45
+#: ./lib/app/settings/notify/page.dart:60
msgid "只接收海嘯警報"
msgstr ""
-#: ./lib/app/settings/notify/page.dart:41
+#: ./lib/app/settings/notify/page.dart:66
msgid "接收所在地"
msgstr ""
-#: ./lib/app/settings/notify/page.dart:27
+#: ./lib/app/settings/notify/page.dart:54
msgid "所在地震度4以上"
msgstr ""
-#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:28
+#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:33
msgid "音效測試"
msgstr ""
-#: ./lib/route/announcement/announcement.dart:81
+#: ./lib/route/announcement/announcement.dart:80
msgid "公告"
msgstr ""
-#: ./lib/app/settings/notify/(5.basic)/announcement/page.dart:32
+#: ./lib/app/settings/notify/(5.basic)/announcement/page.dart:37
msgid "發送公告時"
msgstr ""
-#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:46
+#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:51
msgid "音效測試為在裝置上執行的本地通知,僅用於確認裝置在接收通知時是否能正常播放音效。此測試不會向伺服器發送任何請求"
msgstr ""
-#: ./lib/app/settings/notify/page.dart:82
+#: ./lib/app/settings/notify/page.dart:104
msgid "伺服器排隊中,請稍候…"
msgstr ""
-#: ./lib/app/welcome/4-permissions/page.dart:166
+#: ./lib/app/welcome/4-permissions/page.dart:111
msgid "通知"
msgstr ""
-#: ./lib/app/settings/page.dart:182
+#: ./lib/app/settings/page.dart:189
msgid "推播通知設定與通知音效測試"
msgstr ""
-#: ./lib/app/home/_widgets/location_not_set_card.dart:33
+#: ./lib/app/home_old/_widgets/location_not_set_card.dart:41
msgid "尚未設定所在地"
msgstr ""
-#: ./lib/app/settings/notify/page.dart:201
+#: ./lib/app/settings/notify/page.dart:208
msgid "請先設定所在地來使用通知功能"
msgstr ""
-#: ./lib/app/settings/notify/page.dart:211
+#: ./lib/app/settings/notify/page.dart:217
msgid "設定所在地"
msgstr ""
-#: ./lib/app/settings/experimental/page.dart:209
+#: ./lib/app/settings/experimental/page.dart:215
msgid "地震速報"
msgstr ""
-#: ./lib/app/settings/notify/page.dart:232
+#: ./lib/app/settings/notify/page.dart:238
msgid "緊急地震速報"
msgstr ""
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:113
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:125
msgid "地震"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:1579
+#: ./lib/app/map/_lib/managers/monitor.dart:1584
msgid "強震監視器"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:1302
+#: ./lib/app/map/_lib/managers/report.dart:1294
msgid "地震報告"
msgstr ""
-#: ./lib/app/settings/notify/page.dart:290
+#: ./lib/app/settings/notify/page.dart:297
msgid "震度速報"
msgstr ""
-#: ./lib/app/settings/notify/page.dart:304
+#: ./lib/app/settings/notify/page.dart:310
msgid "天氣"
msgstr ""
-#: ./lib/app/home/_widgets/thunderstorm_card.dart:63
+#: ./lib/app/home_old/_widgets/thunderstorm_card.dart:72
msgid "雷雨即時訊息"
msgstr ""
-#: ./lib/app/settings/notify/page.dart:334
+#: ./lib/app/settings/notify/page.dart:339
msgid "天氣警特報"
msgstr ""
-#: ./lib/app/settings/notify/page.dart:354
+#: ./lib/app/settings/notify/page.dart:358
msgid "防災資訊"
msgstr ""
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:129
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:141
msgid "海嘯"
msgstr ""
-#: ./lib/app/settings/notify/page.dart:378
+#: ./lib/app/settings/notify/page.dart:383
msgid "海嘯資訊"
msgstr ""
-#: ./lib/app/settings/notify/page.dart:390
+#: ./lib/app/settings/notify/page.dart:394
msgid "其他"
msgstr ""
-#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:31
+#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:36
msgid "重大"
msgstr ""
-#: ./lib/app/settings/notify/(4.tsunami)/tsunami/page.dart:31
+#: ./lib/app/settings/notify/(4.tsunami)/tsunami/page.dart:36
msgid "海嘯警報發布時"
msgstr ""
-#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:37
+#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:42
msgid "一般"
msgstr ""
-#: ./lib/app/settings/notify/(4.tsunami)/tsunami/page.dart:37
+#: ./lib/app/settings/notify/(4.tsunami)/tsunami/page.dart:42
msgid "海嘯消息發布時"
msgstr ""
-#: ./lib/app/settings/notify/(4.tsunami)/tsunami/page.dart:41
+#: ./lib/app/settings/notify/(4.tsunami)/tsunami/page.dart:46
msgid "太平洋海嘯消息(無聲通知)"
msgstr ""
-#: ./lib/app/settings/notify/(4.tsunami)/tsunami/page.dart:42
+#: ./lib/app/settings/notify/(4.tsunami)/tsunami/page.dart:47
msgid "太平洋海嘯消息發布時"
msgstr ""
-#: ./lib/app/settings/notify/(2.earthquake)/monitor/page.dart:30
+#: ./lib/app/settings/notify/(2.earthquake)/monitor/page.dart:35
msgid "強震監視器(一般)"
msgstr ""
-#: ./lib/app/settings/notify/(2.earthquake)/monitor/page.dart:31
+#: ./lib/app/settings/notify/(2.earthquake)/monitor/page.dart:36
msgid "偵測到晃動"
msgstr ""
-#: ./lib/app/settings/notify/(2.earthquake)/intensity/page.dart:30
+#: ./lib/app/settings/notify/(2.earthquake)/intensity/page.dart:35
msgid "震度速報(一般)"
msgstr ""
-#: ./lib/app/settings/notify/(2.earthquake)/intensity/page.dart:31
+#: ./lib/app/settings/notify/(2.earthquake)/intensity/page.dart:36
msgid "所在地(鄉鎮)實測震度 3 以上"
msgstr ""
-#: ./lib/app/settings/notify/(2.earthquake)/intensity/page.dart:36
+#: ./lib/app/settings/notify/(2.earthquake)/intensity/page.dart:41
msgid "震度速報(無聲通知)"
msgstr ""
-#: ./lib/app/settings/notify/(2.earthquake)/intensity/page.dart:37
+#: ./lib/app/settings/notify/(2.earthquake)/intensity/page.dart:42
msgid "所在地(鄉鎮)實測震度 1 以上"
msgstr ""
-#: ./lib/app/settings/notify/(2.earthquake)/report/page.dart:30
+#: ./lib/app/settings/notify/(2.earthquake)/report/page.dart:35
msgid "地震報告(一般)"
msgstr ""
-#: ./lib/app/settings/notify/(2.earthquake)/report/page.dart:31
+#: ./lib/app/settings/notify/(2.earthquake)/report/page.dart:36
msgid "所在地(縣市)實測震度 3 以上"
msgstr ""
-#: ./lib/app/settings/notify/(2.earthquake)/report/page.dart:36
+#: ./lib/app/settings/notify/(2.earthquake)/report/page.dart:41
msgid "地震報告(無聲通知)"
msgstr ""
-#: ./lib/app/settings/notify/(2.earthquake)/report/page.dart:37
+#: ./lib/app/settings/notify/(2.earthquake)/report/page.dart:42
msgid "所在地(縣市)實測震度 1 以上"
msgstr ""
-#: ./lib/app/settings/notify/_lib/utils.dart:15
+#: ./lib/app/settings/notify/_lib/utils.dart:29
msgid "已更新通知設定"
msgstr ""
-#: ./lib/app/settings/notify/_lib/utils.dart:22
+#: ./lib/app/settings/notify/_lib/utils.dart:40
msgid "更新通知設定失敗"
msgstr ""
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:30
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:35
msgid "緊急地震速報(重大)"
msgstr ""
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:31
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:36
msgid ""
"最大震度 5 弱以上 且\n"
"所在地(鄉鎮)預估震度 4 以上"
msgstr ""
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:36
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:41
msgid "緊急地震速報(一般)"
msgstr ""
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:37
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:42
msgid ""
"最大震度 5 弱以上 且\n"
"所在地(鄉鎮)預估震度 2 以上"
msgstr ""
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:41
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:46
msgid "緊急地震速報(無聲)"
msgstr ""
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:42
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:47
msgid ""
"最大震度 5 弱以上 且\n"
"所在地(鄉鎮)預估震度 1 以上"
msgstr ""
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:46
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:51
msgid "地震速報(重大)"
msgstr ""
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:47
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:52
msgid "所在地(鄉鎮)預估震度 4 以上"
msgstr ""
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:51
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:56
msgid "地震速報(一般)"
msgstr ""
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:52
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:57
msgid "所在地(鄉鎮)預估震度 2 以上"
msgstr ""
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:56
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:61
msgid "地震速報(無聲)"
msgstr ""
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:57
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:62
msgid "所在地(鄉鎮)預估震度 1 以上"
msgstr ""
-#: ./lib/app/settings/notify/(3.weather)/evacuation/page.dart:32
+#: ./lib/app/settings/notify/(3.weather)/evacuation/page.dart:36
msgid "所在地(鄉鎮)發布防災警訊時"
msgstr ""
-#: ./lib/app/settings/notify/(3.weather)/evacuation/page.dart:38
+#: ./lib/app/settings/notify/(3.weather)/evacuation/page.dart:42
msgid "所在地(鄉鎮)發布防災資訊時"
msgstr ""
-#: ./lib/app/settings/notify/(3.weather)/advisory/page.dart:32
+#: ./lib/app/settings/notify/(3.weather)/advisory/page.dart:37
msgid ""
"所在地(鄉鎮)發布紅色燈號之\n"
"天氣警特報"
msgstr ""
-#: ./lib/app/settings/notify/(3.weather)/advisory/page.dart:38
+#: ./lib/app/settings/notify/(3.weather)/advisory/page.dart:43
msgid ""
"所在地(鄉鎮)發布上述除外燈號之\n"
"天氣警特報"
msgstr ""
-#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:32
+#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:37
msgid "所在地(鄉鎮)發布山區暴雨時"
msgstr ""
-#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:38
+#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:43
msgid "所在地(鄉鎮)發布雷雨即時訊息時"
msgstr ""
-#: ./lib/app/settings/experimental/page.dart:197
-msgid "啟動時進入強震監視器"
+#: ./lib/app/settings/experimental/page.dart:48
+msgid "地震速報不限制非 CWA 來源"
msgstr ""
-#: ./lib/app/settings/experimental/page.dart:57
-msgid "地震速報不限制非 CWA 來源"
+#: ./lib/app/settings/experimental/page.dart:203
+msgid "啟動時進入強震監視器"
msgstr ""
-#: ./lib/app/settings/page.dart:428
+#: ./lib/app/settings/page.dart:430
msgid "實驗性功能"
msgstr ""
-#: ./lib/app/settings/page.dart:429
+#: ./lib/app/settings/page.dart:431
msgid "搶先體驗開發中的新功能"
msgstr ""
-#: ./lib/app/settings/experimental/page.dart:154
+#: ./lib/app/settings/experimental/page.dart:160
msgid "注意"
msgstr ""
-#: ./lib/app/settings/experimental/page.dart:162
+#: ./lib/app/settings/experimental/page.dart:168
msgid "這些功能仍在開發中,可能會不穩定或在未來的版本中變更。"
msgstr ""
-#: ./lib/app/settings/experimental/page.dart:188
+#: ./lib/app/settings/experimental/page.dart:194
msgid "啟動行為"
msgstr ""
-#: ./lib/app/settings/experimental/page.dart:198
+#: ./lib/app/settings/experimental/page.dart:204
msgid "開啟 App 時直接進入強震監視器地圖"
msgstr ""
-#: ./lib/app/settings/experimental/page.dart:218
+#: ./lib/app/settings/experimental/page.dart:224
msgid "不限制非 CWA 來源"
msgstr ""
-#: ./lib/app/settings/experimental/page.dart:219
+#: ./lib/app/settings/experimental/page.dart:225
msgid "顯示所有來源的地震速報資料"
msgstr ""
-#: ./lib/app/settings/experimental/page.dart:280
+#: ./lib/app/settings/experimental/page.dart:281
msgid "啟用實驗性功能"
msgstr ""
-#: ./lib/app/settings/experimental/page.dart:286
+#: ./lib/app/settings/experimental/page.dart:287
msgid "你即將啟用:"
msgstr ""
-#: ./lib/app/settings/experimental/page.dart:317
+#: ./lib/app/settings/experimental/page.dart:318
msgid "此功能為實驗性質,可能會造成應用程式不穩定或行為異常。如遇問題,請至設定中關閉此功能。"
msgstr ""
@@ -340,15 +340,15 @@ msgstr ""
msgid "取消"
msgstr ""
-#: ./lib/app/settings/layout/page.dart:272
+#: ./lib/app/settings/layout/page.dart:163
msgid "啟用"
msgstr ""
-#: ./lib/app/settings/page.dart:151
+#: ./lib/app/settings/page.dart:158
msgid "單位"
msgstr ""
-#: ./lib/app/settings/page.dart:152
+#: ./lib/app/settings/page.dart:159
msgid "調整 DPIP 顯示數值時使用的單位"
msgstr ""
@@ -360,131 +360,131 @@ msgstr ""
msgid "切換溫度顯示單位為華氏度 (℉)"
msgstr ""
-#: ./lib/app/settings/page.dart:141
+#: ./lib/app/settings/page.dart:148
msgid "語言"
msgstr ""
-#: ./lib/app/settings/page.dart:142
+#: ./lib/app/settings/page.dart:149
msgid "調整 DPIP 的顯示語言"
msgstr ""
-#: ./lib/app/settings/locale/page.dart:41
+#: ./lib/app/settings/locale/page.dart:49
msgid "顯示語言"
msgstr ""
-#: ./lib/app/settings/locale/page.dart:42
+#: ./lib/app/settings/locale/page.dart:50
msgid "系統語言"
msgstr ""
-#: ./lib/app/settings/locale/page.dart:52
+#: ./lib/app/settings/locale/page.dart:60
msgid "協助翻譯"
msgstr ""
-#: ./lib/app/settings/locale/page.dart:53
+#: ./lib/app/settings/locale/page.dart:61
msgid "點擊這裡來幫助我們改進 DPIP 的翻譯"
msgstr ""
-#: ./lib/app/settings/locale/select/page.dart:116
+#: ./lib/app/settings/locale/select/page.dart:122
msgid "已翻譯 {translated}・已校對 {approved}"
msgstr ""
-#: ./lib/app/settings/locale/select/page.dart:129
+#: ./lib/app/settings/locale/select/page.dart:135
msgid "來源語言"
msgstr ""
-#: ./lib/app/settings/locale/select/page.dart:163
+#: ./lib/app/settings/locale/select/page.dart:172
msgid "選擇語言"
msgstr ""
-#: ./lib/app/settings/donate/page.dart:52
+#: ./lib/app/settings/donate/page.dart:59
msgid "無法連線至商店,請稍後再試"
msgstr ""
-#: ./lib/app/settings/donate/page.dart:59
+#: ./lib/app/settings/donate/page.dart:65
msgid "找不到商品,請稍候再試"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:521
-msgid "重新載入"
-msgstr ""
-
-#: ./lib/app/settings/donate/page.dart:171
-msgid "正在載入商店物品中"
-msgstr ""
-
-#: ./lib/app/settings/donate/page.dart:225
+#: ./lib/app/settings/donate/page.dart:136
msgid "支持 DPIP"
msgstr ""
-#: ./lib/app/settings/donate/page.dart:233
+#: ./lib/app/settings/donate/page.dart:144
msgid "DPIP 作為一款致力於提供即時地震資訊的 App,目前並無廣告或其他盈利模式。您的支持將幫助我們維持伺服器運行與持續開發。"
msgstr ""
-#: ./lib/app/settings/donate/page.dart:260
+#: ./lib/app/settings/donate/page.dart:170
msgid "訂閱制"
msgstr ""
-#: ./lib/app/settings/donate/page.dart:276
+#: ./lib/app/settings/donate/page.dart:189
msgid "推薦"
msgstr ""
-#: ./lib/app/settings/donate/page.dart:443
+#: ./lib/app/settings/donate/page.dart:353
msgid "{price}/月"
msgstr ""
-#: ./lib/app/settings/donate/page.dart:475
+#: ./lib/app/settings/donate/page.dart:385
msgid "單次支援"
msgstr ""
-#: ./lib/app/settings/donate/page.dart:615
+#: ./lib/app/settings/donate/page.dart:520
msgid "恢復購買"
msgstr ""
-#: ./lib/app/settings/donate/page.dart:628
+#: ./lib/app/settings/donate/page.dart:530
msgid "無法連線至 {store},請稍後再試。"
msgstr ""
-#: ./lib/app/settings/donate/page.dart:639
+#: ./lib/app/settings/donate/page.dart:541
msgid "正在恢復您購買的訂閱"
msgstr ""
-#: ./lib/app/settings/donate/page.dart:644
+#: ./lib/app/settings/donate/page.dart:546
msgid "使用條款"
msgstr ""
-#: ./lib/app/settings/donate/page.dart:648
+#: ./lib/app/settings/donate/page.dart:550
msgid "隱私權政策"
msgstr ""
-#: ./lib/app/settings/proxy/page.dart:51
+#: ./lib/app/map/_lib/managers/report.dart:546
+msgid "重新載入"
+msgstr ""
+
+#: ./lib/app/settings/donate/page.dart:636
+msgid "正在載入商店物品中"
+msgstr ""
+
+#: ./lib/app/settings/proxy/page.dart:38
msgid "設定已儲存"
msgstr ""
-#: ./lib/app/settings/page.dart:200
+#: ./lib/app/settings/page.dart:207
msgid "HTTP 代理"
msgstr ""
-#: ./lib/app/settings/page.dart:201
+#: ./lib/app/settings/page.dart:208
msgid "調整 HTTP 代理伺服器設定"
msgstr ""
-#: ./lib/app/settings/proxy/page.dart:75
+#: ./lib/app/settings/proxy/page.dart:74
msgid "啟用代理"
msgstr ""
-#: ./lib/app/settings/proxy/page.dart:76
+#: ./lib/app/settings/proxy/page.dart:75
msgid "透過代理伺服器發送所有網路請求"
msgstr ""
-#: ./lib/app/settings/proxy/page.dart:88
+#: ./lib/app/settings/proxy/page.dart:87
msgid "代理主機"
msgstr ""
-#: ./lib/app/settings/proxy/page.dart:102
+#: ./lib/app/settings/proxy/page.dart:101
msgid "代理端口"
msgstr ""
-#: ./lib/app/settings/proxy/page.dart:117
+#: ./lib/app/settings/proxy/page.dart:116
msgid "設定儲存後,需要重新啟動應用程式才能生效"
msgstr ""
@@ -492,123 +492,123 @@ msgstr ""
msgid "設定"
msgstr ""
-#: ./lib/app/settings/page.dart:71
+#: ./lib/app/settings/page.dart:79
msgid "自訂你的 DPIP 使用體驗"
msgstr ""
-#: ./lib/app/welcome/4-permissions/page.dart:174
+#: ./lib/app/welcome/4-permissions/page.dart:119
msgid "位置"
msgstr ""
-#: ./lib/app/settings/location/page.dart:417
+#: ./lib/app/settings/location/page.dart:421
msgid "所在地"
msgstr ""
-#: ./lib/app/settings/location/page.dart:241
+#: ./lib/app/settings/location/page.dart:245
msgid "設定你的所在地來接收當地的即時資訊"
msgstr ""
-#: ./lib/app/settings/page.dart:113
+#: ./lib/app/settings/page.dart:120
msgid "介面"
msgstr ""
-#: ./lib/app/settings/layout/page.dart:47
+#: ./lib/app/settings/layout/page.dart:183
msgid "版面"
msgstr ""
-#: ./lib/app/settings/layout/page.dart:48
+#: ./lib/app/settings/layout/page.dart:184
msgid "調整首頁的版面樣式"
msgstr ""
-#: ./lib/app/settings/theme/page.dart:25
+#: ./lib/app/settings/theme/page.dart:32
msgid "主題"
msgstr ""
-#: ./lib/app/settings/theme/page.dart:26
+#: ./lib/app/settings/theme/page.dart:33
msgid "調整 DPIP 整體的外觀與顏色"
msgstr ""
-#: ./lib/app/settings/map/page.dart:49
+#: ./lib/app/home/layout.dart:93
msgid "地圖"
msgstr ""
-#: ./lib/app/settings/map/page.dart:50
+#: ./lib/app/settings/map/page.dart:59
msgid "調整地圖的顯示樣式"
msgstr ""
-#: ./lib/app/settings/page.dart:191
+#: ./lib/app/settings/page.dart:198
msgid "網路"
msgstr ""
-#: ./lib/app/settings/page.dart:210
+#: ./lib/app/settings/page.dart:217
msgid "資訊"
msgstr ""
-#: ./lib/app/changelog/page.dart:54
+#: ./lib/app/home_old/page.dart:163
msgid "更新日誌"
msgstr ""
-#: ./lib/app/settings/page.dart:230
+#: ./lib/app/settings/page.dart:237
msgid "瀏覽 DPIP 的歷次更新紀錄"
msgstr ""
-#: ./lib/app/settings/page.dart:240
+#: ./lib/app/settings/page.dart:247
msgid "第三方套件授權"
msgstr ""
-#: ./lib/app/settings/page.dart:241
+#: ./lib/app/settings/page.dart:248
msgid "DPIP 的實現歸功於開放原始碼"
msgstr ""
-#: ./lib/app/settings/page.dart:264
+#: ./lib/app/settings/page.dart:271
msgid "贊助我們"
msgstr ""
-#: ./lib/app/settings/page.dart:267
+#: ./lib/app/settings/page.dart:274
msgid "幫助我們維護伺服器的穩定和長久發展"
msgstr ""
-#: ./lib/app/settings/page.dart:343
+#: ./lib/app/settings/page.dart:349
msgid "下載"
msgstr ""
-#: ./lib/app/settings/page.dart:383
+#: ./lib/app/settings/page.dart:385
msgid "除錯"
msgstr ""
-#: ./lib/app/settings/page.dart:391
+#: ./lib/app/settings/page.dart:393
msgid "應用程式版本"
msgstr ""
-#: ./lib/app/settings/page.dart:400
+#: ./lib/app/settings/page.dart:402
msgid "裝置資訊"
msgstr ""
-#: ./lib/app/settings/page.dart:409
+#: ./lib/app/settings/page.dart:411
msgid "複製通知 Token"
msgstr ""
-#: ./lib/app/debug/logs/page.dart:16
+#: ./lib/app/debug/logs/page.dart:22
msgid "App 日誌"
msgstr ""
-#: ./lib/app/settings/page.dart:463
+#: ./lib/app/settings/page.dart:465
msgid "任何資訊應以中央氣象署發布之內容為準"
msgstr ""
-#: ./lib/app/settings/location/page.dart:74
+#: ./lib/app/settings/location/page.dart:85
msgid "無法取得通知權限"
msgstr ""
-#: ./lib/app/settings/location/page.dart:76
+#: ./lib/app/settings/location/page.dart:87
msgid "無法取得位置權限"
msgstr ""
-#: ./lib/app/settings/location/page.dart:77
+#: ./lib/app/settings/location/page.dart:88
msgid "無法取得自啟動權限"
msgstr ""
-#: ./lib/app/welcome/4-permissions/page.dart:180
+#: ./lib/app/welcome/4-permissions/page.dart:125
msgid "省電策略"
msgstr ""
@@ -616,782 +616,810 @@ msgstr ""
msgid "無法取得權限"
msgstr ""
-#: ./lib/app/settings/location/page.dart:84
+#: ./lib/app/settings/location/page.dart:94
msgid "自動定位功能需要您允許 DPIP 使用通知權限才能正常運作。請您到應用程式設定中找到並允許「通知」權限後再試一次。"
msgstr ""
-#: ./lib/app/settings/location/page.dart:86
+#: ./lib/app/settings/location/page.dart:95
msgid "自動定位功能需要您允許 DPIP 使用位置權限才能正常運作。請您到應用程式設定中找到並允許「位置」權限後再試一次。"
msgstr ""
-#: ./lib/app/settings/location/page.dart:89
+#: ./lib/app/settings/location/page.dart:98
msgid "自動定位功能需要您永遠允許 DPIP 使用位置權限才能正常運作。請您到應用程式設定中找到位置權限設定並選擇「永遠」後再試一次。"
msgstr ""
-#: ./lib/app/settings/location/page.dart:91
+#: ./lib/app/settings/location/page.dart:99
msgid "自動定位功能需要您一律允許 DPIP 使用位置權限才能正常運作。請您到應用程式設定中找到位置權限設定並選擇「一律允許」後再試一次。"
msgstr ""
-#: ./lib/app/settings/location/page.dart:93
+#: ./lib/app/settings/location/page.dart:100
msgid "為了獲得更好的自動定位體驗,您需要給予「自啟動權限」以便讓 DPIP 在背景自動設定所在地資訊。"
msgstr ""
-#: ./lib/app/settings/location/page.dart:95
+#: ./lib/app/settings/location/page.dart:101
msgid "為了獲得更好的自動定位體驗,您需要給予「無限制」以便讓 DPIP 在背景自動設定所在地資訊。"
msgstr ""
-#: ./lib/app/settings/location/page.dart:96
+#: ./lib/app/settings/location/page.dart:102
msgid "自動定位功能需要您允許 DPIP 使用權限才能正常運作。請您到應用程式設定中找到並允許「權限」後再試一次。"
msgstr ""
-#: ./lib/app/settings/location/page.dart:174
+#: ./lib/app/settings/location/page.dart:180
msgid "自動啟動"
msgstr ""
-#: ./lib/app/settings/location/page.dart:175
+#: ./lib/app/settings/location/page.dart:181
msgid "為了獲得更好的 DPIP 體驗,請依照步驟啟用自動啟動功能,以便讓 DPIP 在背景能正常接收資訊以及更新所在地。"
msgstr ""
-#: ./lib/app/settings/location/page.dart:199
+#: ./lib/app/settings/location/page.dart:203
msgid "為了獲得更好的 DPIP 體驗,請依照步驟關閉省電策略,以便讓 DPIP 在背景能正常接收資訊以及更新所在地。"
msgstr ""
-#: ./lib/app/settings/location/page.dart:233
+#: ./lib/app/settings/location/page.dart:237
msgid "一律允許"
msgstr ""
-#: ./lib/app/settings/location/page.dart:233
+#: ./lib/app/settings/location/page.dart:237
msgid "永遠"
msgstr ""
-#: ./lib/app/settings/location/page.dart:253
+#: ./lib/app/settings/location/page.dart:257
msgid "自動更新"
msgstr ""
-#: ./lib/app/settings/location/page.dart:254
+#: ./lib/app/settings/location/page.dart:258
msgid "定期更新目前的所在地"
msgstr ""
-#: ./lib/app/settings/location/page.dart:263
+#: ./lib/app/settings/location/page.dart:267
msgid ""
"自動定位功能將使用您的裝置上的 GPS,即使 DPIP "
"關閉或未在使用時,也會根據您的地理位置,自動更新您的所在地,提供即時的天氣和地震資訊,讓您隨時掌握當地最新狀況。"
msgstr ""
-#: ./lib/app/settings/location/page.dart:334
+#: ./lib/app/settings/location/page.dart:338
msgid "通知功能已被拒絕,請移至設定允許權限。"
msgstr ""
-#: ./lib/app/settings/location/page.dart:395
+#: ./lib/app/settings/location/page.dart:399
msgid "省電策略已被拒絕,請移至設定允許權限。"
msgstr ""
-#: ./lib/app/settings/location/select/[city]/page.dart:98
+#: ./lib/app/settings/location/select/[city]/page.dart:109
msgid "設定所在地時發生錯誤,請稍候再試一次。"
msgstr ""
-#: ./lib/app/home/_widgets/location_button.dart:233
+#: ./lib/app/home_old/_widgets/location_button.dart:204
msgid "新增地點"
msgstr ""
-#: ./lib/app/settings/location/select/page.dart:33
+#: ./lib/app/home/_widgets/location_chip.dart:148
msgid "縣市"
msgstr ""
-#: ./lib/app/settings/location/select/page.dart:44
+#: ./lib/app/settings/location/select/page.dart:49
msgid "目前所在地"
msgstr ""
-#: ./lib/app/settings/layout/page.dart:56
+#: ./lib/app/settings/layout/page.dart:90
+msgid "停用"
+msgstr ""
+
+#: ./lib/app/settings/layout/page.dart:192
msgid "拖曳調整順序"
msgstr ""
-#: ./lib/app/settings/layout/page.dart:100
+#: ./lib/app/settings/layout/page.dart:236
msgid "已停用"
msgstr ""
-#: ./lib/app/settings/layout/page.dart:135
+#: ./lib/app/settings/layout/page.dart:271
msgid "所有區塊皆已啟用"
msgstr ""
-#: ./lib/app/settings/layout/page.dart:202
-msgid "停用"
-msgstr ""
-
-#: ./lib/app/settings/map/page.dart:61
+#: ./lib/app/settings/map/page.dart:70
msgid "初始圖層"
msgstr ""
-#: ./lib/app/settings/map/page.dart:62
+#: ./lib/app/settings/map/page.dart:71
msgid "調整地圖的底圖以及初始顯示的圖層"
msgstr ""
-#: ./lib/app/settings/map/page.dart:74
+#: ./lib/app/settings/map/page.dart:83
msgid "自動縮放"
msgstr ""
-#: ./lib/app/settings/map/page.dart:75
+#: ./lib/app/settings/map/page.dart:84
msgid "接收到檢知時自動縮放地圖"
msgstr ""
-#: ./lib/app/settings/map/page.dart:101
+#: ./lib/app/settings/map/page.dart:110
msgid "動畫幀率"
msgstr ""
-#: ./lib/app/settings/map/page.dart:102
+#: ./lib/app/settings/map/page.dart:111
msgid "調整強震監視器震波模擬動畫的流暢度"
msgstr ""
-#: ./lib/app/settings/map/page.dart:136
+#: ./lib/app/settings/map/page.dart:144
msgid "過高的動畫幀率可能會造成卡頓或裝置發熱"
msgstr ""
-#: ./lib/app/settings/theme/page.dart:46
+#: ./lib/app/settings/theme/page.dart:53
msgid "主題模式"
msgstr ""
-#: ./lib/app/settings/theme/mode/page.dart:63
+#: ./lib/app/settings/theme/mode/page.dart:69
msgid "淺色"
msgstr ""
-#: ./lib/app/settings/theme/mode/page.dart:64
+#: ./lib/app/settings/theme/mode/page.dart:70
msgid "深色"
msgstr ""
-#: ./lib/app/settings/theme/mode/page.dart:59
+#: ./lib/app/settings/theme/mode/page.dart:67
msgid "跟隨系統主題"
msgstr ""
-#: ./lib/app/settings/theme/color/page.dart:22
+#: ./lib/app/settings/theme/color/page.dart:30
msgid "主題色彩"
msgstr ""
-#: ./lib/app/settings/theme/color/page.dart:43
+#: ./lib/app/settings/theme/color/page.dart:51
msgid "使用系統配色"
msgstr ""
-#: ./lib/app/settings/theme/color/page.dart:62
+#: ./lib/app/settings/theme/color/page.dart:70
msgid "自訂"
msgstr ""
-#: ./lib/app/settings/theme/color/page.dart:72
+#: ./lib/app/settings/theme/color/page.dart:79
msgid "自訂色彩"
msgstr ""
-#: ./lib/app/home/_widgets/thunderstorm_card.dart:84
-msgid "您所在區域附近有劇烈雷雨或降雨發生,請注意防範,持續至 {time} 。"
+#: ./lib/app/map/_lib/managers/radar.dart:614
+msgid "雷達回波"
msgstr ""
-#: ./lib/app/home/_widgets/forecast_card.dart:59
-msgid "天氣預報(24h)"
+#: ./lib/app/home/_widgets/weather_parameters.dart:52
+msgid "相對溼度"
msgstr ""
-#: ./lib/app/home/_widgets/location_out_of_service.dart:28
-msgid "服務區域外,僅在臺灣各地可用"
+#: ./lib/app/home/_widgets/weather_parameters.dart:57
+msgid "空氣品質"
msgstr ""
-#: ./lib/app/map/_lib/managers/radar.dart:587
-msgid "雷達回波"
+#: ./lib/app/map/_lib/managers/wind.dart:343
+msgid "風向/風速"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:497
-msgid "無資料"
+#: ./lib/app/home/_widgets/weather_parameters.dart:68
+msgid "降水量"
msgstr ""
-#: ./lib/app/home/_widgets/wind_card.dart:372
-msgid "{wind}級 {Desc}"
+#: ./lib/app/home/_widgets/location_chip.dart:84
+msgid "清除暫時位置"
msgstr ""
-#: ./lib/app/home/_widgets/wind_card.dart:389
-msgid "陣風 {speed} m/s"
+#: ./lib/app/home/_widgets/location_chip.dart:159
+msgid "目前選擇地區"
msgstr ""
-#: ./lib/app/home/_widgets/wind_card.dart:471
-msgid "無法測量"
+#: ./lib/app/home_old/_widgets/location_button.dart:170
+msgid "快速切換"
msgstr ""
-#: ./lib/app/home/_widgets/wind_card.dart:492
-msgid "指北針不可靠"
+#: ./lib/app/home/layout.dart:88
+msgid "首頁"
msgstr ""
-#: ./lib/app/home/_widgets/wind_card.dart:494
-msgid "指北針準確度下降"
+#: ./lib/app/home/layout.dart:98
+msgid "小工具"
msgstr ""
-#: ./lib/app/home/_widgets/wind_card.dart:495
-msgid "指北針正常"
+#: ./lib/app/changelog/page.dart:86
+msgid "發生錯誤"
msgstr ""
-#: ./lib/app/home/_widgets/wind_card.dart:502
-msgid "方向精確度"
+#: ./lib/route/image_viewer/image_viewer.dart:85
+msgid "再試一次"
msgstr ""
-#: ./lib/app/home/_widgets/wind_card.dart:521
-msgid "正常範圍:±0-15°"
+#: ./lib/app/changelog/page.dart:217
+msgid "目前版本"
msgstr ""
-#: ./lib/app/home/_widgets/wind_card.dart:538
-msgid "附近有強磁場干擾,指北針方向可能完全不準確。請遠離磁鐵、電子裝置或金屬物品。"
+#: ./lib/app/welcome/4-permissions/page.dart:383
+msgid "下一步"
msgstr ""
-#: ./lib/app/home/_widgets/wind_card.dart:539
-msgid "附近可能有磁場干擾,指北針方向可能有偏差。"
+#: ./lib/app/welcome/1-about/page.dart:73
+msgid "防災資訊平台"
msgstr ""
-#: ./lib/route/image_viewer/image_viewer.dart:145
-msgid "確定"
+#: ./lib/app/welcome/2-exptech/page.dart:98
+msgid "我們是誰?"
msgstr ""
-#: ./lib/app/home/_widgets/location_button.dart:29
-msgid "尚未設定"
+#: ./lib/app/welcome/2-exptech/page.dart:105
+msgid "ExpTech Studio 是一群大部分由學生組成,平均年齡未滿 20 歲、人數超過 15 + 的團體。成員來自臺灣北中南、日本、韓國、中國的學生。"
msgstr ""
-#: ./lib/app/home/_widgets/location_button.dart:138
-msgid "切換區域"
+#: ./lib/app/welcome/2-exptech/page.dart:111
+msgid "我們的初衷"
msgstr ""
-#: ./lib/app/home/_widgets/location_button.dart:144
-msgid "位置設定"
+#: ./lib/app/welcome/2-exptech/page.dart:118
+msgid "成立初衷是招募一群對電腦及科技有興趣及能力的同學,後來發展至校外,並逐漸形成現在的樣子。"
msgstr ""
-#: ./lib/app/home/_widgets/location_button.dart:195
-msgid "快速切換"
+#: ./lib/app/welcome/3-notice/page.dart:50
+msgid "注意事項"
msgstr ""
-#: ./lib/app/home/_widgets/location_button.dart:240
-msgid "選擇縣市"
+#: ./lib/app/welcome/3-notice/page.dart:70
+msgid "任何資訊應以中央氣象署發布之內容為準。"
msgstr ""
-#: ./lib/app/home/_widgets/location_button.dart:250
-msgid "目前選擇"
+#: ./lib/app/welcome/3-notice/page.dart:87
+msgid "根據網路狀態、伺服器狀態、應用程式狀態、上游資料來源狀態等,有收不到資訊的可能性,我們會盡力避免此類情況,但不保證一定不會發生。"
msgstr ""
-#: ./lib/app/home/page.dart:888
-msgid "濕度"
+#: ./lib/app/welcome/3-notice/page.dart:101
+msgid "強烈搖晃有機率比通知早抵達使用者所在地。"
msgstr ""
-#: ./lib/app/home/page.dart:916
-msgid "風速"
+#: ./lib/app/welcome/3-notice/page.dart:115
+msgid "地震速報為快速計算之結果,可能存在較大誤差,應理解並謹慎使用。"
msgstr ""
-#: ./lib/app/home/_widgets/weather_header.dart:181
-msgid "風向"
+#: ./lib/app/welcome/3-notice/page.dart:129
+msgid "任何不被官方所認可的行為均有可能承擔法律風險,請務必遵守相關規範。"
msgstr ""
-#: ./lib/app/home/_widgets/weather_header.dart:190
-msgid "風級"
+#: ./lib/app/welcome/1-about/page.dart:51
+msgid "歡迎使用 DPIP"
msgstr ""
-#: ./lib/app/home/page.dart:895
-msgid "氣壓"
+#: ./lib/app/welcome/1-about/page.dart:96
+msgid ""
+"DPIP 是一款由臺灣本土團隊設計的 App,整合 TREM-Net (臺灣即時地震觀測網) "
+"之資訊,以及中央氣象署資料,提供一個整合、單一且便利的防災資訊應用程式。"
msgstr ""
-#: ./lib/app/home/page.dart:902
-msgid "降雨"
+#: ./lib/app/welcome/4-permissions/page.dart:112
+msgid "在重大災害發生時以通知來傳遞即時防災資訊"
msgstr ""
-#: ./lib/app/home/page.dart:909
-msgid "能見度"
+#: ./lib/app/welcome/4-permissions/page.dart:120
+msgid "使用定位來自動更新所在地設定,提供當地的即時防災資訊"
msgstr ""
-#: ./lib/app/home/page.dart:923
-msgid "陣風"
+#: ./lib/app/welcome/4-permissions/page.dart:126
+msgid "允許 DPIP 在背景中持續運行,以便即時防災通知資訊。"
msgstr ""
-#: ./lib/app/home/_widgets/weather_header.dart:233
-msgid "陣風級"
+#: ./lib/route/image_viewer/image_viewer.dart:254
+msgid "儲存"
msgstr ""
-#: ./lib/app/home/_widgets/weather_header.dart:241
-msgid "日照"
+#: ./lib/app/welcome/4-permissions/page.dart:133
+msgid "用於儲存中央氣象署或 ExpTech 提供之資料視覺化圖片"
msgstr ""
-#: ./lib/app/home/_widgets/hero_weather.dart:142
-msgid "體感 {feelsLike}°"
+#: ./lib/app/welcome/4-permissions/page.dart:293
+msgid "權限請求"
msgstr ""
-#: ./lib/app/home/_widgets/hero_weather.dart:185
-msgid "無天氣資料"
+#: ./lib/app/welcome/4-permissions/page.dart:294
+msgid "需要使用者手動到設定開啟相關權限。"
msgstr ""
-#: ./lib/app/home/_widgets/mode_toggle_button.dart:14
-msgid "全國 · 生效中"
+#: ./lib/route/image_viewer/image_viewer.dart:145
+msgid "確定"
msgstr ""
-#: ./lib/app/home/_widgets/mode_toggle_button.dart:16
-msgid "全國 · 歷史"
+#: ./lib/app/welcome/4-permissions/page.dart:317
+msgid "需要背景位置權限"
msgstr ""
-#: ./lib/app/home/_widgets/mode_toggle_button.dart:18
-msgid "所在地 · 生效中"
+#: ./lib/app/welcome/4-permissions/page.dart:319
+msgid ""
+"為了在背景持續提供即時防災資訊,DPIP 需要「永遠允許」位置權限。\n"
+"\n"
+"接下來系統會引導您到設定頁面,請選擇「永遠允許」選項。"
msgstr ""
-#: ./lib/app/home/_widgets/mode_toggle_button.dart:20
-msgid "所在地 · 歷史"
+#: ./lib/app/welcome/4-permissions/page.dart:325
+msgid "稍後"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:1258
-msgid "EEW"
+#: ./lib/app/welcome/4-permissions/page.dart:329
+msgid "前往設定"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:1397
-msgid "第 {serial} 報"
+#: ./lib/app/welcome/4-permissions/page.dart:406
+msgid "權限"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:1415
-msgid ""
-"{time} 左右,{location}附近發生有感地震,預估規模 "
-"M{magnitude}、所在地最大震度{intensity}。"
+#: ./lib/app/welcome/4-permissions/page.dart:419
+msgid "我們一直和使用者站在一起,為使用者的隱私而不斷努力。"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:1423
-msgid ""
-"{time} 左右,{location}附近發生有感地震,預估規模 "
-"M{magnitude}、深度{depth}公里。"
+#: ./lib/app/home_old/_widgets/thunderstorm_card.dart:93
+msgid "您所在區域附近有劇烈雷雨或降雨發生,請注意防範,持續至 {time} 。"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:1469
-msgid "所在地預估"
+#: ./lib/app/home_old/_widgets/forecast_card.dart:68
+msgid "天氣預報(24h)"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:1505
-msgid "震波"
+#: ./lib/app/home_old/_widgets/location_out_of_service.dart:34
+msgid "服務區域外,僅在臺灣各地可用"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:1527
-msgid " 秒"
+#: ./lib/app/map/_lib/managers/monitor.dart:518
+msgid "無資料"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:1544
-msgid "抵達"
+#: ./lib/app/home_old/_widgets/wind_card.dart:329
+msgid "{wind}級 {Desc}"
msgstr ""
-#: ./lib/app/home/page.dart:195
-msgid "已更新至 {version}"
+#: ./lib/app/home_old/_widgets/wind_card.dart:346
+msgid "陣風 {speed} m/s"
msgstr ""
-#: ./lib/utils/weather_icon.dart:284
-msgid "取得天氣異常"
+#: ./lib/app/home_old/_widgets/wind_card.dart:408
+msgid "無法測量"
msgstr ""
-#: ./lib/app/home/page.dart:366
-msgid "取得歷史資訊異常"
+#: ./lib/app/home_old/_widgets/wind_card.dart:429
+msgid "指北針不可靠"
msgstr ""
-#: ./lib/app/home/page.dart:777
-msgid "上午"
+#: ./lib/app/home_old/_widgets/wind_card.dart:431
+msgid "指北針準確度下降"
msgstr ""
-#: ./lib/app/home/page.dart:777
-msgid "下午"
+#: ./lib/app/home_old/_widgets/wind_card.dart:432
+msgid "指北針正常"
msgstr ""
-#: ./lib/app/changelog/page.dart:76
-msgid "發生錯誤"
+#: ./lib/app/home_old/_widgets/wind_card.dart:439
+msgid "方向精確度"
msgstr ""
-#: ./lib/route/image_viewer/image_viewer.dart:85
-msgid "再試一次"
+#: ./lib/app/home_old/_widgets/wind_card.dart:458
+msgid "正常範圍:±0-15°"
msgstr ""
-#: ./lib/app/changelog/page.dart:203
-msgid "目前版本"
+#: ./lib/app/home_old/_widgets/wind_card.dart:475
+msgid "附近有強磁場干擾,指北針方向可能完全不準確。請遠離磁鐵、電子裝置或金屬物品。"
msgstr ""
-#: ./lib/app/welcome/4-permissions/page.dart:403
-msgid "下一步"
+#: ./lib/app/home_old/_widgets/wind_card.dart:476
+msgid "附近可能有磁場干擾,指北針方向可能有偏差。"
msgstr ""
-#: ./lib/app/welcome/1-about/page.dart:68
-msgid "防災資訊平台"
+#: ./lib/app/home_old/_widgets/location_button.dart:38
+msgid "尚未設定"
msgstr ""
-#: ./lib/app/welcome/2-exptech/page.dart:93
-msgid "我們是誰?"
+#: ./lib/app/home_old/_widgets/location_button.dart:211
+msgid "選擇縣市"
msgstr ""
-#: ./lib/app/welcome/2-exptech/page.dart:100
-msgid "ExpTech Studio 是一群大部分由學生組成,平均年齡未滿 20 歲、人數超過 15 + 的團體。成員來自臺灣北中南、日本、韓國、中國的學生。"
+#: ./lib/app/home_old/_widgets/location_button.dart:221
+msgid "目前選擇"
msgstr ""
-#: ./lib/app/welcome/2-exptech/page.dart:106
-msgid "我們的初衷"
+#: ./lib/app/home_old/_widgets/location_button.dart:279
+msgid "切換區域"
msgstr ""
-#: ./lib/app/welcome/2-exptech/page.dart:113
-msgid "成立初衷是招募一群對電腦及科技有興趣及能力的同學,後來發展至校外,並逐漸形成現在的樣子。"
+#: ./lib/app/home_old/_widgets/location_button.dart:285
+msgid "位置設定"
msgstr ""
-#: ./lib/app/welcome/3-notice/page.dart:45
-msgid "注意事項"
+#: ./lib/app/home_old/page.dart:630
+msgid "濕度"
msgstr ""
-#: ./lib/app/welcome/3-notice/page.dart:65
-msgid "任何資訊應以中央氣象署發布之內容為準。"
+#: ./lib/app/home_old/page.dart:658
+msgid "風速"
msgstr ""
-#: ./lib/app/welcome/3-notice/page.dart:82
-msgid "根據網路狀態、伺服器狀態、應用程式狀態、上游資料來源狀態等,有收不到資訊的可能性,我們會盡力避免此類情況,但不保證一定不會發生。"
+#: ./lib/app/home_old/_widgets/weather_header.dart:199
+msgid "風向"
msgstr ""
-#: ./lib/app/welcome/3-notice/page.dart:97
-msgid "強烈搖晃有機率比通知早抵達使用者所在地。"
+#: ./lib/app/home_old/_widgets/weather_header.dart:206
+msgid "風級"
msgstr ""
-#: ./lib/app/welcome/3-notice/page.dart:111
-msgid "地震速報為快速計算之結果,可能存在較大誤差,應理解並謹慎使用。"
+#: ./lib/app/home_old/page.dart:637
+msgid "氣壓"
msgstr ""
-#: ./lib/app/welcome/3-notice/page.dart:125
-msgid "任何不被官方所認可的行為均有可能承擔法律風險,請務必遵守相關規範。"
+#: ./lib/app/home_old/page.dart:644
+msgid "降雨"
msgstr ""
-#: ./lib/app/welcome/1-about/page.dart:46
-msgid "歡迎使用 DPIP"
+#: ./lib/app/home_old/page.dart:651
+msgid "能見度"
msgstr ""
-#: ./lib/app/welcome/1-about/page.dart:91
-msgid ""
-"DPIP 是一款由臺灣本土團隊設計的 App,整合 TREM-Net (臺灣即時地震觀測網) "
-"之資訊,以及中央氣象署資料,提供一個整合、單一且便利的防災資訊應用程式。"
+#: ./lib/app/home_old/page.dart:665
+msgid "陣風"
msgstr ""
-#: ./lib/app/welcome/4-permissions/page.dart:167
-msgid "在重大災害發生時以通知來傳遞即時防災資訊"
+#: ./lib/app/home_old/_widgets/weather_header.dart:243
+msgid "陣風級"
msgstr ""
-#: ./lib/app/welcome/4-permissions/page.dart:175
-msgid "使用定位來自動更新所在地設定,提供當地的即時防災資訊"
+#: ./lib/app/home_old/_widgets/weather_header.dart:251
+msgid "日照"
msgstr ""
-#: ./lib/app/welcome/4-permissions/page.dart:181
-msgid "允許 DPIP 在背景中持續運行,以便即時防災通知資訊。"
+#: ./lib/app/home_old/_widgets/hero_weather.dart:151
+msgid "體感 {feelsLike}°"
msgstr ""
-#: ./lib/route/image_viewer/image_viewer.dart:255
-msgid "儲存"
+#: ./lib/app/home_old/_widgets/hero_weather.dart:194
+msgid "無天氣資料"
msgstr ""
-#: ./lib/app/welcome/4-permissions/page.dart:188
-msgid "用於儲存中央氣象署或 ExpTech 提供之資料視覺化圖片"
+#: ./lib/app/home_old/_widgets/mode_toggle_button.dart:32
+msgid "全國 · 生效中"
msgstr ""
-#: ./lib/app/welcome/4-permissions/page.dart:352
-msgid "權限請求"
+#: ./lib/app/home_old/_widgets/mode_toggle_button.dart:34
+msgid "全國 · 歷史"
msgstr ""
-#: ./lib/app/welcome/4-permissions/page.dart:353
-msgid "需要使用者手動到設定開啟相關權限。"
+#: ./lib/app/home_old/_widgets/mode_toggle_button.dart:36
+msgid "所在地 · 生效中"
msgstr ""
-#: ./lib/app/welcome/4-permissions/page.dart:376
-msgid "需要背景位置權限"
+#: ./lib/app/home_old/_widgets/mode_toggle_button.dart:38
+msgid "所在地 · 歷史"
msgstr ""
-#: ./lib/app/welcome/4-permissions/page.dart:378
+#: ./lib/app/map/_lib/managers/monitor.dart:1274
+msgid "EEW"
+msgstr ""
+
+#: ./lib/app/map/_lib/managers/monitor.dart:1407
+msgid "第 {serial} 報"
+msgstr ""
+
+#: ./lib/app/map/_lib/managers/monitor.dart:1425
msgid ""
-"為了在背景持續提供即時防災資訊,DPIP 需要「永遠允許」位置權限。\n"
-"\n"
-"接下來系統會引導您到設定頁面,請選擇「永遠允許」選項。"
+"{time} 左右,{location}附近發生有感地震,預估規模 "
+"M{magnitude}、所在地最大震度{intensity}。"
msgstr ""
-#: ./lib/app/welcome/4-permissions/page.dart:384
-msgid "稍後"
+#: ./lib/app/map/_lib/managers/monitor.dart:1433
+msgid ""
+"{time} 左右,{location}附近發生有感地震,預估規模 "
+"M{magnitude}、深度{depth}公里。"
msgstr ""
-#: ./lib/app/welcome/4-permissions/page.dart:388
-msgid "前往設定"
+#: ./lib/app/map/_lib/managers/monitor.dart:1479
+msgid "所在地預估"
msgstr ""
-#: ./lib/app/welcome/4-permissions/page.dart:426
-msgid "權限"
+#: ./lib/app/map/_lib/managers/monitor.dart:1515
+msgid "震波"
msgstr ""
-#: ./lib/app/welcome/4-permissions/page.dart:439
-msgid "我們一直和使用者站在一起,為使用者的隱私而不斷努力。"
+#: ./lib/app/map/_lib/managers/monitor.dart:1535
+msgid " 秒"
+msgstr ""
+
+#: ./lib/app/map/_lib/managers/monitor.dart:1551
+msgid "抵達"
msgstr ""
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:84
+#: ./lib/app/home_old/page.dart:158
+msgid "已更新至 {version}"
+msgstr ""
+
+#: ./lib/utils/weather_icon.dart:282
+msgid "取得天氣異常"
+msgstr ""
+
+#: ./lib/app/home_old/page.dart:331
+msgid "取得歷史資訊異常"
+msgstr ""
+
+#: ./lib/app/home_old/page.dart:571
+msgid "上午"
+msgstr ""
+
+#: ./lib/app/home_old/page.dart:571
+msgid "下午"
+msgstr ""
+
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:96
msgid "地圖圖層"
msgstr ""
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:85
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:97
msgid "選擇要顯示的地圖圖層"
msgstr ""
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:91
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:103
msgid "底圖"
msgstr ""
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:97
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:109
msgid "簡單"
msgstr ""
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:119
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:131
msgid "監視器"
msgstr ""
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:124
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:136
msgid "報告"
msgstr ""
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:135
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:147
msgid "氣象"
msgstr ""
-#: ./lib/app/map/_lib/managers/temperature.dart:453
+#: ./lib/app/map/_lib/managers/temperature.dart:480
msgid "氣溫"
msgstr ""
-#: ./lib/app/map/_lib/managers/precipitation.dart:577
+#: ./lib/app/map/_lib/managers/precipitation.dart:587
msgid "降水"
msgstr ""
-#: ./lib/app/map/_lib/managers/wind.dart:320
-msgid "風向/風速"
-msgstr ""
-
-#: ./lib/app/map/_lib/managers/lightning.dart:294
+#: ./lib/app/map/_lib/managers/lightning.dart:302
msgid "閃電"
msgstr ""
-#: ./lib/app/map/_widgets/map_legend.dart:202
+#: ./lib/app/map/_widgets/map_legend.dart:250
msgid "單位:{unit}"
msgstr ""
-#: ./lib/app/map/_lib/managers/tsunami.dart:485
+#: ./lib/app/map/_lib/managers/tsunami.dart:494
msgid "近期無海嘯資訊"
msgstr ""
-#: ./lib/app/map/_lib/managers/tsunami.dart:486
+#: ./lib/app/map/_lib/managers/tsunami.dart:494
msgid "海嘯警報"
msgstr ""
-#: ./lib/app/map/_lib/managers/tsunami.dart:496
+#: ./lib/app/map/_lib/managers/tsunami.dart:504
msgid "{id}號 第{serial}報"
msgstr ""
-#: ./lib/app/map/_lib/managers/tsunami.dart:551
+#: ./lib/app/map/_lib/managers/tsunami.dart:558
msgid "發布"
msgstr ""
-#: ./lib/app/map/_lib/managers/tsunami.dart:553
+#: ./lib/app/map/_lib/managers/tsunami.dart:560
msgid "更新"
msgstr ""
-#: ./lib/app/map/_lib/managers/tsunami.dart:554
+#: ./lib/app/map/_lib/managers/tsunami.dart:561
msgid "解除"
msgstr ""
-#: ./lib/app/map/_lib/managers/tsunami.dart:607
+#: ./lib/app/map/_lib/managers/tsunami.dart:612
msgid "預估海嘯到達時間及波高"
msgstr ""
-#: ./lib/app/map/_lib/managers/tsunami.dart:626
+#: ./lib/app/map/_lib/managers/tsunami.dart:630
msgid "各地觀測到的海嘯"
msgstr ""
-#: ./lib/app/map/_lib/managers/tsunami.dart:641
+#: ./lib/app/map/_lib/managers/tsunami.dart:645
msgid "地震資訊"
msgstr ""
-#: ./lib/app/map/_lib/managers/tsunami.dart:654
+#: ./lib/app/map/_lib/managers/tsunami.dart:657
msgid "發生時間"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:1072
+#: ./lib/app/map/_lib/managers/report.dart:1069
msgid "位於"
msgstr ""
-#: ./lib/app/map/_lib/managers/tsunami.dart:736
+#: ./lib/app/map/_lib/managers/tsunami.dart:730
msgid "規模"
msgstr ""
-#: ./lib/app/map/_lib/managers/tsunami.dart:765
+#: ./lib/app/map/_lib/managers/tsunami.dart:753
msgid "深度"
msgstr ""
-#: ./lib/app/map/_lib/managers/radar.dart:744
+#: ./lib/app/map/_lib/managers/radar.dart:750
msgid "長按設定播放起點"
msgstr ""
-#: ./lib/app/map/_lib/managers/radar.dart:760
+#: ./lib/app/map/_lib/managers/radar.dart:766
msgid "目前時間"
msgstr ""
-#: ./lib/app/map/_lib/managers/radar.dart:765
+#: ./lib/app/map/_lib/managers/radar.dart:771
msgid "播放起點"
msgstr ""
-#: ./lib/app/map/_lib/managers/radar.dart:1099
+#: ./lib/app/map/_lib/managers/radar.dart:1100
msgid "播放進度"
msgstr ""
-#: ./lib/app/map/_lib/managers/lightning.dart:393
+#: ./lib/app/map/_lib/managers/lightning.dart:399
msgid "5 分鐘內對地閃電"
msgstr ""
-#: ./lib/app/map/_lib/managers/lightning.dart:401
+#: ./lib/app/map/_lib/managers/lightning.dart:407
msgid "10 分鐘內對地閃電"
msgstr ""
-#: ./lib/app/map/_lib/managers/lightning.dart:409
+#: ./lib/app/map/_lib/managers/lightning.dart:415
msgid "30 分鐘內對地閃電"
msgstr ""
-#: ./lib/app/map/_lib/managers/lightning.dart:417
+#: ./lib/app/map/_lib/managers/lightning.dart:423
msgid "60 分鐘內對地閃電"
msgstr ""
-#: ./lib/app/map/_lib/managers/lightning.dart:425
+#: ./lib/app/map/_lib/managers/lightning.dart:431
msgid "5 分鐘內雲間閃電"
msgstr ""
-#: ./lib/app/map/_lib/managers/lightning.dart:433
+#: ./lib/app/map/_lib/managers/lightning.dart:439
msgid "10 分鐘內雲間閃電"
msgstr ""
-#: ./lib/app/map/_lib/managers/lightning.dart:441
+#: ./lib/app/map/_lib/managers/lightning.dart:447
msgid "30 分鐘內雲間閃電"
msgstr ""
-#: ./lib/app/map/_lib/managers/lightning.dart:449
+#: ./lib/app/map/_lib/managers/lightning.dart:455
msgid "60 分鐘內雲間閃電"
msgstr ""
-#: ./lib/app/map/_lib/managers/precipitation.dart:396
+#: ./lib/app/map/_lib/managers/precipitation.dart:420
msgid "今日"
msgstr ""
-#: ./lib/app/map/_lib/managers/precipitation.dart:397
+#: ./lib/app/map/_lib/managers/precipitation.dart:421
msgid "10 分鐘"
msgstr ""
-#: ./lib/app/map/_lib/managers/precipitation.dart:398
+#: ./lib/app/map/_lib/managers/precipitation.dart:422
msgid "1 小時"
msgstr ""
-#: ./lib/app/map/_lib/managers/precipitation.dart:399
+#: ./lib/app/map/_lib/managers/precipitation.dart:423
msgid "3 小時"
msgstr ""
-#: ./lib/app/map/_lib/managers/precipitation.dart:400
+#: ./lib/app/map/_lib/managers/precipitation.dart:424
msgid "6 小時"
msgstr ""
-#: ./lib/app/map/_lib/managers/precipitation.dart:401
+#: ./lib/app/map/_lib/managers/precipitation.dart:425
msgid "12 小時"
msgstr ""
-#: ./lib/app/map/_lib/managers/precipitation.dart:402
+#: ./lib/app/map/_lib/managers/precipitation.dart:426
msgid "24 小時"
msgstr ""
-#: ./lib/app/map/_lib/managers/precipitation.dart:403
+#: ./lib/app/map/_lib/managers/precipitation.dart:427
msgid "2 天"
msgstr ""
-#: ./lib/app/map/_lib/managers/precipitation.dart:404
+#: ./lib/app/map/_lib/managers/precipitation.dart:428
msgid "3 天"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:474
+#: ./lib/app/map/_lib/managers/monitor.dart:495
msgid "海外測站"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:494
+#: ./lib/app/map/_lib/managers/monitor.dart:515
msgid "即時震度:"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:517
+#: ./lib/app/map/_lib/managers/monitor.dart:538
msgid "地動加速度:"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:541
+#: ./lib/app/map/_lib/managers/monitor.dart:562
msgid "地動速度:"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:1331
+#: ./lib/app/map/_lib/managers/monitor.dart:1346
msgid "規模 M{magnitude},所在地預估{intensity}"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:1349
+#: ./lib/app/map/_lib/managers/monitor.dart:1362
msgid "{countdown}秒後抵達"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:1352
+#: ./lib/app/map/_lib/managers/monitor.dart:1365
msgid "已抵達"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:1364
+#: ./lib/app/map/_lib/managers/monitor.dart:1376
msgid "規模 M{magnitude},深度{depth}公里"
msgstr ""
-#: ./lib/app/map/_lib/managers/monitor.dart:1591
+#: ./lib/app/map/_lib/managers/monitor.dart:1594
msgid "目前沒有生效中的地震速報"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:516
+#: ./lib/app/map/_lib/managers/report.dart:541
msgid "CWA 正在製圖中"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:639
+#: ./lib/app/map/_lib/managers/report.dart:660
msgid "近期的地震報告"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:647
+#: ./lib/app/map/_lib/managers/report.dart:667
msgid "更多"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:995
+#: ./lib/app/map/_lib/managers/report.dart:994
msgid "編號 {number} 顯著有感地震"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:998
+#: ./lib/app/map/_lib/managers/report.dart:997
msgid "小區域有感地震"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:1083
+#: ./lib/app/map/_lib/managers/report.dart:1080
msgid "地震規模"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:1110
+#: ./lib/app/map/_lib/managers/report.dart:1107
msgid "震源深度"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:886
+#: ./lib/app/map/_lib/managers/report.dart:893
msgid "沒有更多資料"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:1030
+#: ./lib/app/map/_lib/managers/report.dart:1029
msgid "報告頁面"
msgstr ""
-#: ./lib/route/report/report_sheet_content.dart:90
+#: ./lib/route/report/report_sheet_content.dart:88
msgid "重播"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:1064
+#: ./lib/app/map/_lib/managers/report.dart:1061
msgid "發震時間"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:1132
+#: ./lib/app/map/_lib/managers/report.dart:1129
msgid "各地震度"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:1212
+#: ./lib/app/map/_lib/managers/report.dart:1205
msgid "地震報告圖"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:1228
+#: ./lib/app/map/_lib/managers/report.dart:1220
msgid "震度圖"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:1248
+#: ./lib/app/map/_lib/managers/report.dart:1240
msgid "最大地動加速度圖"
msgstr ""
-#: ./lib/app/map/_lib/managers/report.dart:1268
+#: ./lib/app/map/_lib/managers/report.dart:1260
msgid "最大地動速度圖"
msgstr ""
@@ -1443,11 +1471,11 @@ msgstr ""
msgid "未知"
msgstr ""
-#: ./lib/route/announcement/announcement.dart:105
+#: ./lib/route/announcement/announcement.dart:104
msgid "目前沒有公告"
msgstr ""
-#: ./lib/route/announcement/announcement.dart:246
+#: ./lib/route/announcement/announcement.dart:245
msgid "公告詳情"
msgstr ""
@@ -1463,302 +1491,302 @@ msgstr ""
msgid "儲存圖片時發生錯誤"
msgstr ""
-#: ./lib/utils/extensions/number.dart:26
+#: ./lib/utils/extensions/number.dart:25
msgid "0級"
msgstr ""
-#: ./lib/utils/extensions/number.dart:27
+#: ./lib/utils/extensions/number.dart:26
msgid "1級"
msgstr ""
-#: ./lib/utils/extensions/number.dart:28
+#: ./lib/utils/extensions/number.dart:27
msgid "2級"
msgstr ""
-#: ./lib/utils/extensions/number.dart:29
+#: ./lib/utils/extensions/number.dart:28
msgid "3級"
msgstr ""
-#: ./lib/utils/extensions/number.dart:30
+#: ./lib/utils/extensions/number.dart:29
msgid "4級"
msgstr ""
-#: ./lib/utils/extensions/number.dart:31
+#: ./lib/utils/extensions/number.dart:30
msgid "5弱"
msgstr ""
-#: ./lib/utils/extensions/number.dart:32
+#: ./lib/utils/extensions/number.dart:31
msgid "5強"
msgstr ""
-#: ./lib/utils/extensions/number.dart:33
+#: ./lib/utils/extensions/number.dart:32
msgid "6弱"
msgstr ""
-#: ./lib/utils/extensions/number.dart:34
+#: ./lib/utils/extensions/number.dart:33
msgid "6強"
msgstr ""
-#: ./lib/utils/extensions/number.dart:35
+#: ./lib/utils/extensions/number.dart:34
msgid "7級"
msgstr ""
-#: ./lib/utils/weather_icon.dart:285
+#: ./lib/utils/weather_icon.dart:283
msgid "晴"
msgstr ""
-#: ./lib/utils/weather_icon.dart:286
+#: ./lib/utils/weather_icon.dart:284
msgid "晴有霾"
msgstr ""
-#: ./lib/utils/weather_icon.dart:287
+#: ./lib/utils/weather_icon.dart:285
msgid "晴有靄"
msgstr ""
-#: ./lib/utils/weather_icon.dart:288
+#: ./lib/utils/weather_icon.dart:286
msgid "晴有閃電"
msgstr ""
-#: ./lib/utils/weather_icon.dart:304
+#: ./lib/utils/weather_icon.dart:302
msgid "晴天伴有雷"
msgstr ""
-#: ./lib/utils/weather_icon.dart:290
+#: ./lib/utils/weather_icon.dart:288
msgid "晴有霧"
msgstr ""
-#: ./lib/utils/weather_icon.dart:291
+#: ./lib/utils/weather_icon.dart:289
msgid "晴有雨"
msgstr ""
-#: ./lib/utils/weather_icon.dart:292
+#: ./lib/utils/weather_icon.dart:290
msgid "晴有雨雪"
msgstr ""
-#: ./lib/utils/weather_icon.dart:293
+#: ./lib/utils/weather_icon.dart:291
msgid "晴有大雪"
msgstr ""
-#: ./lib/utils/weather_icon.dart:294
+#: ./lib/utils/weather_icon.dart:292
msgid "晴有雪珠"
msgstr ""
-#: ./lib/utils/weather_icon.dart:295
+#: ./lib/utils/weather_icon.dart:293
msgid "晴有冰珠"
msgstr ""
-#: ./lib/utils/weather_icon.dart:296
+#: ./lib/utils/weather_icon.dart:294
msgid "晴有陣雪"
msgstr ""
-#: ./lib/utils/weather_icon.dart:297
+#: ./lib/utils/weather_icon.dart:295
msgid "晴陣雨雪"
msgstr ""
-#: ./lib/utils/weather_icon.dart:298
+#: ./lib/utils/weather_icon.dart:296
msgid "晴有雹"
msgstr ""
-#: ./lib/utils/weather_icon.dart:299
+#: ./lib/utils/weather_icon.dart:297
msgid "晴有雷雨"
msgstr ""
-#: ./lib/utils/weather_icon.dart:300
+#: ./lib/utils/weather_icon.dart:298
msgid "晴有雷雪"
msgstr ""
-#: ./lib/utils/weather_icon.dart:301
+#: ./lib/utils/weather_icon.dart:299
msgid "晴有雷雹"
msgstr ""
-#: ./lib/utils/weather_icon.dart:302
+#: ./lib/utils/weather_icon.dart:300
msgid "晴大雷雨"
msgstr ""
-#: ./lib/utils/weather_icon.dart:303
+#: ./lib/utils/weather_icon.dart:301
msgid "晴大雷雹"
msgstr ""
-#: ./lib/utils/weather_icon.dart:305
+#: ./lib/utils/weather_icon.dart:303
msgid "多雲"
msgstr ""
-#: ./lib/utils/weather_icon.dart:306
+#: ./lib/utils/weather_icon.dart:304
msgid "多雲有霾"
msgstr ""
-#: ./lib/utils/weather_icon.dart:307
+#: ./lib/utils/weather_icon.dart:305
msgid "多雲有靄"
msgstr ""
-#: ./lib/utils/weather_icon.dart:308
+#: ./lib/utils/weather_icon.dart:306
msgid "多雲有閃電"
msgstr ""
-#: ./lib/utils/weather_icon.dart:324
+#: ./lib/utils/weather_icon.dart:322
msgid "多雲伴有雷"
msgstr ""
-#: ./lib/utils/weather_icon.dart:310
+#: ./lib/utils/weather_icon.dart:308
msgid "多雲有霧"
msgstr ""
-#: ./lib/utils/weather_icon.dart:311
+#: ./lib/utils/weather_icon.dart:309
msgid "多雲有雨"
msgstr ""
-#: ./lib/utils/weather_icon.dart:312
+#: ./lib/utils/weather_icon.dart:310
msgid "多雲有雨雪"
msgstr ""
-#: ./lib/utils/weather_icon.dart:313
+#: ./lib/utils/weather_icon.dart:311
msgid "多雲有大雪"
msgstr ""
-#: ./lib/utils/weather_icon.dart:314
+#: ./lib/utils/weather_icon.dart:312
msgid "多雲有雪珠"
msgstr ""
-#: ./lib/utils/weather_icon.dart:315
+#: ./lib/utils/weather_icon.dart:313
msgid "多雲有冰珠"
msgstr ""
-#: ./lib/utils/weather_icon.dart:316
+#: ./lib/utils/weather_icon.dart:314
msgid "多雲有陣雪"
msgstr ""
-#: ./lib/utils/weather_icon.dart:317
+#: ./lib/utils/weather_icon.dart:315
msgid "多雲陣雨雪"
msgstr ""
-#: ./lib/utils/weather_icon.dart:318
+#: ./lib/utils/weather_icon.dart:316
msgid "多雲有雹"
msgstr ""
-#: ./lib/utils/weather_icon.dart:319
+#: ./lib/utils/weather_icon.dart:317
msgid "多雲有雷雨"
msgstr ""
-#: ./lib/utils/weather_icon.dart:320
+#: ./lib/utils/weather_icon.dart:318
msgid "多雲有雷雪"
msgstr ""
-#: ./lib/utils/weather_icon.dart:321
+#: ./lib/utils/weather_icon.dart:319
msgid "多雲有雷雹"
msgstr ""
-#: ./lib/utils/weather_icon.dart:322
+#: ./lib/utils/weather_icon.dart:320
msgid "多雲大雷雨"
msgstr ""
-#: ./lib/utils/weather_icon.dart:323
+#: ./lib/utils/weather_icon.dart:321
msgid "多雲大雷雹"
msgstr ""
-#: ./lib/utils/weather_icon.dart:325
+#: ./lib/utils/weather_icon.dart:323
msgid "陰"
msgstr ""
-#: ./lib/utils/weather_icon.dart:326
+#: ./lib/utils/weather_icon.dart:324
msgid "陰有霾"
msgstr ""
-#: ./lib/utils/weather_icon.dart:327
+#: ./lib/utils/weather_icon.dart:325
msgid "陰有靄"
msgstr ""
-#: ./lib/utils/weather_icon.dart:328
+#: ./lib/utils/weather_icon.dart:326
msgid "陰有閃電"
msgstr ""
-#: ./lib/utils/weather_icon.dart:344
+#: ./lib/utils/weather_icon.dart:342
msgid "陰天伴有雷"
msgstr ""
-#: ./lib/utils/weather_icon.dart:330
+#: ./lib/utils/weather_icon.dart:328
msgid "陰有霧"
msgstr ""
-#: ./lib/utils/weather_icon.dart:331
+#: ./lib/utils/weather_icon.dart:329
msgid "陰有雨"
msgstr ""
-#: ./lib/utils/weather_icon.dart:332
+#: ./lib/utils/weather_icon.dart:330
msgid "陰有雨雪"
msgstr ""
-#: ./lib/utils/weather_icon.dart:333
+#: ./lib/utils/weather_icon.dart:331
msgid "陰有大雪"
msgstr ""
-#: ./lib/utils/weather_icon.dart:334
+#: ./lib/utils/weather_icon.dart:332
msgid "陰有雪珠"
msgstr ""
-#: ./lib/utils/weather_icon.dart:335
+#: ./lib/utils/weather_icon.dart:333
msgid "陰有冰珠"
msgstr ""
-#: ./lib/utils/weather_icon.dart:336
+#: ./lib/utils/weather_icon.dart:334
msgid "陰有陣雪"
msgstr ""
-#: ./lib/utils/weather_icon.dart:337
+#: ./lib/utils/weather_icon.dart:335
msgid "陰陣雨雪"
msgstr ""
-#: ./lib/utils/weather_icon.dart:338
+#: ./lib/utils/weather_icon.dart:336
msgid "陰有雹"
msgstr ""
-#: ./lib/utils/weather_icon.dart:339
+#: ./lib/utils/weather_icon.dart:337
msgid "陰有雷雨"
msgstr ""
-#: ./lib/utils/weather_icon.dart:340
+#: ./lib/utils/weather_icon.dart:338
msgid "陰有雷雪"
msgstr ""
-#: ./lib/utils/weather_icon.dart:341
+#: ./lib/utils/weather_icon.dart:339
msgid "陰有雷雹"
msgstr ""
-#: ./lib/utils/weather_icon.dart:342
+#: ./lib/utils/weather_icon.dart:340
msgid "陰大雷雨"
msgstr ""
-#: ./lib/utils/weather_icon.dart:343
+#: ./lib/utils/weather_icon.dart:341
msgid "陰大雷雹"
msgstr ""
-#: ./lib/api/model/location/location.dart:85
+#: ./lib/api/model/location/location.dart:84
msgid "{city}{cityLevel} {town}{townLevel}"
msgstr ""
-#: ./lib/api/model/location/location.dart:98
+#: ./lib/api/model/location/location.dart:97
msgid "{city} {town}"
msgstr ""
-#: ./lib/api/model/location/location.dart:113
+#: ./lib/api/model/location/location.dart:112
msgid "{city}{cityLevel}"
msgstr ""
-#: ./lib/api/model/location/location.dart:130
+#: ./lib/api/model/location/location.dart:129
msgid "{town}{townLevel}"
msgstr ""
-#: ./lib/widgets/ui/color_picker.dart:363
+#: ./lib/widgets/ui/color_picker.dart:361
msgid "色相"
msgstr ""
-#: ./lib/widgets/ui/color_picker.dart:379
+#: ./lib/widgets/ui/color_picker.dart:377
msgid "彩度"
msgstr ""
-#: ./lib/widgets/ui/color_picker.dart:395
+#: ./lib/widgets/ui/color_picker.dart:393
msgid "明度"
msgstr ""
-#: ./lib/widgets/ui/color_picker.dart:415
+#: ./lib/widgets/ui/color_picker.dart:413
msgid "十六進位值"
msgstr ""
\ No newline at end of file
diff --git a/analysis_options.yaml b/analysis_options.yaml
index c20a06e4b..726c8e47b 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -12,7 +12,9 @@ linter:
flutter_style_todos: error
prefer_single_quotes: warning
avoid_annotating_with_dynamic: error
- public_member_api_docs: warning
+ prefer_const_constructors: info
+ prefer_const_constructors_in_immutables: info
+ prefer_const_declarations: info
formatter:
page_width: 100
diff --git a/android/gradle.properties b/android/gradle.properties
index 9b2416014..540567a6c 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -2,3 +2,7 @@ org.gradle.jvmargs=-Xmx4G
android.useAndroidX=true
android.enableJetifier=true
kotlin.jvm.target.validation.mode = IGNORE
+# This builtInKotlin flag was added automatically by Flutter migrator
+android.builtInKotlin=false
+# This newDsl flag was added automatically by Flutter migrator
+android.newDsl=false
diff --git a/assets/fonts/GoogleSansFlex.ttf b/assets/fonts/GoogleSansFlex.ttf
new file mode 100644
index 000000000..6cd6eaf58
Binary files /dev/null and b/assets/fonts/GoogleSansFlex.ttf differ
diff --git a/assets/fonts/NotoSansTC.ttf b/assets/fonts/NotoSansTC.ttf
new file mode 100644
index 000000000..2defdb937
Binary files /dev/null and b/assets/fonts/NotoSansTC.ttf differ
diff --git a/assets/translations/zh-Hant.po b/assets/translations/zh-Hant.po
index 6753f1f6d..2ec760e6d 100644
--- a/assets/translations/zh-Hant.po
+++ b/assets/translations/zh-Hant.po
@@ -11,215 +11,215 @@ msgstr ""
"Language-Team: Chinese Traditional\n"
"Language: zh_TW\n"
-#: ./lib/core/service.dart:291
+#: ./lib/core/service.dart:285
msgid "正在更新位置"
msgstr "正在更新位置"
-#: ./lib/core/service.dart:292
+#: ./lib/core/service.dart:286
msgid "取得 GPS 位置中..."
msgstr "取得 GPS 位置中..."
-#: ./lib/app/settings/notify/page.dart:51
+#: ./lib/app/settings/notify/page.dart:56
msgid "接收全部"
msgstr "接收全部"
-#: ./lib/app/settings/notify/page.dart:50
+#: ./lib/app/settings/notify/page.dart:65
msgid "關閉"
msgstr "關閉"
-#: ./lib/app/settings/notify/_widgets/eew_notify_section.dart:51
+#: ./lib/app/settings/notify/_widgets/eew_notify_section.dart:70
msgid "接收類別"
msgstr "接收類別"
-#: ./lib/app/settings/notify/page.dart:35
+#: ./lib/app/settings/notify/page.dart:55
msgid "所在地震度1以上"
msgstr "所在地震度1以上"
-#: ./lib/app/settings/notify/page.dart:46
+#: ./lib/app/settings/notify/page.dart:61
msgid "海嘯消息、海嘯警報"
msgstr "海嘯消息、海嘯警報"
-#: ./lib/app/settings/notify/page.dart:45
+#: ./lib/app/settings/notify/page.dart:60
msgid "只接收海嘯警報"
msgstr "只接收海嘯警報"
-#: ./lib/app/settings/notify/page.dart:41
+#: ./lib/app/settings/notify/page.dart:66
msgid "接收所在地"
msgstr "接收所在地"
-#: ./lib/app/settings/notify/page.dart:27
+#: ./lib/app/settings/notify/page.dart:54
msgid "所在地震度4以上"
msgstr "所在地震度4以上"
-#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:28
+#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:33
msgid "音效測試"
msgstr "音效測試"
-#: ./lib/route/announcement/announcement.dart:81
+#: ./lib/route/announcement/announcement.dart:80
msgid "公告"
msgstr "公告"
-#: ./lib/app/settings/notify/(5.basic)/announcement/page.dart:32
+#: ./lib/app/settings/notify/(5.basic)/announcement/page.dart:37
msgid "發送公告時"
msgstr "發送公告時"
-#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:46
+#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:51
msgid "音效測試為在裝置上執行的本地通知,僅用於確認裝置在接收通知時是否能正常播放音效。此測試不會向伺服器發送任何請求"
msgstr "音效測試為在裝置上執行的本地通知,僅用於確認裝置在接收通知時是否能正常播放音效。此測試不會向伺服器發送任何請求"
-#: ./lib/app/settings/notify/page.dart:82
+#: ./lib/app/settings/notify/page.dart:104
msgid "伺服器排隊中,請稍候…"
msgstr "伺服器排隊中,請稍候…"
-#: ./lib/app/welcome/4-permissions/page.dart:166
+#: ./lib/app/welcome/4-permissions/page.dart:111
msgid "通知"
msgstr "通知"
-#: ./lib/app/settings/page.dart:182
+#: ./lib/app/settings/page.dart:189
msgid "推播通知設定與通知音效測試"
msgstr "推播通知設定與通知音效測試"
-#: ./lib/app/home/_widgets/location_not_set_card.dart:33
+#: ./lib/app/home_old/_widgets/location_not_set_card.dart:41
msgid "尚未設定所在地"
msgstr "尚未設定所在地"
-#: ./lib/app/settings/notify/page.dart:201
+#: ./lib/app/settings/notify/page.dart:208
msgid "請先設定所在地來使用通知功能"
msgstr "請先設定所在地來使用通知功能"
-#: ./lib/app/settings/notify/page.dart:211
+#: ./lib/app/settings/notify/page.dart:217
msgid "設定所在地"
msgstr "設定所在地"
-#: ./lib/app/settings/experimental/page.dart:209
+#: ./lib/app/settings/experimental/page.dart:215
msgid "地震速報"
msgstr "地震速報"
-#: ./lib/app/settings/notify/page.dart:232
+#: ./lib/app/settings/notify/page.dart:238
msgid "緊急地震速報"
msgstr "緊急地震速報"
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:113
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:125
msgid "地震"
msgstr "地震"
-#: ./lib/app/map/_lib/managers/monitor.dart:1579
+#: ./lib/app/map/_lib/managers/monitor.dart:1584
msgid "強震監視器"
msgstr "強震監視器"
-#: ./lib/app/map/_lib/managers/report.dart:1302
+#: ./lib/app/map/_lib/managers/report.dart:1294
msgid "地震報告"
msgstr "地震報告"
-#: ./lib/app/settings/notify/page.dart:290
+#: ./lib/app/settings/notify/page.dart:297
msgid "震度速報"
msgstr "震度速報"
-#: ./lib/app/settings/notify/page.dart:304
+#: ./lib/app/settings/notify/page.dart:310
msgid "天氣"
msgstr "天氣"
-#: ./lib/app/home/_widgets/thunderstorm_card.dart:63
+#: ./lib/app/home_old/_widgets/thunderstorm_card.dart:72
msgid "雷雨即時訊息"
msgstr "雷雨即時訊息"
-#: ./lib/app/settings/notify/page.dart:334
+#: ./lib/app/settings/notify/page.dart:339
msgid "天氣警特報"
msgstr "天氣警特報"
-#: ./lib/app/settings/notify/page.dart:354
+#: ./lib/app/settings/notify/page.dart:358
msgid "防災資訊"
msgstr "防災資訊"
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:129
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:141
msgid "海嘯"
msgstr "海嘯"
-#: ./lib/app/settings/notify/page.dart:378
+#: ./lib/app/settings/notify/page.dart:383
msgid "海嘯資訊"
msgstr "海嘯資訊"
-#: ./lib/app/settings/notify/page.dart:390
+#: ./lib/app/settings/notify/page.dart:394
msgid "其他"
msgstr "其他"
-#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:31
+#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:36
msgid "重大"
msgstr "重大"
-#: ./lib/app/settings/notify/(4.tsunami)/tsunami/page.dart:31
+#: ./lib/app/settings/notify/(4.tsunami)/tsunami/page.dart:36
msgid "海嘯警報發布時"
msgstr "海嘯警報發布時"
-#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:37
+#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:42
msgid "一般"
msgstr "一般"
-#: ./lib/app/settings/notify/(4.tsunami)/tsunami/page.dart:37
+#: ./lib/app/settings/notify/(4.tsunami)/tsunami/page.dart:42
msgid "海嘯消息發布時"
msgstr "海嘯消息發布時"
-#: ./lib/app/settings/notify/(4.tsunami)/tsunami/page.dart:41
+#: ./lib/app/settings/notify/(4.tsunami)/tsunami/page.dart:46
msgid "太平洋海嘯消息(無聲通知)"
msgstr "太平洋海嘯消息(無聲通知)"
-#: ./lib/app/settings/notify/(4.tsunami)/tsunami/page.dart:42
+#: ./lib/app/settings/notify/(4.tsunami)/tsunami/page.dart:47
msgid "太平洋海嘯消息發布時"
msgstr "太平洋海嘯消息發布時"
-#: ./lib/app/settings/notify/(2.earthquake)/monitor/page.dart:30
+#: ./lib/app/settings/notify/(2.earthquake)/monitor/page.dart:35
msgid "強震監視器(一般)"
msgstr "強震監視器(一般)"
-#: ./lib/app/settings/notify/(2.earthquake)/monitor/page.dart:31
+#: ./lib/app/settings/notify/(2.earthquake)/monitor/page.dart:36
msgid "偵測到晃動"
msgstr "偵測到晃動"
-#: ./lib/app/settings/notify/(2.earthquake)/intensity/page.dart:30
+#: ./lib/app/settings/notify/(2.earthquake)/intensity/page.dart:35
msgid "震度速報(一般)"
msgstr "震度速報(一般)"
-#: ./lib/app/settings/notify/(2.earthquake)/intensity/page.dart:31
+#: ./lib/app/settings/notify/(2.earthquake)/intensity/page.dart:36
msgid "所在地(鄉鎮)實測震度 3 以上"
msgstr "所在地(鄉鎮)實測震度 3 以上"
-#: ./lib/app/settings/notify/(2.earthquake)/intensity/page.dart:36
+#: ./lib/app/settings/notify/(2.earthquake)/intensity/page.dart:41
msgid "震度速報(無聲通知)"
msgstr "震度速報(無聲通知)"
-#: ./lib/app/settings/notify/(2.earthquake)/intensity/page.dart:37
+#: ./lib/app/settings/notify/(2.earthquake)/intensity/page.dart:42
msgid "所在地(鄉鎮)實測震度 1 以上"
msgstr "所在地(鄉鎮)實測震度 1 以上"
-#: ./lib/app/settings/notify/(2.earthquake)/report/page.dart:30
+#: ./lib/app/settings/notify/(2.earthquake)/report/page.dart:35
msgid "地震報告(一般)"
msgstr "地震報告(一般)"
-#: ./lib/app/settings/notify/(2.earthquake)/report/page.dart:31
+#: ./lib/app/settings/notify/(2.earthquake)/report/page.dart:36
msgid "所在地(縣市)實測震度 3 以上"
msgstr "所在地(縣市)實測震度 3 以上"
-#: ./lib/app/settings/notify/(2.earthquake)/report/page.dart:36
+#: ./lib/app/settings/notify/(2.earthquake)/report/page.dart:41
msgid "地震報告(無聲通知)"
msgstr "地震報告(無聲通知)"
-#: ./lib/app/settings/notify/(2.earthquake)/report/page.dart:37
+#: ./lib/app/settings/notify/(2.earthquake)/report/page.dart:42
msgid "所在地(縣市)實測震度 1 以上"
msgstr "所在地(縣市)實測震度 1 以上"
-#: ./lib/app/settings/notify/_lib/utils.dart:15
+#: ./lib/app/settings/notify/_lib/utils.dart:29
msgid "已更新通知設定"
msgstr "已更新通知設定"
-#: ./lib/app/settings/notify/_lib/utils.dart:22
+#: ./lib/app/settings/notify/_lib/utils.dart:40
msgid "更新通知設定失敗"
msgstr "更新通知設定失敗"
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:30
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:35
msgid "緊急地震速報(重大)"
msgstr "緊急地震速報(重大)"
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:31
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:36
msgid ""
"最大震度 5 弱以上 且\n"
"所在地(鄉鎮)預估震度 4 以上"
@@ -227,11 +227,11 @@ msgstr ""
"最大震度 5 弱以上 且\n"
"所在地(鄉鎮)預估震度 4 以上"
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:36
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:41
msgid "緊急地震速報(一般)"
msgstr "緊急地震速報(一般)"
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:37
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:42
msgid ""
"最大震度 5 弱以上 且\n"
"所在地(鄉鎮)預估震度 2 以上"
@@ -239,11 +239,11 @@ msgstr ""
"最大震度 5 弱以上 且\n"
"所在地(鄉鎮)預估震度 2 以上"
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:41
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:46
msgid "緊急地震速報(無聲)"
msgstr "緊急地震速報(無聲)"
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:42
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:47
msgid ""
"最大震度 5 弱以上 且\n"
"所在地(鄉鎮)預估震度 1 以上"
@@ -251,39 +251,39 @@ msgstr ""
"最大震度 5 弱以上 且\n"
"所在地(鄉鎮)預估震度 1 以上"
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:46
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:51
msgid "地震速報(重大)"
msgstr "地震速報(重大)"
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:47
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:52
msgid "所在地(鄉鎮)預估震度 4 以上"
msgstr "所在地(鄉鎮)預估震度 4 以上"
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:51
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:56
msgid "地震速報(一般)"
msgstr "地震速報(一般)"
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:52
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:57
msgid "所在地(鄉鎮)預估震度 2 以上"
msgstr "所在地(鄉鎮)預估震度 2 以上"
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:56
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:61
msgid "地震速報(無聲)"
msgstr "地震速報(無聲)"
-#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:57
+#: ./lib/app/settings/notify/(1.eew)/eew/page.dart:62
msgid "所在地(鄉鎮)預估震度 1 以上"
msgstr "所在地(鄉鎮)預估震度 1 以上"
-#: ./lib/app/settings/notify/(3.weather)/evacuation/page.dart:32
+#: ./lib/app/settings/notify/(3.weather)/evacuation/page.dart:36
msgid "所在地(鄉鎮)發布防災警訊時"
msgstr "所在地(鄉鎮)發布防災警訊時"
-#: ./lib/app/settings/notify/(3.weather)/evacuation/page.dart:38
+#: ./lib/app/settings/notify/(3.weather)/evacuation/page.dart:42
msgid "所在地(鄉鎮)發布防災資訊時"
msgstr "所在地(鄉鎮)發布防災資訊時"
-#: ./lib/app/settings/notify/(3.weather)/advisory/page.dart:32
+#: ./lib/app/settings/notify/(3.weather)/advisory/page.dart:37
msgid ""
"所在地(鄉鎮)發布紅色燈號之\n"
"天氣警特報"
@@ -291,7 +291,7 @@ msgstr ""
"所在地(鄉鎮)發布紅色燈號之\n"
"天氣警特報"
-#: ./lib/app/settings/notify/(3.weather)/advisory/page.dart:38
+#: ./lib/app/settings/notify/(3.weather)/advisory/page.dart:43
msgid ""
"所在地(鄉鎮)發布上述除外燈號之\n"
"天氣警特報"
@@ -299,63 +299,63 @@ msgstr ""
"所在地(鄉鎮)發布上述除外燈號之\n"
"天氣警特報"
-#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:32
+#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:37
msgid "所在地(鄉鎮)發布山區暴雨時"
msgstr "所在地(鄉鎮)發布山區暴雨時"
-#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:38
+#: ./lib/app/settings/notify/(3.weather)/thunderstorm/page.dart:43
msgid "所在地(鄉鎮)發布雷雨即時訊息時"
msgstr "所在地(鄉鎮)發布雷雨即時訊息時"
-#: ./lib/app/settings/experimental/page.dart:197
-msgid "啟動時進入強震監視器"
-msgstr "啟動時進入強震監視器"
-
-#: ./lib/app/settings/experimental/page.dart:57
+#: ./lib/app/settings/experimental/page.dart:48
msgid "地震速報不限制非 CWA 來源"
msgstr "地震速報不限制非 CWA 來源"
-#: ./lib/app/settings/page.dart:428
+#: ./lib/app/settings/experimental/page.dart:203
+msgid "啟動時進入強震監視器"
+msgstr "啟動時進入強震監視器"
+
+#: ./lib/app/settings/page.dart:430
msgid "實驗性功能"
msgstr "實驗性功能"
-#: ./lib/app/settings/page.dart:429
+#: ./lib/app/settings/page.dart:431
msgid "搶先體驗開發中的新功能"
msgstr "搶先體驗開發中的新功能"
-#: ./lib/app/settings/experimental/page.dart:154
+#: ./lib/app/settings/experimental/page.dart:160
msgid "注意"
msgstr "注意"
-#: ./lib/app/settings/experimental/page.dart:162
+#: ./lib/app/settings/experimental/page.dart:168
msgid "這些功能仍在開發中,可能會不穩定或在未來的版本中變更。"
msgstr "這些功能仍在開發中,可能會不穩定或在未來的版本中變更。"
-#: ./lib/app/settings/experimental/page.dart:188
+#: ./lib/app/settings/experimental/page.dart:194
msgid "啟動行為"
msgstr "啟動行為"
-#: ./lib/app/settings/experimental/page.dart:198
+#: ./lib/app/settings/experimental/page.dart:204
msgid "開啟 App 時直接進入強震監視器地圖"
msgstr "開啟 App 時直接進入強震監視器地圖"
-#: ./lib/app/settings/experimental/page.dart:218
+#: ./lib/app/settings/experimental/page.dart:224
msgid "不限制非 CWA 來源"
msgstr "不限制非 CWA 來源"
-#: ./lib/app/settings/experimental/page.dart:219
+#: ./lib/app/settings/experimental/page.dart:225
msgid "顯示所有來源的地震速報資料"
msgstr "顯示所有來源的地震速報資料"
-#: ./lib/app/settings/experimental/page.dart:280
+#: ./lib/app/settings/experimental/page.dart:281
msgid "啟用實驗性功能"
msgstr "啟用實驗性功能"
-#: ./lib/app/settings/experimental/page.dart:286
+#: ./lib/app/settings/experimental/page.dart:287
msgid "你即將啟用:"
msgstr "你即將啟用:"
-#: ./lib/app/settings/experimental/page.dart:317
+#: ./lib/app/settings/experimental/page.dart:318
msgid "此功能為實驗性質,可能會造成應用程式不穩定或行為異常。如遇問題,請至設定中關閉此功能。"
msgstr "此功能為實驗性質,可能會造成應用程式不穩定或行為異常。如遇問題,請至設定中關閉此功能。"
@@ -363,15 +363,15 @@ msgstr "此功能為實驗性質,可能會造成應用程式不穩定或行為
msgid "取消"
msgstr "取消"
-#: ./lib/app/settings/layout/page.dart:272
+#: ./lib/app/settings/layout/page.dart:163
msgid "啟用"
msgstr "啟用"
-#: ./lib/app/settings/page.dart:151
+#: ./lib/app/settings/page.dart:158
msgid "單位"
msgstr "單位"
-#: ./lib/app/settings/page.dart:152
+#: ./lib/app/settings/page.dart:159
msgid "調整 DPIP 顯示數值時使用的單位"
msgstr "調整 DPIP 顯示數值時使用的單位"
@@ -383,131 +383,131 @@ msgstr "使用華氏度"
msgid "切換溫度顯示單位為華氏度 (℉)"
msgstr "切換溫度顯示單位為華氏度 (℉)"
-#: ./lib/app/settings/page.dart:141
+#: ./lib/app/settings/page.dart:148
msgid "語言"
msgstr "語言"
-#: ./lib/app/settings/page.dart:142
+#: ./lib/app/settings/page.dart:149
msgid "調整 DPIP 的顯示語言"
msgstr "調整 DPIP 的顯示語言"
-#: ./lib/app/settings/locale/page.dart:41
+#: ./lib/app/settings/locale/page.dart:49
msgid "顯示語言"
msgstr "顯示語言"
-#: ./lib/app/settings/locale/page.dart:42
+#: ./lib/app/settings/locale/page.dart:50
msgid "系統語言"
msgstr "系統語言"
-#: ./lib/app/settings/locale/page.dart:52
+#: ./lib/app/settings/locale/page.dart:60
msgid "協助翻譯"
msgstr "協助翻譯"
-#: ./lib/app/settings/locale/page.dart:53
+#: ./lib/app/settings/locale/page.dart:61
msgid "點擊這裡來幫助我們改進 DPIP 的翻譯"
msgstr "點擊這裡來幫助我們改進 DPIP 的翻譯"
-#: ./lib/app/settings/locale/select/page.dart:116
+#: ./lib/app/settings/locale/select/page.dart:122
msgid "已翻譯 {translated}・已校對 {approved}"
msgstr "已翻譯 {translated}・已校對 {approved}"
-#: ./lib/app/settings/locale/select/page.dart:129
+#: ./lib/app/settings/locale/select/page.dart:135
msgid "來源語言"
msgstr "來源語言"
-#: ./lib/app/settings/locale/select/page.dart:163
+#: ./lib/app/settings/locale/select/page.dart:172
msgid "選擇語言"
msgstr "選擇語言"
-#: ./lib/app/settings/donate/page.dart:52
+#: ./lib/app/settings/donate/page.dart:59
msgid "無法連線至商店,請稍後再試"
msgstr "無法連線至商店,請稍後再試"
-#: ./lib/app/settings/donate/page.dart:59
+#: ./lib/app/settings/donate/page.dart:65
msgid "找不到商品,請稍候再試"
msgstr "找不到商品,請稍候再試"
-#: ./lib/app/map/_lib/managers/report.dart:521
-msgid "重新載入"
-msgstr "重新載入"
-
-#: ./lib/app/settings/donate/page.dart:171
-msgid "正在載入商店物品中"
-msgstr "正在載入商店物品中"
-
-#: ./lib/app/settings/donate/page.dart:225
+#: ./lib/app/settings/donate/page.dart:136
msgid "支持 DPIP"
msgstr "支持 DPIP"
-#: ./lib/app/settings/donate/page.dart:233
+#: ./lib/app/settings/donate/page.dart:144
msgid "DPIP 作為一款致力於提供即時地震資訊的 App,目前並無廣告或其他盈利模式。您的支持將幫助我們維持伺服器運行與持續開發。"
msgstr "DPIP 作為一款致力於提供即時地震資訊的 App,目前並無廣告或其他盈利模式。您的支持將幫助我們維持伺服器運行與持續開發。"
-#: ./lib/app/settings/donate/page.dart:260
+#: ./lib/app/settings/donate/page.dart:170
msgid "訂閱制"
msgstr "訂閱制"
-#: ./lib/app/settings/donate/page.dart:276
+#: ./lib/app/settings/donate/page.dart:189
msgid "推薦"
msgstr "推薦"
-#: ./lib/app/settings/donate/page.dart:443
+#: ./lib/app/settings/donate/page.dart:353
msgid "{price}/月"
msgstr "{price}/月"
-#: ./lib/app/settings/donate/page.dart:475
+#: ./lib/app/settings/donate/page.dart:385
msgid "單次支援"
msgstr "單次支援"
-#: ./lib/app/settings/donate/page.dart:615
+#: ./lib/app/settings/donate/page.dart:520
msgid "恢復購買"
msgstr "恢復購買"
-#: ./lib/app/settings/donate/page.dart:628
+#: ./lib/app/settings/donate/page.dart:530
msgid "無法連線至 {store},請稍後再試。"
msgstr "無法連線至 {store},請稍後再試。"
-#: ./lib/app/settings/donate/page.dart:639
+#: ./lib/app/settings/donate/page.dart:541
msgid "正在恢復您購買的訂閱"
msgstr "正在恢復您購買的訂閱"
-#: ./lib/app/settings/donate/page.dart:644
+#: ./lib/app/settings/donate/page.dart:546
msgid "使用條款"
msgstr "使用條款"
-#: ./lib/app/settings/donate/page.dart:648
+#: ./lib/app/settings/donate/page.dart:550
msgid "隱私權政策"
msgstr "隱私權政策"
-#: ./lib/app/settings/proxy/page.dart:51
+#: ./lib/app/map/_lib/managers/report.dart:546
+msgid "重新載入"
+msgstr "重新載入"
+
+#: ./lib/app/settings/donate/page.dart:636
+msgid "正在載入商店物品中"
+msgstr "正在載入商店物品中"
+
+#: ./lib/app/settings/proxy/page.dart:38
msgid "設定已儲存"
msgstr "設定已儲存"
-#: ./lib/app/settings/page.dart:200
+#: ./lib/app/settings/page.dart:207
msgid "HTTP 代理"
msgstr "HTTP 代理"
-#: ./lib/app/settings/page.dart:201
+#: ./lib/app/settings/page.dart:208
msgid "調整 HTTP 代理伺服器設定"
msgstr "調整 HTTP 代理伺服器設定"
-#: ./lib/app/settings/proxy/page.dart:75
+#: ./lib/app/settings/proxy/page.dart:74
msgid "啟用代理"
msgstr "啟用代理"
-#: ./lib/app/settings/proxy/page.dart:76
+#: ./lib/app/settings/proxy/page.dart:75
msgid "透過代理伺服器發送所有網路請求"
msgstr "透過代理伺服器發送所有網路請求"
-#: ./lib/app/settings/proxy/page.dart:88
+#: ./lib/app/settings/proxy/page.dart:87
msgid "代理主機"
msgstr "代理主機"
-#: ./lib/app/settings/proxy/page.dart:102
+#: ./lib/app/settings/proxy/page.dart:101
msgid "代理端口"
msgstr "代理端口"
-#: ./lib/app/settings/proxy/page.dart:117
+#: ./lib/app/settings/proxy/page.dart:116
msgid "設定儲存後,需要重新啟動應用程式才能生效"
msgstr "設定儲存後,需要重新啟動應用程式才能生效"
@@ -515,123 +515,123 @@ msgstr "設定儲存後,需要重新啟動應用程式才能生效"
msgid "設定"
msgstr "設定"
-#: ./lib/app/settings/page.dart:71
+#: ./lib/app/settings/page.dart:79
msgid "自訂你的 DPIP 使用體驗"
msgstr "自訂你的 DPIP 使用體驗"
-#: ./lib/app/welcome/4-permissions/page.dart:174
+#: ./lib/app/welcome/4-permissions/page.dart:119
msgid "位置"
msgstr "位置"
-#: ./lib/app/settings/location/page.dart:417
+#: ./lib/app/settings/location/page.dart:421
msgid "所在地"
msgstr "所在地"
-#: ./lib/app/settings/location/page.dart:241
+#: ./lib/app/settings/location/page.dart:245
msgid "設定你的所在地來接收當地的即時資訊"
msgstr "設定你的所在地來接收當地的即時資訊"
-#: ./lib/app/settings/page.dart:113
+#: ./lib/app/settings/page.dart:120
msgid "介面"
msgstr "介面"
-#: ./lib/app/settings/layout/page.dart:47
+#: ./lib/app/settings/layout/page.dart:183
msgid "版面"
msgstr "版面"
-#: ./lib/app/settings/layout/page.dart:48
+#: ./lib/app/settings/layout/page.dart:184
msgid "調整首頁的版面樣式"
msgstr "調整首頁的版面樣式"
-#: ./lib/app/settings/theme/page.dart:25
+#: ./lib/app/settings/theme/page.dart:32
msgid "主題"
msgstr "主題"
-#: ./lib/app/settings/theme/page.dart:26
+#: ./lib/app/settings/theme/page.dart:33
msgid "調整 DPIP 整體的外觀與顏色"
msgstr "調整 DPIP 整體的外觀與顏色"
-#: ./lib/app/settings/map/page.dart:49
+#: ./lib/app/home/layout.dart:93
msgid "地圖"
msgstr "地圖"
-#: ./lib/app/settings/map/page.dart:50
+#: ./lib/app/settings/map/page.dart:59
msgid "調整地圖的顯示樣式"
msgstr "調整地圖的顯示樣式"
-#: ./lib/app/settings/page.dart:191
+#: ./lib/app/settings/page.dart:198
msgid "網路"
msgstr "網路"
-#: ./lib/app/settings/page.dart:210
+#: ./lib/app/settings/page.dart:217
msgid "資訊"
msgstr "資訊"
-#: ./lib/app/changelog/page.dart:54
+#: ./lib/app/home_old/page.dart:163
msgid "更新日誌"
msgstr "更新日誌"
-#: ./lib/app/settings/page.dart:230
+#: ./lib/app/settings/page.dart:237
msgid "瀏覽 DPIP 的歷次更新紀錄"
msgstr "瀏覽 DPIP 的歷次更新紀錄"
-#: ./lib/app/settings/page.dart:240
+#: ./lib/app/settings/page.dart:247
msgid "第三方套件授權"
msgstr "第三方套件授權"
-#: ./lib/app/settings/page.dart:241
+#: ./lib/app/settings/page.dart:248
msgid "DPIP 的實現歸功於開放原始碼"
msgstr "DPIP 的實現歸功於開放原始碼"
-#: ./lib/app/settings/page.dart:264
+#: ./lib/app/settings/page.dart:271
msgid "贊助我們"
msgstr "贊助我們"
-#: ./lib/app/settings/page.dart:267
+#: ./lib/app/settings/page.dart:274
msgid "幫助我們維護伺服器的穩定和長久發展"
msgstr "幫助我們維護伺服器的穩定和長久發展"
-#: ./lib/app/settings/page.dart:343
+#: ./lib/app/settings/page.dart:349
msgid "下載"
msgstr "下載"
-#: ./lib/app/settings/page.dart:383
+#: ./lib/app/settings/page.dart:385
msgid "除錯"
msgstr "除錯"
-#: ./lib/app/settings/page.dart:391
+#: ./lib/app/settings/page.dart:393
msgid "應用程式版本"
msgstr "應用程式版本"
-#: ./lib/app/settings/page.dart:400
+#: ./lib/app/settings/page.dart:402
msgid "裝置資訊"
msgstr "裝置資訊"
-#: ./lib/app/settings/page.dart:409
+#: ./lib/app/settings/page.dart:411
msgid "複製通知 Token"
msgstr "複製通知 Token"
-#: ./lib/app/debug/logs/page.dart:16
+#: ./lib/app/debug/logs/page.dart:22
msgid "App 日誌"
msgstr "App 日誌"
-#: ./lib/app/settings/page.dart:463
+#: ./lib/app/settings/page.dart:465
msgid "任何資訊應以中央氣象署發布之內容為準"
msgstr "任何資訊應以中央氣象署發布之內容為準"
-#: ./lib/app/settings/location/page.dart:74
+#: ./lib/app/settings/location/page.dart:85
msgid "無法取得通知權限"
msgstr "無法取得通知權限"
-#: ./lib/app/settings/location/page.dart:76
+#: ./lib/app/settings/location/page.dart:87
msgid "無法取得位置權限"
msgstr "無法取得位置權限"
-#: ./lib/app/settings/location/page.dart:77
+#: ./lib/app/settings/location/page.dart:88
msgid "無法取得自啟動權限"
msgstr "無法取得自啟動權限"
-#: ./lib/app/welcome/4-permissions/page.dart:180
+#: ./lib/app/welcome/4-permissions/page.dart:125
msgid "省電策略"
msgstr "省電策略"
@@ -639,63 +639,63 @@ msgstr "省電策略"
msgid "無法取得權限"
msgstr "無法取得權限"
-#: ./lib/app/settings/location/page.dart:84
+#: ./lib/app/settings/location/page.dart:94
msgid "自動定位功能需要您允許 DPIP 使用通知權限才能正常運作。請您到應用程式設定中找到並允許「通知」權限後再試一次。"
msgstr "自動定位功能需要您允許 DPIP 使用通知權限才能正常運作。請您到應用程式設定中找到並允許「通知」權限後再試一次。"
-#: ./lib/app/settings/location/page.dart:86
+#: ./lib/app/settings/location/page.dart:95
msgid "自動定位功能需要您允許 DPIP 使用位置權限才能正常運作。請您到應用程式設定中找到並允許「位置」權限後再試一次。"
msgstr "自動定位功能需要您允許 DPIP 使用位置權限才能正常運作。請您到應用程式設定中找到並允許「位置」權限後再試一次。"
-#: ./lib/app/settings/location/page.dart:89
+#: ./lib/app/settings/location/page.dart:98
msgid "自動定位功能需要您永遠允許 DPIP 使用位置權限才能正常運作。請您到應用程式設定中找到位置權限設定並選擇「永遠」後再試一次。"
msgstr "自動定位功能需要您永遠允許 DPIP 使用位置權限才能正常運作。請您到應用程式設定中找到位置權限設定並選擇「永遠」後再試一次。"
-#: ./lib/app/settings/location/page.dart:91
+#: ./lib/app/settings/location/page.dart:99
msgid "自動定位功能需要您一律允許 DPIP 使用位置權限才能正常運作。請您到應用程式設定中找到位置權限設定並選擇「一律允許」後再試一次。"
msgstr "自動定位功能需要您一律允許 DPIP 使用位置權限才能正常運作。請您到應用程式設定中找到位置權限設定並選擇「一律允許」後再試一次。"
-#: ./lib/app/settings/location/page.dart:93
+#: ./lib/app/settings/location/page.dart:100
msgid "為了獲得更好的自動定位體驗,您需要給予「自啟動權限」以便讓 DPIP 在背景自動設定所在地資訊。"
msgstr "為了獲得更好的自動定位體驗,您需要給予「自啟動權限」以便讓 DPIP 在背景自動設定所在地資訊。"
-#: ./lib/app/settings/location/page.dart:95
+#: ./lib/app/settings/location/page.dart:101
msgid "為了獲得更好的自動定位體驗,您需要給予「無限制」以便讓 DPIP 在背景自動設定所在地資訊。"
msgstr "為了獲得更好的自動定位體驗,您需要給予「無限制」以便讓 DPIP 在背景自動設定所在地資訊。"
-#: ./lib/app/settings/location/page.dart:96
+#: ./lib/app/settings/location/page.dart:102
msgid "自動定位功能需要您允許 DPIP 使用權限才能正常運作。請您到應用程式設定中找到並允許「權限」後再試一次。"
msgstr "自動定位功能需要您允許 DPIP 使用權限才能正常運作。請您到應用程式設定中找到並允許「權限」後再試一次。"
-#: ./lib/app/settings/location/page.dart:174
+#: ./lib/app/settings/location/page.dart:180
msgid "自動啟動"
msgstr "自動啟動"
-#: ./lib/app/settings/location/page.dart:175
+#: ./lib/app/settings/location/page.dart:181
msgid "為了獲得更好的 DPIP 體驗,請依照步驟啟用自動啟動功能,以便讓 DPIP 在背景能正常接收資訊以及更新所在地。"
msgstr "為了獲得更好的 DPIP 體驗,請依照步驟啟用自動啟動功能,以便讓 DPIP 在背景能正常接收資訊以及更新所在地。"
-#: ./lib/app/settings/location/page.dart:199
+#: ./lib/app/settings/location/page.dart:203
msgid "為了獲得更好的 DPIP 體驗,請依照步驟關閉省電策略,以便讓 DPIP 在背景能正常接收資訊以及更新所在地。"
msgstr "為了獲得更好的 DPIP 體驗,請依照步驟關閉省電策略,以便讓 DPIP 在背景能正常接收資訊以及更新所在地。"
-#: ./lib/app/settings/location/page.dart:233
+#: ./lib/app/settings/location/page.dart:237
msgid "一律允許"
msgstr "一律允許"
-#: ./lib/app/settings/location/page.dart:233
+#: ./lib/app/settings/location/page.dart:237
msgid "永遠"
msgstr "永遠"
-#: ./lib/app/settings/location/page.dart:253
+#: ./lib/app/settings/location/page.dart:257
msgid "自動更新"
msgstr "自動更新"
-#: ./lib/app/settings/location/page.dart:254
+#: ./lib/app/settings/location/page.dart:258
msgid "定期更新目前的所在地"
msgstr "定期更新目前的所在地"
-#: ./lib/app/settings/location/page.dart:263
+#: ./lib/app/settings/location/page.dart:267
msgid ""
"自動定位功能將使用您的裝置上的 GPS,即使 DPIP "
"關閉或未在使用時,也會根據您的地理位置,自動更新您的所在地,提供即時的天氣和地震資訊,讓您隨時掌握當地最新狀況。"
@@ -703,267 +703,429 @@ msgstr ""
"自動定位功能將使用您的裝置上的 GPS,即使 DPIP "
"關閉或未在使用時,也會根據您的地理位置,自動更新您的所在地,提供即時的天氣和地震資訊,讓您隨時掌握當地最新狀況。"
-#: ./lib/app/settings/location/page.dart:334
+#: ./lib/app/settings/location/page.dart:338
msgid "通知功能已被拒絕,請移至設定允許權限。"
msgstr "通知功能已被拒絕,請移至設定允許權限。"
-#: ./lib/app/settings/location/page.dart:395
+#: ./lib/app/settings/location/page.dart:399
msgid "省電策略已被拒絕,請移至設定允許權限。"
msgstr "省電策略已被拒絕,請移至設定允許權限。"
-#: ./lib/app/settings/location/select/[city]/page.dart:98
+#: ./lib/app/settings/location/select/[city]/page.dart:109
msgid "設定所在地時發生錯誤,請稍候再試一次。"
msgstr "設定所在地時發生錯誤,請稍候再試一次。"
-#: ./lib/app/home/_widgets/location_button.dart:233
+#: ./lib/app/home_old/_widgets/location_button.dart:204
msgid "新增地點"
msgstr "新增地點"
-#: ./lib/app/settings/location/select/page.dart:33
+#: ./lib/app/home/_widgets/location_chip.dart:148
msgid "縣市"
msgstr "縣市"
-#: ./lib/app/settings/location/select/page.dart:44
+#: ./lib/app/settings/location/select/page.dart:49
msgid "目前所在地"
msgstr "目前所在地"
-#: ./lib/app/settings/layout/page.dart:56
+#: ./lib/app/settings/layout/page.dart:90
+msgid "停用"
+msgstr "停用"
+
+#: ./lib/app/settings/layout/page.dart:192
msgid "拖曳調整順序"
msgstr "拖曳調整順序"
-#: ./lib/app/settings/layout/page.dart:100
+#: ./lib/app/settings/layout/page.dart:236
msgid "已停用"
msgstr "已停用"
-#: ./lib/app/settings/layout/page.dart:135
+#: ./lib/app/settings/layout/page.dart:271
msgid "所有區塊皆已啟用"
msgstr "所有區塊皆已啟用"
-#: ./lib/app/settings/layout/page.dart:202
-msgid "停用"
-msgstr "停用"
-
-#: ./lib/app/settings/map/page.dart:61
+#: ./lib/app/settings/map/page.dart:70
msgid "初始圖層"
msgstr "初始圖層"
-#: ./lib/app/settings/map/page.dart:62
+#: ./lib/app/settings/map/page.dart:71
msgid "調整地圖的底圖以及初始顯示的圖層"
msgstr "調整地圖的底圖以及初始顯示的圖層"
-#: ./lib/app/settings/map/page.dart:74
+#: ./lib/app/settings/map/page.dart:83
msgid "自動縮放"
msgstr "自動縮放"
-#: ./lib/app/settings/map/page.dart:75
+#: ./lib/app/settings/map/page.dart:84
msgid "接收到檢知時自動縮放地圖"
msgstr "接收到檢知時自動縮放地圖"
-#: ./lib/app/settings/map/page.dart:101
+#: ./lib/app/settings/map/page.dart:110
msgid "動畫幀率"
msgstr "動畫幀率"
-#: ./lib/app/settings/map/page.dart:102
+#: ./lib/app/settings/map/page.dart:111
msgid "調整強震監視器震波模擬動畫的流暢度"
msgstr "調整強震監視器震波模擬動畫的流暢度"
-#: ./lib/app/settings/map/page.dart:136
+#: ./lib/app/settings/map/page.dart:144
msgid "過高的動畫幀率可能會造成卡頓或裝置發熱"
msgstr "過高的動畫幀率可能會造成卡頓或裝置發熱"
-#: ./lib/app/settings/theme/page.dart:46
+#: ./lib/app/settings/theme/page.dart:53
msgid "主題模式"
msgstr "主題模式"
-#: ./lib/app/settings/theme/mode/page.dart:63
+#: ./lib/app/settings/theme/mode/page.dart:69
msgid "淺色"
msgstr "淺色"
-#: ./lib/app/settings/theme/mode/page.dart:64
+#: ./lib/app/settings/theme/mode/page.dart:70
msgid "深色"
msgstr "深色"
-#: ./lib/app/settings/theme/mode/page.dart:59
+#: ./lib/app/settings/theme/mode/page.dart:67
msgid "跟隨系統主題"
msgstr "跟隨系統主題"
-#: ./lib/app/settings/theme/color/page.dart:22
+#: ./lib/app/settings/theme/color/page.dart:30
msgid "主題色彩"
msgstr "主題色彩"
-#: ./lib/app/settings/theme/color/page.dart:43
+#: ./lib/app/settings/theme/color/page.dart:51
msgid "使用系統配色"
msgstr "使用系統配色"
-#: ./lib/app/settings/theme/color/page.dart:62
+#: ./lib/app/settings/theme/color/page.dart:70
msgid "自訂"
msgstr "自訂"
-#: ./lib/app/settings/theme/color/page.dart:72
+#: ./lib/app/settings/theme/color/page.dart:79
msgid "自訂色彩"
msgstr "自訂色彩"
-#: ./lib/app/home/_widgets/thunderstorm_card.dart:84
+#: ./lib/app/map/_lib/managers/radar.dart:614
+msgid "雷達回波"
+msgstr "雷達回波"
+
+#: ./lib/app/home/_widgets/weather_parameters.dart:52
+msgid "相對溼度"
+msgstr "相對溼度"
+
+#: ./lib/app/home/_widgets/weather_parameters.dart:57
+msgid "空氣品質"
+msgstr "空氣品質"
+
+#: ./lib/app/map/_lib/managers/wind.dart:343
+msgid "風向/風速"
+msgstr "風向/風速"
+
+#: ./lib/app/home/_widgets/weather_parameters.dart:68
+msgid "降水量"
+msgstr "降水量"
+
+#: ./lib/app/home/_widgets/location_chip.dart:84
+msgid "清除暫時位置"
+msgstr "清除暫時位置"
+
+#: ./lib/app/home/_widgets/location_chip.dart:159
+msgid "目前選擇地區"
+msgstr "目前選擇地區"
+
+#: ./lib/app/home_old/_widgets/location_button.dart:170
+msgid "快速切換"
+msgstr "快速切換"
+
+#: ./lib/app/home/layout.dart:88
+msgid "首頁"
+msgstr "首頁"
+
+#: ./lib/app/home/layout.dart:98
+msgid "小工具"
+msgstr "小工具"
+
+#: ./lib/app/changelog/page.dart:86
+msgid "發生錯誤"
+msgstr "發生錯誤"
+
+#: ./lib/route/image_viewer/image_viewer.dart:85
+msgid "再試一次"
+msgstr "再試一次"
+
+#: ./lib/app/changelog/page.dart:217
+msgid "目前版本"
+msgstr "目前版本"
+
+#: ./lib/app/welcome/4-permissions/page.dart:383
+msgid "下一步"
+msgstr "下一步"
+
+#: ./lib/app/welcome/1-about/page.dart:73
+msgid "防災資訊平台"
+msgstr "防災資訊平台"
+
+#: ./lib/app/welcome/2-exptech/page.dart:98
+msgid "我們是誰?"
+msgstr "我們是誰?"
+
+#: ./lib/app/welcome/2-exptech/page.dart:105
+msgid "ExpTech Studio 是一群大部分由學生組成,平均年齡未滿 20 歲、人數超過 15 + 的團體。成員來自臺灣北中南、日本、韓國、中國的學生。"
+msgstr "ExpTech Studio 是一群大部分由學生組成,平均年齡未滿 20 歲、人數超過 15 + 的團體。成員來自臺灣北中南、日本、韓國、中國的學生。"
+
+#: ./lib/app/welcome/2-exptech/page.dart:111
+msgid "我們的初衷"
+msgstr "我們的初衷"
+
+#: ./lib/app/welcome/2-exptech/page.dart:118
+msgid "成立初衷是招募一群對電腦及科技有興趣及能力的同學,後來發展至校外,並逐漸形成現在的樣子。"
+msgstr "成立初衷是招募一群對電腦及科技有興趣及能力的同學,後來發展至校外,並逐漸形成現在的樣子。"
+
+#: ./lib/app/welcome/3-notice/page.dart:50
+msgid "注意事項"
+msgstr "注意事項"
+
+#: ./lib/app/welcome/3-notice/page.dart:70
+msgid "任何資訊應以中央氣象署發布之內容為準。"
+msgstr "任何資訊應以中央氣象署發布之內容為準。"
+
+#: ./lib/app/welcome/3-notice/page.dart:87
+msgid "根據網路狀態、伺服器狀態、應用程式狀態、上游資料來源狀態等,有收不到資訊的可能性,我們會盡力避免此類情況,但不保證一定不會發生。"
+msgstr "根據網路狀態、伺服器狀態、應用程式狀態、上游資料來源狀態等,有收不到資訊的可能性,我們會盡力避免此類情況,但不保證一定不會發生。"
+
+#: ./lib/app/welcome/3-notice/page.dart:101
+msgid "強烈搖晃有機率比通知早抵達使用者所在地。"
+msgstr "強烈搖晃有機率比通知早抵達使用者所在地。"
+
+#: ./lib/app/welcome/3-notice/page.dart:115
+msgid "地震速報為快速計算之結果,可能存在較大誤差,應理解並謹慎使用。"
+msgstr "地震速報為快速計算之結果,可能存在較大誤差,應理解並謹慎使用。"
+
+#: ./lib/app/welcome/3-notice/page.dart:129
+msgid "任何不被官方所認可的行為均有可能承擔法律風險,請務必遵守相關規範。"
+msgstr "任何不被官方所認可的行為均有可能承擔法律風險,請務必遵守相關規範。"
+
+#: ./lib/app/welcome/1-about/page.dart:51
+msgid "歡迎使用 DPIP"
+msgstr "歡迎使用 DPIP"
+
+#: ./lib/app/welcome/1-about/page.dart:96
+msgid ""
+"DPIP 是一款由臺灣本土團隊設計的 App,整合 TREM-Net (臺灣即時地震觀測網) "
+"之資訊,以及中央氣象署資料,提供一個整合、單一且便利的防災資訊應用程式。"
+msgstr ""
+"DPIP 是一款由臺灣本土團隊設計的 App,整合 TREM-Net (臺灣即時地震觀測網) "
+"之資訊,以及中央氣象署資料,提供一個整合、單一且便利的防災資訊應用程式。"
+
+#: ./lib/app/welcome/4-permissions/page.dart:112
+msgid "在重大災害發生時以通知來傳遞即時防災資訊"
+msgstr "在重大災害發生時以通知來傳遞即時防災資訊"
+
+#: ./lib/app/welcome/4-permissions/page.dart:120
+msgid "使用定位來自動更新所在地設定,提供當地的即時防災資訊"
+msgstr "使用定位來自動更新所在地設定,提供當地的即時防災資訊"
+
+#: ./lib/app/welcome/4-permissions/page.dart:126
+msgid "允許 DPIP 在背景中持續運行,以便即時防災通知資訊。"
+msgstr "允許 DPIP 在背景中持續運行,以便即時防災通知資訊。"
+
+#: ./lib/route/image_viewer/image_viewer.dart:254
+msgid "儲存"
+msgstr "儲存"
+
+#: ./lib/app/welcome/4-permissions/page.dart:133
+msgid "用於儲存中央氣象署或 ExpTech 提供之資料視覺化圖片"
+msgstr "用於儲存中央氣象署或 ExpTech 提供之資料視覺化圖片"
+
+#: ./lib/app/welcome/4-permissions/page.dart:293
+msgid "權限請求"
+msgstr "權限請求"
+
+#: ./lib/app/welcome/4-permissions/page.dart:294
+msgid "需要使用者手動到設定開啟相關權限。"
+msgstr "需要使用者手動到設定開啟相關權限。"
+
+#: ./lib/route/image_viewer/image_viewer.dart:145
+msgid "確定"
+msgstr "確定"
+
+#: ./lib/app/welcome/4-permissions/page.dart:317
+msgid "需要背景位置權限"
+msgstr "需要背景位置權限"
+
+#: ./lib/app/welcome/4-permissions/page.dart:319
+msgid ""
+"為了在背景持續提供即時防災資訊,DPIP 需要「永遠允許」位置權限。\n"
+"\n"
+"接下來系統會引導您到設定頁面,請選擇「永遠允許」選項。"
+msgstr ""
+"為了在背景持續提供即時防災資訊,DPIP 需要「永遠允許」位置權限。\n"
+"\n"
+"接下來系統會引導您到設定頁面,請選擇「永遠允許」選項。"
+
+#: ./lib/app/welcome/4-permissions/page.dart:325
+msgid "稍後"
+msgstr "稍後"
+
+#: ./lib/app/welcome/4-permissions/page.dart:329
+msgid "前往設定"
+msgstr "前往設定"
+
+#: ./lib/app/welcome/4-permissions/page.dart:406
+msgid "權限"
+msgstr "權限"
+
+#: ./lib/app/welcome/4-permissions/page.dart:419
+msgid "我們一直和使用者站在一起,為使用者的隱私而不斷努力。"
+msgstr "我們一直和使用者站在一起,為使用者的隱私而不斷努力。"
+
+#: ./lib/app/home_old/_widgets/thunderstorm_card.dart:93
msgid "您所在區域附近有劇烈雷雨或降雨發生,請注意防範,持續至 {time} 。"
msgstr "您所在區域附近有劇烈雷雨或降雨發生,請注意防範,持續至 {time} 。"
-#: ./lib/app/home/_widgets/forecast_card.dart:59
+#: ./lib/app/home_old/_widgets/forecast_card.dart:68
msgid "天氣預報(24h)"
msgstr "天氣預報(24h)"
-#: ./lib/app/home/_widgets/location_out_of_service.dart:28
+#: ./lib/app/home_old/_widgets/location_out_of_service.dart:34
msgid "服務區域外,僅在臺灣各地可用"
msgstr "服務區域外,僅在臺灣各地可用"
-#: ./lib/app/map/_lib/managers/radar.dart:587
-msgid "雷達回波"
-msgstr "雷達回波"
-
-#: ./lib/app/map/_lib/managers/monitor.dart:497
+#: ./lib/app/map/_lib/managers/monitor.dart:518
msgid "無資料"
msgstr "無資料"
-#: ./lib/app/home/_widgets/wind_card.dart:372
+#: ./lib/app/home_old/_widgets/wind_card.dart:329
msgid "{wind}級 {Desc}"
msgstr "{wind}級 {Desc}"
-#: ./lib/app/home/_widgets/wind_card.dart:389
+#: ./lib/app/home_old/_widgets/wind_card.dart:346
msgid "陣風 {speed} m/s"
msgstr "陣風 {speed} m/s"
-#: ./lib/app/home/_widgets/wind_card.dart:471
+#: ./lib/app/home_old/_widgets/wind_card.dart:408
msgid "無法測量"
msgstr "無法測量"
-#: ./lib/app/home/_widgets/wind_card.dart:492
+#: ./lib/app/home_old/_widgets/wind_card.dart:429
msgid "指北針不可靠"
msgstr "指北針不可靠"
-#: ./lib/app/home/_widgets/wind_card.dart:494
+#: ./lib/app/home_old/_widgets/wind_card.dart:431
msgid "指北針準確度下降"
msgstr "指北針準確度下降"
-#: ./lib/app/home/_widgets/wind_card.dart:495
+#: ./lib/app/home_old/_widgets/wind_card.dart:432
msgid "指北針正常"
msgstr "指北針正常"
-#: ./lib/app/home/_widgets/wind_card.dart:502
+#: ./lib/app/home_old/_widgets/wind_card.dart:439
msgid "方向精確度"
msgstr "方向精確度"
-#: ./lib/app/home/_widgets/wind_card.dart:521
+#: ./lib/app/home_old/_widgets/wind_card.dart:458
msgid "正常範圍:±0-15°"
msgstr "正常範圍:±0-15°"
-#: ./lib/app/home/_widgets/wind_card.dart:538
+#: ./lib/app/home_old/_widgets/wind_card.dart:475
msgid "附近有強磁場干擾,指北針方向可能完全不準確。請遠離磁鐵、電子裝置或金屬物品。"
msgstr "附近有強磁場干擾,指北針方向可能完全不準確。請遠離磁鐵、電子裝置或金屬物品。"
-#: ./lib/app/home/_widgets/wind_card.dart:539
+#: ./lib/app/home_old/_widgets/wind_card.dart:476
msgid "附近可能有磁場干擾,指北針方向可能有偏差。"
msgstr "附近可能有磁場干擾,指北針方向可能有偏差。"
-#: ./lib/route/image_viewer/image_viewer.dart:145
-msgid "確定"
-msgstr "確定"
-
-#: ./lib/app/home/_widgets/location_button.dart:29
+#: ./lib/app/home_old/_widgets/location_button.dart:38
msgid "尚未設定"
msgstr "尚未設定"
-#: ./lib/app/home/_widgets/location_button.dart:138
-msgid "切換區域"
-msgstr "切換區域"
-
-#: ./lib/app/home/_widgets/location_button.dart:144
-msgid "位置設定"
-msgstr "位置設定"
-
-#: ./lib/app/home/_widgets/location_button.dart:195
-msgid "快速切換"
-msgstr "快速切換"
-
-#: ./lib/app/home/_widgets/location_button.dart:240
+#: ./lib/app/home_old/_widgets/location_button.dart:211
msgid "選擇縣市"
msgstr "選擇縣市"
-#: ./lib/app/home/_widgets/location_button.dart:250
+#: ./lib/app/home_old/_widgets/location_button.dart:221
msgid "目前選擇"
msgstr "目前選擇"
-#: ./lib/app/home/page.dart:888
+#: ./lib/app/home_old/_widgets/location_button.dart:279
+msgid "切換區域"
+msgstr "切換區域"
+
+#: ./lib/app/home_old/_widgets/location_button.dart:285
+msgid "位置設定"
+msgstr "位置設定"
+
+#: ./lib/app/home_old/page.dart:630
msgid "濕度"
msgstr "濕度"
-#: ./lib/app/home/page.dart:916
+#: ./lib/app/home_old/page.dart:658
msgid "風速"
msgstr "風速"
-#: ./lib/app/home/_widgets/weather_header.dart:181
+#: ./lib/app/home_old/_widgets/weather_header.dart:199
msgid "風向"
msgstr "風向"
-#: ./lib/app/home/_widgets/weather_header.dart:190
+#: ./lib/app/home_old/_widgets/weather_header.dart:206
msgid "風級"
msgstr "風級"
-#: ./lib/app/home/page.dart:895
+#: ./lib/app/home_old/page.dart:637
msgid "氣壓"
msgstr "氣壓"
-#: ./lib/app/home/page.dart:902
+#: ./lib/app/home_old/page.dart:644
msgid "降雨"
msgstr "降雨"
-#: ./lib/app/home/page.dart:909
+#: ./lib/app/home_old/page.dart:651
msgid "能見度"
msgstr "能見度"
-#: ./lib/app/home/page.dart:923
+#: ./lib/app/home_old/page.dart:665
msgid "陣風"
msgstr "陣風"
-#: ./lib/app/home/_widgets/weather_header.dart:233
+#: ./lib/app/home_old/_widgets/weather_header.dart:243
msgid "陣風級"
msgstr "陣風級"
-#: ./lib/app/home/_widgets/weather_header.dart:241
+#: ./lib/app/home_old/_widgets/weather_header.dart:251
msgid "日照"
msgstr "日照"
-#: ./lib/app/home/_widgets/hero_weather.dart:142
+#: ./lib/app/home_old/_widgets/hero_weather.dart:151
msgid "體感 {feelsLike}°"
msgstr "體感 {feelsLike}°"
-#: ./lib/app/home/_widgets/hero_weather.dart:185
+#: ./lib/app/home_old/_widgets/hero_weather.dart:194
msgid "無天氣資料"
msgstr "無天氣資料"
-#: ./lib/app/home/_widgets/mode_toggle_button.dart:14
+#: ./lib/app/home_old/_widgets/mode_toggle_button.dart:32
msgid "全國 · 生效中"
msgstr "全國 · 生效中"
-#: ./lib/app/home/_widgets/mode_toggle_button.dart:16
+#: ./lib/app/home_old/_widgets/mode_toggle_button.dart:34
msgid "全國 · 歷史"
msgstr "全國 · 歷史"
-#: ./lib/app/home/_widgets/mode_toggle_button.dart:18
+#: ./lib/app/home_old/_widgets/mode_toggle_button.dart:36
msgid "所在地 · 生效中"
msgstr "所在地 · 生效中"
-#: ./lib/app/home/_widgets/mode_toggle_button.dart:20
+#: ./lib/app/home_old/_widgets/mode_toggle_button.dart:38
msgid "所在地 · 歷史"
msgstr "所在地 · 歷史"
-#: ./lib/app/map/_lib/managers/monitor.dart:1258
+#: ./lib/app/map/_lib/managers/monitor.dart:1274
msgid "EEW"
msgstr "EEW"
-#: ./lib/app/map/_lib/managers/monitor.dart:1397
+#: ./lib/app/map/_lib/managers/monitor.dart:1407
msgid "第 {serial} 報"
msgstr "第 {serial} 報"
-#: ./lib/app/map/_lib/managers/monitor.dart:1415
+#: ./lib/app/map/_lib/managers/monitor.dart:1425
msgid ""
"{time} 左右,{location}附近發生有感地震,預估規模 "
"M{magnitude}、所在地最大震度{intensity}。"
@@ -971,7 +1133,7 @@ msgstr ""
"{time} 左右,{location}附近發生有感地震,預估規模 "
"M{magnitude}、所在地最大震度{intensity}。"
-#: ./lib/app/map/_lib/managers/monitor.dart:1423
+#: ./lib/app/map/_lib/managers/monitor.dart:1433
msgid ""
"{time} 左右,{location}附近發生有感地震,預估規模 "
"M{magnitude}、深度{depth}公里。"
@@ -979,453 +1141,319 @@ msgstr ""
"{time} 左右,{location}附近發生有感地震,預估規模 "
"M{magnitude}、深度{depth}公里。"
-#: ./lib/app/map/_lib/managers/monitor.dart:1469
+#: ./lib/app/map/_lib/managers/monitor.dart:1479
msgid "所在地預估"
msgstr "所在地預估"
-#: ./lib/app/map/_lib/managers/monitor.dart:1505
+#: ./lib/app/map/_lib/managers/monitor.dart:1515
msgid "震波"
msgstr "震波"
-#: ./lib/app/map/_lib/managers/monitor.dart:1527
+#: ./lib/app/map/_lib/managers/monitor.dart:1535
msgid " 秒"
msgstr " 秒"
-#: ./lib/app/map/_lib/managers/monitor.dart:1544
+#: ./lib/app/map/_lib/managers/monitor.dart:1551
msgid "抵達"
msgstr "抵達"
-#: ./lib/app/home/page.dart:195
+#: ./lib/app/home_old/page.dart:158
msgid "已更新至 {version}"
msgstr "已更新至 {version}"
-#: ./lib/utils/weather_icon.dart:284
+#: ./lib/utils/weather_icon.dart:282
msgid "取得天氣異常"
msgstr "取得天氣異常"
-#: ./lib/app/home/page.dart:366
+#: ./lib/app/home_old/page.dart:331
msgid "取得歷史資訊異常"
msgstr "取得歷史資訊異常"
-#: ./lib/app/home/page.dart:777
+#: ./lib/app/home_old/page.dart:571
msgid "上午"
msgstr "上午"
-#: ./lib/app/home/page.dart:777
+#: ./lib/app/home_old/page.dart:571
msgid "下午"
msgstr "下午"
-#: ./lib/app/changelog/page.dart:76
-msgid "發生錯誤"
-msgstr "發生錯誤"
-
-#: ./lib/route/image_viewer/image_viewer.dart:85
-msgid "再試一次"
-msgstr "再試一次"
-
-#: ./lib/app/changelog/page.dart:203
-msgid "目前版本"
-msgstr "目前版本"
-
-#: ./lib/app/welcome/4-permissions/page.dart:403
-msgid "下一步"
-msgstr "下一步"
-
-#: ./lib/app/welcome/1-about/page.dart:68
-msgid "防災資訊平台"
-msgstr "防災資訊平台"
-
-#: ./lib/app/welcome/2-exptech/page.dart:93
-msgid "我們是誰?"
-msgstr "我們是誰?"
-
-#: ./lib/app/welcome/2-exptech/page.dart:100
-msgid "ExpTech Studio 是一群大部分由學生組成,平均年齡未滿 20 歲、人數超過 15 + 的團體。成員來自臺灣北中南、日本、韓國、中國的學生。"
-msgstr "ExpTech Studio 是一群大部分由學生組成,平均年齡未滿 20 歲、人數超過 15 + 的團體。成員來自臺灣北中南、日本、韓國、中國的學生。"
-
-#: ./lib/app/welcome/2-exptech/page.dart:106
-msgid "我們的初衷"
-msgstr "我們的初衷"
-
-#: ./lib/app/welcome/2-exptech/page.dart:113
-msgid "成立初衷是招募一群對電腦及科技有興趣及能力的同學,後來發展至校外,並逐漸形成現在的樣子。"
-msgstr "成立初衷是招募一群對電腦及科技有興趣及能力的同學,後來發展至校外,並逐漸形成現在的樣子。"
-
-#: ./lib/app/welcome/3-notice/page.dart:45
-msgid "注意事項"
-msgstr "注意事項"
-
-#: ./lib/app/welcome/3-notice/page.dart:65
-msgid "任何資訊應以中央氣象署發布之內容為準。"
-msgstr "任何資訊應以中央氣象署發布之內容為準。"
-
-#: ./lib/app/welcome/3-notice/page.dart:82
-msgid "根據網路狀態、伺服器狀態、應用程式狀態、上游資料來源狀態等,有收不到資訊的可能性,我們會盡力避免此類情況,但不保證一定不會發生。"
-msgstr "根據網路狀態、伺服器狀態、應用程式狀態、上游資料來源狀態等,有收不到資訊的可能性,我們會盡力避免此類情況,但不保證一定不會發生。"
-
-#: ./lib/app/welcome/3-notice/page.dart:97
-msgid "強烈搖晃有機率比通知早抵達使用者所在地。"
-msgstr "強烈搖晃有機率比通知早抵達使用者所在地。"
-
-#: ./lib/app/welcome/3-notice/page.dart:111
-msgid "地震速報為快速計算之結果,可能存在較大誤差,應理解並謹慎使用。"
-msgstr "地震速報為快速計算之結果,可能存在較大誤差,應理解並謹慎使用。"
-
-#: ./lib/app/welcome/3-notice/page.dart:125
-msgid "任何不被官方所認可的行為均有可能承擔法律風險,請務必遵守相關規範。"
-msgstr "任何不被官方所認可的行為均有可能承擔法律風險,請務必遵守相關規範。"
-
-#: ./lib/app/welcome/1-about/page.dart:46
-msgid "歡迎使用 DPIP"
-msgstr "歡迎使用 DPIP"
-
-#: ./lib/app/welcome/1-about/page.dart:91
-msgid ""
-"DPIP 是一款由臺灣本土團隊設計的 App,整合 TREM-Net (臺灣即時地震觀測網) "
-"之資訊,以及中央氣象署資料,提供一個整合、單一且便利的防災資訊應用程式。"
-msgstr ""
-"DPIP 是一款由臺灣本土團隊設計的 App,整合 TREM-Net (臺灣即時地震觀測網) "
-"之資訊,以及中央氣象署資料,提供一個整合、單一且便利的防災資訊應用程式。"
-
-#: ./lib/app/welcome/4-permissions/page.dart:167
-msgid "在重大災害發生時以通知來傳遞即時防災資訊"
-msgstr "在重大災害發生時以通知來傳遞即時防災資訊"
-
-#: ./lib/app/welcome/4-permissions/page.dart:175
-msgid "使用定位來自動更新所在地設定,提供當地的即時防災資訊"
-msgstr "使用定位來自動更新所在地設定,提供當地的即時防災資訊"
-
-#: ./lib/app/welcome/4-permissions/page.dart:181
-msgid "允許 DPIP 在背景中持續運行,以便即時防災通知資訊。"
-msgstr "允許 DPIP 在背景中持續運行,以便即時防災通知資訊。"
-
-#: ./lib/route/image_viewer/image_viewer.dart:255
-msgid "儲存"
-msgstr "儲存"
-
-#: ./lib/app/welcome/4-permissions/page.dart:188
-msgid "用於儲存中央氣象署或 ExpTech 提供之資料視覺化圖片"
-msgstr "用於儲存中央氣象署或 ExpTech 提供之資料視覺化圖片"
-
-#: ./lib/app/welcome/4-permissions/page.dart:352
-msgid "權限請求"
-msgstr "權限請求"
-
-#: ./lib/app/welcome/4-permissions/page.dart:353
-msgid "需要使用者手動到設定開啟相關權限。"
-msgstr "需要使用者手動到設定開啟相關權限。"
-
-#: ./lib/app/welcome/4-permissions/page.dart:376
-msgid "需要背景位置權限"
-msgstr "需要背景位置權限"
-
-#: ./lib/app/welcome/4-permissions/page.dart:378
-msgid ""
-"為了在背景持續提供即時防災資訊,DPIP 需要「永遠允許」位置權限。\n"
-"\n"
-"接下來系統會引導您到設定頁面,請選擇「永遠允許」選項。"
-msgstr ""
-"為了在背景持續提供即時防災資訊,DPIP 需要「永遠允許」位置權限。\n"
-"\n"
-"接下來系統會引導您到設定頁面,請選擇「永遠允許」選項。"
-
-#: ./lib/app/welcome/4-permissions/page.dart:384
-msgid "稍後"
-msgstr "稍後"
-
-#: ./lib/app/welcome/4-permissions/page.dart:388
-msgid "前往設定"
-msgstr "前往設定"
-
-#: ./lib/app/welcome/4-permissions/page.dart:426
-msgid "權限"
-msgstr "權限"
-
-#: ./lib/app/welcome/4-permissions/page.dart:439
-msgid "我們一直和使用者站在一起,為使用者的隱私而不斷努力。"
-msgstr "我們一直和使用者站在一起,為使用者的隱私而不斷努力。"
-
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:84
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:96
msgid "地圖圖層"
msgstr "地圖圖層"
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:85
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:97
msgid "選擇要顯示的地圖圖層"
msgstr "選擇要顯示的地圖圖層"
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:91
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:103
msgid "底圖"
msgstr "底圖"
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:97
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:109
msgid "簡單"
msgstr "簡單"
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:119
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:131
msgid "監視器"
msgstr "監視器"
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:124
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:136
msgid "報告"
msgstr "報告"
-#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:135
+#: ./lib/app/map/_widgets/layer_toggle_sheet.dart:147
msgid "氣象"
msgstr "氣象"
-#: ./lib/app/map/_lib/managers/temperature.dart:453
+#: ./lib/app/map/_lib/managers/temperature.dart:480
msgid "氣溫"
msgstr "氣溫"
-#: ./lib/app/map/_lib/managers/precipitation.dart:577
+#: ./lib/app/map/_lib/managers/precipitation.dart:587
msgid "降水"
msgstr "降水"
-#: ./lib/app/map/_lib/managers/wind.dart:320
-msgid "風向/風速"
-msgstr "風向/風速"
-
-#: ./lib/app/map/_lib/managers/lightning.dart:294
+#: ./lib/app/map/_lib/managers/lightning.dart:302
msgid "閃電"
msgstr "閃電"
-#: ./lib/app/map/_widgets/map_legend.dart:202
+#: ./lib/app/map/_widgets/map_legend.dart:250
msgid "單位:{unit}"
msgstr "單位:{unit}"
-#: ./lib/app/map/_lib/managers/tsunami.dart:485
+#: ./lib/app/map/_lib/managers/tsunami.dart:494
msgid "近期無海嘯資訊"
msgstr "近期無海嘯資訊"
-#: ./lib/app/map/_lib/managers/tsunami.dart:486
+#: ./lib/app/map/_lib/managers/tsunami.dart:494
msgid "海嘯警報"
msgstr "海嘯警報"
-#: ./lib/app/map/_lib/managers/tsunami.dart:496
+#: ./lib/app/map/_lib/managers/tsunami.dart:504
msgid "{id}號 第{serial}報"
msgstr "{id}號 第{serial}報"
-#: ./lib/app/map/_lib/managers/tsunami.dart:551
+#: ./lib/app/map/_lib/managers/tsunami.dart:558
msgid "發布"
msgstr "發布"
-#: ./lib/app/map/_lib/managers/tsunami.dart:553
+#: ./lib/app/map/_lib/managers/tsunami.dart:560
msgid "更新"
msgstr "更新"
-#: ./lib/app/map/_lib/managers/tsunami.dart:554
+#: ./lib/app/map/_lib/managers/tsunami.dart:561
msgid "解除"
msgstr "解除"
-#: ./lib/app/map/_lib/managers/tsunami.dart:607
+#: ./lib/app/map/_lib/managers/tsunami.dart:612
msgid "預估海嘯到達時間及波高"
msgstr "預估海嘯到達時間及波高"
-#: ./lib/app/map/_lib/managers/tsunami.dart:626
+#: ./lib/app/map/_lib/managers/tsunami.dart:630
msgid "各地觀測到的海嘯"
msgstr "各地觀測到的海嘯"
-#: ./lib/app/map/_lib/managers/tsunami.dart:641
+#: ./lib/app/map/_lib/managers/tsunami.dart:645
msgid "地震資訊"
msgstr "地震資訊"
-#: ./lib/app/map/_lib/managers/tsunami.dart:654
+#: ./lib/app/map/_lib/managers/tsunami.dart:657
msgid "發生時間"
msgstr "發生時間"
-#: ./lib/app/map/_lib/managers/report.dart:1072
+#: ./lib/app/map/_lib/managers/report.dart:1069
msgid "位於"
msgstr "位於"
-#: ./lib/app/map/_lib/managers/tsunami.dart:736
+#: ./lib/app/map/_lib/managers/tsunami.dart:730
msgid "規模"
msgstr "規模"
-#: ./lib/app/map/_lib/managers/tsunami.dart:765
+#: ./lib/app/map/_lib/managers/tsunami.dart:753
msgid "深度"
msgstr "深度"
-#: ./lib/app/map/_lib/managers/radar.dart:744
+#: ./lib/app/map/_lib/managers/radar.dart:750
msgid "長按設定播放起點"
msgstr "長按設定播放起點"
-#: ./lib/app/map/_lib/managers/radar.dart:760
+#: ./lib/app/map/_lib/managers/radar.dart:766
msgid "目前時間"
msgstr "目前時間"
-#: ./lib/app/map/_lib/managers/radar.dart:765
+#: ./lib/app/map/_lib/managers/radar.dart:771
msgid "播放起點"
msgstr "播放起點"
-#: ./lib/app/map/_lib/managers/radar.dart:1099
+#: ./lib/app/map/_lib/managers/radar.dart:1100
msgid "播放進度"
msgstr "播放進度"
-#: ./lib/app/map/_lib/managers/lightning.dart:393
+#: ./lib/app/map/_lib/managers/lightning.dart:399
msgid "5 分鐘內對地閃電"
msgstr "5 分鐘內對地閃電"
-#: ./lib/app/map/_lib/managers/lightning.dart:401
+#: ./lib/app/map/_lib/managers/lightning.dart:407
msgid "10 分鐘內對地閃電"
msgstr "10 分鐘內對地閃電"
-#: ./lib/app/map/_lib/managers/lightning.dart:409
+#: ./lib/app/map/_lib/managers/lightning.dart:415
msgid "30 分鐘內對地閃電"
msgstr "30 分鐘內對地閃電"
-#: ./lib/app/map/_lib/managers/lightning.dart:417
+#: ./lib/app/map/_lib/managers/lightning.dart:423
msgid "60 分鐘內對地閃電"
msgstr "60 分鐘內對地閃電"
-#: ./lib/app/map/_lib/managers/lightning.dart:425
+#: ./lib/app/map/_lib/managers/lightning.dart:431
msgid "5 分鐘內雲間閃電"
msgstr "5 分鐘內雲間閃電"
-#: ./lib/app/map/_lib/managers/lightning.dart:433
+#: ./lib/app/map/_lib/managers/lightning.dart:439
msgid "10 分鐘內雲間閃電"
msgstr "10 分鐘內雲間閃電"
-#: ./lib/app/map/_lib/managers/lightning.dart:441
+#: ./lib/app/map/_lib/managers/lightning.dart:447
msgid "30 分鐘內雲間閃電"
msgstr "30 分鐘內雲間閃電"
-#: ./lib/app/map/_lib/managers/lightning.dart:449
+#: ./lib/app/map/_lib/managers/lightning.dart:455
msgid "60 分鐘內雲間閃電"
msgstr "60 分鐘內雲間閃電"
-#: ./lib/app/map/_lib/managers/precipitation.dart:396
+#: ./lib/app/map/_lib/managers/precipitation.dart:420
msgid "今日"
msgstr "今日"
-#: ./lib/app/map/_lib/managers/precipitation.dart:397
+#: ./lib/app/map/_lib/managers/precipitation.dart:421
msgid "10 分鐘"
msgstr "10 分鐘"
-#: ./lib/app/map/_lib/managers/precipitation.dart:398
+#: ./lib/app/map/_lib/managers/precipitation.dart:422
msgid "1 小時"
msgstr "1 小時"
-#: ./lib/app/map/_lib/managers/precipitation.dart:399
+#: ./lib/app/map/_lib/managers/precipitation.dart:423
msgid "3 小時"
msgstr "3 小時"
-#: ./lib/app/map/_lib/managers/precipitation.dart:400
+#: ./lib/app/map/_lib/managers/precipitation.dart:424
msgid "6 小時"
msgstr "6 小時"
-#: ./lib/app/map/_lib/managers/precipitation.dart:401
+#: ./lib/app/map/_lib/managers/precipitation.dart:425
msgid "12 小時"
msgstr "12 小時"
-#: ./lib/app/map/_lib/managers/precipitation.dart:402
+#: ./lib/app/map/_lib/managers/precipitation.dart:426
msgid "24 小時"
msgstr "24 小時"
-#: ./lib/app/map/_lib/managers/precipitation.dart:403
+#: ./lib/app/map/_lib/managers/precipitation.dart:427
msgid "2 天"
msgstr "2 天"
-#: ./lib/app/map/_lib/managers/precipitation.dart:404
+#: ./lib/app/map/_lib/managers/precipitation.dart:428
msgid "3 天"
msgstr "3 天"
-#: ./lib/app/map/_lib/managers/monitor.dart:474
+#: ./lib/app/map/_lib/managers/monitor.dart:495
msgid "海外測站"
msgstr "海外測站"
-#: ./lib/app/map/_lib/managers/monitor.dart:494
+#: ./lib/app/map/_lib/managers/monitor.dart:515
msgid "即時震度:"
msgstr "即時震度:"
-#: ./lib/app/map/_lib/managers/monitor.dart:517
+#: ./lib/app/map/_lib/managers/monitor.dart:538
msgid "地動加速度:"
msgstr "地動加速度:"
-#: ./lib/app/map/_lib/managers/monitor.dart:541
+#: ./lib/app/map/_lib/managers/monitor.dart:562
msgid "地動速度:"
msgstr "地動速度:"
-#: ./lib/app/map/_lib/managers/monitor.dart:1331
+#: ./lib/app/map/_lib/managers/monitor.dart:1346
msgid "規模 M{magnitude},所在地預估{intensity}"
msgstr "規模 M{magnitude},所在地預估{intensity}"
-#: ./lib/app/map/_lib/managers/monitor.dart:1349
+#: ./lib/app/map/_lib/managers/monitor.dart:1362
msgid "{countdown}秒後抵達"
msgstr "{countdown}秒後抵達"
-#: ./lib/app/map/_lib/managers/monitor.dart:1352
+#: ./lib/app/map/_lib/managers/monitor.dart:1365
msgid "已抵達"
msgstr "已抵達"
-#: ./lib/app/map/_lib/managers/monitor.dart:1364
+#: ./lib/app/map/_lib/managers/monitor.dart:1376
msgid "規模 M{magnitude},深度{depth}公里"
msgstr "規模 M{magnitude},深度{depth}公里"
-#: ./lib/app/map/_lib/managers/monitor.dart:1591
+#: ./lib/app/map/_lib/managers/monitor.dart:1594
msgid "目前沒有生效中的地震速報"
msgstr "目前沒有生效中的地震速報"
-#: ./lib/app/map/_lib/managers/report.dart:516
+#: ./lib/app/map/_lib/managers/report.dart:541
msgid "CWA 正在製圖中"
msgstr "CWA 正在製圖中"
-#: ./lib/app/map/_lib/managers/report.dart:639
+#: ./lib/app/map/_lib/managers/report.dart:660
msgid "近期的地震報告"
msgstr "近期的地震報告"
-#: ./lib/app/map/_lib/managers/report.dart:647
+#: ./lib/app/map/_lib/managers/report.dart:667
msgid "更多"
msgstr "更多"
-#: ./lib/app/map/_lib/managers/report.dart:995
+#: ./lib/app/map/_lib/managers/report.dart:994
msgid "編號 {number} 顯著有感地震"
msgstr "編號 {number} 顯著有感地震"
-#: ./lib/app/map/_lib/managers/report.dart:998
+#: ./lib/app/map/_lib/managers/report.dart:997
msgid "小區域有感地震"
msgstr "小區域有感地震"
-#: ./lib/app/map/_lib/managers/report.dart:1083
+#: ./lib/app/map/_lib/managers/report.dart:1080
msgid "地震規模"
msgstr "地震規模"
-#: ./lib/app/map/_lib/managers/report.dart:1110
+#: ./lib/app/map/_lib/managers/report.dart:1107
msgid "震源深度"
msgstr "震源深度"
-#: ./lib/app/map/_lib/managers/report.dart:886
+#: ./lib/app/map/_lib/managers/report.dart:893
msgid "沒有更多資料"
msgstr "沒有更多資料"
-#: ./lib/app/map/_lib/managers/report.dart:1030
+#: ./lib/app/map/_lib/managers/report.dart:1029
msgid "報告頁面"
msgstr "報告頁面"
-#: ./lib/route/report/report_sheet_content.dart:90
+#: ./lib/route/report/report_sheet_content.dart:88
msgid "重播"
msgstr "重播"
-#: ./lib/app/map/_lib/managers/report.dart:1064
+#: ./lib/app/map/_lib/managers/report.dart:1061
msgid "發震時間"
msgstr "發震時間"
-#: ./lib/app/map/_lib/managers/report.dart:1132
+#: ./lib/app/map/_lib/managers/report.dart:1129
msgid "各地震度"
msgstr "各地震度"
-#: ./lib/app/map/_lib/managers/report.dart:1212
+#: ./lib/app/map/_lib/managers/report.dart:1205
msgid "地震報告圖"
msgstr "地震報告圖"
-#: ./lib/app/map/_lib/managers/report.dart:1228
+#: ./lib/app/map/_lib/managers/report.dart:1220
msgid "震度圖"
msgstr "震度圖"
-#: ./lib/app/map/_lib/managers/report.dart:1248
+#: ./lib/app/map/_lib/managers/report.dart:1240
msgid "最大地動加速度圖"
msgstr "最大地動加速度圖"
-#: ./lib/app/map/_lib/managers/report.dart:1268
+#: ./lib/app/map/_lib/managers/report.dart:1260
msgid "最大地動速度圖"
msgstr "最大地動速度圖"
@@ -1477,11 +1505,11 @@ msgstr "氣象相關"
msgid "未知"
msgstr "未知"
-#: ./lib/route/announcement/announcement.dart:105
+#: ./lib/route/announcement/announcement.dart:104
msgid "目前沒有公告"
msgstr "目前沒有公告"
-#: ./lib/route/announcement/announcement.dart:246
+#: ./lib/route/announcement/announcement.dart:245
msgid "公告詳情"
msgstr "公告詳情"
@@ -1497,302 +1525,302 @@ msgstr "已儲存圖片"
msgid "儲存圖片時發生錯誤"
msgstr "儲存圖片時發生錯誤"
-#: ./lib/utils/extensions/number.dart:26
+#: ./lib/utils/extensions/number.dart:25
msgid "0級"
msgstr "0級"
-#: ./lib/utils/extensions/number.dart:27
+#: ./lib/utils/extensions/number.dart:26
msgid "1級"
msgstr "1級"
-#: ./lib/utils/extensions/number.dart:28
+#: ./lib/utils/extensions/number.dart:27
msgid "2級"
msgstr "2級"
-#: ./lib/utils/extensions/number.dart:29
+#: ./lib/utils/extensions/number.dart:28
msgid "3級"
msgstr "3級"
-#: ./lib/utils/extensions/number.dart:30
+#: ./lib/utils/extensions/number.dart:29
msgid "4級"
msgstr "4級"
-#: ./lib/utils/extensions/number.dart:31
+#: ./lib/utils/extensions/number.dart:30
msgid "5弱"
msgstr "5弱"
-#: ./lib/utils/extensions/number.dart:32
+#: ./lib/utils/extensions/number.dart:31
msgid "5強"
msgstr "5強"
-#: ./lib/utils/extensions/number.dart:33
+#: ./lib/utils/extensions/number.dart:32
msgid "6弱"
msgstr "6弱"
-#: ./lib/utils/extensions/number.dart:34
+#: ./lib/utils/extensions/number.dart:33
msgid "6強"
msgstr "6強"
-#: ./lib/utils/extensions/number.dart:35
+#: ./lib/utils/extensions/number.dart:34
msgid "7級"
msgstr "7級"
-#: ./lib/utils/weather_icon.dart:285
+#: ./lib/utils/weather_icon.dart:283
msgid "晴"
msgstr "晴"
-#: ./lib/utils/weather_icon.dart:286
+#: ./lib/utils/weather_icon.dart:284
msgid "晴有霾"
msgstr "晴有霾"
-#: ./lib/utils/weather_icon.dart:287
+#: ./lib/utils/weather_icon.dart:285
msgid "晴有靄"
msgstr "晴有靄"
-#: ./lib/utils/weather_icon.dart:288
+#: ./lib/utils/weather_icon.dart:286
msgid "晴有閃電"
msgstr "晴有閃電"
-#: ./lib/utils/weather_icon.dart:304
+#: ./lib/utils/weather_icon.dart:302
msgid "晴天伴有雷"
msgstr "晴天伴有雷"
-#: ./lib/utils/weather_icon.dart:290
+#: ./lib/utils/weather_icon.dart:288
msgid "晴有霧"
msgstr "晴有霧"
-#: ./lib/utils/weather_icon.dart:291
+#: ./lib/utils/weather_icon.dart:289
msgid "晴有雨"
msgstr "晴有雨"
-#: ./lib/utils/weather_icon.dart:292
+#: ./lib/utils/weather_icon.dart:290
msgid "晴有雨雪"
msgstr "晴有雨雪"
-#: ./lib/utils/weather_icon.dart:293
+#: ./lib/utils/weather_icon.dart:291
msgid "晴有大雪"
msgstr "晴有大雪"
-#: ./lib/utils/weather_icon.dart:294
+#: ./lib/utils/weather_icon.dart:292
msgid "晴有雪珠"
msgstr "晴有雪珠"
-#: ./lib/utils/weather_icon.dart:295
+#: ./lib/utils/weather_icon.dart:293
msgid "晴有冰珠"
msgstr "晴有冰珠"
-#: ./lib/utils/weather_icon.dart:296
+#: ./lib/utils/weather_icon.dart:294
msgid "晴有陣雪"
msgstr "晴有陣雪"
-#: ./lib/utils/weather_icon.dart:297
+#: ./lib/utils/weather_icon.dart:295
msgid "晴陣雨雪"
msgstr "晴陣雨雪"
-#: ./lib/utils/weather_icon.dart:298
+#: ./lib/utils/weather_icon.dart:296
msgid "晴有雹"
msgstr "晴有雹"
-#: ./lib/utils/weather_icon.dart:299
+#: ./lib/utils/weather_icon.dart:297
msgid "晴有雷雨"
msgstr "晴有雷雨"
-#: ./lib/utils/weather_icon.dart:300
+#: ./lib/utils/weather_icon.dart:298
msgid "晴有雷雪"
msgstr "晴有雷雪"
-#: ./lib/utils/weather_icon.dart:301
+#: ./lib/utils/weather_icon.dart:299
msgid "晴有雷雹"
msgstr "晴有雷雹"
-#: ./lib/utils/weather_icon.dart:302
+#: ./lib/utils/weather_icon.dart:300
msgid "晴大雷雨"
msgstr "晴大雷雨"
-#: ./lib/utils/weather_icon.dart:303
+#: ./lib/utils/weather_icon.dart:301
msgid "晴大雷雹"
msgstr "晴大雷雹"
-#: ./lib/utils/weather_icon.dart:305
+#: ./lib/utils/weather_icon.dart:303
msgid "多雲"
msgstr "多雲"
-#: ./lib/utils/weather_icon.dart:306
+#: ./lib/utils/weather_icon.dart:304
msgid "多雲有霾"
msgstr "多雲有霾"
-#: ./lib/utils/weather_icon.dart:307
+#: ./lib/utils/weather_icon.dart:305
msgid "多雲有靄"
msgstr "多雲有靄"
-#: ./lib/utils/weather_icon.dart:308
+#: ./lib/utils/weather_icon.dart:306
msgid "多雲有閃電"
msgstr "多雲有閃電"
-#: ./lib/utils/weather_icon.dart:324
+#: ./lib/utils/weather_icon.dart:322
msgid "多雲伴有雷"
msgstr "多雲伴有雷"
-#: ./lib/utils/weather_icon.dart:310
+#: ./lib/utils/weather_icon.dart:308
msgid "多雲有霧"
msgstr "多雲有霧"
-#: ./lib/utils/weather_icon.dart:311
+#: ./lib/utils/weather_icon.dart:309
msgid "多雲有雨"
msgstr "多雲有雨"
-#: ./lib/utils/weather_icon.dart:312
+#: ./lib/utils/weather_icon.dart:310
msgid "多雲有雨雪"
msgstr "多雲有雨雪"
-#: ./lib/utils/weather_icon.dart:313
+#: ./lib/utils/weather_icon.dart:311
msgid "多雲有大雪"
msgstr "多雲有大雪"
-#: ./lib/utils/weather_icon.dart:314
+#: ./lib/utils/weather_icon.dart:312
msgid "多雲有雪珠"
msgstr "多雲有雪珠"
-#: ./lib/utils/weather_icon.dart:315
+#: ./lib/utils/weather_icon.dart:313
msgid "多雲有冰珠"
msgstr "多雲有冰珠"
-#: ./lib/utils/weather_icon.dart:316
+#: ./lib/utils/weather_icon.dart:314
msgid "多雲有陣雪"
msgstr "多雲有陣雪"
-#: ./lib/utils/weather_icon.dart:317
+#: ./lib/utils/weather_icon.dart:315
msgid "多雲陣雨雪"
msgstr "多雲陣雨雪"
-#: ./lib/utils/weather_icon.dart:318
+#: ./lib/utils/weather_icon.dart:316
msgid "多雲有雹"
msgstr "多雲有雹"
-#: ./lib/utils/weather_icon.dart:319
+#: ./lib/utils/weather_icon.dart:317
msgid "多雲有雷雨"
msgstr "多雲有雷雨"
-#: ./lib/utils/weather_icon.dart:320
+#: ./lib/utils/weather_icon.dart:318
msgid "多雲有雷雪"
msgstr "多雲有雷雪"
-#: ./lib/utils/weather_icon.dart:321
+#: ./lib/utils/weather_icon.dart:319
msgid "多雲有雷雹"
msgstr "多雲有雷雹"
-#: ./lib/utils/weather_icon.dart:322
+#: ./lib/utils/weather_icon.dart:320
msgid "多雲大雷雨"
msgstr "多雲大雷雨"
-#: ./lib/utils/weather_icon.dart:323
+#: ./lib/utils/weather_icon.dart:321
msgid "多雲大雷雹"
msgstr "多雲大雷雹"
-#: ./lib/utils/weather_icon.dart:325
+#: ./lib/utils/weather_icon.dart:323
msgid "陰"
msgstr "陰"
-#: ./lib/utils/weather_icon.dart:326
+#: ./lib/utils/weather_icon.dart:324
msgid "陰有霾"
msgstr "陰有霾"
-#: ./lib/utils/weather_icon.dart:327
+#: ./lib/utils/weather_icon.dart:325
msgid "陰有靄"
msgstr "陰有靄"
-#: ./lib/utils/weather_icon.dart:328
+#: ./lib/utils/weather_icon.dart:326
msgid "陰有閃電"
msgstr "陰有閃電"
-#: ./lib/utils/weather_icon.dart:344
+#: ./lib/utils/weather_icon.dart:342
msgid "陰天伴有雷"
msgstr "陰天伴有雷"
-#: ./lib/utils/weather_icon.dart:330
+#: ./lib/utils/weather_icon.dart:328
msgid "陰有霧"
msgstr "陰有霧"
-#: ./lib/utils/weather_icon.dart:331
+#: ./lib/utils/weather_icon.dart:329
msgid "陰有雨"
msgstr "陰有雨"
-#: ./lib/utils/weather_icon.dart:332
+#: ./lib/utils/weather_icon.dart:330
msgid "陰有雨雪"
msgstr "陰有雨雪"
-#: ./lib/utils/weather_icon.dart:333
+#: ./lib/utils/weather_icon.dart:331
msgid "陰有大雪"
msgstr "陰有大雪"
-#: ./lib/utils/weather_icon.dart:334
+#: ./lib/utils/weather_icon.dart:332
msgid "陰有雪珠"
msgstr "陰有雪珠"
-#: ./lib/utils/weather_icon.dart:335
+#: ./lib/utils/weather_icon.dart:333
msgid "陰有冰珠"
msgstr "陰有冰珠"
-#: ./lib/utils/weather_icon.dart:336
+#: ./lib/utils/weather_icon.dart:334
msgid "陰有陣雪"
msgstr "陰有陣雪"
-#: ./lib/utils/weather_icon.dart:337
+#: ./lib/utils/weather_icon.dart:335
msgid "陰陣雨雪"
msgstr "陰陣雨雪"
-#: ./lib/utils/weather_icon.dart:338
+#: ./lib/utils/weather_icon.dart:336
msgid "陰有雹"
msgstr "陰有雹"
-#: ./lib/utils/weather_icon.dart:339
+#: ./lib/utils/weather_icon.dart:337
msgid "陰有雷雨"
msgstr "陰有雷雨"
-#: ./lib/utils/weather_icon.dart:340
+#: ./lib/utils/weather_icon.dart:338
msgid "陰有雷雪"
msgstr "陰有雷雪"
-#: ./lib/utils/weather_icon.dart:341
+#: ./lib/utils/weather_icon.dart:339
msgid "陰有雷雹"
msgstr "陰有雷雹"
-#: ./lib/utils/weather_icon.dart:342
+#: ./lib/utils/weather_icon.dart:340
msgid "陰大雷雨"
msgstr "陰大雷雨"
-#: ./lib/utils/weather_icon.dart:343
+#: ./lib/utils/weather_icon.dart:341
msgid "陰大雷雹"
msgstr "陰大雷雹"
-#: ./lib/api/model/location/location.dart:85
+#: ./lib/api/model/location/location.dart:84
msgid "{city}{cityLevel} {town}{townLevel}"
msgstr "{city}{cityLevel} {town}{townLevel}"
-#: ./lib/api/model/location/location.dart:98
+#: ./lib/api/model/location/location.dart:97
msgid "{city} {town}"
msgstr "{city} {town}"
-#: ./lib/api/model/location/location.dart:113
+#: ./lib/api/model/location/location.dart:112
msgid "{city}{cityLevel}"
msgstr "{city}{cityLevel}"
-#: ./lib/api/model/location/location.dart:130
+#: ./lib/api/model/location/location.dart:129
msgid "{town}{townLevel}"
msgstr "{town}{townLevel}"
-#: ./lib/widgets/ui/color_picker.dart:363
+#: ./lib/widgets/ui/color_picker.dart:361
msgid "色相"
msgstr "色相"
-#: ./lib/widgets/ui/color_picker.dart:379
+#: ./lib/widgets/ui/color_picker.dart:377
msgid "彩度"
msgstr "彩度"
-#: ./lib/widgets/ui/color_picker.dart:395
+#: ./lib/widgets/ui/color_picker.dart:393
msgid "明度"
msgstr "明度"
-#: ./lib/widgets/ui/color_picker.dart:415
+#: ./lib/widgets/ui/color_picker.dart:413
msgid "十六進位值"
msgstr "十六進位值"
diff --git a/lib/api/endpoints/earthquake.dart b/lib/api/endpoints/earthquake.dart
index b88f605d7..eeeb7f302 100644
--- a/lib/api/endpoints/earthquake.dart
+++ b/lib/api/endpoints/earthquake.dart
@@ -66,7 +66,7 @@ mixin EarthquakeEndpoints {
final eewList = (res.data as List).map(
(e) => Eew.fromMap(e as Map),
);
- if (Preference.experimentalEewAllSource == true) return eewList.toList();
+ if (Preference.experimental__eewAllSource == true) return eewList.toList();
return eewList.where((e) => e.agency == 'cwa').toList();
}
}
diff --git a/lib/app.dart b/lib/app.dart
index f265dd5ac..de20fcd9d 100644
--- a/lib/app.dart
+++ b/lib/app.dart
@@ -28,6 +28,7 @@ import 'main.dart';
class DpipApp extends StatefulWidget {
/// Creates a new [DpipApp] instance.
final String? initialShortcut;
+
const DpipApp({super.key, this.initialShortcut});
@override
@@ -130,11 +131,16 @@ class _DpipAppState extends State with WidgetsBindingObserver {
),
);
+ final cardTheme = CardThemeData(
+ shape: RoundedRectangleBorder(borderRadius: .circular(16)),
+ );
+
ThemeData lightTheme = .new(
colorSchemeSeed: model.themeColor ?? lightDynamic?.primary,
brightness: .light,
snackBarTheme: const .new(behavior: .floating),
pageTransitionsTheme: kZoomPageTransitionsTheme,
+ cardTheme: cardTheme,
switchTheme: switchTheme,
// TODO(kamiya4047): Opt-in to new Material 3 update, remove this after it becomes the default option
sliderTheme: const .new(year2023: false),
@@ -145,32 +151,51 @@ class _DpipAppState extends State with WidgetsBindingObserver {
brightness: .dark,
snackBarTheme: const .new(behavior: .floating),
pageTransitionsTheme: kZoomPageTransitionsTheme,
+ cardTheme: cardTheme,
switchTheme: switchTheme,
// TODO(kamiya4047): Opt-in to new Material 3 update, remove this after it becomes the default option
sliderTheme: const .new(year2023: false),
progressIndicatorTheme: const .new(year2023: false),
);
- final fontTextTheme = switch (I18n.locale.toLanguageTag()) {
- 'zh-Hans' => GoogleFonts.notoSansScTextTheme,
- 'ja' => GoogleFonts.notoSansJpTextTheme,
- 'ko' => GoogleFonts.notoSansKrTextTheme,
- 'vi' => GoogleFonts.notoSansTextTheme,
- 'ru' => GoogleFonts.notoSansTextTheme,
- _ => GoogleFonts.notoSansTcTextTheme,
+ final notoFallback = switch (I18n.locale.toLanguageTag()) {
+ 'zh-Hans' => GoogleFonts.notoSansSc().fontFamily!,
+ 'ja' => GoogleFonts.notoSansJp().fontFamily!,
+ 'ko' => GoogleFonts.notoSansKr().fontFamily!,
+ 'vi' => GoogleFonts.notoSans().fontFamily!,
+ 'ru' => GoogleFonts.notoSans().fontFamily!,
+ _ => GoogleFonts.notoSansTc().fontFamily!,
};
- lightTheme = lightTheme.copyWith(
- textTheme: GoogleFonts.latoTextTheme(
- fontTextTheme(lightTheme.textTheme),
- ),
- );
- darkTheme = darkTheme.copyWith(
- textTheme: GoogleFonts.latoTextTheme(
- fontTextTheme(darkTheme.textTheme),
- ),
+ TextStyle applyFlex(TextStyle? base) {
+ final style = base ?? const TextStyle();
+ return style.copyWith(
+ fontFamily: 'Google Sans Flex',
+ fontFamilyFallback: [...?style.fontFamilyFallback, notoFallback],
+ );
+ }
+
+ TextTheme buildTextTheme(TextTheme base) => base.copyWith(
+ displayLarge: applyFlex(base.displayLarge),
+ displayMedium: applyFlex(base.displayMedium),
+ displaySmall: applyFlex(base.displaySmall),
+ headlineLarge: applyFlex(base.headlineLarge),
+ headlineMedium: applyFlex(base.headlineMedium),
+ headlineSmall: applyFlex(base.headlineSmall),
+ titleLarge: applyFlex(base.titleLarge),
+ titleMedium: applyFlex(base.titleMedium),
+ titleSmall: applyFlex(base.titleSmall),
+ bodyLarge: applyFlex(base.bodyLarge),
+ bodyMedium: applyFlex(base.bodyMedium),
+ bodySmall: applyFlex(base.bodySmall),
+ labelLarge: applyFlex(base.labelLarge),
+ labelMedium: applyFlex(base.labelMedium),
+ labelSmall: applyFlex(base.labelSmall),
);
+ lightTheme = lightTheme.copyWith(textTheme: buildTextTheme(lightTheme.textTheme));
+ darkTheme = darkTheme.copyWith(textTheme: buildTextTheme(darkTheme.textTheme));
+
return MaterialApp.router(
builder: (context, child) {
final mediaQueryData = MediaQuery.of(context);
diff --git a/lib/app/home/page.dart b/lib/app/home/page.dart
index df5fd690d..8daf6622e 100644
--- a/lib/app/home/page.dart
+++ b/lib/app/home/page.dart
@@ -892,7 +892,8 @@ class _HomePageState extends State with WidgetsBindingObserver {
return BlurredIconButton(
icon: const Icon(Symbols.map_rounded),
tooltip: '地圖',
- onPressed: () => MapRoute(layers: layers.map((l) => l.name).join(',')).push(context),
+ onPressed: () =>
+ MapRoute(layers: layers.map((l) => l.name).join(',')).push(context),
elevation: 2,
);
},
diff --git a/lib/app/new_home/_models/home_model.dart b/lib/app/new_home/_models/home_model.dart
new file mode 100644
index 000000000..cdf0ac653
--- /dev/null
+++ b/lib/app/new_home/_models/home_model.dart
@@ -0,0 +1,129 @@
+/// Provider model for the home page weather data and temporary location override.
+library;
+
+import 'dart:async';
+
+import 'package:dpip/api/model/weather_schema.dart';
+import 'package:dpip/global.dart';
+import 'package:dpip/models/settings/location.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+/// Manages weather data and a temporary location override for the home page.
+///
+/// Pass the app-level [SettingsLocationModel] at construction; the model
+/// subscribes to it internally and re-fetches whenever the persisted location
+/// changes (unless a temporary override is active).
+///
+/// Call [setTemporaryCode] to temporarily show weather for a different location.
+/// Call [startAutoRefresh] once on page init to begin a 30-minute refresh cycle.
+class HomeModel extends ChangeNotifier {
+ static const _autoRefreshInterval = Duration(minutes: 30);
+
+ final SettingsLocationModel _settingsLocation;
+ String? _temporaryCode;
+ RealtimeWeather? _weather;
+ bool _isLoading = false;
+ Object? _error;
+ Timer? _autoRefreshTimer;
+
+ /// Creates a [HomeModel] backed by [settingsLocation].
+ ///
+ /// Attaches a listener so the model re-fetches automatically when the
+ /// persisted location changes.
+ HomeModel(this._settingsLocation) {
+ _settingsLocation.addListener(_onSettingsLocationChanged);
+ }
+
+ void _onSettingsLocationChanged() {
+ if (_temporaryCode == null) _doRefresh();
+ }
+
+ Future _doRefresh() async {
+ final code = _temporaryCode ?? _settingsLocation.code;
+ double? lat;
+ double? lon;
+
+ if (code != null) {
+ final loc = Global.location[code];
+ if (loc != null) {
+ lat = loc.lat;
+ lon = loc.lng;
+ }
+ }
+
+ lat ??= _settingsLocation.coordinates?.latitude;
+ lon ??= _settingsLocation.coordinates?.longitude;
+
+ if (lat == null || lon == null) return;
+
+ _isLoading = true;
+ notifyListeners();
+
+ try {
+ _weather = await Global.api.getWeatherRealtimeByCoords(lat, lon);
+ _error = null;
+ } catch (e) {
+ _error = e;
+ } finally {
+ _isLoading = false;
+ notifyListeners();
+ }
+ }
+
+ /// The most recently fetched weather data, or `null` if not yet loaded.
+ RealtimeWeather? get weather => _weather;
+
+ /// Whether a weather fetch is currently in progress.
+ bool get isLoading => _isLoading;
+
+ /// The error from the last failed fetch, or `null` when the last fetch succeeded.
+ Object? get error => _error;
+
+ /// The currently active temporary location code, or `null` when unset.
+ String? get temporaryCode => _temporaryCode;
+
+ /// Temporarily overrides the location to [code] and refreshes weather data.
+ ///
+ /// Pass `null` to clear the override and revert to the persisted location.
+ void setTemporaryCode(String? code) {
+ if (_temporaryCode == code) return;
+ _temporaryCode = code;
+ notifyListeners();
+ _doRefresh();
+ }
+
+ /// Manually triggers a weather data refresh.
+ Future manualRefresh() => _doRefresh();
+
+ /// Starts the 30-minute auto-refresh timer.
+ ///
+ /// Safe to call multiple times; cancels any existing timer first.
+ void startAutoRefresh() {
+ _autoRefreshTimer?.cancel();
+ _autoRefreshTimer = Timer.periodic(_autoRefreshInterval, (_) => _doRefresh());
+ _doRefresh();
+ }
+
+ /// Cancels the auto-refresh timer.
+ void stopAutoRefresh() {
+ _autoRefreshTimer?.cancel();
+ _autoRefreshTimer = null;
+ }
+
+ @override
+ void dispose() {
+ _settingsLocation.removeListener(_onSettingsLocationChanged);
+ stopAutoRefresh();
+ super.dispose();
+ }
+}
+
+/// Extension on [BuildContext] for ergonomic [HomeModel] access.
+extension HomeModelExtension on BuildContext {
+ /// Watches [HomeModel] and rebuilds the calling widget when it notifies.
+ HomeModel get useHome => watch();
+
+ /// Reads [HomeModel] without subscribing to updates.
+ HomeModel get home => read();
+}
diff --git a/lib/app/new_home/_models/weather_params.dart b/lib/app/new_home/_models/weather_params.dart
new file mode 100644
index 000000000..13c40cca6
--- /dev/null
+++ b/lib/app/new_home/_models/weather_params.dart
@@ -0,0 +1,67 @@
+/// Continuous weights derived from realtime weather data, used to drive the
+/// procedural sky background.
+library;
+
+import 'package:dpip/api/model/weather_schema.dart';
+
+/// Resolves a time-of-day scene index for the sky shader.
+///
+/// `0` = day, `1` = night, `2` = dawn, `3` = sunset.
+int resolveSkyScene(int hour) {
+ if (hour < 5 || hour >= 19) return 1;
+ if (hour < 7) return 2;
+ if (hour >= 17) return 3;
+ return 0;
+}
+
+/// Cloud coverage in `[0, 1]`.
+///
+/// Combines weather code (`2xx` partly cloudy = `0.50`, `3xx` overcast = `0.85`)
+/// with a humidity boost above 60% and a small extra weight for cold humid air
+/// (low temperature with high humidity tends to thicken cloud).
+double cloudWeight(RealtimeWeatherData? d) {
+ if (d == null) return 0;
+ final base = switch (d.weatherCode ~/ 100) {
+ 2 => 0.50,
+ 3 => 0.85,
+ _ => 0.0,
+ };
+ final humidityBoost = ((d.humidity - 60).clamp(0, 40) / 40.0) * 0.15;
+ final coldHumid = (d.temperature < 15 && d.humidity > 80) ? 0.10 : 0.0;
+ return (base + humidityBoost + coldHumid).clamp(0.0, 1.0);
+}
+
+/// Rain intensity in `[0, 1]`.
+///
+/// Takes the maximum of two signals: the weather-code precipitation type
+/// (showers, thunderstorm, etc.) and the measured rainfall in mm/h, capped at
+/// 5 mm/h for normalization.
+double rainWeight(RealtimeWeatherData? d) {
+ if (d == null) return 0;
+ final fromCode = switch (d.weatherCode % 100) {
+ 3 || 4 => 0.55,
+ 6 || 11 => 0.45,
+ 7 || 12 => 0.40,
+ 14 || 17 => 0.65,
+ 18 || 19 => 0.85,
+ _ => 0.0,
+ };
+ final fromAmount = (d.rain / 5.0).clamp(0.0, 1.0);
+ return fromCode > fromAmount ? fromCode : fromAmount;
+}
+
+/// Wind strength in `[0, 1]` from the Beaufort scale, normalized at force 8.
+double windWeight(RealtimeWeatherData? d) {
+ if (d == null) return 0.3;
+ return (d.wind.beaufort / 8.0).clamp(0.0, 1.0);
+}
+
+/// Solar phase in `[0, 1]` across the visible window from 5am to 7pm.
+///
+/// `0.0` ≈ sunrise at the eastern edge, `0.5` ≈ noon overhead,
+/// `1.0` ≈ sunset at the western edge. The shader maps this to a horizontal
+/// arc, so the sun is no longer pinned to the screen center.
+double sunPhase(DateTime now) {
+ final h = now.hour + now.minute / 60.0;
+ return ((h - 5.0) / 14.0).clamp(0.0, 1.0);
+}
diff --git a/lib/app/new_home/_widgets/all_observation_average.dart b/lib/app/new_home/_widgets/all_observation_average.dart
new file mode 100644
index 000000000..3dfb489a1
--- /dev/null
+++ b/lib/app/new_home/_widgets/all_observation_average.dart
@@ -0,0 +1,92 @@
+/// A card widget showing the nearest-station temperature and humidity summary.
+library;
+
+import 'package:dpip/app/new_home/_models/home_model.dart';
+import 'package:dpip/utils/extensions/build_context.dart';
+import 'package:dpip/widgets/typography.dart';
+import 'package:flutter/material.dart';
+import 'package:material_symbols_icons/material_symbols_icons.dart';
+import 'package:provider/provider.dart';
+
+/// Displays temperature and humidity from the nearest CWA weather station.
+///
+/// The left column shows TREM network data (currently unavailable in this
+/// context, displayed as "--"). The right column shows CWA nearest-station
+/// readings from [HomeModel]. Rebuilds only when temperature or humidity change.
+class AllObservationAverage extends StatelessWidget {
+ /// Creates an [AllObservationAverage] widget.
+ const AllObservationAverage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Selector(
+ selector: (_, m) {
+ final d = m.weather?.data;
+ return d != null ? (d.temperature, d.humidity) : null;
+ },
+ builder: (context, data, _) {
+ final tremLabel = '--° / --%';
+
+ final cwaLabel = data != null
+ ? '${data.$1.toStringAsFixed(1)}° / ${data.$2.round()}%'
+ : '--° / --%';
+
+ return Padding(
+ padding: const .symmetric(horizontal: 12, vertical: 8),
+ child: Column(
+ crossAxisAlignment: .start,
+ spacing: 4,
+ children: [
+ Padding(
+ padding: const .symmetric(horizontal: 8),
+ child: LabelText.large(
+ '所有測站平均',
+ color: Colors.white,
+ shadows: kElevationToShadow[1],
+ ),
+ ),
+ Card(
+ child: Padding(
+ padding: const .all(12),
+ child: IntrinsicHeight(
+ child: Row(
+ children: [
+ Expanded(
+ child: Row(
+ spacing: 8,
+ children: [
+ Icon(
+ Symbols.cell_tower_rounded,
+ fill: 1,
+ color: context.colors.onSurfaceVariant,
+ ),
+ Text(tremLabel, style: const TextStyle(fontSize: 16)),
+ ],
+ ),
+ ),
+ const VerticalDivider(width: 24),
+ Expanded(
+ child: Row(
+ spacing: 8,
+ children: [
+ Icon(
+ Symbols.globe_asia_rounded,
+ fill: 1,
+ color: context.colors.onSurfaceVariant,
+ ),
+ Text(cwaLabel, style: const TextStyle(fontSize: 16)),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ },
+ );
+ }
+}
diff --git a/lib/app/new_home/_widgets/assistant_hint.dart b/lib/app/new_home/_widgets/assistant_hint.dart
new file mode 100644
index 000000000..c475fb2aa
--- /dev/null
+++ b/lib/app/new_home/_widgets/assistant_hint.dart
@@ -0,0 +1,80 @@
+/// A card widget that shows a contextual weather hint message.
+library;
+
+import 'package:dpip/app/new_home/_models/home_model.dart';
+import 'package:dpip/utils/extensions/build_context.dart';
+import 'package:flutter/material.dart';
+import 'package:material_symbols_icons/material_symbols_icons.dart';
+import 'package:provider/provider.dart';
+
+/// Displays a time- and weather-aware hint message.
+///
+/// Generates a contextual greeting and temperature comment based on the current
+/// hour and the latest weather data. Rebuilds only when the temperature or
+/// weather description changes.
+class AssistantHint extends StatelessWidget {
+ /// Creates an [AssistantHint] widget.
+ const AssistantHint({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Selector(
+ selector: (_, m) {
+ final d = m.weather?.data;
+ return d != null ? (d.temperature, d.weather) : null;
+ },
+ builder: (context, data, _) {
+ final text = data != null ? _buildHintText(data.$1, data.$2) : '載入天氣資料中…';
+
+ return Padding(
+ padding: const .symmetric(horizontal: 12, vertical: 8),
+ child: Card(
+ child: Padding(
+ padding: const .all(12),
+ child: Row(
+ spacing: 8,
+ crossAxisAlignment: .start,
+ children: [
+ Icon(
+ Symbols.auto_awesome_rounded,
+ fill: 1,
+ color: context.colors.onSurfaceVariant,
+ ),
+ Expanded(
+ child: Text(
+ text,
+ style: TextStyle(fontSize: 16, color: context.colors.onSurfaceVariant),
+ maxLines: 2,
+ overflow: .ellipsis,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ },
+ );
+ }
+}
+
+String _buildHintText(double temperature, String weather) {
+ final hour = DateTime.now().hour;
+
+ final greeting = switch (hour) {
+ < 6 => '夜深了',
+ < 12 => '早安',
+ < 18 => '午安',
+ _ => '晚安',
+ };
+
+ final tempComment = switch (temperature) {
+ < 10 => '天氣寒冷,注意保暖。',
+ < 20 => '氣溫涼爽,適合外出。',
+ < 26 => '氣溫舒適,天氣宜人。',
+ < 30 => '氣溫偏高,多補充水分。',
+ _ => '高溫注意,避免長時間戶外活動。',
+ };
+
+ return '$greeting,目前$weather,氣溫 ${temperature.toStringAsFixed(1)}°C。$tempComment';
+}
diff --git a/lib/app/new_home/_widgets/day_cycle.dart b/lib/app/new_home/_widgets/day_cycle.dart
new file mode 100644
index 000000000..1cf23111a
--- /dev/null
+++ b/lib/app/new_home/_widgets/day_cycle.dart
@@ -0,0 +1,284 @@
+/// A card widget displaying the sun's daily arc with sunrise and sunset times.
+library;
+
+import 'dart:math';
+
+import 'package:dpip/utils/extensions/build_context.dart';
+import 'package:dpip/widgets/typography.dart';
+import 'package:flutter/material.dart';
+import 'package:material_symbols_icons/material_symbols_icons.dart';
+
+/// A card that renders the sun's arc from sunrise to sunset.
+///
+/// Shows the full day arc as a track, with the elapsed portion highlighted in
+/// amber up to the sun's current position. Sunrise time appears bottom-left,
+/// sunset time bottom-right.
+class DayCycle extends StatelessWidget {
+ const DayCycle({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const .symmetric(horizontal: 12, vertical: 4),
+ child: Card(
+ child: Padding(
+ padding: const .all(16),
+ child: Column(
+ crossAxisAlignment: .start,
+ spacing: 8,
+ children: [
+ Row(
+ spacing: 4,
+ children: [
+ const Icon(
+ Symbols.wb_twilight_rounded,
+ fill: 1,
+ color: Colors.orangeAccent,
+ ),
+ BodyText.large('日出日落', weight: .bold),
+ ],
+ ),
+ const SizedBox(height: 16),
+ _SunCycleGraph(
+ sunrise: const TimeOfDay(hour: 5, minute: 30),
+ sunset: const TimeOfDay(hour: 18, minute: 30),
+ now: TimeOfDay.now(),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+class _SunCycleGraph extends StatelessWidget {
+ final TimeOfDay now;
+ final TimeOfDay sunrise;
+ final TimeOfDay sunset;
+
+ const _SunCycleGraph({
+ required this.sunrise,
+ required this.sunset,
+ required this.now,
+ });
+
+ String _formatTime(TimeOfDay t) {
+ return '${t.hour}:${t.minute}';
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ children: [
+ SizedBox(
+ height: 140,
+ child: CustomPaint(
+ painter: _SunArcPainter(
+ now: now,
+ sunrise: sunrise,
+ sunset: sunset,
+ primaryColor: context.colors.primary,
+ surfaceVariantColor: context.colors.outlineVariant,
+ ),
+ size: Size.infinite,
+ ),
+ ),
+ const SizedBox(height: 4),
+ Row(
+ mainAxisAlignment: .spaceBetween,
+ children: [
+ Column(
+ crossAxisAlignment: .start,
+ children: [
+ Row(
+ spacing: 4,
+ children: [
+ const Icon(
+ Symbols.sunny_rounded,
+ fill: 1,
+ size: 20,
+ color: Colors.orangeAccent,
+ ),
+ LabelText.medium(
+ '日出',
+ color: context.colors.onSurfaceVariant,
+ ),
+ ],
+ ),
+ BodyText.large(
+ _formatTime(sunrise),
+ weight: .bold,
+ ),
+ ],
+ ),
+ Column(
+ crossAxisAlignment: .end,
+ children: [
+ Row(
+ spacing: 4,
+ children: [
+ const Icon(
+ Symbols.wb_twilight_rounded,
+ fill: 1,
+ size: 20,
+ color: Colors.indigoAccent,
+ ),
+ LabelText.medium(
+ '日落',
+ color: context.colors.onSurfaceVariant,
+ ),
+ ],
+ ),
+ BodyText.large(
+ _formatTime(sunset),
+ weight: .bold,
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ );
+ }
+}
+
+class _SunArcPainter extends CustomPainter {
+ final TimeOfDay now;
+ final Color primaryColor;
+ final TimeOfDay sunrise;
+ final TimeOfDay sunset;
+ final Color surfaceVariantColor;
+
+ const _SunArcPainter({
+ required this.now,
+ required this.sunrise,
+ required this.sunset,
+ required this.primaryColor,
+ required this.surfaceVariantColor,
+ });
+
+ /// Builds a sine arch path from x=0 to x=[maxT/π × width].
+ ///
+ /// [maxT] is the upper bound of the parameter t ∈ [0, π].
+ Path _buildSinePath(double maxT, double width, double cy, double peakHeight) {
+ const steps = 100;
+ final path = Path();
+ for (var i = 0; i <= steps; i++) {
+ final t = (i / steps) * maxT;
+ final x = (t / pi) * width;
+ final y = cy - peakHeight * sin(t);
+ if (i == 0) {
+ path.moveTo(x, y);
+ } else {
+ path.lineTo(x, y);
+ }
+ }
+ return path;
+ }
+
+ double _toMinutes(TimeOfDay t) => t.hour * 60.0 + t.minute;
+
+ @override
+ void paint(Canvas canvas, Size size) {
+ const topPad = 20.0;
+ const bottomPad = 8.0;
+ final width = size.width;
+ final cy = size.height - bottomPad;
+ final peakHeight = cy - topPad;
+
+ final sunriseMin = _toMinutes(sunrise);
+ final sunsetMin = _toMinutes(sunset);
+ final nowMin = _toMinutes(now);
+ final progress = ((nowMin - sunriseMin) / (sunsetMin - sunriseMin)).clamp(0.0, 1.0);
+
+ final fullPath = _buildSinePath(pi, width, cy, peakHeight);
+
+ // Sky gradient: fills the dome under the arc.
+ final skyPath = _buildSinePath(pi, width, cy, peakHeight)
+ ..lineTo(width, cy)
+ ..lineTo(0, cy)
+ ..close();
+ canvas.drawPath(
+ skyPath,
+ Paint()
+ ..shader = LinearGradient(
+ begin: .topCenter,
+ end: .bottomCenter,
+ colors: [
+ primaryColor.withValues(alpha: 0.12),
+ primaryColor.withValues(alpha: 0.0),
+ ],
+ ).createShader(Rect.fromLTWH(0, topPad, width, peakHeight)),
+ );
+
+ // Horizon line.
+ canvas.drawLine(
+ Offset(0, cy),
+ Offset(width, cy),
+ Paint()
+ ..color = surfaceVariantColor
+ ..strokeWidth = 1,
+ );
+
+ // Full arc track (faint).
+ canvas.drawPath(
+ fullPath,
+ Paint()
+ ..color = surfaceVariantColor
+ ..strokeWidth = 2
+ ..style = .stroke
+ ..strokeCap = .round,
+ );
+
+ // Elapsed arc (amber).
+ if (progress > 0) {
+ canvas.drawPath(
+ _buildSinePath(pi * progress, width, cy, peakHeight),
+ Paint()
+ ..color = Colors.amber
+ ..strokeWidth = 3
+ ..style = .stroke
+ ..strokeCap = .round,
+ );
+ }
+
+ // Sun position along the sine curve.
+ final sunOffset = Offset(
+ progress * width,
+ cy - peakHeight * sin(pi * progress),
+ );
+
+ // Glow layers.
+ canvas.drawCircle(sunOffset, 18, Paint()..color = Colors.amber.withValues(alpha: 0.1));
+ canvas.drawCircle(sunOffset, 12, Paint()..color = Colors.amber.withValues(alpha: 0.2));
+
+ // Sun disc.
+ canvas.drawCircle(sunOffset, 7, Paint()..color = Colors.amber);
+ canvas.drawCircle(
+ sunOffset,
+ 7,
+ Paint()
+ ..color = Colors.white.withValues(alpha: 0.45)
+ ..style = .stroke
+ ..strokeWidth = 1.5,
+ );
+
+ // Endpoint dots at the horizon.
+ canvas.drawCircle(Offset(0, cy), 3.5, Paint()..color = Colors.amber.withValues(alpha: 0.7));
+ canvas.drawCircle(
+ Offset(width, cy),
+ 3.5,
+ Paint()..color = surfaceVariantColor.withValues(alpha: 0.7),
+ );
+ }
+
+ @override
+ bool shouldRepaint(_SunArcPainter old) {
+ return old.now != now ||
+ old.sunrise != sunrise ||
+ old.sunset != sunset ||
+ old.primaryColor != primaryColor ||
+ old.surfaceVariantColor != surfaceVariantColor;
+ }
+}
diff --git a/lib/app/new_home/_widgets/greeting.dart b/lib/app/new_home/_widgets/greeting.dart
new file mode 100644
index 000000000..293b2347e
--- /dev/null
+++ b/lib/app/new_home/_widgets/greeting.dart
@@ -0,0 +1,33 @@
+/// A greeting widget that displays a time-aware salutation.
+library;
+
+import 'package:dpip/core/i18n.dart';
+import 'package:dpip/widgets/typography.dart';
+import 'package:flutter/material.dart';
+
+/// Displays a greeting that changes based on the current hour.
+class Greeting extends StatelessWidget {
+ /// Creates a [Greeting] widget.
+ const Greeting({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final hour = DateTime.now().hour;
+
+ final greeting = switch (hour) {
+ < 6 => '夜深了',
+ < 12 => '早安',
+ < 18 => '午安',
+ _ => '晚安',
+ };
+
+ return Padding(
+ padding: const .all(16),
+ child: TitleText.large(
+ greeting.i18n,
+ color: Colors.white,
+ shadows: kElevationToShadow[2],
+ ),
+ );
+ }
+}
diff --git a/lib/app/new_home/_widgets/location_chip.dart b/lib/app/new_home/_widgets/location_chip.dart
new file mode 100644
index 000000000..709e4c89d
--- /dev/null
+++ b/lib/app/new_home/_widgets/location_chip.dart
@@ -0,0 +1,311 @@
+/// A chip widget that displays the current location and supports temporary overrides.
+library;
+
+import 'package:collection/collection.dart';
+import 'package:dpip/api/model/location/location.dart';
+import 'package:dpip/app/new_home/_models/home_model.dart';
+import 'package:dpip/core/i18n.dart';
+import 'package:dpip/global.dart';
+import 'package:dpip/models/settings/location.dart';
+import 'package:dpip/router.dart';
+import 'package:dpip/utils/constants.dart';
+import 'package:dpip/utils/extensions/build_context.dart';
+import 'package:dpip/utils/extensions/color.dart';
+import 'package:dpip/widgets/list/segmented_list.dart';
+import 'package:flutter/material.dart';
+import 'package:material_symbols_icons/material_symbols_icons.dart';
+import 'package:provider/provider.dart';
+
+/// Displays the effective location as a tappable chip.
+///
+/// Tapping opens a two-level city→district picker to set a temporary location
+/// override. A close button appears when an override is active, reverting to
+/// the persisted location on tap.
+class LocationChip extends StatelessWidget {
+ /// Creates a [LocationChip].
+ const LocationChip({super.key});
+
+ void _showLocationPicker(BuildContext context) {
+ final homeModel = context.home;
+ showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ sheetAnimationStyle: kEmphasizedAnimationStyle,
+ constraints: context.bottomSheetConstraints,
+ builder: (_) => _LocationPickerSheet(homeModel),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Selector2(
+ selector: (_, home, settings) => (home.temporaryCode, settings.code),
+ builder: (context, data, _) {
+ final (temporaryCode, settingsCode) = data;
+
+ final code = temporaryCode ?? settingsCode;
+ final hasOverride = code != settingsCode;
+
+ final location = switch (code) {
+ final code? => Global.location[code],
+ _ => null,
+ };
+
+ final displayName = switch (location) {
+ final location? => location.cityTownWithLevel,
+ _ => '未設定',
+ };
+
+ return Padding(
+ padding: const .symmetric(horizontal: 12, vertical: 4),
+ child: Row(
+ children: [
+ FilledButton.icon(
+ onPressed: () => _showLocationPicker(context),
+ icon: const Icon(Symbols.location_on_rounded, fill: 1),
+ label: Text(displayName),
+ style: FilledButton.styleFrom(
+ elevation: hasOverride ? 4 : 0,
+ backgroundColor: hasOverride
+ ? context.colors.primaryContainer
+ : context.colors.surfaceContainerLow / 80,
+ foregroundColor: hasOverride
+ ? context.colors.onPrimaryContainer
+ : context.colors.onSurface,
+ ),
+ ),
+ if (hasOverride)
+ IconButton(
+ onPressed: () => context.home.setTemporaryCode(null),
+ icon: Icon(
+ Symbols.close_rounded,
+ shadows: kElevationToShadow[4],
+ ),
+ tooltip: '清除暫時位置'.i18n,
+ style: IconButton.styleFrom(
+ foregroundColor: Colors.white,
+ ),
+ ),
+ ],
+ ),
+ );
+ },
+ );
+ }
+}
+
+class _LocationPickerSheet extends StatefulWidget {
+ final HomeModel homeModel;
+
+ const _LocationPickerSheet(this.homeModel);
+
+ @override
+ State<_LocationPickerSheet> createState() => _LocationPickerSheetState();
+}
+
+class _LocationPickerSheetState extends State<_LocationPickerSheet> {
+ final _scrollController = ScrollController();
+ String? _selectedCity;
+
+ List<(String cityWithLevel, Location location)> get _cities {
+ final seen = {};
+ return [
+ for (final entry in Global.location.entries)
+ if (seen.add(entry.value.cityWithLevel)) (entry.value.cityWithLevel, entry.value),
+ ];
+ }
+
+ List<(String code, Location location)> get _towns {
+ final city = _selectedCity;
+ if (city == null) return [];
+ return [
+ for (final entry in Global.location.entries)
+ if (entry.value.cityWithLevel == city) (entry.key, entry.value),
+ ];
+ }
+
+ void dismissSheet() => Navigator.of(context).popUntil((route) {
+ if (route.settings is Page) return true;
+ return false;
+ });
+
+ void selectCode(String code) {
+ widget.homeModel.setTemporaryCode(code);
+ dismissSheet();
+ }
+
+ void selectCity(String name) {
+ setState(() => _selectedCity = name);
+ _scrollController.jumpTo(0);
+ }
+
+ Widget _buildCityList() {
+ final cities = _cities;
+ final resolvedCode = widget.homeModel.temporaryCode ?? context.location.code;
+ final resolvedLocation = Global.location[resolvedCode];
+
+ return SegmentedList.builder(
+ label: Text('縣市'.i18n),
+ itemCount: cities.length,
+ itemBuilder: (context, index) {
+ final (cityName, _) = cities[index];
+
+ final isSelected = resolvedLocation?.cityWithLevel == cityName;
+
+ return SegmentedListTile(
+ isFirst: index == 0,
+ isLast: index == cities.length - 1,
+ title: Text(cityName),
+ subtitle: isSelected ? Text('目前選擇地區'.i18n) : null,
+ trailing: const Icon(Symbols.chevron_right_rounded),
+ onTap: () => selectCity(cityName),
+ );
+ },
+ );
+ }
+
+ Widget _buildTownList() {
+ final towns = _towns;
+ final temporaryCode = widget.homeModel.temporaryCode;
+ final currentCode = context.location.code;
+
+ return SegmentedList.builder(
+ label: Text(towns.first.$2.cityWithLevel),
+ itemCount: towns.length,
+ itemBuilder: (context, index) {
+ final (code, location) = towns[index];
+
+ final isCurrentCode = code == currentCode;
+ final isSelected = (temporaryCode == null && isCurrentCode) || code == temporaryCode;
+ final isOverride = temporaryCode != null && !isCurrentCode;
+
+ final lng = location.lng.toStringAsFixed(2);
+ final lat = location.lat.toStringAsFixed(2);
+
+ final backgroundColor = isSelected ? context.colors.secondaryContainer : null;
+ final foregroundColor = isSelected ? context.colors.onSecondaryContainer : null;
+
+ return SegmentedListTile(
+ isFirst: index == 0,
+ isLast: index == towns.length - 1,
+ tileColor: backgroundColor,
+ title: Text(location.cityTownWithLevel),
+ subtitle: Text(
+ '$code・$lng°E・$lat°N',
+ style: TextStyle(color: foregroundColor),
+ ),
+ trailing: Icon(
+ switch ((isSelected, isOverride)) {
+ (true, true) => Symbols.check_rounded,
+ (true, false) => Symbols.home_rounded,
+ _ => null,
+ },
+ fill: 1,
+ color: foregroundColor,
+ ),
+ onTap: () => selectCode(code),
+ );
+ },
+ );
+ }
+
+ Widget _buildQuickLocations() {
+ final temporaryCode = widget.homeModel.temporaryCode;
+
+ return Selector)>(
+ selector: (_, model) => (model.code, model.favorited),
+ builder: (context, data, child) {
+ final (currentCode, favorited) = data;
+
+ final quickCodes = Set();
+
+ if (currentCode != null) quickCodes.add(currentCode);
+ quickCodes.addAll(favorited);
+
+ final quickLocations = quickCodes.mapIndexed((index, code) {
+ final location = Global.location[code]!;
+
+ final isCurrentCode = code == currentCode;
+ final isSelected = (temporaryCode == null && isCurrentCode) || code == temporaryCode;
+ final backgroundColor = isSelected ? context.colors.secondaryContainer : null;
+ final foregroundColor = isSelected ? context.colors.onSecondaryContainer : null;
+
+ return SegmentedListTile(
+ isFirst: index == 0,
+ tileColor: backgroundColor,
+ leading: Icon(
+ isCurrentCode ? Symbols.home_rounded : Symbols.star_rounded,
+ fill: 1,
+ color: foregroundColor,
+ ),
+ title: Text(
+ location.cityTownWithLevel,
+ style: TextStyle(color: foregroundColor),
+ ),
+ trailing: Icon(
+ isSelected ? Symbols.check_rounded : null,
+ color: foregroundColor,
+ ),
+ onTap: () => selectCode(code),
+ );
+ });
+
+ return SegmentedList(
+ label: Text('快速切換'.i18n),
+ children: [
+ ...quickLocations,
+ SegmentedListTile(
+ isFirst: quickLocations.isEmpty,
+ isLast: true,
+ leading: const Icon(Symbols.add_circle_rounded),
+ title: Text('新增地點'.i18n),
+ onTap: () => const SettingsLocationSelectRoute().push(context),
+ ),
+ ],
+ );
+ },
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: Colors.transparent,
+ appBar: AppBar(
+ backgroundColor: Colors.transparent,
+ surfaceTintColor: Colors.transparent,
+ leading: IconButton(
+ icon: Icon(_selectedCity != null ? Symbols.arrow_back_rounded : Symbols.close_rounded),
+ onPressed: () {
+ if (_selectedCity != null) {
+ setState(() => _selectedCity = null);
+ _scrollController.jumpTo(0);
+ } else {
+ dismissSheet();
+ }
+ },
+ tooltip: _selectedCity != null ? '返回' : '關閉',
+ ),
+ title: Text(_selectedCity ?? '選擇地區', style: context.texts.titleMedium),
+ actions: [
+ IconButton(
+ onPressed: () {
+ dismissSheet();
+ const SettingsLocationRoute().push(context);
+ },
+ icon: const Icon(Symbols.settings_rounded, fill: 1),
+ tooltip: '所在地設定',
+ ),
+ ],
+ actionsPadding: const .only(right: 4),
+ ),
+ body: ListView(
+ controller: _scrollController,
+ padding: .only(bottom: context.padding.bottom + 16),
+ children: _selectedCity == null
+ ? [_buildQuickLocations(), _buildCityList()]
+ : [_buildTownList()],
+ ),
+ );
+ }
+}
diff --git a/lib/app/new_home/_widgets/radar.dart b/lib/app/new_home/_widgets/radar.dart
new file mode 100644
index 000000000..feb4b922f
--- /dev/null
+++ b/lib/app/new_home/_widgets/radar.dart
@@ -0,0 +1,216 @@
+/// A home-page card that embeds a read-only radar map preview.
+library;
+
+import 'package:dpip/api/exptech.dart';
+import 'package:dpip/api/route.dart';
+import 'package:dpip/app/new_home/_models/home_model.dart';
+import 'package:dpip/app/map/_lib/utils.dart';
+import 'package:dpip/core/i18n.dart';
+import 'package:dpip/core/providers.dart';
+import 'package:dpip/global.dart';
+import 'package:dpip/router.dart';
+import 'package:dpip/utils/extensions/build_context.dart';
+import 'package:dpip/utils/extensions/maplibre.dart';
+import 'package:dpip/utils/extensions/string.dart';
+import 'package:dpip/utils/log.dart';
+import 'package:dpip/widgets/map/map.dart';
+import 'package:dpip/widgets/typography.dart';
+import 'package:flutter/material.dart';
+import 'package:maplibre_gl/maplibre_gl.dart';
+import 'package:material_symbols_icons/material_symbols_icons.dart';
+
+/// A card that shows the latest precipitation radar imagery on a mini map.
+///
+/// Tapping the map area navigates to the full radar layer on the map page.
+/// Manages the [MapLibreMapController] lifecycle via [RouteAware] and
+/// [WidgetsBindingObserver].
+class Radar extends StatefulWidget {
+ /// Creates a [Radar] card.
+ const Radar({super.key});
+
+ @override
+ State createState() => _RadarState();
+}
+
+class _RadarState extends State with WidgetsBindingObserver, RouteAware {
+ MapLibreMapController? _mapController;
+ bool _homeListenerAdded = false;
+
+ /// Resolves to the list of available radar timestamps once fetched.
+ late Future> _radarListFuture;
+
+ void _onHomeModelChanged() {
+ final code = context.home.temporaryCode ?? GlobalProviders.location.code;
+ final LatLng target;
+ final double zoom;
+
+ if (code != null && Global.location[code] != null) {
+ final loc = Global.location[code]!;
+ target = LatLng(loc.lat, loc.lng);
+ zoom = DpipMap.kUserLocationZoom;
+ } else if (GlobalProviders.location.coordinates != null) {
+ target = GlobalProviders.location.coordinates!;
+ zoom = DpipMap.kUserLocationZoom;
+ } else {
+ target = DpipMap.kTaiwanCenter;
+ zoom = DpipMap.kTaiwanZoom;
+ }
+
+ _mapController?.animateCamera(CameraUpdate.newLatLngZoom(target, zoom));
+ }
+
+ Future _setupMapLayers() async {
+ final controller = _mapController;
+ if (controller == null) return;
+
+ final sourceId = MapSourceIds.radar();
+ final layerId = MapLayerIds.radar();
+
+ try {
+ final time = (await _radarListFuture).last;
+ final tileUrl = radarTile(time);
+
+ if (await controller.exists(sourceId, source: true)) {
+ await controller.removeSource(sourceId);
+ }
+
+ await controller.addSource(
+ sourceId,
+ RasterSourceProperties(tiles: [tileUrl], tileSize: 256),
+ );
+
+ if (!mounted) return;
+
+ if (!await controller.exists(layerId, layer: true)) {
+ await controller.addLayer(
+ sourceId,
+ layerId,
+ const RasterLayerProperties(),
+ belowLayerId: BaseMapLayerIds.exptechCountyOutline,
+ );
+ }
+ } catch (e, s) {
+ TalkerManager.instance.error('Radar._setupMapLayers', e, s);
+ }
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addObserver(this);
+ _radarListFuture = ExpTech().getRadarList();
+ }
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+
+ final route = ModalRoute.of(context);
+ if (route != null) routeObserver.subscribe(this, route);
+
+ if (!_homeListenerAdded) {
+ context.home.addListener(_onHomeModelChanged);
+ _homeListenerAdded = true;
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final userLocation = GlobalProviders.location.coordinates;
+ final targetLocation = userLocation ?? DpipMap.kTaiwanCenter;
+ final targetZoom = userLocation != null ? DpipMap.kUserLocationZoom : DpipMap.kTaiwanZoom;
+
+ return Padding(
+ padding: .symmetric(horizontal: 12, vertical: 4),
+ child: Card(
+ clipBehavior: .antiAlias,
+ child: InkWell(
+ onTap: () => MapRoute(layers: 'radar').push(context),
+ child: Padding(
+ padding: .all(12),
+ child: Column(
+ crossAxisAlignment: .start,
+ spacing: 12,
+ children: [
+ Row(
+ spacing: 4,
+ children: [
+ const Icon(
+ Symbols.radar_rounded,
+ fill: 1,
+ color: Colors.lightBlue,
+ ),
+ BodyText.large('雷達回波'.i18n, weight: .bold),
+ FutureBuilder(
+ future: _radarListFuture,
+ builder: (context, snapshot) {
+ final data = snapshot.data;
+ if (data == null) return const SizedBox.shrink();
+
+ return Container(
+ padding: const .fromLTRB(6, 4, 8, 4),
+ decoration: BoxDecoration(
+ borderRadius: .circular(64),
+ color: context.colors.surfaceContainerHighest,
+ ),
+ child: Row(
+ spacing: 4,
+ mainAxisSize: .min,
+ children: [
+ Icon(
+ Symbols.schedule_rounded,
+ size: 14,
+ color: context.colors.onSurfaceVariant,
+ ),
+ LabelText.medium(
+ data.last.toSimpleDateTimeString(),
+ color: context.colors.onSurfaceVariant,
+ ),
+ ],
+ ),
+ );
+ },
+ ),
+ const Spacer(),
+ const Icon(
+ Symbols.chevron_right_rounded,
+ size: 16,
+ ),
+ ],
+ ),
+ ClipRRect(
+ borderRadius: .circular(12),
+ child: IgnorePointer(
+ child: SizedBox(
+ height: 200,
+ child: DpipMap(
+ initialCameraPosition: CameraPosition(
+ target: targetLocation,
+ zoom: targetZoom,
+ ),
+ onMapCreated: (controller) => _mapController = controller,
+ onStyleLoadedCallback: _setupMapLayers,
+ dragEnabled: false,
+ rotateGesturesEnabled: false,
+ zoomGesturesEnabled: false,
+ focusUserLocationWhenUpdated: false,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ @override
+ void dispose() {
+ context.home.removeListener(_onHomeModelChanged);
+ routeObserver.unsubscribe(this);
+ WidgetsBinding.instance.removeObserver(this);
+ super.dispose();
+ }
+}
diff --git a/lib/app/new_home/_widgets/temperature.dart b/lib/app/new_home/_widgets/temperature.dart
new file mode 100644
index 000000000..c95ee45a4
--- /dev/null
+++ b/lib/app/new_home/_widgets/temperature.dart
@@ -0,0 +1,53 @@
+/// Large temperature display for the home page.
+library;
+
+import 'package:dpip/app/new_home/_models/home_model.dart';
+import 'package:dpip/utils/extensions/build_context.dart';
+import 'package:dpip/widgets/typography.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+/// Displays the current temperature in a large, thin-weight format.
+///
+/// Shows "--" when weather data is unavailable. Rebuilds only when the
+/// temperature value changes.
+class Temperature extends StatelessWidget {
+ /// Creates a [Temperature] widget.
+ const Temperature({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Selector(
+ selector: (_, m) => m.weather?.data.temperature,
+ builder: (context, temp, _) {
+ final tempStr = temp != null ? temp.toStringAsFixed(1) : '--';
+ return Padding(
+ padding: const .symmetric(horizontal: 16),
+ child: Row(
+ mainAxisAlignment: .start,
+ crossAxisAlignment: .start,
+ children: [
+ DisplayText.large(
+ tempStr,
+ color: context.colors.secondaryFixed,
+ fontFamily: 'Google Sans Flex',
+ fontSize: 96,
+ fontVariations: [const .new('ROND', 100)],
+ shadows: kElevationToShadow[4],
+ ),
+ DisplayText.large(
+ '°',
+ color: context.colors.secondaryFixed,
+ fontFamily: 'Google Sans Flex',
+ weight: .w300,
+ fontSize: 96,
+ fontVariations: [const .new('ROND', 100)],
+ shadows: kElevationToShadow[4],
+ ),
+ ],
+ ),
+ );
+ },
+ );
+ }
+}
diff --git a/lib/app/new_home/_widgets/weather.dart b/lib/app/new_home/_widgets/weather.dart
new file mode 100644
index 000000000..63ed3c654
--- /dev/null
+++ b/lib/app/new_home/_widgets/weather.dart
@@ -0,0 +1,74 @@
+/// Weather condition icon and label for the home page.
+library;
+
+import 'package:dpip/app/new_home/_models/home_model.dart';
+import 'package:dpip/widgets/typography.dart';
+import 'package:flutter/material.dart';
+import 'package:material_symbols_icons/material_symbols_icons.dart';
+import 'package:provider/provider.dart';
+
+/// Displays the current weather condition as an icon and description label.
+///
+/// Shows a generic offline icon when weather data is unavailable. Rebuilds only
+/// when the weather description or code changes.
+class Weather extends StatelessWidget {
+ /// Creates a [Weather] widget.
+ const Weather({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Selector(
+ selector: (_, m) {
+ final d = m.weather?.data;
+ return d != null ? (d.weather, d.weatherCode) : null;
+ },
+ builder: (context, data, _) {
+ final icon = data != null ? _weatherIcon(data.$2) : Symbols.cloud_off_rounded;
+ final color = data != null ? _weatherIconColor(data.$2) : Colors.grey;
+ final label = data?.$1 ?? '--';
+
+ return Padding(
+ padding: const .symmetric(horizontal: 16),
+ child: Row(
+ spacing: 8,
+ children: [
+ Icon(
+ icon,
+ fill: 1,
+ color: color,
+ shadows: kElevationToShadow[2],
+ ),
+ BodyText.large(
+ label,
+ fontSize: 20,
+ color: Colors.white,
+ shadows: kElevationToShadow[2],
+ ),
+ ],
+ ),
+ );
+ },
+ );
+ }
+}
+
+IconData _weatherIcon(int code) {
+ if (code >= 1 && code <= 3) return Symbols.clear_day_rounded;
+ if (code >= 4 && code <= 7) return Symbols.partly_cloudy_day_rounded;
+ if (code >= 8 && code <= 14) return Symbols.cloud_rounded;
+ if (code >= 15 && code <= 22) return Symbols.rainy_rounded;
+ if (code >= 23 && code <= 28) return Symbols.rainy_heavy_rounded;
+ if (code >= 29 && code <= 35) return Symbols.thunderstorm_rounded;
+ if (code >= 36 && code <= 41) return Symbols.weather_snowy_rounded;
+ return Symbols.foggy_rounded;
+}
+
+Color _weatherIconColor(int code) {
+ if (code >= 1 && code <= 3) return Colors.orangeAccent;
+ if (code >= 4 && code <= 7) return Colors.amber;
+ if (code >= 8 && code <= 14) return Colors.grey;
+ if (code >= 15 && code <= 28) return Colors.blueAccent;
+ if (code >= 29 && code <= 35) return Colors.yellowAccent;
+ if (code >= 36 && code <= 41) return Colors.lightBlue;
+ return Colors.grey;
+}
diff --git a/lib/app/new_home/_widgets/weather_background.dart b/lib/app/new_home/_widgets/weather_background.dart
new file mode 100644
index 000000000..df2abe177
--- /dev/null
+++ b/lib/app/new_home/_widgets/weather_background.dart
@@ -0,0 +1,179 @@
+/// GPU-accelerated weather sky background driven by a fragment shader.
+library;
+
+import 'dart:ui';
+
+import 'package:dpip/app/new_home/_models/home_model.dart';
+import 'package:dpip/app/new_home/_models/weather_params.dart';
+import 'package:dpip/utils/extensions/build_context.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+typedef _BgParams = ({
+ int scene,
+ double cloud,
+ double rain,
+ double wind,
+ double sunPhase,
+});
+
+/// Full-screen procedurally-generated sky background.
+///
+/// Selects a time-of-day scene and continuous cloud, rain, and wind weights
+/// from [HomeModel], then feeds them to a fragment shader. The [scrollOffset]
+/// drives a layered parallax effect: distant elements drift slowly while
+/// foreground elements drift more.
+class WeatherBackground extends StatefulWidget {
+ /// Current scroll offset of the home page list.
+ final ValueListenable scrollOffset;
+
+ /// Creates a [WeatherBackground] driven by [scrollOffset].
+ const WeatherBackground({required this.scrollOffset, super.key});
+
+ @override
+ State createState() => _WeatherBackgroundState();
+}
+
+class _WeatherBackgroundState extends State with SingleTickerProviderStateMixin {
+ FragmentShader? _shader;
+ late final AnimationController _ticker = AnimationController(
+ duration: const Duration(seconds: 60),
+ vsync: this,
+ )..repeat();
+ final _epoch = DateTime.now().millisecondsSinceEpoch;
+
+ double get _elapsed => (DateTime.now().millisecondsSinceEpoch - _epoch) / 1000.0;
+
+ Future _loadShader() async {
+ try {
+ final program = await FragmentProgram.fromAsset('shaders/weather_sky.frag');
+ if (mounted) setState(() => _shader = program.fragmentShader());
+ } catch (e) {
+ debugPrint('Failed to load weather_sky shader: $e');
+ }
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ _loadShader();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final light = context.theme.brightness == .light ? 1.0 : 0.0;
+
+ return Selector(
+ selector: (_, m) {
+ final d = m.weather?.data;
+ final now = DateTime.now();
+
+ return (
+ scene: resolveSkyScene(now.hour),
+ cloud: cloudWeight(d),
+ rain: rainWeight(d),
+ wind: windWeight(d),
+ sunPhase: sunPhase(now),
+ );
+ },
+ builder: (context, params, _) {
+ if (_shader == null) {
+ return ColoredBox(
+ color: _fallbackColor(params.scene, light),
+ );
+ }
+
+ return AnimatedBuilder(
+ animation: .merge([_ticker, widget.scrollOffset]),
+ builder: (context, _) {
+ return CustomPaint(
+ painter: _SkyShaderPainter(
+ shader: _shader!,
+ scene: params.scene,
+ cloud: params.cloud,
+ rain: params.rain,
+ wind: params.wind,
+ sunPhase: params.sunPhase,
+ light: light,
+ time: _elapsed,
+ scroll: widget.scrollOffset.value,
+ ),
+ size: .infinite,
+ );
+ },
+ );
+ },
+ );
+ }
+
+ @override
+ void dispose() {
+ _ticker.dispose();
+ _shader?.dispose();
+ super.dispose();
+ }
+}
+
+Color _fallbackColor(int scene, double light) {
+ if (scene == 0 && light > 0.5) return const Color(0xFFAFD4F0);
+
+ return switch (scene) {
+ 1 => const Color(0xFF0A1220),
+ 2 => const Color(0xFF5E2455),
+ 3 => const Color(0xFFA0331E),
+ _ => const Color(0xFF1E6FC4),
+ };
+}
+
+class _SkyShaderPainter extends CustomPainter {
+ final double cloud;
+ final double light;
+ final double rain;
+ final int scene;
+ final double scroll;
+ final FragmentShader shader;
+ final double sunPhase;
+ final double time;
+ final double wind;
+
+ const _SkyShaderPainter({
+ required this.shader,
+ required this.scene,
+ required this.cloud,
+ required this.rain,
+ required this.wind,
+ required this.sunPhase,
+ required this.light,
+ required this.time,
+ required this.scroll,
+ });
+
+ @override
+ void paint(Canvas canvas, Size size) {
+ shader
+ ..setFloat(0, time)
+ ..setFloat(1, size.width)
+ ..setFloat(2, size.height)
+ ..setFloat(3, scene.toDouble())
+ ..setFloat(4, scroll)
+ ..setFloat(5, cloud)
+ ..setFloat(6, rain)
+ ..setFloat(7, wind)
+ ..setFloat(8, sunPhase)
+ ..setFloat(9, light);
+
+ canvas.drawRect(Offset.zero & size, Paint()..shader = shader);
+ }
+
+ @override
+ bool shouldRepaint(_SkyShaderPainter old) =>
+ old.scene != scene ||
+ old.time != time ||
+ old.scroll != scroll ||
+ old.cloud != cloud ||
+ old.rain != rain ||
+ old.wind != wind ||
+ old.sunPhase != sunPhase ||
+ old.light != light;
+}
diff --git a/lib/app/new_home/_widgets/weather_parameters.dart b/lib/app/new_home/_widgets/weather_parameters.dart
new file mode 100644
index 000000000..679c60d5d
--- /dev/null
+++ b/lib/app/new_home/_widgets/weather_parameters.dart
@@ -0,0 +1,125 @@
+/// A grid of weather parameter cards for the home page.
+library;
+
+import 'package:dpip/api/model/weather_schema.dart';
+import 'package:dpip/app/new_home/_models/home_model.dart';
+import 'package:dpip/core/i18n.dart';
+import 'package:dpip/utils/extensions/build_context.dart';
+import 'package:dpip/widgets/typography.dart';
+import 'package:flutter/material.dart';
+import 'package:material_symbols_icons/material_symbols_icons.dart';
+import 'package:provider/provider.dart';
+
+typedef _Params = ({
+ double? humidity,
+ RealtimeWeatherWind? wind,
+ double? rain,
+});
+
+/// A 2×2 grid of cards showing humidity, air quality, wind, and rainfall.
+///
+/// Reads values from [HomeModel] and rebuilds only when the relevant weather
+/// parameters change.
+class WeatherParameters extends StatelessWidget {
+ /// Creates a [WeatherParameters] widget.
+ const WeatherParameters({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Selector(
+ selector: (_, m) {
+ final d = m.weather?.data;
+ return (
+ humidity: d?.humidity,
+ wind: d?.wind,
+ rain: d?.rain,
+ );
+ },
+ builder: (context, params, _) {
+ final (:humidity, :wind, :rain) = params;
+
+ return GridView.count(
+ crossAxisCount: 2,
+ childAspectRatio: 7 / 5,
+ mainAxisSpacing: 8,
+ crossAxisSpacing: 8,
+ padding: const .symmetric(horizontal: 12, vertical: 4),
+ physics: const NeverScrollableScrollPhysics(),
+ shrinkWrap: true,
+ children: [
+ _ParameterCard(
+ icon: const Icon(Symbols.water_drop_rounded, fill: 1, color: Colors.blueAccent),
+ label: Text('相對溼度'.i18n),
+ value: humidity != null ? '${humidity.round()}%' : '--',
+ ),
+ _ParameterCard(
+ icon: const Icon(Symbols.mist_rounded, fill: 1, color: Colors.grey),
+ label: Text('空氣品質'.i18n),
+ value: '--',
+ ),
+ _ParameterCard(
+ icon: const Icon(Symbols.air_rounded, fill: 1, color: Colors.lightBlue),
+ label: Text('風向/風速'.i18n),
+ value: wind != null && wind.direction.isNotEmpty ? wind.direction : '--',
+ footer: wind != null ? Text('${wind.speed.toStringAsFixed(1)} m/s') : null,
+ ),
+ _ParameterCard(
+ icon: const Icon(Symbols.umbrella_rounded, fill: 1, color: Colors.indigoAccent),
+ label: Text('降水量'.i18n),
+ value: rain != null ? '${rain.toStringAsFixed(1)} mm' : '--',
+ ),
+ ],
+ );
+ },
+ );
+ }
+}
+
+class _ParameterCard extends StatelessWidget {
+ final Icon icon;
+ final Widget label;
+ final String value;
+ final Widget? footer;
+
+ const _ParameterCard({
+ required this.icon,
+ required this.label,
+ required this.value,
+ this.footer,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Card(
+ child: Padding(
+ padding: const .all(16),
+ child: Column(
+ crossAxisAlignment: .start,
+ spacing: 4,
+ children: [
+ Row(
+ spacing: 4,
+ children: [
+ icon,
+ DefaultTextStyle(
+ style: context.texts.bodyMedium!.copyWith(
+ color: context.colors.onSurfaceVariant,
+ ),
+ child: label,
+ ),
+ ],
+ ),
+ HeadLineText.medium(value, weight: .bold),
+ if (footer != null)
+ DefaultTextStyle(
+ style: context.texts.bodyLarge!.copyWith(
+ color: context.colors.onSurfaceVariant,
+ ),
+ child: footer!,
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/app/new_home/layout.dart b/lib/app/new_home/layout.dart
new file mode 100644
index 000000000..726fceb29
--- /dev/null
+++ b/lib/app/new_home/layout.dart
@@ -0,0 +1,110 @@
+import 'package:dpip/core/i18n.dart';
+import 'package:dpip/router.dart';
+import 'package:dpip/utils/extensions/build_context.dart';
+import 'package:dpip/utils/extensions/color.dart';
+import 'package:flutter/material.dart';
+import 'package:m3e_collection/m3e_collection.dart';
+import 'package:material_symbols_icons/material_symbols_icons.dart';
+
+class NewHomeLayout extends StatefulWidget {
+ final Widget child;
+
+ const NewHomeLayout({required this.child, super.key});
+
+ @override
+ State createState() => _NewHomeLayoutState();
+}
+
+class _NewHomeLayoutState extends State with TickerProviderStateMixin {
+ late final _scrollAnimator = AnimationController(vsync: this);
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Stack(
+ children: [
+ Positioned.fill(
+ child: widget.child,
+ ),
+ Positioned(
+ top: 0,
+ left: 0,
+ right: 0,
+ height: context.padding.top + 8,
+ child: Container(
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: .topCenter,
+ end: .bottomCenter,
+ colors: [
+ context.colors.surface / 40,
+ context.colors.surface / 0,
+ ],
+ ),
+ ),
+ ),
+ ),
+ Positioned(
+ top: context.padding.top + 8,
+ right: 8,
+ child: Row(
+ mainAxisSize: .min,
+ mainAxisAlignment: .end,
+ children: [
+ IconButton.filledTonal(
+ onPressed: () {},
+ icon: const Icon(
+ Symbols.notifications_rounded,
+ fill: 1,
+ ),
+ style: IconButton.styleFrom(
+ elevation: 4,
+ ),
+ ),
+ IconButton.filledTonal(
+ onPressed: () => const SettingsIndexRoute().push(context),
+ icon: const Icon(
+ Symbols.settings_rounded,
+ fill: 1,
+ ),
+ style: IconButton.styleFrom(
+ elevation: 4,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ extendBody: true,
+ bottomNavigationBar: NavigationBarM3E(
+ elevation: 2,
+ padding: const .symmetric(horizontal: 72, vertical: 8),
+ density: .compact,
+ destinations: [
+ NavigationDestinationM3E(
+ icon: const Icon(Symbols.home_rounded),
+ selectedIcon: const Icon(Symbols.home_rounded, fill: 1),
+ label: '首頁'.i18n,
+ ),
+ NavigationDestinationM3E(
+ icon: const Icon(Symbols.map_rounded),
+ selectedIcon: const Icon(Symbols.map_rounded, fill: 1),
+ label: '地圖'.i18n,
+ ),
+ NavigationDestinationM3E(
+ icon: const Icon(Symbols.category_rounded),
+ selectedIcon: const Icon(Symbols.category_rounded, fill: 1),
+ label: '小工具'.i18n,
+ ),
+ ],
+ ),
+ );
+ }
+
+ @override
+ void dispose() {
+ _scrollAnimator.dispose();
+ super.dispose();
+ }
+}
diff --git a/lib/app/new_home/page.dart b/lib/app/new_home/page.dart
new file mode 100644
index 000000000..ed2285d1f
--- /dev/null
+++ b/lib/app/new_home/page.dart
@@ -0,0 +1,138 @@
+/// The new home page providing weather data via [HomeModel].
+library;
+
+import 'package:dpip/app/new_home/_models/home_model.dart';
+import 'package:dpip/app/new_home/_models/weather_params.dart';
+import 'package:dpip/app/new_home/_widgets/all_observation_average.dart';
+import 'package:dpip/app/new_home/_widgets/assistant_hint.dart';
+import 'package:dpip/app/new_home/_widgets/day_cycle.dart';
+import 'package:dpip/app/new_home/_widgets/greeting.dart';
+import 'package:dpip/app/new_home/_widgets/location_chip.dart';
+import 'package:dpip/app/new_home/_widgets/radar.dart';
+import 'package:dpip/app/new_home/_widgets/temperature.dart';
+import 'package:dpip/app/new_home/_widgets/weather.dart';
+import 'package:dpip/app/new_home/_widgets/weather_background.dart';
+import 'package:dpip/app/new_home/_widgets/weather_parameters.dart';
+import 'package:dpip/models/settings/location.dart';
+import 'package:dpip/utils/extensions/build_context.dart';
+import 'package:dpip/utils/extensions/color.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+/// The main home page widget.
+///
+/// Lazily creates a [HomeModel] on first dependency resolution, provides it to
+/// all child widgets, supports pull-to-refresh, and automatically refreshes
+/// weather data every 30 minutes.
+class NewHomePage extends StatefulWidget {
+ /// Creates a [NewHomePage].
+ const NewHomePage({super.key});
+
+ @override
+ State createState() => _NewHomePageState();
+}
+
+class _NewHomePageState extends State {
+ final _scrollOffset = ValueNotifier(0);
+ final _scrollController = ScrollController();
+ HomeModel? _homeModel;
+
+ void _onScroll() => _scrollOffset.value = _scrollController.offset;
+
+ @override
+ void initState() {
+ super.initState();
+ _scrollController.addListener(_onScroll);
+ }
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ _homeModel ??= HomeModel(context.read())..startAutoRefresh();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final homeModel = _homeModel!;
+
+ return ChangeNotifierProvider.value(
+ value: homeModel,
+ child: Selector(
+ selector: (_, m) {
+ final d = m.weather?.data;
+ return (
+ scene: resolveSkyScene(DateTime.now().hour),
+ cloud: cloudWeight(d),
+ rain: rainWeight(d),
+ );
+ },
+ builder: (context, params, _) {
+ final colorScheme = ColorScheme.fromSeed(
+ seedColor: _seedColor(params.scene, params.cloud, params.rain),
+ brightness: context.theme.brightness,
+ );
+
+ return AnimatedTheme(
+ duration: const Duration(milliseconds: 600),
+ data: context.theme.copyWith(
+ colorScheme: colorScheme,
+ cardTheme: CardThemeData(
+ color: colorScheme.surface / 95,
+ ),
+ ),
+ child: Stack(
+ children: [
+ Positioned.fill(child: WeatherBackground(scrollOffset: _scrollOffset)),
+ RefreshIndicator(
+ onRefresh: homeModel.manualRefresh,
+ child: ListView(
+ controller: _scrollController,
+ children: const [
+ Greeting(),
+ LocationChip(),
+ SizedBox(height: 16),
+ Temperature(),
+ Weather(),
+ SizedBox(height: 16),
+ AssistantHint(),
+ AllObservationAverage(),
+ WeatherParameters(),
+ DayCycle(),
+ Radar(),
+ ],
+ ),
+ ),
+ ],
+ ),
+ );
+ },
+ ),
+ );
+ }
+
+ @override
+ void dispose() {
+ _scrollController
+ ..removeListener(_onScroll)
+ ..dispose();
+ _scrollOffset.dispose();
+ _homeModel?.dispose();
+ super.dispose();
+ }
+}
+
+Color _seedColor(int scene, double cloud, double rain) {
+ final base = switch (scene) {
+ 1 => const Color(0xFF1A237E),
+ 2 => const Color(0xFF6A1B9A),
+ 3 => const Color(0xFFC62828),
+ _ => const Color(0xFF1565C0),
+ };
+ if (rain > 0.4) {
+ return Color.lerp(base, const Color(0xFF263238), ((rain - 0.4) * 1.2).clamp(0.0, 0.7))!;
+ }
+ if (cloud > 0.5) {
+ return Color.lerp(base, const Color(0xFF546E7A), (cloud - 0.5) * 0.5)!;
+ }
+ return base;
+}
diff --git a/lib/app/settings/donate/page.dart b/lib/app/settings/donate/page.dart
index 932344b13..4d48a5bf0 100644
--- a/lib/app/settings/donate/page.dart
+++ b/lib/app/settings/donate/page.dart
@@ -7,6 +7,7 @@ import 'dart:io';
import 'package:collection/collection.dart';
import 'package:dpip/core/i18n.dart';
import 'package:dpip/utils/extensions/build_context.dart';
+import 'package:dpip/utils/extensions/color.dart';
import 'package:dpip/utils/extensions/product_detail.dart';
import 'package:dpip/utils/functions.dart';
import 'package:flutter/material.dart';
@@ -115,8 +116,8 @@ class _SettingsDonatePageState extends State
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
- context.colors.primaryContainer.withOpacity(0.5),
- context.colors.tertiaryContainer.withOpacity(0.5),
+ context.colors.primaryContainer / 0.5,
+ context.colors.tertiaryContainer / 0.5,
],
begin: .topLeft,
end: .bottomRight,
diff --git a/lib/app/settings/experimental/page.dart b/lib/app/settings/experimental/page.dart
index a9279db2f..2ddff00c4 100644
--- a/lib/app/settings/experimental/page.dart
+++ b/lib/app/settings/experimental/page.dart
@@ -1,133 +1,24 @@
/// Experimental features settings page.
library;
-import 'dart:async';
-
+import 'package:dpip/app/settings/_widgets/settings_header.dart';
import 'package:dpip/core/i18n.dart';
-import 'package:dpip/core/preference.dart';
+import 'package:dpip/models/settings/experimental.dart';
import 'package:dpip/utils/extensions/build_context.dart';
import 'package:dpip/widgets/list/segmented_list.dart';
+import 'package:dpip/widgets/ui/icon_container.dart';
import 'package:flutter/material.dart';
-import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart';
+import 'package:provider/provider.dart';
/// A page for toggling experimental (in-development) features.
///
/// Each feature shows a confirmation dialog with a countdown before it can be
/// enabled. Disabled features can be turned off immediately.
-class SettingsExperimentalPage extends StatefulWidget {
+class SettingsExperimentalPage extends StatelessWidget {
/// Creates a [SettingsExperimentalPage].
const SettingsExperimentalPage({super.key});
- @override
- State createState() => _SettingsExperimentalPageState();
-}
-
-class _SettingsExperimentalPageState extends State {
- bool _launchToMonitor = Preference.experimentalLaunchToMonitor ?? false;
- bool _eewAllSource = Preference.experimentalEewAllSource ?? false;
-
- Future _showEnableWarningDialog({
- required String featureName,
- required VoidCallback onConfirm,
- }) async {
- final confirmed = await showDialog(
- context: context,
- barrierDismissible: false,
- builder: (context) => _ExperimentalWarningDialog(featureName: featureName),
- );
-
- if (confirmed == true) {
- onConfirm();
- }
- }
-
- void _toggleEewAllSource(bool value) {
- if (value) {
- _showEnableWarningDialog(
- featureName: '地震速報不限制非 CWA 來源'.i18n,
- onConfirm: () {
- setState(() => _eewAllSource = true);
- Preference.experimentalEewAllSource = true;
- },
- );
- } else {
- setState(() => _eewAllSource = false);
- Preference.experimentalEewAllSource = false;
- }
- }
-
- void _toggleLaunchToMonitor(bool value) {
- if (value) {
- _showEnableWarningDialog(
- featureName: '啟動時進入強震監視器'.i18n,
- onConfirm: () {
- setState(() => _launchToMonitor = true);
- Preference.experimentalLaunchToMonitor = true;
- },
- );
- } else {
- setState(() => _launchToMonitor = false);
- Preference.experimentalLaunchToMonitor = false;
- }
- }
-
- Widget _buildHeader(BuildContext context) {
- return Padding(
- padding: const .fromLTRB(16, 8, 16, 0),
- child: Row(
- children: [
- Container(
- padding: const .all(10),
- decoration: BoxDecoration(
- color: context.colors.primaryContainer,
- borderRadius: .circular(12),
- ),
- child: Icon(
- Symbols.science_rounded,
- color: context.colors.onPrimaryContainer,
- size: 24,
- ),
- ),
- const SizedBox(width: 12),
- Expanded(
- child: Column(
- crossAxisAlignment: .start,
- children: [
- Text(
- '實驗性功能'.i18n,
- style: context.texts.titleLarge?.copyWith(
- fontWeight: .bold,
- ),
- ),
- Text(
- '搶先體驗開發中的新功能'.i18n,
- style: context.texts.bodySmall?.copyWith(
- color: context.colors.onSurfaceVariant,
- ),
- ),
- ],
- ),
- ),
- ],
- ),
- );
- }
-
- Widget _buildIconContainer({
- required IconData icon,
- required Color color,
- }) {
- return Container(
- padding: const .all(8),
- decoration: BoxDecoration(
- color: color.withValues(alpha: 0.15),
- borderRadius: .circular(10),
- ),
- child: Icon(icon, color: color, size: 20),
- );
- }
-
Widget _buildWarningCard(BuildContext context) {
return Container(
margin: const .fromLTRB(16, 16, 16, 0),
@@ -139,17 +30,9 @@ class _SettingsExperimentalPageState extends State {
child: Row(
crossAxisAlignment: .start,
children: [
- Container(
- padding: const .all(10),
- decoration: BoxDecoration(
- color: Colors.amber.withValues(alpha: 0.2),
- borderRadius: .circular(12),
- ),
- child: Icon(
- Symbols.warning_rounded,
- color: Colors.amber[700],
- size: 24,
- ),
+ const ContainedIcon(
+ Symbols.warning_rounded,
+ color: Colors.amberAccent,
),
const SizedBox(width: 16),
Expanded(
@@ -181,165 +64,72 @@ class _SettingsExperimentalPageState extends State {
@override
Widget build(BuildContext context) {
return ListView(
- padding: .only(
- top: 8,
- bottom: 16 + context.padding.bottom,
- ),
children: [
- _buildHeader(context),
- _buildWarningCard(context),
-
- // 啟動行為
- SegmentedList(
- label: Text('啟動行為'.i18n),
- children: [
- SegmentedListTile(
- isFirst: true,
- isLast: true,
- leading: _buildIconContainer(
- icon: Symbols.monitor_heart_rounded,
- color: Colors.red,
- ),
- title: Text('啟動時進入強震監視器'.i18n),
- subtitle: Text('開啟 App 時直接進入強震監視器地圖'.i18n),
- trailing: Switch(
- value: _launchToMonitor,
- onChanged: _toggleLaunchToMonitor,
- ),
- onTap: () => _toggleLaunchToMonitor(!_launchToMonitor),
- ),
- ],
+ SettingsHeader(
+ icon: Symbols.science_rounded,
+ title: Text('實驗性功能'.i18n),
+ subtitle: Text('搶先體驗開發中的新功能'.i18n),
),
-
+ _buildWarningCard(context),
+ const SizedBox(height: 16),
SegmentedList(
- label: Text('地震速報'.i18n),
children: [
- SegmentedListTile(
- isFirst: true,
- isLast: true,
- leading: _buildIconContainer(
- icon: Symbols.earthquake_rounded,
- color: Colors.orange,
- ),
- title: Text('不限制非 CWA 來源'.i18n),
- subtitle: Text('顯示所有來源的地震速報資料'.i18n),
- trailing: Switch(
- value: _eewAllSource,
- onChanged: _toggleEewAllSource,
- ),
- onTap: () => _toggleEewAllSource(!_eewAllSource),
- ),
- ],
- ),
- ],
- );
- }
-}
-
-/// A confirmation dialog with a countdown timer before the confirm button
-/// becomes enabled.
-class _ExperimentalWarningDialog extends StatefulWidget {
- /// The display name of the experimental feature being enabled.
- final String featureName;
-
- const _ExperimentalWarningDialog({required this.featureName});
-
- @override
- State<_ExperimentalWarningDialog> createState() => _ExperimentalWarningDialogState();
-}
-
-class _ExperimentalWarningDialogState extends State<_ExperimentalWarningDialog> {
- int _countdown = 5;
- Timer? _timer;
-
- void _startCountdown() {
- _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
- if (_countdown > 0) {
- setState(() => _countdown--);
- } else {
- timer.cancel();
- }
- });
- }
-
- @override
- void initState() {
- super.initState();
- _startCountdown();
- }
-
- @override
- Widget build(BuildContext context) {
- final canConfirm = _countdown == 0;
-
- return AlertDialog(
- icon: Icon(
- Symbols.warning_rounded,
- color: Colors.amber[700],
- size: 48,
- ),
- title: Text('啟用實驗性功能'.i18n),
- content: Column(
- mainAxisSize: .min,
- crossAxisAlignment: .start,
- children: [
- Text(
- '你即將啟用:'.i18n,
- style: context.texts.bodyMedium,
- ),
- const SizedBox(height: 8),
- Container(
- padding: const .all(12),
- decoration: BoxDecoration(
- color: context.colors.surfaceContainerHighest,
- borderRadius: .circular(8),
+ Selector(
+ selector: (_, model) => model.experimental__launchToMonitor,
+ builder: (context, experimental__launchToMonitor, child) {
+ return SegmentedListTile(
+ isFirst: true,
+ leading: const ContainedIcon(
+ Symbols.monitor_heart_rounded,
+ color: Colors.red,
+ ),
+ title: Text('啟動時進入強震監視器'.i18n),
+ subtitle: Text('開啟 App 時直接進入強震監視器地圖'.i18n),
+ trailing: Switch(
+ value: experimental__launchToMonitor,
+ onChanged: context.experimental.set_experimental__launchToMonitor,
+ ),
+ );
+ },
),
- child: Row(
- children: [
- Icon(
- Symbols.science_rounded,
- color: context.colors.primary,
- size: 20,
- ),
- const SizedBox(width: 8),
- Expanded(
- child: Text(
- widget.featureName,
- style: context.texts.bodyMedium?.copyWith(
- fontWeight: .bold,
- ),
+ Selector(
+ selector: (_, model) => model.experimental__eewAllSource,
+ builder: (context, experimental__eewAllSource, child) {
+ return SegmentedListTile(
+ leading: const ContainedIcon(
+ Symbols.earthquake_rounded,
+ color: Colors.orange,
),
- ),
- ],
+ title: Text('不限制非 CWA 來源'.i18n),
+ subtitle: Text('顯示所有來源的地震速報資料'.i18n),
+ trailing: Switch(
+ value: experimental__eewAllSource,
+ onChanged: context.experimental.set_experimental__eewAllSource,
+ ),
+ );
+ },
),
- ),
- const SizedBox(height: 16),
- Text(
- '此功能為實驗性質,可能會造成應用程式不穩定或行為異常。如遇問題,請至設定中關閉此功能。'.i18n,
- style: context.texts.bodySmall?.copyWith(
- color: context.colors.onSurfaceVariant,
+ Selector(
+ selector: (_, model) => model.experimental__newHomeScreen,
+ builder: (context, experimental__newHomeScreen, child) {
+ return SegmentedListTile(
+ isLast: true,
+ leading: const ContainedIcon(
+ Symbols.home_rounded,
+ color: Colors.blueAccent,
+ ),
+ title: Text('新首頁樣式'.i18n),
+ subtitle: Text('使用新的首頁,目前還在開發中\n需要重新啟動 DPIP 來套用設定'.i18n),
+ trailing: Switch(
+ value: experimental__newHomeScreen,
+ onChanged: context.experimental.set_experimental__newHomeScreen,
+ ),
+ );
+ },
),
- ),
- ],
- ),
- actions: [
- TextButton(
- onPressed: () => context.pop(false),
- child: Text('取消'.i18n),
- ),
- FilledButton(
- onPressed: canConfirm ? () => context.pop(true) : null,
- child: Text(
- canConfirm ? '啟用'.i18n : '${_countdown}s',
- ),
+ ],
),
],
);
}
-
- @override
- void dispose() {
- _timer?.cancel();
- super.dispose();
- }
}
diff --git a/lib/app/settings/layout/page.dart b/lib/app/settings/layout/page.dart
index bff0eea7d..9a73fba37 100644
--- a/lib/app/settings/layout/page.dart
+++ b/lib/app/settings/layout/page.dart
@@ -200,7 +200,7 @@ class SettingsLayoutPage extends StatelessWidget {
padding: const .symmetric(horizontal: 16),
sliver: SliverReorderableList(
itemCount: sections.length,
- onReorderItem: (oldIndex, newIndex) {
+ onReorder: (oldIndex, newIndex) {
context.userInterface.reorderSection(oldIndex, newIndex);
},
itemBuilder: (context, index) {
diff --git a/lib/app/settings/theme/page.dart b/lib/app/settings/theme/page.dart
index add31711f..f1177754a 100644
--- a/lib/app/settings/theme/page.dart
+++ b/lib/app/settings/theme/page.dart
@@ -23,64 +23,60 @@ class SettingsThemePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return Consumer(
- builder: (context, model, child) {
- return ListView(
+ return ListView(
+ children: [
+ SettingsHeader(
+ icon: Symbols.palette_rounded,
+ title: Text('主題'.i18n),
+ subtitle: Text('調整 DPIP 整體的外觀與顏色'.i18n),
+ ),
+ const SizedBox(height: 16),
+ SegmentedList(
children: [
- SettingsHeader(
- icon: Symbols.palette_rounded,
- title: Text('主題'.i18n),
- subtitle: Text('調整 DPIP 整體的外觀與顏色'.i18n),
+ Selector(
+ selector: (context, model) => model.themeMode,
+ builder: (context, themeMode, child) {
+ return SegmentedListTile(
+ isFirst: true,
+ leading: ContainedIcon(
+ switch (context.theme.brightness) {
+ .light => Symbols.light_mode_rounded,
+ .dark => Symbols.dark_mode_rounded,
+ },
+ color: switch (context.theme.brightness) {
+ .light => Colors.orange,
+ .dark => Colors.blue[300]!,
+ },
+ ),
+ title: Text('主題模式'.i18n),
+ subtitle: Text(themeMode.label.i18n),
+ trailing: const Icon(Symbols.chevron_right_rounded),
+ onTap: () => const SettingsThemeModeRoute().push(context),
+ );
+ },
),
- const SizedBox(height: 16),
- SegmentedList(
- children: [
- Selector(
- selector: (context, model) => model.themeMode,
- builder: (context, themeMode, child) {
- return SegmentedListTile(
- isFirst: true,
- leading: ContainedIcon(
- switch (context.theme.brightness) {
- .light => Symbols.light_mode_rounded,
- .dark => Symbols.dark_mode_rounded,
- },
- color: switch (context.theme.brightness) {
- .light => Colors.orange,
- .dark => Colors.blue[300]!,
- },
- ),
- title: Text('主題模式'.i18n),
- subtitle: Text(themeMode.label.i18n),
- trailing: const Icon(Symbols.chevron_right_rounded),
- onTap: () => const SettingsThemeModeRoute().push(context),
- );
- },
- ),
- Selector(
- selector: (context, model) => model.themeColor,
- builder: (context, themeColor, child) {
- return SegmentedListTile(
- isLast: true,
- leading: ContainedIcon(
- Symbols.colorize_rounded,
- color: themeColor ?? context.colors.primary,
- ),
- title: Text('主題色彩'.i18n),
- subtitle: Text(switch (themeColor) {
- null => '使用系統配色'.i18n,
- final v => ColorTools.nameThatColor(v),
- }),
- trailing: const Icon(Symbols.chevron_right_rounded),
- onTap: () => const SettingsThemeColorRoute().push(context),
- );
- },
- ),
- ],
+ Selector(
+ selector: (context, model) => model.themeColor,
+ builder: (context, themeColor, child) {
+ return SegmentedListTile(
+ isLast: true,
+ leading: ContainedIcon(
+ Symbols.colorize_rounded,
+ color: themeColor ?? context.colors.primary,
+ ),
+ title: Text('主題色彩'.i18n),
+ subtitle: Text(switch (themeColor) {
+ null => '使用系統配色'.i18n,
+ final v => ColorTools.nameThatColor(v),
+ }),
+ trailing: const Icon(Symbols.chevron_right_rounded),
+ onTap: () => const SettingsThemeColorRoute().push(context),
+ );
+ },
),
],
- );
- },
+ ),
+ ],
);
}
}
diff --git a/lib/core/fcm.dart b/lib/core/fcm.dart
index ee96ddd1b..b36fcf869 100644
--- a/lib/core/fcm.dart
+++ b/lib/core/fcm.dart
@@ -10,6 +10,7 @@ import 'package:flutter/foundation.dart';
Future fcmInit() async {
await Firebase.initializeApp();
+
if (Platform.isAndroid) {
await AwesomeNotificationsFcm().initialize(
onFcmTokenHandle: onTokenHandle,
diff --git a/lib/core/preference.dart b/lib/core/preference.dart
index dff0b330b..d1908c05a 100644
--- a/lib/core/preference.dart
+++ b/lib/core/preference.dart
@@ -66,8 +66,9 @@ class PreferenceKeys {
// #endregion
// #region Experimental
- static const experimentalLaunchToMonitor = 'experimental:launchToMonitor';
- static const experimentalEewAllSource = 'experimental:eewAllSource';
+ static const experimental__launchToMonitor = 'experimental:launchToMonitor';
+ static const experimental__eewAllSource = 'experimental:eewAllSource';
+ static const experimental__newHomeScreen = 'experimental:newHomeScreen';
// #endregion
}
@@ -210,14 +211,19 @@ class Preference {
// #endregion
// #region Experimental
- static bool? get experimentalLaunchToMonitor =>
- instance.getBool(PreferenceKeys.experimentalLaunchToMonitor);
- static set experimentalLaunchToMonitor(bool? value) =>
- instance.set(PreferenceKeys.experimentalLaunchToMonitor, value);
-
- static bool? get experimentalEewAllSource =>
- instance.getBool(PreferenceKeys.experimentalEewAllSource);
- static set experimentalEewAllSource(bool? value) =>
- instance.set(PreferenceKeys.experimentalEewAllSource, value);
+ static bool? get experimental__launchToMonitor =>
+ instance.getBool(PreferenceKeys.experimental__launchToMonitor);
+ static set experimental__launchToMonitor(bool? value) =>
+ instance.set(PreferenceKeys.experimental__launchToMonitor, value);
+
+ static bool? get experimental__eewAllSource =>
+ instance.getBool(PreferenceKeys.experimental__eewAllSource);
+ static set experimental__eewAllSource(bool? value) =>
+ instance.set(PreferenceKeys.experimental__eewAllSource, value);
+
+ static bool? get experimental__newHomeScreen =>
+ instance.getBool(PreferenceKeys.experimental__newHomeScreen);
+ static set experimental__newHomeScreen(bool? value) =>
+ instance.set(PreferenceKeys.experimental__newHomeScreen, value);
// #endregion
}
diff --git a/lib/core/providers.dart b/lib/core/providers.dart
index 29b0bc534..3cef9fa56 100644
--- a/lib/core/providers.dart
+++ b/lib/core/providers.dart
@@ -1,4 +1,5 @@
import 'package:dpip/models/data.dart';
+import 'package:dpip/models/settings/experimental.dart';
import 'package:dpip/models/settings/location.dart';
import 'package:dpip/models/settings/map.dart';
import 'package:dpip/models/settings/notify.dart';
@@ -8,6 +9,7 @@ class GlobalProviders {
GlobalProviders._();
static late DpipDataModel data;
+ static late SettingsExperimentalModel experimental;
static late SettingsLocationModel location;
static late SettingsMapModel map;
static late SettingsNotificationModel notification;
@@ -15,6 +17,7 @@ class GlobalProviders {
static void init() {
data = DpipDataModel();
+ experimental = SettingsExperimentalModel();
location = SettingsLocationModel();
map = SettingsMapModel();
notification = SettingsNotificationModel();
diff --git a/lib/main.dart b/lib/main.dart
index 4041b6549..9acbd8bff 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -24,12 +24,14 @@ import 'package:timezone/data/latest.dart';
final fcmReadyCompleter = Completer();
final talker = TalkerManager.instance;
+
void main() async {
final overallStartTime = DateTime.now();
talker.log('--- 冷啟動偵測開始 ---');
talker.log('🔥 1. (main) 啟動時間: ${overallStartTime.toIso8601String()}');
WidgetsFlutterBinding.ensureInitialized();
String? initialShortcut;
+
if (Platform.isIOS) {
// iOS 14 以下改回用 StoreKit1
InAppPurchaseStoreKitPlatform.enableStoreKit1();
@@ -45,9 +47,6 @@ void main() async {
FlutterError.onError = (FlutterErrorDetails details) {
talker.handle(details.exception, details.stack);
- if (Platform.isAndroid) {
- FirebaseCrashlytics.instance.recordFlutterFatalError(details);
- }
};
final globalInitStart = DateTime.now();
@@ -132,6 +131,7 @@ void main() async {
child: MultiProvider(
providers: [
ChangeNotifierProvider.value(value: GlobalProviders.data),
+ ChangeNotifierProvider.value(value: GlobalProviders.experimental),
ChangeNotifierProvider.value(value: GlobalProviders.location),
ChangeNotifierProvider.value(value: GlobalProviders.map),
ChangeNotifierProvider.value(value: GlobalProviders.notification),
diff --git a/lib/models/settings/experimental.dart b/lib/models/settings/experimental.dart
new file mode 100644
index 000000000..71c45f088
--- /dev/null
+++ b/lib/models/settings/experimental.dart
@@ -0,0 +1,88 @@
+import 'package:dpip/core/preference.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class _SettingsExperimentalModel extends ChangeNotifier {
+ /// The underlying [ValueNotifier] for the "launch to monitor" experimental setting.
+ ///
+ /// Returns the stored preference value, defaulting to `false` if no preference has been set.
+ final $experimental__launchToMonitor = ValueNotifier(
+ Preference.experimental__launchToMonitor ?? false,
+ );
+
+ /// Whether the app launches directly into the monitor screen.
+ ///
+ /// Returns the current state of the "launch to monitor" experimental setting.
+ /// Defaults to `false` if no preference has been set.
+ bool get experimental__launchToMonitor => $experimental__launchToMonitor.value;
+
+ /// Sets whether the app should launch directly into the monitor screen.
+ ///
+ /// Takes a [bool] value indicating if the app should open to the monitor screen on launch.
+ ///
+ /// Invoking this method will also update [$experimental__launchToMonitor] and notify all attached listeners.
+ void set_experimental__launchToMonitor(bool value) {
+ Preference.experimental__launchToMonitor = value;
+
+ $experimental__launchToMonitor.value = value;
+
+ notifyListeners();
+ }
+
+ /// The underlying [ValueNotifier] for the EEW (Early Earthquake Warning) all-source experimental setting.
+ ///
+ /// Returns the stored preference value, defaulting to `false` if no preference has been set.
+ final $experimental__eewAllSource = ValueNotifier(Preference.experimental__eewAllSource ?? false);
+
+ /// Whether to enable the all-source EEW experimental feature.
+ ///
+ /// Returns the current state of the EEW all-source experimental setting.
+ /// Defaults to `false` if no preference has been set.
+ bool get experimental__eewAllSource => $experimental__eewAllSource.value;
+
+ /// Sets whether the all-source EEW experimental feature is enabled.
+ ///
+ /// Takes a [bool] value indicating if the all-source EEW feature should be enabled.
+ ///
+ /// Invoking this method will also update [$experimental__eewAllSource] and notify all attached listeners.
+ void set_experimental__eewAllSource(bool value) {
+ Preference.experimental__eewAllSource = value;
+
+ $experimental__eewAllSource.value = value;
+
+ notifyListeners();
+ }
+
+ /// The underlying [ValueNotifier] for the new home screen experimental setting.
+ ///
+ /// Returns the stored preference value, defaulting to `false` if no preference has been set.
+ final $experimental__newHomeScreen = ValueNotifier(
+ Preference.experimental__newHomeScreen ?? false,
+ );
+
+ /// Whether to enable the new home screen experimental feature.
+ ///
+ /// Returns the current state of the new home screen experimental setting.
+ /// Defaults to `false` if no preference has been set.
+ bool get experimental__newHomeScreen => $experimental__newHomeScreen.value;
+
+ /// Sets whether the new home screen experimental feature is enabled.
+ ///
+ /// Takes a [bool] value indicating if the new home screen feature should be enabled.
+ ///
+ /// Invoking this method will also update [$experimental__newHomeScreen] and notify all attached listeners.
+ void set_experimental__newHomeScreen(bool value) {
+ Preference.experimental__newHomeScreen = value;
+
+ $experimental__newHomeScreen.value = value;
+
+ notifyListeners();
+ }
+}
+
+class SettingsExperimentalModel extends _SettingsExperimentalModel {}
+
+extension SettingsExperimentalModelExtension on BuildContext {
+ SettingsExperimentalModel get useExperimental => watch();
+ SettingsExperimentalModel get experimental => read();
+}
diff --git a/lib/router.dart b/lib/router.dart
index 5c2a48d83..565e2d037 100644
--- a/lib/router.dart
+++ b/lib/router.dart
@@ -2,6 +2,8 @@ import 'package:dpip/app/changelog/page.dart';
import 'package:dpip/app/debug/logs/page.dart';
import 'package:dpip/app/home/layout.dart';
import 'package:dpip/app/home/page.dart';
+import 'package:dpip/app/new_home/layout.dart';
+import 'package:dpip/app/new_home/page.dart';
import 'package:dpip/app/map/page.dart';
import 'package:dpip/app/settings/donate/page.dart';
import 'package:dpip/app/settings/experimental/page.dart';
@@ -34,6 +36,7 @@ import 'package:dpip/app/welcome/2-exptech/page.dart';
import 'package:dpip/app/welcome/3-notice/page.dart';
import 'package:dpip/app/welcome/4-permissions/page.dart';
import 'package:dpip/core/preference.dart';
+import 'package:dpip/models/settings/experimental.dart';
import 'package:dpip/route/announcement/announcement.dart';
import 'package:dpip/utils/log.dart';
import 'package:dpip/widgets/shell_wrapper.dart';
@@ -104,7 +107,11 @@ class HomeRoute extends GoRouteData with $HomeRoute {
@override
Widget build(BuildContext context, GoRouterState state) {
- return const HomeLayout(child: HomePage());
+ if (context.experimental.experimental__newHomeScreen) {
+ return const Material(child: NewHomeLayout(child: NewHomePage()));
+ }
+
+ return const Material(child: HomeLayout(child: HomePage()));
}
}
@@ -538,9 +545,10 @@ final router = GoRouter(
return const WelcomeRoute().location;
}
// Experimental: Launch to monitor if enabled
- if (Preference.experimentalLaunchToMonitor == true) {
+ if (Preference.experimental__launchToMonitor == true) {
return const MapRoute(layers: 'monitor').location;
}
+
return const HomeRoute().location;
}
return null;
diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart
index b15675b73..6a0af9b6a 100644
--- a/lib/utils/constants.dart
+++ b/lib/utils/constants.dart
@@ -34,7 +34,7 @@ const kFadeForwardPageTransitionsTheme = PageTransitionsTheme(
const kEmphasizedAnimationStyle = AnimationStyle(
curve: Easing.emphasizedDecelerate,
duration: Durations.medium4,
- reverseCurve: Easing.emphasizedDecelerate,
+ reverseCurve: Easing.standardDecelerate,
reverseDuration: Durations.short4,
);
diff --git a/lib/utils/extensions/color.dart b/lib/utils/extensions/color.dart
index 35a1bcde7..20361053f 100644
--- a/lib/utils/extensions/color.dart
+++ b/lib/utils/extensions/color.dart
@@ -1,5 +1,7 @@
import 'dart:ui';
+import 'package:dpip/utils/extensions/number.dart';
+
/// Extension on [Color] that provides color manipulation utilities.
///
/// This extension adds helpful methods and getters for transforming and manipulating colors, including color inversion
@@ -26,4 +28,32 @@ extension ColorExtension on Color {
green: 1 - g,
blue: 1 - b,
);
+
+ /// Returns a copy of this color with the given opacity applied.
+ ///
+ /// [value] can be a fraction in `[0, 1]` or a percentage in `(1, 100]` — both
+ /// map to the same `[0, 1]` alpha range. Values outside `[0, 100]` are clamped.
+ ///
+ /// Example:
+ /// ```dart
+ /// color / 0.5 // 50% opacity (fraction form)
+ /// color / 50 // 50% opacity (percentage form)
+ /// ```
+ ///
+ /// You can also divide it by zero, which may not sound great in common math,
+ /// but it will just simply make the color transparent. *(Go take a screenshot
+ /// and confuse your best friends!)*
+ ///
+ /// ```dart
+ /// color / 0 // fully transparent
+ /// color / 0.0 // or a double if you are psychopath
+ /// ```
+ Color operator /(num value) {
+ final alpha = switch (value) {
+ > 1 => value / 100,
+ _ => value,
+ }.clamp(0, 1).asDouble;
+
+ return withValues(alpha: alpha);
+ }
}
diff --git a/lib/widgets/typography.dart b/lib/widgets/typography.dart
index 9a7c7277b..f7ff60dae 100644
--- a/lib/widgets/typography.dart
+++ b/lib/widgets/typography.dart
@@ -31,9 +31,20 @@ class DisplayText extends StatelessWidget {
super.key,
Color? color,
FontWeight? weight,
+ String? fontFamily,
+ double? fontSize,
+ List? fontVariations,
+ List? shadows,
TextStyle? style,
this.align,
- }) : style = TextStyle(color: color, fontWeight: weight).merge(style),
+ }) : style = TextStyle(
+ color: color,
+ fontWeight: weight,
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontVariations: fontVariations,
+ shadows: shadows,
+ ).merge(style),
_textStyleGetter = ((context) => context.texts.displaySmall);
/// Creates a medium display text widget.
@@ -45,9 +56,20 @@ class DisplayText extends StatelessWidget {
super.key,
Color? color,
FontWeight? weight,
+ String? fontFamily,
+ double? fontSize,
+ List? fontVariations,
+ List? shadows,
TextStyle? style,
this.align,
- }) : style = TextStyle(color: color, fontWeight: weight).merge(style),
+ }) : style = TextStyle(
+ color: color,
+ fontWeight: weight,
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontVariations: fontVariations,
+ shadows: shadows,
+ ).merge(style),
_textStyleGetter = ((context) => context.texts.displayMedium);
/// Creates a large display text widget.
@@ -59,9 +81,20 @@ class DisplayText extends StatelessWidget {
super.key,
Color? color,
FontWeight? weight,
+ String? fontFamily,
+ double? fontSize,
+ List? fontVariations,
+ List? shadows,
TextStyle? style,
this.align,
- }) : style = TextStyle(color: color, fontWeight: weight).merge(style),
+ }) : style = TextStyle(
+ color: color,
+ fontWeight: weight,
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontVariations: fontVariations,
+ shadows: shadows,
+ ).merge(style),
_textStyleGetter = ((context) => context.texts.displayLarge);
@override
@@ -103,9 +136,20 @@ class HeadLineText extends StatelessWidget {
super.key,
Color? color,
FontWeight? weight,
+ String? fontFamily,
+ double? fontSize,
+ List? fontVariations,
+ List? shadows,
TextStyle? style,
this.align,
- }) : style = TextStyle(color: color, fontWeight: weight).merge(style),
+ }) : style = TextStyle(
+ color: color,
+ fontWeight: weight,
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontVariations: fontVariations,
+ shadows: shadows,
+ ).merge(style),
_textStyleGetter = ((context) => context.texts.headlineSmall);
/// Creates a medium headline text widget.
@@ -117,9 +161,20 @@ class HeadLineText extends StatelessWidget {
super.key,
Color? color,
FontWeight? weight,
+ String? fontFamily,
+ double? fontSize,
+ List? fontVariations,
+ List? shadows,
TextStyle? style,
this.align,
- }) : style = TextStyle(color: color, fontWeight: weight).merge(style),
+ }) : style = TextStyle(
+ color: color,
+ fontWeight: weight,
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontVariations: fontVariations,
+ shadows: shadows,
+ ).merge(style),
_textStyleGetter = ((context) => context.texts.headlineMedium);
/// Creates a large headline text widget.
@@ -131,9 +186,20 @@ class HeadLineText extends StatelessWidget {
super.key,
Color? color,
FontWeight? weight,
+ String? fontFamily,
+ double? fontSize,
+ List? fontVariations,
+ List? shadows,
TextStyle? style,
this.align,
- }) : style = TextStyle(color: color, fontWeight: weight).merge(style),
+ }) : style = TextStyle(
+ color: color,
+ fontWeight: weight,
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontVariations: fontVariations,
+ shadows: shadows,
+ ).merge(style),
_textStyleGetter = ((context) => context.texts.headlineLarge);
@override
@@ -176,13 +242,21 @@ class TitleText extends StatelessWidget {
super.key,
Color? color,
FontWeight? weight,
+ String? fontFamily,
+ double? fontSize,
+ List? fontVariations,
double? leading,
+ List? shadows,
TextStyle? style,
this.align,
}) : style = TextStyle(
color: color,
fontWeight: weight,
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontVariations: fontVariations,
height: leading,
+ shadows: shadows,
).merge(style),
_textStyleGetter = ((context) => context.texts.titleSmall);
@@ -195,13 +269,21 @@ class TitleText extends StatelessWidget {
super.key,
Color? color,
FontWeight? weight,
+ String? fontFamily,
+ double? fontSize,
+ List? fontVariations,
double? leading,
+ List? shadows,
TextStyle? style,
this.align,
}) : style = TextStyle(
color: color,
fontWeight: weight,
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontVariations: fontVariations,
height: leading,
+ shadows: shadows,
).merge(style),
_textStyleGetter = ((context) => context.texts.titleMedium);
@@ -214,13 +296,21 @@ class TitleText extends StatelessWidget {
super.key,
Color? color,
FontWeight? weight,
+ String? fontFamily,
+ double? fontSize,
+ List? fontVariations,
double? leading,
+ List? shadows,
TextStyle? style,
this.align,
}) : style = TextStyle(
color: color,
fontWeight: weight,
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontVariations: fontVariations,
height: leading,
+ shadows: shadows,
).merge(style),
_textStyleGetter = ((context) => context.texts.titleLarge);
@@ -264,13 +354,21 @@ class BodyText extends StatelessWidget {
super.key,
Color? color,
FontWeight? weight,
+ String? fontFamily,
+ double? fontSize,
+ List? fontVariations,
double? leading,
+ List? shadows,
TextStyle? style,
this.align,
}) : style = TextStyle(
color: color,
fontWeight: weight,
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontVariations: fontVariations,
height: leading,
+ shadows: shadows,
).merge(style),
_textStyleGetter = ((context) => context.texts.bodySmall);
@@ -283,13 +381,21 @@ class BodyText extends StatelessWidget {
super.key,
Color? color,
FontWeight? weight,
+ String? fontFamily,
+ double? fontSize,
+ List? fontVariations,
double? leading,
+ List? shadows,
TextStyle? style,
this.align,
}) : style = TextStyle(
color: color,
fontWeight: weight,
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontVariations: fontVariations,
height: leading,
+ shadows: shadows,
).merge(style),
_textStyleGetter = ((context) => context.texts.bodyMedium);
@@ -302,13 +408,21 @@ class BodyText extends StatelessWidget {
super.key,
Color? color,
FontWeight? weight,
+ String? fontFamily,
+ double? fontSize,
+ List? fontVariations,
double? leading,
+ List? shadows,
TextStyle? style,
this.align,
}) : style = TextStyle(
color: color,
fontWeight: weight,
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontVariations: fontVariations,
height: leading,
+ shadows: shadows,
).merge(style),
_textStyleGetter = ((context) => context.texts.bodyLarge);
@@ -352,13 +466,21 @@ class LabelText extends StatelessWidget {
super.key,
Color? color,
FontWeight? weight,
+ String? fontFamily,
+ double? fontSize,
+ List? fontVariations,
double? leading,
+ List? shadows,
TextStyle? style,
this.align,
}) : style = TextStyle(
color: color,
fontWeight: weight,
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontVariations: fontVariations,
height: leading,
+ shadows: shadows,
).merge(style),
_textStyleGetter = ((context) => context.texts.labelSmall);
@@ -371,13 +493,21 @@ class LabelText extends StatelessWidget {
super.key,
Color? color,
FontWeight? weight,
+ String? fontFamily,
+ double? fontSize,
+ List? fontVariations,
double? leading,
+ List? shadows,
TextStyle? style,
this.align,
}) : style = TextStyle(
color: color,
fontWeight: weight,
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontVariations: fontVariations,
height: leading,
+ shadows: shadows,
).merge(style),
_textStyleGetter = ((context) => context.texts.labelMedium);
@@ -390,13 +520,21 @@ class LabelText extends StatelessWidget {
super.key,
Color? color,
FontWeight? weight,
+ String? fontFamily,
+ double? fontSize,
+ List? fontVariations,
double? leading,
+ List? shadows,
TextStyle? style,
this.align,
}) : style = TextStyle(
color: color,
fontWeight: weight,
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontVariations: fontVariations,
height: leading,
+ shadows: shadows,
).merge(style),
_textStyleGetter = ((context) => context.texts.labelLarge);
@@ -409,3 +547,7 @@ class LabelText extends StatelessWidget {
);
}
}
+
+extension FontVariationExtension on FontVariation {
+ static roundness(double round) => FontVariation('ROND', round);
+}
diff --git a/pubspec.lock b/pubspec.lock
index a558bc487..d2c45361f 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
- sha256: "8d718c5c58904f9937290fd5dbf2d6a0e02456867706bfb6cd7b81d394e738d5"
+ sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d"
url: "https://pub.dev"
source: hosted
- version: "98.0.0"
+ version: "93.0.0"
_flutterfire_internals:
dependency: transitive
description:
@@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: analyzer
- sha256: "6141ad5d092d1e1d13929c0504658bbeccc1703505830d7c26e859908f5efc88"
+ sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b
url: "https://pub.dev"
source: hosted
- version: "12.0.0"
+ version: "10.0.1"
android_alarm_manager_plus:
dependency: "direct main"
description:
@@ -262,10 +262,10 @@ packages:
dependency: transitive
description:
name: dart_style
- sha256: a4c1ccfee44c7e75ed80484071a5c142a385345e658fd8bd7c4b5c97e7198f98
+ sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2"
url: "https://pub.dev"
source: hosted
- version: "3.1.8"
+ version: "3.1.7"
dbus:
dependency: transitive
description:
@@ -987,10 +987,10 @@ packages:
dependency: transitive
description:
name: meta
- sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
+ sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
- version: "1.18.0"
+ version: "1.17.0"
mime:
dependency: transitive
description:
@@ -1760,26 +1760,26 @@ packages:
dependency: "direct main"
description:
name: zstandard
- sha256: "0332045357644eaf66ff0b92619edefc5f8d2e1ef385403d2cb46c150db5db89"
+ sha256: d2cae71d34dfdd014525259be2f84ebc2a738c94cc4232515df44a1415dde9d3
url: "https://pub.dev"
source: hosted
- version: "1.5.0"
+ version: "1.3.29"
zstandard_android:
- dependency: transitive
+ dependency: "direct main"
description:
name: zstandard_android
- sha256: b8dc9860d2ae834744bd82d6b648fc15bdf11dd85d61e01ac563c9e1d51f4bcf
+ sha256: "7d85d814f147faa7a33d5cd5b5610dea451a57f95b34f98c93723bb2f2efbf98"
url: "https://pub.dev"
source: hosted
- version: "1.5.0"
+ version: "1.3.29"
zstandard_ios:
- dependency: transitive
+ dependency: "direct main"
description:
name: zstandard_ios
- sha256: a992ee46aef915029abce6acd54f03a4bf7076e92d9148000fc56e9e6068f6d9
+ sha256: "50e6b3d05ad6ecf8def65c9fae46d4b786edd89150fd2bb207cdcef918946f0d"
url: "https://pub.dev"
source: hosted
- version: "1.5.0"
+ version: "1.3.29"
zstandard_linux:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index e53283d7b..c75f1550c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -71,7 +71,9 @@ dependencies:
talker_flutter: ^5.1.9
timezone: ^0.11.0
url_launcher: ^6.3.2
- zstandard: ^1.3.29
+ zstandard: 1.3.29
+ zstandard_android: 1.3.29
+ zstandard_ios: 1.3.29
dev_dependencies:
build_runner: ^2.10.4
flutter_test:
@@ -87,6 +89,7 @@ flutter:
shaders:
- shaders/fog.frag
- shaders/thunderstorm.frag
+ - shaders/weather_sky.frag
assets:
# localizations
- assets/translations/
@@ -101,3 +104,7 @@ flutter:
- assets/location.json.gz
- assets/notify_test.json
- assets/time.json.gz
+ fonts:
+ - family: Google Sans Flex
+ fonts:
+ - asset: assets/fonts/GoogleSansFlex.ttf
diff --git a/shaders/weather_sky.frag b/shaders/weather_sky.frag
new file mode 100644
index 000000000..5a6eb3d1b
--- /dev/null
+++ b/shaders/weather_sky.frag
@@ -0,0 +1,238 @@
+#include
+
+uniform float iTime;
+uniform vec2 iResolution;
+uniform float iScene;
+uniform float iScroll;
+uniform float iCloud;
+uniform float iRain;
+uniform float iWind;
+uniform float iSunPhase;
+uniform float iLight;
+
+out vec4 fragColor;
+
+float hash12(vec2 p) {
+ vec3 p3 = fract(vec3(p.xyx) * 0.1031);
+ p3 += dot(p3, p3.yzx + 33.33);
+ return fract((p3.x + p3.y) * p3.z);
+}
+
+vec2 hash22(vec2 p) {
+ vec3 p3 = fract(vec3(p.xyx) * vec3(0.1031, 0.1030, 0.0973));
+ p3 += dot(p3, p3.yzx + 33.33);
+ return fract((p3.xx + p3.yz) * p3.zy);
+}
+
+float vnoise(vec2 p) {
+ vec2 i = floor(p);
+ vec2 f = fract(p);
+ vec2 u = f * f * (3.0 - 2.0 * f);
+ return mix(
+ mix(hash12(i), hash12(i + vec2(1.0, 0.0)), u.x),
+ mix(hash12(i + vec2(0.0, 1.0)), hash12(i + vec2(1.0, 1.0)), u.x),
+ u.y
+ );
+}
+
+float cloudFbm(vec2 p) {
+ float v = 0.0;
+ float a = 0.5;
+ mat2 rot = mat2(0.8, 0.6, -0.6, 0.8);
+ for (int i = 0; i < 3; i++) {
+ v += a * vnoise(p);
+ p = rot * p * 2.0;
+ a *= 0.5;
+ }
+ return v;
+}
+
+float cloudShape(vec2 uv, float time, float density) {
+ vec2 p = uv * 1.2;
+ p.x += time * 0.02;
+ float threshold = mix(0.58, 0.40, density);
+ return smoothstep(threshold, threshold + 0.05, cloudFbm(p));
+}
+
+vec3 skyColor(float y, int scene, float light) {
+ if (scene == 0) {
+ vec3 darkTop = vec3(0.05, 0.16, 0.34);
+ vec3 darkBot = vec3(0.36, 0.68, 0.87);
+ vec3 lightTop = vec3(0.42, 0.66, 0.90);
+ vec3 lightBot = vec3(0.78, 0.90, 0.98);
+ return mix(mix(darkTop, lightTop, light), mix(darkBot, lightBot, light), y);
+ }
+ if (scene == 1) {
+ return mix(vec3(0.02, 0.04, 0.08), vec3(0.07, 0.11, 0.17), y);
+ }
+ if (scene == 2) {
+ vec3 a = vec3(0.83, 0.38, 0.16);
+ vec3 b = vec3(0.37, 0.14, 0.33);
+ vec3 c = vec3(0.09, 0.04, 0.16);
+ return y < 0.5 ? mix(c, b, y * 2.0) : mix(b, a, (y - 0.5) * 2.0);
+ }
+ vec3 a = vec3(0.94, 0.50, 0.19);
+ vec3 b = vec3(0.63, 0.20, 0.12);
+ vec3 c = vec3(0.09, 0.05, 0.05);
+ return y < 0.5 ? mix(c, b, y * 2.0) : mix(b, a, (y - 0.5) * 2.0);
+}
+
+float starField(vec2 uv, float density, float time) {
+ vec2 grid = floor(uv);
+ vec2 gv = fract(uv) - 0.5;
+ vec2 r = hash22(grid);
+ float exists = step(density, r.x);
+ vec2 starPos = (r - 0.5) * 0.7;
+ float d = length(gv - starPos);
+ float core = 1.0 / (1.0 + d * d * 600.0);
+ float twinkle = 0.55 + 0.45 * sin(time * 2.2 + r.x * 6.28);
+ return core * exists * mix(0.45, 1.0, r.y) * twinkle;
+}
+
+float crescentMoon(vec2 uv, vec2 c, float r) {
+ float d1 = length(uv - c) - r;
+ float d2 = length(uv - c - vec2(r * 0.45, -r * 0.07)) - r * 0.85;
+ float crescent = max(d1, -d2);
+ return smoothstep(0.004, -0.004, crescent);
+}
+
+float radialGlow(vec2 uv, vec2 c, float falloff) {
+ float d = length(uv - c);
+ return exp(-d * falloff);
+}
+
+float disc(vec2 uv, vec2 c, float r) {
+ float d = length(uv - c);
+ return smoothstep(r + 0.005, r - 0.005, d);
+}
+
+float rainStreaks(vec2 uv, float time) {
+ vec2 p = uv * vec2(50.0, 8.0);
+ p.x += p.y * 0.5;
+ p.y -= time * 6.0;
+ vec2 cell = floor(p);
+ vec2 cv = fract(p);
+ float r = hash12(cell);
+ float alive = step(0.62, r);
+ float streak = (1.0 - cv.y * 0.7) * (1.0 - smoothstep(0.0, 0.06, abs(cv.x - 0.5)));
+ return streak * alive;
+}
+
+void main() {
+ vec2 fc = FlutterFragCoord();
+ vec2 uv = fc.xy / iResolution.xy;
+ float aspect = iResolution.x / iResolution.y;
+ vec2 puv = vec2(uv.x * aspect, uv.y);
+
+ int scene = int(iScene + 0.5);
+
+ float scrollN = iScroll / iResolution.y;
+ float p1 = scrollN * 0.10;
+ float p2 = scrollN * 0.22;
+ float p3 = scrollN * 0.40;
+
+ vec3 col = skyColor(uv.y, scene, iLight);
+
+ // Slightly dim sky as cloud cover increases
+ col = mix(col, col * mix(0.70, 0.82, iLight), iCloud * 0.30);
+
+ // Sun position arcs east-to-west with the time-of-day phase
+ float phase = clamp(iSunPhase, 0.0, 1.0);
+ float sunX = mix(0.50, 0.95, phase) * aspect;
+ float sunY = 0.40 - sin(phase * 3.14159) * 0.30;
+ vec2 sunC = vec2(sunX, sunY - p3);
+
+ if (scene == 0) {
+ float sunVis = 1.0 - iCloud * 0.7;
+ float dSun = length(puv - sunC);
+ // Soft outer bloom
+ col += vec3(1.0, 0.76, 0.28) * exp(-dSun * 9.0) * 0.25 * sunVis;
+ // Tighter glow
+ col += vec3(1.0, 0.86, 0.42) * exp(-dSun * 18.0) * 0.55 * sunVis;
+ // Disc with internal gradient: golden rim → bright yellow core,
+ // gradient sampled from a point slightly above-left of the sun center
+ float discR = 0.040;
+ vec2 gradC = sunC - vec2(0.003, 0.003);
+ float dGrad = length(puv - gradC);
+ vec3 rim = vec3(1.0, 0.78, 0.32);
+ vec3 core = vec3(1.0, 0.95, 0.60);
+ vec3 discCol = mix(core, rim, smoothstep(0.0, discR, dGrad));
+ float discMask = smoothstep(discR + 0.004, discR - 0.004, dSun);
+ col = mix(col, discCol, discMask * sunVis);
+ }
+ else if (scene == 1) {
+ float starVis = 1.0 - iCloud * 0.85;
+ float bgY = uv.y + p1;
+ float bgStars = starField(vec2(uv.x * aspect, bgY) * 65.0, 0.78, iTime)
+ * smoothstep(0.78, 0.0, bgY);
+ col += vec3(bgStars * 0.55 * starVis);
+
+ float midY = uv.y + p2;
+ float midStars = starField(vec2(uv.x * aspect, midY) * 38.0, 0.66, iTime + 1.7)
+ * smoothstep(0.58, 0.0, midY);
+ col += vec3(midStars * 0.95 * starVis);
+
+ vec2 moonC = vec2(aspect * 0.84, 0.10 - p3);
+ col += vec3(0.85, 0.88, 0.97) * radialGlow(puv, moonC, 9.0) * 0.20 * starVis;
+ col = mix(col, vec3(0.88, 0.91, 0.99), crescentMoon(puv, moonC, 0.06) * starVis);
+ }
+ else {
+ // Dawn (scene 2) or sunset (scene 3): warm sun rides the same arc.
+ vec3 sunCol = scene == 2 ? vec3(0.93, 0.46, 0.27) : vec3(1.0, 0.55, 0.26);
+ float sunVis = 1.0 - iCloud * 0.5;
+ col += sunCol * radialGlow(puv, sunC, 5.0) * 0.40 * sunVis;
+ col += sunCol * radialGlow(puv, sunC, 9.0) * 0.70 * sunVis;
+ col = mix(col, sunCol * 1.2, disc(puv, sunC, 0.085) * sunVis);
+ }
+
+ // Cloud overlay
+ if (iCloud > 0.01) {
+ vec3 cloudHi;
+ vec3 cloudLo;
+ if (scene == 1) {
+ cloudHi = vec3(0.20, 0.22, 0.28);
+ cloudLo = vec3(0.10, 0.12, 0.16);
+ }
+ else if (scene == 2) {
+ cloudHi = vec3(0.85, 0.55, 0.45);
+ cloudLo = vec3(0.45, 0.22, 0.30);
+ }
+ else if (scene == 3) {
+ cloudHi = vec3(1.0, 0.65, 0.40);
+ cloudLo = vec3(0.55, 0.25, 0.18);
+ }
+ else {
+ cloudHi = vec3(1.0, 0.99, 0.95);
+ cloudLo = vec3(0.62, 0.68, 0.76);
+ }
+ vec3 cloudCol = mix(cloudHi, cloudLo, iCloud);
+
+ float drift = iTime * (0.3 + iWind * 1.4);
+ float cy1 = uv.y + p1;
+ vec2 cloudUV = vec2(uv.x * aspect, cy1) * vec2(1.8, 2.5);
+ float c1 = cloudShape(cloudUV, drift, iCloud) * smoothstep(0.55, 0.0, cy1);
+ float c1Up = cloudShape(cloudUV + vec2(0.0, -0.10), drift, iCloud)
+ * smoothstep(0.55, 0.0, cy1);
+ col = mix(col, cloudCol, c1);
+ col = mix(col, cloudLo * 0.55, c1 * c1Up * 0.45);
+
+ if (iCloud > 0.4) {
+ float cy2 = uv.y + p3;
+ vec2 cloudUV2 = vec2(uv.x * aspect, cy2) * vec2(2.0, 2.2) + 17.3;
+ float c2 = cloudShape(cloudUV2, drift * 1.4 + 50.0, iCloud)
+ * smoothstep(0.40, 0.0, cy2);
+ col = mix(col, cloudLo, c2 * 0.6);
+ }
+ }
+
+ // Rain overlay
+ if (iRain > 0.02) {
+ float ry = uv.y + p3;
+ float rain = rainStreaks(vec2(uv.x * aspect, ry), iTime)
+ * smoothstep(0.60, 0.10, ry);
+ vec3 rainTint = scene == 1 ? vec3(0.4, 0.5, 0.65) : vec3(0.55, 0.65, 0.78);
+ col += rainTint * rain * 0.45 * iRain;
+ }
+
+ fragColor = vec4(col, 1.0);
+}