feat: implement FCM push notifications via firebase_messaging
- 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>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
5e92d2b908
commit
79f350e85f
+7
-1
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:prosappco/blocs/notification_bloc/notification_bloc.dart';
|
||||||
import 'package:prosappco/screens/authentication/welcome_screen.dart';
|
import 'package:prosappco/screens/authentication/welcome_screen.dart';
|
||||||
import 'package:prosappco/screens/home/home_screen.dart';
|
import 'package:prosappco/screens/home/home_screen.dart';
|
||||||
import 'package:prosappco/screens/splash/splash_screen.dart';
|
import 'package:prosappco/screens/splash/splash_screen.dart';
|
||||||
@@ -36,7 +37,12 @@ class MyAppView extends StatelessWidget {
|
|||||||
secondaryContainer: Colors.red,
|
secondaryContainer: Colors.red,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
|
home: BlocConsumer<AuthenticationBloc, AuthenticationState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state.status == AuthenticationStatus.authenticated) {
|
||||||
|
context.read<NotificationBloc>().initializePushNotifications();
|
||||||
|
}
|
||||||
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return getScreen(state);
|
return getScreen(state);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
|
import 'dart:convert';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:equatable/equatable.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:user_repository/user_repository.dart';
|
||||||
|
import 'package:prosappco/constansts.dart' show API_BASE_URL;
|
||||||
|
|
||||||
part 'notification_event.dart';
|
part 'notification_event.dart';
|
||||||
part 'notification_state.dart';
|
part 'notification_state.dart';
|
||||||
|
|
||||||
// Stub: Firebase messaging removed. Background handler is no longer needed.
|
// Handles FCM messages when app is terminated/background
|
||||||
Future<void> firebaseMessagingBackgroundHandler(dynamic message) async {}
|
@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> {
|
class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
|
||||||
final Future<void> Function() requestLocalNotificationPermission;
|
final Future<void> Function() requestLocalNotificationPermission;
|
||||||
@@ -31,6 +39,60 @@ class NotificationBloc extends Bloc<NotificationEvent, NotificationState> {
|
|||||||
emit(state.copyWith(status: event.status));
|
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 {
|
void requestPermission() async {
|
||||||
try {
|
try {
|
||||||
await requestLocalNotificationPermission();
|
await requestLocalNotificationPermission();
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@@ -16,6 +18,9 @@ import 'simple_bloc_observer.dart';
|
|||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
await Firebase.initializeApp();
|
||||||
|
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
|
||||||
|
|
||||||
// Inicializacion de notificaciones locales
|
// Inicializacion de notificaciones locales
|
||||||
await LocalNotifications.initializeLocalNotifications();
|
await LocalNotifications.initializeLocalNotifications();
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ environment:
|
|||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
animate_do: ^3.0.2
|
animate_do: ^3.0.2
|
||||||
|
firebase_core: ^3.0.0
|
||||||
|
firebase_messaging: ^15.0.0
|
||||||
animated_splash_screen: ^1.3.0
|
animated_splash_screen: ^1.3.0
|
||||||
chat_repository:
|
chat_repository:
|
||||||
path: packages/chat_repository
|
path: packages/chat_repository
|
||||||
|
|||||||
Reference in New Issue
Block a user