Committed by
GitHub
Merge pull request #3050 from jonataslaw/snackbar-fix
Snackbar fix
Showing
8 changed files
with
71 additions
and
31 deletions
@@ -4,7 +4,7 @@ | @@ -4,7 +4,7 @@ | ||
4 | # This file should be version controlled and should not be manually edited. | 4 | # This file should be version controlled and should not be manually edited. |
5 | 5 | ||
6 | version: | 6 | version: |
7 | - revision: "e1e47221e86272429674bec4f1bd36acc4fc7b77" | 7 | + revision: "ba393198430278b6595976de84fe170f553cc728" |
8 | channel: "stable" | 8 | channel: "stable" |
9 | 9 | ||
10 | project_type: app | 10 | project_type: app |
@@ -13,11 +13,11 @@ project_type: app | @@ -13,11 +13,11 @@ project_type: app | ||
13 | migration: | 13 | migration: |
14 | platforms: | 14 | platforms: |
15 | - platform: root | 15 | - platform: root |
16 | - create_revision: e1e47221e86272429674bec4f1bd36acc4fc7b77 | ||
17 | - base_revision: e1e47221e86272429674bec4f1bd36acc4fc7b77 | 16 | + create_revision: ba393198430278b6595976de84fe170f553cc728 |
17 | + base_revision: ba393198430278b6595976de84fe170f553cc728 | ||
18 | - platform: ios | 18 | - platform: ios |
19 | - create_revision: e1e47221e86272429674bec4f1bd36acc4fc7b77 | ||
20 | - base_revision: e1e47221e86272429674bec4f1bd36acc4fc7b77 | 19 | + create_revision: ba393198430278b6595976de84fe170f553cc728 |
20 | + base_revision: ba393198430278b6595976de84fe170f553cc728 | ||
21 | 21 | ||
22 | # User provided section | 22 | # User provided section |
23 | 23 |
@@ -76,9 +76,18 @@ class First extends StatelessWidget { | @@ -76,9 +76,18 @@ class First extends StatelessWidget { | ||
76 | leading: IconButton( | 76 | leading: IconButton( |
77 | icon: const Icon(Icons.more), | 77 | icon: const Icon(Icons.more), |
78 | onPressed: () { | 78 | onPressed: () { |
79 | - print('THEME CHANGED'); | ||
80 | - Get.changeTheme( | ||
81 | - Get.isDarkMode ? ThemeData.light() : ThemeData.dark()); | 79 | + Get.snackbar( |
80 | + 'title', | ||
81 | + "message", | ||
82 | + mainButton: | ||
83 | + TextButton(onPressed: () {}, child: const Text('button')), | ||
84 | + isDismissible: true, | ||
85 | + duration: Duration(seconds: 5), | ||
86 | + snackbarStatus: (status) => print(status), | ||
87 | + ); | ||
88 | + // print('THEME CHANGED'); | ||
89 | + // Get.changeTheme( | ||
90 | + // Get.isDarkMode ? ThemeData.light() : ThemeData.dark()); | ||
82 | }, | 91 | }, |
83 | ), | 92 | ), |
84 | ), | 93 | ), |
@@ -45,6 +45,7 @@ class ConfigData { | @@ -45,6 +45,7 @@ class ConfigData { | ||
45 | final Duration defaultDialogTransitionDuration; | 45 | final Duration defaultDialogTransitionDuration; |
46 | final Routing routing; | 46 | final Routing routing; |
47 | final Map<String, String?> parameters; | 47 | final Map<String, String?> parameters; |
48 | + final SnackBarQueue snackBarQueue = SnackBarQueue(); | ||
48 | 49 | ||
49 | ConfigData({ | 50 | ConfigData({ |
50 | required this.routingCallback, | 51 | required this.routingCallback, |
@@ -340,6 +341,7 @@ class GetRootState extends State<GetRoot> with WidgetsBindingObserver { | @@ -340,6 +341,7 @@ class GetRootState extends State<GetRoot> with WidgetsBindingObserver { | ||
340 | void onClose() { | 341 | void onClose() { |
341 | config.onDispose?.call(); | 342 | config.onDispose?.call(); |
342 | Get.clearTranslations(); | 343 | Get.clearTranslations(); |
344 | + config.snackBarQueue.disposeControllers(); | ||
343 | RouterReportManager.instance.clearRouteKeys(); | 345 | RouterReportManager.instance.clearRouteKeys(); |
344 | RouterReportManager.dispose(); | 346 | RouterReportManager.dispose(); |
345 | Get.resetInstance(clearRouteBindings: true); | 347 | Get.resetInstance(clearRouteBindings: true); |
@@ -19,6 +19,13 @@ class GetSnackBar extends StatefulWidget { | @@ -19,6 +19,13 @@ class GetSnackBar extends StatefulWidget { | ||
19 | /// The title displayed to the user | 19 | /// The title displayed to the user |
20 | final String? title; | 20 | final String? title; |
21 | 21 | ||
22 | + /// Defines how the snack bar area, including margin, will behave during hit testing. | ||
23 | + /// | ||
24 | + /// If this property is null and [margin] is not null, then [HitTestBehavior.deferToChild] is used by default. | ||
25 | + /// | ||
26 | + /// Please refer to [HitTestBehavior] for a detailed explanation of every behavior. | ||
27 | + final HitTestBehavior? hitTestBehavior; | ||
28 | + | ||
22 | /// The direction in which the SnackBar can be dismissed. | 29 | /// The direction in which the SnackBar can be dismissed. |
23 | /// | 30 | /// |
24 | /// Default is [DismissDirection.down] when | 31 | /// Default is [DismissDirection.down] when |
@@ -203,6 +210,7 @@ class GetSnackBar extends StatefulWidget { | @@ -203,6 +210,7 @@ class GetSnackBar extends StatefulWidget { | ||
203 | this.overlayColor = Colors.transparent, | 210 | this.overlayColor = Colors.transparent, |
204 | this.userInputForm, | 211 | this.userInputForm, |
205 | this.snackbarStatus, | 212 | this.snackbarStatus, |
213 | + this.hitTestBehavior, | ||
206 | }) : super(key: key); | 214 | }) : super(key: key); |
207 | 215 | ||
208 | @override | 216 | @override |
@@ -5,12 +5,14 @@ import 'dart:ui'; | @@ -5,12 +5,14 @@ import 'dart:ui'; | ||
5 | import 'package:flutter/material.dart'; | 5 | import 'package:flutter/material.dart'; |
6 | 6 | ||
7 | import '../../../get.dart'; | 7 | import '../../../get.dart'; |
8 | +import '../root/get_root.dart'; | ||
8 | 9 | ||
9 | class SnackbarController { | 10 | class SnackbarController { |
10 | - static final _snackBarQueue = _SnackBarQueue(); | ||
11 | - static bool get isSnackbarBeingShown => _snackBarQueue._isJobInProgress; | ||
12 | final key = GlobalKey<GetSnackBarState>(); | 11 | final key = GlobalKey<GetSnackBarState>(); |
13 | 12 | ||
13 | + static bool get isSnackbarBeingShown => | ||
14 | + GetRootState.controller.config.snackBarQueue.isJobInProgress; | ||
15 | + | ||
14 | late Animation<double> _filterBlurAnimation; | 16 | late Animation<double> _filterBlurAnimation; |
15 | late Animation<Color?> _filterColorAnimation; | 17 | late Animation<Color?> _filterColorAnimation; |
16 | 18 | ||
@@ -60,7 +62,7 @@ class SnackbarController { | @@ -60,7 +62,7 @@ class SnackbarController { | ||
60 | /// Only one GetSnackbar will be displayed at a time, and this method returns | 62 | /// Only one GetSnackbar will be displayed at a time, and this method returns |
61 | /// a future to when the snackbar disappears. | 63 | /// a future to when the snackbar disappears. |
62 | Future<void> show() { | 64 | Future<void> show() { |
63 | - return _snackBarQueue._addJob(this); | 65 | + return GetRootState.controller.config.snackBarQueue.addJob(this); |
64 | } | 66 | } |
65 | 67 | ||
66 | void _cancelTimer() { | 68 | void _cancelTimer() { |
@@ -243,6 +245,7 @@ class SnackbarController { | @@ -243,6 +245,7 @@ class SnackbarController { | ||
243 | snackbar.onHover?.call(snackbar, SnackHoverState.entered), | 245 | snackbar.onHover?.call(snackbar, SnackHoverState.entered), |
244 | onExit: (_) => snackbar.onHover?.call(snackbar, SnackHoverState.exited), | 246 | onExit: (_) => snackbar.onHover?.call(snackbar, SnackHoverState.exited), |
245 | child: GestureDetector( | 247 | child: GestureDetector( |
248 | + behavior: snackbar.hitTestBehavior ?? HitTestBehavior.deferToChild, | ||
246 | onTap: snackbar.onTap != null | 249 | onTap: snackbar.onTap != null |
247 | ? () => snackbar.onTap?.call(snackbar) | 250 | ? () => snackbar.onTap?.call(snackbar) |
248 | : null, | 251 | : null, |
@@ -261,6 +264,7 @@ class SnackbarController { | @@ -261,6 +264,7 @@ class SnackbarController { | ||
261 | 264 | ||
262 | Widget _getDismissibleSnack(Widget child) { | 265 | Widget _getDismissibleSnack(Widget child) { |
263 | return Dismissible( | 266 | return Dismissible( |
267 | + behavior: snackbar.hitTestBehavior ?? HitTestBehavior.opaque, | ||
264 | direction: snackbar.dismissDirection ?? _getDefaultDismissDirection(), | 268 | direction: snackbar.dismissDirection ?? _getDefaultDismissDirection(), |
265 | resizeDuration: null, | 269 | resizeDuration: null, |
266 | confirmDismiss: (_) { | 270 | confirmDismiss: (_) { |
@@ -348,15 +352,15 @@ class SnackbarController { | @@ -348,15 +352,15 @@ class SnackbarController { | ||
348 | } | 352 | } |
349 | 353 | ||
350 | static Future<void> cancelAllSnackbars() async { | 354 | static Future<void> cancelAllSnackbars() async { |
351 | - await _snackBarQueue._cancelAllJobs(); | 355 | + await GetRootState.controller.config.snackBarQueue.cancelAllJobs(); |
352 | } | 356 | } |
353 | 357 | ||
354 | static Future<void> closeCurrentSnackbar() async { | 358 | static Future<void> closeCurrentSnackbar() async { |
355 | - await _snackBarQueue._closeCurrentJob(); | 359 | + await GetRootState.controller.config.snackBarQueue.closeCurrentJob(); |
356 | } | 360 | } |
357 | } | 361 | } |
358 | 362 | ||
359 | -class _SnackBarQueue { | 363 | +class SnackBarQueue { |
360 | final _queue = GetQueue(); | 364 | final _queue = GetQueue(); |
361 | final _snackbarList = <SnackbarController>[]; | 365 | final _snackbarList = <SnackbarController>[]; |
362 | 366 | ||
@@ -365,22 +369,37 @@ class _SnackBarQueue { | @@ -365,22 +369,37 @@ class _SnackBarQueue { | ||
365 | return _snackbarList.first; | 369 | return _snackbarList.first; |
366 | } | 370 | } |
367 | 371 | ||
368 | - bool get _isJobInProgress => _snackbarList.isNotEmpty; | 372 | + bool get isJobInProgress => _snackbarList.isNotEmpty; |
369 | 373 | ||
370 | - Future<void> _addJob(SnackbarController job) async { | 374 | + Future<void> addJob(SnackbarController job) async { |
371 | _snackbarList.add(job); | 375 | _snackbarList.add(job); |
372 | final data = await _queue.add(job._show); | 376 | final data = await _queue.add(job._show); |
373 | _snackbarList.remove(job); | 377 | _snackbarList.remove(job); |
374 | return data; | 378 | return data; |
375 | } | 379 | } |
376 | 380 | ||
377 | - Future<void> _cancelAllJobs() async { | 381 | + Future<void> cancelAllJobs() async { |
378 | await _currentSnackbar?.close(); | 382 | await _currentSnackbar?.close(); |
379 | _queue.cancelAllJobs(); | 383 | _queue.cancelAllJobs(); |
380 | _snackbarList.clear(); | 384 | _snackbarList.clear(); |
381 | } | 385 | } |
382 | 386 | ||
383 | - Future<void> _closeCurrentJob() async { | 387 | + void disposeControllers() { |
388 | + if (_currentSnackbar != null) { | ||
389 | + _currentSnackbar?._removeOverlay(); | ||
390 | + _currentSnackbar?._controller.dispose(); | ||
391 | + _snackbarList.remove(_currentSnackbar); | ||
392 | + } | ||
393 | + | ||
394 | + _queue.cancelAllJobs(); | ||
395 | + | ||
396 | + for (var element in _snackbarList) { | ||
397 | + element._controller.dispose(); | ||
398 | + } | ||
399 | + _snackbarList.clear(); | ||
400 | + } | ||
401 | + | ||
402 | + Future<void> closeCurrentJob() async { | ||
384 | if (_currentSnackbar == null) return; | 403 | if (_currentSnackbar == null) return; |
385 | await _currentSnackbar!.close(); | 404 | await _currentSnackbar!.close(); |
386 | } | 405 | } |
@@ -6,9 +6,10 @@ mixin Equality { | @@ -6,9 +6,10 @@ mixin Equality { | ||
6 | List get props; | 6 | List get props; |
7 | 7 | ||
8 | @override | 8 | @override |
9 | - bool operator ==(dynamic other) { | 9 | + bool operator ==(Object other) { |
10 | return identical(this, other) || | 10 | return identical(this, other) || |
11 | runtimeType == other.runtimeType && | 11 | runtimeType == other.runtimeType && |
12 | + other is Equality && | ||
12 | const DeepCollectionEquality().equals(props, other.props); | 13 | const DeepCollectionEquality().equals(props, other.props); |
13 | } | 14 | } |
14 | 15 |
@@ -107,10 +107,17 @@ void main() { | @@ -107,10 +107,17 @@ void main() { | ||
107 | }); | 107 | }); |
108 | 108 | ||
109 | testWidgets("test snackbar dismissible", (tester) async { | 109 | testWidgets("test snackbar dismissible", (tester) async { |
110 | - const dismissDirection = DismissDirection.vertical; | 110 | + const dismissDirection = DismissDirection.down; |
111 | const snackBarTapTarget = Key('snackbar-tap-target'); | 111 | const snackBarTapTarget = Key('snackbar-tap-target'); |
112 | 112 | ||
113 | - late final GetSnackBar getBar; | 113 | + const GetSnackBar getBar = GetSnackBar( |
114 | + key: ValueKey('dismissible'), | ||
115 | + message: 'bar1', | ||
116 | + duration: Duration(seconds: 2), | ||
117 | + isDismissible: true, | ||
118 | + snackPosition: SnackPosition.bottom, | ||
119 | + dismissDirection: dismissDirection, | ||
120 | + ); | ||
114 | 121 | ||
115 | await tester.pumpWidget(GetMaterialApp( | 122 | await tester.pumpWidget(GetMaterialApp( |
116 | home: Scaffold( | 123 | home: Scaffold( |
@@ -121,12 +128,6 @@ void main() { | @@ -121,12 +128,6 @@ void main() { | ||
121 | GestureDetector( | 128 | GestureDetector( |
122 | key: snackBarTapTarget, | 129 | key: snackBarTapTarget, |
123 | onTap: () { | 130 | onTap: () { |
124 | - getBar = const GetSnackBar( | ||
125 | - message: 'bar1', | ||
126 | - duration: Duration(seconds: 2), | ||
127 | - isDismissible: true, | ||
128 | - dismissDirection: dismissDirection, | ||
129 | - ); | ||
130 | Get.showSnackbar(getBar); | 131 | Get.showSnackbar(getBar); |
131 | }, | 132 | }, |
132 | behavior: HitTestBehavior.opaque, | 133 | behavior: HitTestBehavior.opaque, |
@@ -154,9 +155,9 @@ void main() { | @@ -154,9 +155,9 @@ void main() { | ||
154 | await tester.pump(const Duration(milliseconds: 500)); | 155 | await tester.pump(const Duration(milliseconds: 500)); |
155 | expect(find.byWidget(getBar), findsOneWidget); | 156 | expect(find.byWidget(getBar), findsOneWidget); |
156 | await tester.ensureVisible(find.byWidget(getBar)); | 157 | await tester.ensureVisible(find.byWidget(getBar)); |
157 | - await tester.drag(find.byWidget(getBar), const Offset(0.0, 50.0)); | 158 | + await tester.drag(find.byType(Dismissible), const Offset(0.0, 50.0)); |
159 | + await tester.pumpAndSettle(); | ||
158 | await tester.pump(const Duration(milliseconds: 500)); | 160 | await tester.pump(const Duration(milliseconds: 500)); |
159 | - | ||
160 | expect(Get.isSnackbarOpen, false); | 161 | expect(Get.isSnackbarOpen, false); |
161 | }); | 162 | }); |
162 | 163 |
-
Please register or login to post a comment