ui stuff around currenty playing

This commit is contained in:
Moritz Weber 2020-12-31 17:52:20 +01:00
parent 59eba0aa0a
commit a4d7a0071e
12 changed files with 238 additions and 146 deletions

View file

@ -5,9 +5,10 @@ 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_background.dart';
import '../widgets/next_indicator.dart';
import '../widgets/next_song.dart';
import '../widgets/playback_control.dart';
import '../widgets/song_customization_buttons.dart';
import '../widgets/time_progress_indicator.dart';
@ -25,15 +26,39 @@ class CurrentlyPlayingPage extends StatelessWidget {
return Scaffold(
body: SafeArea(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) =>
Observer(
child: GestureDetector(
onVerticalDragEnd: (dragEndDetails) {
if (dragEndDetails.primaryVelocity < 0) {
_openQueue(context);
} else if (dragEndDetails.primaryVelocity > 0) {
Navigator.pop(context);
}
},
child: Observer(
builder: (BuildContext context) {
_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,
@ -51,6 +76,27 @@ class CurrentlyPlayingPage extends StatelessWidget {
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: () {},
@ -76,31 +122,28 @@ class CurrentlyPlayingPage extends StatelessWidget {
),
),
const Spacer(
flex: 80,
flex: 60,
),
const Padding(
padding: EdgeInsets.only(left: 2.0, right: 2.0, bottom: 10.0),
padding: EdgeInsets.only(left: 2.0, right: 2.0),
child: SongCustomizationButtons(),
),
const Spacer(
flex: 50,
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: TimeProgressIndicator(),
),
const Spacer(
flex: 50,
flex: 30,
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 2.0),
child: PlaybackControl(),
),
const Spacer(
flex: 40,
flex: 30,
),
NextIndicator(
onTapAction: openQueue,
const Padding(
padding: EdgeInsets.only(left: 16.0, right: 16.0, top: 10.0),
child: TimeProgressIndicator(),
),
const Spacer(
flex: 100,
),
],
),
@ -113,7 +156,7 @@ class CurrentlyPlayingPage extends StatelessWidget {
);
}
void openQueue(BuildContext context) {
void _openQueue(BuildContext context) {
Navigator.push(
context,
MaterialPageRoute<Widget>(

View file

@ -9,6 +9,7 @@ import 'package:reorderables/reorderables.dart';
import '../../domain/entities/song.dart';
import '../state/audio_store.dart';
import '../widgets/album_art_list_tile.dart';
import '../widgets/album_background.dart';
class QueuePage extends StatelessWidget {
const QueuePage({Key key}) : super(key: key);
@ -20,7 +21,8 @@ class QueuePage extends StatelessWidget {
final ObservableStream<int> queueIndexStream = audioStore.queueIndexStream;
final initialIndex = max(((queueIndexStream?.value) ?? 0) - 2, 0);
final ScrollController _scrollController = ScrollController(initialScrollOffset: initialIndex * 72.0);
final ScrollController _scrollController =
ScrollController(initialScrollOffset: initialIndex * 72.0);
return Scaffold(
body: SafeArea(
@ -32,38 +34,53 @@ class QueuePage extends StatelessWidget {
switch (queueStream.status) {
case StreamStatus.active:
final int activeIndex = queueIndexStream.value;
return 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 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),
)
],
),
);
case StreamStatus.waiting:
case StreamStatus.done:

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:mucke/presentation/theming.dart';
import '../../domain/entities/song.dart';
import '../theming.dart';
import '../utils.dart';
class AlbumArt extends StatelessWidget {
@ -34,26 +34,22 @@ class AlbumArt extends StatelessWidget {
bottom: 0,
left: 0,
right: 0,
height: 140,
height: 150,
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0x00000000),
Color(0xCC000000)
],
stops: [
0.0,
1.0
]),
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0x00000000), Color(0xCC000000)],
stops: [0.0, 1.0],
),
),
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
@ -61,11 +57,31 @@ class AlbumArt extends StatelessWidget {
children: <Widget>[
Text(
song.title,
style: TEXT_BIG,
overflow: TextOverflow.ellipsis,
// softWrap: false,
maxLines: 2,
style: TEXT_BIG.copyWith(
shadows: [
const Shadow(
blurRadius: 2.0,
color: Colors.black45,
offset: Offset(.5, .5),
),
],
),
),
Text(
song.artist,
style: TEXT_SUBTITLE.copyWith(color: Colors.white70),
style: TEXT_SUBTITLE.copyWith(
color: Colors.grey[300],
shadows: [
const Shadow(
blurRadius: 2.0,
color: Colors.black45,
offset: Offset(.5, .5),
),
],
),
),
],
),

View file

@ -6,10 +6,11 @@ import '../../domain/entities/song.dart';
import '../utils.dart';
class AlbumBackground extends StatelessWidget {
const AlbumBackground({Key key, this.child, this.song}) : super(key: key);
const AlbumBackground({Key key, this.child, this.song, this.gradient}) : super(key: key);
final Widget child;
final Song song;
final Gradient gradient;
@override
Widget build(BuildContext context) {
@ -24,25 +25,8 @@ class AlbumBackground extends StatelessWidget {
),
child: Container(
height: double.infinity,
decoration: const BoxDecoration(
gradient: 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,
],
),
decoration: BoxDecoration(
gradient: gradient,
),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 96.0, sigmaY: 96.0),

View file

@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
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 '../theming.dart';
class LikeButton extends StatelessWidget {
const LikeButton({Key key, this.iconSize = 20.0}) : super(key: key);
final double iconSize;
@override
Widget build(BuildContext context) {
final MusicDataStore musicDataStore = Provider.of<MusicDataStore>(context);
final AudioStore audioStore = Provider.of<AudioStore>(context);
return Observer(
builder: (BuildContext context) {
final Song song = audioStore.currentSong;
if (song.likeCount == 0) {
return IconButton(
icon: Icon(
Icons.favorite_rounded,
size: iconSize,
color: Colors.white24,
),
onPressed: null,
);
} else {
return IconButton(
icon: Stack(
children: [
Icon(
Icons.favorite_rounded,
color: Colors.white,
size: iconSize,
),
Text(
song.likeCount.toString(),
style: const TextStyle(
color: DARK1,
fontSize: 10.0,
fontWeight: FontWeight.bold,
),
),
],
alignment: AlignmentDirectional.center,
),
onPressed: null,
);
}
},
);
}
}

View file

@ -22,7 +22,7 @@ class LoopButton extends StatelessWidget {
return IconButton(
icon: const Icon(
Icons.repeat_rounded,
color: Colors.white30,
color: Colors.white24,
),
iconSize: iconSize,
onPressed: () {

View file

@ -15,16 +15,14 @@ class NextButton extends StatelessWidget {
return Observer(
builder: (BuildContext context) {
final queue = audioStore.queueStream.value; //
final int index = audioStore.queueIndexStream.value; //
final queue = audioStore.queueStream.value;
final int index = audioStore.queueIndexStream.value;
if (index != null && index < queue.length - 1) { //
if (index != null && index < queue.length - 1) {
return IconButton(
icon: const Icon(Icons.skip_next), //
icon: const Icon(Icons.skip_next_rounded),
iconSize: iconSize,
onPressed: () {
audioStore.skipToNext(); //
},
onPressed: () => audioStore.skipToNext(),
);
}

View file

@ -13,13 +13,19 @@ class NextSong extends StatelessWidget {
if (index < queue.length - 1) {
final Song song = queue[index + 1];
return RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: const TextStyle(
fontSize: 14,
color: Colors.white70,
),
children: [
TextSpan(text: '${song.title}'),
TextSpan(
text: '${song.title}',
style: const TextStyle(
fontWeight: FontWeight.w300,
),
),
const TextSpan(text: ''),
TextSpan(
text: '${song.artist}',

View file

@ -22,8 +22,8 @@ class PlayPauseButton extends StatelessWidget {
case PlaybackState.playing:
return IconButton(
icon: circle
? const Icon(Icons.pause_circle_filled)
: const Icon(Icons.pause),
? const Icon(Icons.pause_circle_filled_rounded)
: const Icon(Icons.pause_rounded),
iconSize: iconSize,
onPressed: () {
audioStore.pause();
@ -33,8 +33,8 @@ class PlayPauseButton extends StatelessWidget {
default:
return IconButton(
icon: circle
? const Icon(Icons.play_circle_filled)
: const Icon(Icons.play_arrow),
? const Icon(Icons.play_circle_filled_rounded)
: const Icon(Icons.play_arrow_rounded),
iconSize: iconSize,
onPressed: () {
audioStore.play();

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:provider/provider.dart';
import '../state/audio_store.dart';
@ -13,27 +12,11 @@ class PreviousButton extends StatelessWidget {
Widget build(BuildContext context) {
final AudioStore audioStore = Provider.of<AudioStore>(context);
return Observer(
builder: (BuildContext context) {
final int index = audioStore.queueIndexStream.value; //
if (index > 0) { //
return IconButton(
icon: const Icon(Icons.skip_previous), //
iconSize: iconSize,
onPressed: () {
audioStore.skipToPrevious(); //
},
);
}
return IconButton(
icon: const Icon(
Icons.skip_previous,
),
iconSize: iconSize,
onPressed: null,
);
return IconButton(
icon: const Icon(Icons.skip_previous_rounded),
iconSize: iconSize,
onPressed: () {
audioStore.skipToPrevious();
},
);
}

View file

@ -21,35 +21,29 @@ class ShuffleButton extends StatelessWidget {
case ShuffleMode.none:
return IconButton(
icon: const Icon(
Icons.shuffle,
Icons.shuffle_rounded,
color: Colors.white30,
),
iconSize: iconSize,
onPressed: () {
audioStore.setShuffleMode(ShuffleMode.standard);
},
onPressed: () => audioStore.setShuffleMode(ShuffleMode.standard),
);
case ShuffleMode.standard:
return IconButton(
icon: const Icon(
Icons.shuffle,
Icons.shuffle_rounded,
color: Colors.white,
),
iconSize: iconSize,
onPressed: () {
audioStore.setShuffleMode(ShuffleMode.plus);
},
onPressed: () => audioStore.setShuffleMode(ShuffleMode.plus),
);
case ShuffleMode.plus:
return IconButton(
icon: const Icon(
Icons.fingerprint,
Icons.fingerprint_rounded,
color: Colors.white,
),
iconSize: iconSize,
onPressed: () {
audioStore.setShuffleMode(ShuffleMode.none);
},
onPressed: () => audioStore.setShuffleMode(ShuffleMode.none),
);
}
}

View file

@ -5,7 +5,7 @@ import 'package:provider/provider.dart';
import '../../domain/entities/song.dart';
import '../state/audio_store.dart';
import '../state/music_data_store.dart';
import '../theming.dart';
import 'like_button.dart';
class SongCustomizationButtons extends StatelessWidget {
const SongCustomizationButtons({Key key}) : super(key: key);
@ -24,28 +24,20 @@ class SongCustomizationButtons extends StatelessWidget {
children: [
IconButton(
icon: Icon(
Icons.link,
// size: 20.0,
color: song.next == null ? Colors.white70 : LIGHT1,
song.next == null ? Icons.link_off : Icons.link,
color: song.next == null ? Colors.white24 : Colors.white,
),
iconSize: 20.0,
onPressed: () => musicDataStore.toggleNextSongLink(song),
),
const Spacer(),
const IconButton(
icon: Icon(
Icons.favorite,
size: 20.0,
color: Colors.white10,
),
onPressed: null,
),
const LikeButton(),
const Spacer(),
IconButton(
icon: Icon(
Icons.remove_circle_outline,
Icons.remove_circle_outline_rounded,
size: 20.0,
color: isBlocked ? RASPBERRY : Colors.white70,
color: isBlocked ? Colors.white : Colors.white24,
),
onPressed: () => musicDataStore.setSongBlocked(song, !isBlocked),
),