active source switching and reactivity

This commit is contained in:
austinried 2025-11-22 11:33:40 +09:00
parent de9bc98044
commit 914ec77ce0
11 changed files with 271 additions and 773 deletions

View File

@ -1,5 +1,5 @@
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';
@ -23,15 +23,17 @@ class AlbumsGrid extends HookConsumerWidget {
final controller = usePagingController<int, Album>(
getNextPageKey: (state) =>
state.lastPageIsEmpty ? null : state.nextIntPageKey,
fetchPage: (pageKey) async {
final query = db.albums.select()
..where((f) => f.sourceId.equals(sourceId))
..limit(kPageSize, offset: (pageKey - 1) * kPageSize);
return await query.get();
},
fetchPage: (pageKey) => db.libraryDao.listAlbums(
limit: kPageSize,
offset: (pageKey - 1) * kPageSize,
),
);
useEffect(() {
controller.refresh();
return;
}, [sourceId]);
return PagingListener(
controller: controller,
builder: (context, state, fetchNextPage) {

View File

@ -1,5 +1,5 @@
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';
@ -25,36 +25,17 @@ class ArtistsList extends HookConsumerWidget {
final controller = usePagingController<int, _ArtistItem>(
getNextPageKey: (state) =>
state.lastPageIsEmpty ? null : state.nextIntPageKey,
fetchPage: (pageKey) async {
final albumCount = db.albums.id.count();
final query =
db.artists.select().join([
leftOuterJoin(
db.albums,
db.albums.artistId.equalsExp(db.artists.id),
),
])
..addColumns([albumCount])
..where(
db.artists.sourceId.equals(sourceId) &
db.albums.sourceId.equals(sourceId),
)
..groupBy([db.artists.sourceId, db.artists.id])
..orderBy([OrderingTerm.asc(db.artists.name)])
..limit(kPageSize, offset: (pageKey - 1) * kPageSize);
return (await query.get())
.map(
(row) => (
artist: row.readTable(db.artists),
albumCount: row.read(albumCount),
),
)
.toList();
},
fetchPage: (pageKey) => db.libraryDao.listArtists(
limit: kPageSize,
offset: (pageKey - 1) * kPageSize,
),
);
useEffect(() {
controller.refresh();
return;
}, [sourceId]);
return PagingListener(
controller: controller,
builder: (context, state, fetchNextPage) {

View File

@ -1,9 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../../l10n/generated/app_localizations.dart';
import '../state/database.dart';
import '../state/source.dart';
import '../ui/text.dart';
const kHorizontalPadding = 16.0;
const kHorizontalPadding = 18.0;
class SettingsScreen extends HookConsumerWidget {
const SettingsScreen({super.key});
@ -13,11 +17,14 @@ class SettingsScreen extends HookConsumerWidget {
final l = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: TextH1(l.navigationTabsSettings),
),
body: ListView(
children: [
const SizedBox(height: 96),
// const SizedBox(height: 96),
_SectionHeader(l.settingsServersName),
// const _Sources(),
const _Sources(),
// _SectionHeader(l.settingsNetworkName),
// const _Network(),
// _SectionHeader(l.settingsAboutName),
@ -29,10 +36,10 @@ class SettingsScreen extends HookConsumerWidget {
}
class _Section extends StatelessWidget {
final List<Widget> children;
const _Section({required this.children});
final List<Widget> children;
@override
Widget build(BuildContext context) {
return Column(
@ -46,27 +53,22 @@ class _Section extends StatelessWidget {
}
class _SectionHeader extends StatelessWidget {
final String title;
const _SectionHeader(this.title);
final String title;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
children: [
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: kHorizontalPadding),
child: Text(
title,
style: theme.textTheme.displaySmall,
),
child: TextH2(title),
),
),
const SizedBox(height: 12),
],
);
}
@ -346,79 +348,66 @@ class _SectionHeader extends StatelessWidget {
// }
// }
// class _Sources extends HookConsumerWidget {
// const _Sources();
class _Sources extends HookConsumerWidget {
const _Sources();
// @override
// Widget build(BuildContext context, WidgetRef ref) {
// final sources = ref.watch(
// settingsServiceProvider.select(
// (value) => value.sources,
// ),
// );
// final activeSource = ref.watch(
// settingsServiceProvider.select(
// (value) => value.activeSource,
// ),
// );
@override
Widget build(BuildContext context, WidgetRef ref) {
final db = ref.watch(databaseProvider);
final activeSourceId = ref.watch(sourceIdProvider);
final sources = useStream(db.sourcesDao.listSources()).data;
// final l = AppLocalizations.of(context);
final l = AppLocalizations.of(context);
// return _Section(
// children: [
// for (var source in sources)
// RadioListTile<int>(
// value: source.id,
// groupValue: activeSource?.id,
// onChanged: (value) {
// ref
// .read(settingsServiceProvider.notifier)
// .setActiveSource(source.id);
// },
// title: Text(source.name),
// subtitle: Text(
// source.address.toString(),
// maxLines: 1,
// softWrap: false,
// overflow: TextOverflow.fade,
// ),
// secondary: IconButton(
// icon: const Icon(Icons.edit_rounded),
// onPressed: () {
// context.pushRoute(SourceRoute(id: source.id));
// },
// ),
// ),
// const SizedBox(height: 8),
// Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// OutlinedButton.icon(
// icon: const Icon(Icons.add_rounded),
// label: Text(l.settingsServersActionsAdd),
// onPressed: () {
// context.pushRoute(SourceRoute());
// },
// ),
// ],
// ),
// // TODO: remove
// if (kDebugMode)
// Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// OutlinedButton.icon(
// icon: const Icon(Icons.add_rounded),
// label: const Text('Add TEST'),
// onPressed: () {
// ref
// .read(settingsServiceProvider.notifier)
// .addTestSource('TEST');
// },
// ),
// ],
// ),
// ],
// );
// }
// }
if (sources == null) {
return Container();
}
return _Section(
children: [
RadioGroup<int>(
groupValue: activeSourceId,
onChanged: (value) {
if (value != null) {
db.sourcesDao.setActiveSource(value);
}
},
child: Column(
children: [
for (final (source, settings) in sources)
RadioListTile<int>(
value: source.id,
title: Text(source.name),
subtitle: Text(
settings.address.toString(),
maxLines: 1,
softWrap: false,
overflow: TextOverflow.fade,
),
secondary: IconButton(
icon: const Icon(Icons.edit_rounded),
onPressed: () {
// context.pushRoute(SourceRoute(id: source.id));
},
),
),
],
),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
OutlinedButton.icon(
icon: const Icon(Icons.add_rounded),
label: Text(l.settingsServersActionsAdd),
onPressed: () {
// context.pushRoute(SourceRoute());
},
),
],
),
],
);
}
}

View File

@ -11,8 +11,8 @@ final databaseInitializer = FutureProvider<SubtracksDatabase>((ref) async {
.insertOnConflictUpdate(
SourcesCompanion.insert(
id: Value(1),
name: 'test navidrome',
isActive: Value(true),
name: 'test subsonic',
// isActive: Value(true),
),
);
await db
@ -23,9 +23,26 @@ final databaseInitializer = FutureProvider<SubtracksDatabase>((ref) async {
address: Uri.parse('http://demo.subsonic.org'),
username: 'guest1',
password: 'guest',
// address: Uri.parse('http://10.0.2.2:4533'),
// username: 'admin',
// password: 'password',
useTokenAuth: Value(true),
),
);
await db
.into(db.sources)
.insertOnConflictUpdate(
SourcesCompanion.insert(
id: Value(2),
name: 'test navidrome',
// isActive: Value(null),
),
);
await db
.into(db.subsonicSettings)
.insertOnConflictUpdate(
SubsonicSettingsCompanion.insert(
sourceId: Value(2),
address: Uri.parse('http://10.0.2.2:4533'),
username: 'admin',
password: 'password',
useTokenAuth: Value(true),
),
);

View File

@ -10,17 +10,17 @@ final activeSourceInitializer = StreamProvider<(int, SubsonicSource)>((
) async* {
final db = ref.watch(databaseProvider);
final activeSource = db.managers.sources
.filter((f) => f.isActive.equals(true))
.watchSingle();
final activeSource = db.sourcesDao.activeSourceId().watchSingle();
await for (final source in activeSource) {
final sourceId = source.read(db.sources.id)!;
final subsonicSettings = await db.managers.subsonicSettings
.filter((f) => f.sourceId.equals(source.id))
.filter((f) => f.sourceId.equals(sourceId))
.getSingle();
yield (
source.id,
sourceId,
SubsonicSource(
SubsonicClient(
http: SubtracksHttpClient(),

View File

@ -0,0 +1,63 @@
import 'package:drift/drift.dart';
import '../../sources/models.dart' as models;
import '../database.dart';
part 'library_dao.g.dart';
@DriftAccessor(include: {'../tables.drift'})
class LibraryDao extends DatabaseAccessor<SubtracksDatabase>
with _$LibraryDaoMixin {
LibraryDao(super.db);
Future<List<models.Album>> listAlbums({
required int limit,
required int offset,
}) {
final query = albums.select()
..where(
(f) => f.sourceId.equalsExp(
subqueryExpression(db.sourcesDao.activeSourceId()),
),
)
..limit(limit, offset: offset);
return query.get();
}
Future<List<({models.Artist artist, int albumCount})>> listArtists({
required int limit,
required int offset,
}) async {
final albumCount = albums.id.count();
final query =
artists.select().join([
leftOuterJoin(
albums,
albums.artistId.equalsExp(artists.id),
),
])
..addColumns([albumCount])
..where(
artists.sourceId.equalsExp(
subqueryExpression(db.sourcesDao.activeSourceId()),
) &
albums.sourceId.equalsExp(
subqueryExpression(db.sourcesDao.activeSourceId()),
),
)
..groupBy([artists.sourceId, artists.id])
..orderBy([OrderingTerm.asc(artists.name)])
..limit(limit, offset: offset);
return (await query.get())
.map(
(row) => (
artist: row.readTable(artists),
albumCount: row.read(albumCount) ?? 0,
),
)
.toList();
}
}

View File

@ -0,0 +1,14 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'library_dao.dart';
// ignore_for_file: type=lint
mixin _$LibraryDaoMixin on DatabaseAccessor<SubtracksDatabase> {
Sources get sources => attachedDatabase.sources;
SubsonicSettings get subsonicSettings => attachedDatabase.subsonicSettings;
Artists get artists => attachedDatabase.artists;
Albums get albums => attachedDatabase.albums;
Playlists get playlists => attachedDatabase.playlists;
PlaylistSongs get playlistSongs => attachedDatabase.playlistSongs;
Songs get songs => attachedDatabase.songs;
}

View File

@ -0,0 +1,46 @@
import 'package:drift/drift.dart';
import '../database.dart';
part 'sources_dao.g.dart';
@DriftAccessor(include: {'../tables.drift'})
class SourcesDao extends DatabaseAccessor<SubtracksDatabase>
with _$SourcesDaoMixin {
SourcesDao(super.db);
JoinedSelectStatement<Sources, Source> activeSourceId() {
return selectOnly(sources)
..addColumns([sources.id])
..where(sources.isActive.equals(true));
}
Stream<List<(Source, SubsonicSetting)>> listSources() {
final query = select(sources).join([
innerJoin(
subsonicSettings,
sources.id.equalsExp(subsonicSettings.sourceId),
),
]);
return query.watch().map(
(rows) => rows
.map(
(row) => (
row.readTable(sources),
row.readTable(subsonicSettings),
),
)
.toList(),
);
}
Future<void> setActiveSource(int id) async {
await transaction(() async {
await db.managers.sources.update((o) => o(isActive: Value(null)));
await db.managers.sources
.filter((f) => f.id.equals(id))
.update((o) => o(isActive: Value(true)));
});
}
}

View File

@ -0,0 +1,14 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'sources_dao.dart';
// ignore_for_file: type=lint
mixin _$SourcesDaoMixin on DatabaseAccessor<SubtracksDatabase> {
Sources get sources => attachedDatabase.sources;
SubsonicSettings get subsonicSettings => attachedDatabase.subsonicSettings;
Artists get artists => attachedDatabase.artists;
Albums get albums => attachedDatabase.albums;
Playlists get playlists => attachedDatabase.playlists;
PlaylistSongs get playlistSongs => attachedDatabase.playlistSongs;
Songs get songs => attachedDatabase.songs;
}

View File

@ -5,6 +5,8 @@ import 'package:path_provider/path_provider.dart';
import '../sources/models.dart' as models;
import 'converters.dart';
import 'dao/library_dao.dart';
import 'dao/sources_dao.dart';
part 'database.g.dart';
@ -12,7 +14,13 @@ part 'database.g.dart';
// https://www.sqlite.org/limits.html
const kSqliteMaxVariableNumber = 32766;
@DriftDatabase(include: {'tables.drift'})
@DriftDatabase(
include: {'tables.drift'},
daos: [
SourcesDao,
LibraryDao,
],
)
class SubtracksDatabase extends _$SubtracksDatabase {
SubtracksDatabase([QueryExecutor? executor])
: super(executor ?? _openConnection());
@ -40,395 +48,6 @@ class SubtracksDatabase extends _$SubtracksDatabase {
},
);
}
// MultiSelectable<Album> albumsList(int sourceId, ListQuery opt) {
// return filterAlbums(
// (_) => _filterPredicate('albums', sourceId, opt),
// (_) => _filterOrderBy(opt),
// (_) => _filterLimit(opt),
// );
// }
// MultiSelectable<Album> albumsListDownloaded(int sourceId, ListQuery opt) {
// return filterAlbumsDownloaded(
// (_, __) => _filterPredicate('albums', sourceId, opt),
// (_, __) => _filterOrderBy(opt),
// (_, __) => _filterLimit(opt),
// );
// }
// MultiSelectable<Artist> artistsList(int sourceId, ListQuery opt) {
// return filterArtists(
// (_) => _filterPredicate('artists', sourceId, opt),
// (_) => _filterOrderBy(opt),
// (_) => _filterLimit(opt),
// );
// }
// MultiSelectable<Artist> artistsListDownloaded(int sourceId, ListQuery opt) {
// return filterArtistsDownloaded(
// (_, __, ___) => _filterPredicate('artists', sourceId, opt),
// (_, __, ___) => _filterOrderBy(opt),
// (_, __, ___) => _filterLimit(opt),
// );
// }
// MultiSelectable<Playlist> playlistsList(int sourceId, ListQuery opt) {
// return filterPlaylists(
// (_) => _filterPredicate('playlists', sourceId, opt),
// (_) => _filterOrderBy(opt),
// (_) => _filterLimit(opt),
// );
// }
// MultiSelectable<Playlist> playlistsListDownloaded(
// int sourceId,
// ListQuery opt,
// ) {
// return filterPlaylistsDownloaded(
// (_, __, ___) => _filterPredicate('playlists', sourceId, opt),
// (_, __, ___) => _filterOrderBy(opt),
// (_, __, ___) => _filterLimit(opt),
// );
// }
// MultiSelectable<Song> songsList(int sourceId, ListQuery opt) {
// return filterSongs(
// (_) => _filterPredicate('songs', sourceId, opt),
// (_) => _filterOrderBy(opt),
// (_) => _filterLimit(opt),
// );
// }
// MultiSelectable<Song> songsListDownloaded(int sourceId, ListQuery opt) {
// return filterSongsDownloaded(
// (_) => _filterPredicate('songs', sourceId, opt),
// (_) => _filterOrderBy(opt),
// (_) => _filterLimit(opt),
// );
// }
// Expression<bool> _filterPredicate(String table, int sourceId, ListQuery opt) {
// return opt.filters
// .map((filter) => buildFilter<bool>(filter))
// .fold(
// CustomExpression('$table.source_id = $sourceId'),
// (previousValue, element) => previousValue & element,
// );
// }
// OrderBy _filterOrderBy(ListQuery opt) {
// return opt.sort != null
// ? OrderBy([_buildOrder(opt.sort!)])
// : const OrderBy.nothing();
// }
// Limit _filterLimit(ListQuery opt) {
// return Limit(opt.page.limit, opt.page.offset);
// }
// MultiSelectable<Song> albumSongsList(SourceId sid, ListQuery opt) {
// return listQuery(
// select(songs)..where(
// (tbl) => tbl.sourceId.equals(sid.sourceId) & tbl.albumId.equals(sid.id),
// ),
// opt,
// );
// }
// MultiSelectable<Song> songsByAlbumList(int sourceId, ListQuery opt) {
// return filterSongsByGenre(
// (_, __) => _filterPredicate('songs', sourceId, opt),
// (_, __) => _filterOrderBy(opt),
// (_, __) => _filterLimit(opt),
// );
// }
// MultiSelectable<Song> playlistSongsList(SourceId sid, ListQuery opt) {
// return listQueryJoined(
// select(songs).join([
// innerJoin(
// playlistSongs,
// playlistSongs.sourceId.equalsExp(songs.sourceId) &
// playlistSongs.songId.equalsExp(songs.id),
// useColumns: false,
// ),
// ])..where(
// playlistSongs.sourceId.equals(sid.sourceId) &
// playlistSongs.playlistId.equals(sid.id),
// ),
// opt,
// ).map((row) => row.readTable(songs));
// }
// Future<void> saveArtists(Iterable<ArtistsCompanion> artists) async {
// await batch((batch) {
// batch.insertAllOnConflictUpdate(this.artists, artists);
// });
// }
// Future<void> deleteArtistsNotIn(int sourceId, Set<String> ids) {
// return transaction(() async {
// final allIds =
// (await (selectOnly(artists)
// ..addColumns([artists.id])
// ..where(artists.sourceId.equals(sourceId)))
// .map((row) => row.read(artists.id))
// .get())
// .whereNotNull()
// .toSet();
// final downloadIds = (await artistIdsWithDownloadStatus(
// sourceId,
// ).get()).whereNotNull().toSet();
// final diff = allIds.difference(downloadIds).difference(ids);
// for (var slice in diff.slices(kSqliteMaxVariableNumber)) {
// await (delete(artists)..where(
// (tbl) => tbl.sourceId.equals(sourceId) & tbl.id.isIn(slice),
// ))
// .go();
// }
// });
// }
// Future<void> saveAlbums(Iterable<AlbumsCompanion> albums) async {
// await batch((batch) {
// batch.insertAllOnConflictUpdate(this.albums, albums);
// });
// }
// Future<void> deleteAlbumsNotIn(int sourceId, Set<String> ids) {
// return transaction(() async {
// final allIds =
// (await (selectOnly(albums)
// ..addColumns([albums.id])
// ..where(albums.sourceId.equals(sourceId)))
// .map((row) => row.read(albums.id))
// .get())
// .whereNotNull()
// .toSet();
// final downloadIds = (await albumIdsWithDownloadStatus(
// sourceId,
// ).get()).whereNotNull().toSet();
// final diff = allIds.difference(downloadIds).difference(ids);
// for (var slice in diff.slices(kSqliteMaxVariableNumber)) {
// await (delete(albums)..where(
// (tbl) => tbl.sourceId.equals(sourceId) & tbl.id.isIn(slice),
// ))
// .go();
// }
// });
// }
// Future<void> savePlaylists(
// Iterable<PlaylistWithSongsCompanion> playlistsWithSongs,
// ) async {
// final playlists = playlistsWithSongs.map((e) => e.playist);
// final playlistSongs = playlistsWithSongs.expand((e) => e.songs);
// final sourceId = playlists.first.sourceId.value;
// await (delete(this.playlistSongs)..where(
// (tbl) =>
// tbl.sourceId.equals(sourceId) &
// tbl.playlistId.isIn(playlists.map((e) => e.id.value)),
// ))
// .go();
// await batch((batch) {
// batch.insertAllOnConflictUpdate(this.playlists, playlists);
// batch.insertAllOnConflictUpdate(this.playlistSongs, playlistSongs);
// });
// }
// Future<void> deletePlaylistsNotIn(int sourceId, Set<String> ids) {
// return transaction(() async {
// final allIds =
// (await (selectOnly(playlists)
// ..addColumns([playlists.id])
// ..where(playlists.sourceId.equals(sourceId)))
// .map((row) => row.read(playlists.id))
// .get())
// .whereNotNull()
// .toSet();
// final downloadIds = (await playlistIdsWithDownloadStatus(
// sourceId,
// ).get()).whereNotNull().toSet();
// final diff = allIds.difference(downloadIds).difference(ids);
// for (var slice in diff.slices(kSqliteMaxVariableNumber)) {
// await (delete(playlists)..where(
// (tbl) => tbl.sourceId.equals(sourceId) & tbl.id.isIn(slice),
// ))
// .go();
// await (delete(playlistSongs)..where(
// (tbl) =>
// tbl.sourceId.equals(sourceId) & tbl.playlistId.isIn(slice),
// ))
// .go();
// }
// });
// }
// Future<void> savePlaylistSongs(
// int sourceId,
// List<String> ids,
// Iterable<PlaylistSongsCompanion> playlistSongs,
// ) async {
// await (delete(this.playlistSongs)..where(
// (tbl) => tbl.sourceId.equals(sourceId) & tbl.playlistId.isIn(ids),
// ))
// .go();
// await batch((batch) {
// batch.insertAllOnConflictUpdate(this.playlistSongs, playlistSongs);
// });
// }
// Future<void> saveSongs(Iterable<SongsCompanion> songs) async {
// await batch((batch) {
// batch.insertAllOnConflictUpdate(this.songs, songs);
// });
// }
// Future<void> deleteSongsNotIn(int sourceId, Set<String> ids) {
// return transaction(() async {
// final allIds =
// (await (selectOnly(songs)
// ..addColumns([songs.id])
// ..where(
// songs.sourceId.equals(sourceId) &
// songs.downloadFilePath.isNull() &
// songs.downloadTaskId.isNull(),
// ))
// .map((row) => row.read(songs.id))
// .get())
// .whereNotNull()
// .toSet();
// final diff = allIds.difference(ids);
// for (var slice in diff.slices(kSqliteMaxVariableNumber)) {
// await (delete(songs)..where(
// (tbl) => tbl.sourceId.equals(sourceId) & tbl.id.isIn(slice),
// ))
// .go();
// await (delete(playlistSongs)..where(
// (tbl) => tbl.sourceId.equals(sourceId) & tbl.songId.isIn(slice),
// ))
// .go();
// }
// });
// }
// Selectable<LastBottomNavStateData> getLastBottomNavState() {
// return select(lastBottomNavState)..where((tbl) => tbl.id.equals(1));
// }
// Future<void> saveLastBottomNavState(LastBottomNavStateData update) {
// return into(lastBottomNavState).insertOnConflictUpdate(update);
// }
// Selectable<LastLibraryStateData> getLastLibraryState() {
// return select(lastLibraryState)..where((tbl) => tbl.id.equals(1));
// }
// Future<void> saveLastLibraryState(LastLibraryStateData update) {
// return into(lastLibraryState).insertOnConflictUpdate(update);
// }
// Selectable<LastAudioStateData> getLastAudioState() {
// return select(lastAudioState)..where((tbl) => tbl.id.equals(1));
// }
// Future<void> saveLastAudioState(LastAudioStateCompanion update) {
// return into(lastAudioState).insertOnConflictUpdate(update);
// }
// Future<void> insertQueue(Iterable<QueueCompanion> songs) async {
// await batch((batch) {
// batch.insertAll(queue, songs);
// });
// }
// Future<void> clearQueue() async {
// await delete(queue).go();
// }
// Future<void> setCurrentTrack(int index) async {
// await transaction(() async {
// await (update(queue)..where((tbl) => tbl.index.equals(index).not()))
// .write(const QueueCompanion(currentTrack: Value(null)));
// await (update(queue)..where((tbl) => tbl.index.equals(index))).write(
// const QueueCompanion(currentTrack: Value(true)),
// );
// });
// }
// Future<void> createSource(
// SourcesCompanion source,
// SubsonicSourcesCompanion subsonic,
// ) async {
// await transaction(() async {
// final count = await sourcesCount().getSingle();
// if (count == 0) {
// source = source.copyWith(isActive: const Value(true));
// }
// final id = await into(sources).insert(source);
// subsonic = subsonic.copyWith(sourceId: Value(id));
// await into(subsonicSources).insert(subsonic);
// });
// }
// Future<void> updateSource(SubsonicSettings source) async {
// await transaction(() async {
// await into(sources).insertOnConflictUpdate(source.toSourceInsertable());
// await into(
// subsonicSources,
// ).insertOnConflictUpdate(source.toSubsonicInsertable());
// });
// }
// Future<void> deleteSource(int sourceId) async {
// await transaction(() async {
// await (delete(
// subsonicSources,
// )..where((tbl) => tbl.sourceId.equals(sourceId))).go();
// await (delete(sources)..where((tbl) => tbl.id.equals(sourceId))).go();
// await (delete(songs)..where((tbl) => tbl.sourceId.equals(sourceId))).go();
// await (delete(
// albums,
// )..where((tbl) => tbl.sourceId.equals(sourceId))).go();
// await (delete(
// artists,
// )..where((tbl) => tbl.sourceId.equals(sourceId))).go();
// await (delete(
// playlistSongs,
// )..where((tbl) => tbl.sourceId.equals(sourceId))).go();
// await (delete(
// playlists,
// )..where((tbl) => tbl.sourceId.equals(sourceId))).go();
// });
// }
// Future<void> setActiveSource(int id) async {
// await batch((batch) {
// batch.update(
// sources,
// const SourcesCompanion(isActive: Value(null)),
// where: (t) => t.id.isNotValue(id),
// );
// batch.update(
// sources,
// const SourcesCompanion(isActive: Value(true)),
// where: (t) => t.id.equals(id),
// );
// });
// }
// Future<void> updateSettings(AppSettingsCompanion settings) async {
// await into(appSettings).insertOnConflictUpdate(settings);
// }
}
extension ArtistToDb on models.Artist {
@ -495,252 +114,3 @@ extension PlaylistSongToDb on models.PlaylistSong {
position: position,
);
}
// LazyDatabase _openConnection() {
// return LazyDatabase(() async {
// final dbFolder = await getApplicationDocumentsDirectory();
// final file = File(p.join(dbFolder.path, 'subtracks.sqlite'));
// // return NativeDatabase.createInBackground(file, logStatements: true);
// return ErrorLoggingDatabase(
// NativeDatabase.createInBackground(file),
// (e, s) => log.severe('SQL error', e, s),
// );
// });
// }
// @Riverpod(keepAlive: true)
// SubtracksDatabase database(DatabaseRef ref) {
// return SubtracksDatabase();
// }
// OrderingTerm _buildOrder(SortBy sort) {
// OrderingMode? mode = sort.dir == SortDirection.asc
// ? OrderingMode.asc
// : OrderingMode.desc;
// return OrderingTerm(
// expression: CustomExpression(sort.column),
// mode: mode,
// );
// }
// SimpleSelectStatement<T, R> listQuery<T extends HasResultSet, R>(
// SimpleSelectStatement<T, R> query,
// ListQuery opt,
// ) {
// if (opt.page.limit > 0) {
// query.limit(opt.page.limit, offset: opt.page.offset);
// }
// if (opt.sort != null) {
// OrderingMode? mode = opt.sort != null && opt.sort!.dir == SortDirection.asc
// ? OrderingMode.asc
// : OrderingMode.desc;
// query.orderBy([
// (t) => OrderingTerm(
// expression: CustomExpression(opt.sort!.column),
// mode: mode,
// ),
// ]);
// }
// for (var filter in opt.filters) {
// query.where((tbl) => buildFilter(filter));
// }
// return query;
// }
// JoinedSelectStatement<T, R> listQueryJoined<T extends HasResultSet, R>(
// JoinedSelectStatement<T, R> query,
// ListQuery opt,
// ) {
// if (opt.page.limit > 0) {
// query.limit(opt.page.limit, offset: opt.page.offset);
// }
// if (opt.sort != null) {
// OrderingMode? mode = opt.sort != null && opt.sort!.dir == SortDirection.asc
// ? OrderingMode.asc
// : OrderingMode.desc;
// query.orderBy([
// OrderingTerm(
// expression: CustomExpression(opt.sort!.column),
// mode: mode,
// ),
// ]);
// }
// for (var filter in opt.filters) {
// query.where(buildFilter(filter));
// }
// return query;
// }
// CustomExpression<T> buildFilter<T extends Object>(
// FilterWith filter,
// ) {
// return filter.when(
// equals: (column, value, invert) => CustomExpression<T>(
// '$column ${invert ? '<>' : '='} \'$value\'',
// ),
// greaterThan: (column, value, orEquals) => CustomExpression<T>(
// '$column ${orEquals ? '>=' : '>'} $value',
// ),
// isNull: (column, invert) => CustomExpression<T>(
// '$column ${invert ? 'IS NOT' : 'IS'} NULL',
// ),
// betweenInt: (column, from, to) => CustomExpression<T>(
// '$column BETWEEN $from AND $to',
// ),
// isIn: (column, invert, values) => CustomExpression<T>(
// '$column ${invert ? 'NOT IN' : 'IN'} (${values.join(',')})',
// ),
// );
// }
// class AlbumSongsCompanion {
// final AlbumsCompanion album;
// final Iterable<SongsCompanion> songs;
// AlbumSongsCompanion(this.album, this.songs);
// }
// class ArtistAlbumsCompanion {
// final ArtistsCompanion artist;
// final Iterable<AlbumsCompanion> albums;
// ArtistAlbumsCompanion(this.artist, this.albums);
// }
// class PlaylistWithSongsCompanion {
// final PlaylistsCompanion playist;
// final Iterable<PlaylistSongsCompanion> songs;
// PlaylistWithSongsCompanion(this.playist, this.songs);
// }
// Future<void> saveArtist(
// SubtracksDatabase db,
// ArtistAlbumsCompanion artistAlbums,
// ) async {
// return db.background((db) async {
// final artist = artistAlbums.artist;
// final albums = artistAlbums.albums;
// await db.batch((batch) {
// batch.insertAllOnConflictUpdate(db.artists, [artist]);
// batch.insertAllOnConflictUpdate(db.albums, albums);
// // remove this artistId from albums not found in source
// // don't delete them since they coud have been moved to another artist
// // that we haven't synced yet
// final albumIds = {for (var a in albums) a.id.value};
// batch.update(
// db.albums,
// const AlbumsCompanion(artistId: Value(null)),
// where: (tbl) =>
// tbl.sourceId.equals(artist.sourceId.value) &
// tbl.artistId.equals(artist.id.value) &
// tbl.id.isNotIn(albumIds),
// );
// });
// });
// }
// Future<void> saveAlbum(
// SubtracksDatabase db,
// AlbumSongsCompanion albumSongs,
// ) async {
// return db.background((db) async {
// final album = albumSongs.album.copyWith(synced: Value(DateTime.now()));
// final songs = albumSongs.songs;
// final songIds = {for (var a in songs) a.id.value};
// final hardDeletedSongIds = (await (db.selectOnly(db.songs)
// ..addColumns([db.songs.id])
// ..where(
// db.songs.sourceId.equals(album.sourceId.value) &
// db.songs.albumId.equals(album.id.value) &
// db.songs.id.isNotIn(songIds) &
// db.songs.downloadFilePath.isNull() &
// db.songs.downloadTaskId.isNull(),
// ))
// .map((row) => row.read(db.songs.id))
// .get())
// .whereNotNull();
// await db.batch((batch) {
// batch.insertAllOnConflictUpdate(db.albums, [album]);
// batch.insertAllOnConflictUpdate(db.songs, songs);
// // soft delete songs that have been downloaded so that the user
// // can decide to keep or remove them later
// // TODO: add a setting to skip soft delete and just remove download too
// batch.update(
// db.songs,
// const SongsCompanion(isDeleted: Value(true)),
// where: (tbl) =>
// tbl.sourceId.equals(album.sourceId.value) &
// tbl.albumId.equals(album.id.value) &
// tbl.id.isNotIn(songIds) &
// (tbl.downloadFilePath.isNotNull() | tbl.downloadTaskId.isNotNull()),
// );
// // safe to hard delete songs that have not been downloaded
// batch.deleteWhere(
// db.songs,
// (tbl) =>
// tbl.sourceId.equals(album.sourceId.value) &
// tbl.id.isIn(hardDeletedSongIds),
// );
// // also need to remove these songs from any playlists that contain them
// batch.deleteWhere(
// db.playlistSongs,
// (tbl) =>
// tbl.sourceId.equals(album.sourceId.value) &
// tbl.songId.isIn(hardDeletedSongIds),
// );
// });
// });
// }
// Future<void> savePlaylist(
// SubtracksDatabase db,
// PlaylistWithSongsCompanion playlistWithSongs,
// ) async {
// return db.background((db) async {
// final playlist =
// playlistWithSongs.playist.copyWith(synced: Value(DateTime.now()));
// final songs = playlistWithSongs.songs;
// await db.batch((batch) {
// batch.insertAllOnConflictUpdate(db.playlists, [playlist]);
// batch.insertAllOnConflictUpdate(db.songs, songs);
// batch.insertAllOnConflictUpdate(
// db.playlistSongs,
// songs.mapIndexed(
// (index, song) => PlaylistSongsCompanion.insert(
// sourceId: playlist.sourceId.value,
// playlistId: playlist.id.value,
// songId: song.id.value,
// position: index,
// ),
// ),
// );
// // the new playlist could be shorter than the old one, so we delete
// // playlist songs above our new playlist's length
// batch.deleteWhere(
// db.playlistSongs,
// (tbl) =>
// tbl.sourceId.equals(playlist.sourceId.value) &
// tbl.playlistId.equals(playlist.id.value) &
// tbl.position.isBiggerOrEqualValue(songs.length),
// );
// });
// });
// }

View File

@ -2454,6 +2454,8 @@ abstract class _$SubtracksDatabase extends GeneratedDatabase {
'songs_source_id_artist_id_idx',
'CREATE INDEX songs_source_id_artist_id_idx ON songs (source_id, artist_id)',
);
late final SourcesDao sourcesDao = SourcesDao(this as SubtracksDatabase);
late final LibraryDao libraryDao = LibraryDao(this as SubtracksDatabase);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();