From 09c82da57f1b8d0315b3167e022a9f5064682267 Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Mon, 25 Aug 2025 23:41:54 +0200 Subject: [PATCH 1/5] add notification test --- .../lib/classes/notification_service.dart | 82 +++++++++++++++++++ covas_mobile/lib/main.dart | 2 + covas_mobile/lib/pages/ListItemMenu.dart | 15 ++++ .../Flutter/GeneratedPluginRegistrant.swift | 2 + covas_mobile/pubspec.lock | 40 +++++++++ covas_mobile/pubspec.yaml | 2 + 6 files changed, 143 insertions(+) create mode 100644 covas_mobile/lib/classes/notification_service.dart diff --git a/covas_mobile/lib/classes/notification_service.dart b/covas_mobile/lib/classes/notification_service.dart new file mode 100644 index 0000000..2b072b6 --- /dev/null +++ b/covas_mobile/lib/classes/notification_service.dart @@ -0,0 +1,82 @@ +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; + +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); + } + + /// 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', // id du canal + 'Évènements', // nom du canal + channelDescription: 'Notifications des évènements favoris', + importance: Importance.high, + priority: Priority.high, + ), + iOS: DarwinNotificationDetails(), + ), + androidScheduleMode: AndroidScheduleMode.inexactAllowWhileIdle, + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, + matchDateTimeComponents: DateTimeComponents.dateAndTime, + ); + } + + static Future showTestNotification() async { + await _notificationsPlugin.show( + 0, // id arbitraire + "Test Notification", + "Ceci est une notification de debug", + const NotificationDetails( + android: AndroidNotificationDetails( + 'debug_channel', + 'Debug Notifications', + channelDescription: 'Notifications de test pour debug', + importance: Importance.max, + priority: Priority.high, + ), + iOS: DarwinNotificationDetails(), + ), + ); + } + + /// 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/ListItemMenu.dart b/covas_mobile/lib/pages/ListItemMenu.dart index 21c411d..1c31526 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,20 @@ 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!), + ); + + NotificationService.showTestNotification(); + } 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 From f9934a0d5d5185411a57fac71c4827ba79048c9a Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Tue, 26 Aug 2025 20:29:55 +0200 Subject: [PATCH 2/5] debug ok --- covas_mobile/lib/pages/ListItemMenu.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/covas_mobile/lib/pages/ListItemMenu.dart b/covas_mobile/lib/pages/ListItemMenu.dart index 1c31526..7ce2daf 100644 --- a/covas_mobile/lib/pages/ListItemMenu.dart +++ b/covas_mobile/lib/pages/ListItemMenu.dart @@ -921,8 +921,6 @@ class _MyHomePageState extends State { "Ton évènement '${post.name}' commence dans 1 heure !", eventDate: DateTime.parse(post.startDate!), ); - - NotificationService.showTestNotification(); } else { NotificationService.cancel(post.id!); } From 8a7515aaf62911a59df263684dd03810dde4e253 Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Tue, 26 Aug 2025 20:34:01 +0200 Subject: [PATCH 3/5] add list in listitem --- covas_mobile/lib/pages/ListItemByOrganizers.dart | 12 ++++++++++++ covas_mobile/lib/pages/ListItemByTags.dart | 12 ++++++++++++ 2 files changed, 24 insertions(+) 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( From 5d9802d56e456becbdeb950aa9ece3efcedbfd3e Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Tue, 26 Aug 2025 20:57:50 +0200 Subject: [PATCH 4/5] ask permission --- .../lib/classes/notification_service.dart | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/covas_mobile/lib/classes/notification_service.dart b/covas_mobile/lib/classes/notification_service.dart index 2b072b6..c7a6f6e 100644 --- a/covas_mobile/lib/classes/notification_service.dart +++ b/covas_mobile/lib/classes/notification_service.dart @@ -19,6 +19,28 @@ class NotificationService { ); 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 @@ -42,24 +64,26 @@ class NotificationService { tz.TZDateTime.from(scheduledDate, tz.local), const NotificationDetails( android: AndroidNotificationDetails( - 'events_channel', // id du canal - 'Évènements', // nom du canal + 'events_channel', + 'Évènements', channelDescription: 'Notifications des évènements favoris', importance: Importance.high, priority: Priority.high, ), iOS: DarwinNotificationDetails(), ), - androidScheduleMode: AndroidScheduleMode.inexactAllowWhileIdle, + androidScheduleMode: AndroidScheduleMode + .inexactAllowWhileIdle, // évite l'erreur Exact Alarm uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime, matchDateTimeComponents: DateTimeComponents.dateAndTime, ); } + /// Notification immédiate (debug) static Future showTestNotification() async { await _notificationsPlugin.show( - 0, // id arbitraire + 0, "Test Notification", "Ceci est une notification de debug", const NotificationDetails( From 6e0b54a9250a876b79cc21aa493b09b9b0e8ed62 Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Wed, 27 Aug 2025 23:51:08 +0200 Subject: [PATCH 5/5] remove debug --- .../lib/classes/notification_service.dart | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/covas_mobile/lib/classes/notification_service.dart b/covas_mobile/lib/classes/notification_service.dart index c7a6f6e..3c4bb0c 100644 --- a/covas_mobile/lib/classes/notification_service.dart +++ b/covas_mobile/lib/classes/notification_service.dart @@ -2,6 +2,10 @@ 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(); @@ -65,8 +69,8 @@ class NotificationService { const NotificationDetails( android: AndroidNotificationDetails( 'events_channel', - 'Évènements', - channelDescription: 'Notifications des évènements favoris', + 'Events', + channelDescription: 'Favorite event notifications', importance: Importance.high, priority: Priority.high, ), @@ -80,25 +84,6 @@ class NotificationService { ); } - /// Notification immédiate (debug) - static Future showTestNotification() async { - await _notificationsPlugin.show( - 0, - "Test Notification", - "Ceci est une notification de debug", - const NotificationDetails( - android: AndroidNotificationDetails( - 'debug_channel', - 'Debug Notifications', - channelDescription: 'Notifications de test pour debug', - importance: Importance.max, - priority: Priority.high, - ), - iOS: DarwinNotificationDetails(), - ), - ); - } - /// Annule une notification planifiée static Future cancel(String eventId) async { await _notificationsPlugin.cancel(eventId.hashCode);