import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../core/constants/app_constants.dart'; import '../../../../core/errors/failure.dart'; import '../../../../core/network/api_handler.dart'; import '../../../../core/network/dio_client.dart'; import '../../../../core/theme/branding_config.dart'; import '../../../../core/theme/theme_provider.dart'; import '../../../../core/utils/favicon_store.dart'; import '../../../../core/utils/favicon_updater.dart'; import '../../../../core/utils/media_url.dart'; import '../../data/datasources/settings_local_data_source.dart'; import '../../data/datasources/settings_remote_data_source.dart'; import '../../data/repositories/settings_repository_impl.dart'; import '../../domain/entities/app_settings.dart'; import '../../domain/repositories/settings_repository.dart'; import '../../domain/usecases/get_settings_use_case.dart'; import '../../domain/usecases/save_settings_use_case.dart'; final settingsLocalDataSourceProvider = Provider((ref) { return SettingsLocalDataSource(ref.watch(sharedPreferencesProvider)); }); final settingsRemoteDataSourceProvider = Provider((ref) { return SettingsRemoteDataSource(ref.watch(dioProvider)); }); final settingsRepositoryProvider = Provider((ref) { return SettingsRepositoryImpl( local: ref.watch(settingsLocalDataSourceProvider), remote: ref.watch(settingsRemoteDataSourceProvider), ); }); final getSettingsUseCaseProvider = Provider((ref) { return GetSettingsUseCase(ref.watch(settingsRepositoryProvider)); }); final saveSettingsUseCaseProvider = Provider((ref) { return SaveSettingsUseCase(ref.watch(settingsRepositoryProvider)); }); final appSettingsProvider = StateNotifierProvider((ref) { return AppSettingsNotifier( getSettings: ref.watch(getSettingsUseCaseProvider), saveSettings: ref.watch(saveSettingsUseCaseProvider), repository: ref.watch(settingsRepositoryProvider), faviconStore: FaviconStore(ref.watch(sharedPreferencesProvider)), syncAppLogo: (logoUrl, companyName) async { final branding = ref.read(brandingProvider); await ref.read(brandingProvider.notifier).updateBranding( BrandingConfig( logoUrl: logoUrl, primaryColorValue: branding.primaryColorValue, secondaryColorValue: branding.secondaryColorValue, companyName: companyName ?? branding.companyName, ), ); }, ); }); class AppSettingsNotifier extends StateNotifier { AppSettingsNotifier({ required GetSettingsUseCase getSettings, required SaveSettingsUseCase saveSettings, required SettingsRepository repository, required FaviconStore faviconStore, required Future Function(String? logoUrl, String? companyName) syncAppLogo, }) : _getSettings = getSettings, _saveSettings = saveSettings, _repository = repository, _faviconStore = faviconStore, _syncAppLogo = syncAppLogo, super(const AppSettings()) { _load(); } final GetSettingsUseCase _getSettings; final SaveSettingsUseCase _saveSettings; final SettingsRepository _repository; final FaviconStore _faviconStore; final Future Function(String? logoUrl, String? companyName) _syncAppLogo; Future? _companyProfileInFlight; Future _syncMainLogo(CompanyProfileSettings profile) async { final logo = resolveMediaUrl(profile.logoUrl); await _syncAppLogo( (logo == null || logo.isEmpty) ? null : logo, profile.companyName.isEmpty ? null : profile.companyName, ); } Future _syncFavicon(String? faviconUrl) async { final resolved = resolveMediaUrl(faviconUrl) ?? faviconUrl?.trim(); final value = (resolved == null || resolved.isEmpty) ? null : resolved; await _faviconStore.write(value); updateFavicon( resolveFaviconHref(value ?? AppConstants.defaultFaviconAsset), ); } Future _load() async { final result = await _getSettings(); state = result.data ?? const AppSettings(); await _syncFavicon(state.companyProfile.faviconUrl); await _syncMainLogo(state.companyProfile); } Future refreshCompanyProfile() { final existing = _companyProfileInFlight; if (existing != null) return existing; final future = () async { try { final result = await _repository.fetchCompanyProfile(); if (result.failure == null && result.data != null) { state = state.copyWith(companyProfile: result.data!); await _syncMainLogo(result.data!); await _syncFavicon(result.data!.faviconUrl); } return result.failure; } finally { _companyProfileInFlight = null; } }(); _companyProfileInFlight = future; return future; } Future refreshEmailSettings() async { final result = await _repository.fetchEmailSettings(); if (result.failure == null && result.data != null) { state = state.copyWith(email: result.data!); } return result.failure; } /// Fetches Company Profile + Email Settings after login / session restore /// and applies them app-wide (logo, favicon, company name, email config). Future syncCompanyAndEmailFromServer() async { await Future.wait([ refreshCompanyProfile(), refreshEmailSettings(), ]); } Future _persist(AppSettings settings) async { state = settings; final result = await _saveSettings(settings); state = result.data ?? settings; } Future updateGeneral(GeneralSettings general) async { await _persist(state.copyWith(general: general)); } /// PUT `/settings/company` — returns failure when the API call fails. Future updateCompanyProfile(CompanyProfileSettings profile) async { final result = await _repository.saveCompanyProfile(profile); if (result.failure != null) return result.failure; if (result.data != null) { state = state.copyWith(companyProfile: result.data!); await _saveSettings(state); await _syncMainLogo(result.data!); await _syncFavicon(result.data!.faviconUrl); } return null; } /// POST `/settings/company/logo` — also updates the app main logo. Future> uploadCompanyLogo( List bytes, String filename, ) async { final result = await _repository.uploadCompanyLogo(bytes, filename); if (result.failure == null && result.data != null && result.data!.isNotEmpty) { final logoUrl = resolveMediaUrl(result.data!) ?? result.data!; final profile = state.companyProfile.copyWith(logoUrl: logoUrl); state = state.copyWith(companyProfile: profile); await _saveSettings(state); await _syncMainLogo(profile); return (data: logoUrl, failure: null); } return result; } /// POST `/settings/company/favicon` — also updates the browser tab icon. Future> uploadCompanyFavicon( List bytes, String filename, ) async { final result = await _repository.uploadCompanyFavicon(bytes, filename); if (result.failure == null && result.data != null && result.data!.isNotEmpty) { final faviconUrl = resolveMediaUrl(result.data!) ?? result.data!; final profile = state.companyProfile.copyWith(faviconUrl: faviconUrl); state = state.copyWith(companyProfile: profile); await _saveSettings(state); await _syncFavicon(faviconUrl); return (data: faviconUrl, failure: null); } return result; } /// Applies a local/preview logo to company profile + main app branding. Future applyLocalCompanyLogo(String logoUrl) async { final resolved = resolveMediaUrl(logoUrl) ?? logoUrl; final profile = state.companyProfile.copyWith(logoUrl: resolved); state = state.copyWith(companyProfile: profile); await _saveSettings(state); await _syncMainLogo(profile); } /// Applies a local/preview favicon when upload is unavailable. Future applyLocalCompanyFavicon(String faviconUrl) async { final resolved = resolveMediaUrl(faviconUrl) ?? faviconUrl; final profile = state.companyProfile.copyWith(faviconUrl: resolved); state = state.copyWith(companyProfile: profile); await _saveSettings(state); await _syncFavicon(resolved); } Future updateUiPreferences(UiPreferencesSettings prefs) async { await _persist(state.copyWith(uiPreferences: prefs)); } Future updateAsset(AssetSettingsConfig asset) async { await _persist(state.copyWith(asset: asset)); } Future updateNotifications(NotificationSettingsConfig notifications) async { await _persist(state.copyWith(notifications: notifications)); } /// PUT `/settings/email` — returns failure when the API call fails. Future updateEmail(EmailConfigurationSettings email) async { final result = await _repository.saveEmailSettings(email); if (result.failure != null) return result.failure; if (result.data != null) { state = state.copyWith(email: result.data!); await _saveSettings(state); } return null; } Future updateSecurity(SecuritySettingsConfig security) async { await _persist(state.copyWith(security: security)); } Future resetToDefaults() async { await _persist(const AppSettings()); await _syncAppLogo(null, null); } }