Files
SpotiFLAC-Mobile/lib/providers/recent_access_provider.dart
T

274 lines
7.1 KiB
Dart

import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
const _recentAccessKey = 'recent_access_history';
const _hiddenDownloadsKey = 'hidden_downloads_in_recents';
const _maxRecentItems = 20;
/// Types of items that can be accessed
enum RecentAccessType {
artist,
album,
track,
playlist,
}
/// Represents a recently accessed item
class RecentAccessItem {
final String id;
final String name;
final String? subtitle; // Artist name for tracks/albums, null for artists
final String? imageUrl;
final RecentAccessType type;
final DateTime accessedAt;
final String? providerId; // Extension ID or 'deezer' for built-in
const RecentAccessItem({
required this.id,
required this.name,
this.subtitle,
this.imageUrl,
required this.type,
required this.accessedAt,
this.providerId,
});
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'subtitle': subtitle,
'imageUrl': imageUrl,
'type': type.name,
'accessedAt': accessedAt.toIso8601String(),
'providerId': providerId,
};
factory RecentAccessItem.fromJson(Map<String, dynamic> json) {
return RecentAccessItem(
id: json['id'] as String,
name: json['name'] as String,
subtitle: json['subtitle'] as String?,
imageUrl: json['imageUrl'] as String?,
type: RecentAccessType.values.firstWhere(
(e) => e.name == json['type'],
orElse: () => RecentAccessType.track,
),
accessedAt: DateTime.parse(json['accessedAt'] as String),
providerId: json['providerId'] as String?,
);
}
/// Create a unique key for deduplication
String get uniqueKey => '${type.name}:${providerId ?? 'default'}:$id';
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is RecentAccessItem &&
runtimeType == other.runtimeType &&
uniqueKey == other.uniqueKey;
@override
int get hashCode => uniqueKey.hashCode;
}
/// State for recent access history
class RecentAccessState {
final List<RecentAccessItem> items;
final Set<String> hiddenDownloadIds; // IDs of downloads hidden from recents
final bool isLoaded;
const RecentAccessState({
this.items = const [],
this.hiddenDownloadIds = const {},
this.isLoaded = false,
});
RecentAccessState copyWith({
List<RecentAccessItem>? items,
Set<String>? hiddenDownloadIds,
bool? isLoaded,
}) {
return RecentAccessState(
items: items ?? this.items,
hiddenDownloadIds: hiddenDownloadIds ?? this.hiddenDownloadIds,
isLoaded: isLoaded ?? this.isLoaded,
);
}
}
/// Provider for managing recent access history
class RecentAccessNotifier extends Notifier<RecentAccessState> {
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
@override
RecentAccessState build() {
_loadHistory();
return const RecentAccessState();
}
Future<void> _loadHistory() async {
final prefs = await _prefs;
final json = prefs.getString(_recentAccessKey);
final hiddenJson = prefs.getStringList(_hiddenDownloadsKey);
List<RecentAccessItem> items = [];
Set<String> hiddenIds = {};
if (json != null) {
try {
final List<dynamic> decoded = jsonDecode(json);
items = decoded
.map((e) => RecentAccessItem.fromJson(e as Map<String, dynamic>))
.toList();
} catch (_) {
// Ignore JSON parse errors, use empty list
}
}
if (hiddenJson != null) {
hiddenIds = hiddenJson.toSet();
}
state = state.copyWith(items: items, hiddenDownloadIds: hiddenIds, isLoaded: true);
}
Future<void> _saveHistory() async {
final prefs = await _prefs;
final json = jsonEncode(state.items.map((e) => e.toJson()).toList());
await prefs.setString(_recentAccessKey, json);
}
Future<void> _saveHiddenDownloads() async {
final prefs = await _prefs;
await prefs.setStringList(_hiddenDownloadsKey, state.hiddenDownloadIds.toList());
}
/// Record an access to an artist
void recordArtistAccess({
required String id,
required String name,
String? imageUrl,
String? providerId,
}) {
_recordAccess(RecentAccessItem(
id: id,
name: name,
imageUrl: imageUrl,
type: RecentAccessType.artist,
accessedAt: DateTime.now(),
providerId: providerId,
));
}
/// Record an access to an album
void recordAlbumAccess({
required String id,
required String name,
String? artistName,
String? imageUrl,
String? providerId,
}) {
_recordAccess(RecentAccessItem(
id: id,
name: name,
subtitle: artistName,
imageUrl: imageUrl,
type: RecentAccessType.album,
accessedAt: DateTime.now(),
providerId: providerId,
));
}
/// Record an access to a track
void recordTrackAccess({
required String id,
required String name,
String? artistName,
String? imageUrl,
String? providerId,
}) {
_recordAccess(RecentAccessItem(
id: id,
name: name,
subtitle: artistName,
imageUrl: imageUrl,
type: RecentAccessType.track,
accessedAt: DateTime.now(),
providerId: providerId,
));
}
/// Record an access to a playlist
void recordPlaylistAccess({
required String id,
required String name,
String? ownerName,
String? imageUrl,
String? providerId,
}) {
_recordAccess(RecentAccessItem(
id: id,
name: name,
subtitle: ownerName,
imageUrl: imageUrl,
type: RecentAccessType.playlist,
accessedAt: DateTime.now(),
providerId: providerId,
));
}
void _recordAccess(RecentAccessItem item) {
final updatedItems = state.items
.where((e) => e.uniqueKey != item.uniqueKey)
.toList();
updatedItems.insert(0, item);
if (updatedItems.length > _maxRecentItems) {
updatedItems.removeRange(_maxRecentItems, updatedItems.length);
}
state = state.copyWith(items: updatedItems);
_saveHistory();
}
/// Remove a specific item from history
void removeItem(RecentAccessItem item) {
final updatedItems = state.items
.where((e) => e.uniqueKey != item.uniqueKey)
.toList();
state = state.copyWith(items: updatedItems);
_saveHistory();
}
/// Hide a download item from recents (without deleting the actual download)
void hideDownloadFromRecents(String downloadId) {
final updatedHidden = {...state.hiddenDownloadIds, downloadId};
state = state.copyWith(hiddenDownloadIds: updatedHidden);
_saveHiddenDownloads();
}
/// Check if a download is hidden from recents
bool isDownloadHidden(String downloadId) {
return state.hiddenDownloadIds.contains(downloadId);
}
/// Clear all history
void clearHistory() {
state = state.copyWith(items: []);
_saveHistory();
}
/// Clear hidden downloads (show all again)
void clearHiddenDownloads() {
state = state.copyWith(hiddenDownloadIds: {});
_saveHiddenDownloads();
}
}
final recentAccessProvider = NotifierProvider<RecentAccessNotifier, RecentAccessState>(
RecentAccessNotifier.new,
);