diff --git a/covas_mobile/lib/classes/notification_service.dart b/covas_mobile/lib/classes/notification_service.dart new file mode 100644 index 0000000..3c4bb0c --- /dev/null +++ b/covas_mobile/lib/classes/notification_service.dart @@ -0,0 +1,91 @@ +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:timezone/timezone.dart' as tz; +import 'package:timezone/data/latest_all.dart' as tz; + +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; + +class NotificationService { + static final FlutterLocalNotificationsPlugin _notificationsPlugin = + FlutterLocalNotificationsPlugin(); + + /// Initialisation (à appeler dans main()) + static Future initialize() async { + tz.initializeTimeZones(); + + const AndroidInitializationSettings androidInitSettings = + AndroidInitializationSettings('@mipmap/ic_launcher'); + + const InitializationSettings initSettings = InitializationSettings( + android: androidInitSettings, + iOS: DarwinInitializationSettings(), + ); + + await _notificationsPlugin.initialize(initSettings); + + // Demande les permissions au lancement + await requestPermissions(); + } + + /// Demander les permissions (Android 13+ et iOS) + static Future requestPermissions() async { + // Android 13+ + final androidImplementation = + _notificationsPlugin.resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>(); + await androidImplementation?.requestNotificationsPermission(); + + // iOS + final iosImplementation = + _notificationsPlugin.resolvePlatformSpecificImplementation< + IOSFlutterLocalNotificationsPlugin>(); + await iosImplementation?.requestPermissions( + alert: true, + badge: true, + sound: true, + ); + } + + /// Planifie une notification 1h avant l’évènement + static Future scheduleEventNotification({ + required String eventId, + required String title, + required String body, + required DateTime eventDate, + }) async { + final scheduledDate = eventDate.subtract(const Duration(hours: 1)); + + if (scheduledDate.isBefore(DateTime.now())) { + // Trop tard pour notifier + return; + } + + await _notificationsPlugin.zonedSchedule( + eventId.hashCode, // identifiant unique pour l’évènement + title, + body, + tz.TZDateTime.from(scheduledDate, tz.local), + const NotificationDetails( + android: AndroidNotificationDetails( + 'events_channel', + 'Events', + channelDescription: 'Favorite event notifications', + importance: Importance.high, + priority: Priority.high, + ), + iOS: DarwinNotificationDetails(), + ), + androidScheduleMode: AndroidScheduleMode + .inexactAllowWhileIdle, // évite l'erreur Exact Alarm + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, + matchDateTimeComponents: DateTimeComponents.dateAndTime, + ); + } + + /// Annule une notification planifiée + static Future cancel(String eventId) async { + await _notificationsPlugin.cancel(eventId.hashCode); + } +} diff --git a/covas_mobile/lib/main.dart b/covas_mobile/lib/main.dart index f01e1a3..cba36b6 100644 --- a/covas_mobile/lib/main.dart +++ b/covas_mobile/lib/main.dart @@ -5,10 +5,12 @@ import 'package:provider/provider.dart'; import 'pages/LoginDemo.dart'; import 'locale_provider.dart'; // <-- à adapter selon ton arborescence import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'classes/notification_service.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await MobileAds.instance.initialize(); + await NotificationService.initialize(); runApp( ChangeNotifierProvider( diff --git a/covas_mobile/lib/pages/ListItemByOrganizers.dart b/covas_mobile/lib/pages/ListItemByOrganizers.dart index 228bd15..65bc4f7 100644 --- a/covas_mobile/lib/pages/ListItemByOrganizers.dart +++ b/covas_mobile/lib/pages/ListItemByOrganizers.dart @@ -18,6 +18,7 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; import '../locale_provider.dart'; // +import '../classes/notification_service.dart'; // app starting point void main() async { @@ -232,6 +233,17 @@ class _MyHomePageState extends State { post.interested = result["interested"]; post.interestedCount = result["interested_count"]; }); + if (result["interested"] == true) { + NotificationService.scheduleEventNotification( + eventId: post.id!, + title: "Rappel évènement", + body: + "Ton évènement '${post.name}' commence dans 1 heure !", + eventDate: DateTime.parse(post.startDate!), + ); + } else { + NotificationService.cancel(post.id!); + } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( diff --git a/covas_mobile/lib/pages/ListItemByTags.dart b/covas_mobile/lib/pages/ListItemByTags.dart index 14c762e..1dbd8df 100644 --- a/covas_mobile/lib/pages/ListItemByTags.dart +++ b/covas_mobile/lib/pages/ListItemByTags.dart @@ -19,6 +19,7 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; import '../locale_provider.dart'; // +import '../classes/notification_service.dart'; // app starting point void main() async { @@ -234,6 +235,17 @@ class _MyHomePageState extends State { post.interested = result["interested"]; post.interestedCount = result["interested_count"]; }); + if (result["interested"] == true) { + NotificationService.scheduleEventNotification( + eventId: post.id!, + title: "Rappel évènement", + body: + "Ton évènement '${post.name}' commence dans 1 heure !", + eventDate: DateTime.parse(post.startDate!), + ); + } else { + NotificationService.cancel(post.id!); + } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( diff --git a/covas_mobile/lib/pages/ListItemMenu.dart b/covas_mobile/lib/pages/ListItemMenu.dart index 21c411d..7ce2daf 100644 --- a/covas_mobile/lib/pages/ListItemMenu.dart +++ b/covas_mobile/lib/pages/ListItemMenu.dart @@ -23,6 +23,7 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; import '../locale_provider.dart'; // Créé plus loin +import '../classes/notification_service.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -911,6 +912,18 @@ class _MyHomePageState extends State { post.interested = result["interested"]; post.interestedCount = result["interested_count"]; }); + + if (result["interested"] == true) { + NotificationService.scheduleEventNotification( + eventId: post.id!, + title: "Rappel évènement", + body: + "Ton évènement '${post.name}' commence dans 1 heure !", + eventDate: DateTime.parse(post.startDate!), + ); + } else { + NotificationService.cancel(post.id!); + } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( diff --git a/covas_mobile/macos/Flutter/GeneratedPluginRegistrant.swift b/covas_mobile/macos/Flutter/GeneratedPluginRegistrant.swift index 3f99c38..c21fe97 100644 --- a/covas_mobile/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/covas_mobile/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,7 @@ import FlutterMacOS import Foundation import file_selector_macos +import flutter_local_notifications import geolocator_apple import path_provider_foundation import shared_preferences_foundation @@ -14,6 +15,7 @@ import webview_flutter_wkwebview func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/covas_mobile/pubspec.lock b/covas_mobile/pubspec.lock index 311394d..000d6d5 100644 --- a/covas_mobile/pubspec.lock +++ b/covas_mobile/pubspec.lock @@ -145,6 +145,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.0" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" dio: dependency: transitive description: @@ -270,6 +278,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: "674173fd3c9eda9d4c8528da2ce0ea69f161577495a9cc835a2a4ecd7eadeb35" + url: "https://pub.dev" + source: hosted + version: "17.2.4" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af + url: "https://pub.dev" + source: hosted + version: "4.0.1" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" + url: "https://pub.dev" + source: hosted + version: "7.2.0" flutter_localizations: dependency: "direct main" description: flutter @@ -850,6 +882,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + timezone: + dependency: "direct main" + description: + name: timezone + sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" + url: "https://pub.dev" + source: hosted + version: "0.9.4" typed_data: dependency: transitive description: diff --git a/covas_mobile/pubspec.yaml b/covas_mobile/pubspec.yaml index 614bc53..65ffd79 100644 --- a/covas_mobile/pubspec.yaml +++ b/covas_mobile/pubspec.yaml @@ -36,6 +36,8 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. + flutter_local_notifications: ^17.2.0 + timezone: ^0.9.4 cupertino_icons: ^1.0.2 http: ^1.2.1 shared_preferences: ^2.2.3