- Add firebase_core + firebase_messaging to pubspec.yaml - Initialize Firebase in main.dart before runApp - NotificationBloc: request permission, get FCM token, register with backend (PATCH /users/me/fcm-token), show local notification on foreground messages, refresh token on rotation - app_view.dart: call initializePushNotifications() on authenticated state Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
104 lines
3.4 KiB
Dart
104 lines
3.4 KiB
Dart
import 'dart:convert';
|
|
import 'dart:developer';
|
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:equatable/equatable.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:user_repository/user_repository.dart';
|
|
import 'package:prosappco/constansts.dart' show API_BASE_URL;
|
|
|
|
part 'notification_event.dart';
|
|
part 'notification_state.dart';
|
|
|
|
// Handles FCM messages when app is terminated/background
|
|
@pragma('vm:entry-point')
|
|
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
|
// Local notification shown by the system tray automatically for data-only
|
|
// messages; nothing extra needed here.
|
|
}
|
|
|
|
class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
|
|
final Future<void> Function() requestLocalNotificationPermission;
|
|
final void Function({
|
|
required int id,
|
|
String? title,
|
|
String? body,
|
|
String? data,
|
|
}) showLocalNotification;
|
|
|
|
NotificationBloc(
|
|
{required this.requestLocalNotificationPermission,
|
|
required this.showLocalNotification})
|
|
: super(const NotificationState()) {
|
|
on<NotificationStatusChanged>(_notificationStatusChanged);
|
|
}
|
|
|
|
void _notificationStatusChanged(
|
|
NotificationStatusChanged event, Emitter<NotificationState> emit) {
|
|
emit(state.copyWith(status: event.status));
|
|
}
|
|
|
|
// Call once after login to register the FCM token and listen for foreground messages.
|
|
Future<void> initializePushNotifications() async {
|
|
try {
|
|
final messaging = FirebaseMessaging.instance;
|
|
final settings = await messaging.requestPermission();
|
|
add(NotificationStatusChanged(
|
|
settings.authorizationStatus == AuthorizationStatus.authorized ||
|
|
settings.authorizationStatus == AuthorizationStatus.provisional
|
|
? AuthorizationStatus.authorized
|
|
: AuthorizationStatus.denied,
|
|
));
|
|
|
|
final token = await messaging.getToken();
|
|
if (token != null) await _registerToken(token);
|
|
|
|
// Refresh token (e.g. after reinstall)
|
|
messaging.onTokenRefresh.listen(_registerToken);
|
|
|
|
// Show local notification when app is in foreground
|
|
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
|
|
final n = message.notification;
|
|
if (n != null) {
|
|
showLocalNotification(
|
|
id: message.hashCode,
|
|
title: n.title,
|
|
body: n.body,
|
|
data: jsonEncode(message.data),
|
|
);
|
|
}
|
|
});
|
|
} catch (e) {
|
|
log('NotificationBloc.initializePushNotifications error: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> _registerToken(String token) async {
|
|
try {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final jwt = prefs.getString('token');
|
|
if (jwt == null) return;
|
|
await http.patch(
|
|
Uri.parse('$API_BASE_URL/users/me/fcm-token'),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Bearer $jwt',
|
|
},
|
|
body: jsonEncode({'token': token}),
|
|
);
|
|
log('FCM token registered');
|
|
} catch (e) {
|
|
log('NotificationBloc._registerToken error: $e');
|
|
}
|
|
}
|
|
|
|
void requestPermission() async {
|
|
try {
|
|
await requestLocalNotificationPermission();
|
|
} catch (e) {
|
|
log('NotificationBloc.requestPermission error: $e');
|
|
}
|
|
}
|
|
}
|