basic music playback implemented
This commit is contained in:
parent
10f9fe19f2
commit
13edbcefd2
16 changed files with 412 additions and 41 deletions
|
@ -1,21 +1,13 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.mosh">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.mosh">
|
||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||
In most cases you can leave this as-is, but you if you want to provide
|
||||
additional functionality it is fine to subclass or reimplement
|
||||
FlutterApplication and put your custom class here. -->
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterApplication"
|
||||
android:label="mosh"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<application android:name="io.flutter.app.FlutterApplication" android:label="mosh" android:icon="@mipmap/ic_launcher">
|
||||
<activity android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
@ -23,8 +15,18 @@
|
|||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
<meta-data android:name="flutterEmbedding" android:value="2" />
|
||||
|
||||
<service android:name="com.ryanheise.audioservice.AudioService">
|
||||
<intent-filter>
|
||||
<action android:name="android.media.browse.MediaBrowserService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver android:name="androidx.media.session.MediaButtonReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
@ -3,4 +3,9 @@ import 'package:equatable/equatable.dart';
|
|||
abstract class Failure extends Equatable {
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class IndexFailure extends Failure {
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
8
lib/domain/repositories/audio_repository.dart
Normal file
8
lib/domain/repositories/audio_repository.dart
Normal file
|
@ -0,0 +1,8 @@
|
|||
import 'package:dartz/dartz.dart';
|
||||
|
||||
import '../../core/error/failures.dart';
|
||||
import '../entities/song.dart';
|
||||
|
||||
abstract class AudioRepository {
|
||||
Future<Either<Failure, void>> playSong(int index, List<Song> songList);
|
||||
}
|
28
lib/domain/usecases/play_song.dart
Normal file
28
lib/domain/usecases/play_song.dart
Normal file
|
@ -0,0 +1,28 @@
|
|||
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];
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_audio_query/flutter_audio_query.dart';
|
||||
import 'package:mosh/system/datasources/local_music_fetcher.dart';
|
||||
import 'package:mosh/system/datasources/moor_music_data_source.dart';
|
||||
import 'package:mosh/system/repositories/music_data_repository_impl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'presentation/pages/home_page.dart';
|
||||
|
@ -11,7 +9,13 @@ import 'presentation/pages/library_page.dart';
|
|||
import 'presentation/pages/settings_page.dart';
|
||||
import 'presentation/state/music_store.dart';
|
||||
import 'presentation/theming.dart';
|
||||
import 'presentation/widgets/audio_service_widget.dart';
|
||||
import 'presentation/widgets/navbar.dart';
|
||||
import 'system/datasources/audio_manager.dart';
|
||||
import 'system/datasources/local_music_fetcher.dart';
|
||||
import 'system/datasources/moor_music_data_source.dart';
|
||||
import 'system/repositories/audio_repository_impl.dart';
|
||||
import 'system/repositories/music_data_repository_impl.dart';
|
||||
|
||||
void main() => runApp(MyApp());
|
||||
|
||||
|
@ -26,12 +30,15 @@ class MyApp extends StatelessWidget {
|
|||
return MaterialApp(
|
||||
title: 'mosh',
|
||||
theme: theme(),
|
||||
home: Provider<MusicStore>(
|
||||
child: RootPage(),
|
||||
create: (BuildContext context) => MusicStore(
|
||||
MusicDataRepositoryImpl(
|
||||
localMusicFetcher: LocalMusicFetcherImpl(FlutterAudioQuery()),
|
||||
musicDataSource: MoorMusicDataSource(),
|
||||
home: AudioServiceWidget(
|
||||
child: Provider<MusicStore>(
|
||||
child: const RootPage(),
|
||||
create: (BuildContext context) => MusicStore(
|
||||
musicDataRepository: MusicDataRepositoryImpl(
|
||||
localMusicFetcher: LocalMusicFetcherImpl(FlutterAudioQuery()),
|
||||
musicDataSource: MoorMusicDataSource(),
|
||||
),
|
||||
audioRepository: AudioRepositoryImpl(AudioManagerImpl()),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -40,7 +47,7 @@ class MyApp extends StatelessWidget {
|
|||
}
|
||||
|
||||
class RootPage extends StatefulWidget {
|
||||
RootPage({Key key}) : super(key: key);
|
||||
const RootPage({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_RootPageState createState() => _RootPageState();
|
||||
|
@ -58,6 +65,8 @@ class _RootPageState extends State<RootPage> {
|
|||
_musicStore.fetchAlbums();
|
||||
_musicStore.fetchSongs();
|
||||
|
||||
AudioService.start(backgroundTaskEntrypoint: _backgroundTaskEntrypoint);
|
||||
|
||||
_pages = <Widget>[
|
||||
HomePage(),
|
||||
LibraryPage(
|
||||
|
@ -72,6 +81,12 @@ class _RootPageState extends State<RootPage> {
|
|||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
AudioService.stop();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
@ -90,3 +105,7 @@ class _RootPageState extends State<RootPage> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _backgroundTaskEntrypoint() {
|
||||
AudioServiceBackground.run(() => AudioPlayerTask());
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
@ -45,7 +46,7 @@ class _SongsPageState extends State<SongsPage>
|
|||
title: song.title,
|
||||
subtitle: '${song.artist} • ${song.album}',
|
||||
albumArtPath: song.albumArtPath,
|
||||
onTap: () {},
|
||||
onTap: () => _playSong(index, songs),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) => const Divider(
|
||||
|
@ -58,4 +59,9 @@ class _SongsPageState extends State<SongsPage>
|
|||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
void _playSong(int index, List<Song> songList) {
|
||||
widget.store.playSong(index, songList);
|
||||
// AudioService.playFromMediaId(songList[index].path);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
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';
|
||||
|
@ -12,19 +15,21 @@ import '../../domain/usecases/update_database.dart';
|
|||
part 'music_store.g.dart';
|
||||
|
||||
class MusicStore extends _MusicStore with _$MusicStore {
|
||||
MusicStore(MusicDataRepository musicDataRepository)
|
||||
: super(musicDataRepository);
|
||||
MusicStore({@required MusicDataRepository musicDataRepository, @required AudioRepository audioRepository})
|
||||
: super(musicDataRepository, audioRepository);
|
||||
}
|
||||
|
||||
abstract class _MusicStore with Store {
|
||||
_MusicStore(MusicDataRepository _musicDataRepository)
|
||||
_MusicStore(MusicDataRepository _musicDataRepository, AudioRepository _audioRepository)
|
||||
: _updateDatabase = UpdateDatabase(_musicDataRepository),
|
||||
_getAlbums = GetAlbums(_musicDataRepository),
|
||||
_getSongs = GetSongs(_musicDataRepository);
|
||||
_getSongs = GetSongs(_musicDataRepository),
|
||||
_playSong = PlaySong(_audioRepository);
|
||||
|
||||
final UpdateDatabase _updateDatabase;
|
||||
final GetAlbums _getAlbums;
|
||||
final GetSongs _getSongs;
|
||||
final PlaySong _playSong;
|
||||
|
||||
@observable
|
||||
ObservableFuture<List<Album>> albumsFuture;
|
||||
|
@ -76,4 +81,9 @@ abstract class _MusicStore with Store {
|
|||
|
||||
isFetchingSongs = false;
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> playSong(int index, List<Song> songList) async {
|
||||
await _playSong(Params(index, songList));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,40 @@ mixin _$MusicStore on _MusicStore, Store {
|
|||
}, _$albumsFutureAtom, name: '${_$albumsFutureAtom.name}_set');
|
||||
}
|
||||
|
||||
final _$songsAtom = Atom(name: '_MusicStore.songs');
|
||||
|
||||
@override
|
||||
ObservableList<Song> get songs {
|
||||
_$songsAtom.context.enforceReadPolicy(_$songsAtom);
|
||||
_$songsAtom.reportObserved();
|
||||
return super.songs;
|
||||
}
|
||||
|
||||
@override
|
||||
set songs(ObservableList<Song> value) {
|
||||
_$songsAtom.context.conditionallyRunInAction(() {
|
||||
super.songs = value;
|
||||
_$songsAtom.reportChanged();
|
||||
}, _$songsAtom, name: '${_$songsAtom.name}_set');
|
||||
}
|
||||
|
||||
final _$isFetchingSongsAtom = Atom(name: '_MusicStore.isFetchingSongs');
|
||||
|
||||
@override
|
||||
bool get isFetchingSongs {
|
||||
_$isFetchingSongsAtom.context.enforceReadPolicy(_$isFetchingSongsAtom);
|
||||
_$isFetchingSongsAtom.reportObserved();
|
||||
return super.isFetchingSongs;
|
||||
}
|
||||
|
||||
@override
|
||||
set isFetchingSongs(bool value) {
|
||||
_$isFetchingSongsAtom.context.conditionallyRunInAction(() {
|
||||
super.isFetchingSongs = value;
|
||||
_$isFetchingSongsAtom.reportChanged();
|
||||
}, _$isFetchingSongsAtom, name: '${_$isFetchingSongsAtom.name}_set');
|
||||
}
|
||||
|
||||
final _$isUpdatingDatabaseAtom = Atom(name: '_MusicStore.isUpdatingDatabase');
|
||||
|
||||
@override
|
||||
|
@ -51,22 +85,31 @@ mixin _$MusicStore on _MusicStore, Store {
|
|||
return _$updateDatabaseAsyncAction.run(() => super.updateDatabase());
|
||||
}
|
||||
|
||||
final _$_MusicStoreActionController = ActionController(name: '_MusicStore');
|
||||
final _$fetchAlbumsAsyncAction = AsyncAction('fetchAlbums');
|
||||
|
||||
@override
|
||||
Future<void> fetchAlbums() {
|
||||
final _$actionInfo = _$_MusicStoreActionController.startAction();
|
||||
try {
|
||||
return super.fetchAlbums();
|
||||
} finally {
|
||||
_$_MusicStoreActionController.endAction(_$actionInfo);
|
||||
}
|
||||
return _$fetchAlbumsAsyncAction.run(() => super.fetchAlbums());
|
||||
}
|
||||
|
||||
final _$fetchSongsAsyncAction = AsyncAction('fetchSongs');
|
||||
|
||||
@override
|
||||
Future<void> fetchSongs() {
|
||||
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 =
|
||||
'albumsFuture: ${albumsFuture.toString()},isUpdatingDatabase: ${isUpdatingDatabase.toString()}';
|
||||
'albumsFuture: ${albumsFuture.toString()},songs: ${songs.toString()},isFetchingSongs: ${isFetchingSongs.toString()},isUpdatingDatabase: ${isUpdatingDatabase.toString()}';
|
||||
return '{$string}';
|
||||
}
|
||||
}
|
||||
|
|
53
lib/presentation/widgets/audio_service_widget.dart
Normal file
53
lib/presentation/widgets/audio_service_widget.dart
Normal file
|
@ -0,0 +1,53 @@
|
|||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AudioServiceWidget extends StatefulWidget {
|
||||
const AudioServiceWidget({@required this.child});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
_AudioServiceWidgetState createState() => _AudioServiceWidgetState();
|
||||
}
|
||||
|
||||
class _AudioServiceWidgetState extends State<AudioServiceWidget>
|
||||
with WidgetsBindingObserver {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
AudioService.connect();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
AudioService.disconnect();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
switch (state) {
|
||||
case AppLifecycleState.resumed:
|
||||
AudioService.connect();
|
||||
break;
|
||||
case AppLifecycleState.paused:
|
||||
AudioService.disconnect();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
AudioService.disconnect();
|
||||
return true;
|
||||
},
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
48
lib/system/datasources/audio_manager.dart
Normal file
48
lib/system/datasources/audio_manager.dart
Normal file
|
@ -0,0 +1,48 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
|
||||
import '../models/song_model.dart';
|
||||
import 'audio_manager_contract.dart';
|
||||
|
||||
class AudioManagerImpl implements AudioManager {
|
||||
@override
|
||||
Future<void> playSong(int index, List<SongModel> songList) async {
|
||||
final List<MediaItem> queue = songList.map((s) => s.toMediaItem()).toList();
|
||||
|
||||
// await AudioService.addQueueItem(queue[index]);
|
||||
AudioService.playFromMediaId(queue[index].id);
|
||||
}
|
||||
}
|
||||
|
||||
class AudioPlayerTask extends BackgroundAudioTask {
|
||||
final _audioPlayer = AudioPlayer();
|
||||
final _completer = Completer();
|
||||
|
||||
final _mediaItems = <String, MediaItem>{};
|
||||
|
||||
@override
|
||||
Future<void> onStart() async {
|
||||
print('onStart');
|
||||
await _completer.future;
|
||||
}
|
||||
|
||||
@override
|
||||
void onStop() {
|
||||
_audioPlayer.stop();
|
||||
_completer.complete();
|
||||
}
|
||||
|
||||
@override
|
||||
void onAddQueueItem(MediaItem mediaItem) {
|
||||
_mediaItems[mediaItem.id] = mediaItem;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPlayFromMediaId(String mediaId) async {
|
||||
// _audioPlayer.setFilePath(_mediaItems[mediaId].id);
|
||||
await _audioPlayer.setFilePath(mediaId);
|
||||
_audioPlayer.play();
|
||||
}
|
||||
}
|
5
lib/system/datasources/audio_manager_contract.dart
Normal file
5
lib/system/datasources/audio_manager_contract.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
import '../models/song_model.dart';
|
||||
|
||||
abstract class AudioManager {
|
||||
Future<void> playSong(int index, List<SongModel> songList);
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:flutter_audio_query/flutter_audio_query.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moor/moor.dart';
|
||||
|
@ -55,4 +56,13 @@ class SongModel extends Song {
|
|||
albumArtPath: Value(albumArtPath),
|
||||
trackNumber: Value(trackNumber),
|
||||
);
|
||||
|
||||
// TODO: test!
|
||||
MediaItem toMediaItem() => MediaItem(
|
||||
title: title,
|
||||
album: album,
|
||||
artist: artist,
|
||||
artUri: albumArtPath,
|
||||
id: path,
|
||||
);
|
||||
}
|
||||
|
|
24
lib/system/repositories/audio_repository_impl.dart
Normal file
24
lib/system/repositories/audio_repository_impl.dart
Normal file
|
@ -0,0 +1,24 @@
|
|||
import 'package:dartz/dartz.dart';
|
||||
import 'package:mosh/system/models/song_model.dart';
|
||||
|
||||
import '../../core/error/failures.dart';
|
||||
import '../../domain/entities/song.dart';
|
||||
import '../../domain/repositories/audio_repository.dart';
|
||||
import '../datasources/audio_manager_contract.dart';
|
||||
|
||||
class AudioRepositoryImpl implements AudioRepository {
|
||||
AudioRepositoryImpl(this._audioManager);
|
||||
|
||||
final AudioManager _audioManager;
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> playSong(int index, List<Song> songList) async {
|
||||
final List<SongModel> songModelList = songList.map((song) => song as SongModel).toList();
|
||||
|
||||
if (0 <= index && index < songList.length) {
|
||||
await _audioManager.playSong(index, songModelList);
|
||||
return Right(null);
|
||||
}
|
||||
return Left(IndexFailure());
|
||||
}
|
||||
}
|
51
test/domain/usecases/play_song_test.dart
Normal file
51
test/domain/usecases/play_song_test.dart
Normal file
|
@ -0,0 +1,51 @@
|
|||
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);
|
||||
},
|
||||
);
|
||||
}
|
53
test/system/repositories/audio_repository_impl_test.dart
Normal file
53
test/system/repositories/audio_repository_impl_test.dart
Normal file
|
@ -0,0 +1,53 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:mosh/system/datasources/audio_manager_contract.dart';
|
||||
import 'package:mosh/system/models/song_model.dart';
|
||||
import 'package:mosh/system/repositories/audio_repository_impl.dart';
|
||||
|
||||
import '../../test_constants.dart';
|
||||
|
||||
class MockAudioManager extends Mock implements AudioManager {}
|
||||
|
||||
void main() {
|
||||
AudioRepositoryImpl repository;
|
||||
MockAudioManager mockAudioManager;
|
||||
|
||||
const int tIndex = 0;
|
||||
final List<SongModel> tSongList = setupSongList();
|
||||
|
||||
setUp(() {
|
||||
mockAudioManager = MockAudioManager();
|
||||
repository = AudioRepositoryImpl(mockAudioManager);
|
||||
});
|
||||
|
||||
group('playSong', () {
|
||||
test(
|
||||
'should forward index and song list to AudioManager',
|
||||
() async {
|
||||
// act
|
||||
await repository.playSong(tIndex, tSongList);
|
||||
// assert
|
||||
verify(mockAudioManager.playSong(tIndex, tSongList));
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
List<SongModel> setupSongList() => [
|
||||
SongModel(
|
||||
title: SONG_TITLE_3,
|
||||
album: ALBUM_TITLE_3,
|
||||
artist: ARTIST_3,
|
||||
path: PATH_3,
|
||||
trackNumber: TRACKNUMBER_3,
|
||||
albumArtPath: ALBUM_ART_PATH_3,
|
||||
),
|
||||
SongModel(
|
||||
title: SONG_TITLE_4,
|
||||
album: ALBUM_TITLE_4,
|
||||
artist: ARTIST_4,
|
||||
path: PATH_4,
|
||||
trackNumber: TRACKNUMBER_4,
|
||||
albumArtPath: ALBUM_ART_PATH_4,
|
||||
),
|
||||
];
|
|
@ -88,7 +88,8 @@ void main() {
|
|||
'should get songs from MusicDataSource',
|
||||
() async {
|
||||
// arrange
|
||||
when(mockMusicDataSource.getSongs()).thenAnswer((_) async => tEmptySongList);
|
||||
when(mockMusicDataSource.getSongs())
|
||||
.thenAnswer((_) async => tEmptySongList);
|
||||
// act
|
||||
final result = await repository.getSongs();
|
||||
// assert
|
||||
|
@ -99,13 +100,18 @@ void main() {
|
|||
});
|
||||
|
||||
group('updateDatabase', () {
|
||||
setUp(() {});
|
||||
setUp(() {
|
||||
when(mockLocalMusicFetcher.getSongs())
|
||||
.thenAnswer((_) async => tEmptySongList);
|
||||
when(mockMusicDataSource.songExists(any)).thenAnswer((_) async => false);
|
||||
});
|
||||
|
||||
test(
|
||||
'should fetch list of albums from LocalMusicFetcher',
|
||||
() async {
|
||||
when(mockLocalMusicFetcher.getAlbums())
|
||||
.thenAnswer((_) async => tAlbumList);
|
||||
|
||||
when(mockMusicDataSource.albumExists(any))
|
||||
.thenAnswer((_) async => false);
|
||||
// act
|
||||
|
|
Loading…
Add table
Reference in a new issue