implemented currentSong
This commit is contained in:
parent
c425b63fc2
commit
7fbd4a07a2
13 changed files with 406 additions and 212 deletions
|
@ -4,5 +4,7 @@ import '../../core/error/failures.dart';
|
||||||
import '../entities/song.dart';
|
import '../entities/song.dart';
|
||||||
|
|
||||||
abstract class AudioRepository {
|
abstract class AudioRepository {
|
||||||
|
Stream<Song> get watchCurrentSong;
|
||||||
|
|
||||||
Future<Either<Failure, void>> playSong(int index, List<Song> songList);
|
Future<Either<Failure, void>> playSong(int index, List<Song> songList);
|
||||||
}
|
}
|
|
@ -1,17 +1,17 @@
|
||||||
import 'package:audio_service/audio_service.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:mosh/presentation/widgets/injection_widget.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'presentation/pages/currently_playing.dart';
|
||||||
import 'presentation/pages/home_page.dart';
|
import 'presentation/pages/home_page.dart';
|
||||||
import 'presentation/pages/library_page.dart';
|
import 'presentation/pages/library_page.dart';
|
||||||
import 'presentation/pages/settings_page.dart';
|
import 'presentation/pages/settings_page.dart';
|
||||||
|
import 'presentation/state/audio_store.dart';
|
||||||
import 'presentation/state/music_data_store.dart';
|
import 'presentation/state/music_data_store.dart';
|
||||||
import 'presentation/theming.dart';
|
import 'presentation/theming.dart';
|
||||||
import 'presentation/widgets/audio_service_widget.dart';
|
import 'presentation/widgets/audio_service_widget.dart';
|
||||||
|
import 'presentation/widgets/injection_widget.dart';
|
||||||
import 'presentation/widgets/navbar.dart';
|
import 'presentation/widgets/navbar.dart';
|
||||||
import 'system/datasources/audio_manager.dart';
|
|
||||||
|
|
||||||
void main() => runApp(MyApp());
|
void main() => runApp(MyApp());
|
||||||
|
|
||||||
|
@ -23,12 +23,16 @@ class MyApp extends StatelessWidget {
|
||||||
DeviceOrientation.portraitUp,
|
DeviceOrientation.portraitUp,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return MaterialApp(
|
return InjectionWidget(
|
||||||
title: 'mucke',
|
child: AudioServiceWidget(
|
||||||
theme: theme(),
|
child: MaterialApp(
|
||||||
home: const AudioServiceWidget(
|
title: 'mucke',
|
||||||
child: InjectionWidget(
|
theme: theme(),
|
||||||
child: RootPage(),
|
initialRoute: '/',
|
||||||
|
routes: {
|
||||||
|
'/': (context) => const RootPage(),
|
||||||
|
'/playing': (context) => const CurrentlyPlayingPage(),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -43,7 +47,7 @@ class RootPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RootPageState extends State<RootPage> {
|
class _RootPageState extends State<RootPage> {
|
||||||
var navIndex = 0;
|
var navIndex = 1;
|
||||||
|
|
||||||
final List<Widget> _pages = <Widget>[
|
final List<Widget> _pages = <Widget>[
|
||||||
HomePage(),
|
HomePage(),
|
||||||
|
@ -61,16 +65,19 @@ class _RootPageState extends State<RootPage> {
|
||||||
_musicStore.fetchAlbums();
|
_musicStore.fetchAlbums();
|
||||||
_musicStore.fetchSongs();
|
_musicStore.fetchSongs();
|
||||||
|
|
||||||
// TODO: don't do this here...
|
final AudioStore _audioStore = Provider.of<AudioStore>(context);
|
||||||
AudioService.start(
|
_audioStore.init();
|
||||||
backgroundTaskEntrypoint: _backgroundTaskEntrypoint,
|
|
||||||
enableQueue: true,
|
|
||||||
androidStopOnRemoveTask: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
final AudioStore _audioStore = Provider.of<AudioStore>(context);
|
||||||
|
_audioStore.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -89,7 +96,3 @@ class _RootPageState extends State<RootPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _backgroundTaskEntrypoint() {
|
|
||||||
AudioServiceBackground.run(() => AudioPlayerTask());
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,114 +1,126 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:mosh/presentation/theming.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../../domain/entities/song.dart';
|
||||||
|
import '../state/audio_store.dart';
|
||||||
|
import '../theming.dart';
|
||||||
import '../widgets/album_art.dart';
|
import '../widgets/album_art.dart';
|
||||||
import '../widgets/queue_card.dart';
|
import '../widgets/queue_card.dart';
|
||||||
import '../widgets/time_progress_indicator.dart';
|
import '../widgets/time_progress_indicator.dart';
|
||||||
|
|
||||||
class CurrentlyPlayingPage extends StatefulWidget {
|
class CurrentlyPlayingPage extends StatelessWidget {
|
||||||
CurrentlyPlayingPage({Key key}) : super(key: key);
|
const CurrentlyPlayingPage({Key key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
|
||||||
_CurrentlyPlayingPageState createState() => _CurrentlyPlayingPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CurrentlyPlayingPageState extends State<CurrentlyPlayingPage> {
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
print('CurrentlyPlayingPage.build');
|
||||||
|
final AudioStore audioStore = Provider.of<AudioStore>(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (BuildContext context, BoxConstraints constraints) => Stack(
|
builder: (BuildContext context, BoxConstraints constraints) =>
|
||||||
children: <Widget>[
|
Observer(
|
||||||
Padding(
|
builder: (BuildContext context) {
|
||||||
padding: const EdgeInsets.only(
|
print('CurrentlyPlayingPage.build -> Observer.build');
|
||||||
left: 12.0,
|
final Song song = audioStore.song;
|
||||||
right: 12.0,
|
|
||||||
top: 8.0,
|
return Stack(
|
||||||
),
|
children: <Widget>[
|
||||||
child: Column(
|
Padding(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
padding: const EdgeInsets.only(
|
||||||
children: [
|
left: 12.0,
|
||||||
Row(
|
right: 12.0,
|
||||||
|
top: 8.0,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
Row(
|
||||||
icon: Icon(Icons.expand_more),
|
children: [
|
||||||
onPressed: () {
|
IconButton(
|
||||||
Navigator.pop(context);
|
icon: Icon(Icons.expand_more),
|
||||||
},
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.more_vert),
|
||||||
|
onPressed: () {},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
),
|
),
|
||||||
IconButton(
|
const Spacer(),
|
||||||
icon: Icon(Icons.more_vert),
|
Padding(
|
||||||
onPressed: () {},
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
)
|
child: AlbumArt(
|
||||||
],
|
song: song,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
child: AlbumArt(),
|
|
||||||
),
|
|
||||||
const Spacer(
|
|
||||||
flex: 4,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.link,
|
|
||||||
size: 20.0,
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
width: 40,
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
Icons.favorite,
|
|
||||||
size: 20.0,
|
|
||||||
color: RASPBERRY,
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
width: 40,
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
Icons.remove_circle_outline,
|
|
||||||
size: 20.0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
),
|
|
||||||
const Spacer(
|
|
||||||
flex: 3,
|
|
||||||
),
|
|
||||||
TimeProgressIndicator(),
|
|
||||||
const Spacer(
|
|
||||||
flex: 3,
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.repeat, size: 20.0),
|
|
||||||
Icon(Icons.skip_previous, size: 32.0),
|
|
||||||
Icon(
|
|
||||||
Icons.play_circle_filled,
|
|
||||||
size: 52.0,
|
|
||||||
),
|
),
|
||||||
Icon(Icons.skip_next, size: 32.0),
|
),
|
||||||
Icon(Icons.shuffle, size: 20.0),
|
const Spacer(
|
||||||
],
|
flex: 4,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
),
|
||||||
),
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.link,
|
||||||
|
size: 20.0,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 40,
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
Icons.favorite,
|
||||||
|
size: 20.0,
|
||||||
|
color: RASPBERRY,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 40,
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
Icons.remove_circle_outline,
|
||||||
|
size: 20.0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
),
|
||||||
|
const Spacer(
|
||||||
|
flex: 3,
|
||||||
|
),
|
||||||
|
TimeProgressIndicator(),
|
||||||
|
const Spacer(
|
||||||
|
flex: 3,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.repeat, size: 20.0),
|
||||||
|
Icon(Icons.skip_previous, size: 32.0),
|
||||||
|
Icon(
|
||||||
|
Icons.play_circle_filled,
|
||||||
|
size: 52.0,
|
||||||
|
),
|
||||||
|
Icon(Icons.skip_next, size: 32.0),
|
||||||
|
Icon(Icons.shuffle, size: 20.0),
|
||||||
|
],
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 64,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Container(
|
),
|
||||||
height: 64,
|
QueueCard(
|
||||||
),
|
boxConstraints: constraints,
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
);
|
||||||
QueueCard(
|
},
|
||||||
boxConstraints: constraints,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -18,9 +18,39 @@ abstract class _AudioStore with Store {
|
||||||
final MusicDataRepository _musicDataRepository;
|
final MusicDataRepository _musicDataRepository;
|
||||||
final AudioRepository _audioRepository;
|
final AudioRepository _audioRepository;
|
||||||
|
|
||||||
|
ReactionDisposer _disposer;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
ObservableStream<Song> currentSong;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
Song song;
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> init() async {
|
||||||
|
currentSong = _audioRepository.watchCurrentSong.asObservable();
|
||||||
|
|
||||||
|
_disposer = autorun((_) {
|
||||||
|
updateSong(currentSong.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
_disposer();
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> playSong(int index, List<Song> songList) async {
|
Future<void> playSong(int index, List<Song> songList) async {
|
||||||
_audioRepository.playSong(index, songList);
|
_audioRepository.playSong(index, songList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> updateSong(Song streamValue) async {
|
||||||
|
print('updateSong');
|
||||||
|
if (streamValue != null && streamValue != song) {
|
||||||
|
print('actually updating...');
|
||||||
|
song = streamValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -9,6 +9,47 @@ part of 'audio_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
|
// 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 {
|
mixin _$AudioStore on _AudioStore, Store {
|
||||||
|
final _$currentSongAtom = Atom(name: '_AudioStore.currentSong');
|
||||||
|
|
||||||
|
@override
|
||||||
|
ObservableStream<Song> get currentSong {
|
||||||
|
_$currentSongAtom.context.enforceReadPolicy(_$currentSongAtom);
|
||||||
|
_$currentSongAtom.reportObserved();
|
||||||
|
return super.currentSong;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
set currentSong(ObservableStream<Song> value) {
|
||||||
|
_$currentSongAtom.context.conditionallyRunInAction(() {
|
||||||
|
super.currentSong = value;
|
||||||
|
_$currentSongAtom.reportChanged();
|
||||||
|
}, _$currentSongAtom, name: '${_$currentSongAtom.name}_set');
|
||||||
|
}
|
||||||
|
|
||||||
|
final _$songAtom = Atom(name: '_AudioStore.song');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Song get song {
|
||||||
|
_$songAtom.context.enforceReadPolicy(_$songAtom);
|
||||||
|
_$songAtom.reportObserved();
|
||||||
|
return super.song;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
set song(Song value) {
|
||||||
|
_$songAtom.context.conditionallyRunInAction(() {
|
||||||
|
super.song = value;
|
||||||
|
_$songAtom.reportChanged();
|
||||||
|
}, _$songAtom, name: '${_$songAtom.name}_set');
|
||||||
|
}
|
||||||
|
|
||||||
|
final _$initAsyncAction = AsyncAction('init');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> init() {
|
||||||
|
return _$initAsyncAction.run(() => super.init());
|
||||||
|
}
|
||||||
|
|
||||||
final _$playSongAsyncAction = AsyncAction('playSong');
|
final _$playSongAsyncAction = AsyncAction('playSong');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -16,9 +57,17 @@ mixin _$AudioStore on _AudioStore, Store {
|
||||||
return _$playSongAsyncAction.run(() => super.playSong(index, songList));
|
return _$playSongAsyncAction.run(() => super.playSong(index, songList));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final _$updateSongAsyncAction = AsyncAction('updateSong');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> updateSong(Song streamValue) {
|
||||||
|
return _$updateSongAsyncAction.run(() => super.updateSong(streamValue));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
final string = '';
|
final string =
|
||||||
|
'currentSong: ${currentSong.toString()},song: ${song.toString()}';
|
||||||
return '{$string}';
|
return '{$string}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../domain/entities/song.dart';
|
||||||
|
import '../utils.dart';
|
||||||
|
|
||||||
class AlbumArt extends StatelessWidget {
|
class AlbumArt extends StatelessWidget {
|
||||||
const AlbumArt({Key key}) : super(key: key);
|
const AlbumArt({Key key, this.song}) : super(key: key);
|
||||||
|
|
||||||
|
final Song song;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -10,10 +15,12 @@ class AlbumArt extends StatelessWidget {
|
||||||
child: Card(
|
child: Card(
|
||||||
elevation: 2.0,
|
elevation: 2.0,
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
margin: EdgeInsets.all(0),
|
margin: const EdgeInsets.all(0),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Image.asset('assets/twilight.jpg'),
|
Image(
|
||||||
|
image: getAlbumImage(song.albumArtPath),
|
||||||
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
|
@ -24,13 +31,13 @@ class AlbumArt extends StatelessWidget {
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: [
|
colors: const [
|
||||||
const Color(0x00555555),
|
Color(0x00555555),
|
||||||
const Color(0x77333333),
|
Color(0x77333333),
|
||||||
const Color(0xCC111111),
|
Color(0xCC111111),
|
||||||
const Color(0xEE000000)
|
Color(0xEE000000)
|
||||||
],
|
],
|
||||||
stops: [
|
stops: const [
|
||||||
0.0,
|
0.0,
|
||||||
0.6,
|
0.6,
|
||||||
0.8,
|
0.8,
|
||||||
|
@ -48,20 +55,20 @@ class AlbumArt extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
'Guardians of Asgaard',
|
song.title,
|
||||||
style: Theme.of(context).textTheme.title,
|
style: Theme.of(context).textTheme.title,
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
height: 4.0,
|
height: 4.0,
|
||||||
),
|
),
|
||||||
const Text(
|
Text(
|
||||||
'Amon Amarth',
|
song.artist,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white70,
|
color: Colors.white70,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Text(
|
Text(
|
||||||
'Twilight of the Thunder God',
|
song.album,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w300,
|
fontWeight: FontWeight.w300,
|
||||||
color: Colors.white70,
|
color: Colors.white70,
|
||||||
|
|
88
lib/presentation/widgets/currently_playing_bar.dart
Normal file
88
lib/presentation/widgets/currently_playing_bar.dart
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../../domain/entities/song.dart';
|
||||||
|
import '../pages/currently_playing.dart';
|
||||||
|
import '../state/audio_store.dart';
|
||||||
|
import '../utils.dart';
|
||||||
|
|
||||||
|
class CurrentlyPlayingBar extends StatelessWidget {
|
||||||
|
const CurrentlyPlayingBar({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final AudioStore audioStore = Provider.of<AudioStore>(context);
|
||||||
|
|
||||||
|
return Observer(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
if (audioStore.currentSong.value != null) {
|
||||||
|
final Song song = audioStore.currentSong.value;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
child: const LinearProgressIndicator(
|
||||||
|
value: 0.42,
|
||||||
|
),
|
||||||
|
height: 2,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => _onTap(context),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Image(
|
||||||
|
image: getAlbumImage(song.albumArtPath),
|
||||||
|
height: 64.0,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 10.0,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(song.title),
|
||||||
|
Text(
|
||||||
|
song.artist,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.0,
|
||||||
|
color: Colors.white70,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.favorite_border),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.pause),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.skip_next),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container(
|
||||||
|
height: 0,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onTap(BuildContext context) async {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute<Widget>(
|
||||||
|
builder: (BuildContext context) => const CurrentlyPlayingPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,8 @@ class InjectionWidget extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// TODO: this does not dispose correctly! use ProxyProvider
|
||||||
|
|
||||||
final MusicDataRepository musicDataRepository = MusicDataRepositoryImpl(
|
final MusicDataRepository musicDataRepository = MusicDataRepositoryImpl(
|
||||||
localMusicFetcher: LocalMusicFetcherImpl(FlutterAudioQuery()),
|
localMusicFetcher: LocalMusicFetcherImpl(FlutterAudioQuery()),
|
||||||
musicDataSource: MoorMusicDataSource(),
|
musicDataSource: MoorMusicDataSource(),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:mosh/presentation/pages/currently_playing.dart';
|
|
||||||
|
import 'currently_playing_bar.dart';
|
||||||
|
|
||||||
class NavBar extends StatefulWidget {
|
class NavBar extends StatefulWidget {
|
||||||
const NavBar({Key key, @required this.onTap, @required this.currentIndex})
|
const NavBar({Key key, @required this.onTap, @required this.currentIndex})
|
||||||
|
@ -24,77 +25,39 @@ class _NavBarState extends State<NavBar> {
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
const CurrentlyPlayingBar(),
|
||||||
child: const LinearProgressIndicator(
|
|
||||||
value: 0.42,
|
|
||||||
),
|
|
||||||
height: 2,
|
|
||||||
),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute<Widget>(
|
|
||||||
builder: (BuildContext context) => CurrentlyPlayingPage()),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
const Image(
|
|
||||||
image: AssetImage('assets/twilight.jpg'),
|
|
||||||
height: 64.0,
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
width: 10.0,
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Text('Guardians of Asgaard'),
|
|
||||||
Text(
|
|
||||||
"Amon Amarth",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12.0,
|
|
||||||
color: Colors.white70,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Spacer(),
|
|
||||||
IconButton(icon: Icon(Icons.favorite_border), onPressed: () {}),
|
|
||||||
IconButton(icon: Icon(Icons.pause), onPressed: () {}),
|
|
||||||
IconButton(icon: Icon(Icons.skip_next), onPressed: () {}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
Container(
|
||||||
color: Colors.grey[850],
|
color: Colors.grey[850],
|
||||||
height: 1.0,
|
height: 1.0,
|
||||||
),
|
),
|
||||||
BottomNavigationBar(
|
BottomNavigationBar(
|
||||||
backgroundColor: Colors.grey[900],
|
backgroundColor: Colors.grey[900],
|
||||||
currentIndex: widget.currentIndex,
|
currentIndex: widget.currentIndex,
|
||||||
onTap: widget.onTap,
|
onTap: widget.onTap,
|
||||||
items: [
|
items: [
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(Icons.home),
|
icon: Icon(Icons.home),
|
||||||
title: Text(
|
title: Text(
|
||||||
"Home",
|
'Home',
|
||||||
style: _optionTextStyle,
|
style: _optionTextStyle,
|
||||||
)),
|
),
|
||||||
BottomNavigationBarItem(
|
),
|
||||||
icon: Icon(Icons.library_music),
|
BottomNavigationBarItem(
|
||||||
title: Text(
|
icon: Icon(Icons.library_music),
|
||||||
"Library",
|
title: Text(
|
||||||
style: _optionTextStyle,
|
'Library',
|
||||||
)),
|
style: _optionTextStyle,
|
||||||
BottomNavigationBarItem(
|
),
|
||||||
icon: Icon(Icons.settings),
|
),
|
||||||
title: Text(
|
BottomNavigationBarItem(
|
||||||
"Settings",
|
icon: Icon(Icons.settings),
|
||||||
style: _optionTextStyle,
|
title: Text(
|
||||||
)),
|
'Settings',
|
||||||
])
|
style: _optionTextStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,13 +7,34 @@ import '../models/song_model.dart';
|
||||||
import 'audio_manager_contract.dart';
|
import 'audio_manager_contract.dart';
|
||||||
|
|
||||||
class AudioManagerImpl implements AudioManager {
|
class AudioManagerImpl implements AudioManager {
|
||||||
|
@override
|
||||||
|
Stream<SongModel> watchCurrentSong = AudioService.currentMediaItemStream.map(
|
||||||
|
(currentMediaItem) {
|
||||||
|
return SongModel.fromMediaItem(currentMediaItem);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> playSong(int index, List<SongModel> songList) async {
|
Future<void> playSong(int index, List<SongModel> songList) async {
|
||||||
|
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.addQueueItem(queue[index]);
|
||||||
AudioService.playFromMediaId(queue[index].id);
|
AudioService.playFromMediaId(queue[index].id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _startAudioService() async {
|
||||||
|
if (!await AudioService.running) {
|
||||||
|
await AudioService.start(
|
||||||
|
backgroundTaskEntrypoint: _backgroundTaskEntrypoint,
|
||||||
|
enableQueue: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _backgroundTaskEntrypoint() {
|
||||||
|
AudioServiceBackground.run(() => AudioPlayerTask());
|
||||||
}
|
}
|
||||||
|
|
||||||
class AudioPlayerTask extends BackgroundAudioTask {
|
class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
|
@ -24,13 +45,6 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onStart() async {
|
Future<void> onStart() async {
|
||||||
print('onStart');
|
|
||||||
|
|
||||||
AudioServiceBackground.setState(
|
|
||||||
controls: [pauseControl, stopControl],
|
|
||||||
basicState: BasicPlaybackState.playing,
|
|
||||||
);
|
|
||||||
|
|
||||||
await _completer.future;
|
await _completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,11 +61,16 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onPlayFromMediaId(String mediaId) async {
|
Future<void> onPlayFromMediaId(String mediaId) async {
|
||||||
|
AudioServiceBackground.setState(
|
||||||
|
controls: [pauseControl, stopControl],
|
||||||
|
basicState: BasicPlaybackState.playing,
|
||||||
|
);
|
||||||
|
|
||||||
// await _audioPlayer.setFilePath(_mediaItems[mediaId]);
|
Future.wait([
|
||||||
AudioServiceBackground.setMediaItem(_mediaItems[mediaId]);
|
AudioServiceBackground.setMediaItem(_mediaItems[mediaId]),
|
||||||
|
_audioPlayer.setFilePath(mediaId),
|
||||||
|
]);
|
||||||
|
|
||||||
await _audioPlayer.setFilePath(mediaId);
|
|
||||||
_audioPlayer.play();
|
_audioPlayer.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import '../models/song_model.dart';
|
import '../models/song_model.dart';
|
||||||
|
|
||||||
abstract class AudioManager {
|
abstract class AudioManager {
|
||||||
|
Stream<SongModel> get watchCurrentSong;
|
||||||
|
|
||||||
Future<void> playSong(int index, List<SongModel> songList);
|
Future<void> playSong(int index, List<SongModel> songList);
|
||||||
}
|
}
|
|
@ -46,6 +46,19 @@ class SongModel extends Song {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: test
|
||||||
|
factory SongModel.fromMediaItem(MediaItem mediaItem) {
|
||||||
|
final String artUri = mediaItem.artUri.replaceFirst('file://', '');
|
||||||
|
|
||||||
|
return SongModel(
|
||||||
|
title: mediaItem.title,
|
||||||
|
album: mediaItem.album,
|
||||||
|
artist: mediaItem.artist,
|
||||||
|
path: mediaItem.id,
|
||||||
|
albumArtPath: artUri,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final int id;
|
final int id;
|
||||||
|
|
||||||
SongsCompanion toSongsCompanion() => SongsCompanion(
|
SongsCompanion toSongsCompanion() => SongsCompanion(
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:mosh/system/models/song_model.dart';
|
|
||||||
|
|
||||||
import '../../core/error/failures.dart';
|
import '../../core/error/failures.dart';
|
||||||
import '../../domain/entities/song.dart';
|
import '../../domain/entities/song.dart';
|
||||||
import '../../domain/repositories/audio_repository.dart';
|
import '../../domain/repositories/audio_repository.dart';
|
||||||
import '../datasources/audio_manager_contract.dart';
|
import '../datasources/audio_manager_contract.dart';
|
||||||
|
import '../models/song_model.dart';
|
||||||
|
|
||||||
class AudioRepositoryImpl implements AudioRepository {
|
class AudioRepositoryImpl implements AudioRepository {
|
||||||
AudioRepositoryImpl(this._audioManager);
|
AudioRepositoryImpl(this._audioManager);
|
||||||
|
@ -12,12 +12,16 @@ class AudioRepositoryImpl implements AudioRepository {
|
||||||
final AudioManager _audioManager;
|
final AudioManager _audioManager;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Either<Failure, void>> playSong(int index, List<Song> songList) async {
|
Stream<Song> get watchCurrentSong => _audioManager.watchCurrentSong;
|
||||||
final List<SongModel> songModelList = songList.map((song) => song as SongModel).toList();
|
|
||||||
|
@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) {
|
if (0 <= index && index < songList.length) {
|
||||||
await _audioManager.playSong(index, songModelList);
|
await _audioManager.playSong(index, songModelList);
|
||||||
return Right(null);
|
return Right(null);
|
||||||
}
|
}
|
||||||
return Left(IndexFailure());
|
return Left(IndexFailure());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue