Migrate to null safety

This commit is contained in:
David Peters 2021-03-06 15:26:00 +01:00
commit b44039f9b6
9 changed files with 87 additions and 85 deletions

View File

@ -3,7 +3,7 @@ import 'screens/audio_screen.dart';
import 'widgets/player.dart'; import 'widgets/player.dart';
import 'utils.dart'; import 'utils.dart';
ValueNotifier<AudioObject> currentlyPlaying = ValueNotifier(null); ValueNotifier<AudioObject?> currentlyPlaying = ValueNotifier(null);
const double playerMinHeight = 70; const double playerMinHeight = 70;
const double playerMaxHeight = 370; const double playerMaxHeight = 370;
@ -48,30 +48,20 @@ class _MyHomePageState extends State<MyHomePage> {
), ),
ValueListenableBuilder( ValueListenableBuilder(
valueListenable: currentlyPlaying, valueListenable: currentlyPlaying,
builder: builder: (BuildContext context, AudioObject? audioObject,
(BuildContext context, AudioObject audioObject, Widget child) => Widget? child) =>
audioObject != null audioObject != null
? DetailedPlayer(audioObject: audioObject) ? DetailedPlayer(audioObject: audioObject)
: Container(), : Container(),
), ),
], ],
), ),
bottomNavigationBar: ValueListenableBuilder( bottomNavigationBar: ValueListenableBuilder(
valueListenable: playerExpandProgress, valueListenable: playerExpandProgress,
child: BottomNavigationBar( builder: (BuildContext context, double height, Widget? child) {
currentIndex: 0,
selectedItemColor: Colors.blue,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Feed'),
BottomNavigationBarItem(
icon: Icon(Icons.library_books), label: 'Library'),
],
),
builder: (BuildContext context, double height, Widget child) {
final value = percentageFromValueInRange( final value = percentageFromValueInRange(
min: playerMinHeight, max: playerMaxHeight, value: height); min: playerMinHeight, max: playerMaxHeight, value: height);
if (value == null) return child;
var opacity = 1 - value; var opacity = 1 - value;
if (opacity < 0) opacity = 0; if (opacity < 0) opacity = 0;
if (opacity > 1) opacity = 1; if (opacity > 1) opacity = 1;
@ -85,6 +75,15 @@ class _MyHomePageState extends State<MyHomePage> {
), ),
); );
}, },
child: BottomNavigationBar(
currentIndex: 0,
selectedItemColor: Colors.blue,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Feed'),
BottomNavigationBarItem(
icon: Icon(Icons.library_books), label: 'Library'),
],
),
), ),
); );
} }

View File

