Jonny Borges

prevent snackbars appear simultaneosly

... ... @@ -2,28 +2,51 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'lang/translation_service.dart';
import 'routes/app_pages.dart';
import 'shared/logger/logger_utils.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// class MyApp extends StatelessWidget {
// const MyApp({Key? key}) : super(key: key);
// @override
// Widget build(BuildContext context) {
// return GetMaterialApp.router(
// debugShowCheckedModeBanner: false,
// enableLog: true,
// logWriterCallback: Logger.write,
// // initialRoute: AppPages.INITIAL,
// getPages: AppPages.routes,
// locale: TranslationService.locale,
// fallbackLocale: TranslationService.fallbackLocale,
// translations: TranslationService(),
// );
// }
// }
class First extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp.router(
debugShowCheckedModeBanner: false,
enableLog: true,
logWriterCallback: Logger.write,
// initialRoute: AppPages.INITIAL,
getPages: AppPages.routes,
locale: TranslationService.locale,
fallbackLocale: TranslationService.fallbackLocale,
translations: TranslationService(),
return Scaffold(
appBar: AppBar(
title: Text('page one'),
leading: IconButton(
icon: Icon(Icons.more),
onPressed: () async {
var controller = Get.snackbar('dsdsds', 'sdsdsdsds');
},
),
),
body: Center(
child: Container(
height: 300,
width: 300,
child: ElevatedButton(
onPressed: () {},
child: Text('next screen'),
),
),
),
);
}
}
... ... @@ -33,98 +56,68 @@ class MyApp extends StatelessWidget {
// runApp(MyApp());
// }
// class MyApp extends StatelessWidget {
// MyApp({Key? key}) : super(key: key);
// @override
// Widget build(BuildContext context) {
// return GetMaterialApp.router(
// getPages: [
// GetPage(
// participatesInRootNavigator: true,
// name: '/first',
// page: () => First()),
// GetPage(
// name: '/second',
// page: () => Second(),
// ),
// GetPage(
// name: '/third',
// page: () => Third(),
// ),
// ],
// debugShowCheckedModeBanner: false,
// );
// }
// }
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
// class First extends StatelessWidget {
// @override
// Widget build(BuildContext context) {
// return Scaffold(
// appBar: AppBar(
// title: Text('page one'),
// leading: IconButton(
// icon: Icon(Icons.more),
// onPressed: () {
// Get.changeTheme(
// context.isDarkMode ? ThemeData.light() : ThemeData.dark());
// },
// ),
// ),
// body: Center(
// child: Container(
// height: 300,
// width: 300,
// child: ElevatedButton(
// onPressed: () {},
// child: Text('next screen'),
// ),
// ),
// ),
// );
// }
// }
@override
Widget build(BuildContext context) {
return GetMaterialApp.router(
getPages: [
GetPage(
participatesInRootNavigator: true, name: '/', page: () => First()),
GetPage(
name: '/second',
page: () => Second(),
),
GetPage(
name: '/third',
page: () => Third(),
),
],
debugShowCheckedModeBanner: false,
);
}
}
// class Second extends StatelessWidget {
// @override
// Widget build(BuildContext context) {
// return Scaffold(
// appBar: AppBar(
// title: Text('page two ${Get.parameters["id"]}'),
// ),
// body: Center(
// child: Container(
// height: 300,
// width: 300,
// child: ElevatedButton(
// onPressed: () {},
// child: Text('next screen'),
// ),
// ),
// ),
// );
// }
// }
class Second extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('page two ${Get.parameters["id"]}'),
),
body: Center(
child: Container(
height: 300,
width: 300,
child: ElevatedButton(
onPressed: () {},
child: Text('next screen'),
),
),
),
);
}
}
// class Third extends StatelessWidget {
// @override
// Widget build(BuildContext context) {
// return Scaffold(
// backgroundColor: Colors.red,
// appBar: AppBar(
// title: Text('page three'),
// ),
// body: Center(
// child: Container(
// height: 300,
// width: 300,
// child: ElevatedButton(
// onPressed: () {},
// child: Text('go to first screen'),
// ),
// ),
// ),
// );
// }
// }
class Third extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.red,
appBar: AppBar(
title: Text('page three'),
),
body: Center(
child: Container(
height: 300,
width: 300,
child: ElevatedButton(
onPressed: () {},
child: Text('go to first screen'),
),
),
),
);
}
}
... ...
... ... @@ -17,3 +17,4 @@ export 'src/routes/observers/route_observer.dart';
export 'src/routes/route_middleware.dart';
export 'src/routes/transitions_type.dart';
export 'src/snackbar/snack.dart';
export 'src/snackbar/snackbar_controller.dart';
... ...
... ... @@ -298,7 +298,7 @@ extension ExtensionSnackbar on GetInterface {
OnTap? onTap,
Duration duration = const Duration(seconds: 3),
bool isDismissible = true,
SnackDismissDirection dismissDirection = SnackDismissDirection.VERTICAL,
DismissDirection? dismissDirection,
bool showProgressIndicator = false,
AnimationController? progressIndicatorController,
Color? progressIndicatorBackgroundColor,
... ... @@ -314,7 +314,7 @@ extension ExtensionSnackbar on GetInterface {
Color? overlayColor,
Form? userInputForm,
}) async {
final getBar = GetBar(
final getBar = GetSnackBar(
snackbarStatus: snackbarStatus,
title: title,
message: message,
... ... @@ -361,11 +361,13 @@ extension ExtensionSnackbar on GetInterface {
}
}
Future<void> showSnackbar<T>(GetBar snackbar) {
return SnackbarController(snackbar).show();
SnackbarController showSnackbar(GetSnackBar snackbar) {
final controller = SnackbarController(snackbar);
controller.show();
return controller;
}
void snackbar<T>(
SnackbarController snackbar(
String title,
String message, {
Color? colorText,
... ... @@ -392,7 +394,7 @@ extension ExtensionSnackbar on GetInterface {
OnTap? onTap,
bool? isDismissible,
bool? showProgressIndicator,
SnackDismissDirection? dismissDirection,
DismissDirection? dismissDirection,
AnimationController? progressIndicatorController,
Color? progressIndicatorBackgroundColor,
Animation<Color>? progressIndicatorValueColor,
... ... @@ -405,8 +407,8 @@ extension ExtensionSnackbar on GetInterface {
SnackbarStatusCallback? snackbarStatus,
Color? overlayColor,
Form? userInputForm,
}) async {
final getBar = GetBar(
}) {
final getSnackBar = GetSnackBar(
snackbarStatus: snackbarStatus,
titleText: titleText ??
Text(
... ... @@ -444,7 +446,7 @@ extension ExtensionSnackbar on GetInterface {
mainButton: mainButton,
onTap: onTap,
isDismissible: isDismissible ?? true,
dismissDirection: dismissDirection ?? SnackDismissDirection.VERTICAL,
dismissDirection: dismissDirection,
showProgressIndicator: showProgressIndicator ?? false,
progressIndicatorController: progressIndicatorController,
progressIndicatorBackgroundColor: progressIndicatorBackgroundColor,
... ... @@ -457,14 +459,17 @@ extension ExtensionSnackbar on GetInterface {
overlayColor: overlayColor ?? Colors.transparent,
userInputForm: userInputForm);
final controller = SnackbarController(getSnackBar);
if (instantInit) {
showSnackbar<T>(getBar);
controller.show();
} else {
//routing.isSnackbar = true;
SchedulerBinding.instance!.addPostFrameCallback((_) {
showSnackbar<T>(getBar);
controller.show();
});
}
return controller;
}
}
... ...
... ... @@ -7,16 +7,18 @@ import 'package:flutter/scheduler.dart';
import '../../../get_core/get_core.dart';
import '../../get_navigation.dart';
typedef OnTap = void Function(GetBar snack);
typedef OnTap = void Function(GetSnackBar snack);
typedef SnackbarStatusCallback = void Function(SnackbarStatus? status);
class GetBar<T extends Object> extends StatefulWidget {
class GetSnackBar extends StatefulWidget {
/// A callback for you to listen to the different Snack status
final SnackbarStatusCallback? snackbarStatus;
/// The title displayed to the user
final String? title;
final DismissDirection? dismissDirection;
/// The message displayed to the user.
final String? message;
... ... @@ -112,11 +114,6 @@ class GetBar<T extends Object> extends StatefulWidget {
/// [SnackPosition.BOTTOM] is the default.
final SnackPosition snackPosition;
/// [SnackDismissDirection.VERTICAL] by default.
/// Can also be [SnackDismissDirection.HORIZONTAL] in which case both left
/// and right dismiss are allowed.
final SnackDismissDirection dismissDirection;
/// Snack can be floating or be grounded to the edge of the screen.
/// If grounded, I do not recommend using [margin] or [borderRadius].
/// [SnackStyle.FLOATING] is the default
... ... @@ -154,7 +151,7 @@ class GetBar<T extends Object> extends StatefulWidget {
/// Every other widget is ignored if this is not null.
final Form? userInputForm;
const GetBar({
const GetSnackBar({
Key? key,
this.title,
this.message,
... ... @@ -176,7 +173,7 @@ class GetBar<T extends Object> extends StatefulWidget {
this.onTap,
this.duration,
this.isDismissible = true,
this.dismissDirection = SnackDismissDirection.VERTICAL,
this.dismissDirection,
this.showProgressIndicator = false,
this.progressIndicatorController,
this.progressIndicatorBackgroundColor,
... ... @@ -194,13 +191,11 @@ class GetBar<T extends Object> extends StatefulWidget {
}) : super(key: key);
@override
State createState() {
return _GetBarState<T>();
}
State createState() => _GetSnackBarState();
/// Show the snack. It's call [SnackbarStatus.OPENING] state
/// followed by [SnackbarStatus.OPEN]
Future<void> show<T>() async {
SnackbarController show() {
return Get.showSnackbar(this);
}
}
... ... @@ -215,21 +210,14 @@ class GetBar<T extends Object> extends StatefulWidget {
/// with the full snackbar dispose
enum SnackbarStatus { OPEN, CLOSED, OPENING, CLOSING }
/// Indicates the direction in which it is possible to dismiss
/// If vertical, dismiss up will be allowed if [SnackPosition.TOP]
/// If vertical, dismiss down will be allowed if [SnackPosition.BOTTOM]
enum SnackDismissDirection { HORIZONTAL, VERTICAL }
/// Indicates if snack is going to start at the [TOP] or at the [BOTTOM]
enum SnackPosition { TOP, BOTTOM }
/// Indicates if snack will be attached to the edge of the screen or not
enum SnackStyle { FLOATING, GROUNDED }
class _GetBarState<K extends Object> extends State<GetBar>
class _GetSnackBarState extends State<GetSnackBar>
with TickerProviderStateMixin {
SnackbarStatus? currentStatus;
AnimationController? _fadeController;
late Animation<double> _fadeAnimation;
... ... @@ -251,7 +239,7 @@ class _GetBarState<K extends Object> extends State<GetBar>
late CurvedAnimation _progressAnimation;
GlobalKey backgroundBoxKey = GlobalKey();
final _backgroundBoxKey = GlobalKey();
@override
Widget build(BuildContext context) {
... ... @@ -339,8 +327,7 @@ Set either a message or messageText""");
void _configureLeftBarFuture() {
SchedulerBinding.instance!.addPostFrameCallback(
(_) {
final keyContext = backgroundBoxKey.currentContext;
final keyContext = _backgroundBoxKey.currentContext;
if (keyContext != null) {
final box = keyContext.findRenderObject() as RenderBox;
_boxHeightCompleter.complete(box.size);
... ... @@ -386,7 +373,7 @@ Set either a message or messageText""");
Widget _generateInputSnack() {
return Container(
key: backgroundBoxKey,
key: _backgroundBoxKey,
constraints: widget.maxWidth != null
? BoxConstraints(maxWidth: widget.maxWidth!)
: null,
... ... @@ -413,7 +400,7 @@ Set either a message or messageText""");
Widget _generateSnack() {
return Container(
key: backgroundBoxKey,
key: _backgroundBoxKey,
constraints: widget.maxWidth != null
? BoxConstraints(maxWidth: widget.maxWidth!)
: null,
... ...
... ... @@ -6,28 +6,30 @@ import 'package:flutter/material.dart';
import '../../../get.dart';
class SnackbarController {
static final _queue = GetQueue();
late Animation<double> _filterBlurAnimation;
late Animation<Color?> _filterColorAnimation;
final GetBar<Object> snack;
final Completer _transitionCompleter = Completer();
late SnackbarStatusCallback? _snackbarStatus;
final GetSnackBar snack;
final _transitionCompleter = Completer<SnackbarController>();
late SnackbarStatusCallback? _snackbarStatus;
late final Alignment? _initialAlignment;
late final Alignment? _endAlignment;
late final Alignment? _endAlignment;
bool _wasDismissedBySwipe = false;
bool _onTappedDismiss = false;
Timer? _timer;
/// The animation that drives the route's transition and the previous route's
/// forward transition.
late Animation<Alignment> _animation;
late final Animation<Alignment> _animation;
/// The animation controller that the route uses to drive the transitions.
///
/// The animation itself is exposed by the [animation] property.
late AnimationController _controller;
late final AnimationController _controller;
SnackbarStatus? _currentStatus;
... ... @@ -37,72 +39,20 @@ class SnackbarController {
SnackbarController(this.snack);
Future get future => _transitionCompleter.future;
Future<SnackbarController> get future => _transitionCompleter.future;
bool get isSnackbarBeingShown => _currentStatus != SnackbarStatus.CLOSED;
Animation<double> createBlurFilterAnimation() {
return Tween(begin: 0.0, end: snack.overlayBlur).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(
0.0,
0.35,
curve: Curves.easeInOutCirc,
),
),
);
}
Animation<Color?> createColorOverlayColor() {
return ColorTween(begin: const Color(0x00000000), end: snack.overlayColor)
.animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(
0.0,
0.35,
curve: Curves.easeInOutCirc,
),
),
);
}
void removeEntry() {
assert(
!_transitionCompleter.isCompleted,
'Cannot remove entry from a disposed snackbar',
);
_cancelTimer();
if (_wasDismissedBySwipe) {
Timer(const Duration(milliseconds: 200), () {
_controller.reset();
});
_wasDismissedBySwipe = false;
} else {
_controller.reverse();
}
}
void removeOverlay() {
for (var element in _overlayEntries) {
element.remove();
}
assert(!_transitionCompleter.isCompleted, 'Cannot remove overlay twice.');
_controller.dispose();
_overlayEntries.clear();
_transitionCompleter.complete();
Future<void> close() async {
_removeEntry();
await future;
}
Future<void> show() {
_configureOverlay();
return future;
return _queue.add(_show);
}
// ignore: avoid_returning_this
void _cancelTimer() {
if (_timer != null && _timer!.isActive) {
_timer!.cancel();
... ... @@ -138,12 +88,10 @@ class SnackbarController {
assert(!_transitionCompleter.isCompleted,
'Cannot configure a snackbar after disposing it.');
_controller = _createAnimationController();
_configureAlignment(snack.snackPosition);
_snackbarStatus = snack.snackbarStatus;
_filterBlurAnimation = createBlurFilterAnimation();
_filterColorAnimation = createColorOverlayColor();
_filterBlurAnimation = _createBlurFilterAnimation();
_filterColorAnimation = _createColorOverlayColor();
_animation = _createAnimation();
_animation.addStatusListener(_handleStatusChanged);
_configureTimer();
... ... @@ -155,7 +103,7 @@ class SnackbarController {
if (_timer != null && _timer!.isActive) {
_timer!.cancel();
}
_timer = Timer(snack.duration!, removeEntry);
_timer = Timer(snack.duration!, _removeEntry);
} else {
if (_timer != null) {
_timer!.cancel();
... ... @@ -192,6 +140,33 @@ class SnackbarController {
);
}
Animation<double> _createBlurFilterAnimation() {
return Tween(begin: 0.0, end: snack.overlayBlur).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(
0.0,
0.35,
curve: Curves.easeInOutCirc,
),
),
);
}
Animation<Color?> _createColorOverlayColor() {
return ColorTween(begin: const Color(0x00000000), end: snack.overlayColor)
.animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(
0.0,
0.35,
curve: Curves.easeInOutCirc,
),
),
);
}
Iterable<OverlayEntry> _createOverlayEntries(Widget child) {
return <OverlayEntry>[
if (snack.overlayBlur > 0.0) ...[
... ... @@ -249,20 +224,16 @@ class SnackbarController {
});
}
DismissDirection _getDismissDirection() {
if (snack.dismissDirection == SnackDismissDirection.HORIZONTAL) {
return DismissDirection.horizontal;
} else {
if (snack.snackPosition == SnackPosition.TOP) {
return DismissDirection.up;
}
return DismissDirection.down;
DismissDirection _getDefaultDismissDirection() {
if (snack.snackPosition == SnackPosition.TOP) {
return DismissDirection.up;
}
return DismissDirection.down;
}
Widget _getDismissibleSnack(Widget child) {
return Dismissible(
direction: _getDismissDirection(),
direction: snack.dismissDirection ?? _getDefaultDismissDirection(),
resizeDuration: null,
confirmDismiss: (_) {
if (_currentStatus == SnackbarStatus.OPENING ||
... ... @@ -273,9 +244,7 @@ class SnackbarController {
},
key: const Key('dismissible'),
onDismissed: (_) {
_cancelTimer();
_wasDismissedBySwipe = true;
removeEntry();
_onDismiss();
},
child: _getSnackbarContainer(child),
);
... ... @@ -309,8 +278,51 @@ class SnackbarController {
assert(!_overlayEntries.first.opaque);
_currentStatus = SnackbarStatus.CLOSED;
_snackbarStatus?.call(_currentStatus);
removeOverlay();
_removeOverlay();
break;
}
}
void _onDismiss() {
_cancelTimer();
_wasDismissedBySwipe = true;
_removeEntry();
}
void _registerSnackbar() {}
void _removeEntry() {
assert(
!_transitionCompleter.isCompleted,
'Cannot remove entry from a disposed snackbar',
);
_cancelTimer();
if (_wasDismissedBySwipe) {
Timer(const Duration(milliseconds: 200), _controller.reset);
_wasDismissedBySwipe = false;
} else {
_controller.reverse();
}
}
void _removeOverlay() {
for (var element in _overlayEntries) {
element.remove();
}
assert(!_transitionCompleter.isCompleted, 'Cannot remove overlay twice.');
_controller.dispose();
_overlayEntries.clear();
_transitionCompleter.complete(this);
}
Future<void> _show() {
_configureOverlay();
return future;
}
void _unRegisterSnackbar() {}
}
... ...