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 {
|
abstract class MusicDataRepository {
|
||||||
Future<Either<Failure, List<Song>>> getSongs();
|
Future<Either<Failure, List<Song>>> getSongs();
|
||||||
|
Future<Either<Failure, List<Song>>> getSongsFromAlbum(Album album);
|
||||||
Future<Either<Failure, List<Album>>> getAlbums();
|
Future<Either<Failure, List<Album>>> getAlbums();
|
||||||
Future<void> updateDatabase();
|
Future<void> updateDatabase();
|
||||||
}
|
}
|
|
@ -1,7 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
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/album.dart';
|
||||||
|
import '../../domain/entities/song.dart';
|
||||||
|
import '../state/audio_store.dart';
|
||||||
|
import '../state/music_data_store.dart';
|
||||||
import '../utils.dart' as utils;
|
import '../utils.dart' as utils;
|
||||||
|
import '../widgets/album_art_list_tile.dart';
|
||||||
|
|
||||||
class AlbumDetailsPage extends StatelessWidget {
|
class AlbumDetailsPage extends StatelessWidget {
|
||||||
const AlbumDetailsPage({Key key, @required this.album}) : super(key: key);
|
const AlbumDetailsPage({Key key, @required this.album}) : super(key: key);
|
||||||
|
@ -10,12 +16,79 @@ class AlbumDetailsPage extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SafeArea(
|
final MusicDataStore musicDataStore = Provider.of<MusicDataStore>(context);
|
||||||
child: Column(
|
final AudioStore audioStore = Provider.of<AudioStore>(context);
|
||||||
children: <Widget>[
|
|
||||||
Image(
|
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),
|
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,
|
subtitle: album.artist,
|
||||||
albumArtPath: album.albumArtPath,
|
albumArtPath: album.albumArtPath,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
store.fetchSongsFromAlbum(album);
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute<Widget>(
|
MaterialPageRoute<Widget>(
|
||||||
|
|
|
@ -12,8 +12,10 @@ class LibraryTabContainer extends StatelessWidget {
|
||||||
length: 3,
|
length: 3,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: const <Widget>[
|
children: <Widget>[
|
||||||
TabBar(
|
Container(
|
||||||
|
color: Colors.grey[900],
|
||||||
|
child: TabBar(
|
||||||
tabs: <Tab>[
|
tabs: <Tab>[
|
||||||
Tab(
|
Tab(
|
||||||
text: 'Artists',
|
text: 'Artists',
|
||||||
|
@ -26,6 +28,7 @@ class LibraryTabContainer extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TabBarView(
|
child: TabBarView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
|
|
@ -18,11 +18,13 @@ class MusicDataStore extends _MusicDataStore with _$MusicDataStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _MusicDataStore with Store {
|
abstract class _MusicDataStore with Store {
|
||||||
_MusicDataStore(MusicDataRepository _musicDataRepository)
|
_MusicDataStore(this._musicDataRepository)
|
||||||
: _updateDatabase = UpdateDatabase(_musicDataRepository),
|
: _updateDatabase = UpdateDatabase(_musicDataRepository),
|
||||||
_getAlbums = GetAlbums(_musicDataRepository),
|
_getAlbums = GetAlbums(_musicDataRepository),
|
||||||
_getSongs = GetSongs(_musicDataRepository);
|
_getSongs = GetSongs(_musicDataRepository);
|
||||||
|
|
||||||
|
final MusicDataRepository _musicDataRepository;
|
||||||
|
|
||||||
bool _initialized = false;
|
bool _initialized = false;
|
||||||
|
|
||||||
final UpdateDatabase _updateDatabase;
|
final UpdateDatabase _updateDatabase;
|
||||||
|
@ -41,6 +43,9 @@ abstract class _MusicDataStore with Store {
|
||||||
@observable
|
@observable
|
||||||
bool isUpdatingDatabase = false;
|
bool isUpdatingDatabase = false;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
ObservableList<Song> albumSongs = <Song>[].asObservable();
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
if (!_initialized) {
|
if (!_initialized) {
|
||||||
|
@ -90,4 +95,14 @@ abstract class _MusicDataStore with Store {
|
||||||
|
|
||||||
isFetchingSongs = false;
|
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');
|
}, _$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');
|
final _$initAsyncAction = AsyncAction('init');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -107,10 +124,18 @@ mixin _$MusicDataStore on _MusicDataStore, Store {
|
||||||
return _$fetchSongsAsyncAction.run(() => super.fetchSongs());
|
return _$fetchSongsAsyncAction.run(() => super.fetchSongs());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final _$fetchSongsFromAlbumAsyncAction = AsyncAction('fetchSongsFromAlbum');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> fetchSongsFromAlbum(Album album) {
|
||||||
|
return _$fetchSongsFromAlbumAsyncAction
|
||||||
|
.run(() => super.fetchSongsFromAlbum(album));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
final string =
|
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}';
|
return '{$string}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,12 @@ ThemeData theme() => ThemeData(
|
||||||
accentColor: Colors.amberAccent,
|
accentColor: Colors.amberAccent,
|
||||||
// https://api.flutter.dev/flutter/material/TextTheme-class.html
|
// https://api.flutter.dev/flutter/material/TextTheme-class.html
|
||||||
textTheme: const TextTheme(
|
textTheme: const TextTheme(
|
||||||
title: TextStyle(fontSize: 20.0),
|
headline6: TextStyle(fontSize: 20.0),
|
||||||
),
|
),
|
||||||
tabBarTheme: TabBarTheme(
|
tabBarTheme: TabBarTheme(
|
||||||
labelColor: Colors.white,
|
labelColor: Colors.white,
|
||||||
),
|
),
|
||||||
|
iconTheme: IconThemeData(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -37,7 +37,9 @@ class AudioManagerImpl implements AudioManager {
|
||||||
await _startAudioService();
|
await _startAudioService();
|
||||||
final List<MediaItem> queue = songList.map((s) => s.toMediaItem()).toList();
|
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);
|
AudioService.playFromMediaId(queue[index].id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,14 @@ import 'dart:async';
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:just_audio/just_audio.dart';
|
import 'package:just_audio/just_audio.dart';
|
||||||
|
|
||||||
|
const String SET_QUEUE = 'SET_QUEUE';
|
||||||
|
|
||||||
class AudioPlayerTask extends BackgroundAudioTask {
|
class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
final _audioPlayer = AudioPlayer();
|
final _audioPlayer = AudioPlayer();
|
||||||
final _completer = Completer();
|
final _completer = Completer();
|
||||||
|
|
||||||
final _mediaItems = <String, MediaItem>{};
|
final _mediaItems = <String, MediaItem>{};
|
||||||
|
final _queue = <MediaItem>[];
|
||||||
|
|
||||||
Duration _position;
|
Duration _position;
|
||||||
|
|
||||||
|
@ -68,6 +71,20 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
);
|
);
|
||||||
await _audioPlayer.pause();
|
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(
|
MediaControl playControl = const MediaControl(
|
||||||
|
|
|
@ -69,6 +69,13 @@ class MoorMusicDataSource extends _$MoorMusicDataSource
|
||||||
.toList());
|
.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
|
@override
|
||||||
Future<void> insertSong(SongModel songModel) async {
|
Future<void> insertSong(SongModel songModel) async {
|
||||||
await into(songs).insert(songModel.toSongsCompanion());
|
await into(songs).insert(songModel.toSongsCompanion());
|
||||||
|
|
|
@ -7,6 +7,7 @@ abstract class MusicDataSource {
|
||||||
Future<void> insertAlbum(AlbumModel albumModel);
|
Future<void> insertAlbum(AlbumModel albumModel);
|
||||||
|
|
||||||
Future<List<SongModel>> getSongs();
|
Future<List<SongModel>> getSongs();
|
||||||
|
Future<List<SongModel>> getSongsFromAlbum(AlbumModel album);
|
||||||
Future<bool> songExists(SongModel songModel);
|
Future<bool> songExists(SongModel songModel);
|
||||||
Future<void> insertSong(SongModel songModel);
|
Future<void> insertSong(SongModel songModel);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,12 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
|
||||||
(List<SongModel> songs) => Right<Failure, List<SongModel>>(songs));
|
(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
|
// TODO: should remove albums that are not longer on the device
|
||||||
@override
|
@override
|
||||||
Future<void> updateDatabase() async {
|
Future<void> updateDatabase() async {
|
||||||
|
|
Loading…
Add table
Reference in a new issue