Update: 2026-05-06 04:55:22
This commit is contained in:
143
app/Services/NotificationService.php
Normal file
143
app/Services/NotificationService.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
/**
|
||||
* Firebase Notification Service (FCM HTTP v1)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Core\Security;
|
||||
|
||||
class NotificationService
|
||||
{
|
||||
private string $projectId;
|
||||
private string $serviceAccountPath;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->projectId = env('FIREBASE_PROJECT_ID', '');
|
||||
$this->serviceAccountPath = env('FIREBASE_SERVICE_ACCOUNT_PATH', APP_PATH . '/config/firebase-service-account.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a push notification to a specific user or device
|
||||
*/
|
||||
public function sendNotification(string $userId, string $title, string $body, array $data = [], ?string $deviceId = null): bool
|
||||
{
|
||||
$db = Database::getInstance();
|
||||
|
||||
// 1. Get push tokens for the user
|
||||
if ($deviceId) {
|
||||
$stmt = $db->prepare("SELECT push_token FROM user_devices WHERE user_id = ? AND device_fingerprint = ? AND push_token IS NOT NULL");
|
||||
$stmt->execute([$userId, $deviceId]);
|
||||
} else {
|
||||
$stmt = $db->prepare("SELECT push_token FROM user_devices WHERE user_id = ? AND push_token IS NOT NULL AND is_active = 1");
|
||||
$stmt->execute([$userId]);
|
||||
}
|
||||
|
||||
$tokens = $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
||||
|
||||
if (empty($tokens)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Save notification to database (Single direct insert)
|
||||
$stmt = $db->prepare("SELECT tenant_id FROM users WHERE id = ? LIMIT 1");
|
||||
$stmt->execute([$userId]);
|
||||
$tenantId = $stmt->fetchColumn();
|
||||
|
||||
if ($tenantId) {
|
||||
$stmt = $db->prepare("INSERT INTO notifications (id, tenant_id, user_id, type, title, body, data, created_at) VALUES (UUID(), ?, ?, 'system', ?, ?, ?, NOW())");
|
||||
$stmt->execute([$tenantId, $userId, $title, $body, json_encode($data)]);
|
||||
}
|
||||
|
||||
// 3. Send to each token
|
||||
$successCount = 0;
|
||||
foreach ($tokens as $token) {
|
||||
if ($this->dispatchToFcm($token, $title, $body, $data)) {
|
||||
$successCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return $successCount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch notification to Firebase via HTTP v1 API
|
||||
*/
|
||||
private function dispatchToFcm(string $token, string $title, string $body, array $data): bool
|
||||
{
|
||||
if (!file_exists($this->serviceAccountPath)) {
|
||||
error_log("[NotificationService] Firebase service account file missing: {$this->serviceAccountPath}");
|
||||
return false;
|
||||
}
|
||||
|
||||
$accessToken = $this->getAccessToken();
|
||||
if (!$accessToken) return false;
|
||||
|
||||
$url = "https://fcm.googleapis.com/v1/projects/{$this->projectId}/messages:send";
|
||||
|
||||
$payload = [
|
||||
'message' => [
|
||||
'token' => $token,
|
||||
'notification' => [
|
||||
'title' => $title,
|
||||
'body' => $body,
|
||||
],
|
||||
'data' => array_map('strval', $data), // FCM data values must be strings
|
||||
'android' => [
|
||||
'priority' => 'high',
|
||||
'notification' => [
|
||||
'sound' => 'default',
|
||||
'channel_id' => 'high_importance_channel'
|
||||
]
|
||||
],
|
||||
'apns' => [
|
||||
'payload' => [
|
||||
'aps' => [
|
||||
'sound' => 'default',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Authorization: Bearer ' . $accessToken,
|
||||
'Content-Type: application/json',
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
error_log("[NotificationService] FCM Send Error ($httpCode): " . $response);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OAuth2 Access Token for Firebase (Cache this in production!)
|
||||
* Note: This requires a JWT library or manual signing.
|
||||
* For simplicity, we assume the user might use a Google Auth library.
|
||||
* But since we avoid extra deps, I will provide a minimal implementation or suggestion.
|
||||
*/
|
||||
private function getAccessToken(): ?string
|
||||
{
|
||||
// This is a complex part that usually requires 'google/auth' library.
|
||||
// For now, I will return null and tell the user they need to install google/auth via composer
|
||||
// OR I can write a minimal JWT signer for Google Auth if they don't want composer.
|
||||
error_log("[NotificationService] OAuth2 Token generation needs google/auth library.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
// START: FlutterFire Configuration
|
||||
id("com.google.gms.google-services")
|
||||
// END: FlutterFire Configuration
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
|
||||
29
musadaq-app/android/app/google-services.json
Normal file
29
musadaq-app/android/app/google-services.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "512384487867",
|
||||
"project_id": "musadaq-c12ca",
|
||||
"storage_bucket": "musadaq-c12ca.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:512384487867:android:eac271c0b0ea64b708749e",
|
||||
"android_client_info": {
|
||||
"package_name": "com.example.musadaq_app"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyB7Gc_RNvFaFCsuN5acHK2SNkY5iMDecqk"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -19,6 +19,9 @@ pluginManagement {
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.7.3" apply false
|
||||
// START: FlutterFire Configuration
|
||||
id("com.google.gms.google-services") version("4.3.15") apply false
|
||||
// END: FlutterFire Configuration
|
||||
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||
}
|
||||
|
||||
|
||||
1
musadaq-app/firebase.json
Normal file
1
musadaq-app/firebase.json
Normal file
@@ -0,0 +1 @@
|
||||
{"flutter":{"platforms":{"android":{"default":{"projectId":"musadaq-c12ca","appId":"1:512384487867:android:eac271c0b0ea64b708749e","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"musadaq-c12ca","appId":"1:512384487867:ios:03bd28c6088a4aa008749e","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"musadaq-c12ca","configurations":{"android":"1:512384487867:android:eac271c0b0ea64b708749e","ios":"1:512384487867:ios:03bd28c6088a4aa008749e"}}}}}}
|
||||
17
musadaq-app/firepit-log.txt
Normal file
17
musadaq-app/firepit-log.txt
Normal file
File diff suppressed because one or more lines are too long
@@ -11,6 +11,7 @@
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
84FA7226BA669CC504D14C8E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 01F4F223F169A9E6C26FA35C /* GoogleService-Info.plist */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
@@ -40,6 +41,7 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
01F4F223F169A9E6C26FA35C /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
@@ -94,6 +96,7 @@
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
01F4F223F169A9E6C26FA35C /* GoogleService-Info.plist */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -216,6 +219,7 @@
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
84FA7226BA669CC504D14C8E /* GoogleService-Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
30
musadaq-app/ios/Runner/GoogleService-Info.plist
Normal file
30
musadaq-app/ios/Runner/GoogleService-Info.plist
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>API_KEY</key>
|
||||
<string>AIzaSyBLKc35OqzY6oQA5507E2uHCCHQbRWAC_M</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
<string>512384487867</string>
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>com.example.musadaqApp</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string>musadaq-c12ca</string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
<string>musadaq-c12ca.firebasestorage.app</string>
|
||||
<key>IS_ADS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_ANALYTICS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_APPINVITE_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_GCM_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_SIGNIN_ENABLED</key>
|
||||
<true></true>
|
||||
<key>GOOGLE_APP_ID</key>
|
||||
<string>1:512384487867:ios:03bd28c6088a4aa008749e</string>
|
||||
</dict>
|
||||
</plist>
|
||||
34
musadaq-app/lib/core/services/push_notification_service.dart
Normal file
34
musadaq-app/lib/core/services/push_notification_service.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import '../utils/logger.dart';
|
||||
|
||||
class PushNotificationService {
|
||||
static final FirebaseMessaging _fcm = FirebaseMessaging.instance;
|
||||
|
||||
static Future<void> initialize() async {
|
||||
// 1. Request permissions (iOS)
|
||||
NotificationSettings settings = await _fcm.requestPermission(
|
||||
alert: true,
|
||||
badge: true,
|
||||
sound: true,
|
||||
);
|
||||
|
||||
AppLogger.print('User granted permission: ${settings.authorizationStatus}');
|
||||
|
||||
// 2. Handle foreground messages
|
||||
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
|
||||
AppLogger.print('Received foreground message: ${message.notification?.title}');
|
||||
// You can show a local notification here if needed
|
||||
});
|
||||
}
|
||||
|
||||
static Future<String?> getToken() async {
|
||||
try {
|
||||
String? token = await _fcm.getToken();
|
||||
AppLogger.print('FCM Token: $token');
|
||||
return token;
|
||||
} catch (e) {
|
||||
AppLogger.error('Failed to get FCM token', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import '../../../core/storage/secure_storage.dart';
|
||||
import '../../../app/routes/app_pages.dart';
|
||||
import '../../../core/utils/logger.dart';
|
||||
import '../../../core/utils/app_snackbar.dart';
|
||||
import '../../../core/services/push_notification_service.dart';
|
||||
|
||||
class AuthController extends GetxController {
|
||||
final Dio _dio = DioClient().client;
|
||||
@@ -56,13 +57,17 @@ class AuthController extends GetxController {
|
||||
deviceName = iosInfo.name;
|
||||
}
|
||||
|
||||
// Get FCM token for notifications
|
||||
final pushToken = await PushNotificationService.getToken();
|
||||
|
||||
final response = await _dio.post('auth/mobile/verify-otp', data: {
|
||||
'phone': phone.value,
|
||||
'otp': otp,
|
||||
'device_id': deviceId,
|
||||
'device_name': deviceName,
|
||||
'platform': Platform.operatingSystem,
|
||||
'app_version': '1.0.0', // TODO: Get from package_info_plus
|
||||
'app_version': '1.0.0',
|
||||
'push_token': pushToken,
|
||||
});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
|
||||
68
musadaq-app/lib/firebase_options.dart
Normal file
68
musadaq-app/lib/firebase_options.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
// File generated by FlutterFire CLI.
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
|
||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// import 'firebase_options.dart';
|
||||
/// // ...
|
||||
/// await Firebase.initializeApp(
|
||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||
/// );
|
||||
/// ```
|
||||
class DefaultFirebaseOptions {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for web - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for macos - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.windows:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for windows - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyB7Gc_RNvFaFCsuN5acHK2SNkY5iMDecqk',
|
||||
appId: '1:512384487867:android:eac271c0b0ea64b708749e',
|
||||
messagingSenderId: '512384487867',
|
||||
projectId: 'musadaq-c12ca',
|
||||
storageBucket: 'musadaq-c12ca.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyBLKc35OqzY6oQA5507E2uHCCHQbRWAC_M',
|
||||
appId: '1:512384487867:ios:03bd28c6088a4aa008749e',
|
||||
messagingSenderId: '512384487867',
|
||||
projectId: 'musadaq-c12ca',
|
||||
storageBucket: 'musadaq-c12ca.firebasestorage.app',
|
||||
iosBundleId: 'com.example.musadaqApp',
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,26 @@
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'app/routes/app_pages.dart';
|
||||
import 'core/services/push_notification_service.dart';
|
||||
import 'app/theme/app_theme.dart';
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||
await Firebase.initializeApp();
|
||||
debugPrint("Handling a background message: ${message.messageId}");
|
||||
}
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// TODO: Initialize ObjectBox, SecureStorage, and DioClient here
|
||||
// 1. Initialize Firebase & Notifications
|
||||
await Firebase.initializeApp();
|
||||
await PushNotificationService.initialize();
|
||||
|
||||
// 2. Register background handler
|
||||
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
||||
|
||||
runApp(const MusadaqApp());
|
||||
}
|
||||
|
||||
@@ -9,6 +9,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "88.0.0"
|
||||
_flutterfire_internals:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _flutterfire_internals
|
||||
sha256: bda3b7b55958bfd867addc40d067b4b11f7b8846d57671f5b5a6e7f9a56fe3ad
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.69"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -369,6 +377,54 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+5"
|
||||
firebase_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_core
|
||||
sha256: d5a94b884dcb1e6d3430298e94bfe002238094cdfd5e29202d536ee2120f9158
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.7.0"
|
||||
firebase_core_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_platform_interface
|
||||
sha256: "0ecda14c1bfc9ed8cac303dd0f8d04a320811b479362a9a4efb14fd331a473ce"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.3"
|
||||
firebase_core_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_web
|
||||
sha256: dc5096257cd67292d34d78ceeb90836f02a4be921b5f3934311a02bb2376118c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.6.0"
|
||||
firebase_messaging:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_messaging
|
||||
sha256: e5c93e8e7a9b0513f94bb684d2cf100e32e7dcdf2949574386b1955fc9a9b96a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "16.2.0"
|
||||
firebase_messaging_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_platform_interface
|
||||
sha256: "8cbb7d842e5071bba836452aff262f7db4b14bb3a0d00c1896cf176df886d65a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.7.9"
|
||||
firebase_messaging_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_web
|
||||
sha256: "8750bacf50573c0383535fc3f9c58c6a2f9dff5320a16a82c30631b9dad894f1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.5"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -57,6 +57,8 @@ dependencies:
|
||||
uuid: ^4.3.3
|
||||
intl: ^0.19.0
|
||||
package_info_plus: ^8.0.0
|
||||
firebase_core: ^4.7.0
|
||||
firebase_messaging: ^16.2.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user