using System.Windows; using System.Threading; using Microsoft.EntityFrameworkCore; using UtopiaCanteenSystem.Data; using UtopiaCanteenSystem.Services; using UtopiaCanteenSystem.ViewModels; namespace UtopiaCanteenSystem; /// /// Application entry point. Initializes database, builds service graph, starts hourly sync timer. /// public partial class App : Application { private static Mutex _mutex; private System.Timers.Timer? _syncTimer; private int _isSyncRunning; protected override void OnStartup(StartupEventArgs e) { bool isNewInstance; _mutex = new Mutex(true, "UtopiaCanteenSystemMutex", out isNewInstance); if (!isNewInstance) { MessageBox.Show("Another instance of the application is already running.", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning); Current.Shutdown(); return; } base.OnStartup(e); // Build services (simple composition; no DI container) var dbFactory = new DbContextFactory(); // Auto-create SQLite database on first run using (var db = dbFactory.CreateDbContext()) { db.EnsureDatabaseCreated(); } var configService = new ConfigService(); var employeeLookupService = new EmployeeLookupService(configService); var employeePhotoService = new EmployeePhotoService(configService); var menuLookupService = new MenuLookupService(configService); var mealScheduleService = new ProductionMealScheduleService(configService); var mealSessionResolver = new DbMealSessionResolver(mealScheduleService); var rfidService = new RfidService(dbFactory, configService, employeeLookupService, mealSessionResolver, menuLookupService); var syncService = new SyncService(dbFactory, configService); var adminAuditService = new AdminAuditService(dbFactory); var session = new AppSession(); var authenticationUrl = "https://portal.utopiaindustries.pk/uind/rest/auth/user/"; var authService = new AuthService(authenticationUrl); // NavigationService: declare first so lambdas can capture it, then assign (avoids "used before declared") NavigationService navigationService = null!; navigationService = new NavigationService( session, () => new AdminLoginViewModel(authService, session, navigationService, configService, adminAuditService), () => new ScannerDashboardViewModel(rfidService, navigationService, session, configService, menuLookupService, employeePhotoService), () => new MainDashboardViewModel(navigationService, rfidService, configService, session), () => new AdminSettingsAuthViewModel(authService, session, navigationService, configService), () => new SettingsViewModel(configService, navigationService, adminAuditService, syncService), () => new MealSchedulesViewModel(mealScheduleService, navigationService, configService)); var mainViewModel = new MainViewModel(navigationService); var mainWindow = new MainWindow { DataContext = mainViewModel }; // Set the window to open maximized mainWindow.WindowState = WindowState.Maximized; mainWindow.Show(); // Background sync: every 15 minutes, POST unsynced lunch_order_transactions to API _syncTimer = new System.Timers.Timer(TimeSpan.FromMinutes(15).TotalMilliseconds) { AutoReset = true }; _syncTimer.Elapsed += async (_, _) => { // Prevent overlapping sync runs; if one is still running, skip this tick. if (Interlocked.Exchange(ref _isSyncRunning, 1) == 1) return; try { await syncService.SyncNowAsync().ConfigureAwait(false); } catch { // Ignore; will retry next tick } finally { Interlocked.Exchange(ref _isSyncRunning, 0); } }; _syncTimer.Start(); } protected override void OnExit(ExitEventArgs e) { _mutex.ReleaseMutex(); _syncTimer?.Stop(); _syncTimer?.Dispose(); base.OnExit(e); } }