Files
Xpress-rider/lib/providers/notifications/notificationservce.dart

403 lines
12 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 'package:shared_preferences/shared_preferences.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:nearle/helpers/http_overrides.dart';
// Top-level background handler required by Firebase Messaging
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
HttpOverrides.global = MyHttpOverrides();
try {
await Firebase.initializeApp();
} catch (_) {}
await NotificationServce.display(message);
}
class NotificationServce {
static final FirebaseMessaging _firebaseMessaging =
FirebaseMessaging.instance;
static final FlutterLocalNotificationsPlugin _notificationsPlugin =
FlutterLocalNotificationsPlugin();
static final AudioPlayer _player = AudioPlayer();
static String _channelId = 'Nearle';
static bool _isPlaying = false;
static const AndroidNotificationChannel channel = AndroidNotificationChannel(
'Nearle',
'Nearle Notification',
description: 'Channel for Nearle notifications',
importance: Importance.max,
playSound: true,
enableVibration: true,
showBadge: true,
);
static Future<void> initialize(BuildContext context) async {
try {
final prefs = await SharedPreferences.getInstance();
final alreadyInit = prefs.getBool('notifications_init_done') ?? false;
if (alreadyInit) {
return;
}
await FirebaseMessaging.instance.requestPermission(
alert: true,
badge: true,
sound: true,
);
final existing = (prefs.getString('order_alert_sound') ?? '').trim();
if (existing.isEmpty) {
await prefs.setString('order_alert_sound', 'assets/audio/alert-1.mp3');
}
await prefs.setBool('notifications_init_done', true);
} catch (_) {}
await _notificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
await _applyChannelSoundFromPrefs();
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 {},
);
RemoteMessage? initialMessage =
await _firebaseMessaging.getInitialMessage();
if (initialMessage != null) {
await _handleInitialMessage(initialMessage);
}
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
await _handleMessage(message);
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
await _handleMessageOpenedApp(message);
});
}
// ✅ Removed duplicated background handler (this was breaking your notifications)
static Future<void> _handleInitialMessage(RemoteMessage message) async {
if (message.notification != null) {
await display(message);
}
}
static Future<void> _handleMessage(RemoteMessage message) async {
if (message.notification != null) {
await display(message);
}
}
static Future<void> _handleMessageOpenedApp(RemoteMessage message) async {}
static Future<void> _applyChannelSoundFromPrefs() async {
try {
final prefs = await SharedPreferences.getInstance();
String sel = (prefs.getString('order_alert_sound') ?? '').trim();
if (sel.isEmpty) {
sel = 'assets/audio/alert-1.mp3';
}
final fileName = sel.split('/').last;
final base = fileName.split('.').first;
final rawName = base.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_');
final androidImpl = _notificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
if (androidImpl != null) {
_channelId = 'Nearle_$rawName';
final custom = AndroidNotificationChannel(
_channelId,
'Nearle Notification',
description: 'Channel for Nearle notifications',
importance: Importance.max,
playSound: true,
sound: RawResourceAndroidNotificationSound(rawName),
enableVibration: true,
showBadge: true,
);
await androidImpl.createNotificationChannel(custom);
}
} catch (_) {}
}
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);
return filePath;
}
return null;
} catch (_) {
return null;
}
}
static String? _extractImageUrl(RemoteMessage message) {
String? imageUrl = message.data['image'] as String?;
imageUrl ??= message.notification?.android?.imageUrl;
imageUrl ??= message.notification?.apple?.imageUrl;
return imageUrl;
}
static Future<void> _playSelectedSound({int times = 1}) async {
try {
if (_isPlaying) return;
_isPlaying = true;
final prefs = await SharedPreferences.getInstance();
String selected = (prefs.getString('order_alert_sound') ?? '').trim();
if (selected.isEmpty) {
selected = 'assets/audio/alert-1.mp3';
}
final rel = selected.startsWith('assets/')
? selected.replaceFirst('assets/', '')
: selected;
await _player.stop();
await _player.setReleaseMode(ReleaseMode.stop);
for (int i = 0; i < times; i++) {
await _player.play(AssetSource(rel));
try {
await _player.onPlayerComplete.first;
} catch (_) {}
if (i < times - 1) {
await Future.delayed(const Duration(milliseconds: 120));
}
}
} catch (_) {} finally {
_isPlaying = false;
}
}
/// Lightweight local notification helper for in-app events (no FCM message).
static Future<void> showLocalNotification({
required String title,
required String body,
bool playSound = true,
String? payload,
}) async {
try {
final id = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final notificationDetails = NotificationDetails(
android: AndroidNotificationDetails(
_channelId,
'Nearle Notification',
importance: Importance.max,
priority: Priority.high,
icon: '@mipmap/ic_launcher',
playSound: playSound,
enableVibration: true,
channelShowBadge: true,
ongoing: false,
autoCancel: true,
),
iOS: DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: playSound,
presentList: true,
presentBanner: true,
),
);
await _notificationsPlugin.show(
id,
title,
body,
notificationDetails,
payload: payload,
);
} catch (_) {}
}
static Future<void> display(RemoteMessage message) async {
final id = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final payload = jsonEncode({'id': id.toString(), 'data': message.data});
final appState = WidgetsBinding.instance.lifecycleState;
final bool isForeground = appState == AppLifecycleState.resumed;
if (isForeground) {
await _playSelectedSound(times: 5);
}
NotificationDetails notificationDetails;
final imageUrl = _extractImageUrl(message);
String? persistedImageUrl = imageUrl;
String? persistedImagePath;
if (imageUrl != null && imageUrl.isNotEmpty) {
final imagePath = await _downloadAndSaveImage(
imageUrl,
'notification_image.jpg',
);
persistedImagePath = imagePath;
notificationDetails = NotificationDetails(
android: AndroidNotificationDetails(
_channelId,
'Nearle Notification',
importance: Importance.max,
priority: Priority.high,
icon: '@mipmap/ic_launcher',
playSound: !isForeground,
enableVibration: true,
fullScreenIntent: true,
channelShowBadge: true,
ongoing: false,
autoCancel: true,
styleInformation: imagePath != null
? BigPictureStyleInformation(FilePathAndroidBitmap(imagePath))
: const DefaultStyleInformation(true, true),
),
iOS: const DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
presentList: true,
presentBanner: true,
),
);
} else {
notificationDetails = NotificationDetails(
android: AndroidNotificationDetails(
_channelId,
'Nearle Notification',
importance: Importance.max,
priority: Priority.high,
icon: '@mipmap/ic_launcher',
playSound: !isForeground,
enableVibration: true,
fullScreenIntent: true,
channelShowBadge: true,
ongoing: false,
autoCancel: true,
),
iOS: const DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
presentList: true,
presentBanner: true,
),
);
}
if (isForeground) {
final ctx = Get.context;
if (ctx != null) {
final title =
message.notification?.title ?? message.data['title'] ?? 'Nearle';
final body = message.notification?.body ?? message.data['body'] ?? '';
ScaffoldMessenger.of(ctx).showSnackBar(
SnackBar(
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontWeight: FontWeight.w700)),
if (body.isNotEmpty) Text(body),
],
),
behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.all(16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
duration: const Duration(seconds: 3),
),
);
}
} else {
await _notificationsPlugin.show(
id,
message.notification?.title ?? message.data['title'] ?? 'Nearle',
message.notification?.body ?? message.data['body'] ?? 'Notification',
notificationDetails,
payload: payload,
);
}
try {
final prefs = await SharedPreferences.getInstance();
final nowIso = DateTime.now().toIso8601String();
final title =
message.notification?.title ?? message.data['title'] ?? 'Nearle';
final body =
message.notification?.body ?? message.data['body'] ?? '';
final entry = {
'id': id,
'title': title,
'body': body,
'time': nowIso,
'data': message.data,
if (persistedImageUrl != null) 'imageUrl': persistedImageUrl,
if (persistedImagePath != null) 'imagePath': persistedImagePath,
};
final existingRaw = prefs.getString('notifications_log');
List<dynamic> list = [];
if (existingRaw != null && existingRaw.isNotEmpty) {
try {
list = jsonDecode(existingRaw) as List<dynamic>;
} catch (_) {}
}
list.insert(0, entry);
if (list.length > 100) list = list.sublist(0, 100);
await prefs.setString('notifications_log', jsonEncode(list));
} catch (_) {}
}
}