318 lines
12 KiB
Dart
318 lines
12 KiB
Dart
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,
|
|
required this.blacklistedTags,
|
|
required this.whitelistedTags,
|
|
required this.smartBlacklistRules,
|
|
required this.hideViewedPosts,
|
|
this.defaultRatingFilter,
|
|
});
|
|
|
|
final List<String> enabledProviderIds;
|
|
final bool nsfwEnabled;
|
|
final int cacheTtlHours;
|
|
final int cacheMaxItems;
|
|
final Map<String, int> providerPriority;
|
|
final String themeMode;
|
|
final int desktopColumns;
|
|
final int mobileColumns;
|
|
final bool blurExplicitContent;
|
|
final bool allowDownloads;
|
|
final List<String> selectedFeedProviderIds;
|
|
final bool showPostBadges;
|
|
final String defaultTopPeriodFilter;
|
|
final List<String> blacklistedTags;
|
|
final List<String> whitelistedTags;
|
|
final List<String> smartBlacklistRules;
|
|
final bool hideViewedPosts;
|
|
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',
|
|
blacklistedTags: [],
|
|
whitelistedTags: [],
|
|
smartBlacklistRules: [],
|
|
hideViewedPosts: false,
|
|
defaultRatingFilter: null,
|
|
);
|
|
|
|
AppSettings copyWith({
|
|
List<String>? enabledProviderIds,
|
|
bool? nsfwEnabled,
|
|
int? cacheTtlHours,
|
|
int? cacheMaxItems,
|
|
Map<String, int>? providerPriority,
|
|
String? themeMode,
|
|
int? desktopColumns,
|
|
int? mobileColumns,
|
|
bool? blurExplicitContent,
|
|
bool? allowDownloads,
|
|
List<String>? selectedFeedProviderIds,
|
|
bool? showPostBadges,
|
|
String? defaultTopPeriodFilter,
|
|
List<String>? blacklistedTags,
|
|
List<String>? whitelistedTags,
|
|
List<String>? smartBlacklistRules,
|
|
bool? hideViewedPosts,
|
|
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,
|
|
blacklistedTags: blacklistedTags ?? this.blacklistedTags,
|
|
whitelistedTags: whitelistedTags ?? this.whitelistedTags,
|
|
smartBlacklistRules: smartBlacklistRules ?? this.smartBlacklistRules,
|
|
hideViewedPosts: hideViewedPosts ?? this.hideViewedPosts,
|
|
defaultRatingFilter: defaultRatingFilter ?? this.defaultRatingFilter,
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> 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,
|
|
'blacklistedTags': blacklistedTags,
|
|
'whitelistedTags': whitelistedTags,
|
|
'smartBlacklistRules': smartBlacklistRules,
|
|
'hideViewedPosts': hideViewedPosts,
|
|
'defaultRatingFilter': defaultRatingFilter,
|
|
};
|
|
|
|
factory AppSettings.fromJson(Map<String, dynamic> json) => AppSettings(
|
|
enabledProviderIds: List<String>.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<String, int>.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<String>.from(
|
|
(json['selectedFeedProviderIds'] as List?) ??
|
|
defaults.selectedFeedProviderIds,
|
|
),
|
|
showPostBadges:
|
|
(json['showPostBadges'] as bool?) ?? defaults.showPostBadges,
|
|
defaultTopPeriodFilter: (json['defaultTopPeriodFilter'] as String?) ??
|
|
defaults.defaultTopPeriodFilter,
|
|
blacklistedTags: List<String>.from(
|
|
(json['blacklistedTags'] as List?) ?? defaults.blacklistedTags,
|
|
),
|
|
whitelistedTags: List<String>.from(
|
|
(json['whitelistedTags'] as List?) ?? defaults.whitelistedTags,
|
|
),
|
|
smartBlacklistRules: List<String>.from(
|
|
(json['smartBlacklistRules'] as List?) ??
|
|
(json['blacklistedTags'] as List?) ??
|
|
defaults.smartBlacklistRules,
|
|
),
|
|
hideViewedPosts:
|
|
(json['hideViewedPosts'] as bool?) ?? defaults.hideViewedPosts,
|
|
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<Result<AppSettings>> 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<String, dynamic>);
|
|
});
|
|
}
|
|
|
|
Future<Result<void>> updateSettings(AppSettings settings) {
|
|
return _databaseService.safeWrite((isar) async {
|
|
await isar.appSettingEntitys.put(
|
|
AppSettingEntity()
|
|
..key = _settingsKey
|
|
..jsonValue = jsonEncode(settings.toJson())
|
|
..updatedAt = DateTime.now(),
|
|
);
|
|
});
|
|
}
|
|
|
|
Future<Result<void>> saveEnabledProviders(List<String> providerIds) async {
|
|
final result = await getSettings();
|
|
if (result is Error<AppSettings>) return Error(result.failure);
|
|
final settings = (result as Success<AppSettings>).data;
|
|
final updateResult = await updateSettings(
|
|
settings.copyWith(enabledProviderIds: providerIds));
|
|
if (updateResult is Error<void>) return updateResult;
|
|
|
|
final repository = _providerRepository;
|
|
if (repository == null) return updateResult;
|
|
|
|
final providersResult = await repository.getProviders();
|
|
if (providersResult is Error<List<ContentProviderConfig>>) {
|
|
return Error(providersResult.failure);
|
|
}
|
|
final enabled = providerIds.toSet();
|
|
for (final provider
|
|
in (providersResult as Success<List<ContentProviderConfig>>).data) {
|
|
await repository.saveProvider(
|
|
provider.copyWith(
|
|
enabled: enabled.contains(provider.id),
|
|
updatedAt: DateTime.now(),
|
|
),
|
|
);
|
|
}
|
|
return updateResult;
|
|
}
|
|
|
|
Future<Result<void>> saveNsfwFilter(bool enabled) async {
|
|
final result = await getSettings();
|
|
if (result is Error<AppSettings>) return Error(result.failure);
|
|
final settings = (result as Success<AppSettings>).data;
|
|
return updateSettings(settings.copyWith(nsfwEnabled: enabled));
|
|
}
|
|
|
|
Future<Result<void>> saveCacheSettings({
|
|
required int ttlHours,
|
|
required int maxItems,
|
|
}) async {
|
|
final result = await getSettings();
|
|
if (result is Error<AppSettings>) return Error(result.failure);
|
|
final settings = (result as Success<AppSettings>).data;
|
|
return updateSettings(
|
|
settings.copyWith(cacheTtlHours: ttlHours, cacheMaxItems: maxItems),
|
|
);
|
|
}
|
|
|
|
Future<Result<String>> exportSettingsToJson() async {
|
|
final result = await getSettings();
|
|
if (result is Error<AppSettings>) return Error(result.failure);
|
|
final settings = (result as Success<AppSettings>).data;
|
|
final providers = <ContentProviderConfig>[];
|
|
final repository = _providerRepository;
|
|
if (repository != null) {
|
|
final providersResult = await repository.getProviders(enabledOnly: false);
|
|
if (providersResult is Success<List<ContentProviderConfig>>) {
|
|
providers.addAll(providersResult.data);
|
|
}
|
|
}
|
|
return Success(
|
|
const JsonEncoder.withIndent(' ').convert({
|
|
'schemaVersion': 2,
|
|
'settings': settings.toJson(),
|
|
'providers': providers.map((provider) => provider.toJson()).toList(),
|
|
}),
|
|
);
|
|
}
|
|
|
|
Future<Result<void>> importSettingsFromJson(String json) async {
|
|
try {
|
|
final decoded = jsonDecode(json) as Map<String, dynamic>;
|
|
final settingsJson =
|
|
(decoded['settings'] as Map?)?.cast<String, dynamic>() ?? decoded;
|
|
final settings = AppSettings.fromJson(settingsJson);
|
|
final result = await updateSettings(settings);
|
|
if (result is Error<void>) return result;
|
|
final repository = _providerRepository;
|
|
final providerItems = decoded['providers'];
|
|
if (repository != null && providerItems is List) {
|
|
for (final item in providerItems.whereType<Map>()) {
|
|
await repository.saveProvider(
|
|
ContentProviderConfig.fromJson(
|
|
Map<String, dynamic>.from(item),
|
|
).copyWith(updatedAt: DateTime.now()),
|
|
);
|
|
}
|
|
}
|
|
await saveEnabledProviders(settings.enabledProviderIds);
|
|
return result;
|
|
} catch (error) {
|
|
return Error(
|
|
Failure(
|
|
code: 'settings_import',
|
|
message: 'Invalid settings JSON',
|
|
details: error,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|