flutter_miniplayer/README.md

273 lines
7.7 KiB
Markdown
Raw Permalink Normal View History

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
2021-09-29 12:31:50 +02:00
Tutorial: https://www.youtube.com/watch?v=umhl2hakkcY
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&lt;double&gt; 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