import 'dart:async'; import 'package:flutter/material.dart'; import 'models/job.dart'; import 'services/database_service.dart'; import 'services/dart_mq.dart'; import 'l10n/app_localizations.dart'; /// Global notifier for language changes final ValueNotifier localeNotifier = ValueNotifier(const Locale('de')); class AppState { static final AppState _instance = AppState._internal(); factory AppState() => _instance; AppState._internal(); String? _loggedInEmail; List _assignedJobs = []; final DatabaseService _databaseService = DatabaseService(); // Language settings String _languageCode = 'de'; String get languageCode => _languageCode; /// Get current locale Locale get currentLocale => Locale(_languageCode); /// Set language and update the global notifier Future setLanguage(String languageCode) async { if (supportedLanguageCodes.contains(languageCode)) { _languageCode = languageCode; await _databaseService.saveLanguagePreference(languageCode); localeNotifier.value = Locale(languageCode); } } /// Load language preference from database Future loadLanguagePreference() async { final savedLanguage = await _databaseService.loadLanguagePreference(); if (savedLanguage != null && supportedLanguageCodes.contains(savedLanguage)) { _languageCode = savedLanguage; localeNotifier.value = Locale(savedLanguage); } } // Serialize persistence to avoid overlapping DB save/load cycles bool _isPersistingJobs = false; List? _pendingJobs; // holds the latest jobs to persist if calls overlap // Jobs update notification (emitted once after DB was updated) final StreamController _jobsUpdatedController = StreamController.broadcast(); Stream get jobsUpdated => _jobsUpdatedController.stream; /// The logged-in user's email (used as local identifier for chats) String? get loggedInEmail => _loggedInEmail; List get assignedJobs => List.unmodifiable(_assignedJobs); void setLoggedInEmail(String email) { _loggedInEmail = email; } Future clearLogin() async { _loggedInEmail = null; _assignedJobs.clear(); // Clear database await _databaseService.clearAllData(); // Notify listeners/UI that jobs were cleared _jobsUpdatedController.add(null); DartMQ().publish(MQTopics.jobsUpdated, null); } bool get isLoggedIn => _loggedInEmail != null; Future setAssignedJobs(List jobs) async { // Coalesce overlapping calls: if a persist is already running, remember only the latest if (_isPersistingJobs) { _pendingJobs = jobs; return; } _isPersistingJobs = true; try { // Start with the initial batch to persist var toPersist = jobs; while (true) { // Normalize first final normalized = toPersist.map((j) => j.normalized()).toList(); // Persist normalized list to DB only (no UI notifications here) await _databaseService.saveJobs(normalized); // If another request came in during persistence, handle only the latest once if (_pendingJobs != null) { toPersist = _pendingJobs!; _pendingJobs = null; continue; } break; } // After DB is updated with the latest data, notify listeners once to refresh UI _jobsUpdatedController.add(null); // Also publish via dart_mq for app-wide decoupled messaging DartMQ().publish(MQTopics.jobsUpdated, null); } finally { _isPersistingJobs = false; } } void addJob(Job job) { if (!_assignedJobs.contains(job)) { _assignedJobs.add(job); } } void removeJob(String jobId) { _assignedJobs.removeWhere((job) => job.id == jobId); // Update database _databaseService.saveJobs(_assignedJobs); } /// Delete a job by ID (called when server sends job_deleted event) Future deleteJob(String jobId) async { _assignedJobs.removeWhere((job) => job.id == jobId); // Delete from database await _databaseService.deleteJob(jobId); // Notify listeners _jobsUpdatedController.add(null); DartMQ().publish(MQTopics.jobsUpdated, null); } /// Add a new job (called when server sends job_created event) Future addNewJob(Job job) async { // Check if job already exists if (_assignedJobs.any((j) => j.id == job.id)) { return; } // Add to memory _assignedJobs.insert(0, job); // Persist to database await _databaseService.saveOrUpdateJob(job); // Notify listeners _jobsUpdatedController.add(null); DartMQ().publish(MQTopics.jobsUpdated, null); } /// Load login state from saved credentials on app start Future loadLoginFromDatabase() async { final credentials = await _databaseService.loadCredentials(); if (credentials != null) { _loggedInEmail = credentials.email; } } void updateJob(Job updatedJob) { final index = _assignedJobs.indexWhere((job) => job.id == updatedJob.id); if (index != -1) { _assignedJobs[index] = updatedJob; } } /// Refresh in-memory jobs from the database without emitting other notifications Future refreshJobsFromDatabase() async { final jobs = await _databaseService.loadJobs(); _assignedJobs = jobs; } /// Persistently upsert a single job and refresh in-memory list Future upsertJob(Job job) async { await _databaseService.saveOrUpdateJob(job); final persisted = await _databaseService.loadJobs(); _assignedJobs = persisted.isNotEmpty ? persisted : _assignedJobs; } }