You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

276 lines
9.9 KiB

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/media/media_service_interface.dart';
import '../data/media/multi_media_service.dart';
import '../data/media/models/media_server_config.dart';
import '../data/recipes/recipe_service.dart';
import 'app_services.dart';
import 'service_locator.dart';
import 'logger.dart';
import 'theme_notifier.dart';
import 'package:flutter/material.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 MultiMediaService for managing multiple media servers with fallback
Logger.debug('Initializing MultiMediaService...');
final multiMediaService = MultiMediaService(localStorage: storageService);
await multiMediaService.loadServers();
// Migrate old single server config if no servers exist
if (multiMediaService.getServers().isEmpty) {
final settingsItem = await storageService.getItem('app_settings');
final immichEnabled = config.immichEnabled;
final defaultBlossomServer = config.blossomServer;
final mediaServerType = settingsItem?.data['media_server_type'] as String? ??
(immichEnabled ? 'immich' : 'blossom');
final immichBaseUrl = settingsItem?.data['immich_base_url'] as String? ?? config.immichBaseUrl;
final immichApiKey = settingsItem?.data['immich_api_key'] as String? ?? config.immichApiKey;
final blossomBaseUrl = settingsItem?.data['blossom_base_url'] as String? ?? defaultBlossomServer;
// Migrate Immich config
if (immichEnabled && mediaServerType == 'immich' && immichBaseUrl.isNotEmpty && immichApiKey.isNotEmpty) {
final config = MediaServerConfig(
id: 'immich-${DateTime.now().millisecondsSinceEpoch}',
type: 'immich',
baseUrl: immichBaseUrl,
apiKey: immichApiKey,
isDefault: true,
name: 'Immich Server',
);
await multiMediaService.addServer(config);
Logger.info('Migrated Immich server config to MultiMediaService');
}
// Migrate Blossom config
if ((mediaServerType == 'blossom' || !immichEnabled) && blossomBaseUrl.isNotEmpty) {
final config = MediaServerConfig(
id: 'blossom-${DateTime.now().millisecondsSinceEpoch}',
type: 'blossom',
baseUrl: blossomBaseUrl,
isDefault: true,
name: 'Blossom Server',
);
await multiMediaService.addServer(config);
Logger.info('Migrated Blossom server config to MultiMediaService');
}
await multiMediaService.saveServers();
}
MediaServiceInterface? mediaService = multiMediaService;
if (multiMediaService.getServers().isEmpty) {
Logger.warning('No media servers configured');
mediaService = null;
} else {
Logger.info('MultiMediaService initialized with ${multiMediaService.getServers().length} server(s)');
}
// 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');
// Initialize RecipeService
Logger.debug('Initializing recipe service...');
final recipeService = RecipeService(
localStorage: storageService,
nostrService: nostrService,
);
try {
await recipeService.initialize();
Logger.info('Recipe service initialized');
} catch (e) {
Logger.error('Failed to initialize recipe service: $e', e);
// Continue without recipe service - it will be initialized later when user logs in
}
// Initialize ThemeNotifier and load theme preference
Logger.debug('Initializing theme notifier...');
final themeNotifier = ThemeNotifier();
try {
final settingsItem = await storageService.getItem('app_settings');
if (settingsItem != null && settingsItem.data.containsKey('dark_mode')) {
final isDark = settingsItem.data['dark_mode'] == true;
themeNotifier.setThemeMode(isDark ? ThemeMode.dark : ThemeMode.light);
Logger.info('Theme preference loaded: ${isDark ? "dark" : "light"}');
} else {
Logger.info('No theme preference found, using light mode');
}
} catch (e) {
Logger.warning('Failed to load theme preference: $e');
}
// Create AppServices container
final appServices = AppServices(
localStorageService: storageService,
nostrService: nostrService,
syncEngine: syncEngine,
firebaseService: firebaseService,
sessionService: sessionService,
mediaService: mediaService,
recipeService: recipeService,
);
// Register all services with ServiceLocator
ServiceLocator.instance.registerServices(
localStorageService: storageService,
nostrService: nostrService,
syncEngine: syncEngine,
firebaseService: firebaseService,
sessionService: sessionService,
mediaService: mediaService,
recipeService: recipeService,
themeNotifier: themeNotifier,
);
Logger.info('Application initialization completed successfully');
return appServices;
}
/// Reinitializes the media service based on current settings.
///
/// This should be called when media server settings are changed.
/// Preserves the Nostr keypair if Blossom is selected.
static Future<void> reinitializeMediaService() async {
Logger.info('Reinitializing media service...');
final localStorage = ServiceLocator.instance.localStorageService;
if (localStorage == null) {
Logger.warning(
'Cannot reinitialize media service: LocalStorageService not available');
return;
}
// Create new MultiMediaService instance and load servers
final multiMediaService = MultiMediaService(localStorage: localStorage);
await multiMediaService.loadServers();
MediaServiceInterface? newMediaService = multiMediaService;
if (multiMediaService.getServers().isEmpty) {
Logger.warning('No media servers configured');
newMediaService = null;
} else {
Logger.info('MultiMediaService reinitialized with ${multiMediaService.getServers().length} server(s)');
}
// Register the new media service with ServiceLocator
ServiceLocator.instance.registerServices(
mediaService: newMediaService,
);
Logger.info('Media service reinitialized and registered successfully');
}
}

Powered by TurnKey Linux.