2020-07-30 16:01:58 +02:00
[![Pub ](https://img.shields.io/pub/v/miniplayer?color=2196F3 )](https://pub.dev/packages/miniplayer)
2020-07-30 15:58:35 +02:00
2020-09-23 10:22:29 +02:00
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?**
2020-09-23 10:26:59 +02:00
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.
2020-09-23 10:22:29 +02:00
See the demo below for an example.
2020-07-29 22:13:39 +02:00
2020-08-03 15:03:54 +02:00
## Demo
2020-08-26 14:47:39 +02:00
![demo ](./example/demo_gif/demo.gif "demo" )
2020-08-03 15:03:54 +02:00
2020-07-30 21:38:41 +02:00
## Usage
2020-07-29 22:13:39 +02:00
2020-07-30 17:02:14 +02:00
```dart
2020-09-26 16:19:58 +02:00
Stack(
children: < Widget > [
YourApp(),
Miniplayer(
minHeight: 70,
maxHeight: 370,
builder: (height, percentage) {
return Center(
child: Text('$height, $percentage'),
);
},
),
],
2020-07-30 17:02:14 +02:00
),
```
2020-09-23 10:22:29 +02:00
2020-09-23 10:26:59 +02:00
## Options
2020-09-23 10:22:29 +02:00
< table >
2020-09-23 15:21:26 +02:00
< tr > < / tr >
< tr >
< th > Parameter< / th >
< th > Implementation< / th >
2021-03-06 11:41:29 +01:00
< th > Example< / th >
2020-09-23 15:21:26 +02:00
< / tr >
2020-09-23 10:22:29 +02:00
< tr >
< td > onDismiss< / td >
2020-09-23 10:26:59 +02:00
< td >
2020-09-23 10:22:29 +02:00
< pre lang = "dart" >
Miniplayer(
onDismiss: () {
2020-09-23 10:30:13 +02:00
//Handle onDismissed here
2020-09-23 10:22:29 +02:00
},
),
< / pre >
< / td >
2020-09-23 10:58:01 +02:00
< td >
2020-09-23 11:44:09 +02:00
< img src = "https://raw.githubusercontent.com/peterscodee/miniplayer/master/example/demo_gif/demo_dismiss.gif" / >
2020-09-24 20:16:03 +02:00
< p > If onDismiss is set, the miniplayer can be dismissed< / p >
2020-09-23 10:22:29 +02:00
< / td >
< / tr >
2020-09-23 11:05:30 +02:00
< tr > < / tr >
2020-09-23 10:58:01 +02:00
< tr >
< td > valueNotifier< / td >
< td >
< pre lang = "dart" >
2020-09-24 20:16:03 +02:00
final ValueNotifier< double> playerExpandProgress =
2020-09-23 10:58:01 +02:00
ValueNotifier(playerMinHeight);
2020-09-24 20:16:03 +02:00
< / br >
2020-09-23 10:58:01 +02:00
Miniplayer(
valueNotifier: playerExpandProgress,
),
< / pre >
< / td >
< td >
2020-09-23 11:44:09 +02:00
< img src = "https://raw.githubusercontent.com/peterscodee/miniplayer/master/example/demo_gif/demo_valueNotifier.gif" / >
2020-09-24 20:16:03 +02:00
< p > Allows you to use a global ValueNotifier with the current progress. This can be used to hide the BottomNavigationBar.< / p >
2020-09-23 10:58:01 +02:00
< / td >
< / tr >
2021-03-06 11:41:29 +01:00
< tr > < / tr >
< tr >
< td > controller< / td >
< td >
< pre lang = "dart" >
final MiniplayerController controller = MiniplayerController();
< / br >
Miniplayer(
controller: controller,
),
< / br >
controller.animateToHeight(state: PanelState.MAX);
< / pre >
< / td >
< td > < / td >
< / tr >
2020-09-23 10:22:29 +02:00
< / table >
2020-09-26 16:19:58 +02:00
## Persistence
2021-03-06 11:41:29 +01:00
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.
2020-09-26 16:19:58 +02:00
2021-03-06 11:41:29 +01:00
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.
2020-07-30 21:38:41 +02:00
2020-09-26 16:19:58 +02:00
## First method (Simple)
2021-03-06 11:41:29 +01:00
Using a `Stack` in the [builder ](https://api.flutter.dev/flutter/material/MaterialApp/builder.html ) method
2020-07-31 08:45:33 +02:00
```dart
2020-07-30 21:38:41 +02:00
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(
2021-03-06 11:41:29 +01:00
title: 'Miniplayer example',
2020-07-30 21:38:41 +02:00
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');
},
),
],
);
},
);
}
}
```
2020-09-26 16:19:58 +02:00
## Second method (Advanced)
2021-03-06 11:41:29 +01:00
Using a `Stack` in combination with a custom `Navigator`
```dart
import 'package:flutter/material.dart';
import 'package:miniplayer/miniplayer.dart';
2020-07-30 21:38:41 +02:00
2021-03-06 11:41:29 +01:00
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: < Widget > [
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')),
);
}
}
```
2020-10-06 17:28:51 +02:00
## Roadmap
2021-03-06 11:41:29 +01:00
- [ ] Provide better examples
2020-10-06 17:28:51 +02:00
- [ ] Add an option to handle horizontal gestures as well (like Spotify does)
- [ ] Rewrite the API for onDismiss (breaking change)
2021-03-06 11:41:29 +01:00
- [x] Marked onDismiss ad deprecated