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/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/extensions/image.dart';
|
import 'package:spotube/extensions/image.dart';
|
||||||
|
import 'package:spotube/models/database/database.dart';
|
||||||
import 'package:spotube/models/local_track.dart';
|
import 'package:spotube/models/local_track.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/blacklist_provider.dart';
|
import 'package:spotube/provider/blacklist_provider.dart';
|
||||||
@ -170,11 +171,8 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
final favorites = useTrackToggleLike(track, ref);
|
final favorites = useTrackToggleLike(track, ref);
|
||||||
|
|
||||||
final isBlackListed = useMemoized(
|
final isBlackListed = useMemoized(
|
||||||
() => blacklist.contains(
|
() => blacklist.asData?.value.any(
|
||||||
BlacklistedElement.track(
|
(element) => element.elementId == track.id,
|
||||||
track.id!,
|
|
||||||
track.name!,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
[blacklist, track],
|
[blacklist, track],
|
||||||
);
|
);
|
||||||
@ -258,13 +256,16 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
.removeTracks(playlistId ?? "", [track.id!]);
|
.removeTracks(playlistId ?? "", [track.id!]);
|
||||||
break;
|
break;
|
||||||
case TrackOptionValue.blacklist:
|
case TrackOptionValue.blacklist:
|
||||||
if (isBlackListed) {
|
if (isBlackListed == null) break;
|
||||||
ref.read(blacklistProvider.notifier).remove(
|
if (isBlackListed == true) {
|
||||||
BlacklistedElement.track(track.id!, track.name!),
|
await ref.read(blacklistProvider.notifier).remove(track.id!);
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
ref.read(blacklistProvider.notifier).add(
|
await ref.read(blacklistProvider.notifier).add(
|
||||||
BlacklistedElement.track(track.id!, track.name!),
|
BlacklistTableCompanion.insert(
|
||||||
|
name: track.name!,
|
||||||
|
elementId: track.id!,
|
||||||
|
elementType: BlacklistedType.track,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -399,10 +400,10 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
PopSheetEntry(
|
PopSheetEntry(
|
||||||
value: TrackOptionValue.blacklist,
|
value: TrackOptionValue.blacklist,
|
||||||
leading: const Icon(SpotubeIcons.playlistRemove),
|
leading: const Icon(SpotubeIcons.playlistRemove),
|
||||||
iconColor: !isBlackListed ? Colors.red[400] : null,
|
iconColor: isBlackListed != true ? Colors.red[400] : null,
|
||||||
textColor: !isBlackListed ? Colors.red[400] : null,
|
textColor: isBlackListed != true ? Colors.red[400] : null,
|
||||||
title: Text(
|
title: Text(
|
||||||
isBlackListed
|
isBlackListed == true
|
||||||
? context.l10n.remove_from_blacklist
|
? context.l10n.remove_from_blacklist
|
||||||
: context.l10n.add_to_blacklist,
|
: context.l10n.add_to_blacklist,
|
||||||
),
|
),
|
||||||
|
@ -53,14 +53,10 @@ class TrackTile extends HookConsumerWidget {
|
|||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
final blacklist = ref.watch(blacklistProvider);
|
final blacklist = ref.watch(blacklistProvider);
|
||||||
|
final blacklistNotifier = ref.watch(blacklistProvider.notifier);
|
||||||
|
|
||||||
final isBlackListed = useMemoized(
|
final isBlackListed = useMemoized(
|
||||||
() => blacklist.contains(
|
() => blacklistNotifier.contains(track),
|
||||||
BlacklistedElement.track(
|
|
||||||
track.id!,
|
|
||||||
track.name!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
[blacklist, track],
|
[blacklist, track],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -19,12 +19,20 @@ part 'database.g.dart';
|
|||||||
part 'tables/preferences.dart';
|
part 'tables/preferences.dart';
|
||||||
part 'tables/source_match.dart';
|
part 'tables/source_match.dart';
|
||||||
part 'tables/skip_segment.dart';
|
part 'tables/skip_segment.dart';
|
||||||
|
part 'tables/blacklist.dart';
|
||||||
|
|
||||||
part 'typeconverters/color.dart';
|
part 'typeconverters/color.dart';
|
||||||
part 'typeconverters/locale.dart';
|
part 'typeconverters/locale.dart';
|
||||||
part 'typeconverters/string_list.dart';
|
part 'typeconverters/string_list.dart';
|
||||||
|
|
||||||
@DriftDatabase(tables: [PreferencesTable, SourceMatchTable, SkipSegmentTable])
|
@DriftDatabase(
|
||||||
|
tables: [
|
||||||
|
PreferencesTable,
|
||||||
|
SourceMatchTable,
|
||||||
|
SkipSegmentTable,
|
||||||
|
BlacklistTable,
|
||||||
|
],
|
||||||
|
)
|
||||||
class AppDatabase extends _$AppDatabase {
|
class AppDatabase extends _$AppDatabase {
|
||||||
AppDatabase() : super(_openConnection());
|
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 {
|
abstract class _$AppDatabase extends GeneratedDatabase {
|
||||||
_$AppDatabase(QueryExecutor e) : super(e);
|
_$AppDatabase(QueryExecutor e) : super(e);
|
||||||
_$AppDatabaseManager get managers => _$AppDatabaseManager(this);
|
_$AppDatabaseManager get managers => _$AppDatabaseManager(this);
|
||||||
@ -1798,14 +2058,23 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||||||
$SourceMatchTableTable(this);
|
$SourceMatchTableTable(this);
|
||||||
late final $SkipSegmentTableTable skipSegmentTable =
|
late final $SkipSegmentTableTable skipSegmentTable =
|
||||||
$SkipSegmentTableTable(this);
|
$SkipSegmentTableTable(this);
|
||||||
|
late final $BlacklistTableTable blacklistTable = $BlacklistTableTable(this);
|
||||||
late final Index uniqTrackMatch = Index('uniq_track_match',
|
late final Index uniqTrackMatch = Index('uniq_track_match',
|
||||||
'CREATE UNIQUE INDEX uniq_track_match ON source_match_table (track_id, source_id, source_type)');
|
'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
|
@override
|
||||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||||
@override
|
@override
|
||||||
List<DatabaseSchemaEntity> get allSchemaEntities =>
|
List<DatabaseSchemaEntity> get allSchemaEntities => [
|
||||||
[preferencesTable, sourceMatchTable, skipSegmentTable, uniqTrackMatch];
|
preferencesTable,
|
||||||
|
sourceMatchTable,
|
||||||
|
skipSegmentTable,
|
||||||
|
blacklistTable,
|
||||||
|
uniqTrackMatch,
|
||||||
|
uniqueBlacklist
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef $$PreferencesTableTableInsertCompanionBuilder
|
typedef $$PreferencesTableTableInsertCompanionBuilder
|
||||||
@ -2571,6 +2840,130 @@ class $$SkipSegmentTableTableOrderingComposer
|
|||||||
ColumnOrderings(column, joinBuilders: joinBuilders));
|
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 {
|
class _$AppDatabaseManager {
|
||||||
final _$AppDatabase _db;
|
final _$AppDatabase _db;
|
||||||
_$AppDatabaseManager(this._db);
|
_$AppDatabaseManager(this._db);
|
||||||
@ -2580,4 +2973,6 @@ class _$AppDatabaseManager {
|
|||||||
$$SourceMatchTableTableTableManager(_db, _db.sourceMatchTable);
|
$$SourceMatchTableTableTableManager(_db, _db.sourceMatchTable);
|
||||||
$$SkipSegmentTableTableTableManager get skipSegmentTable =>
|
$$SkipSegmentTableTableTableManager get skipSegmentTable =>
|
||||||
$$SkipSegmentTableTableTableManager(_db, _db.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(
|
final isBlackListed = ref.watch(
|
||||||
blacklistProvider.select(
|
blacklistProvider.select(
|
||||||
(blacklist) => blacklist.contains(
|
(blacklist) => blacklist.asData?.value.any(
|
||||||
BlacklistedElement.artist(artist.id!, artist.name!),
|
(element) => element.elementId == artist.id,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -55,7 +55,7 @@ class ArtistCard extends HookConsumerWidget {
|
|||||||
elevation: 3,
|
elevation: 3,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: radius,
|
borderRadius: radius,
|
||||||
side: isBlackListed
|
side: isBlackListed == true
|
||||||
? const BorderSide(
|
? const BorderSide(
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
width: 2,
|
width: 2,
|
||||||
|
@ -10,6 +10,7 @@ import 'package:spotube/extensions/constrains.dart';
|
|||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/extensions/image.dart';
|
import 'package:spotube/extensions/image.dart';
|
||||||
import 'package:spotube/hooks/utils/use_breakpoint_value.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/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/blacklist_provider.dart';
|
import 'package:spotube/provider/blacklist_provider.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
@ -39,10 +40,9 @@ class ArtistPageHeader extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final auth = ref.watch(authenticationProvider);
|
final auth = ref.watch(authenticationProvider);
|
||||||
final blacklist = ref.watch(blacklistProvider);
|
ref.watch(blacklistProvider);
|
||||||
final isBlackListed = blacklist.contains(
|
final blacklistNotifier = ref.watch(blacklistProvider.notifier);
|
||||||
BlacklistedElement.artist(artistId, artist.name!),
|
final isBlackListed = blacklistNotifier.containsArtist(artist);
|
||||||
);
|
|
||||||
|
|
||||||
final image = artist.images.asUrlString(
|
final image = artist.images.asUrlString(
|
||||||
placeholder: ImagePlaceholder.artist,
|
placeholder: ImagePlaceholder.artist,
|
||||||
@ -187,14 +187,16 @@ class ArtistPageHeader extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (isBlackListed) {
|
if (isBlackListed) {
|
||||||
ref.read(blacklistProvider.notifier).remove(
|
await ref
|
||||||
BlacklistedElement.artist(
|
.read(blacklistProvider.notifier)
|
||||||
artist.id!, artist.name!),
|
.remove(artist.id!);
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
ref.read(blacklistProvider.notifier).add(
|
await ref.read(blacklistProvider.notifier).add(
|
||||||
BlacklistedElement.artist(
|
BlacklistTableCompanion.insert(
|
||||||
artist.id!, artist.name!),
|
name: artist.name!,
|
||||||
|
elementId: artist.id!,
|
||||||
|
elementType: BlacklistedType.artist,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -24,19 +24,21 @@ class BlackListPage extends HookConsumerWidget {
|
|||||||
final filteredBlacklist = useMemoized(
|
final filteredBlacklist = useMemoized(
|
||||||
() {
|
() {
|
||||||
if (searchText.value.isEmpty) {
|
if (searchText.value.isEmpty) {
|
||||||
return blacklist;
|
return blacklist.asData?.value ?? [];
|
||||||
}
|
}
|
||||||
return blacklist
|
return blacklist.asData?.value
|
||||||
.map(
|
.map(
|
||||||
(e) => (
|
(e) => (
|
||||||
weightedRatio("${e.name} ${e.type.name}", searchText.value),
|
weightedRatio(
|
||||||
e,
|
"${e.name} ${e.elementType.name}", searchText.value),
|
||||||
),
|
e,
|
||||||
)
|
),
|
||||||
.sorted((a, b) => b.$1.compareTo(a.$1))
|
)
|
||||||
.where((e) => e.$1 > 50)
|
.sorted((a, b) => b.$1.compareTo(a.$1))
|
||||||
.map((e) => e.$2)
|
.where((e) => e.$1 > 50)
|
||||||
.toList();
|
.map((e) => e.$2)
|
||||||
|
.toList() ??
|
||||||
|
[];
|
||||||
},
|
},
|
||||||
[blacklist, searchText.value],
|
[blacklist, searchText.value],
|
||||||
);
|
);
|
||||||
@ -70,14 +72,14 @@ class BlackListPage extends HookConsumerWidget {
|
|||||||
final item = filteredBlacklist.elementAt(index);
|
final item = filteredBlacklist.elementAt(index);
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: Text("${index + 1}."),
|
leading: Text("${index + 1}."),
|
||||||
title: Text("${item.name} (${item.type.name})"),
|
title: Text("${item.name} (${item.elementType.name})"),
|
||||||
subtitle: Text(item.id),
|
subtitle: Text(item.elementId),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: Icon(SpotubeIcons.trash, color: Colors.red[400]),
|
icon: Icon(SpotubeIcons.trash, color: Colors.red[400]),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ref
|
ref
|
||||||
.read(blacklistProvider.notifier)
|
.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:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/models/current_playlist.dart';
|
import 'package:spotube/models/current_playlist.dart';
|
||||||
import 'package:spotube/utils/persisted_state_notifier.dart';
|
import 'package:spotube/models/database/database.dart';
|
||||||
|
import 'package:spotube/provider/database/database.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};
|
|
||||||
|
|
||||||
|
class BlackListNotifier extends AsyncNotifier<List<BlacklistTableData>> {
|
||||||
@override
|
@override
|
||||||
operator ==(other) =>
|
build() async {
|
||||||
other is BlacklistedElement &&
|
final database = ref.watch(databaseProvider);
|
||||||
other.id == id &&
|
|
||||||
other.type == type &&
|
|
||||||
other.name == name;
|
|
||||||
|
|
||||||
@override
|
final subscription = database
|
||||||
int get hashCode => id.hashCode ^ type.hashCode ^ name.hashCode;
|
.select(database.blacklistTable)
|
||||||
}
|
.watch()
|
||||||
|
.listen((event) => state = AsyncData(event));
|
||||||
|
|
||||||
class BlackListNotifier
|
ref.onDispose(() {
|
||||||
extends PersistedStateNotifier<Set<BlacklistedElement>> {
|
subscription.cancel();
|
||||||
BlackListNotifier() : super({}, "blacklist");
|
});
|
||||||
|
|
||||||
void add(BlacklistedElement element) {
|
return await database.select(database.blacklistTable).get();
|
||||||
state = state.union({element});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(BlacklistedElement element) {
|
AppDatabase get _database => ref.read(databaseProvider);
|
||||||
state = state.difference({element});
|
|
||||||
|
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) {
|
bool contains(TrackSimple track) {
|
||||||
final containsTrack =
|
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(
|
final containsTrackArtists = track.artists?.any(
|
||||||
(artist) => state.contains(
|
(artist) =>
|
||||||
BlacklistedElement.artist(artist.id!, artist.name ?? "Spotify"),
|
state.asData?.value.any((el) => el.elementId == artist.id) ??
|
||||||
),
|
false,
|
||||||
) ??
|
) ??
|
||||||
false;
|
false;
|
||||||
|
|
||||||
return containsTrack || containsTrackArtists;
|
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]
|
/// Filters the non blacklisted tracks from the given [tracks]
|
||||||
Iterable<TrackSimple> filter(Iterable<TrackSimple> tracks) {
|
Iterable<TrackSimple> filter(Iterable<TrackSimple> tracks) {
|
||||||
return tracks.whereNot(contains).toList();
|
return tracks.whereNot(contains).toList();
|
||||||
@ -75,34 +65,12 @@ class BlackListNotifier
|
|||||||
id: playlist.id,
|
id: playlist.id,
|
||||||
name: playlist.name,
|
name: playlist.name,
|
||||||
thumbnail: playlist.thumbnail,
|
thumbnail: playlist.thumbnail,
|
||||||
tracks: playlist.tracks.where(
|
tracks: playlist.tracks.where((track) => !contains(track)).toList(),
|
||||||
(track) {
|
|
||||||
return !state
|
|
||||||
.contains(BlacklistedElement.track(track.id!, track.name!)) &&
|
|
||||||
!(track.artists ?? []).any(
|
|
||||||
(artist) => state.contains(
|
|
||||||
BlacklistedElement.artist(artist.id!, artist.name!),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
).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 =
|
final blacklistProvider =
|
||||||
StateNotifierProvider<BlackListNotifier, Set<BlacklistedElement>>((ref) {
|
AsyncNotifierProvider<BlackListNotifier, List<BlacklistTableData>>(
|
||||||
return BlackListNotifier();
|
() => BlackListNotifier(),
|
||||||
});
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user