artist page, currently playing cosmetics, new playback functions
This commit is contained in:
parent
4c325396bc
commit
90a77385b5
23 changed files with 466 additions and 244 deletions
|
@ -1,3 +1,5 @@
|
|||
import '../entities/album.dart';
|
||||
import '../entities/artist.dart';
|
||||
import '../entities/loop_mode.dart';
|
||||
import '../entities/playback_state.dart';
|
||||
import '../entities/shuffle_mode.dart';
|
||||
|
@ -15,6 +17,9 @@ abstract class AudioRepository {
|
|||
Future<void> skipToPrevious();
|
||||
Future<void> setIndex(int index);
|
||||
|
||||
Future<void> playAlbum(Album album);
|
||||
Future<void> playArtist(Artist artist, {ShuffleMode shuffleMode = ShuffleMode.none});
|
||||
|
||||
Future<void> setShuffleMode(ShuffleMode shuffleMode);
|
||||
Future<void> setLoopMode(LoopMode loopMode);
|
||||
|
||||
|
|
|
@ -4,8 +4,11 @@ import 'package:provider/provider.dart';
|
|||
|
||||
import '../../domain/entities/album.dart';
|
||||
import '../../domain/entities/artist.dart';
|
||||
import '../state/audio_store.dart';
|
||||
import '../state/music_data_store.dart';
|
||||
import '../theming.dart';
|
||||
import '../widgets/artist_albums.dart';
|
||||
import '../widgets/artist_header.dart';
|
||||
import 'album_details_page.dart';
|
||||
|
||||
class ArtistDetailsPage extends StatelessWidget {
|
||||
|
@ -16,17 +19,57 @@ class ArtistDetailsPage extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final MusicDataStore musicDataStore = Provider.of<MusicDataStore>(context);
|
||||
final AudioStore audioStore = Provider.of<AudioStore>(context);
|
||||
|
||||
return Observer(
|
||||
builder: (BuildContext context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(artist.name),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ArtistAlbumList(
|
||||
albums: musicDataStore.sortedArtistAlbums,
|
||||
onTap: (Album album) => _tapAlbum(album, context, musicDataStore),
|
||||
),
|
||||
builder: (BuildContext context) => SafeArea(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
ArtistHeader(artist: artist),
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate(
|
||||
[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: HORIZONTAL_PADDING,
|
||||
right: HORIZONTAL_PADDING,
|
||||
bottom: 8.0,
|
||||
),
|
||||
child: ElevatedButton(
|
||||
child: const Text('SHUFFLE'),
|
||||
onPressed: () => audioStore.shuffleArtist(artist),
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: HORIZONTAL_PADDING + 2,
|
||||
right: HORIZONTAL_PADDING + 2,
|
||||
bottom: 4.0,
|
||||
),
|
||||
child: Text(
|
||||
'Albums',
|
||||
style: TEXT_HEADER,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: HORIZONTAL_PADDING,
|
||||
vertical: 4.0,
|
||||
),
|
||||
child: Container(
|
||||
height: 1.0,
|
||||
color: Colors.white10,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
ArtistAlbumSliverList(
|
||||
albums: musicDataStore.sortedArtistAlbums,
|
||||
onTap: (Album album) => _tapAlbum(album, context, musicDataStore),
|
||||
onTapPlay: (Album album) => audioStore.playAlbum(album),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -39,115 +39,119 @@ class CurrentlyPlayingPage extends StatelessWidget {
|
|||
_log.info('Observer.build');
|
||||
final Song song = audioStore.currentSong;
|
||||
|
||||
return AlbumBackground(
|
||||
song: song,
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0x55000000),
|
||||
Color(0x22FFFFFF),
|
||||
Color(0x22FFFFFF),
|
||||
Color(0x88000000),
|
||||
Color(0xBB000000),
|
||||
],
|
||||
stops: [
|
||||
0.0,
|
||||
0.1,
|
||||
0.5,
|
||||
0.65,
|
||||
1.0,
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12.0,
|
||||
right: 12.0,
|
||||
top: 8.0,
|
||||
return Stack(
|
||||
children: [
|
||||
AlbumBackground(
|
||||
song: song,
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0x55000000),
|
||||
Color(0x22FFFFFF),
|
||||
Color(0x22FFFFFF),
|
||||
Color(0x88000000),
|
||||
Color(0xBB000000),
|
||||
],
|
||||
stops: [
|
||||
0.0,
|
||||
0.1,
|
||||
0.55,
|
||||
0.75,
|
||||
1.0,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.expand_more),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => _openQueue(context),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Next up'.toUpperCase(),
|
||||
style: TEXT_SMALL_HEADLINE,
|
||||
),
|
||||
NextSong(
|
||||
queue: audioStore.queueStream.value,
|
||||
index: audioStore.queueIndexStream.value,
|
||||
)
|
||||
],
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12.0,
|
||||
right: 12.0,
|
||||
top: 8.0,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.expand_more),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => _openQueue(context),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Next up'.toUpperCase(),
|
||||
style: TEXT_SMALL_HEADLINE,
|
||||
),
|
||||
NextSong(
|
||||
queue: audioStore.queueStream.value,
|
||||
index: audioStore.queueIndexStream.value,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onPressed: () {},
|
||||
)
|
||||
],
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
),
|
||||
const Spacer(
|
||||
flex: 1,
|
||||
),
|
||||
Expanded(
|
||||
flex: 1000,
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 0.0,
|
||||
),
|
||||
child: AlbumArt(
|
||||
song: song,
|
||||
IconButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onPressed: () {},
|
||||
)
|
||||
],
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
),
|
||||
const Spacer(
|
||||
flex: 1,
|
||||
),
|
||||
Expanded(
|
||||
flex: 1000,
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 0.0,
|
||||
),
|
||||
child: AlbumArt(
|
||||
song: song,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(
|
||||
flex: 60,
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 2.0, right: 2.0),
|
||||
child: SongCustomizationButtons(),
|
||||
),
|
||||
const Spacer(
|
||||
flex: 30,
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2.0),
|
||||
child: PlaybackControl(),
|
||||
),
|
||||
const Spacer(
|
||||
flex: 30,
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 10.0),
|
||||
child: TimeProgressIndicator(),
|
||||
),
|
||||
const Spacer(
|
||||
flex: 100,
|
||||
),
|
||||
],
|
||||
const Spacer(
|
||||
flex: 60,
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 2.0, right: 2.0),
|
||||
child: SongCustomizationButtons(),
|
||||
),
|
||||
const Spacer(
|
||||
flex: 30,
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2.0),
|
||||
child: PlaybackControl(),
|
||||
),
|
||||
const Spacer(
|
||||
flex: 30,
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 10.0),
|
||||
child: TimeProgressIndicator(),
|
||||
),
|
||||
const Spacer(
|
||||
flex: 100,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -14,6 +14,7 @@ class HomePage extends StatefulWidget {
|
|||
class _HomePageState extends State<HomePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
print('HomePage.build');
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
|
|
|
@ -34,53 +34,57 @@ class QueuePage extends StatelessWidget {
|
|||
switch (queueStream.status) {
|
||||
case StreamStatus.active:
|
||||
final int activeIndex = queueIndexStream.value;
|
||||
return AlbumBackground(
|
||||
song: audioStore.currentSong,
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0x55000000),
|
||||
Color(0x55000000),
|
||||
],
|
||||
stops: [
|
||||
0.0,
|
||||
1.0,
|
||||
],
|
||||
),
|
||||
child: CustomScrollView(
|
||||
controller: _scrollController,
|
||||
slivers: [
|
||||
ReorderableSliverList(
|
||||
delegate: ReorderableSliverChildBuilderDelegate(
|
||||
(context, int index) {
|
||||
final song = queueStream.value[index];
|
||||
return Dismissible(
|
||||
key: ValueKey(song.path),
|
||||
child: AlbumArtListTile(
|
||||
title: song.title,
|
||||
subtitle: '${song.artist}',
|
||||
albumArtPath: song.albumArtPath,
|
||||
highlight: index == activeIndex,
|
||||
onTap: () => audioStore.setIndex(index),
|
||||
),
|
||||
onDismissed: (direction) {
|
||||
audioStore.removeQueueIndex(index);
|
||||
Scaffold.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('${song.title} removed'),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
childCount: queueStream.value.length,
|
||||
),
|
||||
onReorder: (oldIndex, newIndex) =>
|
||||
audioStore.moveQueueItem(oldIndex, newIndex),
|
||||
)
|
||||
],
|
||||
),
|
||||
return Stack(
|
||||
children: [
|
||||
AlbumBackground(
|
||||
song: audioStore.currentSong,
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0x55000000),
|
||||
Color(0x55000000),
|
||||
],
|
||||
stops: [
|
||||
0.0,
|
||||
1.0,
|
||||
],
|
||||
),
|
||||
),
|
||||
CustomScrollView(
|
||||
controller: _scrollController,
|
||||
slivers: [
|
||||
ReorderableSliverList(
|
||||
delegate: ReorderableSliverChildBuilderDelegate(
|
||||
(context, int index) {
|
||||
final song = queueStream.value[index];
|
||||
return Dismissible(
|
||||
key: ValueKey(song.path),
|
||||
child: AlbumArtListTile(
|
||||
title: song.title,
|
||||
subtitle: '${song.artist}',
|
||||
albumArtPath: song.albumArtPath,
|
||||
highlight: index == activeIndex,
|
||||
onTap: () => audioStore.setIndex(index),
|
||||
),
|
||||
onDismissed: (direction) {
|
||||
audioStore.removeQueueIndex(index);
|
||||
Scaffold.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('${song.title} removed'),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
childCount: queueStream.value.length,
|
||||
),
|
||||
onReorder: (oldIndex, newIndex) =>
|
||||
audioStore.moveQueueItem(oldIndex, newIndex),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
case StreamStatus.waiting:
|
||||
case StreamStatus.done:
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import 'package:meta/meta.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
import '../../domain/entities/album.dart';
|
||||
import '../../domain/entities/artist.dart';
|
||||
import '../../domain/entities/loop_mode.dart';
|
||||
import '../../domain/entities/playback_state.dart';
|
||||
import '../../domain/entities/shuffle_mode.dart';
|
||||
|
@ -112,4 +114,12 @@ abstract class _AudioStore with Store {
|
|||
Future<void> removeQueueIndex(int index) async {
|
||||
_audioRepository.removeQueueIndex(index);
|
||||
}
|
||||
|
||||
Future<void> playAlbum(Album album) async {
|
||||
_audioRepository.playAlbum(album);
|
||||
}
|
||||
|
||||
Future<void> shuffleArtist(Artist artist) async {
|
||||
_audioRepository.playArtist(artist, shuffleMode: ShuffleMode.plus);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,13 @@ abstract class _MusicDataStore with Store {
|
|||
bool isUpdatingDatabase = false;
|
||||
|
||||
@computed
|
||||
List<Album> get sortedArtistAlbums => artistAlbumStream.value.toList()..sort((a, b) => -a.pubYear.compareTo(b.pubYear));
|
||||
List<Album> get sortedArtistAlbums => artistAlbumStream.value.toList()..sort((a, b) {
|
||||
if (b.pubYear == null)
|
||||
return -1;
|
||||
if (a.pubYear == null)
|
||||
return 1;
|
||||
return -a.pubYear.compareTo(b.pubYear);
|
||||
});
|
||||
|
||||
@action
|
||||
Future<void> updateDatabase() async {
|
||||
|
|
|
@ -82,4 +82,6 @@ const TextStyle TEXT_SMALL_HEADLINE = TextStyle(
|
|||
const TextStyle TEXT_SMALL_SUBTITLE = TextStyle(
|
||||
fontSize: 12.0,
|
||||
fontWeight: FontWeight.w300,
|
||||
);
|
||||
);
|
||||
|
||||
const double HORIZONTAL_PADDING = 16.0;
|
|
@ -14,25 +14,19 @@ class AlbumBackground extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: getAlbumImage(song.albumArtPath),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
height: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
gradient: gradient,
|
||||
),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 96.0, sigmaY: 96.0),
|
||||
child: Container(
|
||||
child: child,
|
||||
color: Colors.white.withOpacity(0.0),
|
||||
return ImageFiltered(
|
||||
imageFilter: ImageFilter.blur(sigmaX: 96.0, sigmaY: 96.0),
|
||||
child: ShaderMask(
|
||||
shaderCallback: (Rect bounds) => gradient.createShader(bounds),
|
||||
blendMode: BlendMode.srcATop,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: getAlbumImage(song.albumArtPath),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -5,7 +5,7 @@ import '../utils.dart' as utils;
|
|||
|
||||
class AlbumListTileExtended extends StatelessWidget {
|
||||
const AlbumListTileExtended(
|
||||
{Key key, this.title, this.subtitle, this.albumArtPath, this.onTap, this.highlight = false})
|
||||
{Key key, this.title, this.subtitle, this.albumArtPath, this.onTap, this.highlight = false, this.onTapPlay})
|
||||
: super(key: key);
|
||||
|
||||
final String title;
|
||||
|
@ -13,52 +13,56 @@ class AlbumListTileExtended extends StatelessWidget {
|
|||
final String albumArtPath;
|
||||
final Function onTap;
|
||||
final bool highlight;
|
||||
final Function onTapPlay;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => onTap(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 72,
|
||||
width: 72,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4.0)),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Image(
|
||||
image: utils.getAlbumImage(albumArtPath),
|
||||
fit: BoxFit.cover,
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 72,
|
||||
width: 72,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4.0)),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Image(
|
||||
image: utils.getAlbumImage(albumArtPath),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0),
|
||||
maxLines: 2,
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TEXT_SMALL_SUBTITLE,
|
||||
),
|
||||
],
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0),
|
||||
maxLines: 2,
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TEXT_SMALL_SUBTITLE,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.play_circle_fill_rounded),
|
||||
iconSize: 40.0,
|
||||
onPressed: () => null,
|
||||
),
|
||||
],
|
||||
IconButton(
|
||||
icon: Icon(Icons.play_circle_fill_rounded),
|
||||
iconSize: 40.0,
|
||||
onPressed: () => onTapPlay(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -3,25 +3,29 @@ import 'package:flutter/material.dart';
|
|||
import '../../domain/entities/album.dart';
|
||||
import 'album_list_tile_extended.dart';
|
||||
|
||||
class ArtistAlbumList extends StatelessWidget {
|
||||
const ArtistAlbumList({Key key, this.albums, this.onTap}) : super(key: key);
|
||||
class ArtistAlbumSliverList extends StatelessWidget {
|
||||
const ArtistAlbumSliverList({Key key, this.albums, this.onTap, this.onTapPlay}) : super(key: key);
|
||||
|
||||
final List<Album> albums;
|
||||
final Function onTap;
|
||||
final Function onTapPlay;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
itemCount: albums.length,
|
||||
itemBuilder: (_, int index) {
|
||||
final Album album = albums[index];
|
||||
return AlbumListTileExtended(
|
||||
title: album.title,
|
||||
subtitle: album.pubYear.toString(),
|
||||
albumArtPath: album.albumArtPath,
|
||||
onTap: () => onTap(album),
|
||||
);
|
||||
},
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(_, int index) {
|
||||
final Album album = albums[index];
|
||||
return AlbumListTileExtended(
|
||||
title: album.title,
|
||||
subtitle: album.pubYear.toString(),
|
||||
albumArtPath: album.albumArtPath,
|
||||
onTap: () => onTap(album),
|
||||
onTapPlay: () => onTapPlay(album),
|
||||
);
|
||||
},
|
||||
childCount: albums.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
72
lib/presentation/widgets/artist_header.dart
Normal file
72
lib/presentation/widgets/artist_header.dart
Normal file
|
@ -0,0 +1,72 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../domain/entities/artist.dart';
|
||||
|
||||
class ArtistHeader extends StatelessWidget {
|
||||
const ArtistHeader({Key key, this.artist}) : super(key: key);
|
||||
|
||||
final Artist artist;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const double height = 144.0;
|
||||
return SliverAppBar(
|
||||
brightness: Brightness.dark,
|
||||
pinned: true,
|
||||
expandedHeight: height,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
iconTheme: const IconThemeData(
|
||||
color: Colors.white,
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.chevron_left),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
titleSpacing: 48.0,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
centerTitle: true,
|
||||
titlePadding: const EdgeInsets.only(
|
||||
bottom: 0.0,
|
||||
top: 0.0,
|
||||
left: 48.0,
|
||||
right: 48.0,
|
||||
),
|
||||
title: Container(
|
||||
alignment: Alignment.center,
|
||||
height: height * 0.66,
|
||||
// color: Colors.red,
|
||||
child: Text(
|
||||
artist.name,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 24.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 3,
|
||||
),
|
||||
),
|
||||
background: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
// Colors.deepPurpleAccent,
|
||||
Colors.transparent,
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
// Colors.green,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:mucke/presentation/theming.dart';
|
||||
|
||||
import '../theming.dart';
|
||||
|
||||
class Header extends StatelessWidget {
|
||||
const Header({Key key}) : super(key: key);
|
||||
|
|
|
@ -20,14 +20,12 @@ class ShuffleAllButton extends StatelessWidget {
|
|||
vertical: verticalPad,
|
||||
horizontal: horizontalPad,
|
||||
),
|
||||
child: RaisedButton.icon(
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.shuffle),
|
||||
label: const Text('SHUFFLE ALL'),
|
||||
onPressed: () => audioStore.shuffleAll(),
|
||||
color: Theme.of(context).accentColor,
|
||||
highlightColor: Theme.of(context).highlightColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Theme.of(context).accentColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -8,6 +8,8 @@ import '../../domain/entities/playback_event.dart';
|
|||
import '../../domain/entities/shuffle_mode.dart';
|
||||
import '../datasources/music_data_source_contract.dart';
|
||||
import '../datasources/player_state_data_source.dart';
|
||||
import '../models/album_model.dart';
|
||||
import '../models/artist_model.dart';
|
||||
import '../models/playback_event_model.dart';
|
||||
import '../models/queue_item_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
|
@ -123,6 +125,13 @@ class MyAudioHandler extends BaseAudioHandler {
|
|||
return moveQueueItem(arguments['OLD_INDEX'] as int, arguments['NEW_INDEX'] as int);
|
||||
case SET_INDEX:
|
||||
return setIndex(arguments['INDEX'] as int);
|
||||
case PLAY_ALBUM:
|
||||
return playAlbum(arguments['ALBUM'] as AlbumModel);
|
||||
case PLAY_ARTIST:
|
||||
return playArtist(
|
||||
arguments['ARTIST'] as ArtistModel,
|
||||
arguments['SHUFFLE_MODE'] as ShuffleMode,
|
||||
);
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
@ -163,6 +172,23 @@ class MyAudioHandler extends BaseAudioHandler {
|
|||
_audioPlayer.setIndex(index);
|
||||
}
|
||||
|
||||
Future<void> playAlbum(AlbumModel album) async {
|
||||
_audioPlayer.setShuffleMode(ShuffleMode.none, false);
|
||||
final List<SongModel> songs = await _musicDataSource.getAlbumSongStream(album).first;
|
||||
|
||||
_audioPlayer.playSongList(songs, 0);
|
||||
}
|
||||
|
||||
Future<void> playArtist(ArtistModel artist, ShuffleMode shuffleMode) async {
|
||||
_audioPlayer.setShuffleMode(shuffleMode, false);
|
||||
final List<SongModel> songs = await _musicDataSource.getArtistSongStream(artist).first;
|
||||
|
||||
final rng = Random();
|
||||
final index = rng.nextInt(songs.length);
|
||||
|
||||
_audioPlayer.playSongList(songs, index);
|
||||
}
|
||||
|
||||
void _handleSetQueue(List<QueueItemModel> queueItems) {
|
||||
_playerStateDataSource.setQueue(queueItems);
|
||||
|
||||
|
@ -195,8 +221,7 @@ class MyAudioHandler extends BaseAudioHandler {
|
|||
}
|
||||
|
||||
void _handlePosition(Duration position, SongModel song) {
|
||||
if (song == null || position == null)
|
||||
return;
|
||||
if (song == null || position == null) return;
|
||||
|
||||
final int pos = position.inMilliseconds;
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ import 'package:audio_service/audio_service.dart';
|
|||
import '../../domain/entities/loop_mode.dart';
|
||||
import '../../domain/entities/playback_state.dart' as entity;
|
||||
import '../../domain/entities/shuffle_mode.dart';
|
||||
import '../models/album_model.dart';
|
||||
import '../models/artist_model.dart';
|
||||
import '../models/playback_state_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
import 'audio_manager_contract.dart';
|
||||
|
@ -134,4 +136,17 @@ class AudioManagerImpl implements AudioManager {
|
|||
Future<void> removeQueueIndex(int index) async {
|
||||
await _audioHandler.removeQueueItemAt(index);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> playAlbum(AlbumModel albumModel) async {
|
||||
await _audioHandler.customAction(PLAY_ALBUM, {'ALBUM': albumModel});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> playArtist(ArtistModel artistModel, ShuffleMode shuffleMode) async {
|
||||
await _audioHandler.customAction(PLAY_ARTIST, {
|
||||
'ARTIST': artistModel,
|
||||
'SHUFFLE_MODE': shuffleMode,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import '../../domain/entities/loop_mode.dart';
|
||||
import '../../domain/entities/playback_state.dart';
|
||||
import '../../domain/entities/shuffle_mode.dart';
|
||||
import '../models/album_model.dart';
|
||||
import '../models/artist_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
|
||||
abstract class AudioManager {
|
||||
|
@ -22,4 +24,7 @@ abstract class AudioManager {
|
|||
Future<void> addToQueue(SongModel songModel);
|
||||
Future<void> moveQueueItem(int oldIndex, int newIndex);
|
||||
Future<void> removeQueueIndex(int index);
|
||||
|
||||
Future<void> playAlbum(AlbumModel albumModel);
|
||||
Future<void> playArtist(ArtistModel artistModel, ShuffleMode shuffleMode);
|
||||
}
|
||||
|
|
|
@ -3,4 +3,6 @@ const String SHUFFLE_ALL = 'SHUFFLE_ALL';
|
|||
const String SET_SHUFFLE_MODE = 'SET_SHUFFLE_MODE';
|
||||
const String SET_LOOP_MODE = 'SET_LOOP_MODE';
|
||||
const String SET_INDEX = 'SET_INDEX';
|
||||
const String MOVE_QUEUE_ITEM = 'MOVE_QUEUE_ITEM';
|
||||
const String MOVE_QUEUE_ITEM = 'MOVE_QUEUE_ITEM';
|
||||
const String PLAY_ALBUM = 'PLAY_ALBUM';
|
||||
const String PLAY_ARTIST = 'PLAY_ARTIST';
|
||||
|
|
|
@ -55,7 +55,7 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
|
|||
return _flutterAudioQuery.getArtwork(
|
||||
type: ResourceType.ALBUM,
|
||||
id: id.toString(),
|
||||
size: const Size(500.0, 500.0),
|
||||
size: const Size(480.0, 480.0),
|
||||
);
|
||||
}
|
||||
return Uint8List(0);
|
||||
|
@ -71,6 +71,8 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
|
|||
final artistNames = Set<String>.from(albums.map((album) => album.artist));
|
||||
final artists = await _getFilteredArtists(artistNames);
|
||||
|
||||
assert(false);
|
||||
|
||||
return {
|
||||
'SONGS': songs,
|
||||
'ALBUMS': albums,
|
||||
|
|
|
@ -71,8 +71,20 @@ class MusicDataDao extends DatabaseAccessor<MoorDatabase>
|
|||
(t) => OrderingTerm(expression: t.title),
|
||||
]))
|
||||
.watch()
|
||||
.map((moorAlbumList) =>
|
||||
moorAlbumList.map((moorAlbum) => AlbumModel.fromMoor(moorAlbum)).toList());
|
||||
.map((moorAlbumList) {
|
||||
return moorAlbumList.map((moorAlbum) => AlbumModel.fromMoor(moorAlbum)).toList();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<SongModel>> getArtistSongStream(ArtistModel artist) {
|
||||
return (select(albums)..where((tbl) => tbl.artist.equals(artist.name)))
|
||||
.join([innerJoin(songs, songs.albumId.equalsExp(albums.id))])
|
||||
.map((row) => row.readTable(songs))
|
||||
.watch()
|
||||
.map(
|
||||
(moorSongList) => moorSongList.map((moorSong) => SongModel.fromMoor(moorSong)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -5,6 +5,7 @@ import '../models/song_model.dart';
|
|||
abstract class MusicDataSource {
|
||||
Stream<List<SongModel>> get songStream;
|
||||
Stream<List<SongModel>> getAlbumSongStream(AlbumModel album);
|
||||
Stream<List<SongModel>> getArtistSongStream(ArtistModel artist);
|
||||
Future<List<SongModel>> getSongs();
|
||||
Future<SongModel> getSongByPath(String path);
|
||||
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import '../../domain/entities/album.dart';
|
||||
import '../../domain/entities/artist.dart';
|
||||
import '../../domain/entities/loop_mode.dart';
|
||||
import '../../domain/entities/playback_state.dart';
|
||||
import '../../domain/entities/shuffle_mode.dart';
|
||||
import '../../domain/entities/song.dart';
|
||||
import '../../domain/repositories/audio_repository.dart';
|
||||
import '../audio/audio_manager_contract.dart';
|
||||
import '../models/album_model.dart';
|
||||
import '../models/artist_model.dart';
|
||||
import '../models/song_model.dart';
|
||||
|
||||
class AudioRepositoryImpl implements AudioRepository {
|
||||
|
@ -83,4 +87,14 @@ class AudioRepositoryImpl implements AudioRepository {
|
|||
Future<void> setIndex(int index) async {
|
||||
await _audioManager.setIndex(index);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> playAlbum(Album album) async {
|
||||
await _audioManager.playAlbum(album as AlbumModel);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> playArtist(Artist artist, {ShuffleMode shuffleMode = ShuffleMode.none}) async {
|
||||
await _audioManager.playArtist(artist as ArtistModel, shuffleMode);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import 'package:mucke/presentation/widgets/next_indicator.dart';
|
|||
import 'package:mucke/presentation/widgets/previous_button.dart';
|
||||
import 'package:mucke/presentation/widgets/play_pause_button.dart';
|
||||
import 'package:mucke/presentation/widgets/album_art.dart';
|
||||
import 'package:mucke/presentation/widgets/shuffle_all_button.dart';
|
||||
import 'package:mucke/presentation/widgets/primary_button.dart';
|
||||
import 'package:mucke/presentation/widgets/shuffle_button.dart';
|
||||
import 'package:mucke/presentation/widgets/injection_widget.dart';
|
||||
import 'package:mucke/presentation/widgets/next_song.dart';
|
||||
|
@ -45,8 +45,6 @@ import 'package:mucke/presentation/pages/albums_page.dart';
|
|||
import 'package:mucke/presentation/pages/album_details_page.dart';
|
||||
import 'package:mucke/presentation/pages/currently_playing.dart';
|
||||
import 'package:mucke/presentation/pages/artists_page.dart';
|
||||
import 'package:mucke/core/usecase.dart';
|
||||
import 'package:mucke/core/error/failures.dart';
|
||||
import 'package:mucke/domain/repositories/audio_repository.dart';
|
||||
import 'package:mucke/domain/repositories/music_data_repository.dart';
|
||||
import 'package:mucke/domain/entities/shuffle_mode.dart';
|
||||
|
|
Loading…
Add table
Reference in a new issue