import 'dart:convert'; import 'package:isar/isar.dart'; import '../../core/database/app_database.dart'; import '../../core/database/database_service.dart'; import '../../core/errors/failure.dart'; import '../../core/utils/result.dart'; import '../models/content_provider_config.dart'; import '../repositories/provider_repository.dart'; class AppSettings { const AppSettings({ required this.enabledProviderIds, required this.nsfwEnabled, required this.cacheTtlHours, required this.cacheMaxItems, required this.providerPriority, required this.themeMode, required this.desktopColumns, required this.mobileColumns, required this.blurExplicitContent, required this.allowDownloads, required this.selectedFeedProviderIds, required this.showPostBadges, required this.defaultTopPeriodFilter, this.defaultRatingFilter, }); final List enabledProviderIds; final bool nsfwEnabled; final int cacheTtlHours; final int cacheMaxItems; final Map providerPriority; final String themeMode; final int desktopColumns; final int mobileColumns; final bool blurExplicitContent; final bool allowDownloads; final List selectedFeedProviderIds; final bool showPostBadges; final String defaultTopPeriodFilter; final String? defaultRatingFilter; static const defaults = AppSettings( enabledProviderIds: ['gelbooru', 'rule34', 'safebooru'], nsfwEnabled: true, cacheTtlHours: 24, cacheMaxItems: 2000, providerPriority: {'gelbooru': 0, 'rule34': 1, 'safebooru': 2}, themeMode: 'dark', desktopColumns: 5, mobileColumns: 2, blurExplicitContent: true, allowDownloads: true, selectedFeedProviderIds: [], showPostBadges: true, defaultTopPeriodFilter: 'none', defaultRatingFilter: null, ); AppSettings copyWith({ List? enabledProviderIds, bool? nsfwEnabled, int? cacheTtlHours, int? cacheMaxItems, Map? providerPriority, String? themeMode, int? desktopColumns, int? mobileColumns, bool? blurExplicitContent, bool? allowDownloads, List? selectedFeedProviderIds, bool? showPostBadges, String? defaultTopPeriodFilter, String? defaultRatingFilter, }) { return AppSettings( enabledProviderIds: enabledProviderIds ?? this.enabledProviderIds, nsfwEnabled: nsfwEnabled ?? this.nsfwEnabled, cacheTtlHours: cacheTtlHours ?? this.cacheTtlHours, cacheMaxItems: cacheMaxItems ?? this.cacheMaxItems, providerPriority: providerPriority ?? this.providerPriority, themeMode: themeMode ?? this.themeMode, desktopColumns: desktopColumns ?? this.desktopColumns, mobileColumns: mobileColumns ?? this.mobileColumns, blurExplicitContent: blurExplicitContent ?? this.blurExplicitContent, allowDownloads: allowDownloads ?? this.allowDownloads, selectedFeedProviderIds: selectedFeedProviderIds ?? this.selectedFeedProviderIds, showPostBadges: showPostBadges ?? this.showPostBadges, defaultTopPeriodFilter: defaultTopPeriodFilter ?? this.defaultTopPeriodFilter, defaultRatingFilter: defaultRatingFilter ?? this.defaultRatingFilter, ); } Map toJson() => { 'enabledProviderIds': enabledProviderIds, 'nsfwEnabled': nsfwEnabled, 'cacheTtlHours': cacheTtlHours, 'cacheMaxItems': cacheMaxItems, 'providerPriority': providerPriority, 'themeMode': themeMode, 'desktopColumns': desktopColumns, 'mobileColumns': mobileColumns, 'blurExplicitContent': blurExplicitContent, 'allowDownloads': allowDownloads, 'selectedFeedProviderIds': selectedFeedProviderIds, 'showPostBadges': showPostBadges, 'defaultTopPeriodFilter': defaultTopPeriodFilter, 'defaultRatingFilter': defaultRatingFilter, }; factory AppSettings.fromJson(Map json) => AppSettings( enabledProviderIds: List.from( (json['enabledProviderIds'] as List?) ?? defaults.enabledProviderIds, ), nsfwEnabled: (json['nsfwEnabled'] as bool?) ?? defaults.nsfwEnabled, cacheTtlHours: (json['cacheTtlHours'] as num?)?.toInt() ?? defaults.cacheTtlHours, cacheMaxItems: (json['cacheMaxItems'] as num?)?.toInt() ?? defaults.cacheMaxItems, providerPriority: Map.from( (json['providerPriority'] as Map?) ?? defaults.providerPriority, ), themeMode: (json['themeMode'] as String?) ?? defaults.themeMode, desktopColumns: (json['desktopColumns'] as num?)?.toInt() ?? defaults.desktopColumns, mobileColumns: (json['mobileColumns'] as num?)?.toInt() ?? defaults.mobileColumns, blurExplicitContent: (json['blurExplicitContent'] as bool?) ?? defaults.blurExplicitContent, allowDownloads: (json['allowDownloads'] as bool?) ?? defaults.allowDownloads, selectedFeedProviderIds: List.from( (json['selectedFeedProviderIds'] as List?) ?? defaults.selectedFeedProviderIds, ), showPostBadges: (json['showPostBadges'] as bool?) ?? defaults.showPostBadges, defaultTopPeriodFilter: (json['defaultTopPeriodFilter'] as String?) ?? defaults.defaultTopPeriodFilter, defaultRatingFilter: json['defaultRatingFilter'] as String?, ); } class SettingsService { SettingsService( this._databaseService, { ProviderRepository? providerRepository, }) : _providerRepository = providerRepository; final DatabaseService _databaseService; final ProviderRepository? _providerRepository; static const _settingsKey = 'app_settings'; Future> getSettings() { return _databaseService.safeRead((isar) async { final entity = await isar.appSettingEntitys .filter() .keyEqualTo(_settingsKey) .findFirst(); if (entity == null) return AppSettings.defaults; return AppSettings.fromJson( jsonDecode(entity.jsonValue) as Map); }); } Future> updateSettings(AppSettings settings) { return _databaseService.safeWrite((isar) async { await isar.appSettingEntitys.put( AppSettingEntity() ..key = _settingsKey ..jsonValue = jsonEncode(settings.toJson()) ..updatedAt = DateTime.now(), ); }); } Future> saveEnabledProviders(List providerIds) async { final result = await getSettings(); if (result is Error) return Error(result.failure); final settings = (result as Success).data; final updateResult = await updateSettings( settings.copyWith(enabledProviderIds: providerIds)); if (updateResult is Error) return updateResult; final repository = _providerRepository; if (repository == null) return updateResult; final providersResult = await repository.getProviders(); if (providersResult is Error>) { return Error(providersResult.failure); } final enabled = providerIds.toSet(); for (final provider in (providersResult as Success>).data) { await repository.saveProvider( provider.copyWith( enabled: enabled.contains(provider.id), updatedAt: DateTime.now(), ), ); } return updateResult; } Future> saveNsfwFilter(bool enabled) async { final result = await getSettings(); if (result is Error) return Error(result.failure); final settings = (result as Success).data; return updateSettings(settings.copyWith(nsfwEnabled: enabled)); } Future> saveCacheSettings({ required int ttlHours, required int maxItems, }) async { final result = await getSettings(); if (result is Error) return Error(result.failure); final settings = (result as Success).data; return updateSettings( settings.copyWith(cacheTtlHours: ttlHours, cacheMaxItems: maxItems), ); } Future> exportSettingsToJson() async { final result = await getSettings(); return result.fold( onSuccess: (settings) => Success(jsonEncode(settings.toJson())), onError: Error.new, ); } Future> importSettingsFromJson(String json) async { try { final settings = AppSettings.fromJson(jsonDecode(json) as Map); final result = await updateSettings(settings); if (result is Error) return result; await saveEnabledProviders(settings.enabledProviderIds); return result; } catch (error) { return Error( Failure( code: 'settings_import', message: 'Invalid settings JSON', details: error, ), ); } } }