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); +}