[![Pub](https://img.shields.io/pub/v/miniplayer?color=2196F3)](https://pub.dev/packages/miniplayer) A lightweight flutter package to simplify the creation of a miniplayer by providing a builder function with the current height and percentage progress. The widget responds to tap and drag gestures and is highly customizable. **What is a miniplayer?** Miniplayers are commonly used in media applications like Spotify and Youtube. A miniplayer can be expanded and minified and remains on the screen when minified until dismissed by the user. See the demo below for an example. Tutorial: https://www.youtube.com/watch?v=umhl2hakkcY ## Demo ![demo](./example/demo_gif/demo.gif "demo") ## Usage ```dart Stack( children: [ YourApp(), Miniplayer( minHeight: 70, maxHeight: 370, builder: (height, percentage) { return Center( child: Text('$height, $percentage'), ); }, ), ], ), ``` ## Options
Parameter Implementation Example
onDismiss
Miniplayer(
   onDismiss: () {
      //Handle onDismissed here
   }, 
),
      

If onDismiss is set, the miniplayer can be dismissed

valueNotifier
final ValueNotifier<double> playerExpandProgress =
    ValueNotifier(playerMinHeight);
    
Miniplayer( valueNotifier: playerExpandProgress, ),

Allows you to use a global ValueNotifier with the current progress. This can be used to hide the BottomNavigationBar.

controller
final MiniplayerController controller = MiniplayerController();
    
Miniplayer( controller: controller, ),
controller.animateToHeight(state: PanelState.MAX);
## Persistence Implementing the miniplayer as described under [usage](https://pub.dev/packages/miniplayer#usage) - for instance by wrapping it inside a `Stack` in the `Scaffold` body - would work out of the box but has some disadvantages. If you push a new screen via `Navigator.push` the miniplayer would disappear. What we want is a persistent miniplayer which stays on the screen. If you want to archive persistency, you have the choice between two embedding options, which depends on your use case. The [first method](https://pub.dev/packages/miniplayer#first-method-simple) is only recommended for simple apps. If you want to use dialogs or other persistent widgets such as a BottomNavigationBar, the [second](https://pub.dev/packages/miniplayer#second-method-advanced) (slightly more advanced) method is the right fit for you. ## First method (Simple) Using a `Stack` in the [builder](https://api.flutter.dev/flutter/material/MaterialApp/builder.html) method ```dart import 'package:flutter/material.dart'; import 'package:miniplayer/miniplayer.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Miniplayer example', theme: ThemeData( visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(), builder: (context, child) { // <--- Important part return Stack( children: [ child, Miniplayer( minHeight: 70, maxHeight: 370, builder: (height, percentage) { if(percentage > 0.2) //return Text('!mini'); else //return Text('mini'); }, ), ], ); }, ); } } ``` ## Second method (Advanced) Using a `Stack` in combination with a custom `Navigator` ```dart import 'package:flutter/material.dart'; import 'package:miniplayer/miniplayer.dart'; void main() => runApp(MyApp()); final _navigatorKey = GlobalKey(); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Miniplayer example', debugShowCheckedModeBanner: false, theme: ThemeData( primaryColor: Color(0xFFFAFAFA), ), home: MyHomePage(), ); } } class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return MiniplayerWillPopScope( onWillPop: () async { final NavigatorState navigator = _navigatorKey.currentState; if (!navigator.canPop()) return true; navigator.pop(); return false; }, child: Scaffold( body: Stack( children: [ Navigator( key: _navigatorKey, onGenerateRoute: (RouteSettings settings) => MaterialPageRoute( settings: settings, builder: (BuildContext context) => FirstScreen(), ), ), Miniplayer( minHeight: 70, maxHeight: 370, builder: (height, percentage) => Center( child: Text('$height, $percentage'), ), ), ], ), bottomNavigationBar: BottomNavigationBar( currentIndex: 0, fixedColor: Colors.blue, items: [ BottomNavigationBarItem( icon: Icon(Icons.home), label: 'Home', ), BottomNavigationBarItem( icon: Icon(Icons.mail), label: 'Messages', ), BottomNavigationBarItem( icon: Icon(Icons.person), label: 'Profile', ) ], ), ), ); } } class FirstScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Demo: FirstScreen')), body: Container( constraints: BoxConstraints.expand(), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ ElevatedButton( onPressed: () => Navigator.push( context, MaterialPageRoute(builder: (context) => SecondScreen()), ), child: const Text('Open SecondScreen'), ), ElevatedButton( onPressed: () => Navigator.of(context, rootNavigator: true).push( MaterialPageRoute(builder: (context) => ThirdScreen()), ), child: const Text('Open ThirdScreen with root Navigator'), ), ], ), ), ); } } class SecondScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Demo: SecondScreen')), body: Center(child: Text('SecondScreen')), ); } } class ThirdScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Demo: ThirdScreen')), body: Center(child: Text('ThirdScreen')), ); } } ``` ## Roadmap - [ ] Provide better examples - [ ] Add an option to handle horizontal gestures as well (like Spotify does) - [ ] Rewrite the API for onDismiss (breaking change) - [x] Marked onDismiss ad deprecated