simplified music data components

This commit is contained in:
Moritz Weber 2020-12-30 18:28:16 +01:00
parent f0dbb1ec65
commit d5ca1271a8
10 changed files with 150 additions and 358 deletions

View file

@ -5,12 +5,12 @@ import '../entities/song.dart';
abstract class MusicDataRepository {
Stream<List<Song>> get songStream;
Stream<List<Song>> getAlbumSongStream(Album album);
Stream<List<Album>> get albumStream;
Stream<List<Album>> getArtistAlbumStream(Artist artist);
Future<List<Song>> getSongs();
Future<List<Song>> getSongsFromAlbum(Album album);
Future<List<Album>> getAlbums();
Future<List<Artist>> getArtists();
Stream<List<Artist>> get artistStream;
Future<void> updateDatabase();
Future<void> setSongBlocked(Song song, bool blocked);

View file

@ -40,7 +40,6 @@ Future<void> setupGetIt() async {
musicDataRepository: getIt(),
settingsRepository: getIt(),
);
musicDataStore.init();
return musicDataStore;
},
);
@ -63,8 +62,8 @@ Future<void> setupGetIt() async {
// repositories
getIt.registerLazySingleton<MusicDataRepository>(
() => MusicDataRepositoryImpl(
localMusicFetcher: getIt(),
musicDataSource: getIt(),
getIt(),
getIt(),
),
);
getIt.registerLazySingleton<AudioRepository>(

View file

@ -14,8 +14,7 @@ class AlbumsPage extends StatefulWidget {
_AlbumsPageState createState() => _AlbumsPageState();
}
class _AlbumsPageState extends State<AlbumsPage>
with AutomaticKeepAliveClientMixin {
class _AlbumsPageState extends State<AlbumsPage> with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
print('AlbumsPage.build');
@ -24,44 +23,32 @@ class _AlbumsPageState extends State<AlbumsPage>
super.build(context);
return Observer(builder: (_) {
print('AlbumsPage.build -> Observer.builder');
final bool isFetching = store.isFetchingAlbums;
if (isFetching) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
CircularProgressIndicator(),
Text('Loading items...'),
],
);
} else {
final List<Album> albums = store.albums;
return ListView.separated(
itemCount: albums.length,
itemBuilder: (_, int index) {
final Album album = albums[index];
return AlbumArtListTile(
title: album.title,
subtitle: album.artist,
albumArtPath: album.albumArtPath,
onTap: () {
store.fetchSongsFromAlbum(album);
Navigator.push(
context,
MaterialPageRoute<Widget>(
builder: (BuildContext context) => AlbumDetailsPage(
album: album,
),
final List<Album> albums = store.albumStream.value;
return ListView.separated(
itemCount: albums.length,
itemBuilder: (_, int index) {
final Album album = albums[index];
return AlbumArtListTile(
title: album.title,
subtitle: album.artist,
albumArtPath: album.albumArtPath,
onTap: () {
store.fetchSongsFromAlbum(album);
Navigator.push(
context,
MaterialPageRoute<Widget>(
builder: (BuildContext context) => AlbumDetailsPage(
album: album,
),
);
},
);
},
separatorBuilder: (BuildContext context, int index) => const Divider(
height: 4.0,
),
);
}
),
);
},
);
},
separatorBuilder: (BuildContext context, int index) => const Divider(
height: 4.0,
),
);
});
}

View file

@ -13,8 +13,7 @@ class ArtistsPage extends StatefulWidget {
_ArtistsPageState createState() => _ArtistsPageState();
}
class _ArtistsPageState extends State<ArtistsPage>
with AutomaticKeepAliveClientMixin {
class _ArtistsPageState extends State<ArtistsPage> with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
print('ArtistsPage.build');
@ -23,42 +22,30 @@ class _ArtistsPageState extends State<ArtistsPage>
super.build(context);
return Observer(builder: (_) {
print('ArtistsPage.build -> Observer.builder');
final bool isFetching = musicDataStore.isFetchingArtists;
if (isFetching) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
CircularProgressIndicator(),
Text('Loading items...'),
],
);
} else {
final List<Artist> artists = musicDataStore.artists;
return ListView.separated(
itemCount: artists.length,
itemBuilder: (_, int index) {
final Artist artist = artists[index];
return ListTile(
title: Text(artist.name),
onTap: () {
musicDataStore.fetchAlbumsFromArtist(artist);
Navigator.push(
context,
MaterialPageRoute<Widget>(
builder: (BuildContext context) => ArtistDetailsPage(
artist: artist,
),
final List<Artist> artists = musicDataStore.artistStream.value;
return ListView.separated(
itemCount: artists.length,
itemBuilder: (_, int index) {
final Artist artist = artists[index];
return ListTile(
title: Text(artist.name),
onTap: () {
musicDataStore.fetchAlbumsFromArtist(artist);
Navigator.push(
context,
MaterialPageRoute<Widget>(
builder: (BuildContext context) => ArtistDetailsPage(
artist: artist,
),
);
},
);
},
separatorBuilder: (BuildContext context, int index) => const Divider(
height: 4.0,
),
);
}
),
);
},
);
},
separatorBuilder: (BuildContext context, int index) => const Divider(
height: 4.0,
),
);
});
}

