AlbumDetailsPage and queue preparations

This commit is contained in:
Moritz Weber 2020-04-16 08:04:34 +02:00
parent 9609a89c23
commit 4ada7ce70f
12 changed files with 176 additions and 22 deletions

View file

@ -6,6 +6,7 @@ import '../entities/song.dart';
abstract class MusicDataRepository {
Future<Either<Failure, List<Song>>> getSongs();
Future<Either<Failure, List<Song>>> getSongsFromAlbum(Album album);
Future<Either<Failure, List<Album>>> getAlbums();
Future<void> updateDatabase();
}

View file

@ -1,7 +1,13 @@
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/song.dart';
import '../state/audio_store.dart';
import '../state/music_data_store.dart';
import '../utils.dart' as utils;
import '../widgets/album_art_list_tile.dart';
class AlbumDetailsPage extends StatelessWidget {
const AlbumDetailsPage({Key key, @required this.album}) : super(key: key);
@ -10,11 +16,78 @@ class AlbumDetailsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SafeArea(
child: Column(
children: <Widget>[
Image(
image: utils.getAlbumImage(album.albumArtPath),
final MusicDataStore musicDataStore = Provider.of<MusicDataStore>(context);
final AudioStore audioStore = Provider.of<AudioStore>(context);
return Observer(
builder: (BuildContext context) => CustomScrollView(
slivers: <Widget>[
SliverAppBar(
brightness: Brightness.dark,
pinned: true,
expandedHeight: 250.0,
backgroundColor: Colors.grey[900],
iconTheme: IconThemeData(
color: Colors.white,
),
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
titlePadding: const EdgeInsets.only(
bottom: 16.0,
left: 16.0,
right: 16.0,
),
title: Text(
album.title,
style: TextStyle(
fontWeight: FontWeight.w300,
fontSize: 16.0,
color: Colors.white,
),
textAlign: TextAlign.center,
),
background: Stack(
children: [
Positioned(
left: 0,
right: 0,
child: Image(
image: utils.getAlbumImage(album.albumArtPath),
),
),
Container(
color: Colors.black45,
),
],
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(_, int index) {
if (index.isEven) {
final songIndex = (index / 2).round();
final Song song = musicDataStore.albumSongs[songIndex];
return AlbumArtListTile(
title: song.title,
subtitle: '${song.artist}',
albumArtPath: song.albumArtPath,
onTap: () => audioStore.playSong(songIndex, musicDataStore.albumSongs),
);
}
return const Divider(
height: 4.0,
);
},
semanticIndexCallback: (Widget widget, int localIndex) {
if (localIndex.isEven) {
return localIndex ~/ 2;
}
return null;
},
childCount: musicDataStore.albumSongs.length * 2,
),
),
],
),

View file

@ -49,6 +49,7 @@ class AlbumsPage extends StatelessWidget {
subtitle: album.artist,
albumArtPath: album.albumArtPath,
onTap: () {
store.fetchSongsFromAlbum(album);
Navigator.push(
context,
MaterialPageRoute<Widget>(

View file

@ -12,19 +12,22 @@ class LibraryTabContainer extends StatelessWidget {
length: 3,
child: SafeArea(
child: Column(
children: const <Widget>[
TabBar(
tabs: <Tab>[
Tab(
text: 'Artists',
),
Tab(
text: 'Albums',
),
Tab(
text: 'Songs',
),
],
children: <Widget>[
Container(
color: Colors.grey[900],
child: TabBar(
tabs: <Tab>[
Tab(
text: 'Artists',
),
Tab(
text: 'Albums',
),
Tab(
text: 'Songs',
),
],
),
),
Expanded(
child: TabBarView(

View file

@ -18,11 +18,13 @@ class MusicDataStore extends _MusicDataStore with _$MusicDataStore {
}
abstract class _MusicDataStore with Store {
_MusicDataStore(MusicDataRepository _musicDataRepository)
_MusicDataStore(this._musicDataRepository)
: _updateDatabase = UpdateDatabase(_musicDataRepository),
_getAlbums = GetAlbums(_musicDataRepository),
_getSongs = GetSongs(_musicDataRepository);
final MusicDataRepository _musicDataRepository;
bool _initialized = false;
final UpdateDatabase _updateDatabase;
@ -41,6 +43,9 @@ abstract class _MusicDataStore with Store {
@observable
bool isUpdatingDatabase = false;
@observable
ObservableList<Song> albumSongs = <Song>[].asObservable();
@action
Future<void> init() async {
if (!_initialized) {
@ -90,4 +95,14 @@ abstract class _MusicDataStore with Store {
isFetchingSongs = false;
}
@action
Future<void> fetchSongsFromAlbum(Album album) async {
final result = await _musicDataRepository.getSongsFromAlbum(album);
albumSongs.clear();
result.fold(
(_) => albumSongs = <Song>[].asObservable(),
(songList) => albumSongs.addAll(songList),
);
}
}

View file

@ -79,6 +79,23 @@ mixin _$MusicDataStore on _MusicDataStore, Store {
}, _$isUpdatingDatabaseAtom, name: '${_$isUpdatingDatabaseAtom.name}_set');
}
final _$albumSongsAtom = Atom(name: '_MusicDataStore.albumSongs');
@override
ObservableList<Song> get albumSongs {
_$albumSongsAtom.context.enforceReadPolicy(_$albumSongsAtom);
_$albumSongsAtom.reportObserved();
return super.albumSongs;
}
@override
set albumSongs(ObservableList<Song> value) {
_$albumSongsAtom.context.conditionallyRunInAction(() {
super.albumSongs = value;
_$albumSongsAtom.reportChanged();
}, _$albumSongsAtom, name: '${_$albumSongsAtom.name}_set');
}
final _$initAsyncAction = AsyncAction('init');
@override
@ -107,10 +124,18 @@ mixin _$MusicDataStore on _MusicDataStore, Store {
return _$fetchSongsAsyncAction.run(() => super.fetchSongs());
}
final _$fetchSongsFromAlbumAsyncAction = AsyncAction('fetchSongsFromAlbum');
@override
Future<void> fetchSongsFromAlbum(Album album) {
return _$fetchSongsFromAlbumAsyncAction
.run(() => super.fetchSongsFromAlbum(album));
}
@override
String toString() {
final string =
'albumsFuture: ${albumsFuture.toString()},songs: ${songs.toString()},isFetchingSongs: ${isFetchingSongs.toString()},isUpdatingDatabase: ${isUpdatingDatabase.toString()}';
'albumsFuture: ${albumsFuture.toString()},songs: ${songs.toString()},isFetchingSongs: ${isFetchingSongs.toString()},isUpdatingDatabase: ${isUpdatingDatabase.toString()},albumSongs: ${albumSongs.toString()}';
return '{$string}';
}
}

View file

@ -13,9 +13,12 @@ ThemeData theme() => ThemeData(
accentColor: Colors.amberAccent,
// https://api.flutter.dev/flutter/material/TextTheme-class.html
textTheme: const TextTheme(
title: TextStyle(fontSize: 20.0),
headline6: TextStyle(fontSize: 20.0),
),
tabBarTheme: TabBarTheme(
labelColor: Colors.white,
),
iconTheme: IconThemeData(
color: Colors.white,
),
);

View file

@ -37,7 +37,9 @@ class AudioManagerImpl implements AudioManager {
await _startAudioService();
final List<MediaItem> queue = songList.map((s) => s.toMediaItem()).toList();
await AudioService.addQueueItem(queue[index]);
await AudioService.customAction(SET_QUEUE, queue);
// await AudioService.addQueueItem(queue[index]);
AudioService.playFromMediaId(queue[index].id);
}

View file

@ -3,11 +3,14 @@ import 'dart:async';
import 'package:audio_service/audio_service.dart';
import 'package:just_audio/just_audio.dart';
const String SET_QUEUE = 'SET_QUEUE';
class AudioPlayerTask extends BackgroundAudioTask {
final _audioPlayer = AudioPlayer();
final _completer = Completer();
final _mediaItems = <String, MediaItem>{};
final _queue = <MediaItem>[];
Duration _position;
@ -68,6 +71,20 @@ class AudioPlayerTask extends BackgroundAudioTask {
);
await _audioPlayer.pause();
}
@override
Future<void> onCustomAction(String name, arguments) async {
switch (name) {
case SET_QUEUE:
_setQueue(arguments as List<MediaItem>);
break;
default:
}
}
Future<void> _setQueue(List<MediaItem> queue) async {
}
}
MediaControl playControl = const MediaControl(

View file

@ -69,6 +69,13 @@ class MoorMusicDataSource extends _$MoorMusicDataSource
.toList());
}
@override
Future<List<SongModel>> getSongsFromAlbum(AlbumModel album) {
return (select(songs)..where((tbl) => tbl.album.equals(album.title))).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

@ -7,6 +7,7 @@ abstract class MusicDataSource {
Future<void> insertAlbum(AlbumModel albumModel);
Future<List<SongModel>> getSongs();
Future<List<SongModel>> getSongsFromAlbum(AlbumModel album);
Future<bool> songExists(SongModel songModel);
Future<void> insertSong(SongModel songModel);
}

View file

@ -31,6 +31,12 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
(List<SongModel> songs) => Right<Failure, List<SongModel>>(songs));
}
@override
Future<Either<Failure, List<Song>>> getSongsFromAlbum(Album album) async {
return musicDataSource.getSongsFromAlbum(album as AlbumModel).then(
(List<SongModel> songs) => Right<Failure, List<SongModel>>(songs));
}
// TODO: should remove albums that are not longer on the device
@override
Future<void> updateDatabase() async {