@ -27,7 +27,7 @@ const Set<AudioObject> audioExamples = {
class AudioUi extends StatelessWidget { class AudioUi extends StatelessWidget {
final OnTap onTap; final OnTap onTap;
const AudioUi({Key key, @required this.onTap}) : super(key: key); const AudioUi({Key? key, required this.onTap}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -1,5 +1,3 @@
import 'package:flutter/foundation.dart';
class AudioObject { class AudioObject {
final String title, subtitle, img; final String title, subtitle, img;
@ -7,10 +5,10 @@ class AudioObject {
} }
double valueFromPercentageInRange( double valueFromPercentageInRange(
{@required final double min, max, percentage}) { {required final double min, max, percentage}) {
return percentage * (max - min) + min; return percentage * (max - min) + min;
} }
double percentageFromValueInRange({@required final double min, max, value}) { double percentageFromValueInRange({required final double min, max, value}) {
return (value - min) / (max - min); return (value - min) / (max - min);
} }

View File

@ -9,7 +9,7 @@ class AudioListTile extends StatelessWidget {
final Function onTap; final Function onTap;
const AudioListTile( const AudioListTile(
{Key key, @required this.audioObject, @required this.onTap}) {Key? key, required this.audioObject, required this.onTap})
: super(key: key); : super(key: key);
@override @override
@ -32,7 +32,7 @@ class AudioListTile extends StatelessWidget {
), ),
trailing: IconButton( trailing: IconButton(
icon: Icon(Icons.play_arrow_outlined), icon: Icon(Icons.play_arrow_outlined),
onPressed: onTap, onPressed: () => onTap(),
), ),
); );
} }

View File

@ -12,7 +12,7 @@ final MiniplayerController controller = MiniplayerController();
class DetailedPlayer extends StatelessWidget { class DetailedPlayer extends StatelessWidget {
final AudioObject audioObject; final AudioObject audioObject;
const DetailedPlayer({Key key, @required this.audioObject}) : super(key: key); const DetailedPlayer({Key? key, required this.audioObject}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -150,13 +150,13 @@ class DetailedPlayer extends StatelessWidget {
Text(audioObject.title, Text(audioObject.title,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyText2 .bodyText2!
.copyWith(fontSize: 16)), .copyWith(fontSize: 16)),
Text( Text(
audioObject.subtitle, audioObject.subtitle,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyText2 .bodyText2!
.copyWith( .copyWith(
color: Colors.black.withOpacity(0.55)), color: Colors.black.withOpacity(0.55)),
), ),

View File

@ -37,24 +37,24 @@ class Miniplayer extends StatefulWidget {
///Allows you to use a global ValueNotifier with the current progress. ///Allows you to use a global ValueNotifier with the current progress.
///This can be used to hide the BottomNavigationBar. ///This can be used to hide the BottomNavigationBar.
final ValueNotifier<double> valueNotifier; final ValueNotifier<double>? valueNotifier;
///Deprecated ///Deprecated
@Deprecated( @Deprecated(
"Migrate onDismiss to onDismissed as onDismiss will be used differently in a future version.") "Migrate onDismiss to onDismissed as onDismiss will be used differently in a future version.")
final Function onDismiss; final Function? onDismiss;
///If onDismissed is set, the miniplayer can be dismissed ///If onDismissed is set, the miniplayer can be dismissed
final Function onDismissed; final Function? onDismissed;
//Allows you to manually control the miniplayer in code //Allows you to manually control the miniplayer in code
final MiniplayerController controller; final MiniplayerController? controller;
const Miniplayer({ const Miniplayer({
Key key, Key? key,
@required this.minHeight, required this.minHeight,
@required this.maxHeight, required this.maxHeight,
@required this.builder, required this.builder,
this.curve = Curves.easeOut, this.curve = Curves.easeOut,
this.elevation = 0, this.elevation = 0,
this.backgroundColor = const Color(0x70000000), this.backgroundColor = const Color(0x70000000),
@ -70,17 +70,17 @@ class Miniplayer extends StatefulWidget {
} }
class _MiniplayerState extends State<Miniplayer> with TickerProviderStateMixin { class _MiniplayerState extends State<Miniplayer> with TickerProviderStateMixin {
ValueNotifier<double> heightNotifier; late ValueNotifier<double> heightNotifier;
ValueNotifier<double> dragDownPercentage = ValueNotifier(0); ValueNotifier<double> dragDownPercentage = ValueNotifier(0);
///Temporary variable as long as onDismiss is deprecated. Will be removed in a future version. ///Temporary variable as long as onDismiss is deprecated. Will be removed in a future version.
Function onDismissed; Function? onDismissed;
///Current y position of drag gesture ///Current y position of drag gesture
double _dragHeight; late double _dragHeight;
///Used to determine SnapPosition ///Used to determine SnapPosition
double _startHeight; late double _startHeight;
bool dismissed = false; bool dismissed = false;
@ -91,19 +91,19 @@ class _MiniplayerState extends State<Miniplayer> with TickerProviderStateMixin {
StreamController<double> _heightController = StreamController<double> _heightController =
StreamController<double>.broadcast(); StreamController<double>.broadcast();
AnimationController _animationController; AnimationController? _animationController;
void _statusListener(AnimationStatus status) { void _statusListener(AnimationStatus status) {
if (status == AnimationStatus.completed) _resetAnimationController(); if (status == AnimationStatus.completed) _resetAnimationController();
} }
void _resetAnimationController({Duration duration}) { void _resetAnimationController({Duration? duration}) {
if (_animationController != null) _animationController.dispose(); if (_animationController != null) _animationController!.dispose();
_animationController = AnimationController( _animationController = AnimationController(
vsync: this, vsync: this,
duration: duration == null ? widget.duration : duration, duration: duration ?? widget.duration,
); );
_animationController.addStatusListener(_statusListener); _animationController!.addStatusListener(_statusListener);
animating = false; animating = false;
} }
@ -112,14 +112,14 @@ class _MiniplayerState extends State<Miniplayer> with TickerProviderStateMixin {
if (widget.valueNotifier == null) if (widget.valueNotifier == null)
heightNotifier = ValueNotifier(widget.minHeight); heightNotifier = ValueNotifier(widget.minHeight);
else else
heightNotifier = widget.valueNotifier; heightNotifier = widget.valueNotifier!;
_resetAnimationController(); _resetAnimationController();
_dragHeight = heightNotifier.value; _dragHeight = heightNotifier.value;
if (widget.controller != null) if (widget.controller != null)
widget.controller.addListener(controllerListener); widget.controller!.addListener(controllerListener);
if (widget.onDismissed != null) if (widget.onDismissed != null)
onDismissed = widget.onDismissed; onDismissed = widget.onDismissed;
@ -133,10 +133,10 @@ class _MiniplayerState extends State<Miniplayer> with TickerProviderStateMixin {
@override @override
void dispose() { void dispose() {
_heightController.close(); _heightController.close();
_animationController.dispose(); if (_animationController != null) _animationController!.dispose();
if (widget.controller != null) if (widget.controller != null)
widget.controller.removeListener(controllerListener); widget.controller!.removeListener(controllerListener);
super.dispose(); super.dispose();
} }
@ -154,8 +154,9 @@ class _MiniplayerState extends State<Miniplayer> with TickerProviderStateMixin {
return true; return true;
}, },
child: ValueListenableBuilder( child: ValueListenableBuilder(
builder: (BuildContext context, double value, Widget child) { valueListenable: heightNotifier,
var _percentage = ((value - widget.minHeight)) / builder: (BuildContext context, double height, Widget? _) {
var _percentage = ((height - widget.minHeight)) /
(widget.maxHeight - widget.minHeight); (widget.maxHeight - widget.minHeight);
return Stack( return Stack(
@ -173,12 +174,13 @@ class _MiniplayerState extends State<Miniplayer> with TickerProviderStateMixin {
Align( Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: SizedBox( child: SizedBox(
height: value, height: height,
child: GestureDetector( child: GestureDetector(
child: ValueListenableBuilder( child: ValueListenableBuilder(
valueListenable: dragDownPercentage, valueListenable: dragDownPercentage,
builder: (context, value, child) { builder:
if (value == 0) return child; (BuildContext context, double value, Widget? child) {
if (value == 0) return child!;
return Opacity( return Opacity(
opacity: borderDouble( opacity: borderDouble(
@ -193,7 +195,7 @@ class _MiniplayerState extends State<Miniplayer> with TickerProviderStateMixin {
color: Theme.of(context).canvasColor, color: Theme.of(context).canvasColor,
child: Container( child: Container(
constraints: BoxConstraints.expand(), constraints: BoxConstraints.expand(),
child: widget.builder(value, _percentage), child: widget.builder(height, _percentage),
decoration: BoxDecoration( decoration: BoxDecoration(
boxShadow: <BoxShadow>[ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
@ -278,7 +280,6 @@ class _MiniplayerState extends State<Miniplayer> with TickerProviderStateMixin {
], ],
); );
}, },
valueListenable: heightNotifier,
), ),
); );
} }
@ -306,7 +307,7 @@ class _MiniplayerState extends State<Miniplayer> with TickerProviderStateMixin {
dragDownPercentage.value = percentageDown; dragDownPercentage.value = percentageDown;
if (percentageDown >= 1 && animation && !dismissed) { if (percentageDown >= 1 && animation && !dismissed) {
if (onDismissed != null) onDismissed(); if (onDismissed != null) onDismissed!();
setState(() { setState(() {
dismissed = true; dismissed = true;
}); });
@ -330,7 +331,8 @@ class _MiniplayerState extends State<Miniplayer> with TickerProviderStateMixin {
} }
///Animates the panel height to a specific value ///Animates the panel height to a specific value
void _animateToHeight(final double h, {Duration duration}) { void _animateToHeight(final double h, {Duration? duration}) {
if (_animationController == null) return;
final startHeight = _dragHeight; final startHeight = _dragHeight;
if (duration != null) _resetAnimationController(duration: duration); if (duration != null) _resetAnimationController(duration: duration);
@ -339,7 +341,7 @@ class _MiniplayerState extends State<Miniplayer> with TickerProviderStateMixin {
begin: startHeight, begin: startHeight,
end: h, end: h,
).animate( ).animate(
CurvedAnimation(parent: _animationController, curve: widget.curve)); CurvedAnimation(parent: _animationController!, curve: widget.curve));
_sizeAnimation.addListener(() { _sizeAnimation.addListener(() {
if (_sizeAnimation.value == startHeight) return; if (_sizeAnimation.value == startHeight) return;
@ -350,34 +352,37 @@ class _MiniplayerState extends State<Miniplayer> with TickerProviderStateMixin {
}); });
animating = true; animating = true;
_animationController.forward(from: 0); _animationController!.forward(from: 0);
} }
//Listener function for the controller //Listener function for the controller
void controllerListener() { void controllerListener() {
switch (widget.controller.value.height) { if (widget.controller == null) return;
if (widget.controller!.value == null) return;
switch (widget.controller!.value!.height) {
case -1: case -1:
_animateToHeight( _animateToHeight(
widget.minHeight, widget.minHeight,
duration: widget.controller.value.duration, duration: widget.controller!.value!.duration,
); );
break; break;
case -2: case -2:
_animateToHeight( _animateToHeight(
widget.maxHeight, widget.maxHeight,
duration: widget.controller.value.duration, duration: widget.controller!.value!.duration,
); );
break; break;
case -3: case -3:
_animateToHeight( _animateToHeight(
0, 0,
duration: widget.controller.value.duration, duration: widget.controller!.value!.duration,
); );
break; break;
default: default:
_animateToHeight( _animateToHeight(
widget.controller.value.height.toDouble(), widget.controller!.value!.height.toDouble(),
duration: widget.controller.value.duration, duration: widget.controller!.value!.duration,
); );
break; break;
} }
@ -390,29 +395,30 @@ enum PanelState { MAX, MIN, DISMISS }
//ControllerData class. Used for the controller //ControllerData class. Used for the controller
class ControllerData { class ControllerData {
final int height; final int height;
final Duration duration; final Duration? duration;
const ControllerData(this.height, this.duration); const ControllerData(this.height, this.duration);
} }
//MiniplayerController class //MiniplayerController class
class MiniplayerController extends ValueNotifier<ControllerData> { class MiniplayerController extends ValueNotifier<ControllerData?> {
MiniplayerController() : super(null); MiniplayerController() : super(null);
//Animates to a given height or state(expanded, dismissed, ...) //Animates to a given height or state(expanded, dismissed, ...)
void animateToHeight({double height, PanelState state, Duration duration}) { void animateToHeight(
{double? height, PanelState? state, Duration? duration}) {
if (height == null && state == null) if (height == null && state == null)
throw ("Miniplayer: One of the two parameters, height or status, is required."); throw ("Miniplayer: One of the two parameters, height or status, is required.");
if (height != null && state != null) if (height != null && state != null)
throw ("Miniplayer: Only one of the two parameters, height or status, can be specified."); throw ("Miniplayer: Only one of the two parameters, height or status, can be specified.");
ControllerData valBefore = value; ControllerData? valBefore = value;
if (state != null) if (state != null)
value = ControllerData(state.heightCode, duration); value = ControllerData(state.heightCode, duration);
else { else {
if (height < 0) return; if (height! < 0) return;
value = ControllerData(height.round(), duration); value = ControllerData(height.round(), duration);
} }

View File

@ -2,11 +2,10 @@ import 'package:flutter/material.dart';
class MiniplayerWillPopScope extends StatefulWidget { class MiniplayerWillPopScope extends StatefulWidget {
const MiniplayerWillPopScope({ const MiniplayerWillPopScope({
Key key, Key? key,
@required this.child, required this.child,
@required this.onWillPop, required this.onWillPop,
}) : assert(child != null), }) : super(key: key);
super(key: key);
final Widget child; final Widget child;
final WillPopCallback onWillPop; final WillPopCallback onWillPop;
@ -14,15 +13,15 @@ class MiniplayerWillPopScope extends StatefulWidget {
@override @override
_MiniplayerWillPopScopeState createState() => _MiniplayerWillPopScopeState(); _MiniplayerWillPopScopeState createState() => _MiniplayerWillPopScopeState();
static _MiniplayerWillPopScopeState of(BuildContext context) { static _MiniplayerWillPopScopeState? of(BuildContext context) {
return context.findAncestorStateOfType<_MiniplayerWillPopScopeState>(); return context.findAncestorStateOfType<_MiniplayerWillPopScopeState>();
} }
} }
class _MiniplayerWillPopScopeState extends State<MiniplayerWillPopScope> { class _MiniplayerWillPopScopeState extends State<MiniplayerWillPopScope> {
ModalRoute<dynamic> _route; ModalRoute<dynamic>? _route;
_MiniplayerWillPopScopeState _descendant; _MiniplayerWillPopScopeState? _descendant;
set descendant(state) { set descendant(state) {
_descendant = state; _descendant = state;
@ -30,11 +29,11 @@ class _MiniplayerWillPopScopeState extends State<MiniplayerWillPopScope> {
} }
Future<bool> onWillPop() async { Future<bool> onWillPop() async {
bool willPop; bool? willPop;
if (_descendant != null) { if (_descendant != null) {
willPop = await _descendant.onWillPop(); willPop = await _descendant!.onWillPop();
} }
if (willPop == null || willPop) { if (willPop == null) {
willPop = await widget.onWillPop(); willPop = await widget.onWillPop();
} }
return willPop; return willPop;

View File

@ -16,11 +16,11 @@ extension SelectedColorExtension on PanelState {
} }
///Calculates the percentage of a value within a given range of values ///Calculates the percentage of a value within a given range of values
double percentageFromValueInRange({final double min, max, value}) { double percentageFromValueInRange({required double min, max, value}) {
return (value - min) / (max - min); return (value - min) / (max - min);
} }
double borderDouble({double minRange, double maxRange, double value}) { double borderDouble({required double minRange, maxRange, value}) {
if (value > maxRange) return maxRange; if (value > maxRange) return maxRange;
if (value < minRange) return minRange; if (value < minRange) return minRange;
return value; return value;

View File

@ -5,8 +5,8 @@ homepage: https://www.peterscode.dev
repository: https://github.com/peterscodee/miniplayer repository: https://github.com/peterscodee/miniplayer
environment: environment:
sdk: ">=2.6.0 <3.0.0" sdk: ">=2.12.0 <3.0.0"
flutter: ">=1.0.0 <2.0.0" flutter: ">=2.0.1 <3.0.0"
dependencies: dependencies:
flutter: flutter: