didvan-app/lib/services/notification/notification_service.dart

215 lines
7.6 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:didvan/models/notification_message.dart';
import 'package:didvan/services/app_home_widget/home_widget_repository.dart';
import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/services/storage/storage.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
class NotificationService {
static final FlutterLocalNotificationsPlugin
_flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
static Future<void> initializeNotification() async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
const InitializationSettings initializationSettings =
InitializationSettings(android: initializationSettingsAndroid);
await _flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse: _onNotificationResponse,
);
if (!kIsWeb) {
const AndroidNotificationChannel channel = AndroidNotificationChannel(
'content',
'Content Notification',
description: 'Notification channel',
importance: Importance.max,
playSound: true,
);
await _flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
}
}
static Future<void> _onNotificationResponse(
NotificationResponse response) async {
if (kDebugMode) {
print("=== LOCAL NOTIFICATION CLICKED ===");
print("Notification ID: ${response.id}");
print("Action ID: ${response.actionId}");
print("Input: ${response.input}");
print("Response Type: ${response.notificationResponseType}");
print("Payload: ${response.payload}");
print("===================================");
}
try {
final payload = response.payload;
if (payload != null) {
NotificationMessage data =
NotificationMessage.fromJson(jsonDecode(payload));
HomeWidgetRepository.data = data;
if (kDebugMode) {
print("Parsed payload data: ${data.toJson()}");
}
await HomeWidgetRepository.decideWhereToGoNotif();
await StorageService.delete(
key: 'notification${AppInitializer.createNotificationId(data)}');
}
} catch (e) {
if (kDebugMode) {
print("Error in _onNotificationResponse: $e");
}
e.printError();
}
}
static Future<void> showFirebaseNotification(RemoteMessage message) async {
if (kDebugMode) {
print("=== SHOWING FIREBASE NOTIFICATION ===");
print("Message Data: ${message.data}");
print(
"Notification: ${message.notification?.title} - ${message.notification?.body}");
}
try {
final data = NotificationMessage.fromJson(message.data);
if (kDebugMode) {
print("Parsed NotificationMessage: ${data.toJson()}");
print("Notification Type: ${data.notificationType}");
}
// Widget-refresh pushes carry notificationType=3 and skip the UI.
if (data.notificationType == '3' ||
(data.notificationType?.contains('3') ?? false)) {
if (kDebugMode) {
print("Widget notification - calling fetchWidget()");
}
HomeWidgetRepository.fetchWidget();
return;
}
NotificationService.showNotification(data);
} catch (e) {
if (kDebugMode) {
print("Error in showFirebaseNotification: $e");
}
e.printError();
}
}
/// Downloads [url] into a temp file and returns its absolute path. Used by
/// `BigPictureStyleInformation` which needs a local file path. Returns
/// null on any failure so we degrade to a no-image notification rather
/// than crashing.
static Future<String?> _downloadImageToTemp(String url) async {
try {
final res = await http.get(Uri.parse(url));
if (res.statusCode != 200) {
if (kDebugMode) {
print('image download failed: HTTP ${res.statusCode}');
}
return null;
}
final dir = await getTemporaryDirectory();
final file = File(
'${dir.path}/notif_${DateTime.now().millisecondsSinceEpoch}.img',
);
await file.writeAsBytes(res.bodyBytes, flush: true);
return file.path;
} catch (e) {
if (kDebugMode) print('image download exception: $e');
return null;
}
}
static Future<void> showNotification(NotificationMessage data) async {
final notifType = data.notificationType ?? '';
if (notifType.contains('2')) {
final String? storedValue = await StorageService.getValue(
key: 'notification${AppInitializer.createNotificationId(data)}');
if (storedValue != null) {
_flutterLocalNotificationsPlugin
.cancel(AppInitializer.createNotificationId(data));
data.body = '${data.body}\n$storedValue';
}
}
// Resolve the image we want on the notification.
// - `data.imageUrl` is the new content-service broadcast field (remote
// URL — must be downloaded into the cache before Android can render
// it via BigPictureStyle).
// - `data.image` is the legacy widget-update field (already a local
// path).
String? bigPicturePath;
if (data.imageUrl != null && data.imageUrl!.isNotEmpty) {
bigPicturePath = await _downloadImageToTemp(data.imageUrl!);
} else if (data.image != null && data.image!.isNotEmpty) {
bigPicturePath = data.image;
}
// Pick the Android style:
// - notificationType '1' (legacy "big news" push) → BigPictureStyle
// - any push with a resolved image (content broadcasts) →
// BigPictureStyle so the cover image shows up
// - otherwise → MessagingStyle (compact)
final useBigPicture = notifType.contains('1') || bigPicturePath != null;
final AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'content',
'Content Notification',
channelDescription: 'Notification channel',
importance: Importance.max,
priority: Priority.high,
playSound: true,
styleInformation: useBigPicture && bigPicturePath != null
? BigPictureStyleInformation(
FilePathAndroidBitmap(bigPicturePath),
contentTitle: data.title,
summaryText: data.body ?? '',
)
: MessagingStyleInformation(
Person(name: data.title),
messages: [Message(data.body ?? '', DateTime.now(), null)],
),
);
final NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
// Pass the full data map (incl. id/type/link/imageUrl) through the
// payload so the onTap handler can route correctly.
final payloadJson = jsonEncode(data.toJson());
await _flutterLocalNotificationsPlugin.show(
useBigPicture
? DateTime.now().millisecondsSinceEpoch ~/ 1000
: AppInitializer.createNotificationId(data),
data.title,
data.body,
platformChannelSpecifics,
payload: payloadJson,
);
if (notifType.contains('2')) {
await StorageService.setValue(
key: 'notification${AppInitializer.createNotificationId(data)}',
value: data.body);
}
}
}