AlbumDetailsPage and queue preparations
This commit is contained in:
parent
9609a89c23
commit
4ada7ce70f
12 changed files with 176 additions and 22 deletions
|
@ -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();
|
||||
}
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -49,6 +49,7 @@ class AlbumsPage extends StatelessWidget {
|
|||
subtitle: album.artist,
|
||||
albumArtPath: album.albumArtPath,
|
||||
onTap: () {
|
||||
store.fetchSongsFromAlbum(album);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute<Widget>(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue