mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 00:59:28 +01:00
display albums from db
This commit is contained in:
parent
0c80dbdba5
commit
933775b92f
@ -1,20 +1,35 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
|
||||
import '../../sources/models.dart';
|
||||
import '../hooks/use_paging_controller.dart';
|
||||
import '../state/database.dart';
|
||||
import '../state/settings.dart';
|
||||
import 'list_items.dart';
|
||||
|
||||
class AlbumsGrid extends HookWidget {
|
||||
const kPageSize = 30;
|
||||
|
||||
class AlbumsGrid extends HookConsumerWidget {
|
||||
const AlbumsGrid({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = usePagingController<int, String>(
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final db = ref.watch(databaseProvider);
|
||||
final sourceId = ref.watch(sourceIdProvider);
|
||||
|
||||
final controller = usePagingController<int, Album>(
|
||||
getNextPageKey: (state) =>
|
||||
state.lastPageIsEmpty ? null : state.nextIntPageKey,
|
||||
fetchPage: (pageKey) => List.generate(30, (_) => pageKey.toString()),
|
||||
fetchPage: (pageKey) async {
|
||||
final query = db.albums.select()
|
||||
..where((f) => f.sourceId.equals(sourceId))
|
||||
..limit(kPageSize, offset: (pageKey - 1) * kPageSize);
|
||||
|
||||
return await query.get();
|
||||
},
|
||||
);
|
||||
|
||||
return PagingListener(
|
||||
@ -26,10 +41,11 @@ class AlbumsGrid extends HookWidget {
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
),
|
||||
builderDelegate: PagedChildBuilderDelegate<String>(
|
||||
builderDelegate: PagedChildBuilderDelegate<Album>(
|
||||
itemBuilder: (context, item, index) => AlbumGridTile(
|
||||
onTap: () {
|
||||
context.push('/album');
|
||||
album: item,
|
||||
onTap: () async {
|
||||
context.push('/album/${item.id}');
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@ -1,18 +1,26 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:octo_image/octo_image.dart';
|
||||
|
||||
import '../../sources/models.dart';
|
||||
import '../state/source.dart';
|
||||
import '../util/clip.dart';
|
||||
|
||||
class AlbumGridTile extends StatelessWidget {
|
||||
class AlbumGridTile extends HookConsumerWidget {
|
||||
const AlbumGridTile({
|
||||
super.key,
|
||||
required this.album,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final Album album;
|
||||
final void Function()? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return CardTheme(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: RoundedRectangleBorder(
|
||||
@ -21,16 +29,50 @@ class AlbumGridTile extends StatelessWidget {
|
||||
margin: EdgeInsets.all(2),
|
||||
child: ImageCard(
|
||||
onTap: onTap,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: 'https://placehold.net/400x400.png',
|
||||
placeholder: (context, url) => CircularProgressIndicator(),
|
||||
errorWidget: (context, url, error) => Icon(Icons.error),
|
||||
),
|
||||
child: CoverArtImage(coverArt: album.coverArt),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CoverArtImage extends HookConsumerWidget {
|
||||
const CoverArtImage({
|
||||
super.key,
|
||||
this.coverArt,
|
||||
this.thumbnail = false,
|
||||
});
|
||||
|
||||
final String? coverArt;
|
||||
final bool thumbnail;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final source = ref.watch(sourceProvider);
|
||||
|
||||
CachedNetworkImageProvider buildImageProvider() =>
|
||||
CachedNetworkImageProvider(
|
||||
coverArt != null
|
||||
? source.coverArtUri(coverArt!, thumbnail: thumbnail).toString()
|
||||
: 'https://placehold.net/400x400.png',
|
||||
);
|
||||
|
||||
final imageProvider = useState(buildImageProvider());
|
||||
useEffect(() {
|
||||
imageProvider.value = buildImageProvider();
|
||||
return;
|
||||
}, [source, coverArt, thumbnail]);
|
||||
|
||||
return OctoImage(
|
||||
image: imageProvider.value,
|
||||
placeholderBuilder: (context) => Icon(Symbols.album_rounded),
|
||||
errorBuilder: (context, error, trace) => Icon(Icons.error),
|
||||
fit: BoxFit.cover,
|
||||
fadeOutDuration: Duration(milliseconds: 100),
|
||||
fadeInDuration: Duration(milliseconds: 200),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ArtistListTile extends StatelessWidget {
|
||||
const ArtistListTile({super.key});
|
||||
|
||||
@ -71,7 +113,7 @@ class ImageCard extends StatelessWidget {
|
||||
child,
|
||||
Positioned.fill(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
|
||||
@ -17,8 +17,9 @@ final router = GoRouter(
|
||||
builder: (context, state) => LibraryScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'album',
|
||||
builder: (context, state) => AlbumScreen(),
|
||||
path: 'album/:id',
|
||||
builder: (context, state) =>
|
||||
AlbumScreen(id: state.pathParameters['id']!),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'artist',
|
||||
|
||||
@ -3,7 +3,12 @@ import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class AlbumScreen extends StatelessWidget {
|
||||
const AlbumScreen({super.key});
|
||||
const AlbumScreen({
|
||||
super.key,
|
||||
required this.id,
|
||||
});
|
||||
|
||||
final String id;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -12,7 +17,7 @@ class AlbumScreen extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Album!'),
|
||||
Text('Album $id!'),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.push('/artist');
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import '../lists/albums_grid.dart';
|
||||
import '../state/services.dart';
|
||||
import '../util/custom_scroll_fix.dart';
|
||||
|
||||
class LibraryScreen extends StatefulWidget {
|
||||
@ -126,6 +128,9 @@ class _LibraryScreenState extends State<LibraryScreen>
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
SyncButton(),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.push('/settings');
|
||||
@ -136,6 +141,8 @@ class _LibraryScreenState extends State<LibraryScreen>
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -209,3 +216,19 @@ class _NewWidgetState extends State<NewWidget>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SyncButton extends HookConsumerWidget {
|
||||
const SyncButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final syncService = ref.watch(syncServiceProvider);
|
||||
|
||||
return IconButton(
|
||||
icon: Icon(Symbols.sync_rounded),
|
||||
onPressed: () {
|
||||
syncService.sync();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
7
lib/app/state/database.dart
Normal file
7
lib/app/state/database.dart
Normal file
@ -0,0 +1,7 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import '../../database/database.dart';
|
||||
|
||||
final databaseProvider = Provider<SubtracksDatabase>((ref) {
|
||||
return SubtracksDatabase();
|
||||
});
|
||||
14
lib/app/state/services.dart
Normal file
14
lib/app/state/services.dart
Normal file
@ -0,0 +1,14 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import '../../services/sync_services.dart';
|
||||
import 'database.dart';
|
||||
import 'settings.dart';
|
||||
import 'source.dart';
|
||||
|
||||
final syncServiceProvider = Provider<SyncService>((ref) {
|
||||
final db = ref.watch(databaseProvider);
|
||||
final source = ref.watch(sourceProvider);
|
||||
final sourceId = ref.watch(sourceIdProvider);
|
||||
|
||||
return SyncService(source: source, db: db, sourceId: sourceId);
|
||||
});
|
||||
5
lib/app/state/settings.dart
Normal file
5
lib/app/state/settings.dart
Normal file
@ -0,0 +1,5 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
final sourceIdProvider = Provider<int>((ref) {
|
||||
return 1;
|
||||
});
|
||||
39
lib/app/state/source.dart
Normal file
39
lib/app/state/source.dart
Normal file
@ -0,0 +1,39 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import '../../sources/music_source.dart';
|
||||
import '../../sources/subsonic/client.dart';
|
||||
import '../../sources/subsonic/source.dart';
|
||||
import '../../util/http.dart';
|
||||
import 'database.dart';
|
||||
import 'settings.dart';
|
||||
|
||||
final _sourceProvider = FutureProvider<MusicSource>((ref) async {
|
||||
final db = ref.watch(databaseProvider);
|
||||
final sourceId = ref.watch(sourceIdProvider);
|
||||
|
||||
final query = db.sources.select().join([
|
||||
leftOuterJoin(
|
||||
db.subsonicSettings,
|
||||
db.subsonicSettings.sourceId.equalsExp(db.sources.id),
|
||||
),
|
||||
])..where(db.sources.id.equals(sourceId));
|
||||
|
||||
final result = await query.getSingle();
|
||||
final subsonicSettings = result.readTable(db.subsonicSettings);
|
||||
|
||||
return SubsonicSource(
|
||||
SubsonicClient(
|
||||
http: SubtracksHttpClient(),
|
||||
address: subsonicSettings.address,
|
||||
username: subsonicSettings.username,
|
||||
password: subsonicSettings.password,
|
||||
useTokenAuth: subsonicSettings.useTokenAuth,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
final sourceProvider = Provider<MusicSource>((ref) {
|
||||
final source = ref.watch(_sourceProvider);
|
||||
return source.requireValue;
|
||||
});
|
||||
@ -300,12 +300,12 @@ class SourcesCompanion extends UpdateCompanion<Source> {
|
||||
}
|
||||
}
|
||||
|
||||
class SubsonicSourceOptions extends Table
|
||||
with TableInfo<SubsonicSourceOptions, SubsonicSourceOption> {
|
||||
class SubsonicSettings extends Table
|
||||
with TableInfo<SubsonicSettings, SubsonicSetting> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
SubsonicSourceOptions(this.attachedDatabase, [this._alias]);
|
||||
SubsonicSettings(this.attachedDatabase, [this._alias]);
|
||||
static const VerificationMeta _sourceIdMeta = const VerificationMeta(
|
||||
'sourceId',
|
||||
);
|
||||
@ -325,7 +325,7 @@ class SubsonicSourceOptions extends Table
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL',
|
||||
).withConverter<Uri>(SubsonicSourceOptions.$converteraddress);
|
||||
).withConverter<Uri>(SubsonicSettings.$converteraddress);
|
||||
static const VerificationMeta _usernameMeta = const VerificationMeta(
|
||||
'username',
|
||||
);
|
||||
@ -372,10 +372,10 @@ class SubsonicSourceOptions extends Table
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'subsonic_source_options';
|
||||
static const String $name = 'subsonic_settings';
|
||||
@override
|
||||
VerificationContext validateIntegrity(
|
||||
Insertable<SubsonicSourceOption> instance, {
|
||||
Insertable<SubsonicSetting> instance, {
|
||||
bool isInserting = false,
|
||||
}) {
|
||||
final context = VerificationContext();
|
||||
@ -417,14 +417,14 @@ class SubsonicSourceOptions extends Table
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {sourceId};
|
||||
@override
|
||||
SubsonicSourceOption map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
SubsonicSetting map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return SubsonicSourceOption(
|
||||
return SubsonicSetting(
|
||||
sourceId: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int,
|
||||
data['${effectivePrefix}source_id'],
|
||||
)!,
|
||||
address: SubsonicSourceOptions.$converteraddress.fromSql(
|
||||
address: SubsonicSettings.$converteraddress.fromSql(
|
||||
attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}address'],
|
||||
@ -446,8 +446,8 @@ class SubsonicSourceOptions extends Table
|
||||
}
|
||||
|
||||
@override
|
||||
SubsonicSourceOptions createAlias(String alias) {
|
||||
return SubsonicSourceOptions(attachedDatabase, alias);
|
||||
SubsonicSettings createAlias(String alias) {
|
||||
return SubsonicSettings(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static TypeConverter<Uri, String> $converteraddress = const UriConverter();
|
||||
@ -459,14 +459,13 @@ class SubsonicSourceOptions extends Table
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class SubsonicSourceOption extends DataClass
|
||||
implements Insertable<SubsonicSourceOption> {
|
||||
class SubsonicSetting extends DataClass implements Insertable<SubsonicSetting> {
|
||||
final int sourceId;
|
||||
final Uri address;
|
||||
final String username;
|
||||
final String password;
|
||||
final bool useTokenAuth;
|
||||
const SubsonicSourceOption({
|
||||
const SubsonicSetting({
|
||||
required this.sourceId,
|
||||
required this.address,
|
||||
required this.username,
|
||||
@ -479,7 +478,7 @@ class SubsonicSourceOption extends DataClass
|
||||
map['source_id'] = Variable<int>(sourceId);
|
||||
{
|
||||
map['address'] = Variable<String>(
|
||||
SubsonicSourceOptions.$converteraddress.toSql(address),
|
||||
SubsonicSettings.$converteraddress.toSql(address),
|
||||
);
|
||||
}
|
||||
map['username'] = Variable<String>(username);
|
||||
@ -488,8 +487,8 @@ class SubsonicSourceOption extends DataClass
|
||||
return map;
|
||||
}
|
||||
|
||||
SubsonicSourceOptionsCompanion toCompanion(bool nullToAbsent) {
|
||||
return SubsonicSourceOptionsCompanion(
|
||||
SubsonicSettingsCompanion toCompanion(bool nullToAbsent) {
|
||||
return SubsonicSettingsCompanion(
|
||||
sourceId: Value(sourceId),
|
||||
address: Value(address),
|
||||
username: Value(username),
|
||||
@ -498,12 +497,12 @@ class SubsonicSourceOption extends DataClass
|
||||
);
|
||||
}
|
||||
|
||||
factory SubsonicSourceOption.fromJson(
|
||||
factory SubsonicSetting.fromJson(
|
||||
Map<String, dynamic> json, {
|
||||
ValueSerializer? serializer,
|
||||
}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return SubsonicSourceOption(
|
||||
return SubsonicSetting(
|
||||
sourceId: serializer.fromJson<int>(json['source_id']),
|
||||
address: serializer.fromJson<Uri>(json['address']),
|
||||
username: serializer.fromJson<String>(json['username']),
|
||||
@ -523,21 +522,21 @@ class SubsonicSourceOption extends DataClass
|
||||
};
|
||||
}
|
||||
|
||||
SubsonicSourceOption copyWith({
|
||||
SubsonicSetting copyWith({
|
||||
int? sourceId,
|
||||
Uri? address,
|
||||
String? username,
|
||||
String? password,
|
||||
bool? useTokenAuth,
|
||||
}) => SubsonicSourceOption(
|
||||
}) => SubsonicSetting(
|
||||
sourceId: sourceId ?? this.sourceId,
|
||||
address: address ?? this.address,
|
||||
username: username ?? this.username,
|
||||
password: password ?? this.password,
|
||||
useTokenAuth: useTokenAuth ?? this.useTokenAuth,
|
||||
);
|
||||
SubsonicSourceOption copyWithCompanion(SubsonicSourceOptionsCompanion data) {
|
||||
return SubsonicSourceOption(
|
||||
SubsonicSetting copyWithCompanion(SubsonicSettingsCompanion data) {
|
||||
return SubsonicSetting(
|
||||
sourceId: data.sourceId.present ? data.sourceId.value : this.sourceId,
|
||||
address: data.address.present ? data.address.value : this.address,
|
||||
username: data.username.present ? data.username.value : this.username,
|
||||
@ -550,7 +549,7 @@ class SubsonicSourceOption extends DataClass
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('SubsonicSourceOption(')
|
||||
return (StringBuffer('SubsonicSetting(')
|
||||
..write('sourceId: $sourceId, ')
|
||||
..write('address: $address, ')
|
||||
..write('username: $username, ')
|
||||
@ -566,7 +565,7 @@ class SubsonicSourceOption extends DataClass
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is SubsonicSourceOption &&
|
||||
(other is SubsonicSetting &&
|
||||
other.sourceId == this.sourceId &&
|
||||
other.address == this.address &&
|
||||
other.username == this.username &&
|
||||
@ -574,21 +573,20 @@ class SubsonicSourceOption extends DataClass
|
||||
other.useTokenAuth == this.useTokenAuth);
|
||||
}
|
||||
|
||||
class SubsonicSourceOptionsCompanion
|
||||
extends UpdateCompanion<SubsonicSourceOption> {
|
||||
class SubsonicSettingsCompanion extends UpdateCompanion<SubsonicSetting> {
|
||||
final Value<int> sourceId;
|
||||
final Value<Uri> address;
|
||||
final Value<String> username;
|
||||
final Value<String> password;
|
||||
final Value<bool> useTokenAuth;
|
||||
const SubsonicSourceOptionsCompanion({
|
||||
const SubsonicSettingsCompanion({
|
||||
this.sourceId = const Value.absent(),
|
||||
this.address = const Value.absent(),
|
||||
this.username = const Value.absent(),
|
||||
this.password = const Value.absent(),
|
||||
this.useTokenAuth = const Value.absent(),
|
||||
});
|
||||
SubsonicSourceOptionsCompanion.insert({
|
||||
SubsonicSettingsCompanion.insert({
|
||||
this.sourceId = const Value.absent(),
|
||||
required Uri address,
|
||||
required String username,
|
||||
@ -597,7 +595,7 @@ class SubsonicSourceOptionsCompanion
|
||||
}) : address = Value(address),
|
||||
username = Value(username),
|
||||
password = Value(password);
|
||||
static Insertable<SubsonicSourceOption> custom({
|
||||
static Insertable<SubsonicSetting> custom({
|
||||
Expression<int>? sourceId,
|
||||
Expression<String>? address,
|
||||
Expression<String>? username,
|
||||
@ -613,14 +611,14 @@ class SubsonicSourceOptionsCompanion
|
||||
});
|
||||
}
|
||||
|
||||
SubsonicSourceOptionsCompanion copyWith({
|
||||
SubsonicSettingsCompanion copyWith({
|
||||
Value<int>? sourceId,
|
||||
Value<Uri>? address,
|
||||
Value<String>? username,
|
||||
Value<String>? password,
|
||||
Value<bool>? useTokenAuth,
|
||||
}) {
|
||||
return SubsonicSourceOptionsCompanion(
|
||||
return SubsonicSettingsCompanion(
|
||||
sourceId: sourceId ?? this.sourceId,
|
||||
address: address ?? this.address,
|
||||
username: username ?? this.username,
|
||||
@ -637,7 +635,7 @@ class SubsonicSourceOptionsCompanion
|
||||
}
|
||||
if (address.present) {
|
||||
map['address'] = Variable<String>(
|
||||
SubsonicSourceOptions.$converteraddress.toSql(address.value),
|
||||
SubsonicSettings.$converteraddress.toSql(address.value),
|
||||
);
|
||||
}
|
||||
if (username.present) {
|
||||
@ -654,7 +652,7 @@ class SubsonicSourceOptionsCompanion
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('SubsonicSourceOptionsCompanion(')
|
||||
return (StringBuffer('SubsonicSettingsCompanion(')
|
||||
..write('sourceId: $sourceId, ')
|
||||
..write('address: $address, ')
|
||||
..write('username: $username, ')
|
||||
@ -2386,8 +2384,7 @@ abstract class _$SubtracksDatabase extends GeneratedDatabase {
|
||||
_$SubtracksDatabase(QueryExecutor e) : super(e);
|
||||
$SubtracksDatabaseManager get managers => $SubtracksDatabaseManager(this);
|
||||
late final Sources sources = Sources(this);
|
||||
late final SubsonicSourceOptions subsonicSourceOptions =
|
||||
SubsonicSourceOptions(this);
|
||||
late final SubsonicSettings subsonicSettings = SubsonicSettings(this);
|
||||
late final Artists artists = Artists(this);
|
||||
late final Index artistsSourceId = Index(
|
||||
'artists_source_id',
|
||||
@ -2431,7 +2428,7 @@ abstract class _$SubtracksDatabase extends GeneratedDatabase {
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities => [
|
||||
sources,
|
||||
subsonicSourceOptions,
|
||||
subsonicSettings,
|
||||
artists,
|
||||
artistsSourceId,
|
||||
albums,
|
||||
@ -2453,7 +2450,7 @@ abstract class _$SubtracksDatabase extends GeneratedDatabase {
|
||||
'sources',
|
||||
limitUpdateKind: UpdateKind.delete,
|
||||
),
|
||||
result: [TableUpdate('subsonic_source_options', kind: UpdateKind.delete)],
|
||||
result: [TableUpdate('subsonic_settings', kind: UpdateKind.delete)],
|
||||
),
|
||||
WritePropagation(
|
||||
on: TableUpdateQuery.onTableName(
|
||||
@ -2660,16 +2657,16 @@ typedef $SourcesProcessedTableManager =
|
||||
Source,
|
||||
PrefetchHooks Function()
|
||||
>;
|
||||
typedef $SubsonicSourceOptionsCreateCompanionBuilder =
|
||||
SubsonicSourceOptionsCompanion Function({
|
||||
typedef $SubsonicSettingsCreateCompanionBuilder =
|
||||
SubsonicSettingsCompanion Function({
|
||||
Value<int> sourceId,
|
||||
required Uri address,
|
||||
required String username,
|
||||
required String password,
|
||||
Value<bool> useTokenAuth,
|
||||
});
|
||||
typedef $SubsonicSourceOptionsUpdateCompanionBuilder =
|
||||
SubsonicSourceOptionsCompanion Function({
|
||||
typedef $SubsonicSettingsUpdateCompanionBuilder =
|
||||
SubsonicSettingsCompanion Function({
|
||||
Value<int> sourceId,
|
||||
Value<Uri> address,
|
||||
Value<String> username,
|
||||
@ -2677,9 +2674,9 @@ typedef $SubsonicSourceOptionsUpdateCompanionBuilder =
|
||||
Value<bool> useTokenAuth,
|
||||
});
|
||||
|
||||
class $SubsonicSourceOptionsFilterComposer
|
||||
extends Composer<_$SubtracksDatabase, SubsonicSourceOptions> {
|
||||
$SubsonicSourceOptionsFilterComposer({
|
||||
class $SubsonicSettingsFilterComposer
|
||||
extends Composer<_$SubtracksDatabase, SubsonicSettings> {
|
||||
$SubsonicSettingsFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
@ -2713,9 +2710,9 @@ class $SubsonicSourceOptionsFilterComposer
|
||||
);
|
||||
}
|
||||
|
||||
class $SubsonicSourceOptionsOrderingComposer
|
||||
extends Composer<_$SubtracksDatabase, SubsonicSourceOptions> {
|
||||
$SubsonicSourceOptionsOrderingComposer({
|
||||
class $SubsonicSettingsOrderingComposer
|
||||
extends Composer<_$SubtracksDatabase, SubsonicSettings> {
|
||||
$SubsonicSettingsOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
@ -2748,9 +2745,9 @@ class $SubsonicSourceOptionsOrderingComposer
|
||||
);
|
||||
}
|
||||
|
||||
class $SubsonicSourceOptionsAnnotationComposer
|
||||
extends Composer<_$SubtracksDatabase, SubsonicSourceOptions> {
|
||||
$SubsonicSourceOptionsAnnotationComposer({
|
||||
class $SubsonicSettingsAnnotationComposer
|
||||
extends Composer<_$SubtracksDatabase, SubsonicSettings> {
|
||||
$SubsonicSettingsAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
@ -2775,41 +2772,39 @@ class $SubsonicSourceOptionsAnnotationComposer
|
||||
);
|
||||
}
|
||||
|
||||
class $SubsonicSourceOptionsTableManager
|
||||
class $SubsonicSettingsTableManager
|
||||
extends
|
||||
RootTableManager<
|
||||
_$SubtracksDatabase,
|
||||
SubsonicSourceOptions,
|
||||
SubsonicSourceOption,
|
||||
$SubsonicSourceOptionsFilterComposer,
|
||||
$SubsonicSourceOptionsOrderingComposer,
|
||||
$SubsonicSourceOptionsAnnotationComposer,
|
||||
$SubsonicSourceOptionsCreateCompanionBuilder,
|
||||
$SubsonicSourceOptionsUpdateCompanionBuilder,
|
||||
SubsonicSettings,
|
||||
SubsonicSetting,
|
||||
$SubsonicSettingsFilterComposer,
|
||||
$SubsonicSettingsOrderingComposer,
|
||||
$SubsonicSettingsAnnotationComposer,
|
||||
$SubsonicSettingsCreateCompanionBuilder,
|
||||
$SubsonicSettingsUpdateCompanionBuilder,
|
||||
(
|
||||
SubsonicSourceOption,
|
||||
SubsonicSetting,
|
||||
BaseReferences<
|
||||
_$SubtracksDatabase,
|
||||
SubsonicSourceOptions,
|
||||
SubsonicSourceOption
|
||||
SubsonicSettings,
|
||||
SubsonicSetting
|
||||
>,
|
||||
),
|
||||
SubsonicSourceOption,
|
||||
SubsonicSetting,
|
||||
PrefetchHooks Function()
|
||||
> {
|
||||
$SubsonicSourceOptionsTableManager(
|
||||
_$SubtracksDatabase db,
|
||||
SubsonicSourceOptions table,
|
||||
) : super(
|
||||
$SubsonicSettingsTableManager(_$SubtracksDatabase db, SubsonicSettings table)
|
||||
: super(
|
||||
TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
$SubsonicSourceOptionsFilterComposer($db: db, $table: table),
|
||||
$SubsonicSettingsFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
$SubsonicSourceOptionsOrderingComposer($db: db, $table: table),
|
||||
$SubsonicSettingsOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
$SubsonicSourceOptionsAnnotationComposer($db: db, $table: table),
|
||||
$SubsonicSettingsAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback:
|
||||
({
|
||||
Value<int> sourceId = const Value.absent(),
|
||||
@ -2817,7 +2812,7 @@ class $SubsonicSourceOptionsTableManager
|
||||
Value<String> username = const Value.absent(),
|
||||
Value<String> password = const Value.absent(),
|
||||
Value<bool> useTokenAuth = const Value.absent(),
|
||||
}) => SubsonicSourceOptionsCompanion(
|
||||
}) => SubsonicSettingsCompanion(
|
||||
sourceId: sourceId,
|
||||
address: address,
|
||||
username: username,
|
||||
@ -2831,7 +2826,7 @@ class $SubsonicSourceOptionsTableManager
|
||||
required String username,
|
||||
required String password,
|
||||
Value<bool> useTokenAuth = const Value.absent(),
|
||||
}) => SubsonicSourceOptionsCompanion.insert(
|
||||
}) => SubsonicSettingsCompanion.insert(
|
||||
sourceId: sourceId,
|
||||
address: address,
|
||||
username: username,
|
||||
@ -2846,25 +2841,21 @@ class $SubsonicSourceOptionsTableManager
|
||||
);
|
||||
}
|
||||
|
||||
typedef $SubsonicSourceOptionsProcessedTableManager =
|
||||
typedef $SubsonicSettingsProcessedTableManager =
|
||||
ProcessedTableManager<
|
||||
_$SubtracksDatabase,
|
||||
SubsonicSourceOptions,
|
||||
SubsonicSourceOption,
|
||||
$SubsonicSourceOptionsFilterComposer,
|
||||
$SubsonicSourceOptionsOrderingComposer,
|
||||
$SubsonicSourceOptionsAnnotationComposer,
|
||||
$SubsonicSourceOptionsCreateCompanionBuilder,
|
||||
$SubsonicSourceOptionsUpdateCompanionBuilder,
|
||||
SubsonicSettings,
|
||||
SubsonicSetting,
|
||||
$SubsonicSettingsFilterComposer,
|
||||
$SubsonicSettingsOrderingComposer,
|
||||
$SubsonicSettingsAnnotationComposer,
|
||||
$SubsonicSettingsCreateCompanionBuilder,
|
||||
$SubsonicSettingsUpdateCompanionBuilder,
|
||||
(
|
||||
SubsonicSourceOption,
|
||||
BaseReferences<
|
||||
_$SubtracksDatabase,
|
||||
SubsonicSourceOptions,
|
||||
SubsonicSourceOption
|
||||
>,
|
||||
SubsonicSetting,
|
||||
BaseReferences<_$SubtracksDatabase, SubsonicSettings, SubsonicSetting>,
|
||||
),
|
||||
SubsonicSourceOption,
|
||||
SubsonicSetting,
|
||||
PrefetchHooks Function()
|
||||
>;
|
||||
typedef $ArtistsCreateCompanionBuilder =
|
||||
@ -4137,8 +4128,8 @@ class $SubtracksDatabaseManager {
|
||||
final _$SubtracksDatabase _db;
|
||||
$SubtracksDatabaseManager(this._db);
|
||||
$SourcesTableManager get sources => $SourcesTableManager(_db, _db.sources);
|
||||
$SubsonicSourceOptionsTableManager get subsonicSourceOptions =>
|
||||
$SubsonicSourceOptionsTableManager(_db, _db.subsonicSourceOptions);
|
||||
$SubsonicSettingsTableManager get subsonicSettings =>
|
||||
$SubsonicSettingsTableManager(_db, _db.subsonicSettings);
|
||||
$ArtistsTableManager get artists => $ArtistsTableManager(_db, _db.artists);
|
||||
$AlbumsTableManager get albums => $AlbumsTableManager(_db, _db.albums);
|
||||
$PlaylistsTableManager get playlists =>
|
||||
|
||||
@ -51,7 +51,7 @@ CREATE TABLE sources(
|
||||
created_at DATETIME NOT NULL DEFAULT (strftime('%s', CURRENT_TIMESTAMP))
|
||||
);
|
||||
|
||||
CREATE TABLE subsonic_source_options(
|
||||
CREATE TABLE subsonic_settings(
|
||||
source_id INT NOT NULL PRIMARY KEY,
|
||||
address TEXT NOT NULL MAPPED BY `const UriConverter()`,
|
||||
username TEXT NOT NULL,
|
||||
|
||||
@ -1,8 +1,34 @@
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import 'app/router.dart';
|
||||
import 'database/database.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
final db = SubtracksDatabase();
|
||||
await db
|
||||
.into(db.sources)
|
||||
.insertOnConflictUpdate(
|
||||
SourcesCompanion.insert(
|
||||
id: Value(1),
|
||||
name: 'test navidrome',
|
||||
),
|
||||
);
|
||||
await db
|
||||
.into(db.subsonicSettings)
|
||||
.insertOnConflictUpdate(
|
||||
SubsonicSettingsCompanion.insert(
|
||||
sourceId: Value(1),
|
||||
address: Uri.parse('http://10.0.2.2:4533'),
|
||||
username: 'admin',
|
||||
password: 'password',
|
||||
useTokenAuth: Value(true),
|
||||
),
|
||||
);
|
||||
|
||||
void main() {
|
||||
runApp(const MainApp());
|
||||
}
|
||||
|
||||
@ -11,11 +37,13 @@ class MainApp extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp.router(
|
||||
return ProviderScope(
|
||||
child: MaterialApp.router(
|
||||
themeMode: ThemeMode.dark,
|
||||
darkTheme: ThemeData.dark(useMaterial3: true),
|
||||
debugShowCheckedModeBanner: false,
|
||||
routerConfig: router,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,5 +12,5 @@ abstract class MusicSource {
|
||||
Uri streamUri(String songId);
|
||||
Uri downloadUri(String songId);
|
||||
|
||||
Uri coverArtUri(String coverArtId, {bool thumbnail = true});
|
||||
Uri coverArtUri(String coverArt, {bool thumbnail = false});
|
||||
}
|
||||
|
||||
@ -151,8 +151,8 @@ class SubsonicSource implements MusicSource {
|
||||
}
|
||||
|
||||
@override
|
||||
Uri coverArtUri(String id, {bool thumbnail = true}) {
|
||||
final opts = {'id': id};
|
||||
Uri coverArtUri(String coverArt, {bool thumbnail = false}) {
|
||||
final opts = {'id': coverArt};
|
||||
if (thumbnail) {
|
||||
opts['size'] = 256.toString();
|
||||
}
|
||||
|
||||
6
lib/util/http.dart
Normal file
6
lib/util/http.dart
Normal file
@ -0,0 +1,6 @@
|
||||
import 'package:http/http.dart';
|
||||
|
||||
class SubtracksHttpClient extends BaseClient {
|
||||
@override
|
||||
Future<StreamedResponse> send(BaseRequest request) => request.send();
|
||||
}
|
||||
@ -625,7 +625,7 @@ packages:
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
octo_image:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: octo_image
|
||||
sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
|
||||
|
||||
@ -24,6 +24,7 @@ dependencies:
|
||||
infinite_scroll_pagination: ^5.1.1
|
||||
json_annotation: ^4.9.0
|
||||
material_symbols_icons: ^4.2874.0
|
||||
octo_image: ^2.1.0
|
||||
path: ^1.9.1
|
||||
path_provider: ^2.1.5
|
||||
pool: ^1.5.2
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user