109 lines
4.1 KiB
C#
109 lines
4.1 KiB
C#
using System.Windows;
|
|
using System.Threading;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using UtopiaCanteenSystem.Data;
|
|
using UtopiaCanteenSystem.Services;
|
|
using UtopiaCanteenSystem.ViewModels;
|
|
|
|
namespace UtopiaCanteenSystem;
|
|
|
|
/// <summary>
|
|
/// Application entry point. Initializes database, builds service graph, starts hourly sync timer.
|
|
/// </summary>
|
|
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 menuLookupService = new MenuLookupService(configService);
|
|
var mealScheduleService = new ProductionMealScheduleService(configService);
|
|
var mealSessionResolver = new DbMealSessionResolver(mealScheduleService);
|
|
var rfidService = new RfidService(dbFactory, configService, employeeLookupService, mealSessionResolver);
|
|
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),
|
|
() => new MainDashboardViewModel(navigationService, rfidService, configService, session),
|
|
() => new AdminSettingsAuthViewModel(authService, session, navigationService, configService),
|
|
() => new SettingsViewModel(configService, navigationService, adminAuditService, syncService),
|
|
() => new MealSchedulesViewModel(mealScheduleService, navigationService));
|
|
|
|
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);
|
|
}
|
|
}
|