From bf6cec8d6939bd373f6015af08d8ab34a6e631cd Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 14 Jun 2024 22:23:12 +0600 Subject: [PATCH] refactor(blacklist): use drift sql db instead of hive --- lib/components/track_tile/track_options.dart | 29 +- lib/components/track_tile/track_tile.dart | 8 +- lib/models/database/database.dart | 10 +- lib/models/database/database.g.dart | 399 ++++++++++++++++++- lib/models/database/tables/blacklist.dart | 18 + lib/modules/artist/artist_card.dart | 6 +- lib/pages/artist/section/header.dart | 24 +- lib/pages/settings/blacklist.dart | 32 +- lib/provider/blacklist_provider.dart | 108 ++--- 9 files changed, 512 insertions(+), 122 deletions(-) create mode 100644 lib/models/database/tables/blacklist.dart diff --git a/lib/components/track_tile/track_options.dart b/lib/components/track_tile/track_options.dart index 89f6679d..fd3018ba 100644 --- a/lib/components/track_tile/track_options.dart +++ b/lib/components/track_tile/track_options.dart @@ -18,6 +18,7 @@ import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; +import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/local_track.dart'; import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/provider/blacklist_provider.dart'; @@ -170,11 +171,8 @@ class TrackOptions extends HookConsumerWidget { final favorites = useTrackToggleLike(track, ref); final isBlackListed = useMemoized( - () => blacklist.contains( - BlacklistedElement.track( - track.id!, - track.name!, - ), + () => blacklist.asData?.value.any( + (element) => element.elementId == track.id, ), [blacklist, track], ); @@ -258,13 +256,16 @@ class TrackOptions extends HookConsumerWidget { .removeTracks(playlistId ?? "", [track.id!]); break; case TrackOptionValue.blacklist: - if (isBlackListed) { - ref.read(blacklistProvider.notifier).remove( - BlacklistedElement.track(track.id!, track.name!), - ); + if (isBlackListed == null) break; + if (isBlackListed == true) { + await ref.read(blacklistProvider.notifier).remove(track.id!); } else { - ref.read(blacklistProvider.notifier).add( - BlacklistedElement.track(track.id!, track.name!), + await ref.read(blacklistProvider.notifier).add( + BlacklistTableCompanion.insert( + name: track.name!, + elementId: track.id!, + elementType: BlacklistedType.track, + ), ); } break; @@ -399,10 +400,10 @@ class TrackOptions extends HookConsumerWidget { PopSheetEntry( value: TrackOptionValue.blacklist, leading: const Icon(SpotubeIcons.playlistRemove), - iconColor: !isBlackListed ? Colors.red[400] : null, - textColor: !isBlackListed ? Colors.red[400] : null, + iconColor: isBlackListed != true ? Colors.red[400] : null, + textColor: isBlackListed != true ? Colors.red[400] : null, title: Text( - isBlackListed + isBlackListed == true ? context.l10n.remove_from_blacklist : context.l10n.add_to_blacklist, ), diff --git a/lib/components/track_tile/track_tile.dart b/lib/components/track_tile/track_tile.dart index 9ba87abe..e2e7e293 100644 --- a/lib/components/track_tile/track_tile.dart +++ b/lib/components/track_tile/track_tile.dart @@ -53,14 +53,10 @@ class TrackTile extends HookConsumerWidget { final theme = Theme.of(context); final blacklist = ref.watch(blacklistProvider); + final blacklistNotifier = ref.watch(blacklistProvider.notifier); final isBlackListed = useMemoized( - () => blacklist.contains( - BlacklistedElement.track( - track.id!, - track.name!, - ), - ), + () => blacklistNotifier.contains(track), [blacklist, track], ); diff --git a/lib/models/database/database.dart b/lib/models/database/database.dart index e7ac2558..ad09933d 100644 --- a/lib/models/database/database.dart +++ b/lib/models/database/database.dart @@ -19,12 +19,20 @@ part 'database.g.dart'; part 'tables/preferences.dart'; part 'tables/source_match.dart'; part 'tables/skip_segment.dart'; +part 'tables/blacklist.dart'; part 'typeconverters/color.dart'; part 'typeconverters/locale.dart'; part 'typeconverters/string_list.dart'; -@DriftDatabase(tables: [PreferencesTable, SourceMatchTable, SkipSegmentTable]) +@DriftDatabase( + tables: [ + PreferencesTable, + SourceMatchTable, + SkipSegmentTable, + BlacklistTable, + ], +) class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection()); diff --git a/lib/models/database/database.g.dart b/lib/models/database/database.g.dart index 9cc8a1c1..8c996d21 100644 --- a/lib/models/database/database.g.dart +++ b/lib/models/database/database.g.dart @@ -1789,6 +1789,266 @@ class SkipSegmentTableCompanion extends UpdateCompanion { } } +class $BlacklistTableTable extends BlacklistTable + with TableInfo<$BlacklistTableTable, BlacklistTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $BlacklistTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _elementTypeMeta = + const VerificationMeta('elementType'); + @override + late final GeneratedColumnWithTypeConverter + elementType = GeneratedColumn('element_type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true) + .withConverter( + $BlacklistTableTable.$converterelementType); + static const VerificationMeta _elementIdMeta = + const VerificationMeta('elementId'); + @override + late final GeneratedColumn elementId = GeneratedColumn( + 'element_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, name, elementType, elementId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'blacklist_table'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); + } else if (isInserting) { + context.missing(_nameMeta); + } + context.handle(_elementTypeMeta, const VerificationResult.success()); + if (data.containsKey('element_id')) { + context.handle(_elementIdMeta, + elementId.isAcceptableOrUnknown(data['element_id']!, _elementIdMeta)); + } else if (isInserting) { + context.missing(_elementIdMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + BlacklistTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return BlacklistTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + elementType: $BlacklistTableTable.$converterelementType.fromSql( + attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}element_type'])!), + elementId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}element_id'])!, + ); + } + + @override + $BlacklistTableTable createAlias(String alias) { + return $BlacklistTableTable(attachedDatabase, alias); + } + + static JsonTypeConverter2 + $converterelementType = + const EnumNameConverter(BlacklistedType.values); +} + +class BlacklistTableData extends DataClass + implements Insertable { + final int id; + final String name; + final BlacklistedType elementType; + final String elementId; + const BlacklistTableData( + {required this.id, + required this.name, + required this.elementType, + required this.elementId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + { + map['element_type'] = Variable( + $BlacklistTableTable.$converterelementType.toSql(elementType)); + } + map['element_id'] = Variable(elementId); + return map; + } + + BlacklistTableCompanion toCompanion(bool nullToAbsent) { + return BlacklistTableCompanion( + id: Value(id), + name: Value(name), + elementType: Value(elementType), + elementId: Value(elementId), + ); + } + + factory BlacklistTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return BlacklistTableData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + elementType: $BlacklistTableTable.$converterelementType + .fromJson(serializer.fromJson(json['elementType'])), + elementId: serializer.fromJson(json['elementId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'elementType': serializer.toJson( + $BlacklistTableTable.$converterelementType.toJson(elementType)), + 'elementId': serializer.toJson(elementId), + }; + } + + BlacklistTableData copyWith( + {int? id, + String? name, + BlacklistedType? elementType, + String? elementId}) => + BlacklistTableData( + id: id ?? this.id, + name: name ?? this.name, + elementType: elementType ?? this.elementType, + elementId: elementId ?? this.elementId, + ); + @override + String toString() { + return (StringBuffer('BlacklistTableData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('elementType: $elementType, ') + ..write('elementId: $elementId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, elementType, elementId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is BlacklistTableData && + other.id == this.id && + other.name == this.name && + other.elementType == this.elementType && + other.elementId == this.elementId); +} + +class BlacklistTableCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value elementType; + final Value elementId; + const BlacklistTableCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.elementType = const Value.absent(), + this.elementId = const Value.absent(), + }); + BlacklistTableCompanion.insert({ + this.id = const Value.absent(), + required String name, + required BlacklistedType elementType, + required String elementId, + }) : name = Value(name), + elementType = Value(elementType), + elementId = Value(elementId); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? elementType, + Expression? elementId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (elementType != null) 'element_type': elementType, + if (elementId != null) 'element_id': elementId, + }); + } + + BlacklistTableCompanion copyWith( + {Value? id, + Value? name, + Value? elementType, + Value? elementId}) { + return BlacklistTableCompanion( + id: id ?? this.id, + name: name ?? this.name, + elementType: elementType ?? this.elementType, + elementId: elementId ?? this.elementId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (elementType.present) { + map['element_type'] = Variable( + $BlacklistTableTable.$converterelementType.toSql(elementType.value)); + } + if (elementId.present) { + map['element_id'] = Variable(elementId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('BlacklistTableCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('elementType: $elementType, ') + ..write('elementId: $elementId') + ..write(')')) + .toString(); + } +} + abstract class _$AppDatabase extends GeneratedDatabase { _$AppDatabase(QueryExecutor e) : super(e); _$AppDatabaseManager get managers => _$AppDatabaseManager(this); @@ -1798,14 +2058,23 @@ abstract class _$AppDatabase extends GeneratedDatabase { $SourceMatchTableTable(this); late final $SkipSegmentTableTable skipSegmentTable = $SkipSegmentTableTable(this); + late final $BlacklistTableTable blacklistTable = $BlacklistTableTable(this); late final Index uniqTrackMatch = Index('uniq_track_match', 'CREATE UNIQUE INDEX uniq_track_match ON source_match_table (track_id, source_id, source_type)'); + late final Index uniqueBlacklist = Index('unique_blacklist', + 'CREATE UNIQUE INDEX unique_blacklist ON blacklist_table (element_type, element_id)'); @override Iterable> get allTables => allSchemaEntities.whereType>(); @override - List get allSchemaEntities => - [preferencesTable, sourceMatchTable, skipSegmentTable, uniqTrackMatch]; + List get allSchemaEntities => [ + preferencesTable, + sourceMatchTable, + skipSegmentTable, + blacklistTable, + uniqTrackMatch, + uniqueBlacklist + ]; } typedef $$PreferencesTableTableInsertCompanionBuilder @@ -2571,6 +2840,130 @@ class $$SkipSegmentTableTableOrderingComposer ColumnOrderings(column, joinBuilders: joinBuilders)); } +typedef $$BlacklistTableTableInsertCompanionBuilder = BlacklistTableCompanion + Function({ + Value id, + required String name, + required BlacklistedType elementType, + required String elementId, +}); +typedef $$BlacklistTableTableUpdateCompanionBuilder = BlacklistTableCompanion + Function({ + Value id, + Value name, + Value elementType, + Value elementId, +}); + +class $$BlacklistTableTableTableManager extends RootTableManager< + _$AppDatabase, + $BlacklistTableTable, + BlacklistTableData, + $$BlacklistTableTableFilterComposer, + $$BlacklistTableTableOrderingComposer, + $$BlacklistTableTableProcessedTableManager, + $$BlacklistTableTableInsertCompanionBuilder, + $$BlacklistTableTableUpdateCompanionBuilder> { + $$BlacklistTableTableTableManager( + _$AppDatabase db, $BlacklistTableTable table) + : super(TableManagerState( + db: db, + table: table, + filteringComposer: + $$BlacklistTableTableFilterComposer(ComposerState(db, table)), + orderingComposer: + $$BlacklistTableTableOrderingComposer(ComposerState(db, table)), + getChildManagerBuilder: (p) => + $$BlacklistTableTableProcessedTableManager(p), + getUpdateCompanionBuilder: ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value elementType = const Value.absent(), + Value elementId = const Value.absent(), + }) => + BlacklistTableCompanion( + id: id, + name: name, + elementType: elementType, + elementId: elementId, + ), + getInsertCompanionBuilder: ({ + Value id = const Value.absent(), + required String name, + required BlacklistedType elementType, + required String elementId, + }) => + BlacklistTableCompanion.insert( + id: id, + name: name, + elementType: elementType, + elementId: elementId, + ), + )); +} + +class $$BlacklistTableTableProcessedTableManager extends ProcessedTableManager< + _$AppDatabase, + $BlacklistTableTable, + BlacklistTableData, + $$BlacklistTableTableFilterComposer, + $$BlacklistTableTableOrderingComposer, + $$BlacklistTableTableProcessedTableManager, + $$BlacklistTableTableInsertCompanionBuilder, + $$BlacklistTableTableUpdateCompanionBuilder> { + $$BlacklistTableTableProcessedTableManager(super.$state); +} + +class $$BlacklistTableTableFilterComposer + extends FilterComposer<_$AppDatabase, $BlacklistTableTable> { + $$BlacklistTableTableFilterComposer(super.$state); + ColumnFilters get id => $state.composableBuilder( + column: $state.table.id, + builder: (column, joinBuilders) => + ColumnFilters(column, joinBuilders: joinBuilders)); + + ColumnFilters get name => $state.composableBuilder( + column: $state.table.name, + builder: (column, joinBuilders) => + ColumnFilters(column, joinBuilders: joinBuilders)); + + ColumnWithTypeConverterFilters + get elementType => $state.composableBuilder( + column: $state.table.elementType, + builder: (column, joinBuilders) => ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + + ColumnFilters get elementId => $state.composableBuilder( + column: $state.table.elementId, + builder: (column, joinBuilders) => + ColumnFilters(column, joinBuilders: joinBuilders)); +} + +class $$BlacklistTableTableOrderingComposer + extends OrderingComposer<_$AppDatabase, $BlacklistTableTable> { + $$BlacklistTableTableOrderingComposer(super.$state); + ColumnOrderings get id => $state.composableBuilder( + column: $state.table.id, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); + + ColumnOrderings get name => $state.composableBuilder( + column: $state.table.name, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); + + ColumnOrderings get elementType => $state.composableBuilder( + column: $state.table.elementType, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); + + ColumnOrderings get elementId => $state.composableBuilder( + column: $state.table.elementId, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); +} + class _$AppDatabaseManager { final _$AppDatabase _db; _$AppDatabaseManager(this._db); @@ -2580,4 +2973,6 @@ class _$AppDatabaseManager { $$SourceMatchTableTableTableManager(_db, _db.sourceMatchTable); $$SkipSegmentTableTableTableManager get skipSegmentTable => $$SkipSegmentTableTableTableManager(_db, _db.skipSegmentTable); + $$BlacklistTableTableTableManager get blacklistTable => + $$BlacklistTableTableTableManager(_db, _db.blacklistTable); } diff --git a/lib/models/database/tables/blacklist.dart b/lib/models/database/tables/blacklist.dart new file mode 100644 index 00000000..8a8d9dee --- /dev/null +++ b/lib/models/database/tables/blacklist.dart @@ -0,0 +1,18 @@ +part of '../database.dart'; + +enum BlacklistedType { + artist, + track; +} + +@TableIndex( + name: "unique_blacklist", + unique: true, + columns: {#elementType, #elementId}, +) +class BlacklistTable extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get name => text()(); + TextColumn get elementType => textEnum()(); + TextColumn get elementId => text()(); +} diff --git a/lib/modules/artist/artist_card.dart b/lib/modules/artist/artist_card.dart index c1404e42..896271f2 100644 --- a/lib/modules/artist/artist_card.dart +++ b/lib/modules/artist/artist_card.dart @@ -27,8 +27,8 @@ class ArtistCard extends HookConsumerWidget { ); final isBlackListed = ref.watch( blacklistProvider.select( - (blacklist) => blacklist.contains( - BlacklistedElement.artist(artist.id!, artist.name!), + (blacklist) => blacklist.asData?.value.any( + (element) => element.elementId == artist.id, ), ), ); @@ -55,7 +55,7 @@ class ArtistCard extends HookConsumerWidget { elevation: 3, shape: RoundedRectangleBorder( borderRadius: radius, - side: isBlackListed + side: isBlackListed == true ? const BorderSide( color: Colors.red, width: 2, diff --git a/lib/pages/artist/section/header.dart b/lib/pages/artist/section/header.dart index 7ca8964d..a30535dd 100644 --- a/lib/pages/artist/section/header.dart +++ b/lib/pages/artist/section/header.dart @@ -10,6 +10,7 @@ import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/hooks/utils/use_breakpoint_value.dart'; +import 'package:spotube/models/database/database.dart'; import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/spotify/spotify.dart'; @@ -39,10 +40,9 @@ class ArtistPageHeader extends HookConsumerWidget { ); final auth = ref.watch(authenticationProvider); - final blacklist = ref.watch(blacklistProvider); - final isBlackListed = blacklist.contains( - BlacklistedElement.artist(artistId, artist.name!), - ); + ref.watch(blacklistProvider); + final blacklistNotifier = ref.watch(blacklistProvider.notifier); + final isBlackListed = blacklistNotifier.containsArtist(artist); final image = artist.images.asUrlString( placeholder: ImagePlaceholder.artist, @@ -187,14 +187,16 @@ class ArtistPageHeader extends HookConsumerWidget { ), onPressed: () async { if (isBlackListed) { - ref.read(blacklistProvider.notifier).remove( - BlacklistedElement.artist( - artist.id!, artist.name!), - ); + await ref + .read(blacklistProvider.notifier) + .remove(artist.id!); } else { - ref.read(blacklistProvider.notifier).add( - BlacklistedElement.artist( - artist.id!, artist.name!), + await ref.read(blacklistProvider.notifier).add( + BlacklistTableCompanion.insert( + name: artist.name!, + elementId: artist.id!, + elementType: BlacklistedType.artist, + ), ); } }, diff --git a/lib/pages/settings/blacklist.dart b/lib/pages/settings/blacklist.dart index b5e10821..1f018dab 100644 --- a/lib/pages/settings/blacklist.dart +++ b/lib/pages/settings/blacklist.dart @@ -24,19 +24,21 @@ class BlackListPage extends HookConsumerWidget { final filteredBlacklist = useMemoized( () { if (searchText.value.isEmpty) { - return blacklist; + return blacklist.asData?.value ?? []; } - return blacklist - .map( - (e) => ( - weightedRatio("${e.name} ${e.type.name}", searchText.value), - e, - ), - ) - .sorted((a, b) => b.$1.compareTo(a.$1)) - .where((e) => e.$1 > 50) - .map((e) => e.$2) - .toList(); + return blacklist.asData?.value + .map( + (e) => ( + weightedRatio( + "${e.name} ${e.elementType.name}", searchText.value), + e, + ), + ) + .sorted((a, b) => b.$1.compareTo(a.$1)) + .where((e) => e.$1 > 50) + .map((e) => e.$2) + .toList() ?? + []; }, [blacklist, searchText.value], ); @@ -70,14 +72,14 @@ class BlackListPage extends HookConsumerWidget { final item = filteredBlacklist.elementAt(index); return ListTile( leading: Text("${index + 1}."), - title: Text("${item.name} (${item.type.name})"), - subtitle: Text(item.id), + title: Text("${item.name} (${item.elementType.name})"), + subtitle: Text(item.elementId), trailing: IconButton( icon: Icon(SpotubeIcons.trash, color: Colors.red[400]), onPressed: () { ref .read(blacklistProvider.notifier) - .remove(filteredBlacklist.elementAt(index)); + .remove(filteredBlacklist.elementAt(index).elementId); }, ), ); diff --git a/lib/provider/blacklist_provider.dart b/lib/provider/blacklist_provider.dart index 4f488112..a51d399f 100644 --- a/lib/provider/blacklist_provider.dart +++ b/lib/provider/blacklist_provider.dart @@ -2,69 +2,59 @@ import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/models/current_playlist.dart'; -import 'package:spotube/utils/persisted_state_notifier.dart'; - -enum BlacklistedType { - artist, - track; - - static BlacklistedType fromName(String name) => - BlacklistedType.values.firstWhere((e) => e.name == name); -} - -class BlacklistedElement { - final String id; - final String name; - final BlacklistedType type; - - BlacklistedElement.artist(this.id, this.name) : type = BlacklistedType.artist; - - BlacklistedElement.track(this.id, this.name) : type = BlacklistedType.track; - - BlacklistedElement.fromJson(Map json) - : id = json['id'], - name = json['name'], - type = BlacklistedType.fromName(json['type']); - - Map toJson() => {'id': id, 'type': type.name, 'name': name}; +import 'package:spotube/models/database/database.dart'; +import 'package:spotube/provider/database/database.dart'; +class BlackListNotifier extends AsyncNotifier> { @override - operator ==(other) => - other is BlacklistedElement && - other.id == id && - other.type == type && - other.name == name; + build() async { + final database = ref.watch(databaseProvider); - @override - int get hashCode => id.hashCode ^ type.hashCode ^ name.hashCode; -} + final subscription = database + .select(database.blacklistTable) + .watch() + .listen((event) => state = AsyncData(event)); -class BlackListNotifier - extends PersistedStateNotifier> { - BlackListNotifier() : super({}, "blacklist"); + ref.onDispose(() { + subscription.cancel(); + }); - void add(BlacklistedElement element) { - state = state.union({element}); + return await database.select(database.blacklistTable).get(); } - void remove(BlacklistedElement element) { - state = state.difference({element}); + AppDatabase get _database => ref.read(databaseProvider); + + Future add(BlacklistTableCompanion element) async { + _database.into(_database.blacklistTable).insert(element); + } + + Future remove(String elementId) async { + await (_database.delete(_database.blacklistTable) + ..where((tbl) => tbl.elementId.equals(elementId))) + .go(); } bool contains(TrackSimple track) { final containsTrack = - state.contains(BlacklistedElement.track(track.id!, track.name!)); + state.asData?.value.any((element) => element.elementId == track.id) ?? + false; final containsTrackArtists = track.artists?.any( - (artist) => state.contains( - BlacklistedElement.artist(artist.id!, artist.name ?? "Spotify"), - ), + (artist) => + state.asData?.value.any((el) => el.elementId == artist.id) ?? + false, ) ?? false; return containsTrack || containsTrackArtists; } + bool containsArtist(ArtistSimple artist) { + return state.asData?.value + .any((element) => element.elementId == artist.id) ?? + false; + } + /// Filters the non blacklisted tracks from the given [tracks] Iterable filter(Iterable tracks) { return tracks.whereNot(contains).toList(); @@ -75,34 +65,12 @@ class BlackListNotifier id: playlist.id, name: playlist.name, thumbnail: playlist.thumbnail, - tracks: playlist.tracks.where( - (track) { - return !state - .contains(BlacklistedElement.track(track.id!, track.name!)) && - !(track.artists ?? []).any( - (artist) => state.contains( - BlacklistedElement.artist(artist.id!, artist.name!), - ), - ); - }, - ).toList(), + tracks: playlist.tracks.where((track) => !contains(track)).toList(), ); } - - @override - Set fromJson(Map json) { - return json['blacklist'] - .map((e) => BlacklistedElement.fromJson(e)) - .toSet(); - } - - @override - Map toJson() { - return {'blacklist': state.map((e) => e.toJson()).toList()}; - } } final blacklistProvider = - StateNotifierProvider>((ref) { - return BlackListNotifier(); -}); + AsyncNotifierProvider>( + () => BlackListNotifier(), +);