331 lines
11 KiB
Dart
331 lines
11 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
import '../Helper/Logger.dart';
|
|
|
|
|
|
class LocalNotificationService {
|
|
static final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
|
|
static final FlutterLocalNotificationsPlugin _notificationsPlugin =
|
|
FlutterLocalNotificationsPlugin();
|
|
|
|
static const AndroidNotificationChannel channel = AndroidNotificationChannel(
|
|
'Nearle', // Channel ID
|
|
'Nearle Notification', // Channel name
|
|
description: 'Channel for Nearle notifications', // Channel description
|
|
importance: Importance.max,
|
|
playSound: true,
|
|
sound: RawResourceAndroidNotificationSound('notification_ring'),
|
|
enableVibration: true,
|
|
showBadge: true,
|
|
);
|
|
|
|
static Future<void> initialize(BuildContext context, String status) async {
|
|
// Create Android notification channel
|
|
await _notificationsPlugin
|
|
.resolvePlatformSpecificImplementation<
|
|
AndroidFlutterLocalNotificationsPlugin>()
|
|
?.createNotificationChannel(channel);
|
|
|
|
// Initialize local notifications
|
|
const InitializationSettings initializationSettings = InitializationSettings(
|
|
android: AndroidInitializationSettings("@mipmap/ic_launcher"),
|
|
iOS: DarwinInitializationSettings(
|
|
requestSoundPermission: true,
|
|
requestBadgePermission: true,
|
|
requestAlertPermission: true,
|
|
defaultPresentSound: true,
|
|
defaultPresentBadge: true,
|
|
defaultPresentBanner: true,
|
|
defaultPresentAlert: true,
|
|
defaultPresentList: true,
|
|
),
|
|
);
|
|
|
|
await _notificationsPlugin.initialize(
|
|
initializationSettings,
|
|
onDidReceiveNotificationResponse: (NotificationResponse response) async {
|
|
if (response.payload != null) {
|
|
await selectNotification(response.payload);
|
|
}
|
|
},
|
|
);
|
|
|
|
// Handle messages when the app is opened from a terminated state
|
|
RemoteMessage? initialMessage = await _firebaseMessaging.getInitialMessage();
|
|
if (initialMessage != null) {
|
|
await _handleInitialMessage(initialMessage);
|
|
}
|
|
|
|
// Handle foreground messages
|
|
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
|
|
logger.i('Received foreground message: ${message.data}');
|
|
await _handleMessage(message);
|
|
});
|
|
|
|
// Handle messages when the app is opened from a notification
|
|
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
|
|
logger.i('Message opened app: ${message.data}');
|
|
await _handleMessageOpenedApp(message);
|
|
});
|
|
}
|
|
|
|
static Future<void> _handleInitialMessage(RemoteMessage message) async {
|
|
if (message.notification != null) {
|
|
// Cancel the notification
|
|
final notificationId = int.parse(message.data['notification_id'] ?? '0');
|
|
await _notificationsPlugin.cancel(notificationId);
|
|
await display(message);
|
|
}
|
|
}
|
|
|
|
static Future<void> _handleMessage(RemoteMessage message) async {
|
|
if (message.notification != null) {
|
|
logger.i('Displaying notification: ${message.notification!.body}');
|
|
await display(message);
|
|
}
|
|
}
|
|
|
|
static Future<void> _handleMessageOpenedApp(RemoteMessage message) async {
|
|
try {
|
|
// Cancel the notification using the ID from message.data
|
|
final notificationId = int.parse(message.data['notification_id'] ?? '0');
|
|
await _notificationsPlugin.cancel(notificationId);
|
|
|
|
if (message.notification != null) {
|
|
// Navigator.of(Get.context!).push(
|
|
// MaterialPageRoute(
|
|
// builder: (BuildContext context) => HomeView(selectedIndex: 1),
|
|
// ),
|
|
// );
|
|
}
|
|
} on Exception catch (e) {
|
|
logger.e('Error in handleMessageOpenedApp: $e');
|
|
}
|
|
}
|
|
|
|
static Future<void> selectNotification(String? payload) async {
|
|
try {
|
|
if (payload != null) {
|
|
final decodedPayload = jsonDecode(payload);
|
|
final notificationId = int.parse(decodedPayload['id'] ?? '0');
|
|
|
|
// Cancel the specific notification
|
|
await _notificationsPlugin.cancel(notificationId);
|
|
|
|
// Navigate to the desired page
|
|
// Navigator.of(Get.context!).push(
|
|
// MaterialPageRoute(
|
|
// builder: (BuildContext context) => HomeView(selectedIndex: 1),
|
|
// ),
|
|
// );
|
|
}
|
|
} on Exception catch (e) {
|
|
logger.e('Error in selectNotification: $e');
|
|
}
|
|
}
|
|
|
|
static Future<String?> _downloadAndSaveImage(String imageUrl, String fileName) async {
|
|
try {
|
|
final directory = await getTemporaryDirectory();
|
|
final filePath = '${directory.path}/$fileName';
|
|
final response = await http.get(Uri.parse(imageUrl));
|
|
if (response.statusCode == 200) {
|
|
final file = File(filePath);
|
|
await file.writeAsBytes(response.bodyBytes);
|
|
logger.i('Image downloaded to: $filePath');
|
|
return filePath;
|
|
} else {
|
|
logger.e('Failed to download image: ${response.statusCode}');
|
|
return null;
|
|
}
|
|
} catch (e) {
|
|
logger.e('Error downloading image: $e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
static String? _extractImageUrl(RemoteMessage message) {
|
|
// General data field
|
|
String? imageUrl = message.data['image'] as String?;
|
|
|
|
// Android-specific
|
|
if (imageUrl == null && message.notification?.android?.imageUrl != null) {
|
|
imageUrl = message.notification!.android!.imageUrl;
|
|
}
|
|
|
|
// iOS-specific
|
|
if (imageUrl == null && message.notification?.apple?.imageUrl != null) {
|
|
imageUrl = message.notification!.apple!.imageUrl;
|
|
}
|
|
|
|
logger.i('Extracted image URL: $imageUrl');
|
|
return imageUrl;
|
|
}
|
|
|
|
static Future<void> display(RemoteMessage message) async {
|
|
if (message.notification != null) {
|
|
// Navigator.of(Get.context!).push(
|
|
// MaterialPageRoute(
|
|
// builder: (BuildContext context) => HomeView(selectedIndex: 1),
|
|
// ),
|
|
// );
|
|
}
|
|
|
|
try {
|
|
final id = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
|
|
|
// Create a payload that includes the notification ID
|
|
final payload = {
|
|
'id': id.toString(),
|
|
'data': message.data,
|
|
};
|
|
|
|
NotificationDetails notificationDetails;
|
|
|
|
// Extract image URL from data or android-specific field
|
|
final imageUrl = _extractImageUrl(message);
|
|
|
|
if (imageUrl != null && imageUrl.isNotEmpty) {
|
|
// Download the image
|
|
final imagePath = await _downloadAndSaveImage(imageUrl, 'notification_image.jpg');
|
|
if (imagePath != null) {
|
|
if (Platform.isAndroid) {
|
|
|
|
String? bodyText = message.notification?.body ?? message.data['body'] ?? '';
|
|
List<String>? lines = bodyText?.split('\n'); // or split on ',' or build manually
|
|
|
|
final inboxStyle = InboxStyleInformation(
|
|
lines ?? [],
|
|
contentTitle: message.notification?.title ?? message.data['title'],
|
|
summaryText: '', // optional, can be empty or a short summary
|
|
);
|
|
|
|
notificationDetails = NotificationDetails(
|
|
android: AndroidNotificationDetails(
|
|
'Nearle',
|
|
'Nearle Notification',
|
|
importance: Importance.max,
|
|
priority: Priority.high,
|
|
icon: 'notification',
|
|
playSound: true,
|
|
sound: RawResourceAndroidNotificationSound('notification_ring'),
|
|
enableVibration: true,
|
|
fullScreenIntent: true,
|
|
channelShowBadge: true,
|
|
ongoing: false, // Allow dismissal
|
|
autoCancel: true, // Cancel when tapped
|
|
styleInformation: inboxStyle,
|
|
),
|
|
iOS: DarwinNotificationDetails(
|
|
presentAlert: true,
|
|
presentBadge: true,
|
|
presentSound: true,
|
|
presentList: true,
|
|
presentBanner: true,
|
|
attachments: [
|
|
DarwinNotificationAttachment(imagePath),
|
|
],
|
|
),
|
|
);
|
|
} else {
|
|
// iOS or other platforms
|
|
notificationDetails = NotificationDetails(
|
|
android: AndroidNotificationDetails(
|
|
'Nearle',
|
|
'Nearle Notification',
|
|
importance: Importance.max,
|
|
priority: Priority.high,
|
|
icon: 'notification',
|
|
playSound: true,
|
|
sound: RawResourceAndroidNotificationSound('notification_ring'),
|
|
enableVibration: true,
|
|
fullScreenIntent: true,
|
|
channelShowBadge: true,
|
|
ongoing: false, // Allow dismissal
|
|
autoCancel: true, // Cancel when tapped
|
|
),
|
|
iOS: DarwinNotificationDetails(
|
|
presentAlert: true,
|
|
presentBadge: true,
|
|
presentSound: true,
|
|
presentList: true,
|
|
presentBanner: true,
|
|
attachments: [
|
|
DarwinNotificationAttachment(imagePath),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} else {
|
|
// Fallback if image download fails
|
|
notificationDetails = const NotificationDetails(
|
|
android: AndroidNotificationDetails(
|
|
'Nearle',
|
|
'Nearle Notification',
|
|
importance: Importance.max,
|
|
priority: Priority.high,
|
|
icon: 'notification',
|
|
playSound: true,
|
|
sound: RawResourceAndroidNotificationSound('notification_ring'),
|
|
enableVibration: true,
|
|
fullScreenIntent: true,
|
|
channelShowBadge: true,
|
|
ongoing: false, // Allow dismissal
|
|
autoCancel: true, // Cancel when tapped
|
|
),
|
|
iOS: DarwinNotificationDetails(
|
|
presentAlert: true,
|
|
presentBadge: true,
|
|
presentSound: true,
|
|
presentList: true,
|
|
presentBanner: true,
|
|
),
|
|
);
|
|
}
|
|
} else {
|
|
// No image in payload, use default notification details
|
|
notificationDetails = const NotificationDetails(
|
|
android: AndroidNotificationDetails(
|
|
'Nearle',
|
|
'Nearle Notification',
|
|
importance: Importance.max,
|
|
priority: Priority.high,
|
|
icon: 'notification',
|
|
playSound: true,
|
|
sound: RawResourceAndroidNotificationSound('notification_ring'),
|
|
enableVibration: true,
|
|
fullScreenIntent: true,
|
|
channelShowBadge: true,
|
|
ongoing: false, // Allow dismissal
|
|
autoCancel: true, // Cancel when tapped
|
|
),
|
|
iOS: DarwinNotificationDetails(
|
|
presentAlert: true,
|
|
presentBadge: true,
|
|
presentSound: true,
|
|
presentList: true,
|
|
presentBanner: true,
|
|
),
|
|
);
|
|
}
|
|
|
|
await _notificationsPlugin.show(
|
|
id,
|
|
message.notification?.title ?? message.data['title'] ?? 'Nearle',
|
|
message.notification?.body ?? message.data['body'] ?? 'Notification',
|
|
notificationDetails,
|
|
payload: jsonEncode(payload),
|
|
);
|
|
}
|
|
on Exception catch (e) {
|
|
logger.e('Error displaying notification: $e');
|
|
}
|
|
}
|
|
} |