notification controls, AudioStore
This commit is contained in:
parent
5116e7ea1e
commit
c425b63fc2
12 changed files with 143 additions and 124 deletions
|
@ -1,28 +0,0 @@
|
|||
import 'package:dartz/dartz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:mosh/domain/repositories/audio_repository.dart';
|
||||
|
||||
import '../../core/error/failures.dart';
|
||||
import '../../core/usecase.dart';
|
||||
import '../entities/song.dart';
|
||||
|
||||
class PlaySong implements UseCase<void, Params> {
|
||||
PlaySong(this.audioRepository);
|
||||
|
||||
final AudioRepository audioRepository;
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> call(Params params) async {
|
||||
return audioRepository.playSong(params.index, params.songList);
|
||||
}
|
||||
}
|
||||
|
||||
class Params extends Equatable {
|
||||
const Params(this.index, this.songList);
|
||||
|
||||
final int index;
|
||||
final List<Song> songList;
|
||||
|
||||
@override
|
||||
List<Object> get props => [index, songList];
|
||||
}
|
|
@ -62,7 +62,11 @@ class _RootPageState extends State<RootPage> {
|
|||
_musicStore.fetchSongs();
|
||||
|
||||
// TODO: don't do this here...
|
||||
AudioService.start(backgroundTaskEntrypoint: _backgroundTaskEntrypoint);
|
||||
AudioService.start(
|
||||
backgroundTaskEntrypoint: _backgroundTaskEntrypoint,
|
||||
enableQueue: true,
|
||||
androidStopOnRemoveTask: true,
|
||||
);
|
||||
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
|
|
@ -3,11 +3,12 @@ import 'package:flutter_mobx/flutter_mobx.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../domain/entities/song.dart';
|
||||
import '../state/audio_store.dart';
|
||||
import '../state/music_data_store.dart';
|
||||
import '../widgets/album_art_list_tile.dart';
|
||||
|
||||
class SongsPage extends StatefulWidget {
|
||||
SongsPage({Key key}) : super(key: key);
|
||||
const SongsPage({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SongsPageState createState() => _SongsPageState();
|
||||
|
@ -19,12 +20,13 @@ class _SongsPageState extends State<SongsPage>
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
print('SongsPage.build');
|
||||
final MusicDataStore store = Provider.of<MusicDataStore>(context);
|
||||
final MusicDataStore musicDataStore = Provider.of<MusicDataStore>(context);
|
||||
final AudioStore audioStore = Provider.of<AudioStore>(context);
|
||||
|
||||
super.build(context);
|
||||
return Observer(builder: (_) {
|
||||
print('SongsPage.build -> Observer.builder');
|
||||
final bool isFetching = store.isFetchingSongs;
|
||||
final bool isFetching = musicDataStore.isFetchingSongs;
|
||||
|
||||
if (isFetching) {
|
||||
return Column(
|
||||
|
@ -35,7 +37,7 @@ class _SongsPageState extends State<SongsPage>
|
|||
],
|
||||
);
|
||||
} else {
|
||||
final List<Song> songs = store.songs;
|
||||
final List<Song> songs = musicDataStore.songs;
|
||||
return ListView.separated(
|
||||
itemCount: songs.length,
|
||||
itemBuilder: (_, int index) {
|
||||
|
@ -44,7 +46,7 @@ class _SongsPageState extends State<SongsPage>
|
|||
title: song.title,
|
||||
subtitle: '${song.artist} • ${song.album}',
|
||||
albumArtPath: song.albumArtPath,
|
||||
onTap: () => store.playSong(index, songs),
|
||||
onTap: () => audioStore.playSong(index, songs),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) => const Divider(
|
||||
|
|
26
lib/presentation/state/audio_store.dart
Normal file
26
lib/presentation/state/audio_store.dart
Normal file
|
@ -0,0 +1,26 @@
|
|||
import 'package:meta/meta.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
import '../../domain/entities/song.dart';
|
||||
import '../../domain/repositories/audio_repository.dart';
|
||||
import '../../domain/repositories/music_data_repository.dart';
|
||||
|
||||
part 'audio_store.g.dart';
|
||||
|
||||
class AudioStore extends _AudioStore with _$AudioStore {
|
||||
AudioStore({@required MusicDataRepository musicDataRepository, @required AudioRepository audioRepository})
|
||||
: super(musicDataRepository, audioRepository);
|
||||
}
|
||||
|
||||
abstract class _AudioStore with Store {
|
||||
_AudioStore(this._musicDataRepository, this._audioRepository);
|
||||
|
||||
final MusicDataRepository _musicDataRepository;
|
||||
final AudioRepository _audioRepository;
|
||||
|
||||
@action
|
||||
Future<void> playSong(int index, List<Song> songList) async {
|
||||
_audioRepository.playSong(index, songList);
|
||||
}
|
||||
|
||||
}
|
24
lib/presentation/state/audio_store.g.dart
Normal file
24
lib/presentation/state/audio_store.g.dart
Normal file
|
@ -0,0 +1,24 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'audio_store.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// StoreGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: non_constant_identifier_names, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
|
||||
|
||||
mixin _$AudioStore on _AudioStore, Store {
|
||||
final _$playSongAsyncAction = AsyncAction('playSong');
|
||||
|
||||
@override
|
||||
Future<void> playSong(int index, List<Song> songList) {
|
||||
return _$playSongAsyncAction.run(() => super.playSong(index, songList));
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final string = '';
|
||||
return '{$string}';
|
||||
}
|
||||
}
|
|
@ -1,12 +1,10 @@
|
|||
import 'package:dartz/dartz.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:mosh/domain/usecases/play_song.dart';
|
||||
|
||||
import '../../core/error/failures.dart';
|
||||
import '../../domain/entities/album.dart';
|
||||
import '../../domain/entities/song.dart';
|
||||
import '../../domain/repositories/audio_repository.dart';
|
||||
import '../../domain/repositories/music_data_repository.dart';
|
||||
import '../../domain/usecases/get_albums.dart';
|
||||
import '../../domain/usecases/get_songs.dart';
|
||||
|
@ -14,22 +12,20 @@ import '../../domain/usecases/update_database.dart';
|
|||
|
||||
part 'music_data_store.g.dart';
|
||||
|
||||
class MusicDataStore extends _MusicStore with _$MusicDataStore {
|
||||
MusicDataStore({@required MusicDataRepository musicDataRepository, @required AudioRepository audioRepository})
|
||||
: super(musicDataRepository, audioRepository);
|
||||
class MusicDataStore extends _MusicDataStore with _$MusicDataStore {
|
||||
MusicDataStore({@required MusicDataRepository musicDataRepository})
|
||||
: super(musicDataRepository);
|
||||
}
|
||||
|
||||
abstract class _MusicStore with Store {
|
||||
_MusicStore(MusicDataRepository _musicDataRepository, AudioRepository _audioRepository)
|
||||
abstract class _MusicDataStore with Store {
|
||||
_MusicDataStore(MusicDataRepository _musicDataRepository)
|
||||
: _updateDatabase = UpdateDatabase(_musicDataRepository),
|
||||
_getAlbums = GetAlbums(_musicDataRepository),
|
||||
_getSongs = GetSongs(_musicDataRepository),
|
||||
_playSong = PlaySong(_audioRepository);
|
||||
_getSongs = GetSongs(_musicDataRepository);
|
||||
|
||||
final UpdateDatabase _updateDatabase;
|
||||
final GetAlbums _getAlbums;
|
||||
final GetSongs _getSongs;
|
||||
final PlaySong _playSong;
|
||||
|
||||
@observable
|
||||
ObservableFuture<List<Album>> albumsFuture;
|
||||
|
@ -81,9 +77,4 @@ abstract class _MusicStore with Store {
|
|||
|
||||
isFetchingSongs = false;
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> playSong(int index, List<Song> songList) async {
|
||||
await _playSong(Params(index, songList));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ part of 'music_data_store.dart';
|
|||
|
||||
// ignore_for_file: non_constant_identifier_names, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
|
||||
|
||||
mixin _$MusicDataStore on _MusicStore, Store {
|
||||
final _$albumsFutureAtom = Atom(name: '_MusicStore.albumsFuture');
|
||||
mixin _$MusicDataStore on _MusicDataStore, Store {
|
||||
final _$albumsFutureAtom = Atom(name: '_MusicDataStore.albumsFuture');
|
||||
|
||||
@override
|
||||
ObservableFuture<List<Album>> get albumsFuture {
|
||||
|
@ -26,7 +26,7 @@ mixin _$MusicDataStore on _MusicStore, Store {
|
|||
}, _$albumsFutureAtom, name: '${_$albumsFutureAtom.name}_set');
|
||||
}
|
||||
|
||||
final _$songsAtom = Atom(name: '_MusicStore.songs');
|
||||
final _$songsAtom = Atom(name: '_MusicDataStore.songs');
|
||||
|
||||
@override
|
||||
ObservableList<Song> get songs {
|
||||
|
@ -43,7 +43,7 @@ mixin _$MusicDataStore on _MusicStore, Store {
|
|||
}, _$songsAtom, name: '${_$songsAtom.name}_set');
|
||||
}
|
||||
|
||||
final _$isFetchingSongsAtom = Atom(name: '_MusicStore.isFetchingSongs');
|
||||
final _$isFetchingSongsAtom = Atom(name: '_MusicDataStore.isFetchingSongs');
|
||||
|
||||
@override
|
||||
bool get isFetchingSongs {
|
||||
|
@ -60,7 +60,8 @@ mixin _$MusicDataStore on _MusicStore, Store {
|
|||
}, _$isFetchingSongsAtom, name: '${_$isFetchingSongsAtom.name}_set');
|
||||
}
|
||||
|
||||
final _$isUpdatingDatabaseAtom = Atom(name: '_MusicStore.isUpdatingDatabase');
|
||||
final _$isUpdatingDatabaseAtom =
|
||||
Atom(name: '_MusicDataStore.isUpdatingDatabase');
|
||||
|
||||
@override
|
||||
bool get isUpdatingDatabase {
|
||||
|
@ -99,13 +100,6 @@ mixin _$MusicDataStore on _MusicStore, Store {
|
|||
return _$fetchSongsAsyncAction.run(() => super.fetchSongs());
|
||||
}
|
||||
|
||||
final _$playSongAsyncAction = AsyncAction('playSong');
|
||||
|
||||
@override
|
||||
Future<void> playSong(int index, List<Song> songList) {
|
||||
return _$playSongAsyncAction.run(() => super.playSong(index, songList));
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final string =
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_audio_query/flutter_audio_query.dart';
|
||||
import 'package:mosh/domain/repositories/audio_repository.dart';
|
||||
import 'package:mosh/domain/repositories/music_data_repository.dart';
|
||||
import 'package:mosh/presentation/state/audio_store.dart';
|
||||
import 'package:mosh/presentation/state/music_data_store.dart';
|
||||
import 'package:mosh/system/datasources/audio_manager.dart';
|
||||
import 'package:mosh/system/datasources/local_music_fetcher.dart';
|
||||
|
@ -15,15 +18,29 @@ class InjectionWidget extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Provider<MusicDataStore>(
|
||||
final MusicDataRepository musicDataRepository = MusicDataRepositoryImpl(
|
||||
localMusicFetcher: LocalMusicFetcherImpl(FlutterAudioQuery()),
|
||||
musicDataSource: MoorMusicDataSource(),
|
||||
);
|
||||
|
||||
final AudioRepository audioRepository =
|
||||
AudioRepositoryImpl(AudioManagerImpl());
|
||||
|
||||
return MultiProvider(
|
||||
child: child,
|
||||
create: (BuildContext context) => MusicDataStore(
|
||||
musicDataRepository: MusicDataRepositoryImpl(
|
||||
localMusicFetcher: LocalMusicFetcherImpl(FlutterAudioQuery()),
|
||||
musicDataSource: MoorMusicDataSource(),
|
||||
providers: [
|
||||
Provider<MusicDataStore>(
|
||||
create: (BuildContext context) => MusicDataStore(
|
||||
musicDataRepository: musicDataRepository,
|
||||
),
|
||||
),
|
||||
audioRepository: AudioRepositoryImpl(AudioManagerImpl()),
|
||||
),
|
||||
Provider<AudioStore>(
|
||||
create: (BuildContext context) => AudioStore(
|
||||
musicDataRepository: musicDataRepository,
|
||||
audioRepository: audioRepository,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ class AudioManagerImpl implements AudioManager {
|
|||
Future<void> playSong(int index, List<SongModel> songList) async {
|
||||
final List<MediaItem> queue = songList.map((s) => s.toMediaItem()).toList();
|
||||
|
||||
// await AudioService.addQueueItem(queue[index]);
|
||||
await AudioService.addQueueItem(queue[index]);
|
||||
AudioService.playFromMediaId(queue[index].id);
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,12 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
@override
|
||||
Future<void> onStart() async {
|
||||
print('onStart');
|
||||
|
||||
AudioServiceBackground.setState(
|
||||
controls: [pauseControl, stopControl],
|
||||
basicState: BasicPlaybackState.playing,
|
||||
);
|
||||
|
||||
await _completer.future;
|
||||
}
|
||||
|
||||
|
@ -41,8 +47,43 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
|
||||
@override
|
||||
Future<void> onPlayFromMediaId(String mediaId) async {
|
||||
// _audioPlayer.setFilePath(_mediaItems[mediaId].id);
|
||||
|
||||
// await _audioPlayer.setFilePath(_mediaItems[mediaId]);
|
||||
AudioServiceBackground.setMediaItem(_mediaItems[mediaId]);
|
||||
|
||||
await _audioPlayer.setFilePath(mediaId);
|
||||
_audioPlayer.play();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPlay() async {
|
||||
AudioServiceBackground.setState(
|
||||
controls: [pauseControl, stopControl],
|
||||
basicState: BasicPlaybackState.playing);
|
||||
_audioPlayer.play();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPause() async {
|
||||
AudioServiceBackground.setState(
|
||||
controls: [playControl, stopControl],
|
||||
basicState: BasicPlaybackState.paused);
|
||||
await _audioPlayer.pause();
|
||||
}
|
||||
}
|
||||
|
||||
MediaControl playControl = const MediaControl(
|
||||
androidIcon: 'drawable/ic_action_play_arrow',
|
||||
label: 'Play',
|
||||
action: MediaAction.play,
|
||||
);
|
||||
MediaControl pauseControl = const MediaControl(
|
||||
androidIcon: 'drawable/ic_action_pause',
|
||||
label: 'Pause',
|
||||
action: MediaAction.pause,
|
||||
);
|
||||
MediaControl stopControl = const MediaControl(
|
||||
androidIcon: 'drawable/ic_action_stop',
|
||||
label: 'Stop',
|
||||
action: MediaAction.stop,
|
||||
);
|
||||
|
|
|
@ -57,12 +57,11 @@ class SongModel extends Song {
|
|||
trackNumber: Value(trackNumber),
|
||||
);
|
||||
|
||||
// TODO: test!
|
||||
MediaItem toMediaItem() => MediaItem(
|
||||
title: title,
|
||||
album: album,
|
||||
artist: artist,
|
||||
artUri: albumArtPath,
|
||||
artUri: 'file://$albumArtPath',
|
||||
id: path,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:mosh/domain/entities/song.dart';
|
||||
import 'package:mosh/domain/repositories/audio_repository.dart';
|
||||
import 'package:mosh/domain/usecases/play_song.dart';
|
||||
|
||||
import '../../test_constants.dart';
|
||||
|
||||
class MockAudioRepository extends Mock implements AudioRepository {}
|
||||
|
||||
void main() {
|
||||
PlaySong usecase;
|
||||
MockAudioRepository mockAudioRepository;
|
||||
|
||||
setUp(() {
|
||||
mockAudioRepository = MockAudioRepository();
|
||||
usecase = PlaySong(mockAudioRepository);
|
||||
});
|
||||
|
||||
const tIndex = 0;
|
||||
|
||||
final tSongList = <Song>[
|
||||
Song(
|
||||
album: ALBUM_TITLE_3,
|
||||
artist: ARTIST_3,
|
||||
title: SONG_TITLE_3,
|
||||
path: PATH_3,
|
||||
albumArtPath: ALBUM_ART_PATH_3,
|
||||
trackNumber: TRACKNUMBER_3,
|
||||
),
|
||||
Song(
|
||||
album: ALBUM_TITLE_4,
|
||||
artist: ARTIST_4,
|
||||
title: SONG_TITLE_4,
|
||||
path: PATH_4,
|
||||
albumArtPath: ALBUM_ART_PATH_4,
|
||||
trackNumber: TRACKNUMBER_4,
|
||||
),
|
||||
];
|
||||
|
||||
test(
|
||||
'should forward index and song list to repository',
|
||||
() async {
|
||||
// act
|
||||
await usecase(Params(tIndex, tSongList));
|
||||
// assert
|
||||
verify(mockAudioRepository.playSong(tIndex, tSongList));
|
||||
verifyNoMoreInteractions(mockAudioRepository);
|
||||
},
|
||||
);
|
||||
}
|
|
@ -74,7 +74,7 @@ void main() {
|
|||
title: SONG_TITLE_3,
|
||||
album: ALBUM_TITLE_3,
|
||||
artist: ARTIST_3,
|
||||
artUri: ALBUM_ART_PATH_3,
|
||||
artUri: 'file://$ALBUM_ART_PATH_3',
|
||||
);
|
||||
|
||||
final songModel = SongModel(
|
||||
|
|
Loading…
Add table
Reference in a new issue