From a4d7a0071ed4ee6da002ee6f792a78b75d2432f2 Mon Sep 17 00:00:00 2001 From: Moritz Weber Date: Thu, 31 Dec 2020 17:52:20 +0100 Subject: [PATCH] ui stuff around currenty playing --- lib/presentation/pages/currently_playing.dart | 79 ++++++++++++++---- lib/presentation/pages/queue_page.dart | 83 +++++++++++-------- lib/presentation/widgets/album_art.dart | 44 ++++++---- .../widgets/album_background.dart | 24 +----- lib/presentation/widgets/like_button.dart | 59 +++++++++++++ lib/presentation/widgets/loop_button.dart | 2 +- lib/presentation/widgets/next_button.dart | 12 ++- lib/presentation/widgets/next_song.dart | 8 +- .../widgets/play_pause_button.dart | 8 +- lib/presentation/widgets/previous_button.dart | 27 ++---- lib/presentation/widgets/shuffle_button.dart | 18 ++-- .../widgets/song_customization_buttons.dart | 20 ++--- 12 files changed, 238 insertions(+), 146 deletions(-) create mode 100644 lib/presentation/widgets/like_button.dart diff --git a/lib/presentation/pages/currently_playing.dart b/lib/presentation/pages/currently_playing.dart index d39e4c7..254587d 100644 --- a/lib/presentation/pages/currently_playing.dart +++ b/lib/presentation/pages/currently_playing.dart @@ -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( diff --git a/lib/presentation/pages/queue_page.dart b/lib/presentation/pages/queue_page.dart index a6d6787..503961a 100644 --- a/lib/presentation/pages/queue_page.dart +++ b/lib/presentation/pages/queue_page.dart @@ -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 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: diff --git a/lib/presentation/widgets/album_art.dart b/lib/presentation/widgets/album_art.dart index c5894ef..36ec713 100644 --- a/lib/presentation/widgets/album_art.dart +++ b/lib/presentation/widgets/album_art.dart @@ -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: [ 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), + ), + ], + ), ), ], ), diff --git a/lib/presentation/widgets/album_background.dart b/lib/presentation/widgets/album_background.dart index d089cfc..ca8f2ae 100644 --- a/lib/presentation/widgets/album_background.dart +++ b/lib/presentation/widgets/album_background.dart @@ -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), diff --git a/lib/presentation/widgets/like_button.dart b/lib/presentation/widgets/like_button.dart new file mode 100644 index 0000000..0742354 --- /dev/null +++ b/lib/presentation/widgets/like_button.dart @@ -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(context); + final AudioStore audioStore = Provider.of(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, + ); + } + }, + ); + } +} diff --git a/lib/presentation/widgets/loop_button.dart b/lib/presentation/widgets/loop_button.dart index 0dfb07a..a0cfbb5 100644 --- a/lib/presentation/widgets/loop_button.dart +++ b/lib/presentation/widgets/loop_button.dart @@ -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: () { diff --git a/lib/presentation/widgets/next_button.dart b/lib/presentation/widgets/next_button.dart index 60fa5cd..602eaa1 100644 --- a/lib/presentation/widgets/next_button.dart +++ b/lib/presentation/widgets/next_button.dart @@ -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(), ); } diff --git a/lib/presentation/widgets/next_song.dart b/lib/presentation/widgets/next_song.dart index b935fef..25051f9 100644 --- a/lib/presentation/widgets/next_song.dart +++ b/lib/presentation/widgets/next_song.dart @@ -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}', diff --git a/lib/presentation/widgets/play_pause_button.dart b/lib/presentation/widgets/play_pause_button.dart index 13747e1..273e284 100644 --- a/lib/presentation/widgets/play_pause_button.dart +++ b/lib/presentation/widgets/play_pause_button.dart @@ -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(); diff --git a/lib/presentation/widgets/previous_button.dart b/lib/presentation/widgets/previous_button.dart index 81ff73a..97b81b0 100644 --- a/lib/presentation/widgets/previous_button.dart +++ b/lib/presentation/widgets/previous_button.dart @@ -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(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(); }, ); } diff --git a/lib/presentation/widgets/shuffle_button.dart b/lib/presentation/widgets/shuffle_button.dart index 269cbc4..5b90d3e 100644 --- a/lib/presentation/widgets/shuffle_button.dart +++ b/lib/presentation/widgets/shuffle_button.dart @@ -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), ); } } diff --git a/lib/presentation/widgets/song_customization_buttons.dart b/lib/presentation/widgets/song_customization_buttons.dart index 52312e6..f5248c3 100644 --- a/lib/presentation/widgets/song_customization_buttons.dart +++ b/lib/presentation/widgets/song_customization_buttons.dart @@ -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), ),