mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
refactor: lastfm scrobbling to drift db
This commit is contained in:
parent
d18f74fd65
commit
b9b7d5c8aa
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/provider/scrobbler_provider.dart';
|
import 'package:spotube/provider/scrobbler/scrobbler.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
|
|
||||||
typedef UseTrackToggleLike = ({
|
typedef UseTrackToggleLike = ({
|
||||||
|
@ -22,6 +22,7 @@ part 'database.g.dart';
|
|||||||
part 'tables/authentication.dart';
|
part 'tables/authentication.dart';
|
||||||
part 'tables/blacklist.dart';
|
part 'tables/blacklist.dart';
|
||||||
part 'tables/preferences.dart';
|
part 'tables/preferences.dart';
|
||||||
|
part 'tables/scrobbler.dart';
|
||||||
part 'tables/skip_segment.dart';
|
part 'tables/skip_segment.dart';
|
||||||
part 'tables/source_match.dart';
|
part 'tables/source_match.dart';
|
||||||
|
|
||||||
@ -35,6 +36,7 @@ part 'typeconverters/encrypted_text.dart';
|
|||||||
AuthenticationTable,
|
AuthenticationTable,
|
||||||
BlacklistTable,
|
BlacklistTable,
|
||||||
PreferencesTable,
|
PreferencesTable,
|
||||||
|
ScrobblerTable,
|
||||||
SkipSegmentTable,
|
SkipSegmentTable,
|
||||||
SourceMatchTable,
|
SourceMatchTable,
|
||||||
],
|
],
|
||||||
|
@ -1731,6 +1731,260 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class $ScrobblerTableTable extends ScrobblerTable
|
||||||
|
with TableInfo<$ScrobblerTableTable, ScrobblerTableData> {
|
||||||
|
@override
|
||||||
|
final GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
$ScrobblerTableTable(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 _createdAtMeta =
|
||||||
|
const VerificationMeta('createdAt');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>(
|
||||||
|
'created_at', aliasedName, false,
|
||||||
|
type: DriftSqlType.dateTime,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: currentDateAndTime);
|
||||||
|
static const VerificationMeta _usernameMeta =
|
||||||
|
const VerificationMeta('username');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<String> username = GeneratedColumn<String>(
|
||||||
|
'username', aliasedName, false,
|
||||||
|
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||||
|
static const VerificationMeta _passwordHashMeta =
|
||||||
|
const VerificationMeta('passwordHash');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<String> passwordHash = GeneratedColumn<String>(
|
||||||
|
'password_hash', aliasedName, false,
|
||||||
|
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||||
|
@override
|
||||||
|
List<GeneratedColumn> get $columns => [id, createdAt, username, passwordHash];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'scrobbler_table';
|
||||||
|
@override
|
||||||
|
VerificationContext validateIntegrity(Insertable<ScrobblerTableData> 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('created_at')) {
|
||||||
|
context.handle(_createdAtMeta,
|
||||||
|
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
|
||||||
|
}
|
||||||
|
if (data.containsKey('username')) {
|
||||||
|
context.handle(_usernameMeta,
|
||||||
|
username.isAcceptableOrUnknown(data['username']!, _usernameMeta));
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_usernameMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('password_hash')) {
|
||||||
|
context.handle(
|
||||||
|
_passwordHashMeta,
|
||||||
|
passwordHash.isAcceptableOrUnknown(
|
||||||
|
data['password_hash']!, _passwordHashMeta));
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_passwordHashMeta);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<GeneratedColumn> get $primaryKey => {id};
|
||||||
|
@override
|
||||||
|
ScrobblerTableData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return ScrobblerTableData(
|
||||||
|
id: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
|
||||||
|
createdAt: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
|
||||||
|
username: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}username'])!,
|
||||||
|
passwordHash: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}password_hash'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
$ScrobblerTableTable createAlias(String alias) {
|
||||||
|
return $ScrobblerTableTable(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScrobblerTableData extends DataClass
|
||||||
|
implements Insertable<ScrobblerTableData> {
|
||||||
|
final int id;
|
||||||
|
final DateTime createdAt;
|
||||||
|
final String username;
|
||||||
|
final String passwordHash;
|
||||||
|
const ScrobblerTableData(
|
||||||
|
{required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.username,
|
||||||
|
required this.passwordHash});
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
map['id'] = Variable<int>(id);
|
||||||
|
map['created_at'] = Variable<DateTime>(createdAt);
|
||||||
|
map['username'] = Variable<String>(username);
|
||||||
|
map['password_hash'] = Variable<String>(passwordHash);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrobblerTableCompanion toCompanion(bool nullToAbsent) {
|
||||||
|
return ScrobblerTableCompanion(
|
||||||
|
id: Value(id),
|
||||||
|
createdAt: Value(createdAt),
|
||||||
|
username: Value(username),
|
||||||
|
passwordHash: Value(passwordHash),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory ScrobblerTableData.fromJson(Map<String, dynamic> json,
|
||||||
|
{ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return ScrobblerTableData(
|
||||||
|
id: serializer.fromJson<int>(json['id']),
|
||||||
|
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||||
|
username: serializer.fromJson<String>(json['username']),
|
||||||
|
passwordHash: serializer.fromJson<String>(json['passwordHash']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'id': serializer.toJson<int>(id),
|
||||||
|
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||||
|
'username': serializer.toJson<String>(username),
|
||||||
|
'passwordHash': serializer.toJson<String>(passwordHash),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrobblerTableData copyWith(
|
||||||
|
{int? id,
|
||||||
|
DateTime? createdAt,
|
||||||
|
String? username,
|
||||||
|
String? passwordHash}) =>
|
||||||
|
ScrobblerTableData(
|
||||||
|
id: id ?? this.id,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
username: username ?? this.username,
|
||||||
|
passwordHash: passwordHash ?? this.passwordHash,
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('ScrobblerTableData(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('createdAt: $createdAt, ')
|
||||||
|
..write('username: $username, ')
|
||||||
|
..write('passwordHash: $passwordHash')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(id, createdAt, username, passwordHash);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is ScrobblerTableData &&
|
||||||
|
other.id == this.id &&
|
||||||
|
other.createdAt == this.createdAt &&
|
||||||
|
other.username == this.username &&
|
||||||
|
other.passwordHash == this.passwordHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScrobblerTableCompanion extends UpdateCompanion<ScrobblerTableData> {
|
||||||
|
final Value<int> id;
|
||||||
|
final Value<DateTime> createdAt;
|
||||||
|
final Value<String> username;
|
||||||
|
final Value<String> passwordHash;
|
||||||
|
const ScrobblerTableCompanion({
|
||||||
|
this.id = const Value.absent(),
|
||||||
|
this.createdAt = const Value.absent(),
|
||||||
|
this.username = const Value.absent(),
|
||||||
|
this.passwordHash = const Value.absent(),
|
||||||
|
});
|
||||||
|
ScrobblerTableCompanion.insert({
|
||||||
|
this.id = const Value.absent(),
|
||||||
|
this.createdAt = const Value.absent(),
|
||||||
|
required String username,
|
||||||
|
required String passwordHash,
|
||||||
|
}) : username = Value(username),
|
||||||
|
passwordHash = Value(passwordHash);
|
||||||
|
static Insertable<ScrobblerTableData> custom({
|
||||||
|
Expression<int>? id,
|
||||||
|
Expression<DateTime>? createdAt,
|
||||||
|
Expression<String>? username,
|
||||||
|
Expression<String>? passwordHash,
|
||||||
|
}) {
|
||||||
|
return RawValuesInsertable({
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (createdAt != null) 'created_at': createdAt,
|
||||||
|
if (username != null) 'username': username,
|
||||||
|
if (passwordHash != null) 'password_hash': passwordHash,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrobblerTableCompanion copyWith(
|
||||||
|
{Value<int>? id,
|
||||||
|
Value<DateTime>? createdAt,
|
||||||
|
Value<String>? username,
|
||||||
|
Value<String>? passwordHash}) {
|
||||||
|
return ScrobblerTableCompanion(
|
||||||
|
id: id ?? this.id,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
username: username ?? this.username,
|
||||||
|
passwordHash: passwordHash ?? this.passwordHash,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
if (id.present) {
|
||||||
|
map['id'] = Variable<int>(id.value);
|
||||||
|
}
|
||||||
|
if (createdAt.present) {
|
||||||
|
map['created_at'] = Variable<DateTime>(createdAt.value);
|
||||||
|
}
|
||||||
|
if (username.present) {
|
||||||
|
map['username'] = Variable<String>(username.value);
|
||||||
|
}
|
||||||
|
if (passwordHash.present) {
|
||||||
|
map['password_hash'] = Variable<String>(passwordHash.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('ScrobblerTableCompanion(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('createdAt: $createdAt, ')
|
||||||
|
..write('username: $username, ')
|
||||||
|
..write('passwordHash: $passwordHash')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class $SkipSegmentTableTable extends SkipSegmentTable
|
class $SkipSegmentTableTable extends SkipSegmentTable
|
||||||
with TableInfo<$SkipSegmentTableTable, SkipSegmentTableData> {
|
with TableInfo<$SkipSegmentTableTable, SkipSegmentTableData> {
|
||||||
@override
|
@override
|
||||||
@ -2324,6 +2578,7 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||||||
late final $BlacklistTableTable blacklistTable = $BlacklistTableTable(this);
|
late final $BlacklistTableTable blacklistTable = $BlacklistTableTable(this);
|
||||||
late final $PreferencesTableTable preferencesTable =
|
late final $PreferencesTableTable preferencesTable =
|
||||||
$PreferencesTableTable(this);
|
$PreferencesTableTable(this);
|
||||||
|
late final $ScrobblerTableTable scrobblerTable = $ScrobblerTableTable(this);
|
||||||
late final $SkipSegmentTableTable skipSegmentTable =
|
late final $SkipSegmentTableTable skipSegmentTable =
|
||||||
$SkipSegmentTableTable(this);
|
$SkipSegmentTableTable(this);
|
||||||
late final $SourceMatchTableTable sourceMatchTable =
|
late final $SourceMatchTableTable sourceMatchTable =
|
||||||
@ -2340,6 +2595,7 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||||||
authenticationTable,
|
authenticationTable,
|
||||||
blacklistTable,
|
blacklistTable,
|
||||||
preferencesTable,
|
preferencesTable,
|
||||||
|
scrobblerTable,
|
||||||
skipSegmentTable,
|
skipSegmentTable,
|
||||||
sourceMatchTable,
|
sourceMatchTable,
|
||||||
uniqueBlacklist,
|
uniqueBlacklist,
|
||||||
@ -3081,6 +3337,128 @@ class $$PreferencesTableTableOrderingComposer
|
|||||||
ColumnOrderings(column, joinBuilders: joinBuilders));
|
ColumnOrderings(column, joinBuilders: joinBuilders));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef $$ScrobblerTableTableInsertCompanionBuilder = ScrobblerTableCompanion
|
||||||
|
Function({
|
||||||
|
Value<int> id,
|
||||||
|
Value<DateTime> createdAt,
|
||||||
|
required String username,
|
||||||
|
required String passwordHash,
|
||||||
|
});
|
||||||
|
typedef $$ScrobblerTableTableUpdateCompanionBuilder = ScrobblerTableCompanion
|
||||||
|
Function({
|
||||||
|
Value<int> id,
|
||||||
|
Value<DateTime> createdAt,
|
||||||
|
Value<String> username,
|
||||||
|
Value<String> passwordHash,
|
||||||
|
});
|
||||||
|
|
||||||
|
class $$ScrobblerTableTableTableManager extends RootTableManager<
|
||||||
|
_$AppDatabase,
|
||||||
|
$ScrobblerTableTable,
|
||||||
|
ScrobblerTableData,
|
||||||
|
$$ScrobblerTableTableFilterComposer,
|
||||||
|
$$ScrobblerTableTableOrderingComposer,
|
||||||
|
$$ScrobblerTableTableProcessedTableManager,
|
||||||
|
$$ScrobblerTableTableInsertCompanionBuilder,
|
||||||
|
$$ScrobblerTableTableUpdateCompanionBuilder> {
|
||||||
|
$$ScrobblerTableTableTableManager(
|
||||||
|
_$AppDatabase db, $ScrobblerTableTable table)
|
||||||
|
: super(TableManagerState(
|
||||||
|
db: db,
|
||||||
|
table: table,
|
||||||
|
filteringComposer:
|
||||||
|
$$ScrobblerTableTableFilterComposer(ComposerState(db, table)),
|
||||||
|
orderingComposer:
|
||||||
|
$$ScrobblerTableTableOrderingComposer(ComposerState(db, table)),
|
||||||
|
getChildManagerBuilder: (p) =>
|
||||||
|
$$ScrobblerTableTableProcessedTableManager(p),
|
||||||
|
getUpdateCompanionBuilder: ({
|
||||||
|
Value<int> id = const Value.absent(),
|
||||||
|
Value<DateTime> createdAt = const Value.absent(),
|
||||||
|
Value<String> username = const Value.absent(),
|
||||||
|
Value<String> passwordHash = const Value.absent(),
|
||||||
|
}) =>
|
||||||
|
ScrobblerTableCompanion(
|
||||||
|
id: id,
|
||||||
|
createdAt: createdAt,
|
||||||
|
username: username,
|
||||||
|
passwordHash: passwordHash,
|
||||||
|
),
|
||||||
|
getInsertCompanionBuilder: ({
|
||||||
|
Value<int> id = const Value.absent(),
|
||||||
|
Value<DateTime> createdAt = const Value.absent(),
|
||||||
|
required String username,
|
||||||
|
required String passwordHash,
|
||||||
|
}) =>
|
||||||
|
ScrobblerTableCompanion.insert(
|
||||||
|
id: id,
|
||||||
|
createdAt: createdAt,
|
||||||
|
username: username,
|
||||||
|
passwordHash: passwordHash,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$ScrobblerTableTableProcessedTableManager extends ProcessedTableManager<
|
||||||
|
_$AppDatabase,
|
||||||
|
$ScrobblerTableTable,
|
||||||
|
ScrobblerTableData,
|
||||||
|
$$ScrobblerTableTableFilterComposer,
|
||||||
|
$$ScrobblerTableTableOrderingComposer,
|
||||||
|
$$ScrobblerTableTableProcessedTableManager,
|
||||||
|
$$ScrobblerTableTableInsertCompanionBuilder,
|
||||||
|
$$ScrobblerTableTableUpdateCompanionBuilder> {
|
||||||
|
$$ScrobblerTableTableProcessedTableManager(super.$state);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$ScrobblerTableTableFilterComposer
|
||||||
|
extends FilterComposer<_$AppDatabase, $ScrobblerTableTable> {
|
||||||
|
$$ScrobblerTableTableFilterComposer(super.$state);
|
||||||
|
ColumnFilters<int> get id => $state.composableBuilder(
|
||||||
|
column: $state.table.id,
|
||||||
|
builder: (column, joinBuilders) =>
|
||||||
|
ColumnFilters(column, joinBuilders: joinBuilders));
|
||||||
|
|
||||||
|
ColumnFilters<DateTime> get createdAt => $state.composableBuilder(
|
||||||
|
column: $state.table.createdAt,
|
||||||
|
builder: (column, joinBuilders) =>
|
||||||
|
ColumnFilters(column, joinBuilders: joinBuilders));
|
||||||
|
|
||||||
|
ColumnFilters<String> get username => $state.composableBuilder(
|
||||||
|
column: $state.table.username,
|
||||||
|
builder: (column, joinBuilders) =>
|
||||||
|
ColumnFilters(column, joinBuilders: joinBuilders));
|
||||||
|
|
||||||
|
ColumnFilters<String> get passwordHash => $state.composableBuilder(
|
||||||
|
column: $state.table.passwordHash,
|
||||||
|
builder: (column, joinBuilders) =>
|
||||||
|
ColumnFilters(column, joinBuilders: joinBuilders));
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$ScrobblerTableTableOrderingComposer
|
||||||
|
extends OrderingComposer<_$AppDatabase, $ScrobblerTableTable> {
|
||||||
|
$$ScrobblerTableTableOrderingComposer(super.$state);
|
||||||
|
ColumnOrderings<int> get id => $state.composableBuilder(
|
||||||
|
column: $state.table.id,
|
||||||
|
builder: (column, joinBuilders) =>
|
||||||
|
ColumnOrderings(column, joinBuilders: joinBuilders));
|
||||||
|
|
||||||
|
ColumnOrderings<DateTime> get createdAt => $state.composableBuilder(
|
||||||
|
column: $state.table.createdAt,
|
||||||
|
builder: (column, joinBuilders) =>
|
||||||
|
ColumnOrderings(column, joinBuilders: joinBuilders));
|
||||||
|
|
||||||
|
ColumnOrderings<String> get username => $state.composableBuilder(
|
||||||
|
column: $state.table.username,
|
||||||
|
builder: (column, joinBuilders) =>
|
||||||
|
ColumnOrderings(column, joinBuilders: joinBuilders));
|
||||||
|
|
||||||
|
ColumnOrderings<String> get passwordHash => $state.composableBuilder(
|
||||||
|
column: $state.table.passwordHash,
|
||||||
|
builder: (column, joinBuilders) =>
|
||||||
|
ColumnOrderings(column, joinBuilders: joinBuilders));
|
||||||
|
}
|
||||||
|
|
||||||
typedef $$SkipSegmentTableTableInsertCompanionBuilder
|
typedef $$SkipSegmentTableTableInsertCompanionBuilder
|
||||||
= SkipSegmentTableCompanion Function({
|
= SkipSegmentTableCompanion Function({
|
||||||
Value<int> id,
|
Value<int> id,
|
||||||
@ -3370,6 +3748,8 @@ class _$AppDatabaseManager {
|
|||||||
$$BlacklistTableTableTableManager(_db, _db.blacklistTable);
|
$$BlacklistTableTableTableManager(_db, _db.blacklistTable);
|
||||||
$$PreferencesTableTableTableManager get preferencesTable =>
|
$$PreferencesTableTableTableManager get preferencesTable =>
|
||||||
$$PreferencesTableTableTableManager(_db, _db.preferencesTable);
|
$$PreferencesTableTableTableManager(_db, _db.preferencesTable);
|
||||||
|
$$ScrobblerTableTableTableManager get scrobblerTable =>
|
||||||
|
$$ScrobblerTableTableTableManager(_db, _db.scrobblerTable);
|
||||||
$$SkipSegmentTableTableTableManager get skipSegmentTable =>
|
$$SkipSegmentTableTableTableManager get skipSegmentTable =>
|
||||||
$$SkipSegmentTableTableTableManager(_db, _db.skipSegmentTable);
|
$$SkipSegmentTableTableTableManager(_db, _db.skipSegmentTable);
|
||||||
$$SourceMatchTableTableTableManager get sourceMatchTable =>
|
$$SourceMatchTableTableTableManager get sourceMatchTable =>
|
||||||
|
8
lib/models/database/tables/scrobbler.dart
Normal file
8
lib/models/database/tables/scrobbler.dart
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
part of '../database.dart';
|
||||||
|
|
||||||
|
class ScrobblerTable extends Table {
|
||||||
|
IntColumn get id => integer().autoIncrement()();
|
||||||
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
TextColumn get username => text()();
|
||||||
|
TextColumn get passwordHash => text().map(EncryptedTextConverter())();
|
||||||
|
}
|
@ -7,7 +7,7 @@ import 'package:spotube/collections/spotube_icons.dart';
|
|||||||
import 'package:spotube/components/dialogs/prompt_dialog.dart';
|
import 'package:spotube/components/dialogs/prompt_dialog.dart';
|
||||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/scrobbler_provider.dart';
|
import 'package:spotube/provider/scrobbler/scrobbler.dart';
|
||||||
|
|
||||||
class LastFMLoginPage extends HookConsumerWidget {
|
class LastFMLoginPage extends HookConsumerWidget {
|
||||||
static const name = "lastfm_login";
|
static const name = "lastfm_login";
|
||||||
|
@ -10,7 +10,7 @@ import 'package:spotube/extensions/context.dart';
|
|||||||
import 'package:spotube/extensions/image.dart';
|
import 'package:spotube/extensions/image.dart';
|
||||||
import 'package:spotube/pages/profile/profile.dart';
|
import 'package:spotube/pages/profile/profile.dart';
|
||||||
import 'package:spotube/provider/authentication/authentication.dart';
|
import 'package:spotube/provider/authentication/authentication.dart';
|
||||||
import 'package:spotube/provider/scrobbler_provider.dart';
|
import 'package:spotube/provider/scrobbler/scrobbler.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ class SettingsAccountSection extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
if (scrobbler == null)
|
if (scrobbler.asData?.value == null)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(SpotubeIcons.lastFm),
|
leading: const Icon(SpotubeIcons.lastFm),
|
||||||
title: Text(context.l10n.login_with_lastfm),
|
title: Text(context.l10n.login_with_lastfm),
|
||||||
|
@ -11,7 +11,7 @@ import 'package:spotube/provider/history/history.dart';
|
|||||||
import 'package:spotube/provider/palette_provider.dart';
|
import 'package:spotube/provider/palette_provider.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/player_listeners.dart';
|
import 'package:spotube/provider/proxy_playlist/player_listeners.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart';
|
||||||
import 'package:spotube/provider/scrobbler_provider.dart';
|
import 'package:spotube/provider/scrobbler/scrobbler.dart';
|
||||||
import 'package:spotube/provider/server/sourced_track.dart';
|
import 'package:spotube/provider/server/sourced_track.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
|
|
||||||
|
130
lib/provider/scrobbler/scrobbler.dart
Normal file
130
lib/provider/scrobbler/scrobbler.dart
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:scrobblenaut/scrobblenaut.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/collections/env.dart';
|
||||||
|
import 'package:spotube/extensions/artist_simple.dart';
|
||||||
|
import 'package:spotube/models/database/database.dart';
|
||||||
|
import 'package:spotube/provider/database/database.dart';
|
||||||
|
import 'package:spotube/services/logger/logger.dart';
|
||||||
|
|
||||||
|
class ScrobblerNotifier extends AsyncNotifier<Scrobblenaut?> {
|
||||||
|
final StreamController<Track> _scrobbleController =
|
||||||
|
StreamController<Track>.broadcast();
|
||||||
|
@override
|
||||||
|
build() async {
|
||||||
|
final database = ref.watch(databaseProvider);
|
||||||
|
|
||||||
|
final loginInfo = await (database.select(database.scrobblerTable)
|
||||||
|
..where((t) => t.id.equals(0)))
|
||||||
|
.getSingleOrNull();
|
||||||
|
|
||||||
|
final subscription =
|
||||||
|
database.select(database.scrobblerTable).watch().listen((event) async {
|
||||||
|
if (event.isNotEmpty) {
|
||||||
|
state = await AsyncValue.guard(
|
||||||
|
() async => Scrobblenaut(
|
||||||
|
lastFM: await LastFM.authenticateWithPasswordHash(
|
||||||
|
apiKey: Env.lastFmApiKey,
|
||||||
|
apiSecret: Env.lastFmApiSecret,
|
||||||
|
username: event.first.username,
|
||||||
|
passwordHash: event.first.passwordHash,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
state = const AsyncValue.data(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final scrobblerSubscription =
|
||||||
|
_scrobbleController.stream.listen((track) async {
|
||||||
|
try {
|
||||||
|
await state.asData?.value?.track.scrobble(
|
||||||
|
artist: track.artists!.first.name!,
|
||||||
|
track: track.name!,
|
||||||
|
album: track.album!.name!,
|
||||||
|
chosenByUser: true,
|
||||||
|
duration: track.duration,
|
||||||
|
timestamp: DateTime.now().toUtc(),
|
||||||
|
trackNumber: track.trackNumber,
|
||||||
|
);
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
AppLogger.reportError(e, stackTrace);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ref.onDispose(() {
|
||||||
|
subscription.cancel();
|
||||||
|
scrobblerSubscription.cancel();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loginInfo == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scrobblenaut(
|
||||||
|
lastFM: await LastFM.authenticateWithPasswordHash(
|
||||||
|
apiKey: Env.lastFmApiKey,
|
||||||
|
apiSecret: Env.lastFmApiSecret,
|
||||||
|
username: loginInfo.username,
|
||||||
|
passwordHash: loginInfo.passwordHash,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> login(
|
||||||
|
String username,
|
||||||
|
String password,
|
||||||
|
) async {
|
||||||
|
final database = ref.read(databaseProvider);
|
||||||
|
|
||||||
|
final lastFm = await LastFM.authenticate(
|
||||||
|
apiKey: Env.lastFmApiKey,
|
||||||
|
apiSecret: Env.lastFmApiSecret,
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!lastFm.isAuth) throw Exception("Invalid credentials");
|
||||||
|
|
||||||
|
await database.into(database.scrobblerTable).insert(
|
||||||
|
ScrobblerTableCompanion.insert(
|
||||||
|
id: const Value(0),
|
||||||
|
username: username,
|
||||||
|
passwordHash: lastFm.passwordHash!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> logout() async {
|
||||||
|
state = const AsyncValue.data(null);
|
||||||
|
final database = ref.read(databaseProvider);
|
||||||
|
await database.delete(database.scrobblerTable).go();
|
||||||
|
}
|
||||||
|
|
||||||
|
void scrobble(Track track) {
|
||||||
|
_scrobbleController.add(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> love(Track track) async {
|
||||||
|
await state.asData?.value?.track.love(
|
||||||
|
artist: track.artists!.asString(),
|
||||||
|
track: track.name!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> unlove(Track track) async {
|
||||||
|
await state.asData?.value?.track.unLove(
|
||||||
|
artist: track.artists!.asString(),
|
||||||
|
track: track.name!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final scrobblerProvider =
|
||||||
|
AsyncNotifierProvider<ScrobblerNotifier, Scrobblenaut?>(
|
||||||
|
() => ScrobblerNotifier(),
|
||||||
|
);
|
@ -1,129 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:spotube/services/logger/logger.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:scrobblenaut/scrobblenaut.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/collections/env.dart';
|
|
||||||
import 'package:spotube/extensions/artist_simple.dart';
|
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
|
||||||
import 'package:spotube/utils/persisted_state_notifier.dart';
|
|
||||||
|
|
||||||
class ScrobblerState {
|
|
||||||
final String username;
|
|
||||||
final String passwordHash;
|
|
||||||
|
|
||||||
final Scrobblenaut scrobblenaut;
|
|
||||||
|
|
||||||
ScrobblerState({
|
|
||||||
required this.username,
|
|
||||||
required this.passwordHash,
|
|
||||||
required this.scrobblenaut,
|
|
||||||
});
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return {
|
|
||||||
'username': username,
|
|
||||||
'passwordHash': passwordHash,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ScrobblerNotifier extends PersistedStateNotifier<ScrobblerState?> {
|
|
||||||
final Scrobblenaut? scrobblenaut;
|
|
||||||
|
|
||||||
/// Directly scrobbling in set state of [ProxyPlaylistNotifier]
|
|
||||||
/// brings extra latency in playback
|
|
||||||
final StreamController<Track> _scrobbleController =
|
|
||||||
StreamController<Track>.broadcast();
|
|
||||||
|
|
||||||
ScrobblerNotifier()
|
|
||||||
: scrobblenaut = null,
|
|
||||||
super(null, "scrobbler", encrypted: true) {
|
|
||||||
_scrobbleController.stream.listen((track) async {
|
|
||||||
try {
|
|
||||||
await state?.scrobblenaut.track.scrobble(
|
|
||||||
artist: track.artists!.first.name!,
|
|
||||||
track: track.name!,
|
|
||||||
album: track.album!.name!,
|
|
||||||
chosenByUser: true,
|
|
||||||
duration: track.duration,
|
|
||||||
timestamp: DateTime.now().toUtc(),
|
|
||||||
trackNumber: track.trackNumber,
|
|
||||||
);
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
AppLogger.reportError(e, stackTrace);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> login(
|
|
||||||
String username,
|
|
||||||
String password,
|
|
||||||
) async {
|
|
||||||
final lastFm = await LastFM.authenticate(
|
|
||||||
apiKey: Env.lastFmApiKey,
|
|
||||||
apiSecret: Env.lastFmApiSecret,
|
|
||||||
username: username,
|
|
||||||
password: password,
|
|
||||||
);
|
|
||||||
if (!lastFm.isAuth) throw Exception("Invalid credentials");
|
|
||||||
state = ScrobblerState(
|
|
||||||
username: username,
|
|
||||||
passwordHash: lastFm.passwordHash!,
|
|
||||||
scrobblenaut: Scrobblenaut(lastFM: lastFm),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> logout() async {
|
|
||||||
state = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void scrobble(Track track) {
|
|
||||||
_scrobbleController.add(track);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> love(Track track) async {
|
|
||||||
await state?.scrobblenaut.track.love(
|
|
||||||
artist: track.artists!.asString(),
|
|
||||||
track: track.name!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> unlove(Track track) async {
|
|
||||||
await state?.scrobblenaut.track.unLove(
|
|
||||||
artist: track.artists!.asString(),
|
|
||||||
track: track.name!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
FutureOr<ScrobblerState?> fromJson(Map<String, dynamic> json) async {
|
|
||||||
if (json.isEmpty) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ScrobblerState(
|
|
||||||
username: json['username'],
|
|
||||||
passwordHash: json['passwordHash'],
|
|
||||||
scrobblenaut: Scrobblenaut(
|
|
||||||
lastFM: await LastFM.authenticateWithPasswordHash(
|
|
||||||
apiKey: Env.lastFmApiKey,
|
|
||||||
apiSecret: Env.lastFmApiSecret,
|
|
||||||
username: json["username"],
|
|
||||||
passwordHash: json["passwordHash"],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return state?.toJson() ?? {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final scrobblerProvider =
|
|
||||||
StateNotifierProvider<ScrobblerNotifier, ScrobblerState?>(
|
|
||||||
(ref) => ScrobblerNotifier(),
|
|
||||||
);
|
|
Loading…
Reference in New Issue
Block a user