✨ Remote provider basis (jellyfin)
This commit is contained in:
@@ -42,6 +42,17 @@ class WatchFolders extends Table {
|
|||||||
DateTimeColumn get lastScanned => dateTime().nullable()();
|
DateTimeColumn get lastScanned => dateTime().nullable()();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RemoteProviders extends Table {
|
||||||
|
IntColumn get id => integer().autoIncrement()();
|
||||||
|
TextColumn get serverUrl => text().unique()();
|
||||||
|
TextColumn get name => text()();
|
||||||
|
TextColumn get username => text()();
|
||||||
|
TextColumn get password =>
|
||||||
|
text()(); // Note: In production, this should be encrypted
|
||||||
|
BoolColumn get isActive => boolean().withDefault(const Constant(true))();
|
||||||
|
DateTimeColumn get addedAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
}
|
||||||
|
|
||||||
class AppSettings extends Table {
|
class AppSettings extends Table {
|
||||||
TextColumn get key => text()();
|
TextColumn get key => text()();
|
||||||
TextColumn get value => text()();
|
TextColumn get value => text()();
|
||||||
@@ -51,13 +62,20 @@ class AppSettings extends Table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@DriftDatabase(
|
@DriftDatabase(
|
||||||
tables: [Tracks, Playlists, PlaylistEntries, WatchFolders, AppSettings],
|
tables: [
|
||||||
|
Tracks,
|
||||||
|
Playlists,
|
||||||
|
PlaylistEntries,
|
||||||
|
WatchFolders,
|
||||||
|
RemoteProviders,
|
||||||
|
AppSettings,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
class AppDatabase extends _$AppDatabase {
|
class AppDatabase extends _$AppDatabase {
|
||||||
AppDatabase() : super(_openConnection());
|
AppDatabase() : super(_openConnection());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 6; // Bump version for watch folders and settings
|
int get schemaVersion => 7; // Bump version for remote providers
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration {
|
MigrationStrategy get migration {
|
||||||
@@ -84,6 +102,10 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
await m.createTable(watchFolders);
|
await m.createTable(watchFolders);
|
||||||
await m.createTable(appSettings);
|
await m.createTable(appSettings);
|
||||||
}
|
}
|
||||||
|
if (from < 7) {
|
||||||
|
// Create table for remote providers
|
||||||
|
await m.createTable(remoteProviders);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1597,6 +1597,451 @@ class WatchFoldersCompanion extends UpdateCompanion<WatchFolder> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class $RemoteProvidersTable extends RemoteProviders
|
||||||
|
with TableInfo<$RemoteProvidersTable, RemoteProvider> {
|
||||||
|
@override
|
||||||
|
final GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
$RemoteProvidersTable(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 _serverUrlMeta = const VerificationMeta(
|
||||||
|
'serverUrl',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<String> serverUrl = GeneratedColumn<String>(
|
||||||
|
'server_url',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: DriftSqlType.string,
|
||||||
|
requiredDuringInsert: true,
|
||||||
|
defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE'),
|
||||||
|
);
|
||||||
|
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 _usernameMeta = const VerificationMeta(
|
||||||
|
'username',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<String> username = GeneratedColumn<String>(
|
||||||
|
'username',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: DriftSqlType.string,
|
||||||
|
requiredDuringInsert: true,
|
||||||
|
);
|
||||||
|
static const VerificationMeta _passwordMeta = const VerificationMeta(
|
||||||
|
'password',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<String> password = GeneratedColumn<String>(
|
||||||
|
'password',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: DriftSqlType.string,
|
||||||
|
requiredDuringInsert: true,
|
||||||
|
);
|
||||||
|
static const VerificationMeta _isActiveMeta = const VerificationMeta(
|
||||||
|
'isActive',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<bool> isActive = GeneratedColumn<bool>(
|
||||||
|
'is_active',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: DriftSqlType.bool,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||||
|
'CHECK ("is_active" IN (0, 1))',
|
||||||
|
),
|
||||||
|
defaultValue: const Constant(true),
|
||||||
|
);
|
||||||
|
static const VerificationMeta _addedAtMeta = const VerificationMeta(
|
||||||
|
'addedAt',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<DateTime> addedAt = GeneratedColumn<DateTime>(
|
||||||
|
'added_at',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: DriftSqlType.dateTime,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: currentDateAndTime,
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
List<GeneratedColumn> get $columns => [
|
||||||
|
id,
|
||||||
|
serverUrl,
|
||||||
|
name,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
isActive,
|
||||||
|
addedAt,
|
||||||
|
];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'remote_providers';
|
||||||
|
@override
|
||||||
|
VerificationContext validateIntegrity(
|
||||||
|
Insertable<RemoteProvider> 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('server_url')) {
|
||||||
|
context.handle(
|
||||||
|
_serverUrlMeta,
|
||||||
|
serverUrl.isAcceptableOrUnknown(data['server_url']!, _serverUrlMeta),
|
||||||
|
);
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_serverUrlMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('name')) {
|
||||||
|
context.handle(
|
||||||
|
_nameMeta,
|
||||||
|
name.isAcceptableOrUnknown(data['name']!, _nameMeta),
|
||||||
|
);
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_nameMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('username')) {
|
||||||
|
context.handle(
|
||||||
|
_usernameMeta,
|
||||||
|
username.isAcceptableOrUnknown(data['username']!, _usernameMeta),
|
||||||
|
);
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_usernameMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('password')) {
|
||||||
|
context.handle(
|
||||||
|
_passwordMeta,
|
||||||
|
password.isAcceptableOrUnknown(data['password']!, _passwordMeta),
|
||||||
|
);
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_passwordMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('is_active')) {
|
||||||
|
context.handle(
|
||||||
|
_isActiveMeta,
|
||||||
|
isActive.isAcceptableOrUnknown(data['is_active']!, _isActiveMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data.containsKey('added_at')) {
|
||||||
|
context.handle(
|
||||||
|
_addedAtMeta,
|
||||||
|
addedAt.isAcceptableOrUnknown(data['added_at']!, _addedAtMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<GeneratedColumn> get $primaryKey => {id};
|
||||||
|
@override
|
||||||
|
RemoteProvider map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return RemoteProvider(
|
||||||
|
id: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.int,
|
||||||
|
data['${effectivePrefix}id'],
|
||||||
|
)!,
|
||||||
|
serverUrl: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}server_url'],
|
||||||
|
)!,
|
||||||
|
name: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}name'],
|
||||||
|
)!,
|
||||||
|
username: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}username'],
|
||||||
|
)!,
|
||||||
|
password: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}password'],
|
||||||
|
)!,
|
||||||
|
isActive: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.bool,
|
||||||
|
data['${effectivePrefix}is_active'],
|
||||||
|
)!,
|
||||||
|
addedAt: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.dateTime,
|
||||||
|
data['${effectivePrefix}added_at'],
|
||||||
|
)!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
$RemoteProvidersTable createAlias(String alias) {
|
||||||
|
return $RemoteProvidersTable(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoteProvider extends DataClass implements Insertable<RemoteProvider> {
|
||||||
|
final int id;
|
||||||
|
final String serverUrl;
|
||||||
|
final String name;
|
||||||
|
final String username;
|
||||||
|
final String password;
|
||||||
|
final bool isActive;
|
||||||
|
final DateTime addedAt;
|
||||||
|
const RemoteProvider({
|
||||||
|
required this.id,
|
||||||
|
required this.serverUrl,
|
||||||
|
required this.name,
|
||||||
|
required this.username,
|
||||||
|
required this.password,
|
||||||
|
required this.isActive,
|
||||||
|
required this.addedAt,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
map['id'] = Variable<int>(id);
|
||||||
|
map['server_url'] = Variable<String>(serverUrl);
|
||||||
|
map['name'] = Variable<String>(name);
|
||||||
|
map['username'] = Variable<String>(username);
|
||||||
|
map['password'] = Variable<String>(password);
|
||||||
|
map['is_active'] = Variable<bool>(isActive);
|
||||||
|
map['added_at'] = Variable<DateTime>(addedAt);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteProvidersCompanion toCompanion(bool nullToAbsent) {
|
||||||
|
return RemoteProvidersCompanion(
|
||||||
|
id: Value(id),
|
||||||
|
serverUrl: Value(serverUrl),
|
||||||
|
name: Value(name),
|
||||||
|
username: Value(username),
|
||||||
|
password: Value(password),
|
||||||
|
isActive: Value(isActive),
|
||||||
|
addedAt: Value(addedAt),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory RemoteProvider.fromJson(
|
||||||
|
Map<String, dynamic> json, {
|
||||||
|
ValueSerializer? serializer,
|
||||||
|
}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return RemoteProvider(
|
||||||
|
id: serializer.fromJson<int>(json['id']),
|
||||||
|
serverUrl: serializer.fromJson<String>(json['serverUrl']),
|
||||||
|
name: serializer.fromJson<String>(json['name']),
|
||||||
|
username: serializer.fromJson<String>(json['username']),
|
||||||
|
password: serializer.fromJson<String>(json['password']),
|
||||||
|
isActive: serializer.fromJson<bool>(json['isActive']),
|
||||||
|
addedAt: serializer.fromJson<DateTime>(json['addedAt']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||||
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'id': serializer.toJson<int>(id),
|
||||||
|
'serverUrl': serializer.toJson<String>(serverUrl),
|
||||||
|
'name': serializer.toJson<String>(name),
|
||||||
|
'username': serializer.toJson<String>(username),
|
||||||
|
'password': serializer.toJson<String>(password),
|
||||||
|
'isActive': serializer.toJson<bool>(isActive),
|
||||||
|
'addedAt': serializer.toJson<DateTime>(addedAt),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteProvider copyWith({
|
||||||
|
int? id,
|
||||||
|
String? serverUrl,
|
||||||
|
String? name,
|
||||||
|
String? username,
|
||||||
|
String? password,
|
||||||
|
bool? isActive,
|
||||||
|
DateTime? addedAt,
|
||||||
|
}) => RemoteProvider(
|
||||||
|
id: id ?? this.id,
|
||||||
|
serverUrl: serverUrl ?? this.serverUrl,
|
||||||
|
name: name ?? this.name,
|
||||||
|
username: username ?? this.username,
|
||||||
|
password: password ?? this.password,
|
||||||
|
isActive: isActive ?? this.isActive,
|
||||||
|
addedAt: addedAt ?? this.addedAt,
|
||||||
|
);
|
||||||
|
RemoteProvider copyWithCompanion(RemoteProvidersCompanion data) {
|
||||||
|
return RemoteProvider(
|
||||||
|
id: data.id.present ? data.id.value : this.id,
|
||||||
|
serverUrl: data.serverUrl.present ? data.serverUrl.value : this.serverUrl,
|
||||||
|
name: data.name.present ? data.name.value : this.name,
|
||||||
|
username: data.username.present ? data.username.value : this.username,
|
||||||
|
password: data.password.present ? data.password.value : this.password,
|
||||||
|
isActive: data.isActive.present ? data.isActive.value : this.isActive,
|
||||||
|
addedAt: data.addedAt.present ? data.addedAt.value : this.addedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('RemoteProvider(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('serverUrl: $serverUrl, ')
|
||||||
|
..write('name: $name, ')
|
||||||
|
..write('username: $username, ')
|
||||||
|
..write('password: $password, ')
|
||||||
|
..write('isActive: $isActive, ')
|
||||||
|
..write('addedAt: $addedAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
Object.hash(id, serverUrl, name, username, password, isActive, addedAt);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is RemoteProvider &&
|
||||||
|
other.id == this.id &&
|
||||||
|
other.serverUrl == this.serverUrl &&
|
||||||
|
other.name == this.name &&
|
||||||
|
other.username == this.username &&
|
||||||
|
other.password == this.password &&
|
||||||
|
other.isActive == this.isActive &&
|
||||||
|
other.addedAt == this.addedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoteProvidersCompanion extends UpdateCompanion<RemoteProvider> {
|
||||||
|
final Value<int> id;
|
||||||
|
final Value<String> serverUrl;
|
||||||
|
final Value<String> name;
|
||||||
|
final Value<String> username;
|
||||||
|
final Value<String> password;
|
||||||
|
final Value<bool> isActive;
|
||||||
|
final Value<DateTime> addedAt;
|
||||||
|
const RemoteProvidersCompanion({
|
||||||
|
this.id = const Value.absent(),
|
||||||
|
this.serverUrl = const Value.absent(),
|
||||||
|
this.name = const Value.absent(),
|
||||||
|
this.username = const Value.absent(),
|
||||||
|
this.password = const Value.absent(),
|
||||||
|
this.isActive = const Value.absent(),
|
||||||
|
this.addedAt = const Value.absent(),
|
||||||
|
});
|
||||||
|
RemoteProvidersCompanion.insert({
|
||||||
|
this.id = const Value.absent(),
|
||||||
|
required String serverUrl,
|
||||||
|
required String name,
|
||||||
|
required String username,
|
||||||
|
required String password,
|
||||||
|
this.isActive = const Value.absent(),
|
||||||
|
this.addedAt = const Value.absent(),
|
||||||
|
}) : serverUrl = Value(serverUrl),
|
||||||
|
name = Value(name),
|
||||||
|
username = Value(username),
|
||||||
|
password = Value(password);
|
||||||
|
static Insertable<RemoteProvider> custom({
|
||||||
|
Expression<int>? id,
|
||||||
|
Expression<String>? serverUrl,
|
||||||
|
Expression<String>? name,
|
||||||
|
Expression<String>? username,
|
||||||
|
Expression<String>? password,
|
||||||
|
Expression<bool>? isActive,
|
||||||
|
Expression<DateTime>? addedAt,
|
||||||
|
}) {
|
||||||
|
return RawValuesInsertable({
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (serverUrl != null) 'server_url': serverUrl,
|
||||||
|
if (name != null) 'name': name,
|
||||||
|
if (username != null) 'username': username,
|
||||||
|
if (password != null) 'password': password,
|
||||||
|
if (isActive != null) 'is_active': isActive,
|
||||||
|
if (addedAt != null) 'added_at': addedAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteProvidersCompanion copyWith({
|
||||||
|
Value<int>? id,
|
||||||
|
Value<String>? serverUrl,
|
||||||
|
Value<String>? name,
|
||||||
|
Value<String>? username,
|
||||||
|
Value<String>? password,
|
||||||
|
Value<bool>? isActive,
|
||||||
|
Value<DateTime>? addedAt,
|
||||||
|
}) {
|
||||||
|
return RemoteProvidersCompanion(
|
||||||
|
id: id ?? this.id,
|
||||||
|
serverUrl: serverUrl ?? this.serverUrl,
|
||||||
|
name: name ?? this.name,
|
||||||
|
username: username ?? this.username,
|
||||||
|
password: password ?? this.password,
|
||||||
|
isActive: isActive ?? this.isActive,
|
||||||
|
addedAt: addedAt ?? this.addedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, Expression>{};
|
||||||
|
if (id.present) {
|
||||||
|
map['id'] = Variable<int>(id.value);
|
||||||
|
}
|
||||||
|
if (serverUrl.present) {
|
||||||
|
map['server_url'] = Variable<String>(serverUrl.value);
|
||||||
|
}
|
||||||
|
if (name.present) {
|
||||||
|
map['name'] = Variable<String>(name.value);
|
||||||
|
}
|
||||||
|
if (username.present) {
|
||||||
|
map['username'] = Variable<String>(username.value);
|
||||||
|
}
|
||||||
|
if (password.present) {
|
||||||
|
map['password'] = Variable<String>(password.value);
|
||||||
|
}
|
||||||
|
if (isActive.present) {
|
||||||
|
map['is_active'] = Variable<bool>(isActive.value);
|
||||||
|
}
|
||||||
|
if (addedAt.present) {
|
||||||
|
map['added_at'] = Variable<DateTime>(addedAt.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('RemoteProvidersCompanion(')
|
||||||
|
..write('id: $id, ')
|
||||||
|
..write('serverUrl: $serverUrl, ')
|
||||||
|
..write('name: $name, ')
|
||||||
|
..write('username: $username, ')
|
||||||
|
..write('password: $password, ')
|
||||||
|
..write('isActive: $isActive, ')
|
||||||
|
..write('addedAt: $addedAt')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class $AppSettingsTable extends AppSettings
|
class $AppSettingsTable extends AppSettings
|
||||||
with TableInfo<$AppSettingsTable, AppSetting> {
|
with TableInfo<$AppSettingsTable, AppSetting> {
|
||||||
@override
|
@override
|
||||||
@@ -1814,6 +2259,9 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||||||
this,
|
this,
|
||||||
);
|
);
|
||||||
late final $WatchFoldersTable watchFolders = $WatchFoldersTable(this);
|
late final $WatchFoldersTable watchFolders = $WatchFoldersTable(this);
|
||||||
|
late final $RemoteProvidersTable remoteProviders = $RemoteProvidersTable(
|
||||||
|
this,
|
||||||
|
);
|
||||||
late final $AppSettingsTable appSettings = $AppSettingsTable(this);
|
late final $AppSettingsTable appSettings = $AppSettingsTable(this);
|
||||||
@override
|
@override
|
||||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||||
@@ -1824,6 +2272,7 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||||||
playlists,
|
playlists,
|
||||||
playlistEntries,
|
playlistEntries,
|
||||||
watchFolders,
|
watchFolders,
|
||||||
|
remoteProviders,
|
||||||
appSettings,
|
appSettings,
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
@@ -3118,6 +3567,244 @@ typedef $$WatchFoldersTableProcessedTableManager =
|
|||||||
WatchFolder,
|
WatchFolder,
|
||||||
PrefetchHooks Function()
|
PrefetchHooks Function()
|
||||||
>;
|
>;
|
||||||
|
typedef $$RemoteProvidersTableCreateCompanionBuilder =
|
||||||
|
RemoteProvidersCompanion Function({
|
||||||
|
Value<int> id,
|
||||||
|
required String serverUrl,
|
||||||
|
required String name,
|
||||||
|
required String username,
|
||||||
|
required String password,
|
||||||
|
Value<bool> isActive,
|
||||||
|
Value<DateTime> addedAt,
|
||||||
|
});
|
||||||
|
typedef $$RemoteProvidersTableUpdateCompanionBuilder =
|
||||||
|
RemoteProvidersCompanion Function({
|
||||||
|
Value<int> id,
|
||||||
|
Value<String> serverUrl,
|
||||||
|
Value<String> name,
|
||||||
|
Value<String> username,
|
||||||
|
Value<String> password,
|
||||||
|
Value<bool> isActive,
|
||||||
|
Value<DateTime> addedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
class $$RemoteProvidersTableFilterComposer
|
||||||
|
extends Composer<_$AppDatabase, $RemoteProvidersTable> {
|
||||||
|
$$RemoteProvidersTableFilterComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
ColumnFilters<int> get id => $composableBuilder(
|
||||||
|
column: $table.id,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnFilters<String> get serverUrl => $composableBuilder(
|
||||||
|
column: $table.serverUrl,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnFilters<String> get name => $composableBuilder(
|
||||||
|
column: $table.name,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnFilters<String> get username => $composableBuilder(
|
||||||
|
column: $table.username,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnFilters<String> get password => $composableBuilder(
|
||||||
|
column: $table.password,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnFilters<bool> get isActive => $composableBuilder(
|
||||||
|
column: $table.isActive,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnFilters<DateTime> get addedAt => $composableBuilder(
|
||||||
|
column: $table.addedAt,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$RemoteProvidersTableOrderingComposer
|
||||||
|
extends Composer<_$AppDatabase, $RemoteProvidersTable> {
|
||||||
|
$$RemoteProvidersTableOrderingComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
ColumnOrderings<int> get id => $composableBuilder(
|
||||||
|
column: $table.id,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<String> get serverUrl => $composableBuilder(
|
||||||
|
column: $table.serverUrl,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<String> get name => $composableBuilder(
|
||||||
|
column: $table.name,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<String> get username => $composableBuilder(
|
||||||
|
column: $table.username,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<String> get password => $composableBuilder(
|
||||||
|
column: $table.password,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<bool> get isActive => $composableBuilder(
|
||||||
|
column: $table.isActive,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<DateTime> get addedAt => $composableBuilder(
|
||||||
|
column: $table.addedAt,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$RemoteProvidersTableAnnotationComposer
|
||||||
|
extends Composer<_$AppDatabase, $RemoteProvidersTable> {
|
||||||
|
$$RemoteProvidersTableAnnotationComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
GeneratedColumn<int> get id =>
|
||||||
|
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<String> get serverUrl =>
|
||||||
|
$composableBuilder(column: $table.serverUrl, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<String> get name =>
|
||||||
|
$composableBuilder(column: $table.name, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<String> get username =>
|
||||||
|
$composableBuilder(column: $table.username, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<String> get password =>
|
||||||
|
$composableBuilder(column: $table.password, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<bool> get isActive =>
|
||||||
|
$composableBuilder(column: $table.isActive, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<DateTime> get addedAt =>
|
||||||
|
$composableBuilder(column: $table.addedAt, builder: (column) => column);
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$RemoteProvidersTableTableManager
|
||||||
|
extends
|
||||||
|
RootTableManager<
|
||||||
|
_$AppDatabase,
|
||||||
|
$RemoteProvidersTable,
|
||||||
|
RemoteProvider,
|
||||||
|
$$RemoteProvidersTableFilterComposer,
|
||||||
|
$$RemoteProvidersTableOrderingComposer,
|
||||||
|
$$RemoteProvidersTableAnnotationComposer,
|
||||||
|
$$RemoteProvidersTableCreateCompanionBuilder,
|
||||||
|
$$RemoteProvidersTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
RemoteProvider,
|
||||||
|
BaseReferences<
|
||||||
|
_$AppDatabase,
|
||||||
|
$RemoteProvidersTable,
|
||||||
|
RemoteProvider
|
||||||
|
>,
|
||||||
|
),
|
||||||
|
RemoteProvider,
|
||||||
|
PrefetchHooks Function()
|
||||||
|
> {
|
||||||
|
$$RemoteProvidersTableTableManager(
|
||||||
|
_$AppDatabase db,
|
||||||
|
$RemoteProvidersTable table,
|
||||||
|
) : super(
|
||||||
|
TableManagerState(
|
||||||
|
db: db,
|
||||||
|
table: table,
|
||||||
|
createFilteringComposer: () =>
|
||||||
|
$$RemoteProvidersTableFilterComposer($db: db, $table: table),
|
||||||
|
createOrderingComposer: () =>
|
||||||
|
$$RemoteProvidersTableOrderingComposer($db: db, $table: table),
|
||||||
|
createComputedFieldComposer: () =>
|
||||||
|
$$RemoteProvidersTableAnnotationComposer($db: db, $table: table),
|
||||||
|
updateCompanionCallback:
|
||||||
|
({
|
||||||
|
Value<int> id = const Value.absent(),
|
||||||
|
Value<String> serverUrl = const Value.absent(),
|
||||||
|
Value<String> name = const Value.absent(),
|
||||||
|
Value<String> username = const Value.absent(),
|
||||||
|
Value<String> password = const Value.absent(),
|
||||||
|
Value<bool> isActive = const Value.absent(),
|
||||||
|
Value<DateTime> addedAt = const Value.absent(),
|
||||||
|
}) => RemoteProvidersCompanion(
|
||||||
|
id: id,
|
||||||
|
serverUrl: serverUrl,
|
||||||
|
name: name,
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
isActive: isActive,
|
||||||
|
addedAt: addedAt,
|
||||||
|
),
|
||||||
|
createCompanionCallback:
|
||||||
|
({
|
||||||
|
Value<int> id = const Value.absent(),
|
||||||
|
required String serverUrl,
|
||||||
|
required String name,
|
||||||
|
required String username,
|
||||||
|
required String password,
|
||||||
|
Value<bool> isActive = const Value.absent(),
|
||||||
|
Value<DateTime> addedAt = const Value.absent(),
|
||||||
|
}) => RemoteProvidersCompanion.insert(
|
||||||
|
id: id,
|
||||||
|
serverUrl: serverUrl,
|
||||||
|
name: name,
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
isActive: isActive,
|
||||||
|
addedAt: addedAt,
|
||||||
|
),
|
||||||
|
withReferenceMapper: (p0) => p0
|
||||||
|
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
|
||||||
|
.toList(),
|
||||||
|
prefetchHooksCallback: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef $$RemoteProvidersTableProcessedTableManager =
|
||||||
|
ProcessedTableManager<
|
||||||
|
_$AppDatabase,
|
||||||
|
$RemoteProvidersTable,
|
||||||
|
RemoteProvider,
|
||||||
|
$$RemoteProvidersTableFilterComposer,
|
||||||
|
$$RemoteProvidersTableOrderingComposer,
|
||||||
|
$$RemoteProvidersTableAnnotationComposer,
|
||||||
|
$$RemoteProvidersTableCreateCompanionBuilder,
|
||||||
|
$$RemoteProvidersTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
RemoteProvider,
|
||||||
|
BaseReferences<_$AppDatabase, $RemoteProvidersTable, RemoteProvider>,
|
||||||
|
),
|
||||||
|
RemoteProvider,
|
||||||
|
PrefetchHooks Function()
|
||||||
|
>;
|
||||||
typedef $$AppSettingsTableCreateCompanionBuilder =
|
typedef $$AppSettingsTableCreateCompanionBuilder =
|
||||||
AppSettingsCompanion Function({
|
AppSettingsCompanion Function({
|
||||||
required String key,
|
required String key,
|
||||||
@@ -3269,6 +3956,8 @@ class $AppDatabaseManager {
|
|||||||
$$PlaylistEntriesTableTableManager(_db, _db.playlistEntries);
|
$$PlaylistEntriesTableTableManager(_db, _db.playlistEntries);
|
||||||
$$WatchFoldersTableTableManager get watchFolders =>
|
$$WatchFoldersTableTableManager get watchFolders =>
|
||||||
$$WatchFoldersTableTableManager(_db, _db.watchFolders);
|
$$WatchFoldersTableTableManager(_db, _db.watchFolders);
|
||||||
|
$$RemoteProvidersTableTableManager get remoteProviders =>
|
||||||
|
$$RemoteProvidersTableTableManager(_db, _db.remoteProviders);
|
||||||
$$AppSettingsTableTableManager get appSettings =>
|
$$AppSettingsTableTableManager get appSettings =>
|
||||||
$$AppSettingsTableTableManager(_db, _db.appSettings);
|
$$AppSettingsTableTableManager(_db, _db.appSettings);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ final class PlaylistRepositoryProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$playlistRepositoryHash() =>
|
String _$playlistRepositoryHash() =>
|
||||||
r'614d837f9438d2454778edb4ff60b046418490b8';
|
r'20c2c56f237a9e3ac3efe1225d05db8264b19678';
|
||||||
|
|
||||||
abstract class _$PlaylistRepository extends $AsyncNotifier<void> {
|
abstract class _$PlaylistRepository extends $AsyncNotifier<void> {
|
||||||
FutureOr<void> build();
|
FutureOr<void> build();
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ final class TrackRepositoryProvider
|
|||||||
TrackRepository create() => TrackRepository();
|
TrackRepository create() => TrackRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$trackRepositoryHash() => r'538fedbc358e305aac4517d2c517a8bdf6bbb75c';
|
String _$trackRepositoryHash() => r'655c231192698ef0c31920af846de47def7da81d';
|
||||||
|
|
||||||
abstract class _$TrackRepository extends $AsyncNotifier<void> {
|
abstract class _$TrackRepository extends $AsyncNotifier<void> {
|
||||||
FutureOr<void> build();
|
FutureOr<void> build();
|
||||||
|
|||||||
205
lib/providers/remote_provider.dart
Normal file
205
lib/providers/remote_provider.dart
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:groovybox/data/db.dart';
|
||||||
|
import 'package:groovybox/providers/db_provider.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:jellyfin_dart/jellyfin_dart.dart';
|
||||||
|
|
||||||
|
// Simple remote provider using Riverpod
|
||||||
|
final remoteProvidersProvider = FutureProvider<List<RemoteProvider>>((
|
||||||
|
ref,
|
||||||
|
) async {
|
||||||
|
final db = ref.read(databaseProvider);
|
||||||
|
return await (db.select(
|
||||||
|
db.remoteProviders,
|
||||||
|
)..orderBy([(t) => OrderingTerm(expression: t.addedAt)])).get();
|
||||||
|
});
|
||||||
|
|
||||||
|
final activeRemoteProvidersProvider = Provider<List<RemoteProvider>>((ref) {
|
||||||
|
final remoteProvidersAsync = ref.watch(remoteProvidersProvider);
|
||||||
|
return remoteProvidersAsync.when(
|
||||||
|
data: (providers) =>
|
||||||
|
providers.where((provider) => provider.isActive).toList(),
|
||||||
|
loading: () => [],
|
||||||
|
error: (_, _) => [],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
class RemoteProviderService {
|
||||||
|
final Ref ref;
|
||||||
|
|
||||||
|
RemoteProviderService(this.ref);
|
||||||
|
|
||||||
|
Future<void> addRemoteProvider(
|
||||||
|
String serverUrl,
|
||||||
|
String username,
|
||||||
|
String password, {
|
||||||
|
String? name,
|
||||||
|
}) async {
|
||||||
|
final db = ref.read(databaseProvider);
|
||||||
|
final providerName = name ?? Uri.parse(serverUrl).host;
|
||||||
|
|
||||||
|
await db
|
||||||
|
.into(db.remoteProviders)
|
||||||
|
.insert(
|
||||||
|
RemoteProvidersCompanion.insert(
|
||||||
|
serverUrl: serverUrl,
|
||||||
|
name: providerName,
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Invalidate the provider to refresh UI
|
||||||
|
ref.invalidate(remoteProvidersProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> removeRemoteProvider(int providerId) async {
|
||||||
|
final db = ref.read(databaseProvider);
|
||||||
|
|
||||||
|
await (db.delete(
|
||||||
|
db.remoteProviders,
|
||||||
|
)..where((t) => t.id.equals(providerId))).go();
|
||||||
|
|
||||||
|
// Invalidate the provider to refresh UI
|
||||||
|
ref.invalidate(remoteProvidersProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> toggleRemoteProvider(int providerId, bool isActive) async {
|
||||||
|
final db = ref.read(databaseProvider);
|
||||||
|
|
||||||
|
await (db.update(db.remoteProviders)..where((t) => t.id.equals(providerId)))
|
||||||
|
.write(RemoteProvidersCompanion(isActive: Value(isActive)));
|
||||||
|
|
||||||
|
// Invalidate the provider to refresh UI
|
||||||
|
ref.invalidate(remoteProvidersProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> indexRemoteProvider(int providerId) async {
|
||||||
|
final db = ref.read(databaseProvider);
|
||||||
|
|
||||||
|
// Get provider details
|
||||||
|
final provider = await (db.select(
|
||||||
|
db.remoteProviders,
|
||||||
|
)..where((t) => t.id.equals(providerId))).getSingleOrNull();
|
||||||
|
|
||||||
|
if (provider == null) {
|
||||||
|
throw Exception('Remote provider not found: $providerId');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!provider.isActive) {
|
||||||
|
debugPrint('Provider $providerId is not active, skipping indexing');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create Jellyfin client
|
||||||
|
final client = JellyfinDart(basePathOverride: provider.serverUrl);
|
||||||
|
|
||||||
|
// Set device info
|
||||||
|
client.setDeviceId('groovybox-${providerId}');
|
||||||
|
client.setVersion('1.0.0');
|
||||||
|
|
||||||
|
// Authenticate
|
||||||
|
final userApi = client.getUserApi();
|
||||||
|
final authResponse = await userApi.authenticateUserByName(
|
||||||
|
authenticateUserByName: AuthenticateUserByName(
|
||||||
|
username: provider.username,
|
||||||
|
pw: provider.password,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final token = authResponse.data?.accessToken;
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Authentication failed for provider ${provider.name}');
|
||||||
|
}
|
||||||
|
|
||||||
|
client.setToken(token);
|
||||||
|
|
||||||
|
// Fetch music items
|
||||||
|
final itemsApi = client.getItemsApi();
|
||||||
|
final musicItems = await itemsApi.getItems(
|
||||||
|
includeItemTypes: [BaseItemKind.audio],
|
||||||
|
recursive: true,
|
||||||
|
fields: [
|
||||||
|
ItemFields.path,
|
||||||
|
ItemFields.mediaStreams,
|
||||||
|
ItemFields.mediaSources,
|
||||||
|
ItemFields.genres,
|
||||||
|
ItemFields.tags,
|
||||||
|
ItemFields.overview,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
final items = musicItems.data?.items ?? [];
|
||||||
|
|
||||||
|
// Convert to tracks and store
|
||||||
|
for (final item in items) {
|
||||||
|
await _addRemoteTrack(db, provider, item, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
debugPrint('Indexed $items.length tracks from $provider.name');
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error indexing remote provider $provider.name: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _addRemoteTrack(
|
||||||
|
AppDatabase db,
|
||||||
|
RemoteProvider provider,
|
||||||
|
BaseItemDto item,
|
||||||
|
String token,
|
||||||
|
) async {
|
||||||
|
// Generate streaming URL
|
||||||
|
final streamUrl =
|
||||||
|
'${provider.serverUrl}/Audio/${item.id}/stream.mp3?api_key=$token&static=true';
|
||||||
|
|
||||||
|
// Extract metadata
|
||||||
|
final title = item.name ?? 'Unknown Title';
|
||||||
|
final artist =
|
||||||
|
item.albumArtist ?? item.artists?.join(', ') ?? 'Unknown Artist';
|
||||||
|
final album = item.album ?? 'Unknown Album';
|
||||||
|
final duration =
|
||||||
|
(item.runTimeTicks ?? 0) ~/ 10000; // Convert ticks to milliseconds
|
||||||
|
|
||||||
|
// Check if track already exists
|
||||||
|
final existingTrack = await (db.select(
|
||||||
|
db.tracks,
|
||||||
|
)..where((t) => t.path.equals(streamUrl))).getSingleOrNull();
|
||||||
|
|
||||||
|
if (existingTrack != null) {
|
||||||
|
// Update existing track
|
||||||
|
await (db.update(
|
||||||
|
db.tracks,
|
||||||
|
)..where((t) => t.id.equals(existingTrack.id))).write(
|
||||||
|
TracksCompanion(
|
||||||
|
title: Value(title),
|
||||||
|
artist: Value(artist),
|
||||||
|
album: Value(album),
|
||||||
|
duration: Value(duration),
|
||||||
|
addedAt: Value(DateTime.now()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Insert new track
|
||||||
|
await db
|
||||||
|
.into(db.tracks)
|
||||||
|
.insert(
|
||||||
|
TracksCompanion.insert(
|
||||||
|
title: title,
|
||||||
|
path: streamUrl, // Remote streaming URL
|
||||||
|
artist: Value(artist),
|
||||||
|
album: Value(album),
|
||||||
|
duration: Value(duration),
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider for the service
|
||||||
|
final remoteProviderServiceProvider = Provider<RemoteProviderService>((ref) {
|
||||||
|
return RemoteProviderService(ref);
|
||||||
|
});
|
||||||
@@ -87,7 +87,7 @@ final class ImportModeNotifierProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$importModeNotifierHash() =>
|
String _$importModeNotifierHash() =>
|
||||||
r'eaf3dcf7c74dc24d6ebe14840d597e4a79859a63';
|
r'4a4f8d3bb378e964f1d67159a650a2d7addeab69';
|
||||||
|
|
||||||
abstract class _$ImportModeNotifier extends $Notifier<ImportMode> {
|
abstract class _$ImportModeNotifier extends $Notifier<ImportMode> {
|
||||||
ImportMode build();
|
ImportMode build();
|
||||||
@@ -140,7 +140,7 @@ final class AutoScanNotifierProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$autoScanNotifierHash() => r'56f2f1a2f6aef095782a0ed4407a43a8f589dc4b';
|
String _$autoScanNotifierHash() => r'e8d7c9bd7059e0117979b120616addcd5c1abb8d';
|
||||||
|
|
||||||
abstract class _$AutoScanNotifier extends $Notifier<bool> {
|
abstract class _$AutoScanNotifier extends $Notifier<bool> {
|
||||||
bool build();
|
bool build();
|
||||||
@@ -194,7 +194,7 @@ final class WatchForChangesNotifierProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$watchForChangesNotifierHash() =>
|
String _$watchForChangesNotifierHash() =>
|
||||||
r'b4648380ae989e6e36138780d0c925916b6e20b3';
|
r'1f15ffac52a0401b14d8cd4e04d39c69d5a2e704';
|
||||||
|
|
||||||
abstract class _$WatchForChangesNotifier extends $Notifier<bool> {
|
abstract class _$WatchForChangesNotifier extends $Notifier<bool> {
|
||||||
bool build();
|
bool build();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:groovybox/providers/settings_provider.dart';
|
import 'package:groovybox/providers/settings_provider.dart';
|
||||||
import 'package:groovybox/providers/watch_folder_provider.dart';
|
import 'package:groovybox/providers/watch_folder_provider.dart';
|
||||||
|
import 'package:groovybox/providers/remote_provider.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
@@ -12,6 +13,7 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final settingsAsync = ref.watch(settingsProvider);
|
final settingsAsync = ref.watch(settingsProvider);
|
||||||
final watchFoldersAsync = ref.watch(watchFoldersProvider);
|
final watchFoldersAsync = ref.watch(watchFoldersProvider);
|
||||||
|
final remoteProvidersAsync = ref.watch(remoteProvidersProvider);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Settings')),
|
appBar: AppBar(title: const Text('Settings')),
|
||||||
@@ -79,6 +81,7 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@@ -186,6 +189,122 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Remote Providers Section
|
||||||
|
Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Remote Providers',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () =>
|
||||||
|
_indexRemoteProviders(context, ref),
|
||||||
|
icon: const Icon(Icons.refresh),
|
||||||
|
tooltip: 'Index Remote Providers',
|
||||||
|
visualDensity: const VisualDensity(
|
||||||
|
horizontal: -4,
|
||||||
|
vertical: -4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () =>
|
||||||
|
_addRemoteProvider(context, ref),
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
tooltip: 'Add Remote Provider',
|
||||||
|
visualDensity: const VisualDensity(
|
||||||
|
horizontal: -4,
|
||||||
|
vertical: -4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
'Connect to remote media servers like Jellyfin to access your music library.',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16, top: 16, bottom: 8),
|
||||||
|
remoteProvidersAsync.when(
|
||||||
|
data: (providers) => providers.isEmpty
|
||||||
|
? const Text(
|
||||||
|
'No remote providers added yet.',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
).padding(horizontal: 16, vertical: 8)
|
||||||
|
: Column(
|
||||||
|
children: providers
|
||||||
|
.map(
|
||||||
|
(provider) => ListTile(
|
||||||
|
title: Text(provider.name),
|
||||||
|
subtitle: Text(provider.serverUrl),
|
||||||
|
contentPadding: const EdgeInsets.only(
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Switch(
|
||||||
|
value: provider.isActive,
|
||||||
|
onChanged: (value) {
|
||||||
|
ref
|
||||||
|
.read(
|
||||||
|
remoteProviderServiceProvider,
|
||||||
|
)
|
||||||
|
.toggleRemoteProvider(
|
||||||
|
provider.id,
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
onPressed: () {
|
||||||
|
ref
|
||||||
|
.read(
|
||||||
|
remoteProviderServiceProvider,
|
||||||
|
)
|
||||||
|
.removeRemoteProvider(
|
||||||
|
provider.id,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
loading: () => const CircularProgressIndicator(),
|
||||||
|
error: (error, _) =>
|
||||||
|
Text('Error loading providers: $error'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -237,4 +356,139 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _indexRemoteProviders(BuildContext context, WidgetRef ref) async {
|
||||||
|
try {
|
||||||
|
final service = ref.read(remoteProviderServiceProvider);
|
||||||
|
final providersAsync = ref.read(remoteProvidersProvider);
|
||||||
|
|
||||||
|
providersAsync.when(
|
||||||
|
data: (providers) async {
|
||||||
|
final activeProviders = providers.where((p) => p.isActive).toList();
|
||||||
|
|
||||||
|
if (activeProviders.isEmpty) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('No active remote providers to index'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final provider in activeProviders) {
|
||||||
|
try {
|
||||||
|
await service.indexRemoteProvider(provider.id);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error indexing provider ${provider.name}: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
'Indexed ${activeProviders.length} remote provider(s)',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loading: () {
|
||||||
|
// Providers are still loading, do nothing
|
||||||
|
},
|
||||||
|
error: (error, _) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Error loading providers: $error')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Error indexing remote providers: $e')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addRemoteProvider(BuildContext context, WidgetRef ref) {
|
||||||
|
final serverUrlController = TextEditingController();
|
||||||
|
final usernameController = TextEditingController();
|
||||||
|
final passwordController = TextEditingController();
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Add Remote Provider'),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: serverUrlController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Server URL',
|
||||||
|
hintText: 'https://your-jellyfin-server.com',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextField(
|
||||||
|
controller: usernameController,
|
||||||
|
decoration: const InputDecoration(labelText: 'Username'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextField(
|
||||||
|
controller: passwordController,
|
||||||
|
decoration: const InputDecoration(labelText: 'Password'),
|
||||||
|
obscureText: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final serverUrl = serverUrlController.text.trim();
|
||||||
|
final username = usernameController.text.trim();
|
||||||
|
final password = passwordController.text.trim();
|
||||||
|
|
||||||
|
if (serverUrl.isEmpty || username.isEmpty || password.isEmpty) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('All fields are required')),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final service = ref.read(remoteProviderServiceProvider);
|
||||||
|
await service.addRemoteProvider(serverUrl, username, password);
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Added remote provider: $serverUrl'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Error adding provider: $e')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('Add'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
pubspec.lock
24
pubspec.lock
@@ -233,6 +233,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
|
copy_with_extension:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: copy_with_extension
|
||||||
|
sha256: c9e09bce2fee69729ea55dbd55f7150d4cf6f7e55461091a02839c21346f1b95
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.0.1"
|
||||||
coverage:
|
coverage:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -345,6 +353,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.8"
|
version: "0.2.8"
|
||||||
|
equatable:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: equatable
|
||||||
|
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.7"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -560,6 +576,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
|
jellyfin_dart:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: jellyfin_dart
|
||||||
|
sha256: "269fddfee36ee4a0fa2d03cf9e6f448d77ca35893faa77723c677889ac3f7e6f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ dependencies:
|
|||||||
palette_generator: ^0.3.3+4
|
palette_generator: ^0.3.3+4
|
||||||
watcher: ^1.2.0
|
watcher: ^1.2.0
|
||||||
shared_preferences: ^2.3.5
|
shared_preferences: ^2.3.5
|
||||||
|
jellyfin_dart: ^0.1.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user