From f8d0efb05a437375099d040d25092fbd7b1b7ded Mon Sep 17 00:00:00 2001 From: Ryotaro Onoue <73390859+YumNumm@users.noreply.github.com> Date: Wed, 27 Dec 2023 02:46:39 +0900 Subject: [PATCH] =?UTF-8?q?REST=20API=E3=81=AEURL=E5=88=87=E3=82=8A?= =?UTF-8?q?=E6=9B=BF=E3=81=88=20(#474)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add --- .../app_information_model.freezed.dart | 19 +- .../crashlytics_setting_provider.dart | 9 +- .../crashlytics_setting_provider.g.dart | 2 +- .../intensity_color_provider.dart | 11 +- .../intensity_color_provider.g.dart | 2 +- lib/core/provider/dio_provider.g.dart | 2 +- lib/core/router/router.dart | 44 +++- lib/core/router/router.g.dart | 91 +++++--- .../earthquake_history_tile_widget.dart | 19 ++ .../page/earthquake_history.dart | 199 +++++++++--------- .../earthquake_history_view_model.dart | 83 +++----- .../earthquake_history_view_model.g.dart | 6 +- .../component/earthquake_map.dart | 58 ++--- .../screen/earthquake_history_details.dart | 16 +- .../eew_detailed_history_screen.dart | 2 +- .../home/component/sheet/debug_widget.dart | 115 ---------- .../sheet/earthquake_history_widget.dart | 128 +++++------ .../home/features/eew/eew_provider.dart | 2 +- .../home/features/eew/eew_provider.g.dart | 2 +- .../kmoni/viewmodel/kmoni_view_model.dart | 24 +-- .../kmoni/viewmodel/kmoni_view_model.g.dart | 2 +- .../kmoni/viewmodel/kmoni_view_settings.dart | 15 +- .../viewmodel/kmoni_view_settings.g.dart | 2 +- .../provider/telegram_url_provider.dart | 17 +- .../provider/telegram_url_provider.g.dart | 2 +- .../provider/telegram_provider.dart | 20 +- .../provider/telegram_provider.g.dart | 2 +- .../provider/telegram_socket_io.dart | 2 +- .../provider/telegram_socket_io.g.dart | 2 +- .../api_endpoint_selector_page.dart | 42 ++++ .../children/config/debug/debugger_page.dart | 103 ++++++++- lib/feature/settings/settings_screen.dart | 7 +- 32 files changed, 573 insertions(+), 477 deletions(-) delete mode 100644 lib/feature/home/component/sheet/debug_widget.dart create mode 100644 lib/feature/settings/children/config/debug/api_endpoint_selector/api_endpoint_selector_page.dart diff --git a/lib/core/provider/app_information/app_information_model.freezed.dart b/lib/core/provider/app_information/app_information_model.freezed.dart index 4c3a9dda1..ccd77ee0c 100644 --- a/lib/core/provider/app_information/app_information_model.freezed.dart +++ b/lib/core/provider/app_information/app_information_model.freezed.dart @@ -172,7 +172,9 @@ class __$$AppInformationModelImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _$AppInformationModelImpl implements _AppInformationModel { +class _$AppInformationModelImpl + with DiagnosticableTreeMixin + implements _AppInformationModel { const _$AppInformationModelImpl( {@JsonKey(fromJson: versionFromJson, toJson: versionToJson) required this.latestVersion, @@ -204,10 +206,23 @@ class _$AppInformationModelImpl implements _AppInformationModel { final bool hasForceUpdate; @override - String toString() { + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { return 'AppInformationModel(latestVersion: $latestVersion, minimumVersion: $minimumVersion, downloadUrl: $downloadUrl, forceUpdateMessage: $forceUpdateMessage, hasUpdate: $hasUpdate, hasForceUpdate: $hasForceUpdate)'; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'AppInformationModel')) + ..add(DiagnosticsProperty('latestVersion', latestVersion)) + ..add(DiagnosticsProperty('minimumVersion', minimumVersion)) + ..add(DiagnosticsProperty('downloadUrl', downloadUrl)) + ..add(DiagnosticsProperty('forceUpdateMessage', forceUpdateMessage)) + ..add(DiagnosticsProperty('hasUpdate', hasUpdate)) + ..add(DiagnosticsProperty('hasForceUpdate', hasForceUpdate)); + } + @override bool operator ==(dynamic other) { return identical(this, other) || diff --git a/lib/core/provider/config/crashlytics/crashlytics_setting_provider.dart b/lib/core/provider/config/crashlytics/crashlytics_setting_provider.dart index 4be538876..afb19a333 100644 --- a/lib/core/provider/config/crashlytics/crashlytics_setting_provider.dart +++ b/lib/core/provider/config/crashlytics/crashlytics_setting_provider.dart @@ -11,18 +11,15 @@ part 'crashlytics_setting_provider.g.dart'; class CrashlyticsSetting extends _$CrashlyticsSetting { @override CrashlyticsSettingModel build() { - _prefs = ref.watch(sharedPreferencesProvider); ref.listenSelf((_, next) => _save()); return const CrashlyticsSettingModel(); } - late final SharedPreferences _prefs; - static const String _key = 'crashlytics_setting'; - Future _save() async { - await _prefs.setString(_key, jsonEncode(state.toJson())); - } + Future _save() async => ref + .read(sharedPreferencesProvider) + .setString(_key, jsonEncode(state.toJson())); void setEnabled({required bool isEnabled}) { state = state.copyWith(isEnabled: isEnabled); diff --git a/lib/core/provider/config/crashlytics/crashlytics_setting_provider.g.dart b/lib/core/provider/config/crashlytics/crashlytics_setting_provider.g.dart index f651002ad..32524c760 100644 --- a/lib/core/provider/config/crashlytics/crashlytics_setting_provider.g.dart +++ b/lib/core/provider/config/crashlytics/crashlytics_setting_provider.g.dart @@ -9,7 +9,7 @@ part of 'crashlytics_setting_provider.dart'; // ************************************************************************** String _$crashlyticsSettingHash() => - r'3c9d23bc4227245d7091d8d11b45f91fe5501d6e'; + r'000581239d3ee84dd95a09f814f6d5b8f31fa845'; /// See also [CrashlyticsSetting]. @ProviderFor(CrashlyticsSetting) diff --git a/lib/core/provider/config/theme/intensity_color/intensity_color_provider.dart b/lib/core/provider/config/theme/intensity_color/intensity_color_provider.dart index 2047dae84..f33df8246 100644 --- a/lib/core/provider/config/theme/intensity_color/intensity_color_provider.dart +++ b/lib/core/provider/config/theme/intensity_color/intensity_color_provider.dart @@ -5,7 +5,6 @@ import 'dart:convert'; import 'package:eqmonitor/core/provider/config/theme/intensity_color/model/intensity_color_model.dart'; import 'package:eqmonitor/core/provider/shared_preferences.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:shared_preferences/shared_preferences.dart'; part 'intensity_color_provider.g.dart'; @@ -13,22 +12,22 @@ part 'intensity_color_provider.g.dart'; class IntensityColor extends _$IntensityColor { @override IntensityColorModel build() { - _prefs = ref.watch(sharedPreferencesProvider); + final result = load(); + if (result != null) { + return result; + } return IntensityColorModel.eqmonitor(); } static const _key = 'intensity_color'; - late final SharedPreferences _prefs; // ignore: use_setters_to_change_properties void update(IntensityColorModel model) { state = model; } - - IntensityColorModel? load() { - final value = _prefs.getString(_key); + final value = ref.read(sharedPreferencesProvider).getString(_key); if (value == null) { return null; } diff --git a/lib/core/provider/config/theme/intensity_color/intensity_color_provider.g.dart b/lib/core/provider/config/theme/intensity_color/intensity_color_provider.g.dart index a9c1df2e4..7805c9b9a 100644 --- a/lib/core/provider/config/theme/intensity_color/intensity_color_provider.g.dart +++ b/lib/core/provider/config/theme/intensity_color/intensity_color_provider.g.dart @@ -8,7 +8,7 @@ part of 'intensity_color_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$intensityColorHash() => r'fd22f8f71ad39a3d87dd685e01b4cdbaa87afbee'; +String _$intensityColorHash() => r'6affe0c2bf46fc6afb34347a41a54b73996534f3'; /// See also [IntensityColor]. @ProviderFor(IntensityColor) diff --git a/lib/core/provider/dio_provider.g.dart b/lib/core/provider/dio_provider.g.dart index 4f54da054..885e9cd63 100644 --- a/lib/core/provider/dio_provider.g.dart +++ b/lib/core/provider/dio_provider.g.dart @@ -8,7 +8,7 @@ part of 'dio_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$dioHash() => r'8b0f5ed4db7ba97f9812c3e4be46d3fa5fd984ff'; +String _$dioHash() => r'15e9d5052511a66066cd71665e9bbde4a95efcc6'; /// See also [dio]. @ProviderFor(dio) diff --git a/lib/core/router/router.dart b/lib/core/router/router.dart index ee6bf0c80..98c1ba889 100644 --- a/lib/core/router/router.dart +++ b/lib/core/router/router.dart @@ -9,6 +9,8 @@ import 'package:eqmonitor/feature/settings/children/application_info/license_pag import 'package:eqmonitor/feature/settings/children/application_info/privacy_policy_screen.dart'; import 'package:eqmonitor/feature/settings/children/application_info/term_of_service_screen.dart'; import 'package:eqmonitor/feature/settings/children/config/color_scheme/color_scheme_config_page.dart'; +import 'package:eqmonitor/feature/settings/children/config/debug/api_endpoint_selector/api_endpoint_selector_page.dart'; +import 'package:eqmonitor/feature/settings/children/config/debug/debugger_page.dart'; import 'package:eqmonitor/feature/settings/children/config/notification/children/earthquake/earthquake_notification_settings_page.dart'; import 'package:eqmonitor/feature/settings/children/config/notification/children/eew/eew_notification_settings_page.dart'; import 'package:eqmonitor/feature/settings/children/config/notification/notification_setting_page.dart'; @@ -80,14 +82,6 @@ class EewDetailedHistoryRoute extends GoRouteData { ); } -@TypedGoRoute(path: '/config') -class ColorSchemeConfigRoute extends GoRouteData { - const ColorSchemeConfigRoute(); - @override - Widget build(BuildContext context, GoRouterState state) => - const ColorSchemeConfigPage(); -} - @TypedGoRoute(path: '/') class HomeRoute extends GoRouteData { const HomeRoute(); @@ -123,6 +117,9 @@ class KmoniRoute extends GoRouteData { TypedGoRoute( path: 'license', ), + TypedGoRoute( + path: 'color-schema', + ), TypedGoRoute( path: 'notification', routes: [ @@ -134,6 +131,14 @@ class KmoniRoute extends GoRouteData { ), ], ), + TypedGoRoute( + path: 'debugger', + routes: [ + TypedGoRoute( + path: 'api-endpoint-selector', + ), + ], + ), ], ) class SettingsRoute extends GoRouteData { @@ -144,6 +149,22 @@ class SettingsRoute extends GoRouteData { const SettingsScreen(); } +class DebuggerRoute extends GoRouteData { + const DebuggerRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) => + const DebuggerPage(); +} + +class ApiEndpointSelectorRoute extends GoRouteData { + const ApiEndpointSelectorRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) => + const ApiEndpointSelectorPage(); +} + class TermOfServiceRoute extends GoRouteData { const TermOfServiceRoute({ required this.$extra, @@ -160,6 +181,13 @@ class TermOfServiceRoute extends GoRouteData { ); } +class ColorSchemeConfigRoute extends GoRouteData { + const ColorSchemeConfigRoute(); + @override + Widget build(BuildContext context, GoRouterState state) => + const ColorSchemeConfigPage(); +} + class PrivacyPolicyRoute extends GoRouteData { const PrivacyPolicyRoute({ required this.$extra, diff --git a/lib/core/router/router.g.dart b/lib/core/router/router.g.dart index d146e0902..6b9f38043 100644 --- a/lib/core/router/router.g.dart +++ b/lib/core/router/router.g.dart @@ -13,7 +13,6 @@ List get $appRoutes => [ $earthquakeHistoryRoute, $earthquakeHistoryDetailsRoute, $eewDetailedHistoryRoute, - $colorSchemeConfigRoute, $homeRoute, $talkerRoute, $kmoniRoute, @@ -116,29 +115,6 @@ extension $EewDetailedHistoryRouteExtension on EewDetailedHistoryRoute { void replace(BuildContext context) => context.replace(location); } -RouteBase get $colorSchemeConfigRoute => GoRouteData.$route( - path: '/config', - factory: $ColorSchemeConfigRouteExtension._fromState, - ); - -extension $ColorSchemeConfigRouteExtension on ColorSchemeConfigRoute { - static ColorSchemeConfigRoute _fromState(GoRouterState state) => - const ColorSchemeConfigRoute(); - - String get location => GoRouteData.$location( - '/config', - ); - - void go(BuildContext context) => context.go(location); - - Future push(BuildContext context) => context.push(location); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - void replace(BuildContext context) => context.replace(location); -} - RouteBase get $homeRoute => GoRouteData.$route( path: '/', factory: $HomeRouteExtension._fromState, @@ -221,6 +197,10 @@ RouteBase get $settingsRoute => GoRouteData.$route( path: 'license', factory: $LicenseRouteExtension._fromState, ), + GoRouteData.$route( + path: 'color-schema', + factory: $ColorSchemeConfigRouteExtension._fromState, + ), GoRouteData.$route( path: 'notification', factory: $NotificationSettingsRouteExtension._fromState, @@ -235,6 +215,16 @@ RouteBase get $settingsRoute => GoRouteData.$route( ), ], ), + GoRouteData.$route( + path: 'debugger', + factory: $DebuggerRouteExtension._fromState, + routes: [ + GoRouteData.$route( + path: 'api-endpoint-selector', + factory: $ApiEndpointSelectorRouteExtension._fromState, + ), + ], + ), ], ); @@ -330,6 +320,24 @@ extension $LicenseRouteExtension on LicenseRoute { void replace(BuildContext context) => context.replace(location); } +extension $ColorSchemeConfigRouteExtension on ColorSchemeConfigRoute { + static ColorSchemeConfigRoute _fromState(GoRouterState state) => + const ColorSchemeConfigRoute(); + + String get location => GoRouteData.$location( + '/settings/color-schema', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} + extension $NotificationSettingsRouteExtension on NotificationSettingsRoute { static NotificationSettingsRoute _fromState(GoRouterState state) => const NotificationSettingsRoute(); @@ -386,6 +394,41 @@ extension $EarthquakeNotificationSettingsRouteExtension void replace(BuildContext context) => context.replace(location); } +extension $DebuggerRouteExtension on DebuggerRoute { + static DebuggerRoute _fromState(GoRouterState state) => const DebuggerRoute(); + + String get location => GoRouteData.$location( + '/settings/debugger', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} + +extension $ApiEndpointSelectorRouteExtension on ApiEndpointSelectorRoute { + static ApiEndpointSelectorRoute _fromState(GoRouterState state) => + const ApiEndpointSelectorRoute(); + + String get location => GoRouteData.$location( + '/settings/debugger/api-endpoint-selector', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} + T? _$convertMapValue( String key, Map map, diff --git a/lib/feature/earthquake_history/component/earthquake_history_tile_widget.dart b/lib/feature/earthquake_history/component/earthquake_history_tile_widget.dart index 5f4330f93..453842f4a 100644 --- a/lib/feature/earthquake_history/component/earthquake_history_tile_widget.dart +++ b/lib/feature/earthquake_history/component/earthquake_history_tile_widget.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:eqapi_types/eqapi_types.dart'; import 'package:eqmonitor/core/component/chip/custom_chip.dart'; import 'package:eqmonitor/core/component/intenisty/intensity_icon_type.dart'; @@ -41,6 +42,11 @@ class EarthquakeHistoryTileWidget extends ConsumerWidget { ) && item.telegrams.every((e) => e.type == TelegramType.vxse52); + // 通常報以外かどうか + final telegramStatuses = item.telegrams.map((e) => e.status).toSet(); + final isNotNormal = telegramStatuses.contains(TelegramStatus.test) || + telegramStatuses.contains(TelegramStatus.training); + // ! title final title = StringBuffer(); if (item.earthquake.earthquake != null) { @@ -125,6 +131,19 @@ class EarthquakeHistoryTileWidget extends ConsumerWidget { fontFamilyFallback: [FontFamily.notoSansJP], ), ), + if (!const SetEquality() + .equals(telegramStatuses, {TelegramStatus.normal})) + for (final status in telegramStatuses) + CustomChip( + backgroundColor: Colors.red.shade200, + child: Text( + '${status.type}報', + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + ), if (item.tsunami?.forecasts != null || item.tsunami?.observations != null || item.tsunami?.estimations != null) diff --git a/lib/feature/earthquake_history/page/earthquake_history.dart b/lib/feature/earthquake_history/page/earthquake_history.dart index 6f456734f..27d7a16fd 100644 --- a/lib/feature/earthquake_history/page/earthquake_history.dart +++ b/lib/feature/earthquake_history/page/earthquake_history.dart @@ -25,9 +25,7 @@ class EarthquakeHistoryPage extends HookConsumerWidget { ), ); // 初回読み込みを行う - await ref - .read(earthquakeHistoryViewModelProvider.notifier) - .loadIfNull(); + await ref.read(earthquakeHistoryViewModelProvider.notifier).fetch(); }, ); return null; @@ -42,53 +40,54 @@ class EarthquakeHistoryPage extends HookConsumerWidget { const SliverAppBar.medium( title: Text('地震の履歴'), ), - state.when( - data: (data) { - return EarthquakeHistoryListView( - data: data, - ); - }, - error: (error, stackTrace) { - // dataがある場合にはそれを表示 - if (state.hasValue) { - final data = state.value!; - return EarthquakeHistoryListView( - data: data, - ); - } - return SliverFillRemaining( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - '地震履歴の取得中にエラーが発生しました。', - ), - FilledButton.tonal( - onPressed: ref - .read(earthquakeHistoryViewModelProvider.notifier) - .fetch, - child: const Text('再読み込み'), + state?.when( + data: (data) { + return EarthquakeHistoryListView( + data: data, + ); + }, + error: (error, stackTrace) { + // dataがある場合にはそれを表示 + if (state.hasValue) { + final data = state.value!; + return EarthquakeHistoryListView( + data: data, + ); + } + return SliverFillRemaining( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + '地震履歴の取得中にエラーが発生しました。', + ), + FilledButton.tonal( + onPressed: ref + .read(earthquakeHistoryViewModelProvider.notifier) + .fetch, + child: const Text('再読み込み'), + ), + ], ), - ], - ), - ); - }, - loading: () => const SliverFillRemaining( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - '地震履歴を取得中です。', - ), - Padding( - padding: EdgeInsets.all(16), - child: CircularProgressIndicator.adaptive(), + ); + }, + loading: () => const SliverFillRemaining( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '地震履歴を取得中です。', + ), + Padding( + padding: EdgeInsets.all(16), + child: CircularProgressIndicator.adaptive(), + ), + ], ), - ], - ), - ), - ), + ), + ) ?? + const CircularProgressIndicator.adaptive(), ], ), ); @@ -138,58 +137,60 @@ class _ListBottomWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(earthquakeHistoryViewModelProvider); - return state.map( - data: (data) { - if (state.isLoading) { - return const Padding( - padding: EdgeInsets.all(24), - child: Center( - child: CircularProgressIndicator.adaptive(), - ), - ); - } - return const SizedBox.shrink(); - }, - error: (error) { - if (state.isLoading) { - return const Padding( - padding: EdgeInsets.all(24), - child: Center( - child: CircularProgressIndicator.adaptive(), - ), - ); - } - return Padding( - padding: const EdgeInsets.symmetric( - vertical: 40, - horizontal: 10, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - '地震履歴の取得中にエラーが発生しました。', - ), - // 再読み込み - FilledButton.tonal( - onPressed: () => ref - .read(earthquakeHistoryViewModelProvider.notifier) - .fetch(), - child: const Text('再読み込み'), - ), - Text( - error.error.toString(), - ), - ], - ), - ); - }, - loading: (data) => const Center( - child: Padding( - padding: EdgeInsets.all(24), - child: CircularProgressIndicator.adaptive(), - ), + const loading = Center( + child: Padding( + padding: EdgeInsets.all(24), + child: CircularProgressIndicator.adaptive(), ), ); + return state?.map( + data: (data) { + if (state.isLoading) { + return const Padding( + padding: EdgeInsets.all(24), + child: Center( + child: CircularProgressIndicator.adaptive(), + ), + ); + } + return const SizedBox.shrink(); + }, + error: (error) { + if (state.isLoading) { + return const Padding( + padding: EdgeInsets.all(24), + child: Center( + child: CircularProgressIndicator.adaptive(), + ), + ); + } + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 40, + horizontal: 10, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + '地震履歴の取得中にエラーが発生しました。', + ), + // 再読み込み + FilledButton.tonal( + onPressed: () => ref + .read(earthquakeHistoryViewModelProvider.notifier) + .fetch(), + child: const Text('再読み込み'), + ), + Text( + error.error.toString(), + ), + ], + ), + ); + }, + loading: (data) => loading, + ) ?? + loading; } } diff --git a/lib/feature/earthquake_history/viewmodel/earthquake_history_view_model.dart b/lib/feature/earthquake_history/viewmodel/earthquake_history_view_model.dart index 943987f2b..c7da1c7f3 100644 --- a/lib/feature/earthquake_history/viewmodel/earthquake_history_view_model.dart +++ b/lib/feature/earthquake_history/viewmodel/earthquake_history_view_model.dart @@ -12,6 +12,7 @@ import 'package:eqmonitor/core/provider/app_lifecycle.dart'; import 'package:eqmonitor/feature/earthquake_history/model/state/earthquake_history_item.dart'; import 'package:eqmonitor/feature/earthquake_history/use_case/earthquake_history_use_case.dart'; import 'package:eqmonitor/feature/home/features/telegram_ws/provider/telegram_provider.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -23,7 +24,7 @@ part 'earthquake_history_view_model.g.dart'; ) class EarthquakeHistoryViewModel extends _$EarthquakeHistoryViewModel { @override - AsyncValue> build() { + AsyncValue>? build() { // start listen telegram ws ref ..listen(telegramWsProvider, (previous, next) { @@ -38,22 +39,18 @@ class EarthquakeHistoryViewModel extends _$EarthquakeHistoryViewModel { fetch(isRefresh: true); } }); - return const AsyncData([]); + return null; } // state - final bool _includeTestTelegrams = false; - - Future loadIfNull() async { - // stateがnullなら、loadMoreを呼ぶ - if ((state.value ?? []).isEmpty) { - return fetch(isLoadMore: true); - } - } + final bool _includeTestTelegrams = kDebugMode; void onScrollPositionChanged(ScrollController controller) { // エラー発生時・リロード中は何もしない - if (state.hasError || state.isRefreshing || state.isReloading) { + if (state == null) { + return; + } + if (state!.hasError || state!.isRefreshing || state!.isReloading) { return; } if (controller.position.maxScrollExtent - controller.position.pixels < 20) { @@ -66,18 +63,18 @@ class EarthquakeHistoryViewModel extends _$EarthquakeHistoryViewModel { bool isRefresh = false, int limit = 50, }) async { - if (state.isLoading || state.isRefreshing || state.isReloading) { + if (state?.isLoading ?? false) { return; } - if (isLoadMore || isRefresh) { + if ((isLoadMore || isRefresh) && state != null) { state = const AsyncLoading>() - .copyWithPrevious(state); + .copyWithPrevious(state!); } else { state = const AsyncLoading>(); } // 処理開始 - state = await state.guardPlus(() async { - final offset = isRefresh ? 0 : state.asData?.value.length ?? 0; + state = await state!.guardPlus(() async { + final offset = isRefresh ? 0 : state?.asData?.value.length ?? 0; final useCase = ref.read(earthquakeHistoryUseCaseProvider); final result = await useCase.getEarthquakeHistory( limit: limit, @@ -86,57 +83,47 @@ class EarthquakeHistoryViewModel extends _$EarthquakeHistoryViewModel { ); final items = _toEarthquakeHistoryItem( result, - includeTestTelegrams: _includeTestTelegrams, - ); - final filteredItems = items.where( - (e) => e.telegrams.every( - (telegram) => telegram.status == TelegramStatus.normal, - ), ); + final filteredItems = _includeTestTelegrams + ? items + : items.where( + (e) => e.telegrams.every( + (telegram) => telegram.status == TelegramStatus.normal, + ), + ); if (isRefresh) { return filteredItems.toList(); } return [ - ...state.asData?.value ?? [], + ...state?.asData?.value ?? [], ...filteredItems, ]; }); } List _toEarthquakeHistoryItem( - Map> data, { - bool includeTestTelegrams = true, - }) { + Map> data, + ) { final result = []; data.forEach((eventId, telegrams) { //! EarthquakeData ! // 震度速報 final vxse51 = telegrams.firstWhereOrNull( - (e) => - e.type == TelegramType.vxse51 && - (_includeTestTelegrams || e.status == TelegramStatus.normal), + (e) => e.type == TelegramType.vxse51, ); final vxse52 = telegrams.firstWhereOrNull( - (e) => - e.type == TelegramType.vxse52 && - (_includeTestTelegrams || e.status == TelegramStatus.normal), + (e) => e.type == TelegramType.vxse52, ); final vxse53 = telegrams.firstWhereOrNull( - (e) => - e.type == TelegramType.vxse53 && - (_includeTestTelegrams || e.status == TelegramStatus.normal), + (e) => e.type == TelegramType.vxse53, ); // 顕著な地震の震源要素更新のお知らせ final vxse61 = telegrams.firstWhereOrNull( - (e) => - e.type == TelegramType.vxse61 && - (_includeTestTelegrams || e.status == TelegramStatus.normal), + (e) => e.type == TelegramType.vxse61, ); // 長周期地震動に関する観測情報 final vxse62 = telegrams.firstWhereOrNull( - (e) => - e.type == TelegramType.vxse62 && - (_includeTestTelegrams || e.status == TelegramStatus.normal), + (e) => e.type == TelegramType.vxse62, ); Earthquake? earthquake; if (vxse61 != null) { @@ -190,19 +177,13 @@ class EarthquakeHistoryViewModel extends _$EarthquakeHistoryViewModel { ); //! TsunamiData ! final vtse41 = telegrams.firstWhereOrNull( - (e) => - e.type == TelegramType.vtse41 && - (_includeTestTelegrams || e.status == TelegramStatus.normal), + (e) => e.type == TelegramType.vtse41, ); final vtse51 = telegrams.firstWhereOrNull( - (e) => - e.type == TelegramType.vtse51 && - (_includeTestTelegrams || e.status == TelegramStatus.normal), + (e) => e.type == TelegramType.vtse51, ); final vtse52 = telegrams.firstWhereOrNull( - (e) => - e.type == TelegramType.vtse52 && - (_includeTestTelegrams || e.status == TelegramStatus.normal), + (e) => e.type == TelegramType.vtse52, ); List? forecasts; if (vtse51 != null) { @@ -271,7 +252,7 @@ class EarthquakeHistoryViewModel extends _$EarthquakeHistoryViewModel { void _upsertTelegram(TelegramV3 telegram) { // 既に同一EventIDのTelegramが存在する場合は、上書きする - var data = state.value ?? []; + var data = state?.value ?? []; final index = data.indexWhere((e) => e.eventId == telegram.eventId); if (index != -1) { data[index] = data[index].copyWith( diff --git a/lib/feature/earthquake_history/viewmodel/earthquake_history_view_model.g.dart b/lib/feature/earthquake_history/viewmodel/earthquake_history_view_model.g.dart index fe5b9a4c1..7cf5be480 100644 --- a/lib/feature/earthquake_history/viewmodel/earthquake_history_view_model.g.dart +++ b/lib/feature/earthquake_history/viewmodel/earthquake_history_view_model.g.dart @@ -9,13 +9,13 @@ part of 'earthquake_history_view_model.dart'; // ************************************************************************** String _$earthquakeHistoryViewModelHash() => - r'1714b410d5fb414008e1246390b7f60e33eb275f'; + r'0dea4a5dd478d7c2048be9be50b23617f0d1dbd4'; /// See also [EarthquakeHistoryViewModel]. @ProviderFor(EarthquakeHistoryViewModel) final earthquakeHistoryViewModelProvider = NotifierProvider< EarthquakeHistoryViewModel, - AsyncValue>>.internal( + AsyncValue>?>.internal( EarthquakeHistoryViewModel.new, name: r'earthquakeHistoryViewModelProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') @@ -34,6 +34,6 @@ final earthquakeHistoryViewModelProvider = NotifierProvider< ); typedef _$EarthquakeHistoryViewModel - = Notifier>>; + = Notifier>?>; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/feature/earthquake_history_details/component/earthquake_map.dart b/lib/feature/earthquake_history_details/component/earthquake_map.dart index 98c4c8183..a4a97d41e 100644 --- a/lib/feature/earthquake_history_details/component/earthquake_map.dart +++ b/lib/feature/earthquake_history_details/component/earthquake_map.dart @@ -191,36 +191,38 @@ class EarthquakeMapWidget extends HookConsumerWidget { [], ); - final map = MaplibreMap( - initialCameraPosition: CameraPosition( - target: LatLng( - (bounds.northEast.lat + bounds.southWest.lat) / 2, - (bounds.northEast.lon + bounds.southWest.lon) / 2, + final map = RepaintBoundary( + child: MaplibreMap( + initialCameraPosition: CameraPosition( + target: LatLng( + (bounds.northEast.lat + bounds.southWest.lat) / 2, + (bounds.northEast.lon + bounds.southWest.lon) / 2, + ), + zoom: 6, ), - zoom: 6, - ), - styleString: path, - onMapCreated: (controller) => mapController.value = controller, - onStyleLoadedCallback: () async { - final controller = mapController.value!; + styleString: path, + onMapCreated: (controller) => mapController.value = controller, + onStyleLoadedCallback: () async { + final controller = mapController.value!; - await addImageFromAsset( - controller, - 'hypocenter', - 'assets/images/hypocenter.png', - ); - await _FillAction().init( - controller, - earthquake, - citiesItem, - regionsItem, - stations, - ); - isInitialized.value = true; - return; - }, - rotateGesturesEnabled: false, - tiltGesturesEnabled: false, + await addImageFromAsset( + controller, + 'hypocenter', + 'assets/images/hypocenter.png', + ); + await _FillAction().init( + controller, + earthquake, + citiesItem, + regionsItem, + stations, + ); + isInitialized.value = true; + return; + }, + rotateGesturesEnabled: false, + tiltGesturesEnabled: false, + ), ); return Stack( diff --git a/lib/feature/earthquake_history_details/screen/earthquake_history_details.dart b/lib/feature/earthquake_history_details/screen/earthquake_history_details.dart index 6f7e9c851..fe0976ba0 100644 --- a/lib/feature/earthquake_history_details/screen/earthquake_history_details.dart +++ b/lib/feature/earthquake_history_details/screen/earthquake_history_details.dart @@ -35,7 +35,7 @@ class EarthquakeHistoryDetailsPage extends HookConsumerWidget { // 当該データがアレばOK final data = ref .watch(earthquakeHistoryViewModelProvider) - .value + ?.value ?.firstWhereOrNull((e) => e.eventId == eventId); if (data == null) { return const Scaffold( @@ -60,14 +60,12 @@ class EarthquakeHistoryDetailsPage extends HookConsumerWidget { child: CircularProgressIndicator.adaptive(), ) else - RepaintBoundary( - child: EarthquakeMapWidget( - item: data, - showIntensityIcon: true, - mapData: zoomCachedMapData, - registerNavigateToHome: (func) => - navigateToHomeFunction.value = func, - ), + EarthquakeMapWidget( + item: data, + showIntensityIcon: true, + mapData: zoomCachedMapData, + registerNavigateToHome: (func) => + navigateToHomeFunction.value = func, ), Stack( children: [ diff --git a/lib/feature/eew_detailed_history/eew_detailed_history_screen.dart b/lib/feature/eew_detailed_history/eew_detailed_history_screen.dart index 8489a2779..6025afb09 100644 --- a/lib/feature/eew_detailed_history/eew_detailed_history_screen.dart +++ b/lib/feature/eew_detailed_history/eew_detailed_history_screen.dart @@ -24,7 +24,7 @@ class EewDetailedHistoryScreen extends HookConsumerWidget { final data = ref .watch(earthquakeHistoryViewModelProvider) - .value + ?.value ?.firstWhereOrNull((e) => e.eventId == eventId); final eews = data?.eewList.reversed.toList(); if (eews == null) { diff --git a/lib/feature/home/component/sheet/debug_widget.dart b/lib/feature/home/component/sheet/debug_widget.dart deleted file mode 100644 index 860b2856a..000000000 --- a/lib/feature/home/component/sheet/debug_widget.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'package:eqmonitor/core/router/router.dart'; -import 'package:eqmonitor/feature/home/component/kmoni/kmoni_settings_dialog.dart'; -import 'package:eqmonitor/feature/home/component/sheet/sheet_header.dart'; -import 'package:eqmonitor/feature/home/features/telegram_ws/provider/telegram_provider.dart'; -import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; - -class DebugWidget extends ConsumerWidget { - const DebugWidget({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final theme = Theme.of(context); - - return Card( - margin: const EdgeInsets.all(4), - elevation: 1, - shadowColor: Colors.transparent, - // 角丸にして Border - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: BorderSide( - color: theme.dividerColor.withOpacity(0.6), - width: 0, - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SheetHeader(title: 'デバッグメニュー'), - ListTile( - title: const Text('Request Sample EEW Telegram'), - subtitle: const Text('EventID: 20171213112000'), - leading: const Icon(Icons.send), - onTap: ref.read(telegramWsProvider.notifier).requestSample, - ), - ListTile( - title: const Text('ログ'), - leading: const Icon(Icons.list), - onTap: () => context.push(const TalkerRoute().location), - ), - ListTile( - title: const Text('Kmoni Debug Setting'), - leading: const Icon(Icons.settings), - onTap: () => showDialog( - context: context, - builder: (context) => const KmoniSettingsDialogWidget(), - ), - ), - ListTile( - title: const Text('重大な通知権限'), - leading: const Icon(Icons.notifications_active), - onTap: () async { - { - final result = - await FirebaseMessaging.instance.requestPermission( - criticalAlert: true, - ); - if (context.mounted) { - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('重大な通知権限 (FirebaseMessaging)'), - content: Text(result.toString()), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('OK'), - ), - ], - ), - ); - } - } - }, - ), - ListTile( - title: const Text('Subscribe to YumNumm Notify'), - leading: const Icon(Icons.notifications), - onTap: () async { - try { - await FirebaseMessaging.instance - .subscribeToTopic('yumnumm-notify'); - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Subscribed to YumNumm Notify'), - ), - ); - } - } on Exception catch (e) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text('Failed to subscribe to YumNumm Notify $e'), - ), - ); - } - } - }, - ), - ], - ), - ), - ); - } -} diff --git a/lib/feature/home/component/sheet/earthquake_history_widget.dart b/lib/feature/home/component/sheet/earthquake_history_widget.dart index 4da8563b0..58ea075fb 100644 --- a/lib/feature/home/component/sheet/earthquake_history_widget.dart +++ b/lib/feature/home/component/sheet/earthquake_history_widget.dart @@ -19,14 +19,19 @@ class EarthquakeHistorySheetWidget extends HookConsumerWidget { WidgetsBinding.instance.endOfFrame.then( (_) => // 初回読み込みを行う - ref - .read(earthquakeHistoryViewModelProvider.notifier) - .loadIfNull(), + ref.read(earthquakeHistoryViewModelProvider.notifier).fetch(), ); return null; }, [key], ); + const loading = Center( + child: Padding( + padding: EdgeInsets.all(24), + child: CircularProgressIndicator.adaptive(), + ), + ); + return BorderedContainer( elevation: 1, padding: const EdgeInsets.symmetric( @@ -38,68 +43,65 @@ class EarthquakeHistorySheetWidget extends HookConsumerWidget { const SheetHeader( title: '地震履歴', ), - ...state.when( - data: (data) { - // 地震情報を持つもののうち上から3つのみ表示 - final items = data - .where( - (e) => - e.earthquake.earthquake != null || - e.earthquake.intensity != null, - ) - .take(3) - .toList(); - return [ - for (final item in items) - EarthquakeHistoryTileWidget( - item: item, - onTap: (p0) => context.push( - EarthquakeHistoryDetailsRoute(p0.eventId).location, - ), - showBackgroundColor: false, - ), - ]; - }, - error: (error, stackTrace) { - // dataがある場合にはそれを表示 - if (state.hasValue) { - final data = state.value!; - // 上から3つのみ表示 - final items = data.take(3).toList(); - - return [ - for (final item in items) - EarthquakeHistoryTileWidget( - showBackgroundColor: false, - item: item, - onTap: (p0) => context.push( - EarthquakeHistoryDetailsRoute(p0.eventId).location, + ...state?.when( + data: (data) { + // 地震情報を持つもののうち上から3つのみ表示 + final items = data + .where( + (e) => + e.earthquake.earthquake != null || + e.earthquake.intensity != null, + ) + .take(3) + .toList(); + return [ + for (final item in items) + EarthquakeHistoryTileWidget( + item: item, + onTap: (p0) => context.push( + EarthquakeHistoryDetailsRoute(p0.eventId).location, + ), + showBackgroundColor: false, ), + ]; + }, + error: (error, stackTrace) { + // dataがある場合にはそれを表示 + if (state.hasValue) { + final data = state.value!; + // 上から3つのみ表示 + final items = data.take(3).toList(); + + return [ + for (final item in items) + EarthquakeHistoryTileWidget( + showBackgroundColor: false, + item: item, + onTap: (p0) => context.push( + EarthquakeHistoryDetailsRoute(p0.eventId).location, + ), + ), + ]; + } + return [ + const Text( + '地震履歴の取得中にエラーが発生しました。', ), - ]; - } - return [ - const Text( - '地震履歴の取得中にエラーが発生しました。', - ), - FilledButton.tonal( - onPressed: ref - .read(earthquakeHistoryViewModelProvider.notifier) - .fetch, - child: const Text('再読み込み'), - ), - ]; - }, - loading: () => [ - const Text( - '地震履歴を取得中です。', - ), - const Padding( - padding: EdgeInsets.all(8), - child: CircularProgressIndicator.adaptive(), - ), - ], - ), + FilledButton.tonal( + onPressed: ref + .read(earthquakeHistoryViewModelProvider.notifier) + .fetch, + child: const Text('再読み込み'), + ), + ]; + }, + loading: () => [ + loading, + ], + ) ?? + [ + loading, + ], Row( children: [ const Spacer(), diff --git a/lib/feature/home/features/eew/eew_provider.dart b/lib/feature/home/features/eew/eew_provider.dart index 97c72f69f..5ad976828 100644 --- a/lib/feature/home/features/eew/eew_provider.dart +++ b/lib/feature/home/features/eew/eew_provider.dart @@ -19,7 +19,7 @@ class EewTelegram extends _$EewTelegram { @override List build() { ref.listen(earthquakeHistoryViewModelProvider, (previous, next) { - for (final item in (next.value ?? []) + for (final item in (next?.value ?? []) .where((e) => e.latestEew != null)) { if (_shouldShow(item)) { upsert(item); diff --git a/lib/feature/home/features/eew/eew_provider.g.dart b/lib/feature/home/features/eew/eew_provider.g.dart index 94c19fb30..c197bb01f 100644 --- a/lib/feature/home/features/eew/eew_provider.g.dart +++ b/lib/feature/home/features/eew/eew_provider.g.dart @@ -8,7 +8,7 @@ part of 'eew_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$eewTelegramHash() => r'02131229e453d7aa36e1d82018fcf5641c4b6e34'; +String _$eewTelegramHash() => r'dd2b2c0b3b77b1e27b2b9ef9b729d09777b9aa8c'; /// See also [EewTelegram]. @ProviderFor(EewTelegram) diff --git a/lib/feature/home/features/kmoni/viewmodel/kmoni_view_model.dart b/lib/feature/home/features/kmoni/viewmodel/kmoni_view_model.dart index 6705110ec..dc672dfea 100644 --- a/lib/feature/home/features/kmoni/viewmodel/kmoni_view_model.dart +++ b/lib/feature/home/features/kmoni/viewmodel/kmoni_view_model.dart @@ -10,7 +10,6 @@ import 'package:eqmonitor/feature/home/features/kmoni/use_case/kmoni_use_case.da import 'package:eqmonitor/feature/home/features/kmoni/viewmodel/kmoni_view_settings.dart'; import 'package:eqmonitor/feature/home/features/kmoni_observation_points/provider/kmoni_observation_points_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:talker_flutter/talker_flutter.dart'; part 'kmoni_view_model.g.dart'; @@ -18,8 +17,6 @@ part 'kmoni_view_model.g.dart'; class KmoniViewModel extends _$KmoniViewModel { @override KmoniViewModelState build() { - _useCase = ref.watch(kmoniUseCaseProvider); - _talker = ref.watch(talkerProvider); ref.listen(appLifeCycleProvider, (_, next) async { if (next == AppLifecycleState.resumed) { state = state.copyWith( @@ -44,9 +41,6 @@ class KmoniViewModel extends _$KmoniViewModel { ); } - late final KmoniUseCase _useCase; - late final Talker _talker; - /// 画像取得タイマー Timer _kmoniFetchTimer = Timer.periodic( const Duration(seconds: 1), @@ -100,10 +94,10 @@ class KmoniViewModel extends _$KmoniViewModel { } try { - final result = await _useCase.fetchRealtimeShindo( - now, - obsPoints: ref.read(kmoniObservationPointsProvider) ?? [], - ); + final result = await ref.read(kmoniUseCaseProvider).fetchRealtimeShindo( + now, + obsPoints: ref.read(kmoniObservationPointsProvider) ?? [], + ); state = state.copyWith( lastUpdatedAt: now, analyzedPoints: result, @@ -124,18 +118,20 @@ class KmoniViewModel extends _$KmoniViewModel { } Future syncDelayWithKmoni() async { + final useCase = ref.read(kmoniUseCaseProvider); + final talker = ref.read(talkerProvider); if (state.isDelayAdjusting) { return state.delay!; } state = state.copyWith(isDelayAdjusting: true); try { // kmoniから現在時刻を取得 - final firstDateTime = await _useCase.getLatestDataTime(); + final firstDateTime = await useCase.getLatestDataTime(); var latestDataTime = firstDateTime; // 変わるまで200msごとに取得 while (true) { await Future.delayed(const Duration(milliseconds: 200)); - latestDataTime = await _useCase.getLatestDataTime(); + latestDataTime = await useCase.getLatestDataTime(); if (latestDataTime != firstDateTime) { break; } @@ -153,13 +149,13 @@ class KmoniViewModel extends _$KmoniViewModel { const Duration(seconds: 1), (_) => _update(), ); - _talker.logTyped( + talker.logTyped( KmoniLog('遅延調整を行いました ${diff.inMicroseconds / 1000}ms'), ); return diff; // ignore: avoid_catches_without_on_clauses } catch (e) { - _talker.logTyped( + talker.logTyped( KmoniLog('遅延調整失敗 $e'), ); state = state.copyWith(isDelayAdjusting: false); diff --git a/lib/feature/home/features/kmoni/viewmodel/kmoni_view_model.g.dart b/lib/feature/home/features/kmoni/viewmodel/kmoni_view_model.g.dart index aa7f95547..94d7566a4 100644 --- a/lib/feature/home/features/kmoni/viewmodel/kmoni_view_model.g.dart +++ b/lib/feature/home/features/kmoni/viewmodel/kmoni_view_model.g.dart @@ -8,7 +8,7 @@ part of 'kmoni_view_model.dart'; // RiverpodGenerator // ************************************************************************** -String _$kmoniViewModelHash() => r'990e2679e26210893579b065e242361b5d6d94ed'; +String _$kmoniViewModelHash() => r'abbec603f2734af050631ff5964206728fa5f354'; /// See also [KmoniViewModel]. @ProviderFor(KmoniViewModel) diff --git a/lib/feature/home/features/kmoni/viewmodel/kmoni_view_settings.dart b/lib/feature/home/features/kmoni/viewmodel/kmoni_view_settings.dart index df42411e4..32359b13a 100644 --- a/lib/feature/home/features/kmoni/viewmodel/kmoni_view_settings.dart +++ b/lib/feature/home/features/kmoni/viewmodel/kmoni_view_settings.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:eqmonitor/core/provider/shared_preferences.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:shared_preferences/shared_preferences.dart'; part 'kmoni_view_settings.freezed.dart'; part 'kmoni_view_settings.g.dart'; @@ -33,7 +32,6 @@ class KmoniSettingsState with _$KmoniSettingsState { class KmoniSettings extends _$KmoniSettings { @override KmoniSettingsState build() { - _prefs = ref.watch(sharedPreferencesProvider); ref.listenSelf((_, __) => _save()); final result = _loadFromPrefs(); if (result != null) { @@ -44,10 +42,9 @@ class KmoniSettings extends _$KmoniSettings { } static const _prefsKey = '_kmoni_settings'; - late final SharedPreferences _prefs; KmoniSettingsState? _loadFromPrefs() { - final json = _prefs.getString(_prefsKey); + final json = ref.read(sharedPreferencesProvider).getString(_prefsKey); if (json == null) { return null; } @@ -61,12 +58,10 @@ class KmoniSettings extends _$KmoniSettings { } } - Future _save() async { - await _prefs.setString( - _prefsKey, - jsonEncode(state.toJson()), - ); - } + Future _save() async => ref.read(sharedPreferencesProvider).setString( + _prefsKey, + jsonEncode(state.toJson()), + ); void toggleIsUpper0Only() { state = state.copyWith( diff --git a/lib/feature/home/features/kmoni/viewmodel/kmoni_view_settings.g.dart b/lib/feature/home/features/kmoni/viewmodel/kmoni_view_settings.g.dart index c866207d6..fb7a3dd5c 100644 --- a/lib/feature/home/features/kmoni/viewmodel/kmoni_view_settings.g.dart +++ b/lib/feature/home/features/kmoni/viewmodel/kmoni_view_settings.g.dart @@ -37,7 +37,7 @@ Map _$$KmoniSettingsStateImplToJson( // RiverpodGenerator // ************************************************************************** -String _$kmoniSettingsHash() => r'16b29280c64bcd4a0faccb9a501a56c02c647e9f'; +String _$kmoniSettingsHash() => r'3a624489de7f8bd258d81cbd5e30d570bbe2951a'; /// See also [KmoniSettings]. @ProviderFor(KmoniSettings) diff --git a/lib/feature/home/features/telegram_url/provider/telegram_url_provider.dart b/lib/feature/home/features/telegram_url/provider/telegram_url_provider.dart index cdc6ec063..a875ab6a0 100644 --- a/lib/feature/home/features/telegram_url/provider/telegram_url_provider.dart +++ b/lib/feature/home/features/telegram_url/provider/telegram_url_provider.dart @@ -4,7 +4,6 @@ import 'package:eqmonitor/core/provider/shared_preferences.dart'; import 'package:eqmonitor/env/env.dart'; import 'package:eqmonitor/feature/home/features/telegram_url/model/telegram_url_model.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:shared_preferences/shared_preferences.dart'; part 'telegram_url_provider.g.dart'; @@ -12,14 +11,11 @@ part 'telegram_url_provider.g.dart'; class TelegramUrl extends _$TelegramUrl { @override TelegramUrlModel build() { - _prefs = ref.watch(sharedPreferencesProvider); final result = _load(); if (result != null) { return result; } - ref.listenSelf((_, __) => _save()); - return TelegramUrlModel( restApiUrl: Env.restApiUrl, wsApiUrl: Env.wsApiUrl, @@ -27,14 +23,13 @@ class TelegramUrl extends _$TelegramUrl { ); } - late final SharedPreferences _prefs; - static const _key = 'telegram_url'; - Future _save() async => _prefs.setString(_key, jsonEncode(state)); + Future _save() async => + ref.read(sharedPreferencesProvider).setString(_key, jsonEncode(state)); TelegramUrlModel? _load() { - final jsonString = _prefs.getString(_key); + final jsonString = ref.read(sharedPreferencesProvider).getString(_key); if (jsonString == null) { return null; } @@ -48,6 +43,8 @@ class TelegramUrl extends _$TelegramUrl { } } - // ignore: avoid_setters_without_getters - set setState(TelegramUrlModel model) => state = model; + void updateRestUrl(String url) { + state = state.copyWith(restApiUrl: url); + _save(); + } } diff --git a/lib/feature/home/features/telegram_url/provider/telegram_url_provider.g.dart b/lib/feature/home/features/telegram_url/provider/telegram_url_provider.g.dart index 9311534e2..c13ff0cda 100644 --- a/lib/feature/home/features/telegram_url/provider/telegram_url_provider.g.dart +++ b/lib/feature/home/features/telegram_url/provider/telegram_url_provider.g.dart @@ -8,7 +8,7 @@ part of 'telegram_url_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$telegramUrlHash() => r'b414a20dd3741b0fc51285b9bdbf1396b959f9b3'; +String _$telegramUrlHash() => r'31e58fbed6119af17a5f5b6bbe6cdc40b8717a76'; /// See also [TelegramUrl]. @ProviderFor(TelegramUrl) diff --git a/lib/feature/home/features/telegram_ws/provider/telegram_provider.dart b/lib/feature/home/features/telegram_ws/provider/telegram_provider.dart index 7b28f028e..8068e36fc 100644 --- a/lib/feature/home/features/telegram_ws/provider/telegram_provider.dart +++ b/lib/feature/home/features/telegram_ws/provider/telegram_provider.dart @@ -5,8 +5,6 @@ import 'package:eqmonitor/core/provider/log/talker.dart'; import 'package:eqmonitor/feature/home/features/telegram_ws/model/socket_status.dart'; import 'package:eqmonitor/feature/home/features/telegram_ws/provider/telegram_socket_io.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:socket_io_client/socket_io_client.dart'; -import 'package:talker_flutter/talker_flutter.dart'; part 'telegram_provider.g.dart'; @@ -15,33 +13,29 @@ part 'telegram_provider.g.dart'; dependencies: [telegramSocketIo], ) class TelegramWs extends _$TelegramWs { - late final Talker _talker; - late final Socket _socket; - @override Stream build() { - _talker = ref.watch(talkerProvider); - _socket = ref.watch(telegramSocketIoProvider); + final talker = ref.watch(talkerProvider); + final socket = ref.watch(telegramSocketIoProvider); final stream = StreamController(); ref.onDispose(stream.close); - _socket.on('data', (data) { + socket.on('data', (data) { try { - _talker.logTyped(TelegramWebSocketLog('Data received: $data')); + talker.logTyped(TelegramWebSocketLog('Data received: $data')); final payload = data as Map; final telegram = TelegramV3.fromJson(payload); stream.add(telegram); // ignore: avoid_catches_without_on_clauses } catch (error, stackTrace) { - _talker.handle(error, stackTrace); + talker.handle(error, stackTrace); } }); return stream.stream; } - void requestSample() { - _socket.send(['sample/vxse45']); - } + void requestSample() => + ref.read(telegramSocketIoProvider).send(['sample/vxse45']); } @Riverpod(keepAlive: true) diff --git a/lib/feature/home/features/telegram_ws/provider/telegram_provider.g.dart b/lib/feature/home/features/telegram_ws/provider/telegram_provider.g.dart index 270c4ca7b..3d2b3aa2c 100644 --- a/lib/feature/home/features/telegram_ws/provider/telegram_provider.g.dart +++ b/lib/feature/home/features/telegram_ws/provider/telegram_provider.g.dart @@ -8,7 +8,7 @@ part of 'telegram_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$telegramWsHash() => r'321380dfa157630caf80049d90292e22d483c95c'; +String _$telegramWsHash() => r'937d6559a10e61e282eb1f8ffd32f388653895a2'; /// See also [TelegramWs]. @ProviderFor(TelegramWs) diff --git a/lib/feature/home/features/telegram_ws/provider/telegram_socket_io.dart b/lib/feature/home/features/telegram_ws/provider/telegram_socket_io.dart index 1309cdf05..ff619f6cb 100644 --- a/lib/feature/home/features/telegram_ws/provider/telegram_socket_io.dart +++ b/lib/feature/home/features/telegram_ws/provider/telegram_socket_io.dart @@ -13,7 +13,7 @@ part 'telegram_socket_io.g.dart'; @Riverpod(keepAlive: true, dependencies: []) Socket telegramSocketIo(TelegramSocketIoRef ref) { final talker = ref.watch(talkerProvider); - final url = ref.watch(telegramUrlProvider).wsApiUrl; + final url = ref.watch(telegramUrlProvider.select((v) => v.wsApiUrl)); final authorization = ref.watch(telegramUrlProvider).apiAuthorization ?? ''; final socket = io( url, diff --git a/lib/feature/home/features/telegram_ws/provider/telegram_socket_io.g.dart b/lib/feature/home/features/telegram_ws/provider/telegram_socket_io.g.dart index 23178bcba..6f025d84a 100644 --- a/lib/feature/home/features/telegram_ws/provider/telegram_socket_io.g.dart +++ b/lib/feature/home/features/telegram_ws/provider/telegram_socket_io.g.dart @@ -8,7 +8,7 @@ part of 'telegram_socket_io.dart'; // RiverpodGenerator // ************************************************************************** -String _$telegramSocketIoHash() => r'faefda0ee37828927b22d60ac71e40ec18c76ffe'; +String _$telegramSocketIoHash() => r'499919fc0588ac1b870dbe794df5ad69b9204426'; /// See also [telegramSocketIo]. @ProviderFor(telegramSocketIo) diff --git a/lib/feature/settings/children/config/debug/api_endpoint_selector/api_endpoint_selector_page.dart b/lib/feature/settings/children/config/debug/api_endpoint_selector/api_endpoint_selector_page.dart new file mode 100644 index 000000000..53edfebb8 --- /dev/null +++ b/lib/feature/settings/children/config/debug/api_endpoint_selector/api_endpoint_selector_page.dart @@ -0,0 +1,42 @@ +import 'package:eqmonitor/env/env.dart'; +import 'package:eqmonitor/feature/home/features/telegram_url/provider/telegram_url_provider.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class ApiEndpointSelectorPage extends ConsumerWidget { + const ApiEndpointSelectorPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final defaultUrl = Env.restApiUrl; + final developUrl = defaultUrl.replaceAll('api.', 'dev.api.'); + final state = ref.watch(telegramUrlProvider.select((v) => v.restApiUrl)); + return Scaffold( + appBar: AppBar( + title: const Text('API Endpoint Selector'), + ), + body: ListView( + children: [ + RadioListTile.adaptive( + title: const Text('Default'), + subtitle: Text(defaultUrl), + value: defaultUrl, + groupValue: state, + onChanged: (value) { + ref.read(telegramUrlProvider.notifier).updateRestUrl(value!); + }, + ), + RadioListTile.adaptive( + title: const Text('DEV'), + value: developUrl, + subtitle: Text(developUrl), + groupValue: state, + onChanged: (value) { + ref.read(telegramUrlProvider.notifier).updateRestUrl(value!); + }, + ), + ], + ), + ); + } +} diff --git a/lib/feature/settings/children/config/debug/debugger_page.dart b/lib/feature/settings/children/config/debug/debugger_page.dart index eb7f65d63..3f0c8495e 100644 --- a/lib/feature/settings/children/config/debug/debugger_page.dart +++ b/lib/feature/settings/children/config/debug/debugger_page.dart @@ -1,5 +1,11 @@ -import 'package:eqmonitor/feature/home/component/sheet/debug_widget.dart'; +import 'package:eqmonitor/core/router/router.dart'; +import 'package:eqmonitor/feature/home/component/kmoni/kmoni_settings_dialog.dart'; +import 'package:eqmonitor/feature/home/component/sheet/sheet_header.dart'; +import 'package:eqmonitor/feature/home/features/telegram_url/provider/telegram_url_provider.dart'; +import 'package:eqmonitor/feature/home/features/telegram_ws/provider/telegram_provider.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; class DebuggerPage extends ConsumerWidget { @@ -13,9 +19,102 @@ class DebuggerPage extends ConsumerWidget { ), body: ListView( children: const [ - DebugWidget(), + _DebugWidget(), ], ), ); } } + +class _DebugWidget extends ConsumerWidget { + const _DebugWidget(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = Theme.of(context); + + return Card( + margin: const EdgeInsets.all(4), + elevation: 1, + shadowColor: Colors.transparent, + // 角丸にして Border + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: BorderSide( + color: theme.dividerColor.withOpacity(0.6), + width: 0, + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SheetHeader(title: 'デバッグメニュー'), + ListTile( + title: const Text('Request Sample EEW Telegram'), + subtitle: const Text('EventID: 20171213112000'), + leading: const Icon(Icons.send), + onTap: ref.read(telegramWsProvider.notifier).requestSample, + ), + ListTile( + title: const Text('ログ'), + leading: const Icon(Icons.list), + onTap: () => context.push(const TalkerRoute().location), + ), + ListTile( + title: const Text('強震モニタ'), + leading: const Icon(Icons.settings), + onTap: () => showDialog( + context: context, + builder: (context) => const KmoniSettingsDialogWidget(), + ), + ), + ListTile( + title: const Text('重大な通知権限'), + leading: const Icon(Icons.notifications_active), + onTap: () async { + { + final result = + await FirebaseMessaging.instance.requestPermission( + criticalAlert: true, + ); + if (context.mounted) { + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('重大な通知権限 (FirebaseMessaging)'), + content: Text(result.toString()), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], + ), + ); + } + } + }, + ), + ListTile( + title: const Text('REST APIエンドポイント'), + leading: const Icon(Icons.http), + subtitle: Text(ref.watch(telegramUrlProvider).restApiUrl), + onTap: () => + context.push(const ApiEndpointSelectorRoute().location), + ), + ListTile( + title: const Text('WebSocketエンドポイント'), + leading: const Icon(Icons.http), + subtitle: Text(ref.watch(telegramUrlProvider).wsApiUrl), + ), + ], + ), + ), + ); + } +} diff --git a/lib/feature/settings/settings_screen.dart b/lib/feature/settings/settings_screen.dart index 21876e367..6d1ec4004 100644 --- a/lib/feature/settings/settings_screen.dart +++ b/lib/feature/settings/settings_screen.dart @@ -8,7 +8,6 @@ import 'package:eqmonitor/core/provider/device_info.dart'; import 'package:eqmonitor/core/provider/firebase/firebase_messaging.dart'; import 'package:eqmonitor/core/provider/package_info.dart'; import 'package:eqmonitor/core/router/router.dart'; -import 'package:eqmonitor/feature/home/component/sheet/debug_widget.dart'; import 'package:eqmonitor/feature/home/features/debugger/debugger_provider.dart'; import 'package:eqmonitor/feature/settings/component/settings_section_header.dart'; import 'package:eqmonitor/gen/assets.gen.dart'; @@ -151,7 +150,11 @@ class SettingsScreen extends HookConsumerWidget { ), ), const Divider(), - const DebugWidget(), + ListTile( + title: const Text('デバッグメニュー'), + leading: const Icon(Icons.bug_report), + onTap: () => context.push(const DebuggerRoute().location), + ), ], ], ),