Add notification
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -50,3 +50,4 @@ app.*.map.json
|
|||||||
.env.*.local
|
.env.*.local
|
||||||
firestore.rules
|
firestore.rules
|
||||||
storage.rules
|
storage.rules
|
||||||
|
/functions/node_modules
|
||||||
125
functions/index.js
Normal file
125
functions/index.js
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
const functions = require("firebase-functions");
|
||||||
|
const admin = require("firebase-admin");
|
||||||
|
|
||||||
|
admin.initializeApp();
|
||||||
|
|
||||||
|
// Helper function to send notifications
|
||||||
|
async function sendNotificationToTripParticipants(tripId, title, body, excludeUserId) {
|
||||||
|
try {
|
||||||
|
const tripDoc = await admin.firestore().collection("trips").doc(tripId).get();
|
||||||
|
if (!tripDoc.exists) {
|
||||||
|
console.log(`Trip ${tripId} not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trip = tripDoc.data();
|
||||||
|
const participants = trip.participants || [];
|
||||||
|
// Add creator if not in participants list (though usually they are)
|
||||||
|
if (trip.createdBy && !participants.includes(trip.createdBy)) {
|
||||||
|
participants.push(trip.createdBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokens = [];
|
||||||
|
|
||||||
|
for (const userId of participants) {
|
||||||
|
if (userId === excludeUserId) continue;
|
||||||
|
|
||||||
|
const userDoc = await admin.firestore().collection("users").doc(userId).get();
|
||||||
|
if (userDoc.exists) {
|
||||||
|
const userData = userDoc.data();
|
||||||
|
if (userData.fcmToken) {
|
||||||
|
tokens.push(userData.fcmToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokens.length > 0) {
|
||||||
|
const message = {
|
||||||
|
notification: {
|
||||||
|
title: title,
|
||||||
|
body: body,
|
||||||
|
},
|
||||||
|
tokens: tokens,
|
||||||
|
data: {
|
||||||
|
tripId: tripId,
|
||||||
|
click_action: "FLUTTER_NOTIFICATION_CLICK",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await admin.messaging().sendMulticast(message);
|
||||||
|
console.log(`${response.successCount} messages were sent successfully`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error sending notification:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.onActivityCreated = functions.firestore
|
||||||
|
.document("trips/{tripId}/activities/{activityId}")
|
||||||
|
.onCreate(async (snapshot, context) => {
|
||||||
|
const activity = snapshot.data();
|
||||||
|
const tripId = context.params.tripId;
|
||||||
|
const createdBy = activity.createdBy || "Unknown"; // Assuming createdBy field exists
|
||||||
|
|
||||||
|
// Fetch creator name if possible, otherwise use generic message
|
||||||
|
let creatorName = "Quelqu'un";
|
||||||
|
if (createdBy !== "Unknown") {
|
||||||
|
const userDoc = await admin.firestore().collection("users").doc(createdBy).get();
|
||||||
|
if (userDoc.exists) {
|
||||||
|
creatorName = userDoc.data().prenom || "Quelqu'un";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendNotificationToTripParticipants(
|
||||||
|
tripId,
|
||||||
|
"Nouvelle activité !",
|
||||||
|
`${creatorName} a ajouté une nouvelle activité : ${activity.title}`,
|
||||||
|
createdBy
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.onMessageCreated = functions.firestore
|
||||||
|
.document("trips/{tripId}/messages/{messageId}")
|
||||||
|
.onCreate(async (snapshot, context) => {
|
||||||
|
const message = snapshot.data();
|
||||||
|
const tripId = context.params.tripId;
|
||||||
|
const senderId = message.senderId;
|
||||||
|
|
||||||
|
let senderName = "Quelqu'un";
|
||||||
|
if (senderId) {
|
||||||
|
const userDoc = await admin.firestore().collection("users").doc(senderId).get();
|
||||||
|
if (userDoc.exists) {
|
||||||
|
senderName = userDoc.data().prenom || "Quelqu'un";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendNotificationToTripParticipants(
|
||||||
|
tripId,
|
||||||
|
"Nouveau message",
|
||||||
|
`${senderName} : ${message.content}`,
|
||||||
|
senderId
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.onExpenseCreated = functions.firestore
|
||||||
|
.document("trips/{tripId}/expenses/{expenseId}")
|
||||||
|
.onCreate(async (snapshot, context) => {
|
||||||
|
const expense = snapshot.data();
|
||||||
|
const tripId = context.params.tripId;
|
||||||
|
const paidBy = expense.paidBy;
|
||||||
|
|
||||||
|
let payerName = "Quelqu'un";
|
||||||
|
if (paidBy) {
|
||||||
|
const userDoc = await admin.firestore().collection("users").doc(paidBy).get();
|
||||||
|
if (userDoc.exists) {
|
||||||
|
payerName = userDoc.data().prenom || "Quelqu'un";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendNotificationToTripParticipants(
|
||||||
|
tripId,
|
||||||
|
"Nouvelle dépense",
|
||||||
|
`${payerName} a ajouté une dépense : ${expense.amount} ${expense.currency || '€'}`,
|
||||||
|
paidBy
|
||||||
|
);
|
||||||
|
});
|
||||||
4284
functions/package-lock.json
generated
Normal file
4284
functions/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
functions/package.json
Normal file
25
functions/package.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "functions",
|
||||||
|
"description": "Cloud Functions for Travel Mate",
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint .",
|
||||||
|
"serve": "firebase emulators:start --only functions",
|
||||||
|
"shell": "firebase functions:shell",
|
||||||
|
"start": "npm run shell",
|
||||||
|
"deploy": "firebase deploy --only functions",
|
||||||
|
"logs": "firebase functions:log"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18"
|
||||||
|
},
|
||||||
|
"main": "index.js",
|
||||||
|
"dependencies": {
|
||||||
|
"firebase-admin": "^11.8.0",
|
||||||
|
"firebase-functions": "^4.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^8.15.0",
|
||||||
|
"eslint-config-google": "^0.14.0"
|
||||||
|
},
|
||||||
|
"private": true
|
||||||
|
}
|
||||||
@@ -53,13 +53,6 @@ class SettingsContent extends StatelessWidget {
|
|||||||
onTap: () {},
|
onTap: () {},
|
||||||
),
|
),
|
||||||
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.language),
|
|
||||||
title: const Text('Langue'),
|
|
||||||
trailing: const Icon(Icons.arrow_forward_ios),
|
|
||||||
onTap: () {},
|
|
||||||
),
|
|
||||||
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.privacy_tip),
|
leading: const Icon(Icons.privacy_tip),
|
||||||
title: const Text('Confidentialité'),
|
title: const Text('Confidentialité'),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:cloud_firestore/cloud_firestore.dart';
|
|||||||
import '../models/user.dart';
|
import '../models/user.dart';
|
||||||
import '../services/auth_service.dart';
|
import '../services/auth_service.dart';
|
||||||
import '../services/error_service.dart';
|
import '../services/error_service.dart';
|
||||||
|
import '../services/notification_service.dart';
|
||||||
|
|
||||||
/// Repository for authentication operations and user data management.
|
/// Repository for authentication operations and user data management.
|
||||||
///
|
///
|
||||||
@@ -57,6 +58,7 @@ class AuthRepository {
|
|||||||
email: email,
|
email: email,
|
||||||
password: password,
|
password: password,
|
||||||
);
|
);
|
||||||
|
await _saveFCMToken(firebaseUser.user!.uid);
|
||||||
return await getUserFromFirestore(firebaseUser.user!.uid);
|
return await getUserFromFirestore(firebaseUser.user!.uid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_errorService.showError(message: 'Utilisateur ou mot de passe incorrect');
|
_errorService.showError(message: 'Utilisateur ou mot de passe incorrect');
|
||||||
@@ -102,6 +104,9 @@ class AuthRepository {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
||||||
|
if (user.id != null) {
|
||||||
|
await _saveFCMToken(user.id!);
|
||||||
|
}
|
||||||
return user;
|
return user;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_errorService.showError(message: 'Erreur lors de la création du compte');
|
_errorService.showError(message: 'Erreur lors de la création du compte');
|
||||||
@@ -149,6 +154,9 @@ class AuthRepository {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
||||||
|
if (user.id != null) {
|
||||||
|
await _saveFCMToken(user.id!);
|
||||||
|
}
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -163,6 +171,9 @@ class AuthRepository {
|
|||||||
final firebaseUser = await _authService.signInWithGoogle();
|
final firebaseUser = await _authService.signInWithGoogle();
|
||||||
final user = await getUserFromFirestore(firebaseUser.user!.uid);
|
final user = await getUserFromFirestore(firebaseUser.user!.uid);
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
if (user.id != null) {
|
||||||
|
await _saveFCMToken(user.id!);
|
||||||
|
}
|
||||||
return user;
|
return user;
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Utilisateur non trouvé');
|
throw Exception('Utilisateur non trouvé');
|
||||||
@@ -211,6 +222,9 @@ class AuthRepository {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
||||||
|
if (user.id != null) {
|
||||||
|
await _saveFCMToken(user.id!);
|
||||||
|
}
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -225,6 +239,9 @@ class AuthRepository {
|
|||||||
final firebaseUser = await _authService.signInWithApple();
|
final firebaseUser = await _authService.signInWithApple();
|
||||||
final user = await getUserFromFirestore(firebaseUser.user!.uid);
|
final user = await getUserFromFirestore(firebaseUser.user!.uid);
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
if (user.id != null) {
|
||||||
|
await _saveFCMToken(user.id!);
|
||||||
|
}
|
||||||
return user;
|
return user;
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Utilisateur non trouvé');
|
throw Exception('Utilisateur non trouvé');
|
||||||
@@ -258,7 +275,7 @@ class AuthRepository {
|
|||||||
///
|
///
|
||||||
/// [uid] - The Firebase user ID to look up
|
/// [uid] - The Firebase user ID to look up
|
||||||
///
|
///
|
||||||
/// Returns the [User] model if found, null otherwise.
|
/// Returns the [User] model if successful, null otherwise.
|
||||||
Future<User?> getUserFromFirestore(String uid) async {
|
Future<User?> getUserFromFirestore(String uid) async {
|
||||||
try {
|
try {
|
||||||
final doc = await _firestore.collection('users').doc(uid).get();
|
final doc = await _firestore.collection('users').doc(uid).get();
|
||||||
@@ -271,4 +288,19 @@ class AuthRepository {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper method to save the FCM token for the authenticated user.
|
||||||
|
Future<void> _saveFCMToken(String userId) async {
|
||||||
|
try {
|
||||||
|
final token = await NotificationService().getFCMToken();
|
||||||
|
if (token != null) {
|
||||||
|
await _firestore.collection('users').doc(userId).set({
|
||||||
|
'fcmToken': token,
|
||||||
|
}, SetOptions(merge: true));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Non-blocking error
|
||||||
|
print('Error saving FCM token: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
98
lib/services/notification_service.dart
Normal file
98
lib/services/notification_service.dart
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
import 'package:travel_mate/services/logger_service.dart';
|
||||||
|
|
||||||
|
class NotificationService {
|
||||||
|
static final NotificationService _instance = NotificationService._internal();
|
||||||
|
factory NotificationService() => _instance;
|
||||||
|
NotificationService._internal();
|
||||||
|
|
||||||
|
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
|
||||||
|
final FlutterLocalNotificationsPlugin _localNotifications =
|
||||||
|
FlutterLocalNotificationsPlugin();
|
||||||
|
|
||||||
|
bool _isInitialized = false;
|
||||||
|
|
||||||
|
Future<void> initialize() async {
|
||||||
|
if (_isInitialized) return;
|
||||||
|
|
||||||
|
// Request permissions
|
||||||
|
await _requestPermissions();
|
||||||
|
|
||||||
|
// Initialize local notifications
|
||||||
|
const androidSettings = AndroidInitializationSettings(
|
||||||
|
'@mipmap/ic_launcher',
|
||||||
|
);
|
||||||
|
const iosSettings = DarwinInitializationSettings();
|
||||||
|
const initSettings = InitializationSettings(
|
||||||
|
android: androidSettings,
|
||||||
|
iOS: iosSettings,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _localNotifications.initialize(
|
||||||
|
initSettings,
|
||||||
|
onDidReceiveNotificationResponse: (details) {
|
||||||
|
// Handle notification tap
|
||||||
|
LoggerService.info('Notification tapped: ${details.payload}');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle foreground messages
|
||||||
|
FirebaseMessaging.onMessage.listen(_handleForegroundMessage);
|
||||||
|
|
||||||
|
_isInitialized = true;
|
||||||
|
LoggerService.info('NotificationService initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _requestPermissions() async {
|
||||||
|
NotificationSettings settings = await _firebaseMessaging.requestPermission(
|
||||||
|
alert: true,
|
||||||
|
badge: true,
|
||||||
|
sound: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
LoggerService.info(
|
||||||
|
'User granted permission: ${settings.authorizationStatus}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getFCMToken() async {
|
||||||
|
try {
|
||||||
|
return await _firebaseMessaging.getToken();
|
||||||
|
} catch (e) {
|
||||||
|
LoggerService.error('Error getting FCM token: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleForegroundMessage(RemoteMessage message) async {
|
||||||
|
LoggerService.info('Got a message whilst in the foreground!');
|
||||||
|
LoggerService.info('Message data: ${message.data}');
|
||||||
|
|
||||||
|
if (message.notification != null) {
|
||||||
|
LoggerService.info(
|
||||||
|
'Message also contained a notification: ${message.notification}',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show local notification
|
||||||
|
const androidDetails = AndroidNotificationDetails(
|
||||||
|
'high_importance_channel',
|
||||||
|
'High Importance Notifications',
|
||||||
|
importance: Importance.max,
|
||||||
|
priority: Priority.high,
|
||||||
|
);
|
||||||
|
const iosDetails = DarwinNotificationDetails();
|
||||||
|
const details = NotificationDetails(
|
||||||
|
android: androidDetails,
|
||||||
|
iOS: iosDetails,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _localNotifications.show(
|
||||||
|
message.hashCode,
|
||||||
|
message.notification?.title,
|
||||||
|
message.notification?.body,
|
||||||
|
details,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
76
pubspec.lock
76
pubspec.lock
@@ -5,10 +5,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _flutterfire_internals
|
name: _flutterfire_internals
|
||||||
sha256: f871a7d1b686bea1f13722aa51ab31554d05c81f47054d6de48cc8c45153508b
|
sha256: "8a1f5f3020ef2a74fb93f7ab3ef127a8feea33a7a2276279113660784ee7516a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.63"
|
version: "1.3.64"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -293,10 +293,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: firebase_core
|
name: firebase_core
|
||||||
sha256: "132e1c311bc41e7d387b575df0aacdf24efbf4930365eb61042be5bde3978f03"
|
sha256: "1f2dfd9f535d81f8b06d7a50ecda6eac1e6922191ed42e09ca2c84bd2288927c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.0"
|
version: "4.2.1"
|
||||||
firebase_core_platform_interface:
|
firebase_core_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -309,10 +309,34 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: firebase_core_web
|
name: firebase_core_web
|
||||||
sha256: ecde2def458292404a4fcd3731ee4992fd631a0ec359d2d67c33baa8da5ec8ae
|
sha256: ff18fabb0ad0ed3595d2f2c85007ecc794aadecdff5b3bb1460b7ee47cded398
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.0"
|
version: "3.3.0"
|
||||||
|
firebase_messaging:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: firebase_messaging
|
||||||
|
sha256: "22086f857d2340f5d973776cfd542d3fb30cf98e1c643c3aa4a7520bb12745bb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "16.0.4"
|
||||||
|
firebase_messaging_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_messaging_platform_interface
|
||||||
|
sha256: a59920cbf2eb7c83d34a5f354331210ffec116b216dc72d864d8b8eb983ca398
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.7.4"
|
||||||
|
firebase_messaging_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_messaging_web
|
||||||
|
sha256: "1183e40e6fd2a279a628951cc3b639fcf5ffe7589902632db645011eb70ebefb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.0"
|
||||||
firebase_storage:
|
firebase_storage:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -390,6 +414,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.0.0"
|
||||||
|
flutter_local_notifications:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications
|
||||||
|
sha256: "19ffb0a8bb7407875555e5e98d7343a633bb73707bae6c6a5f37c90014077875"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "19.5.0"
|
||||||
|
flutter_local_notifications_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications_linux
|
||||||
|
sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
|
flutter_local_notifications_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications_platform_interface
|
||||||
|
sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.1.0"
|
||||||
|
flutter_local_notifications_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications_windows
|
||||||
|
sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1157,6 +1213,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.6"
|
version: "0.7.6"
|
||||||
|
timezone:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: timezone
|
||||||
|
sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.1"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ dependencies:
|
|||||||
cached_network_image: ^3.3.1
|
cached_network_image: ^3.3.1
|
||||||
path: ^1.9.1
|
path: ^1.9.1
|
||||||
image: ^4.5.4
|
image: ^4.5.4
|
||||||
|
firebase_messaging: ^16.0.4
|
||||||
|
flutter_local_notifications: ^19.5.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_launcher_icons: ^0.13.1
|
flutter_launcher_icons: ^0.13.1
|
||||||
|
|||||||
Reference in New Issue
Block a user