View file

@ -34,17 +34,10 @@ class SettingsPage extends StatelessWidget {
ListTile(
title: const Text('Update library'),
subtitle: Observer(builder: (_) {
final bool isFetchingArtists = store.isFetchingArtists;
final bool isFetchingAlbums = store.isFetchingAlbums;
final bool isFetchingSongs = store.isFetchingSongs;
if (!isFetchingArtists && !isFetchingAlbums && !isFetchingSongs) {
final int artistCount = store.artists.length;
final int albumCount = store.albums.length;
final int songCount = store.songs.length;
return Text('$artistCount artists, $albumCount albums, $songCount songs');
}
return const Text('');
final int artistCount = store.artistStream.value.length;
final int albumCount = store.albumStream.value.length;
final int songCount = store.songStream.value.length;
return Text('$artistCount artists, $albumCount albums, $songCount songs');
}),
onTap: () => store.updateDatabase(),
trailing: Observer(builder: (_) {

View file

@ -19,93 +19,38 @@ class MusicDataStore extends _MusicDataStore with _$MusicDataStore {
abstract class _MusicDataStore with Store {
_MusicDataStore(this._musicDataRepository, this._settingsRepository) {
songStream = _musicDataRepository.songStream.asObservable(initialValue: []);
albumStream = _musicDataRepository.albumStream.asObservable(initialValue: []);
artistStream = _musicDataRepository.artistStream.asObservable(initialValue: []);
}
final MusicDataRepository _musicDataRepository;
final SettingsRepository _settingsRepository;
bool _initialized = false;
@observable
ObservableStream<List<Song>> songStream;
@observable
ObservableStream<List<Album>> albumStream;
@observable
ObservableStream<List<Artist>> artistStream;
@observable
ObservableStream<List<Song>> albumSongStream;
@observable
ObservableStream<List<Album>> artistAlbumStream;
@observable
ObservableList<Artist> artists = <Artist>[].asObservable();
@observable
bool isFetchingArtists = false;
@observable
ObservableList<Album> albums = <Album>[].asObservable();
@observable
bool isFetchingAlbums = false;
@observable
ObservableList<Song> songs = <Song>[].asObservable();
@observable
bool isFetchingSongs = false;
@observable
bool isUpdatingDatabase = false;
@observable
ObservableList<Song> albumSongs = <Song>[].asObservable();
void init() {
if (!_initialized) {
fetchArtists();
fetchAlbums();
fetchSongs();
}
_initialized = true;
}
@action
Future<void> updateDatabase() async {
isUpdatingDatabase = true;
await _musicDataRepository.updateDatabase();
await Future.wait([
fetchArtists(),
fetchAlbums(),
fetchSongs(),
]);
isUpdatingDatabase = false;
}
@action
Future<void> fetchArtists() async {
isFetchingArtists = true;
final result = await _musicDataRepository.getArtists();
artists.clear();
artists.addAll(result);
isFetchingArtists = false;
}
@action
Future<void> fetchAlbums() async {
isFetchingAlbums = true;
final result = await _musicDataRepository.getAlbums();
albums.clear();
albums.addAll(result);
isFetchingAlbums = false;
}
@action
Future<void> fetchSongs() async {
isFetchingSongs = true;
final result = await _musicDataRepository.getSongs();
songs.clear();
songs.addAll(result);
isFetchingSongs = false;
}
@action
Future<void> fetchSongsFromAlbum(Album album) async {

View file

@ -24,6 +24,36 @@ mixin _$MusicDataStore on _MusicDataStore, Store {
});
}
final _$albumStreamAtom = Atom(name: '_MusicDataStore.albumStream');
@override
ObservableStream<List<Album>> get albumStream {
_$albumStreamAtom.reportRead();
return super.albumStream;
}
@override
set albumStream(ObservableStream<List<Album>> value) {
_$albumStreamAtom.reportWrite(value, super.albumStream, () {
super.albumStream = value;
});
}
final _$artistStreamAtom = Atom(name: '_MusicDataStore.artistStream');
@override
ObservableStream<List<Artist>> get artistStream {
_$artistStreamAtom.reportRead();
return super.artistStream;
}
@override
set artistStream(ObservableStream<List<Artist>> value) {
_$artistStreamAtom.reportWrite(value, super.artistStream, () {
super.artistStream = value;
});
}
final _$albumSongStreamAtom = Atom(name: '_MusicDataStore.albumSongStream');
@override
@ -55,97 +85,6 @@ mixin _$MusicDataStore on _MusicDataStore, Store {
});
}
final _$artistsAtom = Atom(name: '_MusicDataStore.artists');
@override
ObservableList<Artist> get artists {
_$artistsAtom.reportRead();
return super.artists;
}
@override
set artists(ObservableList<Artist> value) {
_$artistsAtom.reportWrite(value, super.artists, () {
super.artists = value;
});
}
final _$isFetchingArtistsAtom =
Atom(name: '_MusicDataStore.isFetchingArtists');
@override
bool get isFetchingArtists {
_$isFetchingArtistsAtom.reportRead();
return super.isFetchingArtists;
}
@override
set isFetchingArtists(bool value) {
_$isFetchingArtistsAtom.reportWrite(value, super.isFetchingArtists, () {
super.isFetchingArtists = value;
});
}
final _$albumsAtom = Atom(name: '_MusicDataStore.albums');
@override
ObservableList<Album> get albums {
_$albumsAtom.reportRead();
return super.albums;
}
@override
set albums(ObservableList<Album> value) {
_$albumsAtom.reportWrite(value, super.albums, () {
super.albums = value;
});
}
final _$isFetchingAlbumsAtom = Atom(name: '_MusicDataStore.isFetchingAlbums');
@override
bool get isFetchingAlbums {
_$isFetchingAlbumsAtom.reportRead();
return super.isFetchingAlbums;
}
@override
set isFetchingAlbums(bool value) {
_$isFetchingAlbumsAtom.reportWrite(value, super.isFetchingAlbums, () {
super.isFetchingAlbums = value;
});
}
final _$songsAtom = Atom(name: '_MusicDataStore.songs');
@override
ObservableList<Song> get songs {
_$songsAtom.reportRead();
return super.songs;
}
@override
set songs(ObservableList<Song> value) {
_$songsAtom.reportWrite(value, super.songs, () {
super.songs = value;
});
}
final _$isFetchingSongsAtom = Atom(name: '_MusicDataStore.isFetchingSongs');
@override
bool get isFetchingSongs {
_$isFetchingSongsAtom.reportRead();
return super.isFetchingSongs;
}
@override
set isFetchingSongs(bool value) {
_$isFetchingSongsAtom.reportWrite(value, super.isFetchingSongs, () {
super.isFetchingSongs = value;
});
}
final _$isUpdatingDatabaseAtom =
Atom(name: '_MusicDataStore.isUpdatingDatabase');
@ -162,21 +101,6 @@ mixin _$MusicDataStore on _MusicDataStore, Store {
});
}
final _$albumSongsAtom = Atom(name: '_MusicDataStore.albumSongs');
@override
ObservableList<Song> get albumSongs {
_$albumSongsAtom.reportRead();
return super.albumSongs;
}
@override
set albumSongs(ObservableList<Song> value) {
_$albumSongsAtom.reportWrite(value, super.albumSongs, () {
super.albumSongs = value;
});
}
final _$updateDatabaseAsyncAction =
AsyncAction('_MusicDataStore.updateDatabase');
@ -185,27 +109,6 @@ mixin _$MusicDataStore on _MusicDataStore, Store {
return _$updateDatabaseAsyncAction.run(() => super.updateDatabase());
}
final _$fetchArtistsAsyncAction = AsyncAction('_MusicDataStore.fetchArtists');
@override
Future<void> fetchArtists() {
return _$fetchArtistsAsyncAction.run(() => super.fetchArtists());
}
final _$fetchAlbumsAsyncAction = AsyncAction('_MusicDataStore.fetchAlbums');
@override
Future<void> fetchAlbums() {
return _$fetchAlbumsAsyncAction.run(() => super.fetchAlbums());
}
final _$fetchSongsAsyncAction = AsyncAction('_MusicDataStore.fetchSongs');
@override
Future<void> fetchSongs() {
return _$fetchSongsAsyncAction.run(() => super.fetchSongs());
}
final _$fetchSongsFromAlbumAsyncAction =
AsyncAction('_MusicDataStore.fetchSongsFromAlbum');
@ -228,16 +131,11 @@ mixin _$MusicDataStore on _MusicDataStore, Store {
String toString() {
return '''
songStream: ${songStream},
albumStream: ${albumStream},
artistStream: ${artistStream},
albumSongStream: ${albumSongStream},
artistAlbumStream: ${artistAlbumStream},
artists: ${artists},
isFetchingArtists: ${isFetchingArtists},
albums: ${albums},
isFetchingAlbums: ${isFetchingAlbums},
songs: ${songs},
isFetchingSongs: ${isFetchingSongs},
isUpdatingDatabase: ${isUpdatingDatabase},
albumSongs: ${albumSongs}
isUpdatingDatabase: ${isUpdatingDatabase}
''';
}
}

View file

@ -130,6 +130,18 @@ class MoorMusicDataSource extends _$MoorMusicDataSource implements MusicDataSour
moorSongList.map((moorSong) => SongModel.fromMoorSong(moorSong)).toList());
}
@override
Stream<List<AlbumModel>> get albumStream {
return select(albums).watch().map((moorAlbumList) =>
moorAlbumList.map((moorAlbum) => AlbumModel.fromMoorAlbum(moorAlbum)).toList());
}
@override
Stream<List<ArtistModel>> get artistStream {
return select(artists).watch().map((moorArtistList) =>
moorArtistList.map((moorArtist) => ArtistModel.fromMoorArtist(moorArtist)).toList());
}
@override
Future<List<SongModel>> getSongs() {
return select(songs).get().then((moorSongList) =>
@ -161,19 +173,6 @@ class MoorMusicDataSource extends _$MoorMusicDataSource implements MusicDataSour
moorAlbumList.map((moorAlbum) => AlbumModel.fromMoorAlbum(moorAlbum)).toList());
}
@override
Future<List<SongModel>> getSongsFromAlbum(AlbumModel album) {
return (select(songs)
..where((tbl) => tbl.albumTitle.equals(album.title))
..orderBy([
(t) => OrderingTerm(expression: t.discNumber),
(t) => OrderingTerm(expression: t.trackNumber)
]))
.get()
.then((moorSongList) =>
moorSongList.map((moorSong) => SongModel.fromMoorSong(moorSong)).toList());
}
@override
Future<void> insertSong(SongModel songModel) async {
await into(songs).insert(songModel.toSongsCompanion());

View file

@ -3,29 +3,28 @@ import '../models/artist_model.dart';
import '../models/song_model.dart';
abstract class MusicDataSource {
Future<List<AlbumModel>> getAlbums();
Stream<List<SongModel>> get songStream;
Stream<List<SongModel>> getAlbumSongStream(AlbumModel album);
Stream<List<AlbumModel>> getArtistAlbumStream(ArtistModel artist);
/// Insert album into the database. Return the ID of the inserted album.
Future<int> insertAlbum(AlbumModel albumModel);
Future<List<SongModel>> getSongs();
Future<List<SongModel>> getSongsFromAlbum(AlbumModel album);
Future<SongModel> getSongByPath(String path);
Stream<List<AlbumModel>> get albumStream;
Stream<List<AlbumModel>> getArtistAlbumStream(ArtistModel artist);
Future<List<AlbumModel>> getAlbums();
Stream<List<ArtistModel>> get artistStream;
Future<List<ArtistModel>> getArtists();
Future<void> insertSong(SongModel songModel);
Future<void> insertSongs(List<SongModel> songModels);
Future<void> setSongBlocked(SongModel song, bool blocked);
Future<void> toggleNextSongLink(SongModel song);
Future<SongModel> getSongByPath(String path);
Future<void> deleteAllArtists();
Future<int> insertArtist(ArtistModel artistModel);
Future<void> deleteAllAlbums();
Future<void> deleteAllSongs();
Future<List<ArtistModel>> getArtists();
/// Insert album into the database. Return the ID of the inserted album.
Future<int> insertAlbum(AlbumModel albumModel);
Future<void> deleteAllAlbums();
Future<int> insertArtist(ArtistModel artistModel);
Future<void> deleteAllArtists();
}

View file

@ -1,7 +1,6 @@
import 'dart:io';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:path_provider/path_provider.dart';
import '../../domain/entities/album.dart';
@ -15,48 +14,34 @@ import '../models/artist_model.dart';
import '../models/song_model.dart';
class MusicDataRepositoryImpl implements MusicDataRepository {
MusicDataRepositoryImpl({
@required this.localMusicFetcher,
@required this.musicDataSource,
});
MusicDataRepositoryImpl(
this._localMusicFetcher,
this._musicDataSource,
);
final LocalMusicFetcher localMusicFetcher;
final MusicDataSource musicDataSource;
final LocalMusicFetcher _localMusicFetcher;
final MusicDataSource _musicDataSource;
static final _log = Logger('MusicDataRepository');
@override
Future<List<Artist>> getArtists() async {
return musicDataSource.getArtists();
}
Stream<List<Song>> get songStream => _musicDataSource.songStream;
@override
Future<List<Album>> getAlbums() async {
return musicDataSource.getAlbums();
}
Stream<List<Album>> get albumStream => _musicDataSource.albumStream;
@override
Future<List<Song>> getSongs() async {
return musicDataSource.getSongs();
}
@override
Stream<List<Song>> get songStream => musicDataSource.songStream;
Stream<List<Artist>> get artistStream => _musicDataSource.artistStream;
@override
Stream<List<Song>> getAlbumSongStream(Album album) =>
musicDataSource.getAlbumSongStream(album as AlbumModel);
@override
Future<List<Song>> getSongsFromAlbum(Album album) async {
return musicDataSource.getSongsFromAlbum(album as AlbumModel);
}
_musicDataSource.getAlbumSongStream(album as AlbumModel);
@override
Future<void> updateDatabase() async {
_log.info('updateDatabase called');
final localMusic = await localMusicFetcher.getLocalMusic();
final localMusic = await _localMusicFetcher.getLocalMusic();
await updateArtists(localMusic['ARTISTS'] as List<ArtistModel>);
final albumIdMap = await updateAlbums(localMusic['ALBUMS'] as List<AlbumModel>);
@ -67,18 +52,18 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
@override
Future<void> setSongBlocked(Song song, bool blocked) async {
await musicDataSource.setSongBlocked(song as SongModel, blocked);
await _musicDataSource.setSongBlocked(song as SongModel, blocked);
}
Future<void> updateArtists(List<ArtistModel> artists) async {
await musicDataSource.deleteAllArtists();
await _musicDataSource.deleteAllArtists();
for (final ArtistModel artist in artists) {
await musicDataSource.insertArtist(artist);
await _musicDataSource.insertArtist(artist);
}
}
Future<Map<int, int>> updateAlbums(List<AlbumModel> albums) async {
await musicDataSource.deleteAllAlbums();
await _musicDataSource.deleteAllAlbums();
final Map<int, int> albumIdMap = {};
final Directory dir = await getApplicationSupportDirectory();
@ -88,16 +73,16 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
if (album.albumArtPath == null) {
final String albumArtPath = '${dir.path}/${album.id}';
final file = File(albumArtPath);
final artwork = await localMusicFetcher.getAlbumArtwork(album.id);
final artwork = await _localMusicFetcher.getAlbumArtwork(album.id);
if (artwork.isNotEmpty) {
file.writeAsBytesSync(artwork);
final newAlbum = album.copyWith(albumArtPath: albumArtPath);
newAlbumId = await musicDataSource.insertAlbum(newAlbum);
newAlbumId = await _musicDataSource.insertAlbum(newAlbum);
} else {
newAlbumId = await musicDataSource.insertAlbum(album);
newAlbumId = await _musicDataSource.insertAlbum(album);
}
} else {
newAlbumId = await musicDataSource.insertAlbum(album);
newAlbumId = await _musicDataSource.insertAlbum(album);
}
albumIdMap[album.id] = newAlbumId;
}
@ -119,16 +104,16 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
songsToInsert.add(song.copyWith(albumId: albumIdMap[song.albumId]));
}
}
await musicDataSource.insertSongs(songsToInsert);
await _musicDataSource.insertSongs(songsToInsert);
}
@override
Future<void> toggleNextSongLink(Song song) async {
musicDataSource.toggleNextSongLink(song as SongModel);
_musicDataSource.toggleNextSongLink(song as SongModel);
}
@override
Stream<List<Album>> getArtistAlbumStream(Artist artist) {
return musicDataSource.getArtistAlbumStream(artist as ArtistModel);
return _musicDataSource.getArtistAlbumStream(artist as ArtistModel);
}
}