Skip to content
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ android {
applicationId 'com.exptech.dpip'
minSdkVersion 26
targetSdkVersion 36
versionCode 300103100
versionCode 300103103
versionName flutterVersionName
multiDexEnabled true
resConfigs "en", "ko", "zh-rTW", "ja", "zh-rCN"
Expand Down
6 changes: 6 additions & 0 deletions lib/app/home/home_display_mode.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
enum HomeDisplaySection {
realtime,
radar,
forecast,
history,
}
48 changes: 41 additions & 7 deletions lib/app/home/page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -20,16 +21,19 @@ 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/layout/page.dart';
import 'package:dpip/core/gps_location.dart';
import 'package:dpip/core/i18n.dart';
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});
Expand Down Expand Up @@ -257,7 +261,9 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
WidgetsBinding.instance.addPostFrameCallback((_) => _refresh());
}
_wasVisible = isVisible;

final homeSections = context.select<SettingsUserInterfaceModel, Set<HomeDisplaySection>>(
(model) => model.homeSections
);
final topPadding = MediaQuery.of(context).padding.top;

WidgetsBinding.instance.addPostFrameCallback((_) {
Expand All @@ -281,18 +287,41 @@ class _HomePageState extends State<HomePage> 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 (!_isLoading) ..._buildRealtimeInfo(),
_buildRadarMap(),
_buildHistoryTimeline(),
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(SettingsLayoutPage.route),
child: Text('前往設定'.i18n),
),
],
),
),
],
),
),
Positioned(
top: 16,
top: 24,
left: 0,
right: 0,
child: SafeArea(
Expand Down Expand Up @@ -332,13 +361,13 @@ class _HomePageState extends State<HomePage> 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) =>
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!),
];
}

Expand All @@ -349,6 +378,11 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
);
}

Widget _buildForecast() {
if (_forecast == null) return const SizedBox.shrink();
return ForecastCard(_forecast!);
}

Widget _buildHistoryTimeline() {
return Builder(
builder: (context) {
Expand Down
53 changes: 53 additions & 0 deletions lib/app/settings/layout/page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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<SettingsUserInterfaceModel>(
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: ListTile.divideTiles(
context: context,
tiles: tiles,
).toList(),
);
},
),
],
);
}
}
10 changes: 10 additions & 0 deletions lib/app/settings/page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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});

Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion lib/app/welcome/4-permissions/page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ class _WelcomePermissionPageState extends State<WelcomePermissionPage> with Widg
announcement: true,
carPlay: true,
criticalAlert: true,
provisional: true,
provisional: false,
);
if (iosSettings.criticalAlert == AppleNotificationSetting.enabled) {
_isNotificationPermission = true;
Expand Down
4 changes: 4 additions & 0 deletions lib/core/preference.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ 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 homeDisplaySections = 'pref:ui:homeDisplaySections';

// #region Notification
static const notifyEew = 'pref:notify:eew';
Expand Down Expand Up @@ -116,6 +117,9 @@ class Preference {

static bool? get mapAutoZoom => instance.getBool(PreferenceKeys.mapAutoZoom);
static set mapAutoZoom(bool? value) => instance.set(PreferenceKeys.mapAutoZoom, value);

static List<String> get homeDisplaySections => instance.getStringList(PreferenceKeys.homeDisplaySections) ?? [];
static set homeDisplaySections(List<String> value) => instance.set(PreferenceKeys.homeDisplaySections, value);
// #endregion

// #region Notification
Expand Down
26 changes: 26 additions & 0 deletions lib/models/settings/ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ 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');

String get _themeMode => Preference.themeMode ?? 'system';
int? get _themeColor => Preference.themeColor;
Locale? get _locale => Preference.locale?.asLocale;
bool get _useFahrenheit => Preference.useFahrenheit ?? false;
late Set<HomeDisplaySection> homeSections;
final savedList = Preference.homeDisplaySections;

ThemeMode get themeMode => ThemeMode.values.byName(_themeMode);
void setThemeMode(ThemeMode value) {
Expand Down Expand Up @@ -48,4 +52,26 @@ class SettingsUserInterfaceModel extends ChangeNotifier {
_log('Changed ${PreferenceKeys.useFahrenheit} to ${Preference.useFahrenheit}');
notifyListeners();
}

SettingsUserInterfaceModel() {
final saved = savedList
.map((s) => HomeDisplaySection.values
.cast<HomeDisplaySection?>()
.firstWhere((e) => e?.name == s, orElse: () => null))
.whereType<HomeDisplaySection>()
.toSet();
homeSections = saved;
}

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();
}
}
13 changes: 13 additions & 0 deletions lib/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -113,6 +114,7 @@ class HomeRoute extends GoRouteData with $HomeRoute {
@TypedShellRoute<SettingsShellRoute>(
routes: <TypedGoRoute<GoRouteData>>[
TypedGoRoute<SettingsIndexRoute>(path: '/settings'),
TypedGoRoute<SettingsLayoutRoute>(path: '/settings/layout'),
TypedGoRoute<SettingsLocationRoute>(path: '/settings/location'),
TypedGoRoute<SettingsLocationSelectRoute>(path: '/settings/location/select'),
TypedGoRoute<SettingsLocationSelectCityRoute>(path: '/settings/location/select/:city'),
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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].
Expand Down
Loading