mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
refactor(blacklist): use drift sql db instead of hive
This commit is contained in:
parent
52d4f60ccc
commit
bf6cec8d69
@ -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,
|
||||
),
|
||||
|
@ -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],
|
||||
);
|
||||
|
||||
|
@ -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());
|
||||
|
||||
|
@ -1789,6 +1789,266 @@ class SkipSegmentTableCompanion extends UpdateCompanion<SkipSegmentTableData> {
|
||||
}
|
||||
}
|
||||
|
||||
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<int> id = GeneratedColumn<int>(
|
||||
'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<String> name = GeneratedColumn<String>(
|
||||
'name', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||
static const VerificationMeta _elementTypeMeta =
|
||||
const VerificationMeta('elementType');
|
||||
@override
|
||||
late final GeneratedColumnWithTypeConverter<BlacklistedType, String>
|
||||
elementType = GeneratedColumn<String>('element_type', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true)
|
||||
.withConverter<BlacklistedType>(
|
||||
$BlacklistTableTable.$converterelementType);
|
||||
static const VerificationMeta _elementIdMeta =
|
||||
const VerificationMeta('elementId');
|
||||
@override
|
||||
late final GeneratedColumn<String> elementId = GeneratedColumn<String>(
|
||||
'element_id', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||
@override
|
||||
List<GeneratedColumn> 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<BlacklistTableData> 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<GeneratedColumn> get $primaryKey => {id};
|
||||
@override
|
||||
BlacklistTableData map(Map<String, dynamic> 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<BlacklistedType, String, String>
|
||||
$converterelementType =
|
||||
const EnumNameConverter<BlacklistedType>(BlacklistedType.values);
|
||||
}
|
||||
|
||||
class BlacklistTableData extends DataClass
|
||||
implements Insertable<BlacklistTableData> {
|
||||
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<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['id'] = Variable<int>(id);
|
||||
map['name'] = Variable<String>(name);
|
||||
{
|
||||
map['element_type'] = Variable<String>(
|
||||
$BlacklistTableTable.$converterelementType.toSql(elementType));
|
||||
}
|
||||
map['element_id'] = Variable<String>(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<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return BlacklistTableData(
|
||||
id: serializer.fromJson<int>(json['id']),
|
||||
name: serializer.fromJson<String>(json['name']),
|
||||
elementType: $BlacklistTableTable.$converterelementType
|
||||
.fromJson(serializer.fromJson<String>(json['elementType'])),
|
||||
elementId: serializer.fromJson<String>(json['elementId']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<int>(id),
|
||||
'name': serializer.toJson<String>(name),
|
||||
'elementType': serializer.toJson<String>(
|
||||
$BlacklistTableTable.$converterelementType.toJson(elementType)),
|
||||
'elementId': serializer.toJson<String>(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<BlacklistTableData> {
|
||||
final Value<int> id;
|
||||
final Value<String> name;
|
||||
final Value<BlacklistedType> elementType;
|
||||
final Value<String> 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<BlacklistTableData> custom({
|
||||
Expression<int>? id,
|
||||
Expression<String>? name,
|
||||
Expression<String>? elementType,
|
||||
Expression<String>? 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<int>? id,
|
||||
Value<String>? name,
|
||||
Value<BlacklistedType>? elementType,
|
||||
Value<String>? elementId}) {
|
||||
return BlacklistTableCompanion(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
elementType: elementType ?? this.elementType,
|
||||
elementId: elementId ?? this.elementId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (id.present) {
|
||||
map['id'] = Variable<int>(id.value);
|
||||
}
|
||||
if (name.present) {
|
||||
map['name'] = Variable<String>(name.value);
|
||||
}
|
||||
if (elementType.present) {
|
||||
map['element_type'] = Variable<String>(
|
||||
$BlacklistTableTable.$converterelementType.toSql(elementType.value));
|
||||
}
|
||||
if (elementId.present) {
|
||||
map['element_id'] = Variable<String>(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<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities =>
|
||||
[preferencesTable, sourceMatchTable, skipSegmentTable, uniqTrackMatch];
|
||||
List<DatabaseSchemaEntity> 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<int> id,
|
||||
required String name,
|
||||
required BlacklistedType elementType,
|
||||
required String elementId,
|
||||
});
|
||||
typedef $$BlacklistTableTableUpdateCompanionBuilder = BlacklistTableCompanion
|
||||
Function({
|
||||
Value<int> id,
|
||||
Value<String> name,
|
||||
Value<BlacklistedType> elementType,
|
||||
Value<String> 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<int> id = const Value.absent(),
|
||||
Value<String> name = const Value.absent(),
|
||||
Value<BlacklistedType> elementType = const Value.absent(),
|
||||
Value<String> elementId = const Value.absent(),
|
||||
}) =>
|
||||
BlacklistTableCompanion(
|
||||
id: id,
|
||||
name: name,
|
||||
elementType: elementType,
|
||||
elementId: elementId,
|
||||
),
|
||||
getInsertCompanionBuilder: ({
|
||||
Value<int> 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<int> get id => $state.composableBuilder(
|
||||
column: $state.table.id,
|
||||
builder: (column, joinBuilders) =>
|
||||
ColumnFilters(column, joinBuilders: joinBuilders));
|
||||
|
||||
ColumnFilters<String> get name => $state.composableBuilder(
|
||||
column: $state.table.name,
|
||||
builder: (column, joinBuilders) =>
|
||||
ColumnFilters(column, joinBuilders: joinBuilders));
|
||||
|
||||
ColumnWithTypeConverterFilters<BlacklistedType, BlacklistedType, String>
|
||||
get elementType => $state.composableBuilder(
|
||||
column: $state.table.elementType,
|
||||
builder: (column, joinBuilders) => ColumnWithTypeConverterFilters(
|
||||
column,
|
||||
joinBuilders: joinBuilders));
|
||||
|
||||
ColumnFilters<String> 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<int> get id => $state.composableBuilder(
|
||||
column: $state.table.id,
|
||||
builder: (column, joinBuilders) =>
|
||||
ColumnOrderings(column, joinBuilders: joinBuilders));
|
||||
|
||||
ColumnOrderings<String> get name => $state.composableBuilder(
|
||||
column: $state.table.name,
|
||||
builder: (column, joinBuilders) =>
|
||||
ColumnOrderings(column, joinBuilders: joinBuilders));
|
||||
|
||||
ColumnOrderings<String> get elementType => $state.composableBuilder(
|
||||
column: $state.table.elementType,
|
||||
builder: (column, joinBuilders) =>
|
||||
ColumnOrderings(column, joinBuilders: joinBuilders));
|
||||
|
||||
ColumnOrderings<String> 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);
|
||||
}
|
||||
|
18
lib/models/database/tables/blacklist.dart
Normal file
18
lib/models/database/tables/blacklist.dart
Normal file
@ -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<BlacklistedType>()();
|
||||
TextColumn get elementId => text()();
|
||||
}
|
@ -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,
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -24,19 +24,21 @@ class BlackListPage extends HookConsumerWidget {
|
||||
final filteredBlacklist = useMemoized(
|
||||
() {
|
||||
if (searchText.value.isEmpty) {
|
||||
return blacklist;
|
||||
return blacklist.asData?.value ?? [];
|
||||
}
|
||||
return blacklist
|
||||
return blacklist.asData?.value
|
||||
.map(
|
||||
(e) => (
|
||||
weightedRatio("${e.name} ${e.type.name}", searchText.value),
|
||||
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();
|
||||
.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);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -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<String, dynamic> json)
|
||||
: id = json['id'],
|
||||
name = json['name'],
|
||||
type = BlacklistedType.fromName(json['type']);
|
||||
|
||||
Map<String, dynamic> 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<List<BlacklistTableData>> {
|
||||
@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<Set<BlacklistedElement>> {
|
||||
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<void> add(BlacklistTableCompanion element) async {
|
||||
_database.into(_database.blacklistTable).insert(element);
|
||||
}
|
||||
|
||||
Future<void> 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<TrackSimple> filter(Iterable<TrackSimple> 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!),
|
||||
),
|
||||
tracks: playlist.tracks.where((track) => !contains(track)).toList(),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Set<BlacklistedElement> fromJson(Map<String, dynamic> json) {
|
||||
return json['blacklist']
|
||||
.map<BlacklistedElement>((e) => BlacklistedElement.fromJson(e))
|
||||
.toSet();
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {'blacklist': state.map((e) => e.toJson()).toList()};
|
||||
}
|
||||
}
|
||||
|
||||
final blacklistProvider =
|
||||
StateNotifierProvider<BlackListNotifier, Set<BlacklistedElement>>((ref) {
|
||||
return BlackListNotifier();
|
||||
});
|
||||
AsyncNotifierProvider<BlackListNotifier, List<BlacklistTableData>>(
|
||||
() => BlackListNotifier(),
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user