diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json index 74619516b..01a7fd6b7 100644 --- a/.dart_tool/package_config.json +++ b/.dart_tool/package_config.json @@ -25,6 +25,12 @@ "packageUri": "lib/", "languageVersion": "2.17" }, + { + "name": "app_settings", + "rootUri": "file:///Users/r_onoue/.pub-cache/hosted/pub.dev/app_settings-4.2.0", + "packageUri": "lib/", + "languageVersion": "2.12" + }, { "name": "archive", "rootUri": "file:///Users/r_onoue/.pub-cache/hosted/pub.dev/archive-3.3.7", @@ -908,7 +914,7 @@ "languageVersion": "3.0" } ], - "generated": "2023-05-20T11:56:02.665453Z", + "generated": "2023-05-20T15:00:13.562714Z", "generator": "pub", "generatorVersion": "3.0.1" } diff --git a/android/app/build.gradle b/android/app/build.gradle index db54346d1..9bdfdaa9b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -9,7 +9,7 @@ def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) -} +} def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { @@ -23,6 +23,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { + namespace "net.yumnmm.eqmonitor" compileSdk 33 compileOptions { @@ -94,9 +95,9 @@ dependencies { implementation 'com.google.firebase:firebase-perf' implementation 'com.google.firebase:firebase-messaging' implementation 'com.google.android.material:material:1.9.0-rc01' - implementation 'androidx.core:core-ktx:1.10.0' + implementation 'androidx.core:core-ktx:1.10.1' implementation 'androidx.work:work-runtime-ktx:2.8.1' - implementation 'androidx.core:core-splashscreen:1.0.0' + implementation 'androidx.core:core-splashscreen:1.0.1' } apply plugin: 'com.google.firebase.crashlytics' diff --git a/android/build.gradle b/android/build.gradle index 01870a718..6d17fb51e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:8.0.0' + classpath 'com.android.tools.build:gradle:7.4.2' classpath 'com.google.gms:google-services:4.3.14' classpath 'com.google.firebase:perf-plugin:1.4.2' @@ -31,7 +31,7 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 80d8b3b17..2aea7ed01 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Apr 17 19:02:42 JST 2023 +#Sat May 20 21:55:30 JST 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/ios/Podfile.lock b/ios/Podfile.lock index e30c26c7f..6e74d6f8d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - "app_settings (3.0.0+1)": + - Flutter - awesome_notifications (0.0.5): - Flutter - IosAwnCore (= 0.7.3) @@ -157,6 +159,7 @@ PODS: - FlutterMacOS DEPENDENCIES: + - app_settings (from `.symlinks/plugins/app_settings/ios`) - awesome_notifications (from `.symlinks/plugins/awesome_notifications/ios`) - awesome_notifications_fcm (from `.symlinks/plugins/awesome_notifications_fcm/ios`) - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) @@ -188,6 +191,8 @@ SPEC REPOS: - PromisesSwift EXTERNAL SOURCES: + app_settings: + :path: ".symlinks/plugins/app_settings/ios" awesome_notifications: :path: ".symlinks/plugins/awesome_notifications/ios" awesome_notifications_fcm: @@ -208,6 +213,7 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" SPEC CHECKSUMS: + app_settings: d103828c9f5d515c4df9ee754dabd443f7cedcf3 awesome_notifications: d63d9a25f126860f9a600850d99772237895b3ba awesome_notifications_fcm: 7e2d7ab4ca1826fe3a9a5ca96771ace73e05db48 Firebase: bd152f0f3d278c4060c5c71359db08ebcfd5a3e2 diff --git a/lib/common/provider/app_lifecycle.dart b/lib/common/provider/app_lifecycle.dart new file mode 100644 index 000000000..0e950c1be --- /dev/null +++ b/lib/common/provider/app_lifecycle.dart @@ -0,0 +1,32 @@ +// Flutter imports: +import 'package:flutter/widgets.dart'; + +// Package imports: +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +/// ref: https://zenn.dev/riscait/books/flutter-riverpod-practical-introduction/viewer/v2-app-lifecycle +final appLifecycleProvider = Provider((ref) { + final observer = _AppLifecycleObserver((value) => ref.state = value, ref); + + final binding = WidgetsBinding.instance..addObserver(observer); + ref.onDispose(() => binding.removeObserver(observer)); + + return AppLifecycleState.resumed; +}); + +class _AppLifecycleObserver extends WidgetsBindingObserver { + _AppLifecycleObserver(this._didChangeState, this.ref); + final Ref ref; + + final ValueChanged _didChangeState; + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + _didChangeState(state); + super.didChangeAppLifecycleState(state); + } +} + +extension AppLifecycleStateExtension on AppLifecycleState { + bool get isResumed => this == AppLifecycleState.resumed; +} diff --git a/lib/common/provider/notification/model/notification_state_model.dart b/lib/common/provider/notification/model/notification_state_model.dart new file mode 100644 index 000000000..1ad38b602 --- /dev/null +++ b/lib/common/provider/notification/model/notification_state_model.dart @@ -0,0 +1,21 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'notification_state_model.freezed.dart'; +part 'notification_state_model.g.dart'; + +@freezed +class NotificationStateModel with _$NotificationStateModel { + const factory NotificationStateModel({ + /// 通知権限が許可されているかどうか + @Default(false) bool isAccepted, + + /// 通知権限要求ダイアログを今後表示しないかどうか + @Default(false) bool neverShowNotificationPermissionDialog, + + /// FCM Token + String? fcmToken, + }) = _NotificationStateModel; + + factory NotificationStateModel.fromJson(Map json) => + _$NotificationStateModelFromJson(json); +} diff --git a/lib/common/provider/notification/provider/notification_provider.dart b/lib/common/provider/notification/provider/notification_provider.dart new file mode 100644 index 000000000..101c3eb9a --- /dev/null +++ b/lib/common/provider/notification/provider/notification_provider.dart @@ -0,0 +1,67 @@ +import 'dart:convert'; + +import 'package:app_settings/app_settings.dart'; +import 'package:eqmonitor/common/provider/app_lifecycle.dart'; +import 'package:eqmonitor/common/provider/notification/model/notification_state_model.dart'; +import 'package:eqmonitor/common/provider/shared_preferences.dart'; +import 'package:flutter/widgets.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +part 'notification_provider.g.dart'; + +@Riverpod(keepAlive: true) +class NotificationState extends _$NotificationState { + @override + NotificationStateModel build() { + _prefs = ref.read(sharedPreferencesProvider); + // アプリから一旦離れて戻ってきたときに + // 再度、通知権限の状態を取得する + ref.listen(appLifecycleProvider, (_, next) { + if (next == AppLifecycleState.resumed) { + init(); + } + }); + final res = _loadFromPrefs(); + if (res != null) {} + return const NotificationStateModel(); + } + + late final SharedPreferences _prefs; + + static const _key = 'NotificationState'; + + Future init() async { + /// 通知権限の状態を取得する + final isNotificationPermissionAllowed = await _notificationPermission(); + state = state.copyWith( + isAccepted: isNotificationPermissionAllowed, + ); + } + + // 通知権限のリクエスト + Future requestNotificationPermission() async { + final status = await Permission.notification.request(); + if (status != PermissionStatus.granted) { + // 通知設定の画面を開く + await AppSettings.openNotificationSettings(); + } + await init(); + } + + Future _notificationPermission() async { + final status = await Permission.notification.status; + return status == PermissionStatus.granted; + } + + NotificationStateModel? _loadFromPrefs() { + final data = _prefs.getString(_key); + if (data == null) { + return const NotificationStateModel(); + } + return NotificationStateModel.fromJson( + jsonDecode(data) as Map, + ); + } +} diff --git a/lib/feature/home/component/map/base_map.dart b/lib/feature/home/component/map/base_map.dart index 1a046a56d..015c2c1e8 100644 --- a/lib/feature/home/component/map/base_map.dart +++ b/lib/feature/home/component/map/base_map.dart @@ -12,6 +12,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; + +/// 各種マップのベースマップ +/// baseMapViewModelProviderからMapStateを取得すること class BaseMapWidget extends HookConsumerWidget { const BaseMapWidget({super.key}); diff --git a/lib/feature/home/component/map/view_model/base_map_viemwodel.dart b/lib/feature/home/component/map/view_model/base_map_viemwodel.dart index 16e0d6a94..19663adae 100644 --- a/lib/feature/home/component/map/view_model/base_map_viemwodel.dart +++ b/lib/feature/home/component/map/view_model/base_map_viemwodel.dart @@ -28,6 +28,11 @@ class BaseMapViewModel extends _$BaseMapViewModel { Size? _widgetSize; LatLng? _scaleStartedLatLng; + Animation? moveAnimation = null; + Animation? scaleAnimation = null; + + + void handleScaleStart(ScaleStartDetails details) { if (details.pointerCount == 1) { return; diff --git a/lib/main.dart b/lib/main.dart index f260cccca..e0e932ad7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,7 +4,6 @@ import 'dart:io'; import 'package:awesome_notifications_fcm/awesome_notifications_fcm.dart'; import 'package:eqmonitor/app.dart'; -import 'package:eqmonitor/common/provider/fcm_token.dart'; import 'package:eqmonitor/common/provider/shared_preferences.dart'; import 'package:eqmonitor/firebase_options.dart'; import 'package:firebase_core/firebase_core.dart'; @@ -13,6 +12,7 @@ import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; Future main() async { @@ -33,24 +33,27 @@ Future main() async { String? fcmToken; if (Platform.isAndroid || Platform.isIOS) { fcmToken = await getFirebaseMessagingToken(); - unawaited(() async { + await Permission.notification.request(); log('Firebase token: $fcmToken'); if (kDebugMode) { unawaited( FirebaseMessaging.instance.subscribeToTopic('config-developer'), ); + log('config-developer OK '); } await FirebaseMessaging.instance.subscribeToTopic('everyone'); + log('everyone OK'); await FirebaseMessaging.instance.subscribeToTopic('eew'); + log('eew OK'); await FirebaseMessaging.instance.subscribeToTopic('earthquake'); + log('earthquake OK'); }()); } return runApp( ProviderScope( overrides: [ sharedPreferencesProvider.overrideWithValue(prefs), - if (fcmToken != null) fcmTokenProvider.overrideWithValue(fcmToken), ], child: const App(), ), diff --git a/pubspec.lock b/pubspec.lock index e121a6bab..d5a851b98 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -33,6 +33,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.11.2" + app_settings: + dependency: "direct main" + description: + name: app_settings + sha256: "66715a323ac36d6c8201035ba678777c0d2ea869e4d7064300d95af10c3bb8cb" + url: "https://pub.dev" + source: hosted + version: "4.2.0" archive: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index dfc0cf5e0..9f225a27d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,7 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: + app_settings: ^4.2.0 awesome_notifications: ^0.7.4+1 awesome_notifications_fcm: ^0.7.3 collection: ^1.17.1