filter music by directory; ArtistDetailsPage added
This commit is contained in:
parent
26756bb0d5
commit
f0fbf8e526
11 changed files with 183 additions and 11 deletions
|
@ -8,6 +8,7 @@ import '../entities/song.dart';
|
|||
abstract class MusicDataRepository {
|
||||
Stream<List<Song>> get songStream;
|
||||
Stream<List<Song>> getAlbumSongStream(Album album);
|
||||
Stream<List<Album>> getArtistAlbumStream(Artist artist);
|
||||
|
||||
Future<Either<Failure, List<Song>>> getSongs();
|
||||
Future<Either<Failure, List<Song>>> getSongsFromAlbum(Album album);
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:just_audio/just_audio.dart' as ja;
|
|||
import 'domain/repositories/audio_repository.dart';
|
||||
import 'domain/repositories/music_data_repository.dart';
|
||||
import 'domain/repositories/persistent_player_state_repository.dart';
|
||||
import 'domain/repositories/settings_repository.dart';
|
||||
import 'presentation/state/audio_store.dart';
|
||||
import 'presentation/state/music_data_store.dart';
|
||||
import 'presentation/state/navigation_store.dart';
|
||||
|
@ -21,9 +22,11 @@ import 'system/datasources/local_music_fetcher_contract.dart';
|
|||
import 'system/datasources/moor_music_data_source.dart';
|
||||
import 'system/datasources/music_data_source_contract.dart';
|
||||
import 'system/datasources/player_state_data_source.dart';
|
||||
import 'system/datasources/settings_data_source.dart';
|
||||
import 'system/repositories/audio_repository_impl.dart';
|
||||
import 'system/repositories/music_data_repository_impl.dart';
|
||||
import 'system/repositories/persistent_player_state_repository_impl.dart';
|
||||
import 'system/repositories/settings_repository_impl.dart';
|
||||
|
||||
final GetIt getIt = GetIt.instance;
|
||||
|
||||
|
@ -35,6 +38,7 @@ Future<void> setupGetIt() async {
|
|||
() {
|
||||
final musicDataStore = MusicDataStore(
|
||||
musicDataRepository: getIt(),
|
||||
settingsRepository: getIt(),
|
||||
);
|
||||
musicDataStore.init();
|
||||
return musicDataStore;
|
||||
|
@ -71,15 +75,18 @@ Future<void> setupGetIt() async {
|
|||
getIt.registerLazySingleton<PlayerStateRepository>(
|
||||
() => PlayerStateRepositoryImpl(getIt()),
|
||||
);
|
||||
getIt.registerLazySingleton<SettingsRepository>(() => SettingsRepositoryImpl(getIt()));
|
||||
|
||||
// data sources
|
||||
final MoorMusicDataSource moorMusicDataSource = MoorMusicDataSource();
|
||||
getIt.registerLazySingleton<MusicDataSource>(() => moorMusicDataSource);
|
||||
getIt.registerLazySingleton<PlayerStateDataSource>(() => moorMusicDataSource.playerStateDao);
|
||||
getIt.registerLazySingleton<SettingsDataSource>(() => moorMusicDataSource.settingsDao);
|
||||
getIt.registerLazySingleton<LocalMusicFetcher>(
|
||||
() => LocalMusicFetcherImpl(
|
||||
getIt(),
|
||||
getIt(),
|
||||
getIt(),
|
||||
),
|
||||
);
|
||||
getIt.registerLazySingleton<AudioManager>(() => AudioManagerImpl(getIt()));
|
||||
|
|
52
lib/presentation/pages/artist_details_page.dart
Normal file
52
lib/presentation/pages/artist_details_page.dart
Normal file
|
@ -0,0 +1,52 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../domain/entities/album.dart';
|
||||
import '../../domain/entities/artist.dart';
|
||||
import '../state/music_data_store.dart';
|
||||
import '../widgets/album_art_list_tile.dart';
|
||||
import 'album_details_page.dart';
|
||||
|
||||
class ArtistDetailsPage extends StatelessWidget {
|
||||
const ArtistDetailsPage({Key key, @required this.artist}) : super(key: key);
|
||||
|
||||
final Artist artist;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final MusicDataStore musicDataStore = Provider.of<MusicDataStore>(context);
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Observer(
|
||||
builder: (BuildContext context) => ListView.separated(
|
||||
itemCount: musicDataStore.artistAlbumStream.value.length,
|
||||
separatorBuilder: (BuildContext context, int index) => const Divider(
|
||||
height: 4.0,
|
||||
),
|
||||
itemBuilder: (_, int index) {
|
||||
final Album album = musicDataStore.artistAlbumStream.value[index];
|
||||
return AlbumArtListTile(
|
||||
title: album.title,
|
||||
subtitle: album.pubYear.toString(),
|
||||
albumArtPath: album.albumArtPath,
|
||||
onTap: () {
|
||||
musicDataStore.fetchSongsFromAlbum(album);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute<Widget>(
|
||||
builder: (BuildContext context) => AlbumDetailsPage(
|
||||
album: album,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
|
|||
|
||||
import '../../domain/entities/artist.dart';
|
||||
import '../state/music_data_store.dart';
|
||||
import 'artist_details_page.dart';
|
||||
|
||||
class ArtistsPage extends StatefulWidget {
|
||||
const ArtistsPage({Key key}) : super(key: key);
|
||||
|
@ -40,6 +41,17 @@ class _ArtistsPageState extends State<ArtistsPage>
|
|||
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(
|
||||
|
|
|
@ -32,6 +32,9 @@ abstract class _MusicDataStore with Store {
|
|||
@observable
|
||||
ObservableStream<List<Song>> albumSongStream;
|
||||
|
||||
@observable
|
||||
ObservableStream<List<Album>> artistAlbumStream;
|
||||
|
||||
@observable
|
||||
ObservableList<Artist> artists = <Artist>[].asObservable();
|
||||
@observable
|
||||
|
@ -127,6 +130,11 @@ abstract class _MusicDataStore with Store {
|
|||
albumSongStream = _musicDataRepository.getAlbumSongStream(album).asObservable(initialValue: []);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> fetchAlbumsFromArtist(Artist artist) async {
|
||||
artistAlbumStream = _musicDataRepository.getArtistAlbumStream(artist).asObservable(initialValue: []);
|
||||
}
|
||||
|
||||
Future<void> setSongBlocked(Song song, bool blocked) async {
|
||||
await _musicDataRepository.setSongBlocked(song, blocked);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,22 @@ mixin _$MusicDataStore on _MusicDataStore, Store {
|
|||
});
|
||||
}
|
||||
|
||||
final _$artistAlbumStreamAtom =
|
||||
Atom(name: '_MusicDataStore.artistAlbumStream');
|
||||
|
||||
@override
|
||||
ObservableStream<List<Album>> get artistAlbumStream {
|
||||
_$artistAlbumStreamAtom.reportRead();
|
||||
return super.artistAlbumStream;
|
||||
}
|
||||
|
||||
@override
|
||||
set artistAlbumStream(ObservableStream<List<Album>> value) {
|
||||
_$artistAlbumStreamAtom.reportWrite(value, super.artistAlbumStream, () {
|
||||
super.artistAlbumStream = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$artistsAtom = Atom(name: '_MusicDataStore.artists');
|
||||
|
||||
@override
|
||||
|
@ -199,11 +215,21 @@ mixin _$MusicDataStore on _MusicDataStore, Store {
|
|||
.run(() => super.fetchSongsFromAlbum(album));
|
||||
}
|
||||
|
||||
final _$fetchAlbumsFromArtistAsyncAction =
|
||||
AsyncAction('_MusicDataStore.fetchAlbumsFromArtist');
|
||||
|
||||
@override
|
||||
Future<void> fetchAlbumsFromArtist(Artist artist) {
|
||||
return _$fetchAlbumsFromArtistAsyncAction
|
||||
.run(() => super.fetchAlbumsFromArtist(artist));
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''
|
||||
songStream: ${songStream},
|
||||
albumSongStream: ${albumSongStream},
|
||||
artistAlbumStream: ${artistAlbumStream},
|
||||
artists: ${artists},
|
||||
isFetchingArtists: ${isFetchingArtists},
|
||||
albums: ${albums},
|
||||
|
|
|
@ -8,11 +8,13 @@ import '../models/album_model.dart';
|
|||
import '../models/artist_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
import 'local_music_fetcher_contract.dart';
|
||||
import 'settings_data_source.dart';
|
||||
|
||||
class LocalMusicFetcherImpl implements LocalMusicFetcher {
|
||||
LocalMusicFetcherImpl(this._flutterAudioQuery, this._deviceInfo);
|
||||
LocalMusicFetcherImpl(this._flutterAudioQuery, this._settingsDataSource, this._deviceInfo);
|
||||
|
||||
final FlutterAudioQuery _flutterAudioQuery;
|
||||
final SettingsDataSource _settingsDataSource;
|
||||
// CODESMELL: should probably encapsulate the deviceinfoplugin
|
||||
final DeviceInfoPlugin _deviceInfo;
|
||||
|
||||
|
@ -58,4 +60,50 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
|
|||
}
|
||||
return Uint8List(0);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, List>> getLocalMusic() async {
|
||||
final musicDirectories = await _settingsDataSource.getLibraryFolders();
|
||||
|
||||
final songs = await _getFilteredSongs(musicDirectories);
|
||||
final albumTitles = Set<String>.from(songs.map((song) => song.album));
|
||||
final albums = await _getFilteredAlbums(albumTitles);
|
||||
final artistNames = Set<String>.from(albums.map((album) => album.artist));
|
||||
final artists = await _getFilteredArtists(artistNames);
|
||||
|
||||
return {
|
||||
'SONGS': songs,
|
||||
'ALBUMS': albums,
|
||||
'ARTISTS': artists,
|
||||
};
|
||||
}
|
||||
|
||||
Future<List<SongModel>> _getFilteredSongs(Iterable<String> musicDirectories) async {
|
||||
final List<SongInfo> songInfoList = await _flutterAudioQuery.getSongs();
|
||||
return songInfoList
|
||||
.where(
|
||||
(songInfo) =>
|
||||
songInfo.isMusic &&
|
||||
musicDirectories.any((element) => songInfo.filePath.startsWith(element)),
|
||||
)
|
||||
.map((SongInfo songInfo) => SongModel.fromSongInfo(songInfo))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<List<AlbumModel>> _getFilteredAlbums(Iterable<String> albumTitles) async {
|
||||
final List<AlbumInfo> albumInfoList = await _flutterAudioQuery.getAlbums();
|
||||
return albumInfoList
|
||||
.where((albumInfo) => albumTitles.contains(albumInfo.title))
|
||||
.map((AlbumInfo albumInfo) => AlbumModel.fromAlbumInfo(albumInfo))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<List<ArtistModel>> _getFilteredArtists(Iterable<String> artistNames) async {
|
||||
final List<ArtistInfo> artistInfoList = await _flutterAudioQuery.getArtists();
|
||||
return artistInfoList
|
||||
.where((artistInfo) => artistNames.contains(artistInfo.name))
|
||||
.map((ArtistInfo artistInfo) => ArtistModel.fromArtistInfo(artistInfo))
|
||||
.toSet()
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import '../models/artist_model.dart';
|
|||
import '../models/song_model.dart';
|
||||
|
||||
abstract class LocalMusicFetcher {
|
||||
Future<Map<String, List>> getLocalMusic();
|
||||
|
||||
Future<List<ArtistModel>> getArtists();
|
||||
Future<List<AlbumModel>> getAlbums();
|
||||
Future<List<SongModel>> getSongs();
|
||||
|
|
|
@ -149,6 +149,18 @@ class MoorMusicDataSource extends _$MoorMusicDataSource implements MusicDataSour
|
|||
moorSongList.map((moorSong) => SongModel.fromMoorSong(moorSong)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<AlbumModel>> getArtistAlbumStream(ArtistModel artist) {
|
||||
return (select(albums)
|
||||
..where((tbl) => tbl.artist.equals(artist.name))
|
||||
..orderBy([
|
||||
(t) => OrderingTerm(expression: t.title),
|
||||
]))
|
||||
.watch()
|
||||
.map((moorAlbumList) =>
|
||||
moorAlbumList.map((moorAlbum) => AlbumModel.fromMoorAlbum(moorAlbum)).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<SongModel>> getSongsFromAlbum(AlbumModel album) {
|
||||
return (select(songs)
|
||||
|
|
|
@ -7,6 +7,7 @@ abstract class MusicDataSource {
|
|||
|
||||
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);
|
||||
|
|
|
@ -61,9 +61,11 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
|
|||
Future<void> updateDatabase() async {
|
||||
_log.info('updateDatabase called');
|
||||
|
||||
await updateArtists();
|
||||
final albumIdMap = await updateAlbums();
|
||||
await updateSongs(albumIdMap);
|
||||
final localMusic = await localMusicFetcher.getLocalMusic();
|
||||
|
||||
await updateArtists(localMusic['ARTISTS'] as List<ArtistModel>);
|
||||
final albumIdMap = await updateAlbums(localMusic['ALBUMS'] as List<AlbumModel>);
|
||||
await updateSongs(localMusic['SONGS'] as List<SongModel>, albumIdMap);
|
||||
|
||||
_log.info('updateDatabase finished');
|
||||
}
|
||||
|
@ -73,18 +75,15 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
|
|||
await musicDataSource.setSongBlocked(song as SongModel, blocked);
|
||||
}
|
||||
|
||||
Future<void> updateArtists() async {
|
||||
Future<void> updateArtists(List<ArtistModel> artists) async {
|
||||
await musicDataSource.deleteAllArtists();
|
||||
final List<ArtistModel> artists = await localMusicFetcher.getArtists();
|
||||
|
||||
for (final ArtistModel artist in artists) {
|
||||
await musicDataSource.insertArtist(artist);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<int, int>> updateAlbums() async {
|
||||
Future<Map<int, int>> updateAlbums(List<AlbumModel> albums) async {
|
||||
await musicDataSource.deleteAllAlbums();
|
||||
final List<AlbumModel> albums = await localMusicFetcher.getAlbums();
|
||||
final Map<int, int> albumIdMap = {};
|
||||
|
||||
final Directory dir = await getApplicationSupportDirectory();
|
||||
|
@ -111,9 +110,8 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
|
|||
return albumIdMap;
|
||||
}
|
||||
|
||||
Future<void> updateSongs(Map<int, int> albumIdMap) async {
|
||||
Future<void> updateSongs(List<SongModel> songs, Map<int, int> albumIdMap) async {
|
||||
final Directory dir = await getApplicationSupportDirectory();
|
||||
final List<SongModel> songs = await localMusicFetcher.getSongs();
|
||||
|
||||
final List<SongModel> songsToInsert = [];
|
||||
for (final SongModel song in songs) {
|
||||
|
@ -133,4 +131,9 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
|
|||
Future<void> toggleNextSongLink(Song song) async {
|
||||
musicDataSource.toggleNextSongLink(song as SongModel);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<Album>> getArtistAlbumStream(Artist artist) {
|
||||
return musicDataSource.getArtistAlbumStream(artist as ArtistModel);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue