parent
8c6bf598f5
commit
3debb6ad7d
@ -0,0 +1,156 @@
|
|||||||
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
import '../config/config_loader.dart';
|
||||||
|
import '../data/local/local_storage_service.dart';
|
||||||
|
import '../data/nostr/nostr_service.dart';
|
||||||
|
import '../data/sync/sync_engine.dart';
|
||||||
|
import '../data/firebase/firebase_service.dart';
|
||||||
|
import '../data/session/session_service.dart';
|
||||||
|
import '../data/immich/immich_service.dart';
|
||||||
|
import 'app_services.dart';
|
||||||
|
import 'service_locator.dart';
|
||||||
|
import 'logger.dart';
|
||||||
|
|
||||||
|
/// Initializes all application services.
|
||||||
|
///
|
||||||
|
/// Handles the complete initialization sequence:
|
||||||
|
/// 1. Load environment configuration
|
||||||
|
/// 2. Initialize Firebase (if enabled)
|
||||||
|
/// 3. Initialize local storage
|
||||||
|
/// 4. Initialize Nostr service and sync engine
|
||||||
|
/// 5. Initialize Immich service
|
||||||
|
/// 6. Initialize Firebase service (if enabled)
|
||||||
|
/// 7. Initialize session service
|
||||||
|
/// 8. Register all services with ServiceLocator
|
||||||
|
class AppInitializer {
|
||||||
|
/// Initializes all application services.
|
||||||
|
///
|
||||||
|
/// [environment] - The environment to use ('dev' or 'prod').
|
||||||
|
///
|
||||||
|
/// Returns an [AppServices] instance with all initialized services.
|
||||||
|
///
|
||||||
|
/// Throws if initialization fails.
|
||||||
|
static Future<AppServices> initialize({
|
||||||
|
String environment = 'dev',
|
||||||
|
}) async {
|
||||||
|
Logger.info('Starting application initialization...');
|
||||||
|
|
||||||
|
// Load .env file (optional - falls back to defaults if not found)
|
||||||
|
try {
|
||||||
|
await dotenv.load(fileName: '.env');
|
||||||
|
Logger.debug('.env file loaded successfully');
|
||||||
|
} catch (e) {
|
||||||
|
Logger.warning('.env file not found, using default values: $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load configuration based on environment
|
||||||
|
final config = ConfigLoader.load(environment);
|
||||||
|
Logger.setEnabled(config.enableLogging);
|
||||||
|
Logger.info('Configuration loaded for environment: $environment');
|
||||||
|
|
||||||
|
// Initialize Firebase if enabled
|
||||||
|
if (config.firebaseConfig.enabled) {
|
||||||
|
try {
|
||||||
|
await Firebase.initializeApp();
|
||||||
|
Logger.info('Firebase initialized successfully');
|
||||||
|
} catch (e) {
|
||||||
|
Logger.error(
|
||||||
|
'Firebase initialization failed: $e',
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
Logger.warning(
|
||||||
|
'Note: Firebase requires google-services.json (Android) and GoogleService-Info.plist (iOS)',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize local storage service
|
||||||
|
Logger.debug('Initializing local storage service...');
|
||||||
|
final storageService = LocalStorageService();
|
||||||
|
try {
|
||||||
|
await storageService.initialize();
|
||||||
|
Logger.info('Local storage service initialized');
|
||||||
|
} catch (e) {
|
||||||
|
Logger.error('Failed to initialize storage: $e', e);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Nostr service and sync engine
|
||||||
|
Logger.debug('Initializing Nostr service...');
|
||||||
|
final nostrService = NostrService();
|
||||||
|
final nostrKeyPair = nostrService.generateKeyPair();
|
||||||
|
|
||||||
|
final syncEngine = SyncEngine(
|
||||||
|
localStorage: storageService,
|
||||||
|
nostrService: nostrService,
|
||||||
|
nostrKeyPair: nostrKeyPair,
|
||||||
|
);
|
||||||
|
Logger.info('Nostr service and sync engine initialized');
|
||||||
|
|
||||||
|
// Load relays from config
|
||||||
|
for (final relayUrl in config.nostrRelays) {
|
||||||
|
nostrService.addRelay(relayUrl);
|
||||||
|
}
|
||||||
|
Logger.debug('Loaded ${config.nostrRelays.length} relay(s) from config');
|
||||||
|
|
||||||
|
// Initialize Immich service
|
||||||
|
Logger.debug('Initializing Immich service...');
|
||||||
|
final immichService = ImmichService(
|
||||||
|
baseUrl: config.immichBaseUrl,
|
||||||
|
apiKey: config.immichApiKey,
|
||||||
|
localStorage: storageService,
|
||||||
|
);
|
||||||
|
Logger.info('Immich service initialized');
|
||||||
|
|
||||||
|
// Initialize Firebase service if enabled
|
||||||
|
FirebaseService? firebaseService;
|
||||||
|
if (config.firebaseConfig.enabled) {
|
||||||
|
try {
|
||||||
|
Logger.debug('Initializing Firebase service...');
|
||||||
|
firebaseService = FirebaseService(
|
||||||
|
config: config.firebaseConfig,
|
||||||
|
localStorage: storageService,
|
||||||
|
);
|
||||||
|
await firebaseService.initialize();
|
||||||
|
Logger.info('Firebase service initialized: ${firebaseService.isEnabled}');
|
||||||
|
} catch (e) {
|
||||||
|
Logger.error('Firebase service initialization failed: $e', e);
|
||||||
|
firebaseService = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize SessionService with Firebase and Nostr integration
|
||||||
|
Logger.debug('Initializing session service...');
|
||||||
|
final sessionService = SessionService(
|
||||||
|
localStorage: storageService,
|
||||||
|
syncEngine: syncEngine,
|
||||||
|
firebaseService: firebaseService,
|
||||||
|
nostrService: nostrService,
|
||||||
|
);
|
||||||
|
Logger.info('Session service initialized');
|
||||||
|
|
||||||
|
// Create AppServices container
|
||||||
|
final appServices = AppServices(
|
||||||
|
localStorageService: storageService,
|
||||||
|
nostrService: nostrService,
|
||||||
|
syncEngine: syncEngine,
|
||||||
|
firebaseService: firebaseService,
|
||||||
|
sessionService: sessionService,
|
||||||
|
immichService: immichService,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register all services with ServiceLocator
|
||||||
|
ServiceLocator.instance.registerServices(
|
||||||
|
localStorageService: storageService,
|
||||||
|
nostrService: nostrService,
|
||||||
|
syncEngine: syncEngine,
|
||||||
|
firebaseService: firebaseService,
|
||||||
|
sessionService: sessionService,
|
||||||
|
immichService: immichService,
|
||||||
|
);
|
||||||
|
|
||||||
|
Logger.info('Application initialization completed successfully');
|
||||||
|
return appServices;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
import '../data/local/local_storage_service.dart';
|
||||||
|
import '../data/nostr/nostr_service.dart';
|
||||||
|
import '../data/sync/sync_engine.dart';
|
||||||
|
import '../data/firebase/firebase_service.dart';
|
||||||
|
import '../data/session/session_service.dart';
|
||||||
|
import '../data/immich/immich_service.dart';
|
||||||
|
|
||||||
|
/// Container for all application services.
|
||||||
|
///
|
||||||
|
/// Holds references to all initialized services and provides
|
||||||
|
/// a convenient way to dispose of them.
|
||||||
|
class AppServices {
|
||||||
|
/// Local storage service.
|
||||||
|
final LocalStorageService localStorageService;
|
||||||
|
|
||||||
|
/// Nostr service.
|
||||||
|
final NostrService? nostrService;
|
||||||
|
|
||||||
|
/// Sync engine.
|
||||||
|
final SyncEngine? syncEngine;
|
||||||
|
|
||||||
|
/// Firebase service.
|
||||||
|
final FirebaseService? firebaseService;
|
||||||
|
|
||||||
|
/// Session service.
|
||||||
|
final SessionService? sessionService;
|
||||||
|
|
||||||
|
/// Immich service.
|
||||||
|
final ImmichService? immichService;
|
||||||
|
|
||||||
|
/// Creates an [AppServices] instance.
|
||||||
|
AppServices({
|
||||||
|
required this.localStorageService,
|
||||||
|
this.nostrService,
|
||||||
|
this.syncEngine,
|
||||||
|
this.firebaseService,
|
||||||
|
this.sessionService,
|
||||||
|
this.immichService,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Disposes of all services that need cleanup.
|
||||||
|
Future<void> dispose() async {
|
||||||
|
syncEngine?.dispose();
|
||||||
|
nostrService?.dispose();
|
||||||
|
firebaseService?.dispose();
|
||||||
|
|
||||||
|
// Close storage service
|
||||||
|
try {
|
||||||
|
await localStorageService.close();
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore errors during cleanup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/// Application-wide constants.
|
||||||
|
class AppConstants {
|
||||||
|
/// Application name.
|
||||||
|
static const String appName = 'App Boilerplate';
|
||||||
|
|
||||||
|
/// Default connection timeout duration.
|
||||||
|
static const Duration connectionTimeout = Duration(seconds: 3);
|
||||||
|
|
||||||
|
/// Default health check timeout duration.
|
||||||
|
static const Duration healthCheckTimeout = Duration(seconds: 2);
|
||||||
|
|
||||||
|
/// Default retry delay for operations.
|
||||||
|
static const Duration retryDelay = Duration(seconds: 1);
|
||||||
|
|
||||||
|
/// Maximum number of retries for failed operations.
|
||||||
|
static const int maxRetries = 3;
|
||||||
|
|
||||||
|
/// Maximum queue size for sync operations.
|
||||||
|
static const int maxQueueSize = 100;
|
||||||
|
|
||||||
|
/// Private constructor to prevent instantiation.
|
||||||
|
AppConstants._();
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/// Route names for navigation.
|
||||||
|
class RouteNames {
|
||||||
|
/// Home route.
|
||||||
|
static const String home = '/home';
|
||||||
|
|
||||||
|
/// Immich route.
|
||||||
|
static const String immich = '/immich';
|
||||||
|
|
||||||
|
/// Nostr Events route.
|
||||||
|
static const String nostrEvents = '/nostr-events';
|
||||||
|
|
||||||
|
/// Session route.
|
||||||
|
static const String session = '/session';
|
||||||
|
|
||||||
|
/// Settings route.
|
||||||
|
static const String settings = '/settings';
|
||||||
|
|
||||||
|
/// Relay Management route.
|
||||||
|
static const String relayManagement = '/relay-management';
|
||||||
|
|
||||||
|
/// Private constructor to prevent instantiation.
|
||||||
|
RouteNames._();
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
/// Barrel file for all exceptions.
|
||||||
|
///
|
||||||
|
/// Import this file to get access to all exception classes.
|
||||||
|
export 'firebase_exception.dart' show FirebaseServiceException;
|
||||||
|
export 'immich_exception.dart';
|
||||||
|
export 'invalid_environment_exception.dart';
|
||||||
|
export 'nostr_exception.dart';
|
||||||
|
export 'session_exception.dart';
|
||||||
|
export 'sync_exception.dart';
|
||||||
|
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
/// Exception thrown when Firebase service operations fail.
|
||||||
|
///
|
||||||
|
/// Note: This is different from Firebase SDK's FirebaseException.
|
||||||
|
/// This exception is used for our FirebaseService wrapper.
|
||||||
|
class FirebaseServiceException implements Exception {
|
||||||
|
/// Error message.
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
/// Creates a [FirebaseServiceException] with the provided message.
|
||||||
|
FirebaseServiceException(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'FirebaseServiceException: $message';
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/// Exception thrown when Immich API operations fail.
|
||||||
|
class ImmichException implements Exception {
|
||||||
|
/// Error message.
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
/// HTTP status code if available.
|
||||||
|
final int? statusCode;
|
||||||
|
|
||||||
|
/// Creates an [ImmichException] with the provided message.
|
||||||
|
ImmichException(this.message, [this.statusCode]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
if (statusCode != null) {
|
||||||
|
return 'ImmichException: $message (Status: $statusCode)';
|
||||||
|
}
|
||||||
|
return 'ImmichException: $message';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
/// Exception thrown when an invalid environment is provided to [ConfigLoader].
|
||||||
|
class InvalidEnvironmentException implements Exception {
|
||||||
|
/// The invalid environment that was provided.
|
||||||
|
final String environment;
|
||||||
|
|
||||||
|
/// Creates an [InvalidEnvironmentException] with the provided environment.
|
||||||
|
InvalidEnvironmentException(this.environment);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'InvalidEnvironmentException: Invalid environment "$environment". '
|
||||||
|
'Valid environments are: dev, prod';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
/// Exception thrown when Nostr operations fail.
|
||||||
|
class NostrException implements Exception {
|
||||||
|
/// Error message.
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
/// Creates a [NostrException] with the provided message.
|
||||||
|
NostrException(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'NostrException: $message';
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
/// Exception thrown when session operations fail.
|
||||||
|
class SessionException implements Exception {
|
||||||
|
/// Error message.
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
/// Creates a [SessionException] with the provided message.
|
||||||
|
SessionException(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'SessionException: $message';
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
/// Exception thrown when sync operations fail.
|
||||||
|
class SyncException implements Exception {
|
||||||
|
/// Error message.
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
/// Creates a [SyncException] with the provided message.
|
||||||
|
SyncException(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'SyncException: $message';
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
/// Log levels for different types of messages.
|
||||||
|
enum LogLevel {
|
||||||
|
/// Debug messages (only in debug mode).
|
||||||
|
debug,
|
||||||
|
|
||||||
|
/// Informational messages.
|
||||||
|
info,
|
||||||
|
|
||||||
|
/// Warning messages.
|
||||||
|
warning,
|
||||||
|
|
||||||
|
/// Error messages (always shown).
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Centralized logging service for the application.
|
||||||
|
///
|
||||||
|
/// Provides consistent logging across the app with different log levels.
|
||||||
|
/// In production, only errors are logged unless explicitly enabled.
|
||||||
|
class Logger {
|
||||||
|
/// Whether logging is enabled (can be configured via config).
|
||||||
|
static bool _enabled = kDebugMode;
|
||||||
|
|
||||||
|
/// Enable or disable logging.
|
||||||
|
static void setEnabled(bool enabled) {
|
||||||
|
_enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs a message with the specified level.
|
||||||
|
///
|
||||||
|
/// [level] - The log level (debug, info, warning, error).
|
||||||
|
/// [message] - The message to log.
|
||||||
|
/// [error] - Optional error object.
|
||||||
|
/// [stackTrace] - Optional stack trace.
|
||||||
|
static void log(
|
||||||
|
LogLevel level,
|
||||||
|
String message, [
|
||||||
|
Object? error,
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
]) {
|
||||||
|
// Always log errors, respect enabled flag for others
|
||||||
|
if (!_enabled && level != LogLevel.error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final prefix = '[${level.name.toUpperCase()}]';
|
||||||
|
final timestamp = DateTime.now().toIso8601String();
|
||||||
|
|
||||||
|
debugPrint('$timestamp $prefix $message');
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
debugPrint('$timestamp $prefix Error: $error');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stackTrace != null) {
|
||||||
|
debugPrint('$timestamp $prefix Stack trace: $stackTrace');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs a debug message (only in debug mode).
|
||||||
|
static void debug(String message) {
|
||||||
|
log(LogLevel.debug, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs an informational message.
|
||||||
|
static void info(String message) {
|
||||||
|
log(LogLevel.info, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs a warning message.
|
||||||
|
static void warning(String message, [Object? error]) {
|
||||||
|
log(LogLevel.warning, message, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs an error message (always shown).
|
||||||
|
static void error(String message, [Object? error, StackTrace? stackTrace]) {
|
||||||
|
log(LogLevel.error, message, error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
/// Service locator for dependency injection.
|
||||||
|
///
|
||||||
|
/// Provides a centralized registry for all application services,
|
||||||
|
/// making it easy to access services throughout the app
|
||||||
|
/// and swap implementations for testing.
|
||||||
|
class ServiceLocator {
|
||||||
|
/// Private constructor to prevent instantiation.
|
||||||
|
ServiceLocator._();
|
||||||
|
|
||||||
|
/// Singleton instance.
|
||||||
|
static final ServiceLocator instance = ServiceLocator._();
|
||||||
|
|
||||||
|
/// Local storage service.
|
||||||
|
dynamic _localStorageService;
|
||||||
|
|
||||||
|
/// Nostr service.
|
||||||
|
dynamic _nostrService;
|
||||||
|
|
||||||
|
/// Sync engine.
|
||||||
|
dynamic _syncEngine;
|
||||||
|
|
||||||
|
/// Firebase service.
|
||||||
|
dynamic _firebaseService;
|
||||||
|
|
||||||
|
/// Session service.
|
||||||
|
dynamic _sessionService;
|
||||||
|
|
||||||
|
/// Immich service.
|
||||||
|
dynamic _immichService;
|
||||||
|
|
||||||
|
/// Registers all services with the locator.
|
||||||
|
///
|
||||||
|
/// All services are optional and can be null if not configured.
|
||||||
|
void registerServices({
|
||||||
|
dynamic localStorageService,
|
||||||
|
dynamic nostrService,
|
||||||
|
dynamic syncEngine,
|
||||||
|
dynamic firebaseService,
|
||||||
|
dynamic sessionService,
|
||||||
|
dynamic immichService,
|
||||||
|
}) {
|
||||||
|
_localStorageService = localStorageService;
|
||||||
|
_nostrService = nostrService;
|
||||||
|
_syncEngine = syncEngine;
|
||||||
|
_firebaseService = firebaseService;
|
||||||
|
_sessionService = sessionService;
|
||||||
|
_immichService = immichService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the local storage service.
|
||||||
|
///
|
||||||
|
/// Throws [StateError] if service is not registered.
|
||||||
|
dynamic get localStorageService {
|
||||||
|
if (_localStorageService == null) {
|
||||||
|
throw StateError('LocalStorageService not registered');
|
||||||
|
}
|
||||||
|
return _localStorageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the Nostr service (nullable).
|
||||||
|
dynamic get nostrService => _nostrService;
|
||||||
|
|
||||||
|
/// Gets the sync engine (nullable).
|
||||||
|
dynamic get syncEngine => _syncEngine;
|
||||||
|
|
||||||
|
/// Gets the Firebase service (nullable).
|
||||||
|
dynamic get firebaseService => _firebaseService;
|
||||||
|
|
||||||
|
/// Gets the session service (nullable).
|
||||||
|
dynamic get sessionService => _sessionService;
|
||||||
|
|
||||||
|
/// Gets the Immich service (nullable).
|
||||||
|
dynamic get immichService => _immichService;
|
||||||
|
|
||||||
|
/// Clears all registered services (useful for testing).
|
||||||
|
void reset() {
|
||||||
|
_localStorageService = null;
|
||||||
|
_nostrService = null;
|
||||||
|
_syncEngine = null;
|
||||||
|
_firebaseService = null;
|
||||||
|
_sessionService = null;
|
||||||
|
_immichService = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in new issue