diff --git a/lib/domain/entities/song.dart b/lib/domain/entities/song.dart index 6b712af..9fbe66f 100644 --- a/lib/domain/entities/song.dart +++ b/lib/domain/entities/song.dart @@ -32,5 +32,5 @@ class Song extends Equatable { final int trackNumber; @override - List get props => [title, album, artist]; + List get props => [title, album, artist, blocked]; } diff --git a/lib/domain/repositories/music_data_repository.dart b/lib/domain/repositories/music_data_repository.dart index 4bfda36..141fb13 100644 --- a/lib/domain/repositories/music_data_repository.dart +++ b/lib/domain/repositories/music_data_repository.dart @@ -6,6 +6,10 @@ import '../entities/artist.dart'; import '../entities/song.dart'; abstract class MusicDataRepository { + Stream> get songStream; + Stream> getAlbumSongStream(Album album); + Stream> get queueStream; + Future>> getSongs(); Future>> getSongsFromAlbum(Album album); Future>> getAlbums(); diff --git a/lib/injection_container.dart b/lib/injection_container.dart index 6fdf6a9..90654d8 100644 --- a/lib/injection_container.dart +++ b/lib/injection_container.dart @@ -38,6 +38,7 @@ Future setupGetIt() async { () { final audioStore = AudioStore( audioRepository: getIt(), + musicDataRepository: getIt(), ); return audioStore; }, diff --git a/lib/presentation/pages/album_details_page.dart b/lib/presentation/pages/album_details_page.dart index 3dcf4b0..1070ef2 100644 --- a/lib/presentation/pages/album_details_page.dart +++ b/lib/presentation/pages/album_details_page.dart @@ -68,11 +68,14 @@ class AlbumDetailsPage extends StatelessWidget { if (index.isEven) { final songIndex = (index / 2).round(); - final Song song = musicDataStore.albumSongs[songIndex]; + final Song song = + musicDataStore.albumSongStream.value[songIndex]; return SongListTile( song: song, inAlbum: true, - onTap: () => audioStore.playSong(songIndex, musicDataStore.albumSongs), + onTap: () => audioStore.playSong( + songIndex, musicDataStore.albumSongStream.value), + onTapMore: () => _openBottomSheet(song, context), ); } return const Divider( @@ -85,11 +88,45 @@ class AlbumDetailsPage extends StatelessWidget { } return null; }, - childCount: musicDataStore.albumSongs.length * 2, + childCount: musicDataStore.albumSongStream.value.length * 2, ), ), ], ), ); } + + void _openBottomSheet(Song song, BuildContext context) { + final AudioStore audioStore = + Provider.of(context, listen: false); + final MusicDataStore musicDataStore = + Provider.of(context, listen: false); + + showModalBottomSheet( + context: context, + builder: (context) { + return Container( + child: Column( + children: [ + ListTile( + title: const Text('Add to queue'), + onTap: () { + audioStore.addToQueue(song); + Navigator.pop(context); + }, + ), + ListTile( + title: song.blocked + ? const Text('Unblock song') + : const Text('Block song'), + onTap: () { + musicDataStore.setSongBlocked(song, !song.blocked); + Navigator.pop(context); + }, + ), + ], + ), + ); + }); + } } diff --git a/lib/presentation/pages/currently_playing.dart b/lib/presentation/pages/currently_playing.dart index a01ad3a..a937576 100644 --- a/lib/presentation/pages/currently_playing.dart +++ b/lib/presentation/pages/currently_playing.dart @@ -29,7 +29,7 @@ class CurrentlyPlayingPage extends StatelessWidget { Observer( builder: (BuildContext context) { _log.info('Observer.build'); - final Song song = audioStore.currentSongStream.value; + final Song song = audioStore.currentSong; return Padding( padding: const EdgeInsets.only( diff --git a/lib/presentation/pages/songs_page.dart b/lib/presentation/pages/songs_page.dart index 2e3141c..3ef2f31 100644 --- a/lib/presentation/pages/songs_page.dart +++ b/lib/presentation/pages/songs_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; import 'package:provider/provider.dart'; import '../../domain/entities/song.dart'; @@ -25,34 +26,39 @@ class _SongsPageState extends State super.build(context); return Observer(builder: (_) { print('SongsPage.build -> Observer.builder'); - final bool isFetching = musicDataStore.isFetchingSongs; - if (isFetching) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - CircularProgressIndicator(), - Text('Loading items...'), - ], - ); - } else { - final List songs = musicDataStore.songs; - return ListView.separated( - itemCount: songs.length, - itemBuilder: (_, int index) { - final Song song = songs[index]; - return SongListTile( - song: song, - inAlbum: false, - onTap: () => audioStore.playSong(index, songs), - onTapMore: () => _openBottomSheet(song), - ); - }, - separatorBuilder: (BuildContext context, int index) => const Divider( - height: 4.0, - ), - ); + final songStream = musicDataStore.songStream; + + switch (songStream.status) { + case StreamStatus.active: + final List songs = songStream.value; + return ListView.separated( + itemCount: songs.length, + itemBuilder: (_, int index) { + final Song song = songs[index]; + return SongListTile( + song: song, + inAlbum: false, + onTap: () => audioStore.playSong(index, songs), + onTapMore: () => _openBottomSheet(song), + ); + }, + separatorBuilder: (BuildContext context, int index) => + const Divider( + height: 4.0, + ), + ); + case StreamStatus.waiting: + case StreamStatus.done: + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + CircularProgressIndicator(), + Text('Loading items...'), + ], + ); } + return Container(); }); } @@ -60,7 +66,10 @@ class _SongsPageState extends State bool get wantKeepAlive => true; void _openBottomSheet(Song song) { - final AudioStore audioStore = Provider.of(context, listen: false); + final AudioStore audioStore = + Provider.of(context, listen: false); + final MusicDataStore musicDataStore = + Provider.of(context, listen: false); showModalBottomSheet( context: context, @@ -75,6 +84,15 @@ class _SongsPageState extends State Navigator.pop(context); }, ), + ListTile( + title: song.blocked + ? const Text('Unblock song') + : const Text('Block song'), + onTap: () { + musicDataStore.setSongBlocked(song, !song.blocked); + Navigator.pop(context); + }, + ), ], ), ); diff --git a/lib/presentation/state/audio_store.dart b/lib/presentation/state/audio_store.dart index 845377d..36de905 100644 --- a/lib/presentation/state/audio_store.dart +++ b/lib/presentation/state/audio_store.dart @@ -1,5 +1,6 @@ import 'package:meta/meta.dart'; import 'package:mobx/mobx.dart'; +import 'package:mucke/domain/repositories/music_data_repository.dart'; import '../../domain/entities/playback_state.dart'; import '../../domain/entities/shuffle_mode.dart'; @@ -9,18 +10,22 @@ import '../../domain/repositories/audio_repository.dart'; part 'audio_store.g.dart'; class AudioStore extends _AudioStore with _$AudioStore { - AudioStore({@required AudioRepository audioRepository}) - : super(audioRepository); + AudioStore({ + @required AudioRepository audioRepository, + @required MusicDataRepository musicDataRepository, + }) : super(audioRepository, musicDataRepository); } abstract class _AudioStore with Store { - _AudioStore(this._audioRepository) { - currentSongStream = _audioRepository.currentSongStream.distinct().asObservable(); + _AudioStore(this._audioRepository, this._musicDataRepository) { + currentSongStream = + _audioRepository.currentSongStream.distinct().asObservable(); currentPositionStream = _audioRepository.currentPositionStream.asObservable(initialValue: 0); - queueStream = _audioRepository.queueStream.asObservable(initialValue: []); + queueStream = + _musicDataRepository.queueStream.asObservable(initialValue: []); queueIndexStream = _audioRepository.queueIndexStream.asObservable(); @@ -31,10 +36,22 @@ abstract class _AudioStore with Store { } final AudioRepository _audioRepository; + final MusicDataRepository _musicDataRepository; @observable ObservableStream currentSongStream; + @computed + Song get currentSong { + print('currentSong!!!'); + if (queueStream.value != [] && queueIndexStream.status == StreamStatus.active) { + final song = queueStream.value[queueIndexStream.value]; + return song; + } + + return null; + } + @observable ObservableStream playbackStateStream; diff --git a/lib/presentation/state/audio_store.g.dart b/lib/presentation/state/audio_store.g.dart index ce884b0..01f8c21 100644 --- a/lib/presentation/state/audio_store.g.dart +++ b/lib/presentation/state/audio_store.g.dart @@ -9,6 +9,14 @@ part of 'audio_store.dart'; // ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic mixin _$AudioStore on _AudioStore, Store { + Computed _$currentSongComputed; + + @override + Song get currentSong => + (_$currentSongComputed ??= Computed(() => super.currentSong, + name: '_AudioStore.currentSong')) + .value; + final _$currentSongStreamAtom = Atom(name: '_AudioStore.currentSongStream'); @override @@ -145,7 +153,8 @@ playbackStateStream: ${playbackStateStream}, currentPositionStream: ${currentPositionStream}, queueStream: ${queueStream}, queueIndexStream: ${queueIndexStream}, -shuffleModeStream: ${shuffleModeStream} +shuffleModeStream: ${shuffleModeStream}, +currentSong: ${currentSong} '''; } } diff --git a/lib/presentation/state/music_data_store.dart b/lib/presentation/state/music_data_store.dart index 6bcc25a..3011ca6 100644 --- a/lib/presentation/state/music_data_store.dart +++ b/lib/presentation/state/music_data_store.dart @@ -14,12 +14,20 @@ class MusicDataStore extends _MusicDataStore with _$MusicDataStore { } abstract class _MusicDataStore with Store { - _MusicDataStore(this._musicDataRepository); + _MusicDataStore(this._musicDataRepository) { + songStream = _musicDataRepository.songStream.asObservable(initialValue: []); + } final MusicDataRepository _musicDataRepository; bool _initialized = false; + @observable + ObservableStream> songStream; + + @observable + ObservableStream> albumSongStream; + @observable ObservableList artists = [].asObservable(); @observable @@ -112,12 +120,14 @@ abstract class _MusicDataStore with Store { @action Future fetchSongsFromAlbum(Album album) async { - final result = await _musicDataRepository.getSongsFromAlbum(album); - albumSongs.clear(); - result.fold( - (_) => albumSongs = [].asObservable(), - (songList) => albumSongs.addAll(songList), - ); + albumSongStream = _musicDataRepository.getAlbumSongStream(album).asObservable(initialValue: []); + + // final result = await _musicDataRepository.getSongsFromAlbum(album); + // albumSongs.clear(); + // result.fold( + // (_) => albumSongs = [].asObservable(), + // (songList) => albumSongs.addAll(songList), + // ); } Future setSongBlocked(Song song, bool blocked) async { diff --git a/lib/presentation/state/music_data_store.g.dart b/lib/presentation/state/music_data_store.g.dart index b95313e..8e1aef4 100644 --- a/lib/presentation/state/music_data_store.g.dart +++ b/lib/presentation/state/music_data_store.g.dart @@ -9,6 +9,36 @@ part of 'music_data_store.dart'; // ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic mixin _$MusicDataStore on _MusicDataStore, Store { + final _$songStreamAtom = Atom(name: '_MusicDataStore.songStream'); + + @override + ObservableStream> get songStream { + _$songStreamAtom.reportRead(); + return super.songStream; + } + + @override + set songStream(ObservableStream> value) { + _$songStreamAtom.reportWrite(value, super.songStream, () { + super.songStream = value; + }); + } + + final _$albumSongStreamAtom = Atom(name: '_MusicDataStore.albumSongStream'); + + @override + ObservableStream> get albumSongStream { + _$albumSongStreamAtom.reportRead(); + return super.albumSongStream; + } + + @override + set albumSongStream(ObservableStream> value) { + _$albumSongStreamAtom.reportWrite(value, super.albumSongStream, () { + super.albumSongStream = value; + }); + } + final _$artistsAtom = Atom(name: '_MusicDataStore.artists'); @override @@ -172,6 +202,8 @@ mixin _$MusicDataStore on _MusicDataStore, Store { @override String toString() { return ''' +songStream: ${songStream}, +albumSongStream: ${albumSongStream}, artists: ${artists}, isFetchingArtists: ${isFetchingArtists}, albums: ${albums}, diff --git a/lib/presentation/widgets/currently_playing_bar.dart b/lib/presentation/widgets/currently_playing_bar.dart index b10f6e8..f2fffb3 100644 --- a/lib/presentation/widgets/currently_playing_bar.dart +++ b/lib/presentation/widgets/currently_playing_bar.dart @@ -18,14 +18,14 @@ class CurrentlyPlayingBar extends StatelessWidget { return Observer( builder: (BuildContext context) { - if (audioStore.currentSongStream.value != null) { - final Song song = audioStore.currentSongStream.value; + if (audioStore.currentSong != null) { + final Song song = audioStore.currentSong; return Column( children: [ Container( child: LinearProgressIndicator( - value: audioStore.currentPositionStream.value / audioStore.currentSongStream.value.duration, + value: audioStore.currentPositionStream.value / audioStore.currentSong.duration, ), height: 2, ), diff --git a/lib/presentation/widgets/song_customization_buttons.dart b/lib/presentation/widgets/song_customization_buttons.dart index 1d3fd89..e0c0333 100644 --- a/lib/presentation/widgets/song_customization_buttons.dart +++ b/lib/presentation/widgets/song_customization_buttons.dart @@ -17,7 +17,9 @@ class SongCustomizationButtons extends StatelessWidget { return Observer( builder: (BuildContext context) { - final Song song = audioStore.currentSongStream.value; + print('building buttons'); + final Song song = audioStore.currentSong; + final bool isBlocked = audioStore.currentSong.blocked; return Row( children: [ IconButton( @@ -46,10 +48,10 @@ class SongCustomizationButtons extends StatelessWidget { icon: Icon( Icons.remove_circle_outline, size: 20.0, - color: song.blocked ? RASPBERRY : Colors.white70, + color: isBlocked ? RASPBERRY : Colors.white70, ), onPressed: () => - musicDataStore.setSongBlocked(song, !song.blocked), + musicDataStore.setSongBlocked(song, !isBlocked), ), ], mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/presentation/widgets/time_progress_indicator.dart b/lib/presentation/widgets/time_progress_indicator.dart index fcbcbf7..076d5d8 100644 --- a/lib/presentation/widgets/time_progress_indicator.dart +++ b/lib/presentation/widgets/time_progress_indicator.dart @@ -14,7 +14,7 @@ class TimeProgressIndicator extends StatelessWidget { return Observer( builder: (BuildContext context) { - final int duration = audioStore.currentSongStream.value?.duration ?? 1000; + final int duration = audioStore.currentSong?.duration ?? 1000; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), diff --git a/lib/system/audio/audio_player_task.dart b/lib/system/audio/audio_player_task.dart index 94c0fdc..f6e7fd7 100644 --- a/lib/system/audio/audio_player_task.dart +++ b/lib/system/audio/audio_player_task.dart @@ -109,7 +109,7 @@ abstract class AudioPlayerTaskBase extends BackgroundAudioTask with Store { Future onAddQueueItem(MediaItem mediaItem) async { await queue.add(AudioSource.uri(Uri.file(mediaItem.id))); mediaItemQueue.add(mediaItem); - AudioServiceBackground.setQueue(mediaItemQueue); + handleSetQueue(mediaItemQueue); } @override @@ -138,6 +138,12 @@ abstract class AudioPlayerTaskBase extends BackgroundAudioTask with Store { } } + Future handleSetQueue(List mediaItemQueue) async { + final songModels = mediaItemQueue.map((e) => SongModel.fromMediaItem(e)).toList(); + AudioServiceBackground.setQueue(mediaItemQueue); + moorMusicDataSource.setQueue(songModels); + } + Future init() async { print('AudioPlayerTask.init'); audioPlayer.playerStateStream.listen((event) => handlePlayerState(event)); @@ -174,7 +180,7 @@ abstract class AudioPlayerTaskBase extends BackgroundAudioTask with Store { mediaItemQueue = playbackContext.map((e) => e.mediaItem).toList(); // FIXME: this does not react correctly when inserted track is currently played - AudioServiceBackground.setQueue(mediaItemQueue); + handleSetQueue(mediaItemQueue); final newQueue = queueGenerator.mediaItemsToAudioSource(mediaItemQueue); _updateQueue(newQueue, currentQueueItem); @@ -228,7 +234,7 @@ abstract class AudioPlayerTaskBase extends BackgroundAudioTask with Store { playbackContext = await queueGenerator.generateQueue(shuffleMode, mediaItems, index); mediaItemQueue = playbackContext.map((e) => e.mediaItem).toList(); - AudioServiceBackground.setQueue(mediaItemQueue); + handleSetQueue(mediaItemQueue); queue = queueGenerator.mediaItemsToAudioSource(mediaItemQueue); audioPlayer.play(); final int startIndex = shuffleMode == ShuffleMode.none ? index : 0; @@ -240,13 +246,13 @@ abstract class AudioPlayerTaskBase extends BackgroundAudioTask with Store { final MediaItem mediaItem = mediaItemQueue.removeAt(oldIndex); final index = newIndex < oldIndex ? newIndex : newIndex - 1; mediaItemQueue.insert(index, mediaItem); - AudioServiceBackground.setQueue(mediaItemQueue); + handleSetQueue(mediaItemQueue); queue.move(oldIndex, index); } Future removeQueueItem(int index) async { mediaItemQueue.removeAt(index); - AudioServiceBackground.setQueue(mediaItemQueue); + handleSetQueue(mediaItemQueue); queue.removeAt(index); } diff --git a/lib/system/datasources/moor_music_data_source.dart b/lib/system/datasources/moor_music_data_source.dart index 3840d6e..21577be 100644 --- a/lib/system/datasources/moor_music_data_source.dart +++ b/lib/system/datasources/moor_music_data_source.dart @@ -54,7 +54,16 @@ class Songs extends Table { Set get primaryKey => {path}; } -@UseMoor(tables: [Artists, Albums, Songs]) +@DataClassName('QueueEntry') +class QueueEntries extends Table { + IntColumn get index => integer()(); + TextColumn get path => text()(); + + @override + Set get primaryKey => {index}; +} + +@UseMoor(tables: [Artists, Albums, Songs, QueueEntries]) class MoorMusicDataSource extends _$MoorMusicDataSource implements MusicDataSource { /// Use MoorMusicDataSource in main isolate only. @@ -83,6 +92,13 @@ class MoorMusicDataSource extends _$MoorMusicDataSource return await into(albums).insert(albumModel.toAlbumsCompanion()); } + @override + Stream> get songStream { + return select(songs).watch().map((moorSongList) => moorSongList + .map((moorSong) => SongModel.fromMoorSong(moorSong)) + .toList()); + } + @override Future> getSongs() { return select(songs).get().then((moorSongList) => moorSongList @@ -90,6 +106,20 @@ class MoorMusicDataSource extends _$MoorMusicDataSource .toList()); } + @override + Stream> getAlbumSongStream(AlbumModel album) { + return (select(songs) + ..where((tbl) => tbl.albumId.equals(album.id)) + ..orderBy([ + (t) => OrderingTerm(expression: t.discNumber), + (t) => OrderingTerm(expression: t.trackNumber) + ])) + .watch() + .map((moorSongList) => moorSongList + .map((moorSong) => SongModel.fromMoorSong(moorSong)) + .toList()); + } + @override Future> getSongsFromAlbum(AlbumModel album) { return (select(songs) @@ -173,15 +203,15 @@ class MoorMusicDataSource extends _$MoorMusicDataSource Future toggleNextSongLink(SongModel song) async { if (song.next == null) { final albumSongs = await (select(songs) - ..where((tbl) => tbl.albumId.equals(song.albumId)) - ..orderBy([ - (t) => OrderingTerm(expression: t.discNumber), - (t) => OrderingTerm(expression: t.trackNumber) - ])) - .get() - .then((moorSongList) => moorSongList - .map((moorSong) => SongModel.fromMoorSong(moorSong)) - .toList()); + ..where((tbl) => tbl.albumId.equals(song.albumId)) + ..orderBy([ + (t) => OrderingTerm(expression: t.discNumber), + (t) => OrderingTerm(expression: t.trackNumber) + ])) + .get() + .then((moorSongList) => moorSongList + .map((moorSong) => SongModel.fromMoorSong(moorSong)) + .toList()); bool current = false; SongModel nextSong; @@ -195,11 +225,41 @@ class MoorMusicDataSource extends _$MoorMusicDataSource } } await (update(songs)..where((tbl) => tbl.path.equals(song.path))) - .write(SongsCompanion(next: Value(nextSong.path))); + .write(SongsCompanion(next: Value(nextSong.path))); } else { await (update(songs)..where((tbl) => tbl.path.equals(song.path))) - .write(const SongsCompanion(next: Value(null))); - } + .write(const SongsCompanion(next: Value(null))); + } + } + + @override + Stream> get queueStream { + final query = (select(queueEntries) + ..orderBy([(t) => OrderingTerm(expression: t.index)])) + .join([innerJoin(songs, songs.path.equalsExp(queueEntries.path))]); + + return query.watch().map((rows) { + return rows + .map((row) => SongModel.fromMoorSong(row.readTable(songs))) + .toList(); + }); + } + + @override + Future setQueue(List queue) async { + final _queueEntries = >[]; + + for (var i = 0; i < queue.length; i++) { + _queueEntries.add(QueueEntriesCompanion(index: Value(i), path: Value(queue[i].path))); + } + + await delete(queueEntries).go(); + await batch((batch) { + batch.insertAll( + queueEntries, + _queueEntries + ); + }); } } diff --git a/lib/system/datasources/moor_music_data_source.g.dart b/lib/system/datasources/moor_music_data_source.g.dart index f2e3e5d..795d561 100644 --- a/lib/system/datasources/moor_music_data_source.g.dart +++ b/lib/system/datasources/moor_music_data_source.g.dart @@ -1213,6 +1213,199 @@ class $SongsTable extends Songs with TableInfo<$SongsTable, MoorSong> { } } +class QueueEntry extends DataClass implements Insertable { + final int index; + final String path; + QueueEntry({@required this.index, @required this.path}); + factory QueueEntry.fromData(Map data, GeneratedDatabase db, + {String prefix}) { + final effectivePrefix = prefix ?? ''; + final intType = db.typeSystem.forDartType(); + final stringType = db.typeSystem.forDartType(); + return QueueEntry( + index: intType.mapFromDatabaseResponse(data['${effectivePrefix}index']), + path: stringType.mapFromDatabaseResponse(data['${effectivePrefix}path']), + ); + } + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (!nullToAbsent || index != null) { + map['index'] = Variable(index); + } + if (!nullToAbsent || path != null) { + map['path'] = Variable(path); + } + return map; + } + + QueueEntriesCompanion toCompanion(bool nullToAbsent) { + return QueueEntriesCompanion( + index: + index == null && nullToAbsent ? const Value.absent() : Value(index), + path: path == null && nullToAbsent ? const Value.absent() : Value(path), + ); + } + + factory QueueEntry.fromJson(Map json, + {ValueSerializer serializer}) { + serializer ??= moorRuntimeOptions.defaultSerializer; + return QueueEntry( + index: serializer.fromJson(json['index']), + path: serializer.fromJson(json['path']), + ); + } + @override + Map toJson({ValueSerializer serializer}) { + serializer ??= moorRuntimeOptions.defaultSerializer; + return { + 'index': serializer.toJson(index), + 'path': serializer.toJson(path), + }; + } + + QueueEntry copyWith({int index, String path}) => QueueEntry( + index: index ?? this.index, + path: path ?? this.path, + ); + @override + String toString() { + return (StringBuffer('QueueEntry(') + ..write('index: $index, ') + ..write('path: $path') + ..write(')')) + .toString(); + } + + @override + int get hashCode => $mrjf($mrjc(index.hashCode, path.hashCode)); + @override + bool operator ==(dynamic other) => + identical(this, other) || + (other is QueueEntry && + other.index == this.index && + other.path == this.path); +} + +class QueueEntriesCompanion extends UpdateCompanion { + final Value index; + final Value path; + const QueueEntriesCompanion({ + this.index = const Value.absent(), + this.path = const Value.absent(), + }); + QueueEntriesCompanion.insert({ + this.index = const Value.absent(), + @required String path, + }) : path = Value(path); + static Insertable custom({ + Expression index, + Expression path, + }) { + return RawValuesInsertable({ + if (index != null) 'index': index, + if (path != null) 'path': path, + }); + } + + QueueEntriesCompanion copyWith({Value index, Value path}) { + return QueueEntriesCompanion( + index: index ?? this.index, + path: path ?? this.path, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (index.present) { + map['index'] = Variable(index.value); + } + if (path.present) { + map['path'] = Variable(path.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('QueueEntriesCompanion(') + ..write('index: $index, ') + ..write('path: $path') + ..write(')')) + .toString(); + } +} + +class $QueueEntriesTable extends QueueEntries + with TableInfo<$QueueEntriesTable, QueueEntry> { + final GeneratedDatabase _db; + final String _alias; + $QueueEntriesTable(this._db, [this._alias]); + final VerificationMeta _indexMeta = const VerificationMeta('index'); + GeneratedIntColumn _index; + @override + GeneratedIntColumn get index => _index ??= _constructIndex(); + GeneratedIntColumn _constructIndex() { + return GeneratedIntColumn( + 'index', + $tableName, + false, + ); + } + + final VerificationMeta _pathMeta = const VerificationMeta('path'); + GeneratedTextColumn _path; + @override + GeneratedTextColumn get path => _path ??= _constructPath(); + GeneratedTextColumn _constructPath() { + return GeneratedTextColumn( + 'path', + $tableName, + false, + ); + } + + @override + List get $columns => [index, path]; + @override + $QueueEntriesTable get asDslTable => this; + @override + String get $tableName => _alias ?? 'queue_entries'; + @override + final String actualTableName = 'queue_entries'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('index')) { + context.handle( + _indexMeta, index.isAcceptableOrUnknown(data['index'], _indexMeta)); + } + if (data.containsKey('path')) { + context.handle( + _pathMeta, path.isAcceptableOrUnknown(data['path'], _pathMeta)); + } else if (isInserting) { + context.missing(_pathMeta); + } + return context; + } + + @override + Set get $primaryKey => {index}; + @override + QueueEntry map(Map data, {String tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; + return QueueEntry.fromData(data, _db, prefix: effectivePrefix); + } + + @override + $QueueEntriesTable createAlias(String alias) { + return $QueueEntriesTable(_db, alias); + } +} + abstract class _$MoorMusicDataSource extends GeneratedDatabase { _$MoorMusicDataSource(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e); @@ -1223,8 +1416,12 @@ abstract class _$MoorMusicDataSource extends GeneratedDatabase { $AlbumsTable get albums => _albums ??= $AlbumsTable(this); $SongsTable _songs; $SongsTable get songs => _songs ??= $SongsTable(this); + $QueueEntriesTable _queueEntries; + $QueueEntriesTable get queueEntries => + _queueEntries ??= $QueueEntriesTable(this); @override Iterable get allTables => allSchemaEntities.whereType(); @override - List get allSchemaEntities => [artists, albums, songs]; + List get allSchemaEntities => + [artists, albums, songs, queueEntries]; } diff --git a/lib/system/datasources/music_data_source_contract.dart b/lib/system/datasources/music_data_source_contract.dart index 23ee6cc..97e725b 100644 --- a/lib/system/datasources/music_data_source_contract.dart +++ b/lib/system/datasources/music_data_source_contract.dart @@ -5,6 +5,12 @@ import '../models/song_model.dart'; abstract class MusicDataSource { Future> getAlbums(); + Stream> get songStream; + Stream> getAlbumSongStream(AlbumModel album); + + Future setQueue(List queue); + Stream> get queueStream; + /// Insert album into the database. Return the ID of the inserted album. Future insertAlbum(AlbumModel albumModel); diff --git a/lib/system/repositories/music_data_repository_impl.dart b/lib/system/repositories/music_data_repository_impl.dart index cdf16b5..586aa7b 100644 --- a/lib/system/repositories/music_data_repository_impl.dart +++ b/lib/system/repositories/music_data_repository_impl.dart @@ -45,6 +45,12 @@ class MusicDataRepositoryImpl implements MusicDataRepository { (List songs) => Right>(songs)); } + @override + Stream> get songStream => musicDataSource.songStream; + + @override + Stream> getAlbumSongStream(Album album) => musicDataSource.getAlbumSongStream(album as AlbumModel); + @override Future>> getSongsFromAlbum(Album album) async { return musicDataSource.getSongsFromAlbum(album as AlbumModel).then( @@ -127,4 +133,7 @@ class MusicDataRepositoryImpl implements MusicDataRepository { Future toggleNextSongLink(Song song) async { musicDataSource.toggleNextSongLink(song as SongModel); } + + @override + Stream> get queueStream => musicDataSource.queueStream; }