From ce3d9ee4278fb61acb3e9009004e3f764fed6e6e Mon Sep 17 00:00:00 2001 From: lowrt Date: Wed, 3 Dec 2025 21:34:29 +0800 Subject: [PATCH 01/16] feat: Homepage mode --- lib/app/home/home_display_mode.dart | 7 +++++ lib/app/home/page.dart | 29 ++++++++++++++++----- lib/app/settings/theme/page.dart | 40 +++++++++++++++++++++++++++++ lib/core/preference.dart | 18 +++++++++++++ lib/models/settings/ui.dart | 27 +++++++++++++++++++ 5 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 lib/app/home/home_display_mode.dart diff --git a/lib/app/home/home_display_mode.dart b/lib/app/home/home_display_mode.dart new file mode 100644 index 000000000..3d42886f1 --- /dev/null +++ b/lib/app/home/home_display_mode.dart @@ -0,0 +1,7 @@ +enum HomeDisplaySection { + weather, + realtime, + radar, + forecast, + history, +} diff --git a/lib/app/home/page.dart b/lib/app/home/page.dart index bd4639b08..540786684 100644 --- a/lib/app/home/page.dart +++ b/lib/app/home/page.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:go_router/go_router.dart'; import 'package:i18n_extension/i18n_extension.dart'; +import 'package:provider/provider.dart'; import 'package:timezone/timezone.dart'; import 'package:dpip/api/exptech.dart'; @@ -26,11 +27,14 @@ import 'package:dpip/core/preference.dart'; import 'package:dpip/core/providers.dart'; import 'package:dpip/global.dart'; import 'package:dpip/utils/constants.dart'; +import 'package:dpip/models/settings/ui.dart'; import 'package:dpip/utils/extensions/build_context.dart'; import 'package:dpip/utils/extensions/datetime.dart'; import 'package:dpip/utils/log.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; +import 'home_display_mode.dart'; + class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -257,7 +261,7 @@ class _HomePageState extends State with WidgetsBindingObserver { WidgetsBinding.instance.addPostFrameCallback((_) => _refresh()); } _wasVisible = isVisible; - + final model = context.watch(); final topPadding = MediaQuery.of(context).padding.top; WidgetsBinding.instance.addPostFrameCallback((_) { @@ -284,10 +288,16 @@ class _HomePageState extends State with WidgetsBindingObserver { top: _locationButtonHeight != null ? 16 + topPadding + _locationButtonHeight! : 0, ), children: [ - _buildWeatherHeader(), - if (!_isLoading) ..._buildRealtimeInfo(), - _buildRadarMap(), - _buildHistoryTimeline(), + if (model.isEnabled(HomeDisplaySection.weather)) + _buildWeatherHeader(), + if (model.isEnabled(HomeDisplaySection.realtime)) + ..._buildRealtimeInfo(), + if (model.isEnabled(HomeDisplaySection.radar)) + _buildRadarMap(), + if (model.isEnabled(HomeDisplaySection.forecast)) + _buildForecast(), + if (model.isEnabled(HomeDisplaySection.history)) + _buildHistoryTimeline(), ], ), ), @@ -338,7 +348,6 @@ class _HomePageState extends State with WidgetsBindingObserver { Padding(padding: const EdgeInsets.all(16), child: EewCard(GlobalProviders.data.eew[index])), ), if (_thunderstorm != null) Padding(padding: const EdgeInsets.all(16), child: ThunderstormCard(_thunderstorm!)), - if (_forecast != null) ForecastCard(_forecast!), ]; } @@ -349,6 +358,14 @@ class _HomePageState extends State with WidgetsBindingObserver { ); } + Widget _buildForecast() { + if (_forecast == null) return const SizedBox.shrink(); + return Padding( + padding: const EdgeInsets.all(16), + child: ForecastCard(_forecast!), + ); + } + Widget _buildHistoryTimeline() { return Builder( builder: (context) { diff --git a/lib/app/settings/theme/page.dart b/lib/app/settings/theme/page.dart index b8c1fecbf..0f1e9922c 100644 --- a/lib/app/settings/theme/page.dart +++ b/lib/app/settings/theme/page.dart @@ -10,6 +10,8 @@ import 'package:go_router/go_router.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:provider/provider.dart'; +import '../../home/home_display_mode.dart'; + class SettingsThemePage extends StatelessWidget { const SettingsThemePage({super.key}); @@ -123,6 +125,44 @@ class SettingsThemePage extends StatelessWidget { ), ], ), + ListSection( + title: '首頁模式'.i18n, + children: [ + Consumer( + builder: (context, model, child) { + return Column( + children: [ + SwitchListTile( + title: Text('天氣資訊'.i18n), + value: model.isEnabled(HomeDisplaySection.weather), + onChanged: (v) => model.toggleSection(HomeDisplaySection.weather, v), + ), + SwitchListTile( + title: Text('圖卡資訊'.i18n), + value: model.isEnabled(HomeDisplaySection.realtime), + onChanged: (v) => model.toggleSection(HomeDisplaySection.realtime, v), + ), + SwitchListTile( + title: Text('雷達回波'.i18n), + value: model.isEnabled(HomeDisplaySection.radar), + onChanged: (v) => model.toggleSection(HomeDisplaySection.radar, v), + ), + SwitchListTile( + title: Text('天氣預報(24h)'.i18n), + value: model.isEnabled(HomeDisplaySection.forecast), + onChanged: (v) => model.toggleSection(HomeDisplaySection.forecast, v), + ), + SwitchListTile( + title: Text('歷史事件'.i18n), + value: model.isEnabled(HomeDisplaySection.history), + onChanged: (v) => model.toggleSection(HomeDisplaySection.history, v), + ), + ], + ); + }, + ), + ], + ), ], ); } diff --git a/lib/core/preference.dart b/lib/core/preference.dart index 296bdc7c8..8eb548900 100644 --- a/lib/core/preference.dart +++ b/lib/core/preference.dart @@ -26,6 +26,8 @@ class PreferenceKeys { static const mapBase = 'pref:ui:map:base'; static const mapLayers = 'pref:ui:map:layers'; static const mapAutoZoom = 'pref:ui:map:autoZoom'; + static const homeDisplayMode = 'pref:ui:homeDisplayMode'; + static const homeDisplaySections = 'pref:ui:homeDisplaySections'; // #region Notification static const notifyEew = 'pref:notify:eew'; @@ -116,6 +118,22 @@ class Preference { static bool? get mapAutoZoom => instance.getBool(PreferenceKeys.mapAutoZoom); static set mapAutoZoom(bool? value) => instance.set(PreferenceKeys.mapAutoZoom, value); + + static String? get homeDisplayMode => instance.getString(PreferenceKeys.homeDisplayMode); + static set homeDisplayMode(String? value) => instance.set(PreferenceKeys.homeDisplayMode, value); + + static List get homeDisplaySections => instance.getStringList(PreferenceKeys.homeDisplaySections) ?? []; + static set homeDisplaySections(List value) => instance.set(PreferenceKeys.homeDisplaySections, value); + + static void toggleHomeSection(String section, bool enabled) { + final current = homeDisplaySections; + if (enabled) { + if (!current.contains(section)) current.add(section); + } else { + current.remove(section); + } + instance.set(PreferenceKeys.homeDisplaySections, current); + } // #endregion // #region Notification diff --git a/lib/models/settings/ui.dart b/lib/models/settings/ui.dart index aeefc0fab..f93085696 100644 --- a/lib/models/settings/ui.dart +++ b/lib/models/settings/ui.dart @@ -6,6 +6,8 @@ import 'package:dpip/core/preference.dart'; import 'package:dpip/utils/extensions/string.dart'; import 'package:flutter/material.dart'; +import '../../app/home/home_display_mode.dart'; + class SettingsUserInterfaceModel extends ChangeNotifier { void _log(String message) => log(message, name: 'SettingsUserInterfaceModel'); @@ -13,6 +15,8 @@ class SettingsUserInterfaceModel extends ChangeNotifier { int? get _themeColor => Preference.themeColor; Locale? get _locale => Preference.locale?.asLocale; bool get _useFahrenheit => Preference.useFahrenheit ?? false; + late Set homeSections; + final savedList = Preference.homeDisplaySections; ThemeMode get themeMode => ThemeMode.values.byName(_themeMode); void setThemeMode(ThemeMode value) { @@ -48,4 +52,27 @@ class SettingsUserInterfaceModel extends ChangeNotifier { _log('Changed ${PreferenceKeys.useFahrenheit} to ${Preference.useFahrenheit}'); notifyListeners(); } + + SettingsUserInterfaceModel() { + final saved = savedList + .map((s) => HomeDisplaySection.values.firstWhere((e) => e.name == s,)) + .whereType() + .toSet(); + homeSections = saved.isNotEmpty + ? saved + : {HomeDisplaySection.weather}; + } + + bool isEnabled(HomeDisplaySection section) => homeSections.contains(section); + + void toggleSection(HomeDisplaySection section, bool enabled) { + if (enabled) { + homeSections.add(section); + } else { + homeSections.remove(section); + } + + Preference.homeDisplaySections = homeSections.map((e) => e.name).toList(); + notifyListeners(); + } } From 93f03ce1747bed1d4d3546f0704a6378f283b676 Mon Sep 17 00:00:00 2001 From: lowrt Date: Wed, 3 Dec 2025 21:47:56 +0800 Subject: [PATCH 02/16] fix: top --- lib/app/home/page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app/home/page.dart b/lib/app/home/page.dart index 540786684..0ae6d293d 100644 --- a/lib/app/home/page.dart +++ b/lib/app/home/page.dart @@ -302,7 +302,7 @@ class _HomePageState extends State with WidgetsBindingObserver { ), ), Positioned( - top: 16, + top: 24, left: 0, right: 0, child: SafeArea( From 9291217cafcf9ce653872276fd7095039008ed07 Mon Sep 17 00:00:00 2001 From: lowrt Date: Wed, 3 Dec 2025 22:17:05 +0800 Subject: [PATCH 03/16] fix: homeSections == 1 --- lib/core/preference.dart | 10 ---------- lib/models/settings/ui.dart | 5 ++++- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/core/preference.dart b/lib/core/preference.dart index 8eb548900..ed071c521 100644 --- a/lib/core/preference.dart +++ b/lib/core/preference.dart @@ -124,16 +124,6 @@ class Preference { static List get homeDisplaySections => instance.getStringList(PreferenceKeys.homeDisplaySections) ?? []; static set homeDisplaySections(List value) => instance.set(PreferenceKeys.homeDisplaySections, value); - - static void toggleHomeSection(String section, bool enabled) { - final current = homeDisplaySections; - if (enabled) { - if (!current.contains(section)) current.add(section); - } else { - current.remove(section); - } - instance.set(PreferenceKeys.homeDisplaySections, current); - } // #endregion // #region Notification diff --git a/lib/models/settings/ui.dart b/lib/models/settings/ui.dart index f93085696..ef47df2b6 100644 --- a/lib/models/settings/ui.dart +++ b/lib/models/settings/ui.dart @@ -66,12 +66,15 @@ class SettingsUserInterfaceModel extends ChangeNotifier { bool isEnabled(HomeDisplaySection section) => homeSections.contains(section); void toggleSection(HomeDisplaySection section, bool enabled) { + if (!enabled && homeSections.length == 1 && homeSections.contains(section)) { + return; + } + if (enabled) { homeSections.add(section); } else { homeSections.remove(section); } - Preference.homeDisplaySections = homeSections.map((e) => e.name).toList(); notifyListeners(); } From 0dc1adef4ee01d60bed3783c16281453f89c74ea Mon Sep 17 00:00:00 2001 From: lowrt Date: Wed, 3 Dec 2025 22:39:11 +0800 Subject: [PATCH 04/16] rm: EdgeInsets --- lib/app/home/page.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/app/home/page.dart b/lib/app/home/page.dart index 0ae6d293d..0086c8fbc 100644 --- a/lib/app/home/page.dart +++ b/lib/app/home/page.dart @@ -360,10 +360,7 @@ class _HomePageState extends State with WidgetsBindingObserver { Widget _buildForecast() { if (_forecast == null) return const SizedBox.shrink(); - return Padding( - padding: const EdgeInsets.all(16), - child: ForecastCard(_forecast!), - ); + return ForecastCard(_forecast!); } Widget _buildHistoryTimeline() { From de22f9a450ed0928b8cb1c1ad099d8a805164424 Mon Sep 17 00:00:00 2001 From: lowrt Date: Wed, 3 Dec 2025 22:40:17 +0800 Subject: [PATCH 05/16] build: 300103101 --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 6f4546b01..42dd9934e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -49,7 +49,7 @@ android { applicationId 'com.exptech.dpip' minSdkVersion 26 targetSdkVersion 36 - versionCode 300103100 + versionCode 300103101 versionName flutterVersionName multiDexEnabled true resConfigs "en", "ko", "zh-rTW", "ja", "zh-rCN" From a81dd2b2f755cb932e6d754427f4a3d36af0f088 Mon Sep 17 00:00:00 2001 From: lowrt Date: Thu, 4 Dec 2025 07:55:51 +0800 Subject: [PATCH 06/16] fix: permissions --- lib/app/welcome/4-permissions/page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app/welcome/4-permissions/page.dart b/lib/app/welcome/4-permissions/page.dart index 5d8ba3e8f..23f0b8319 100644 --- a/lib/app/welcome/4-permissions/page.dart +++ b/lib/app/welcome/4-permissions/page.dart @@ -310,7 +310,7 @@ class _WelcomePermissionPageState extends State with Widg announcement: true, carPlay: true, criticalAlert: true, - provisional: true, + provisional: false, ); if (iosSettings.criticalAlert == AppleNotificationSetting.enabled) { _isNotificationPermission = true; From aeaed45134ccdb16c2ca37c832a0f9eaf788f203 Mon Sep 17 00:00:00 2001 From: lowrt Date: Thu, 4 Dec 2025 09:29:19 +0800 Subject: [PATCH 07/16] rm: weather --- lib/app/home/home_display_mode.dart | 1 - lib/app/home/page.dart | 1 - lib/app/settings/theme/page.dart | 5 ----- lib/models/settings/ui.dart | 10 ++++------ 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/app/home/home_display_mode.dart b/lib/app/home/home_display_mode.dart index 3d42886f1..1129d4c6d 100644 --- a/lib/app/home/home_display_mode.dart +++ b/lib/app/home/home_display_mode.dart @@ -1,5 +1,4 @@ enum HomeDisplaySection { - weather, realtime, radar, forecast, diff --git a/lib/app/home/page.dart b/lib/app/home/page.dart index 0086c8fbc..f88ff4788 100644 --- a/lib/app/home/page.dart +++ b/lib/app/home/page.dart @@ -288,7 +288,6 @@ class _HomePageState extends State with WidgetsBindingObserver { top: _locationButtonHeight != null ? 16 + topPadding + _locationButtonHeight! : 0, ), children: [ - if (model.isEnabled(HomeDisplaySection.weather)) _buildWeatherHeader(), if (model.isEnabled(HomeDisplaySection.realtime)) ..._buildRealtimeInfo(), diff --git a/lib/app/settings/theme/page.dart b/lib/app/settings/theme/page.dart index 0f1e9922c..4e956d5bc 100644 --- a/lib/app/settings/theme/page.dart +++ b/lib/app/settings/theme/page.dart @@ -132,11 +132,6 @@ class SettingsThemePage extends StatelessWidget { builder: (context, model, child) { return Column( children: [ - SwitchListTile( - title: Text('天氣資訊'.i18n), - value: model.isEnabled(HomeDisplaySection.weather), - onChanged: (v) => model.toggleSection(HomeDisplaySection.weather, v), - ), SwitchListTile( title: Text('圖卡資訊'.i18n), value: model.isEnabled(HomeDisplaySection.realtime), diff --git a/lib/models/settings/ui.dart b/lib/models/settings/ui.dart index ef47df2b6..1ed6cb7f4 100644 --- a/lib/models/settings/ui.dart +++ b/lib/models/settings/ui.dart @@ -55,21 +55,19 @@ class SettingsUserInterfaceModel extends ChangeNotifier { SettingsUserInterfaceModel() { final saved = savedList - .map((s) => HomeDisplaySection.values.firstWhere((e) => e.name == s,)) + .map((s) => HomeDisplaySection.values + .cast() + .firstWhere((e) => e?.name == s, orElse: () => null)) .whereType() .toSet(); homeSections = saved.isNotEmpty ? saved - : {HomeDisplaySection.weather}; + : {HomeDisplaySection.realtime}; } bool isEnabled(HomeDisplaySection section) => homeSections.contains(section); void toggleSection(HomeDisplaySection section, bool enabled) { - if (!enabled && homeSections.length == 1 && homeSections.contains(section)) { - return; - } - if (enabled) { homeSections.add(section); } else { From c8348cdc34d77e10dbf9a5af666257e93fd8e0c0 Mon Sep 17 00:00:00 2001 From: lowrt Date: Thu, 4 Dec 2025 10:30:41 +0800 Subject: [PATCH 08/16] build: 300103102 --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 42dd9934e..bf055d3a2 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -49,7 +49,7 @@ android { applicationId 'com.exptech.dpip' minSdkVersion 26 targetSdkVersion 36 - versionCode 300103101 + versionCode 300103102 versionName flutterVersionName multiDexEnabled true resConfigs "en", "ko", "zh-rTW", "ja", "zh-rCN" From dae4d8c6e0cee62add168049798e0695d403a34d Mon Sep 17 00:00:00 2001 From: lowrt Date: Thu, 4 Dec 2025 18:14:57 +0800 Subject: [PATCH 09/16] fix: null --- lib/models/settings/ui.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/models/settings/ui.dart b/lib/models/settings/ui.dart index 1ed6cb7f4..0c5a481d6 100644 --- a/lib/models/settings/ui.dart +++ b/lib/models/settings/ui.dart @@ -60,9 +60,7 @@ class SettingsUserInterfaceModel extends ChangeNotifier { .firstWhere((e) => e?.name == s, orElse: () => null)) .whereType() .toSet(); - homeSections = saved.isNotEmpty - ? saved - : {HomeDisplaySection.realtime}; + homeSections = saved; } bool isEnabled(HomeDisplaySection section) => homeSections.contains(section); From 733a14eb24dfb3751a3573aea0daf4b4bea27c86 Mon Sep 17 00:00:00 2001 From: lowrt Date: Thu, 4 Dec 2025 20:58:32 +0800 Subject: [PATCH 10/16] rm: key --- lib/core/preference.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/core/preference.dart b/lib/core/preference.dart index ed071c521..3618c6bc6 100644 --- a/lib/core/preference.dart +++ b/lib/core/preference.dart @@ -26,7 +26,6 @@ class PreferenceKeys { static const mapBase = 'pref:ui:map:base'; static const mapLayers = 'pref:ui:map:layers'; static const mapAutoZoom = 'pref:ui:map:autoZoom'; - static const homeDisplayMode = 'pref:ui:homeDisplayMode'; static const homeDisplaySections = 'pref:ui:homeDisplaySections'; // #region Notification @@ -119,9 +118,6 @@ class Preference { static bool? get mapAutoZoom => instance.getBool(PreferenceKeys.mapAutoZoom); static set mapAutoZoom(bool? value) => instance.set(PreferenceKeys.mapAutoZoom, value); - static String? get homeDisplayMode => instance.getString(PreferenceKeys.homeDisplayMode); - static set homeDisplayMode(String? value) => instance.set(PreferenceKeys.homeDisplayMode, value); - static List get homeDisplaySections => instance.getStringList(PreferenceKeys.homeDisplaySections) ?? []; static set homeDisplaySections(List value) => instance.set(PreferenceKeys.homeDisplaySections, value); // #endregion From b21b7abb9e616a731c858d2826b0956e4734da18 Mon Sep 17 00:00:00 2001 From: lowrt Date: Thu, 4 Dec 2025 21:00:27 +0800 Subject: [PATCH 11/16] fix: Performance rebuilds --- lib/app/home/page.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/app/home/page.dart b/lib/app/home/page.dart index f88ff4788..aa47c6bba 100644 --- a/lib/app/home/page.dart +++ b/lib/app/home/page.dart @@ -261,7 +261,9 @@ class _HomePageState extends State with WidgetsBindingObserver { WidgetsBinding.instance.addPostFrameCallback((_) => _refresh()); } _wasVisible = isVisible; - final model = context.watch(); + final homeSections = context.select>( + (model) => model.homeSections + ); final topPadding = MediaQuery.of(context).padding.top; WidgetsBinding.instance.addPostFrameCallback((_) { @@ -289,13 +291,13 @@ class _HomePageState extends State with WidgetsBindingObserver { ), children: [ _buildWeatherHeader(), - if (model.isEnabled(HomeDisplaySection.realtime)) + if (homeSections.contains(HomeDisplaySection.realtime)) ..._buildRealtimeInfo(), - if (model.isEnabled(HomeDisplaySection.radar)) + if (homeSections.contains(HomeDisplaySection.radar)) _buildRadarMap(), - if (model.isEnabled(HomeDisplaySection.forecast)) + if (homeSections.contains(HomeDisplaySection.forecast)) _buildForecast(), - if (model.isEnabled(HomeDisplaySection.history)) + if (homeSections.contains(HomeDisplaySection.history)) _buildHistoryTimeline(), ], ), From 19aecb474cade52293977d9c580fedc225ce40b4 Mon Sep 17 00:00:00 2001 From: lowrt Date: Fri, 5 Dec 2025 13:44:06 +0800 Subject: [PATCH 12/16] feat: guidance prompts --- lib/app/home/page.dart | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/lib/app/home/page.dart b/lib/app/home/page.dart index aa47c6bba..95b77f815 100644 --- a/lib/app/home/page.dart +++ b/lib/app/home/page.dart @@ -21,6 +21,7 @@ import 'package:dpip/app/home/_widgets/mode_toggle_button.dart'; import 'package:dpip/app/home/_widgets/radar_card.dart'; import 'package:dpip/app/home/_widgets/thunderstorm_card.dart'; import 'package:dpip/app/home/_widgets/weather_header.dart'; +import 'package:dpip/app/settings/theme/page.dart'; import 'package:dpip/core/gps_location.dart'; import 'package:dpip/core/i18n.dart'; import 'package:dpip/core/preference.dart'; @@ -287,18 +288,36 @@ class _HomePageState extends State with WidgetsBindingObserver { onRefresh: _refresh, child: ListView( padding: EdgeInsets.only( - top: _locationButtonHeight != null ? 16 + topPadding + _locationButtonHeight! : 0, + top: _locationButtonHeight != null ? 24 + topPadding + _locationButtonHeight! : 0, ), children: [ - _buildWeatherHeader(), - if (homeSections.contains(HomeDisplaySection.realtime)) - ..._buildRealtimeInfo(), - if (homeSections.contains(HomeDisplaySection.radar)) - _buildRadarMap(), - if (homeSections.contains(HomeDisplaySection.forecast)) - _buildForecast(), - if (homeSections.contains(HomeDisplaySection.history)) - _buildHistoryTimeline(), + _buildWeatherHeader(), + if (homeSections.isNotEmpty) ...[ + if (homeSections.contains(HomeDisplaySection.realtime)) + ..._buildRealtimeInfo(), + if (homeSections.contains(HomeDisplaySection.radar)) + _buildRadarMap(), + if (homeSections.contains(HomeDisplaySection.forecast)) + _buildForecast(), + if (homeSections.contains(HomeDisplaySection.history)) + _buildHistoryTimeline(), + ] else if (GlobalProviders.location.code != null) + Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Text( + '您還沒有啟用首頁區塊,請到設定選擇要顯示的內容。'.i18n, + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + FilledButton( + onPressed: () => context.push(SettingsThemePage.route), + child: Text('前往設定'.i18n), + ), + ], + ), + ), ], ), ), From 9d6024bc59e5e69f1376127fb3d429a7d80ef542 Mon Sep 17 00:00:00 2001 From: lowrt Date: Fri, 5 Dec 2025 14:54:57 +0800 Subject: [PATCH 13/16] fix: EewCard padding --- lib/app/home/page.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/app/home/page.dart b/lib/app/home/page.dart index 95b77f815..fd3011b92 100644 --- a/lib/app/home/page.dart +++ b/lib/app/home/page.dart @@ -362,6 +362,7 @@ class _HomePageState extends State with WidgetsBindingObserver { if (GlobalProviders.data.eew.isNotEmpty) ListView.builder( shrinkWrap: true, + padding: EdgeInsets.zero, physics: const NeverScrollableScrollPhysics(), itemCount: GlobalProviders.data.eew.length, itemBuilder: (context, index) => From 820e721c787d40915bd5f466dcccd76323ed2d31 Mon Sep 17 00:00:00 2001 From: lowrt Date: Fri, 5 Dec 2025 20:46:39 +0800 Subject: [PATCH 14/16] feat(settings/layout): page --- lib/app/home/page.dart | 5 ++-- lib/app/settings/layout/page.dart | 49 +++++++++++++++++++++++++++++++ lib/app/settings/page.dart | 10 +++++++ lib/app/settings/theme/page.dart | 35 ---------------------- lib/router.dart | 13 ++++++++ 5 files changed, 74 insertions(+), 38 deletions(-) create mode 100644 lib/app/settings/layout/page.dart diff --git a/lib/app/home/page.dart b/lib/app/home/page.dart index fd3011b92..6552e0f47 100644 --- a/lib/app/home/page.dart +++ b/lib/app/home/page.dart @@ -21,7 +21,7 @@ import 'package:dpip/app/home/_widgets/mode_toggle_button.dart'; import 'package:dpip/app/home/_widgets/radar_card.dart'; import 'package:dpip/app/home/_widgets/thunderstorm_card.dart'; import 'package:dpip/app/home/_widgets/weather_header.dart'; -import 'package:dpip/app/settings/theme/page.dart'; +import 'package:dpip/app/settings/layout/page.dart'; import 'package:dpip/core/gps_location.dart'; import 'package:dpip/core/i18n.dart'; import 'package:dpip/core/preference.dart'; @@ -33,7 +33,6 @@ import 'package:dpip/utils/extensions/build_context.dart'; import 'package:dpip/utils/extensions/datetime.dart'; import 'package:dpip/utils/log.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; - import 'home_display_mode.dart'; class HomePage extends StatefulWidget { @@ -312,7 +311,7 @@ class _HomePageState extends State with WidgetsBindingObserver { ), const SizedBox(height: 12), FilledButton( - onPressed: () => context.push(SettingsThemePage.route), + onPressed: () => context.push(SettingsLayoutPage.route), child: Text('前往設定'.i18n), ), ], diff --git a/lib/app/settings/layout/page.dart b/lib/app/settings/layout/page.dart new file mode 100644 index 000000000..4284b865a --- /dev/null +++ b/lib/app/settings/layout/page.dart @@ -0,0 +1,49 @@ +import 'package:dpip/core/i18n.dart'; +import 'package:dpip/models/settings/ui.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../../../widgets/list/list_section.dart'; +import '../../home/home_display_mode.dart'; + +class SettingsLayoutPage extends StatelessWidget { + const SettingsLayoutPage({super.key}); + + static const route = '/settings/layout'; + + @override + Widget build(BuildContext context) { + return ListSection( + title: '首頁樣式'.i18n, + children: [ + Consumer( + builder: (context, model, child) { + return Column( + children: [ + SwitchListTile( + title: Text('圖卡資訊'.i18n), + value: model.isEnabled(HomeDisplaySection.realtime), + onChanged: (v) => model.toggleSection(HomeDisplaySection.realtime, v), + ), + SwitchListTile( + title: Text('雷達回波'.i18n), + value: model.isEnabled(HomeDisplaySection.radar), + onChanged: (v) => model.toggleSection(HomeDisplaySection.radar, v), + ), + SwitchListTile( + title: Text('天氣預報(24h)'.i18n), + value: model.isEnabled(HomeDisplaySection.forecast), + onChanged: (v) => model.toggleSection(HomeDisplaySection.forecast, v), + ), + SwitchListTile( + title: Text('歷史事件'.i18n), + value: model.isEnabled(HomeDisplaySection.history), + onChanged: (v) => model.toggleSection(HomeDisplaySection.history, v), + ), + ], + ); + }, + ), + ], + ); + } +} diff --git a/lib/app/settings/page.dart b/lib/app/settings/page.dart index ed028544b..4d3687310 100644 --- a/lib/app/settings/page.dart +++ b/lib/app/settings/page.dart @@ -20,6 +20,8 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:simple_icons/simple_icons.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'layout/page.dart'; + class SettingsIndexPage extends StatelessWidget { const SettingsIndexPage({super.key}); @@ -47,6 +49,14 @@ class SettingsIndexPage extends StatelessWidget { final userInterface = ListSection( title: '介面'.i18n, children: [ + ListSectionTile( + icon: Symbols.grid_view_rounded, + title: '佈局'.i18n, + subtitle: Text('調整 DPIP 的佈局樣式'.i18n), + onTap: () { + context.push(SettingsLayoutPage.route); + }, + ), ListSectionTile( icon: Symbols.brush_rounded, title: '主題'.i18n, diff --git a/lib/app/settings/theme/page.dart b/lib/app/settings/theme/page.dart index 4e956d5bc..b8c1fecbf 100644 --- a/lib/app/settings/theme/page.dart +++ b/lib/app/settings/theme/page.dart @@ -10,8 +10,6 @@ import 'package:go_router/go_router.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:provider/provider.dart'; -import '../../home/home_display_mode.dart'; - class SettingsThemePage extends StatelessWidget { const SettingsThemePage({super.key}); @@ -125,39 +123,6 @@ class SettingsThemePage extends StatelessWidget { ), ], ), - ListSection( - title: '首頁模式'.i18n, - children: [ - Consumer( - builder: (context, model, child) { - return Column( - children: [ - SwitchListTile( - title: Text('圖卡資訊'.i18n), - value: model.isEnabled(HomeDisplaySection.realtime), - onChanged: (v) => model.toggleSection(HomeDisplaySection.realtime, v), - ), - SwitchListTile( - title: Text('雷達回波'.i18n), - value: model.isEnabled(HomeDisplaySection.radar), - onChanged: (v) => model.toggleSection(HomeDisplaySection.radar, v), - ), - SwitchListTile( - title: Text('天氣預報(24h)'.i18n), - value: model.isEnabled(HomeDisplaySection.forecast), - onChanged: (v) => model.toggleSection(HomeDisplaySection.forecast, v), - ), - SwitchListTile( - title: Text('歷史事件'.i18n), - value: model.isEnabled(HomeDisplaySection.history), - onChanged: (v) => model.toggleSection(HomeDisplaySection.history, v), - ), - ], - ); - }, - ), - ], - ), ], ); } diff --git a/lib/router.dart b/lib/router.dart index af7af8bf2..87f72bd90 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -12,6 +12,7 @@ import 'package:dpip/app/layout.dart'; import 'package:dpip/app/map/page.dart'; import 'package:dpip/app/settings/donate/page.dart'; import 'package:dpip/app/settings/layout.dart'; +import 'package:dpip/app/settings/layout/page.dart'; import 'package:dpip/app/settings/locale/page.dart'; import 'package:dpip/app/settings/locale/select/page.dart'; import 'package:dpip/app/settings/location/page.dart'; @@ -113,6 +114,7 @@ class HomeRoute extends GoRouteData with $HomeRoute { @TypedShellRoute( routes: >[ TypedGoRoute(path: '/settings'), + TypedGoRoute(path: '/settings/layout'), TypedGoRoute(path: '/settings/location'), TypedGoRoute(path: '/settings/location/select'), TypedGoRoute(path: '/settings/location/select/:city'), @@ -166,6 +168,7 @@ class SettingsShellRoute extends ShellRouteData { '/settings/location' => '所在地'.i18n, '/settings/location/select' => '新增地點'.i18n, final p when p?.startsWith('/settings/location/select/') == true => '新增地點'.i18n, + '/settings/layout' => '佈局'.i18n, '/settings/theme' => '主題'.i18n, '/settings/theme/select' => '主題'.i18n, '/settings/locale' => '語言'.i18n, @@ -235,6 +238,16 @@ class SettingsLocationSelectCityRoute extends GoRouteData with $SettingsLocation } } +class SettingsLayoutRoute extends GoRouteData with $SettingsLayoutRoute { + /// Creates a [SettingsLayoutRoute]. + const SettingsLayoutRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return const Material(child: SettingsLayoutPage()); + } +} + /// Settings theme route - displays theme settings. class SettingsThemeRoute extends GoRouteData with $SettingsThemeRoute { /// Creates a [SettingsThemeRoute]. From c3dfbc849e4cd207328b7c8b90cf79053149a8aa Mon Sep 17 00:00:00 2001 From: lowrt Date: Fri, 5 Dec 2025 22:56:52 +0800 Subject: [PATCH 15/16] build: 300103103 --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index bf055d3a2..431db75ad 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -49,7 +49,7 @@ android { applicationId 'com.exptech.dpip' minSdkVersion 26 targetSdkVersion 36 - versionCode 300103102 + versionCode 300103103 versionName flutterVersionName multiDexEnabled true resConfigs "en", "ko", "zh-rTW", "ja", "zh-rCN" From ce42bf85f88c20bf026fbb77f5c0db5351891dbe Mon Sep 17 00:00:00 2001 From: lowrt Date: Sat, 6 Dec 2025 10:31:16 +0800 Subject: [PATCH 16/16] fix: Separator line --- lib/app/settings/layout/page.dart | 48 +++++++++++++++++-------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/lib/app/settings/layout/page.dart b/lib/app/settings/layout/page.dart index 4284b865a..5bd0b496e 100644 --- a/lib/app/settings/layout/page.dart +++ b/lib/app/settings/layout/page.dart @@ -17,29 +17,33 @@ class SettingsLayoutPage extends StatelessWidget { children: [ Consumer( builder: (context, model, child) { + final tiles = [ + SwitchListTile( + title: Text('圖卡資訊'.i18n), + value: model.isEnabled(HomeDisplaySection.realtime), + onChanged: (v) => model.toggleSection(HomeDisplaySection.realtime, v), + ), + SwitchListTile( + title: Text('雷達回波'.i18n), + value: model.isEnabled(HomeDisplaySection.radar), + onChanged: (v) => model.toggleSection(HomeDisplaySection.radar, v), + ), + SwitchListTile( + title: Text('天氣預報(24h)'.i18n), + value: model.isEnabled(HomeDisplaySection.forecast), + onChanged: (v) => model.toggleSection(HomeDisplaySection.forecast, v), + ), + SwitchListTile( + title: Text('歷史事件'.i18n), + value: model.isEnabled(HomeDisplaySection.history), + onChanged: (v) => model.toggleSection(HomeDisplaySection.history, v), + ), + ]; return Column( - children: [ - SwitchListTile( - title: Text('圖卡資訊'.i18n), - value: model.isEnabled(HomeDisplaySection.realtime), - onChanged: (v) => model.toggleSection(HomeDisplaySection.realtime, v), - ), - SwitchListTile( - title: Text('雷達回波'.i18n), - value: model.isEnabled(HomeDisplaySection.radar), - onChanged: (v) => model.toggleSection(HomeDisplaySection.radar, v), - ), - SwitchListTile( - title: Text('天氣預報(24h)'.i18n), - value: model.isEnabled(HomeDisplaySection.forecast), - onChanged: (v) => model.toggleSection(HomeDisplaySection.forecast, v), - ), - SwitchListTile( - title: Text('歷史事件'.i18n), - value: model.isEnabled(HomeDisplaySection.history), - onChanged: (v) => model.toggleSection(HomeDisplaySection.history, v), - ), - ], + children: ListTile.divideTiles( + context: context, + tiles: tiles, + ).toList(), ); }, ),