import 'dart:async' show unawaited; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import '../mixins/disposable_change_notifier_mixin.dart'; import '../models/shader_preset.dart'; import '../services/settings_binding_owner.dart'; import '../services/settings_service.dart'; import '../services/shader_asset_loader.dart'; class ShaderProvider extends ChangeNotifier with DisposableChangeNotifierMixin { late final SettingsBindingOwner _settingsBinding; ShaderPreset _savedPreset = ShaderPreset.none; ShaderPreset _currentPreset = ShaderPreset.none; List _customPresets = []; List _allPresets = ShaderPreset.allPresets; bool _initialized = false; ShaderProvider() { _settingsBinding = SettingsBindingOwner( prefs: [SettingsService.globalShaderPreset, SettingsService.customShaderPresets], onRefresh: _syncFromSettings, ); unawaited(_settingsBinding.bind()); } void _syncFromSettings(SettingsService service) { final customPresets = []; for (final json in service.read(SettingsService.customShaderPresets)) { try { final preset = ShaderPreset.fromJson(json); final fileName = preset.fileName; if (preset.type == ShaderPresetType.custom && fileName != null && ShaderAssetLoader.isValidCustomShaderFileName(fileName)) { customPresets.add(preset); } } on Object { // Imported settings can contain structurally invalid custom rows. } } _customPresets = customPresets; _refreshAllPresets(); final presetId = service.read(SettingsService.globalShaderPreset); _savedPreset = findPresetById(presetId) ?? ShaderPreset.none; _currentPreset = _savedPreset; _initialized = true; safeNotifyListeners(); } @override void dispose() { _settingsBinding.dispose(); super.dispose(); } bool get initialized => _initialized; ShaderPreset get savedPreset => _savedPreset; ShaderPreset get currentPreset => _currentPreset; List get allPresets => _allPresets; List get customPresets => _customPresets; bool get isShaderEnabled => _currentPreset.type != ShaderPresetType.none; ShaderPreset? findPresetById(String id) { return ShaderPreset.fromId(id) ?? _customPresets.firstWhereOrNull((p) => p.id == id); } Future setPreset(ShaderPreset preset) async { final service = _settingsBinding.settings ?? await SettingsService.getInstance(); await service.write(SettingsService.globalShaderPreset, preset.id); final changed = _savedPreset.id != preset.id || _currentPreset.id != preset.id; _savedPreset = preset; _currentPreset = preset; if (changed || _settingsBinding.settings == null) { safeNotifyListeners(); } } /// Update the current preset without persisting (e.g. toggling off temporarily) void setCurrentPreset(ShaderPreset preset) { if (_currentPreset.id != preset.id) { _currentPreset = preset; notifyListeners(); } } /// Import a custom shader from a file path. /// Copies the file to the custom shaders directory and creates a preset. Future importCustomShader(String filePath, String displayName) async { final storedFileName = await ShaderAssetLoader.importCustomShader(filePath); final id = 'custom_$storedFileName'; final preset = ShaderPreset(id: id, name: displayName, type: ShaderPresetType.custom, fileName: storedFileName); _customPresets.add(preset); _refreshAllPresets(); await _saveCustomPresets(); return preset; } /// Delete a custom shader preset and its file. Future deleteCustomShader(ShaderPreset preset) async { final wasActive = _currentPreset.id == preset.id || _savedPreset.id == preset.id; if (preset.fileName != null) { await ShaderAssetLoader.deleteCustomShader(preset.fileName!); } _customPresets.removeWhere((p) => p.id == preset.id); _refreshAllPresets(); await _saveCustomPresets(); // Reset to none if the deleted preset was active if (wasActive) { await setPreset(ShaderPreset.none); } } void _refreshAllPresets() { _allPresets = _customPresets.isEmpty ? ShaderPreset.allPresets : List.unmodifiable([...ShaderPreset.allPresets, ..._customPresets]); } Future _saveCustomPresets() async { final service = _settingsBinding.settings ?? await SettingsService.getInstance(); final data = _customPresets.map((p) => p.toJson()).toList(); await service.write(SettingsService.customShaderPresets, data); } /// Reset to default (no shaders) Future reset() async { await setPreset(ShaderPreset.none); } }