ui stuff around currenty playing
This commit is contained in:
parent
59eba0aa0a
commit
a4d7a0071e
12 changed files with 238 additions and 146 deletions
|
@ -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>(
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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),
|
||||
|
|
59
lib/presentation/widgets/like_button.dart
Normal file
59
lib/presentation/widgets/like_button.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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: () {
|
||||
|
|
|
@ -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(),
|
||||
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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}',
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
|
|
Loading…
Add table
Reference in a new issue