|
|
|
|
@ -7,7 +7,6 @@ import 'package:firebase_messaging/firebase_messaging.dart';
|
|
|
|
|
import 'package:firebase_analytics/firebase_analytics.dart';
|
|
|
|
|
import '../local/local_storage_service.dart';
|
|
|
|
|
import '../local/models/item.dart';
|
|
|
|
|
import '../session/models/user.dart';
|
|
|
|
|
import 'models/firebase_config.dart';
|
|
|
|
|
|
|
|
|
|
/// Exception thrown when Firebase operations fail.
|
|
|
|
|
@ -23,17 +22,17 @@ class FirebaseException implements Exception {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Service for Firebase integration (optional cloud sync, storage, auth, notifications, analytics).
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// This service provides:
|
|
|
|
|
/// - Cloud Firestore for optional metadata sync and backup
|
|
|
|
|
/// - Firebase Storage for optional media storage
|
|
|
|
|
/// - Firebase Authentication for user login/logout
|
|
|
|
|
/// - Firebase Cloud Messaging for push notifications
|
|
|
|
|
/// - Firebase Analytics for optional analytics
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// The service is modular and optional - can be enabled/disabled without affecting other modules.
|
|
|
|
|
/// When disabled, all methods return safely without throwing errors.
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// The service maintains offline-first behavior by syncing with local storage
|
|
|
|
|
/// and only using Firebase as an optional cloud backup/sync layer.
|
|
|
|
|
class FirebaseService {
|
|
|
|
|
@ -65,7 +64,7 @@ class FirebaseService {
|
|
|
|
|
firebase_auth.User? _firebaseUser;
|
|
|
|
|
|
|
|
|
|
/// Creates a [FirebaseService] instance.
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// [config] - Firebase configuration (determines which services are enabled).
|
|
|
|
|
/// [localStorage] - Local storage service for offline-first behavior.
|
|
|
|
|
FirebaseService({
|
|
|
|
|
@ -83,10 +82,10 @@ class FirebaseService {
|
|
|
|
|
bool get isLoggedIn => _auth != null && _firebaseUser != null;
|
|
|
|
|
|
|
|
|
|
/// Initializes Firebase services based on configuration.
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// Must be called before using any Firebase services.
|
|
|
|
|
/// If Firebase is disabled, this method does nothing.
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// Throws [FirebaseException] if initialization fails.
|
|
|
|
|
Future<void> initialize() async {
|
|
|
|
|
if (!config.enabled) {
|
|
|
|
|
@ -141,12 +140,12 @@ class FirebaseService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Logs in a user with email and password.
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// [email] - User email address.
|
|
|
|
|
/// [password] - User password.
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// Returns the Firebase Auth user.
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// Throws [FirebaseException] if auth is disabled or login fails.
|
|
|
|
|
Future<firebase_auth.User> loginWithEmailPassword({
|
|
|
|
|
required String email,
|
|
|
|
|
@ -157,7 +156,8 @@ class FirebaseService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!_initialized || _auth == null) {
|
|
|
|
|
throw FirebaseException('Firebase not initialized. Call initialize() first.');
|
|
|
|
|
throw FirebaseException(
|
|
|
|
|
'Firebase not initialized. Call initialize() first.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
@ -173,7 +173,7 @@ class FirebaseService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Logs out the current user.
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// Throws [FirebaseException] if auth is disabled or logout fails.
|
|
|
|
|
Future<void> logout() async {
|
|
|
|
|
if (!config.enabled || !config.authEnabled) {
|
|
|
|
|
@ -181,7 +181,8 @@ class FirebaseService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!_initialized || _auth == null) {
|
|
|
|
|
throw FirebaseException('Firebase not initialized. Call initialize() first.');
|
|
|
|
|
throw FirebaseException(
|
|
|
|
|
'Firebase not initialized. Call initialize() first.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
@ -193,9 +194,9 @@ class FirebaseService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Syncs local items to Firestore (cloud backup).
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// [userId] - User ID to associate items with (for multi-user support).
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// Throws [FirebaseException] if Firestore is disabled or sync fails.
|
|
|
|
|
Future<void> syncItemsToFirestore(String userId) async {
|
|
|
|
|
if (!config.enabled || !config.firestoreEnabled) {
|
|
|
|
|
@ -203,7 +204,8 @@ class FirebaseService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!_initialized || _firestore == null) {
|
|
|
|
|
throw FirebaseException('Firestore not initialized. Call initialize() first.');
|
|
|
|
|
throw FirebaseException(
|
|
|
|
|
'Firestore not initialized. Call initialize() first.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
@ -212,7 +214,8 @@ class FirebaseService {
|
|
|
|
|
|
|
|
|
|
// Batch write to Firestore
|
|
|
|
|
final batch = _firestore!.batch();
|
|
|
|
|
final collection = _firestore!.collection('users').doc(userId).collection('items');
|
|
|
|
|
final collection =
|
|
|
|
|
_firestore!.collection('users').doc(userId).collection('items');
|
|
|
|
|
|
|
|
|
|
for (final item in items) {
|
|
|
|
|
final docRef = collection.doc(item.id);
|
|
|
|
|
@ -231,9 +234,9 @@ class FirebaseService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Syncs items from Firestore to local storage.
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// [userId] - User ID to fetch items for.
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// Throws [FirebaseException] if Firestore is disabled or sync fails.
|
|
|
|
|
Future<void> syncItemsFromFirestore(String userId) async {
|
|
|
|
|
if (!config.enabled || !config.firestoreEnabled) {
|
|
|
|
|
@ -241,7 +244,8 @@ class FirebaseService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!_initialized || _firestore == null) {
|
|
|
|
|
throw FirebaseException('Firestore not initialized. Call initialize() first.');
|
|
|
|
|
throw FirebaseException(
|
|
|
|
|
'Firestore not initialized. Call initialize() first.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
@ -272,12 +276,12 @@ class FirebaseService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Uploads a file to Firebase Storage.
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// [file] - File to upload.
|
|
|
|
|
/// [path] - Storage path (e.g., 'users/userId/media/image.jpg').
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// Returns the download URL.
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// Throws [FirebaseException] if Storage is disabled or upload fails.
|
|
|
|
|
Future<String> uploadFile(File file, String path) async {
|
|
|
|
|
if (!config.enabled || !config.storageEnabled) {
|
|
|
|
|
@ -285,7 +289,8 @@ class FirebaseService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!_initialized || _storage == null) {
|
|
|
|
|
throw FirebaseException('Firebase Storage not initialized. Call initialize() first.');
|
|
|
|
|
throw FirebaseException(
|
|
|
|
|
'Firebase Storage not initialized. Call initialize() first.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
@ -298,7 +303,7 @@ class FirebaseService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gets the FCM token for push notifications.
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// Returns the FCM token, or null if messaging is disabled.
|
|
|
|
|
Future<String?> getFcmToken() async {
|
|
|
|
|
if (!config.enabled || !config.messagingEnabled || _messaging == null) {
|
|
|
|
|
@ -313,12 +318,13 @@ class FirebaseService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Logs an event to Firebase Analytics.
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// [eventName] - Name of the event.
|
|
|
|
|
/// [parameters] - Optional event parameters.
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// Does nothing if Analytics is disabled.
|
|
|
|
|
Future<void> logEvent(String eventName, {Map<String, dynamic>? parameters}) async {
|
|
|
|
|
Future<void> logEvent(String eventName,
|
|
|
|
|
{Map<String, dynamic>? parameters}) async {
|
|
|
|
|
if (!config.enabled || !config.analyticsEnabled || _analytics == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
@ -327,7 +333,8 @@ class FirebaseService {
|
|
|
|
|
// Convert Map<String, dynamic> to Map<String, Object> for Firebase Analytics
|
|
|
|
|
Map<String, Object>? analyticsParams;
|
|
|
|
|
if (parameters != null) {
|
|
|
|
|
analyticsParams = parameters.map((key, value) => MapEntry(key, value as Object));
|
|
|
|
|
analyticsParams =
|
|
|
|
|
parameters.map((key, value) => MapEntry(key, value as Object));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await _analytics!.logEvent(
|
|
|
|
|
@ -340,7 +347,7 @@ class FirebaseService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Disposes of Firebase resources.
|
|
|
|
|
///
|
|
|
|
|
///
|
|
|
|
|
/// Should be called when the service is no longer needed.
|
|
|
|
|
Future<void> dispose() async {
|
|
|
|
|
if (_auth != null) {
|
|
|
|
|
@ -350,4 +357,3 @@ class FirebaseService {
|
|
|
|
|
_initialized = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|