✨ Remote provider basis (jellyfin)
This commit is contained in:
@@ -42,6 +42,17 @@ class WatchFolders extends Table {
|
||||
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 {
|
||||
TextColumn get key => text()();
|
||||
TextColumn get value => text()();
|
||||
@@ -51,13 +62,20 @@ class AppSettings extends Table {
|
||||
}
|
||||
|
||||
@DriftDatabase(
|
||||
tables: [Tracks, Playlists, PlaylistEntries, WatchFolders, AppSettings],
|
||||
tables: [
|
||||
Tracks,
|
||||
Playlists,
|
||||
PlaylistEntries,
|
||||
WatchFolders,
|
||||
RemoteProviders,
|
||||
AppSettings,
|
||||
],
|
||||
)
|
||||
class AppDatabase extends _$AppDatabase {
|
||||
AppDatabase() : super(_openConnection());
|
||||
|
||||
@override
|
||||
int get schemaVersion => 6; // Bump version for watch folders and settings
|
||||
int get schemaVersion => 7; // Bump version for remote providers
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration {
|
||||
@@ -84,6 +102,10 @@ class AppDatabase extends _$AppDatabase {
|
||||
await m.createTable(watchFolders);
|
||||
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
|
||||
with TableInfo<$AppSettingsTable, AppSetting> {
|
||||
@override
|
||||
@@ -1814,6 +2259,9 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
||||
this,
|
||||
);
|
||||
late final $WatchFoldersTable watchFolders = $WatchFoldersTable(this);
|
||||
late final $RemoteProvidersTable remoteProviders = $RemoteProvidersTable(
|
||||
this,
|
||||
);
|
||||
late final $AppSettingsTable appSettings = $AppSettingsTable(this);
|
||||
@override
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
@@ -1824,6 +2272,7 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
||||
playlists,
|
||||
playlistEntries,
|
||||
watchFolders,
|
||||
remoteProviders,
|
||||
appSettings,
|
||||
];
|
||||
@override
|
||||
@@ -3118,6 +3567,244 @@ typedef $$WatchFoldersTableProcessedTableManager =
|
||||
WatchFolder,
|
||||
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 =
|
||||
AppSettingsCompanion Function({
|
||||
required String key,
|
||||
@@ -3269,6 +3956,8 @@ class $AppDatabaseManager {
|
||||
$$PlaylistEntriesTableTableManager(_db, _db.playlistEntries);
|
||||
$$WatchFoldersTableTableManager get watchFolders =>
|
||||
$$WatchFoldersTableTableManager(_db, _db.watchFolders);
|
||||
$$RemoteProvidersTableTableManager get remoteProviders =>
|
||||
$$RemoteProvidersTableTableManager(_db, _db.remoteProviders);
|
||||
$$AppSettingsTableTableManager get appSettings =>
|
||||
$$AppSettingsTableTableManager(_db, _db.appSettings);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ final class PlaylistRepositoryProvider
|
||||
}
|
||||
|
||||
String _$playlistRepositoryHash() =>
|
||||
r'614d837f9438d2454778edb4ff60b046418490b8';
|
||||
r'20c2c56f237a9e3ac3efe1225d05db8264b19678';
|
||||
|
||||
abstract class _$PlaylistRepository extends $AsyncNotifier<void> {
|
||||
FutureOr<void> build();
|
||||
|
||||
@@ -33,7 +33,7 @@ final class TrackRepositoryProvider
|
||||
TrackRepository create() => TrackRepository();
|
||||
}
|
||||
|
||||
String _$trackRepositoryHash() => r'538fedbc358e305aac4517d2c517a8bdf6bbb75c';
|
||||
String _$trackRepositoryHash() => r'655c231192698ef0c31920af846de47def7da81d';
|
||||
|
||||
abstract class _$TrackRepository extends $AsyncNotifier<void> {
|
||||
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() =>
|
||||
r'eaf3dcf7c74dc24d6ebe14840d597e4a79859a63';
|
||||
r'4a4f8d3bb378e964f1d67159a650a2d7addeab69';
|
||||
|
||||
abstract class _$ImportModeNotifier extends $Notifier<ImportMode> {
|
||||
ImportMode build();
|
||||
@@ -140,7 +140,7 @@ final class AutoScanNotifierProvider
|
||||
}
|
||||
}
|
||||
|
||||
String _$autoScanNotifierHash() => r'56f2f1a2f6aef095782a0ed4407a43a8f589dc4b';
|
||||
String _$autoScanNotifierHash() => r'e8d7c9bd7059e0117979b120616addcd5c1abb8d';
|
||||
|
||||
abstract class _$AutoScanNotifier extends $Notifier<bool> {
|
||||
bool build();
|
||||
@@ -194,7 +194,7 @@ final class WatchForChangesNotifierProvider
|
||||
}
|
||||
|
||||
String _$watchForChangesNotifierHash() =>
|
||||
r'b4648380ae989e6e36138780d0c925916b6e20b3';
|
||||
r'1f15ffac52a0401b14d8cd4e04d39c69d5a2e704';
|
||||
|
||||
abstract class _$WatchForChangesNotifier extends $Notifier<bool> {
|
||||
bool build();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:groovybox/providers/settings_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:file_picker/file_picker.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
@@ -12,6 +13,7 @@ class SettingsScreen extends ConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final settingsAsync = ref.watch(settingsProvider);
|
||||
final watchFoldersAsync = ref.watch(watchFoldersProvider);
|
||||
final remoteProvidersAsync = ref.watch(remoteProvidersProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Settings')),
|
||||
@@ -79,6 +81,7 @@ class SettingsScreen extends ConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user