Files
youtube_personal/lib/services/hidden_channels_service.dart
T

144 lines
3.2 KiB
Dart

import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/subscription_channel.dart';
import '../models/video_item.dart';
class HiddenChannelsService {
HiddenChannelsService._();
static final HiddenChannelsService instance =
HiddenChannelsService._();
static const String _key =
'youtube_personal.hidden_channels.v1';
final SharedPreferencesAsync _preferences =
SharedPreferencesAsync();
final Map<String, String> _hiddenById = <String, String>{};
final Set<String> _hiddenTitles = <String>{};
bool _loaded = false;
/// Lets Home/Channels invalidate their caches when the user hides or
/// restores a channel.
final ValueNotifier<int> revision = ValueNotifier<int>(0);
Future<void> ensureLoaded() async {
if (_loaded) {
return;
}
_loaded = true;
final raw = await _preferences.getString(_key);
if (raw == null || raw.isEmpty) {
return;
}
try {
final decoded = jsonDecode(raw);
if (decoded is! List) {
return;
}
for (final item in decoded) {
if (item is! Map) {
continue;
}
final id = item['id'];
final title = item['title'];
if (id is String && id.trim().isNotEmpty) {
_hiddenById[id.trim()] = title is String ? title : '';
}
if (title is String && title.trim().isNotEmpty) {
_hiddenTitles.add(_normalize(title));
}
}
} catch (_) {
_hiddenById.clear();
_hiddenTitles.clear();
}
}
bool isHiddenChannel({
String? channelId,
String? title,
}) {
final id = channelId?.trim() ?? '';
if (id.isNotEmpty && _hiddenById.containsKey(id)) {
return true;
}
final normalized = _normalize(title ?? '');
return normalized.isNotEmpty && _hiddenTitles.contains(normalized);
}
bool isHiddenVideo(VideoItem video) {
return isHiddenChannel(
channelId: video.channelId,
title: video.author,
);
}
Future<void> hide(SubscriptionChannel channel) async {
await ensureLoaded();
_hiddenById[channel.channelId] = channel.title;
final normalized = _normalize(channel.title);
if (normalized.isNotEmpty) {
_hiddenTitles.add(normalized);
}
await _save();
revision.value++;
}
Future<void> restore(SubscriptionChannel channel) async {
await ensureLoaded();
_hiddenById.remove(channel.channelId);
final normalized = _normalize(channel.title);
if (normalized.isNotEmpty) {
// Keep a title hidden only if another hidden channel still uses it.
final stillUsed = _hiddenById.values.any(
(value) => _normalize(value) == normalized,
);
if (!stillUsed) {
_hiddenTitles.remove(normalized);
}
}
await _save();
revision.value++;
}
Future<void> _save() async {
final payload = <Map<String, String>>[
for (final entry in _hiddenById.entries)
<String, String>{
'id': entry.key,
'title': entry.value,
},
];
await _preferences.setString(
_key,
jsonEncode(payload),
);
}
String _normalize(String value) {
return value.trim().toLowerCase();
}
}