diff --git a/CHANGELOG.md b/CHANGELOG.md index 87601e966..460fd2fbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## [3.0.1] +- Breaking changes on Rx api and GetController and RxController were merged, and now you only have the 'GetxController' +- Refactor routing system. Now you can add custom transitions and more +- Improved the use of dynamic routes, you can now define two different pages according to your arguments. +- Added GetView widget +- Added internacionalization +- Added validations +- Added Get queqe +- Added GetStorage (with separated package) +- Minor bug fixes. + + ## [2.14.0] - Added getPages API. - Deprecated namedPages @@ -12,7 +24,7 @@ ## [2.13.0] - Update docs -- Fix Bindings list on GetRouteBase +- Fix Bindings list on GetPageRoute ## [2.12.5] - Update readme @@ -64,7 +76,7 @@ Get.lazyPut () will not receive this resource. Initially he had it, but we saw i ## [2.10.0] - Added SmartManagement, your application's memory is managed intelligently like never before! - Added Obx, a widget that knows when to rebuild a child, without needing any type. -- Added MIxinBuilder - If you need to use GetBuilder in conjunction with GetX, use GetController with this widget, and the changes will occur either using update (this) or changing some reactive variable. Use only if necessary, for better RAM consumption, prefer widgets in that order: +- Added MIxinBuilder - If you need to use GetBuilder in conjunction with GetX, use GetxController with this widget, and the changes will occur either using update (this) or changing some reactive variable. Use only if necessary, for better RAM consumption, prefer widgets in that order: Obx => GetX => GetBuilder => MixinBuilder. Obx is the lightest of all, and MixinBuilder is a mix of the other 3, whenever possible, use the specific widget. - Refactor: refactor StateManager of Get. diff --git a/README.md b/README.md index 33008598b..c30ce7072 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Buy Me A Coffee -[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors-) @@ -33,7 +33,7 @@ Create your business logic class and place all variables, methods and controller You can make any variable observable using a simple ".obs". ```dart -class Controller extends RxController{ +class Controller extends GetxController{ var count = 0.obs; increment() => count.value++; } @@ -112,6 +112,7 @@ This is a simple project but it already makes clear how powerful Get is. As your - [Change Theme](#change-theme) - [Other Advanced APIs and Manual configurations](#other-advanced-apis-and-manual-configurations) - [Optional Global Settings](#optional-global-settings) +- [Breaking changes from 2.0](#breaking-changes-from-2.0) *Want to contribute to the project? We will be proud to highlight you as one of our collaborators. Here are some points where you can contribute and make Get (and Flutter) even better.* @@ -237,20 +238,14 @@ void main() { GetMaterialApp( initialRoute: '/', getPages: [ - GetPage( - name: '/', - page: () => MyHomePage(), - ), - GetPage( - name: '/second', - page: () => Second(), - ), - GetPage( - name: '/third', - page: () => Third(), - transition: Transition.cupertino - ), - ], + GetPage(name: '/', page: () => MyHomePage()), + GetPage(name: '/second', page: () => Second()), + GetPage( + name: '/third', + page: () => Third(), + transition: Transition.zoom + ), + ], ) ); } @@ -297,12 +292,13 @@ void main() { page: () => MyHomePage(), ), GetPage( - /// Important! :user is not a new route, it is just a parameter - /// specification. Do not use '/second/:user' and '/second' - /// if you need new route to user, use '/second/user/:user' - /// if '/second' is a route. - name: '/second/:user', - page: () => Second(), + name: '/profile/', + page: () => MyProfile(), + ), + //You can define a different page for routes with arguments, and another without arguments, but for that you must use the slash '/' on the route that will not receive arguments as above. + GetPage( + name: '/profile/:user', + page: () => UserProfile(), ), GetPage( name: '/third', @@ -333,7 +329,7 @@ And now, all you need to do is use Get.toNamed() to navigate your named routes, If you want listen Get events to trigger actions, you can to use routingCallback to it ```dart GetMaterialApp( - routingCallback: (route) { + routingCallback: (routing) { if(routing.current == '/second'){ openAds(); } @@ -449,7 +445,8 @@ class Third extends StatelessWidget { ### SnackBars -To have a simple SnackBar with Flutter, you must get the context of Scaffold, or you must use a GlobalKey attached to your Scaffold, +To have a simple SnackBar with Flutter, you must get the context of Scaffold, or you must use a GlobalKey attached to your Scaffold + ```dart final snackBar = SnackBar( content: Text('Hi!'), @@ -584,7 +581,7 @@ Navigator( initialRoute: '/', onGenerateRoute: (settings) { if (settings.name == '/') { - return GetRouteBase( + return GetPageRoute( page: Scaffold( appBar: AppBar( title: Text("Main"), @@ -601,7 +598,7 @@ Navigator( ), ); } else if (settings.name == '/second') { - return GetRouteBase( + return GetPageRoute( page: Center( child: Scaffold( appBar: AppBar( @@ -658,8 +655,8 @@ Get has a state manager that is extremely light and easy, which does not use Cha ### Usage ```dart -// Create controller class and extends GetController -class Controller extends GetController { +// Create controller class and extends GetxController +class Controller extends GetxController { int counter = 0; void increment() { counter++; @@ -700,7 +697,7 @@ class OtherClass extends StatelessWidget { If you need to use your controller in many other places, and outside of GetBuilder, just create a get in your controller and have it easily. (or use `Get.find()`) ```dart -class Controller extends GetController { +class Controller extends GetxController { /// You do not need that. I recommend using it just for ease of syntax. /// with static method: Controller.to.counter(); @@ -768,10 +765,10 @@ So to simplify this: You don't need to call methods in initState and send them by parameter to your controller, nor use your controller constructor for that, you have the onInit() method that is called at the right time for you to start your services. You do not need to call the device, you have the onClose() method that will be called at the exact moment when your controller is no longer needed and will be removed from memory. That way, leave views for widgets only, refrain from any kind of business logic from it. -Do not call a dispose method inside GetController, it will not do anything, remember that the controller is not a Widget, you should not "dispose" it, and it will be automatically and intelligently removed from memory by Get. If you used any stream on it and want to close it, just insert it into the close method. Example: +Do not call a dispose method inside GetxController, it will not do anything, remember that the controller is not a Widget, you should not "dispose" it, and it will be automatically and intelligently removed from memory by Get. If you used any stream on it and want to close it, just insert it into the close method. Example: ```dart -class Controller extends GetController { +class Controller extends GetxController { StreamController user = StreamController(); StreamController name = StreamController(); @@ -804,7 +801,7 @@ GetBuilder( You may also need an instance of your controller outside of your GetBuilder, and you can use these approaches to achieve this: ```dart -class Controller extends GetController { +class Controller extends GetxController { static Controller get to => Get.find(); [...] } @@ -818,7 +815,7 @@ GetBuilder( ``` or ```dart -class Controller extends GetController { +class Controller extends GetxController { // static Controller get to => Get.find(); // with no static get [...] } @@ -870,6 +867,34 @@ GetX does this automatically and only reconstructs the widget that uses the exac ## Reactive State Manager +Reactive programming can alienate many people because it is said to be complicated. Getx turns reactive programming into something so simple, that it can be used and learned by those who started at that very moment in Flutter. No, you will not need to create StreamControllers. You also won't need to create a StreamBuilder for each variable. You will not need to create a class for each state. You will not need to create a get for an initial value. Reactive programming with Get is as easy as using setState (or even easier!). + +Let's imagine that you have a name variable and want that every time you change it, all widgets that use it are automatically changed. + +This is your count variable: +```dart +var name = 'Jonatas Borges'; +``` +To make it observable, you just need to add ".obs" to the end of it: +```dart +var name = 'Jonatas Borges'.obs; +``` + +This borders on absurdity when it comes to practicality. What did we do under the hood? We created a stream of Strings, assigned the initial value "Jonatas Borges", we warn all widgets that use "Jonatas Borges" that they now belong to this variable, and when it is changed, they will be changed too. This is the magic of Get, that only dart allows us to do this. + +Okay, but as we know, a widget can only be changed if it is inside a function, because static classes do not have the power to "auto-change". You will need to create a StreamBuilder, subscribe to listen to this variable, and create a "cascade" of StreamBuilder if you want to change several variables in the same scope, right? +No, you don't need a StreamBuilder, but you are right about static classes. + +Well, in the view we usually have a lot of boilerplate when we want to change a specific widget. With Get you can also forget about this Boilerplate. StreamBuilder? initialValue? builder? +No, you just need to play this variable inside an Obx widget. +```dart +Obx (() => Text (controller.name)); +``` + +What do you need to memorize? "Obx(() =>" You are just passing that widget through an arrow-function into an Obx. Obx is smart, and will only be changed if the value of name is changed. If name is "John" and you change it to "John", it will not have any changes on the screen, and Obx will simply ignore this change, and will not rebuild the widget, to save resources. Isn't that amazing? + +What if I have 5 observable variables within an Obx? It will update when any of them are changed. And if I have 30 variables in a class, when I update one, will it update all variables that are in that class? No, just the specific widget that uses that variable. And if I machine-gun my observable variable 1 billion times with the same value, will I have freeze on the screen for unnecessary reconstructions? No, GetX only updates the screen when the variable changes on the screen, if the screen remains the same, it will not reconstruct anything. + ### Advantages GetBuilder is aimed precisely at multiple state control. Imagine that you added 30 products to a cart, you click delete one, at the same time that the list is updated, the price is updated and the badge in the shopping cart is updated to a smaller number. This type of approach makes GetBuilder killer, because it groups states and changes them all at once without any "computational logic" for that. GetBuilder was created with this type of situation in mind, since for ephemeral change of state, you can use setState and you would not need a state manager for this. However, there are situations where you want only the widget where a certain variable has been changed to be rebuilt, and this is what GetX does with a mastery never seen before. @@ -887,6 +912,29 @@ The state only changes if the values ​​change. That's the main difference be ### Usage +You have 3 ways to turn a variable into an observable. +The first is using Rx{Type}. + +```dart +var count = RxString(); +``` +The second is to use Rx and type it with `Rx` +```dart +var count = Rx(); +``` + +The third, more practical, easy, and incredible approach, is just to add an .obs to your variable. + +```dart +var count = 0.obs; + +// or Rxint count = 0.obs; +// or Rx count = 0.obs; +``` + + As we know, Dart is now heading towards null safety. With that it is a good idea, from now on, you start to use your variables always with an initial value. Transforming a variable into an observable with an initial value with Get is the simplest and most practical approach that currently exists in Flutter. You will literally add a ".obs" to the end of your variable, and that’s it, you’ve made it observable, and its value will be the initial value, this is fantastic! + +You can add variables, and if you want to type your widget to get your controller inside, you just need to use GetX widget instead of Obx ```dart final count1 = 0.obs; final count2 = 0.obs; @@ -895,9 +943,9 @@ int get sum => count1.value + count2.value; ```dart GetX( - builder: (_) { + builder: (value) { print("count 1 rebuild"); - return Text('${_.count1.value}'); + return Text('${value.count1.value}'); }, ), GetX( @@ -959,7 +1007,7 @@ Without decorations, without a code generator, without complications, GetX will Do you know Flutter's counter app? Your Controller class might look like this: ```dart -class CountCtl extends RxController { +class CountCtl extends GetxController { final count = 0.obs; } ``` @@ -1074,9 +1122,7 @@ interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1)); ## Mixing the two state managers Some people opened a feature request, as they wanted to use only one type of reactive variable, and the other mechanics, and needed to insert an Obx into a GetBuilder for this. Thinking about it MixinBuilder was created. It allows both reactive changes by changing ".obs" variables, and mechanical updates via update(). However, of the 4 widgets he is the one that consumes the most resources, since in addition to having a Subscription to receive change events from his children, he subscribes to the update method of his controller. -- Note: To use GetBuilder and MixinBuilder you must use GetController. To use GetX and Obx you must use RxController. -Probably using a GetController using GetX and Obx will work, but it will not be possible to use an RxController on a GetBuilder. -Extending these controllers is important, as they have life cycles, and can "start" and "end" events in their onInit() and onClose() methods. +Extending GetxController is important, as they have life cycles, and can "start" and "end" events in their onInit() and onClose() methods. You can use any class for this, but I strongly recommend you use the GetxController class to place your variables, whether they are observable or not. ## GetBuilder vs GetX vs Obx vs MixinBuilder In a decade working with programming I was able to learn some valuable lessons. @@ -1097,8 +1143,6 @@ There are other situations where reactive programming is really interesting, and GetX is still more economical than any other reactive state manager, but it consumes a little more RAM than GetBuilder. Thinking about it and aiming to maximize the consumption of resources that Obx was created. Unlike GetX and GetBuilder, you will not be able to initialize a controller inside an Obx, it is just a Widget with a StreamSubscription that receives change events from your children, that's all. It is more economical than GetX, but loses to GetBuilder, which was to be expected, since it is reactive, and GetBuilder has the most simplistic approach that exists, of storing a widget's hashcode and its StateSetter. With Obx you don't need to write your controller type, and you can hear the change from multiple different controllers, but it needs to be initialized before, either using the example approach at the beginning of this readme, or using the Bindings class. - - # Dependency Management @@ -1325,6 +1369,57 @@ Get.config( ) ``` +# Breaking changes from 2.0 +1- Rx types: +Before: StringX now: RxString + +Before: IntX now: RxInt + +Before: MapX now: RxMax + +Before: ListX now: RxList + +Before: NumX now: RxNum + +Before: RxDouble now: RxDouble + +RxController and GetBuilder now have merged, you no longer need to memorize which controller you want to use, just use GetxController, it will work for simple state management and for reactive as well. + + +2- NamedRoutes +Before: +```dart +GetMaterialApp( + namedRoutes: { + '/': GetRoute(page: Home()), + } +) +``` +Now: +```dart +GetMaterialApp( + getPages: [ + GetPage(name: '/', page:()=> Home()), + ] +) +``` + +Why this change? +Often, it may be necessary to decide which page will be displayed from a parameter, or a login token, the previous approach was inflexible, as it did not allow this. +Inserting the page into a function has significantly reduced the RAM consumption, since the routes will not be allocated in memory since the app was started, and it also allowed to do this type of approach: + +```dart + +GetStorage box = GetStorage(); + +GetMaterialApp( + getPages: [ + GetPage(name: '/', page:(){ + return box.hasData('token') ? Home() : Login(); + }) + ] +) +``` This library will always be updated and implementing new features. Feel free to offer PRs and contribute to them. diff --git a/lib/extensions.dart b/lib/extensions.dart index c25a95edb..3ff4ea105 100644 --- a/lib/extensions.dart +++ b/lib/extensions.dart @@ -68,3 +68,4 @@ extension MDQ on BuildContext { /// True if the current device is Tablet bool get isTablet => isSmallTablet || isLargeTablet; } + diff --git a/lib/get.dart b/lib/get.dart index 80345cf05..acedac424 100644 --- a/lib/get.dart +++ b/lib/get.dart @@ -6,6 +6,9 @@ export 'src/snackbar/snack.dart'; export 'src/bottomsheet/bottomsheet.dart'; export 'src/snackbar/snack_route.dart'; export 'src/state/get_state.dart'; +export 'src/state/get_view.dart'; +export 'src/regex/get_utils.dart'; +export 'src/queue/get_queue.dart'; export 'src/state/mixin_state.dart'; export 'src/rx/rx_interface.dart'; export 'src/rx/rx_impl.dart'; @@ -21,3 +24,4 @@ export 'src/routes/observers/route_observer.dart'; export 'src/routes/transitions_type.dart'; export 'src/platform/platform.dart'; export 'src/extension_instance.dart'; +export 'src/routes/custom_transition.dart'; diff --git a/lib/src/extension_instance.dart b/lib/src/extension_instance.dart index d003a4850..f87e51b96 100644 --- a/lib/src/extension_instance.dart +++ b/lib/src/extension_instance.dart @@ -7,8 +7,9 @@ extension Storage on GetImpl { return GetInstance().lazyPut(builder, tag: tag); } - Future putAsync(FcBuilderFuncAsync builder, {String tag}) async => - GetInstance().putAsync(builder, tag: tag); + Future putAsync(FcBuilderFuncAsync builder, + {String tag, bool permanent = false}) async => + GetInstance().putAsync(builder, tag: tag, permanent: permanent); S find({String tag, FcBuilderFunc instance}) => GetInstance().find(tag: tag, instance: instance); diff --git a/lib/src/get_instance.dart b/lib/src/get_instance.dart index d2ce4ecbd..779f10435 100644 --- a/lib/src/get_instance.dart +++ b/lib/src/get_instance.dart @@ -2,7 +2,6 @@ import 'root/smart_management.dart'; import 'rx/rx_interface.dart'; import 'typedefs/typedefs.dart'; - class GetConfig { //////////// INSTANCE MANAGER static Map _singl = {}; @@ -26,8 +25,9 @@ class GetInstance { GetConfig._factory.putIfAbsent(key, () => builder); } - Future putAsync(FcBuilderFuncAsync builder, {String tag}) async { - return put(await builder(), tag: tag); + Future putAsync(FcBuilderFuncAsync builder, + {String tag, bool permanent = false}) async { + return put(await builder(), tag: tag, permanent: permanent); } /// Inject class on Get Instance Manager @@ -105,14 +105,13 @@ class GetInstance { } void registerRouteInstance({String tag}) { - // print("Register route [$S] as ${currentRoute}"); GetConfig.routesKey .putIfAbsent(_getKey(S, tag), () => GetConfig.currentRoute); } S findByType(Type type, {String tag}) { String key = _getKey(type, tag); - return GetConfig._singl[key].getSependency(); + return GetConfig._singl[key].getSependency() as S; } void initController({String tag}) { @@ -136,7 +135,7 @@ class GetInstance { callInit = true; } - FcBuilder builder = GetConfig._singl[key]; + FcBuilder builder = GetConfig._singl[key] as FcBuilder; if (builder == null) { if (tag == null) { throw "class ${S.toString()} is not register"; @@ -148,7 +147,7 @@ class GetInstance { initController(tag: tag); } - return GetConfig._singl[key].getSependency(); + return GetConfig._singl[key].getSependency() as S; } else { if (!GetConfig._factory.containsKey(key)) throw " $S not found. You need call put<$S>($S()) before"; @@ -177,7 +176,7 @@ class GetInstance { /// Remove dependency of [S] on dependency abstraction. For concrete class use delete void remove({String tag}) { String key = _getKey(S, tag); - FcBuilder builder = GetConfig._singl[key]; + FcBuilder builder = GetConfig._singl[key] as FcBuilder; final i = builder.dependency; if (i is DisposableInterface) { @@ -217,7 +216,7 @@ class GetInstance { return false; } - FcBuilder builder = GetConfig._singl[newKey]; + FcBuilder builder = GetConfig._singl[newKey] as FcBuilder; if (builder.permanent) { (key == null) ? print( diff --git a/lib/src/get_interface.dart b/lib/src/get_interface.dart index dcbb7547f..4bf580ceb 100644 --- a/lib/src/get_interface.dart +++ b/lib/src/get_interface.dart @@ -184,7 +184,7 @@ abstract class GetService { Color overlayColor = Colors.transparent, Form userInputForm}); - void snackbar(title, message, + void snackbar(String title, String message, {Color colorText, Duration duration, @@ -224,11 +224,11 @@ abstract class GetService { /// INSTANCE MANAGER - GetMaterialController getController; + GetMaterialController getxController; - changeTheme(ThemeData theme); + void changeTheme(ThemeData theme); - changeThemeMode(ThemeMode themeMode); + void changeThemeMode(ThemeMode themeMode); GlobalKey addKey(GlobalKey newKey); diff --git a/lib/src/get_main.dart b/lib/src/get_main.dart index 8687518c9..da7c74ff9 100644 --- a/lib/src/get_main.dart +++ b/lib/src/get_main.dart @@ -5,6 +5,7 @@ import 'package:get/src/get_instance.dart'; import 'package:get/src/get_interface.dart'; import 'bottomsheet/bottomsheet.dart'; import 'platform/platform.dart'; +import 'root/parse_route.dart'; import 'root/root_controller.dart'; import 'routes/bindings_interface.dart'; import 'routes/default_route.dart'; @@ -22,7 +23,7 @@ class GetImpl implements GetService { bool defaultPopGesture = GetPlatform.isIOS; bool defaultOpaqueRoute = true; Transition defaultTransition = - (GetPlatform.isIOS ? Transition.cupertino : Transition.fade); + (GetPlatform.isIOS ? Transition.leftToRight : Transition.fadeIn); Duration defaultDurationTransition = Duration(milliseconds: 400); bool defaultGlobalState = true; RouteSettings settings; @@ -45,32 +46,42 @@ class GetImpl implements GetService { bool fullscreenDialog = false, Object arguments, Bindings binding, + preventDuplicates = true, bool popGesture}) { - return global(id).currentState.push(GetRouteBase( + if (preventDuplicates && + '/' + page.toString().toLowerCase() == currentRoute) { + return null; + } + + return global(id).currentState.push(GetPageRoute( opaque: opaque ?? true, - page: page, + page: () => page, settings: RouteSettings( name: '/' + page.toString().toLowerCase(), arguments: arguments), popGesture: popGesture ?? defaultPopGesture, transition: transition ?? defaultTransition, fullscreenDialog: fullscreenDialog, binding: binding, - transitionDuration: duration ?? defaultDurationTransition)); + duration: duration ?? defaultDurationTransition)); } /// It replaces Navigator.pushNamed, but needs no context, and it doesn't have the Navigator.pushNamed /// routes rebuild bug present in Flutter. If for some strange reason you want the default behavior /// of rebuilding every app after a route, use opaque = true as the parameter. - Future toNamed(String page, {Object arguments, int id}) { - // if (key.currentState.mounted) // add this if appear problems on future with route navigate - // when widget don't mounted + Future toNamed(String page, + {Object arguments, int id, preventDuplicates = true}) { + if (preventDuplicates && page == currentRoute) { + return null; + } return global(id).currentState.pushNamed(page, arguments: arguments); } /// It replaces Navigator.pushReplacementNamed, but needs no context. - Future offNamed(String page, {Object arguments, int id}) { - // if (key.currentState.mounted) // add this if appear problems on future with route navigate - // when widget don't mounted + Future offNamed(String page, + {Object arguments, int id, preventDuplicates = true}) { + if (preventDuplicates && page == currentRoute) { + return null; + } return global(id) .currentState .pushReplacementNamed(page, arguments: arguments); @@ -128,13 +139,21 @@ class GetImpl implements GetService { (!isSnackbarOpen && !isDialogOpen && !isBottomSheetOpen); /// It replaces Navigator.pop, but needs no context. - void back({dynamic result, bool closeOverlays = false, int id}) { + void back( + {dynamic result, + bool closeOverlays = false, + bool canPop = true, + int id}) { if (closeOverlays && isOverlaysOpen) { navigator.popUntil((route) { return (isOverlaysClosed); }); } - if (global(id).currentState.canPop()) { + if (canPop) { + if (global(id).currentState.canPop()) { + global(id).currentState.pop(result); + } + } else { global(id).currentState.pop(result); } } @@ -162,17 +181,22 @@ class GetImpl implements GetService { Object arguments, Bindings binding, bool fullscreenDialog = false, + preventDuplicates = true, Duration duration}) { - return global(id).currentState.pushReplacement(GetRouteBase( + if (preventDuplicates && + '/' + page.toString().toLowerCase() == currentRoute) { + return null; + } + return global(id).currentState.pushReplacement(GetPageRoute( opaque: opaque ?? true, - page: page, + page: () => page, binding: binding, settings: RouteSettings( name: '/' + page.toString().toLowerCase(), arguments: arguments), fullscreenDialog: fullscreenDialog, popGesture: popGesture ?? defaultPopGesture, transition: transition ?? defaultTransition, - transitionDuration: duration ?? defaultDurationTransition)); + duration: duration ?? defaultDurationTransition)); } /// It replaces Navigator.pushAndRemoveUntil, but needs no context @@ -184,21 +208,22 @@ class GetImpl implements GetService { Object arguments, Bindings binding, bool fullscreenDialog = false, - Transition transition, - Duration duration}) { + Duration duration, + Transition transition}) { var route = (Route rota) => false; return global(id).currentState.pushAndRemoveUntil( - GetRouteBase( + GetPageRoute( opaque: opaque ?? true, popGesture: popGesture ?? defaultPopGesture, - page: page, + page: () => page, binding: binding, settings: RouteSettings( - name: '/' + page.toString().toLowerCase(), arguments: arguments), + name: '/' + page.runtimeType.toString().toLowerCase(), + arguments: arguments), fullscreenDialog: fullscreenDialog, transition: transition ?? defaultTransition, - transitionDuration: duration ?? defaultDurationTransition, + duration: duration ?? defaultDurationTransition, ), predicate ?? route); } @@ -461,7 +486,7 @@ class GetImpl implements GetService { } } - void snackbar(title, message, + void snackbar(String title, String message, {Color colorText, Duration duration, @@ -558,6 +583,24 @@ class GetImpl implements GetService { } } + ParseRouteTree routeTree; + + addPages(List getPages) { + if (getPages != null) { + if (routeTree == null) routeTree = ParseRouteTree(); + getPages.forEach((element) { + routeTree.addRoute(element); + }); + } + } + + addPage(GetPage getPage) { + if (getPage != null) { + if (routeTree == null) routeTree = ParseRouteTree(); + routeTree.addRoute(getPage); + } + } + /// change default config of Get config( {bool enableLog, @@ -588,14 +631,27 @@ class GetImpl implements GetService { } } - GetMaterialController getController = GetMaterialController(); + GetMaterialController getxController = GetMaterialController(); + + Locale locale; + + void updateLocale(Locale l) { + locale = l; + getxController.update(); + } + + Map> translations; + + void addTranslations(Map> tr) { + translations.addAll(tr); + } - changeTheme(ThemeData theme) { - getController.setTheme(theme); + void changeTheme(ThemeData theme) { + getxController.setTheme(theme); } - changeThemeMode(ThemeMode themeMode) { - getController.setThemeMode(themeMode); + void changeThemeMode(ThemeMode themeMode) { + getxController.setThemeMode(themeMode); } GlobalKey addKey(GlobalKey newKey) { diff --git a/lib/src/queue/get_queue.dart b/lib/src/queue/get_queue.dart new file mode 100644 index 000000000..427d86515 --- /dev/null +++ b/lib/src/queue/get_queue.dart @@ -0,0 +1,33 @@ +import 'dart:async'; + +class GetQueue { + final List<_Item> _queue = []; + bool _active = false; + + void _check() async { + if (!_active && _queue.isNotEmpty) { + _active = true; + var item = _queue.removeAt(0); + try { + item.completer.complete(await item.job()); + } catch (e) { + item.completer.completeError(e); + } + _active = false; + _check(); + } + } + + Future add(Function job) { + var completer = Completer(); + _queue.add(_Item(completer, job)); + _check(); + return completer.future; + } +} + +class _Item { + final completer; + final job; + _Item(this.completer, this.job); +} diff --git a/lib/src/regex/get_utils.dart b/lib/src/regex/get_utils.dart new file mode 100644 index 000000000..e7f6c5081 --- /dev/null +++ b/lib/src/regex/get_utils.dart @@ -0,0 +1,350 @@ +import 'package:get/src/regex/regex.dart'; + +class GetUtils { + /// Checks if data is null. + static bool isNull(dynamic s) => s == null; + + /// Checks if data is null or blank (empty or only contains whitespace). + static bool isNullOrBlank(dynamic s) { + if (isNull(s)) return true; + switch (s.runtimeType) { + case String: + case List: + case Map: + case Set: + case Iterable: + return s.isEmpty; + break; + default: + return s.toString() == 'null' || s.toString().trim().isEmpty; + } + } + + /// Checks if string is int or double. + static bool isNum(String s) { + if (isNull(s)) return false; + return num.tryParse(s) is num ?? false; + } + + /// Checks if string consist only numeric. + /// Numeric only doesnt accepting "." which double data type have + static bool isNumericOnly(String s) => + RegexValidation.hasMatch(s, regex.numericOnly); + + /// Checks if string consist only Alphabet. (No Whitespace) + static bool isAlphabetOnly(String s) => + RegexValidation.hasMatch(s, regex.alphabetOnly); + + /// Checks if string is boolean. + static bool isBool(String s) { + if (isNull(s)) return false; + return (s == 'true' || s == 'false'); + } + + /// Checks if string is an vector file. + static bool isVector(String s) => RegexValidation.hasMatch(s, regex.vector); + + /// Checks if string is an image file. + static bool isImage(String s) => RegexValidation.hasMatch(s, regex.image); + + /// Checks if string is an audio file. + static bool isAudio(String s) => RegexValidation.hasMatch(s, regex.audio); + + /// Checks if string is an video file. + static bool isVideo(String s) => RegexValidation.hasMatch(s, regex.video); + + /// Checks if string is an txt file. + static bool isTxt(String s) => RegexValidation.hasMatch(s, regex.txt); + + /// Checks if string is an Doc file. + static bool isDocument(String s) => RegexValidation.hasMatch(s, regex.doc); + + /// Checks if string is an Excel file. + static bool isExcel(String s) => RegexValidation.hasMatch(s, regex.excel); + + /// Checks if string is an PPT file. + static bool isPPT(String s) => RegexValidation.hasMatch(s, regex.ppt); + + /// Checks if string is an APK file. + static bool isAPK(String s) => RegexValidation.hasMatch(s, regex.apk); + + /// Checks if string is an video file. + static bool isPDF(String s) => RegexValidation.hasMatch(s, regex.pdf); + + /// Checks if string is an HTML file. + static bool isHTML(String s) => RegexValidation.hasMatch(s, regex.html); + + /// Checks if string is URL. + static bool isURL(String s) => RegexValidation.hasMatch(s, regex.url); + + /// Checks if string is email. + static bool isEmail(String s) => RegexValidation.hasMatch(s, regex.txt); + + /// Checks if string is phone number. + static bool isPhoneNumber(String s) => + RegexValidation.hasMatch(s, regex.phone); + + /// Checks if string is DateTime (UTC or Iso8601). + static bool isDateTime(String s) => + RegexValidation.hasMatch(s, regex.basicDateTime); + + /// Checks if string is MD5 hash. + static bool isMD5(String s) => RegexValidation.hasMatch(s, regex.md5); + + /// Checks if string is SHA1 hash. + static bool isSHA1(String s) => RegexValidation.hasMatch(s, regex.sha1); + + /// Checks if string is SHA256 hash. + static bool isSHA256(String s) => RegexValidation.hasMatch(s, regex.sha256); + + /// Checks if string is ISBN 10 or 13. + static bool isISBN(String s) => RegexValidation.hasMatch(s, regex.isbn); + + /// Checks if string is SSN (Social Security Number). + static bool isSSN(String s) => RegexValidation.hasMatch(s, regex.ssn); + + /// Checks if string is binary. + static bool isBinary(String s) => RegexValidation.hasMatch(s, regex.binary); + + /// Checks if string is IPv4. + static bool isIPv4(String s) => RegexValidation.hasMatch(s, regex.ipv4); + + /// Checks if string is IPv6. + static bool isIPv6(String s) => RegexValidation.hasMatch(s, regex.ipv6); + + /// Checks if string is hexadecimal. + /// Example: HexColor => #12F + static bool isHexadecimal(String s) => + RegexValidation.hasMatch(s, regex.hexadecimal); + + /// Checks if string is Palindrom. + static bool isPalindrom(String s) { + bool isPalindrom = true; + for (var i = 0; i < s.length; i++) { + if (s[i] != s[s.length - i - 1]) isPalindrom = false; + } + return isPalindrom; + } + + /// Checks if all data have same value. + /// Example: 111111 -> true, wwwww -> true, [1,1,1,1] -> true + static bool isOneAKind(dynamic s) { + if ((s is String || s is List) && !isNullOrBlank(s)) { + var first = s[0]; + var isOneAKind = true; + for (var i = 0; i < s.length; i++) { + if (s[i] != first) isOneAKind = false; + } + return isOneAKind; + } + if (s is int) { + String value = s.toString(); + var first = value[0]; + var isOneAKind = true; + for (var i = 0; i < value.length; i++) { + if (value[i] != first) isOneAKind = false; + } + return isOneAKind; + } + return false; + } + + /// Checks if string is Passport No. + static bool isPassport(String s) => + RegexValidation.hasMatch(s, regex.passport); + + /// Checks if string is Currency. + static bool isCurrency(String s) => + RegexValidation.hasMatch(s, regex.currency); + + /// Checks if length of data is LOWER than maxLength. + static bool isLengthLowerThan(dynamic s, int maxLength) { + if (isNull(s)) return (maxLength <= 0) ? true : false; + switch (s.runtimeType) { + case String: + case List: + case Map: + case Set: + case Iterable: + return s.length < maxLength; + break; + case int: + return s.toString().length < maxLength; + break; + case double: + return s.toString().replaceAll('.', '').length < maxLength; + break; + default: + return false; + } + } + + /// Checks if length of data is GREATER than maxLength. + static bool isLengthGreaterThan(dynamic s, int maxLength) { + if (isNull(s)) return false; + switch (s.runtimeType) { + case String: + case List: + case Map: + case Set: + case Iterable: + return s.length > maxLength; + break; + case int: + return s.toString().length > maxLength; + break; + case double: + return s.toString().replaceAll('.', '').length > maxLength; + break; + default: + return false; + } + } + + /// Checks if length of data is GREATER OR EQUAL to maxLength. + static bool isLengthGreaterOrEqual(dynamic s, int maxLength) { + if (isNull(s)) return false; + switch (s.runtimeType) { + case String: + case List: + case Map: + case Set: + case Iterable: + return s.length >= maxLength; + break; + case int: + return s.toString().length >= maxLength; + break; + case double: + return s.toString().replaceAll('.', '').length >= maxLength; + break; + default: + return false; + } + } + + /// Checks if length of data is LOWER OR EQUAL to maxLength. + static bool isLengthLowerOrEqual(dynamic s, int maxLength) { + if (isNull(s)) return false; + switch (s.runtimeType) { + case String: + case List: + case Map: + case Set: + case Iterable: + return s.length <= maxLength; + break; + case int: + return s.toString().length <= maxLength; + break; + case double: + return s.toString().replaceAll('.', '').length <= maxLength; + default: + return false; + } + } + + /// Checks if length of data is EQUAL to maxLength. + static bool isLengthEqualTo(dynamic s, int maxLength) { + if (isNull(s)) return false; + switch (s.runtimeType) { + case String: + case List: + case Map: + case Set: + case Iterable: + return s.length == maxLength; + break; + case int: + return s.toString().length == maxLength; + break; + case double: + return s.toString().replaceAll('.', '').length == maxLength; + break; + default: + return false; + } + } + + /// Checks if length of data is BETWEEN minLength to maxLength. + static bool isLengthBetween(dynamic s, int minLength, int maxLength) { + if (isNull(s)) return false; + return isLengthGreaterOrEqual(s, minLength) && + isLengthLowerOrEqual(s, maxLength); + } + + /// Checks if a contains b (Treating or interpreting upper- and lowercase letters as being the same). + static bool isCaseInsensitiveContains(String a, String b) => + a.toLowerCase().contains(b.toLowerCase()); + + /// Checks if a contains b or b contains a (Treating or interpreting upper- and lowercase letters as being the same). + static bool isCaseInsensitiveContainsAny(String a, String b) { + String lowA = a.toLowerCase(); + String lowB = b.toLowerCase(); + return lowA.contains(lowB) || lowB.contains(lowA); + } + + /// Checks if num a LOWER than num b. + static bool isLowerThan(num a, num b) => a < b; + + /// Checks if num a GREATER than num b. + static bool isGreaterThan(num a, num b) => a > b; + + /// Checks if num a EQUAL than num b. + static bool isEqual(num a, num b) => a == b; + + /// Capitalize each word inside string + /// Example: your name => Your Name, your name => Your name + /// + /// If First Only is `true`, the only letter get uppercase is the first letter + static String capitalize(String s, {bool firstOnly = false}) { + if (isNullOrBlank(s)) return null; + if (firstOnly) return capitalizeFirst(s); + + List lst = s.split(' '); + String newStr = ''; + for (var s in lst) newStr += capitalizeFirst(s); + return newStr; + } + + /// Uppercase first letter inside string and let the others lowercase + /// Example: your name => Your name + static String capitalizeFirst(String s) { + if (isNullOrBlank(s)) return null; + return s[0].toUpperCase() + s.substring(1).toLowerCase(); + } + + /// Remove all whitespace inside string + /// Example: your name => yourname + static String removeAllWhitespace(String s) { + if (isNullOrBlank(s)) return null; + return s.replaceAll(' ', ''); + } + + /// Camelcase string + /// Example: your name => yourName + static String camelCase(String s) { + if (isNullOrBlank(s)) return null; + return s[0].toLowerCase() + removeAllWhitespace(capitalize(s)).substring(1); + } + + /// Extract numeric value of string + /// Example: OTP 12312 27/04/2020 => 1231227042020ß + /// If firstword only is true, then the example return is "12312" (first found numeric word) + static String numericOnly(String s, {bool firstWordOnly = false}) { + String numericOnlyStr = ''; + for (var i = 0; i < s.length; i++) { + if (isNumericOnly(s[i])) numericOnlyStr += s[i]; + if (firstWordOnly && numericOnlyStr.isNotEmpty && s[i] == " ") break; + } + return numericOnlyStr; + } + + static Regex regex = Regex(); +} + +class RegexValidation { + /// Returns whether the pattern has a match in the string [input]. + static bool hasMatch(String s, Pattern p) => + (s == null) ? false : RegExp(p).hasMatch(s); +} diff --git a/lib/src/regex/regex.dart b/lib/src/regex/regex.dart new file mode 100644 index 000000000..8c0946978 --- /dev/null +++ b/lib/src/regex/regex.dart @@ -0,0 +1,163 @@ +class Regex { + /// Username regex + Pattern username = r'^[a-zA-Z0-9][a-zA-Z0-9_.]+[a-zA-Z0-9]$'; + + /// Email regex + Pattern email = + r'^[a-z0-9]+([-+._][a-z0-9]+){0,2}@.*?(\.(a(?:[cdefgilmnoqrstuwxz]|ero|(?:rp|si)a)|b(?:[abdefghijmnorstvwyz]iz)|c(?:[acdfghiklmnoruvxyz]|at|o(?:m|op))|d[ejkmoz]|e(?:[ceghrstu]|du)|f[ijkmor]|g(?:[abdefghilmnpqrstuwy]|ov)|h[kmnrtu]|i(?:[delmnoqrst]|n(?:fo|t))|j(?:[emop]|obs)|k[eghimnprwyz]|l[abcikrstuvy]|m(?:[acdeghklmnopqrstuvwxyz]|il|obi|useum)|n(?:[acefgilopruz]|ame|et)|o(?:m|rg)|p(?:[aefghklmnrstwy]|ro)|qa|r[eosuw]|s[abcdeghijklmnortuvyz]|t(?:[cdfghjklmnoprtvwz]|(?:rav)?el)|u[agkmsyz]|v[aceginu]|w[fs]|y[etu]|z[amw])\b){1,2}$'; + + /// URL regex + Pattern url = + r"^((((H|h)(T|t)|(F|f))(T|t)(P|p)((S|s)?))\://)?(www.|[a-zA-Z0-9].)[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,6}(\:[0-9]{1,5})*(/($|[a-zA-Z0-9\.\,\;\?\'\\\+&%\$#\=~_\-]+))*$"; + + /// Phone Number regex + /// Must started by either, "0", "+", "+XX ", "(+XX )" + /// Can add whitespace separating digit with "+" or "(+XX)" + /// Example: 05555555555, +555 5555555555, (+123) 5555555555, (555) 5555555555, +5555 5555555555 + Pattern phone = + r'^(0|\+|(\+[0-9]{2,4}|\(\+?[0-9]{2,4}\)) ?)([0-9]*|\d{2,4}-\d{2,4}(-\d{2,4})?)$'; + + /// Hexadecimal regex + Pattern hexadecimal = r'^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$'; + + /// Image vector regex + Pattern vector = r'.(svg)$'; + + /// Image regex + Pattern image = r'.(jpeg|jpg|gif|png|bmp)$'; + + /// Audio regex + Pattern audio = r'.(mp3|wav|wma|amr|ogg)$'; + + /// Video regex + Pattern video = r'.(mp4|avi|wmv|rmvb|mpg|mpeg|3gp)$'; + + /// Txt regex + Pattern txt = r'.txt$'; + + /// Document regex + Pattern doc = r'.(doc|docx)$'; + + /// Excel regex + Pattern excel = r'.(xls|xlsx)$'; + + /// PPT regex + Pattern ppt = r'.(ppt|pptx)$'; + + /// Document regex + Pattern apk = r'.apk$'; + + /// PDF regex + Pattern pdf = r'.pdf$'; + + /// HTML regex + Pattern html = r'.html$'; + + /// DateTime regex (UTC) + /// Unformatted date time (UTC and Iso8601) + /// Example: 2020-04-27 08:14:39.977, 2020-04-27T08:14:39.977, 2020-04-27 01:14:39.977Z + Pattern basicDateTime = r'^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}.\d{3}Z?$'; + + /// Binary regex + /// Consist only 0 & 1 + Pattern binary = r'^[0-1]*$'; + + /// MD5 regex + Pattern md5 = r'^[a-f0-9]{32}$'; + + /// SHA1 regex + Pattern sha1 = r'(([A-Fa-f0-9]{2}\:){19}[A-Fa-f0-9]{2}|[A-Fa-f0-9]{40})'; + + /// SHA256 regex + Pattern sha256 = r'([A-Fa-f0-9]{2}\:){31}[A-Fa-f0-9]{2}|[A-Fa-f0-9]{64}'; + + /// SSN (Social Security Number) regex + Pattern ssn = + r'^(?!0{3}|6{3}|9[0-9]{2})[0-9]{3}-?(?!0{2})[0-9]{2}-?(?!0{4})[0-9]{4}$'; + + /// IPv4 regex + Pattern ipv4 = r'^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$'; + + /// IPv6 regex + Pattern ipv6 = + r'^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$'; + + /// ISBN 10 & 13 regex + Pattern isbn = + r'(ISBN(\-1[03])?[:]?[ ]?)?(([0-9Xx][- ]?){13}|([0-9Xx][- ]?){10})'; + + /// Passport No. regex + Pattern passport = r'^(?!^0+$)[a-zA-Z0-9]{6,9}$'; + + /// Currency regex + Pattern currency = + r'^(S?\$|\₩|Rp|\¥|\€|\₹|\₽|fr|R$|R)?[ ]?[-]?([0-9]{1,3}[,.]([0-9]{3}[,.])*[0-9]{3}|[0-9]+)([,.][0-9]{1,2})?( ?(USD?|AUD|NZD|CAD|CHF|GBP|CNY|EUR|JPY|IDR|MXN|NOK|KRW|TRY|INR|RUB|BRL|ZAR|SGD|MYR))?$'; + + /// Numeric Only regex (No Whitespace & Symbols) + Pattern numericOnly = r'^\d+$'; + + /// Alphabet Only regex (No Whitespace & Symbols) + Pattern alphabetOnly = r'^[a-zA-Z]+$'; + + /// Password (Easy) Regex + /// Allowing all character except 'whitespace' + /// Minimum character: 8 + Pattern passwordEasy = r'^\S{8,}$'; + + /// Password (Easy) Regex + /// Allowing all character + /// Minimum character: 8 + Pattern passwordEasyAllowedWhitespace = r'^[\S ]{8,}$'; + + /// Password (Normal) Regex + /// Allowing all character except 'whitespace' + /// Must contains at least: 1 letter & 1 number + /// Minimum character: 8 + Pattern passwordNormal1 = r'^(?=.*[A-Za-z])(?=.*\d)\S{8,}$'; + + /// Password (Normal) Regex + /// Allowing all character + /// Must contains at least: 1 letter & 1 number + /// Minimum character: 8 + Pattern passwordNormal1AllowedWhitespace = + r'^(?=.*[A-Za-z])(?=.*\d)[\S ]{8,}$'; + + /// Password (Normal) Regex + /// Allowing LETTER and NUMBER only + /// Must contains at least: 1 letter & 1 number + /// Minimum character: 8 + Pattern passwordNormal2 = r'^(?=.*[A-Za-z])(?=.*\d)[a-zA-Z0-9]{8,}$'; + + /// Password (Normal) Regex + /// Allowing LETTER and NUMBER only + /// Must contains: 1 letter & 1 number + /// Minimum character: 8 + Pattern passwordNormal2AllowedWhitespace = + r'^(?=.*[A-Za-z])(?=.*\d)[a-zA-Z0-9 ]{8,}$'; + + /// Password (Normal) Regex + /// Allowing all character except 'whitespace' + /// Must contains at least: 1 uppercase letter, 1 lowecase letter & 1 number + /// Minimum character: 8 + Pattern passwordNormal3 = r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)\S{8,}$'; + + /// Password (Normal) Regex + /// Allowing all character + /// Must contains at least: 1 uppercase letter, 1 lowecase letter & 1 number + /// Minimum character: 8 + Pattern passwordNormal3AllowedWhitespace = + r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[\S ]{8,}$'; + + /// Password (Hard) Regex + /// Allowing all character except 'whitespace' + /// Must contains at least: 1 uppercase letter, 1 lowecase letter, 1 number, & 1 special character (symbol) + /// Minimum character: 8 + Pattern passwordHard = r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_])\S{8,}$'; + + /// Password (Hard) Regex + /// Allowing all character + /// Must contains at least: 1 uppercase letter, 1 lowecase letter, 1 number, & 1 special character (symbol) + /// Minimum character: 8 + Pattern passwordHardAllowedWhitespace = + r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_])[\S ]{8,}$'; +} diff --git a/lib/src/root/root_controller.dart b/lib/src/root/root_controller.dart index e7c3794ee..68683a54b 100644 --- a/lib/src/root/root_controller.dart +++ b/lib/src/root/root_controller.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:get/src/routes/utils/parse_arguments.dart'; import 'package:get/src/state/get_state.dart'; -class GetMaterialController extends GetController { - ParseRoute parse = ParseRoute(); +class GetMaterialController extends GetxController { Key key; ThemeData theme; ThemeMode themeMode; diff --git a/lib/src/root/root_widget.dart b/lib/src/root/root_widget.dart index 2d19dae8c..5512f630f 100644 --- a/lib/src/root/root_widget.dart +++ b/lib/src/root/root_widget.dart @@ -1,7 +1,7 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:get/src/routes/get_route.dart'; -import 'package:get/src/routes/utils/parse_arguments.dart'; import '../get_instance.dart'; import 'parse_route.dart'; import 'root_controller.dart'; @@ -19,6 +19,8 @@ class GetMaterialApp extends StatelessWidget { this.onUnknownRoute, this.navigatorObservers = const [], this.builder, + this.translationsKeys, + this.translations, this.title = '', this.onGenerateTitle, this.color, @@ -50,27 +52,6 @@ class GetMaterialApp extends StatelessWidget { this.popGesture, this.transitionDuration, this.defaultGlobalState, - // ignore: deprecated_member_use_from_same_package - @Deprecated( - """Please, use new api getPages. You can now simply create a list of GetPages, - and enter your route name in the 'name' property. - Page now receives a function ()=> Page(), which allows more flexibility. - You can now decide which page you want to display, according to the parameters received on the page. - Example: - GetPage( - name: '/second', - page:(){ - if (Get.parameters['id'] == null) { - return Login(); - } else { - return Home(); - } - }); - - """) - // ignore: deprecated_member_use_from_same_package - this.namedRoutes, - this.unknownRoute, }) : assert(routes != null), assert(navigatorObservers != null), assert(title != null), @@ -97,6 +78,8 @@ class GetMaterialApp extends StatelessWidget { final ThemeData darkTheme; final ThemeMode themeMode; final Color color; + final Map> translationsKeys; + final Translations translations; final Locale locale; final Iterable> localizationsDelegates; final LocaleListResolutionCallback localeListResolutionCallback; @@ -121,135 +104,52 @@ class GetMaterialApp extends StatelessWidget { final Bindings initialBinding; final Duration transitionDuration; final bool defaultGlobalState; - - final Map namedRoutes; final List getPages; - final GetRoute unknownRoute; Route generator(RouteSettings settings) { - final match = _routeTree.matchRoute(settings.name); - print(settings.name); - + final match = Get.routeTree.matchRoute(settings.name); Get.parameters = match?.parameters; - return GetRouteBase( - page: null, - title: match.route.title, - route: match.route.page, + return GetPageRoute( + page: match.route.page, parameter: match.route.parameter, settings: RouteSettings(name: settings.name, arguments: settings.arguments), - maintainState: match.route.maintainState, curve: match.route.curve, - alignment: match.route.alignment, opaque: match.route.opaque, binding: match.route.binding, bindings: match.route.bindings, - transitionDuration: (transitionDuration == null - ? match.route.transitionDuration - : transitionDuration), + duration: (transitionDuration ?? match.route.transitionDuration), transition: match.route.transition, popGesture: match.route.popGesture, fullscreenDialog: match.route.fullscreenDialog, ); } - Route namedRoutesGenerate(RouteSettings settings) { - // Get.setSettings(settings); - - /// onGenerateRoute to FlutterWeb is Broken on Dev/Master. This is a temporary - /// workaround until they fix it, because the problem is with the 'Flutter engine', - /// which changes the initial route for an empty String, not the main Flutter, - /// so only Team can fix it. - var parsedString = Get.getController.parse.split( - (settings.name == '' || settings.name == null) - ? (initialRoute ?? '/') - : settings.name); - - if (parsedString == null) { - parsedString = AppRouteMatch(); - parsedString.route = settings.name; - } - - String settingsName = parsedString.route; - Map newNamedRoutes = {}; - - namedRoutes.forEach((key, value) { - String newName = Get.getController.parse.split(key).route; - newNamedRoutes.addAll({newName: value}); - }); - - if (newNamedRoutes.containsKey(settingsName)) { - Get.parameters = parsedString.parameters; - - return GetRouteBase( - page: newNamedRoutes[settingsName].page, - title: newNamedRoutes[settingsName].title, - parameter: parsedString.parameters, - settings: - RouteSettings(name: settings.name, arguments: settings.arguments), - maintainState: newNamedRoutes[settingsName].maintainState, - curve: newNamedRoutes[settingsName].curve, - alignment: newNamedRoutes[settingsName].alignment, - opaque: newNamedRoutes[settingsName].opaque, - binding: newNamedRoutes[settingsName].binding, - bindings: newNamedRoutes[settingsName].bindings, - transitionDuration: (transitionDuration == null - ? newNamedRoutes[settingsName].transitionDuration - : transitionDuration), - transition: newNamedRoutes[settingsName].transition, - popGesture: newNamedRoutes[settingsName].popGesture, - fullscreenDialog: newNamedRoutes[settingsName].fullscreenDialog, - ); - } else { - return ((unknownRoute == null - ? GetRouteBase( - page: Scaffold( - body: Center( - child: Text("Route not found :("), - ), - )) - : GetRouteBase( - page: unknownRoute.page, - title: unknownRoute.title, - settings: unknownRoute.settings, - maintainState: unknownRoute.maintainState, - curve: unknownRoute.curve, - alignment: unknownRoute.alignment, - parameter: unknownRoute.parameter, - opaque: unknownRoute.opaque, - binding: unknownRoute.binding, - bindings: unknownRoute.bindings, - transitionDuration: unknownRoute.transitionDuration, - popGesture: unknownRoute.popGesture, - transition: unknownRoute.transition, - fullscreenDialog: unknownRoute.fullscreenDialog, - ))); - } - } - @override Widget build(BuildContext context) { return GetBuilder( - init: Get.getController, + init: Get.getxController, dispose: (d) { onDispose?.call(); }, initState: (i) { - if (getPages != null) { - _routeTree = ParseRouteTree(); - getPages.forEach((element) { - _routeTree.addRoute(element); - }); + if (locale != null) { + Get.locale = locale; + } + + if (translations != null) { + if (Get.locale == null) Get.translations = translations.keys; + } + if (translationsKeys != null) { + Get.translations = translationsKeys; } + initialBinding?.dependencies(); + Get.addPages(getPages); GetConfig.smartManagement = smartManagement; onInit?.call(); - if (namedRoutes != null) { - namedRoutes.forEach((key, value) { - Get.getController.parse.addRoute(key); - }); - } + Get.config( enableLog: enableLog ?? GetConfig.isLogEnable, defaultTransition: defaultTransition ?? Get.defaultTransition, @@ -268,32 +168,29 @@ class GetMaterialApp extends StatelessWidget { home: home, routes: routes ?? const {}, initialRoute: initialRoute, - onGenerateRoute: (getPages != null - ? generator - : namedRoutes != null ? namedRoutesGenerate : onGenerateRoute), - onGenerateInitialRoutes: onGenerateInitialRoutes ?? - (getPages == null || home != null) - ? null + onGenerateRoute: (getPages != null ? generator : onGenerateRoute), + onGenerateInitialRoutes: (getPages == null || home != null) + ? onGenerateInitialRoutes : (st) { - final match = _routeTree.matchRoute(initialRoute); - Get.parameters = match.parameters; + GetPageMatch match; + if (initialRoute == null && getPages != null) { + match = Get.routeTree?.matchRoute(getPages.first.name); + } else { + match = Get.routeTree?.matchRoute(initialRoute); + } + Get.parameters = match?.parameters; return [ - GetRouteBase( - page: null, - route: match.route.page, - title: match.route.title, + GetPageRoute( + page: match.route.page, parameter: match.parameters, settings: RouteSettings(name: initialRoute, arguments: null), - maintainState: match.route.maintainState, curve: match.route.curve, - alignment: match.route.alignment, opaque: match.route.opaque, binding: match.route.binding, bindings: match.route.bindings, - transitionDuration: (transitionDuration == null - ? match.route.transitionDuration - : transitionDuration), + duration: (transitionDuration ?? + match.route.transitionDuration), transition: match.route.transition, popGesture: match.route.popGesture, fullscreenDialog: match.route.fullscreenDialog, @@ -312,7 +209,7 @@ class GetMaterialApp extends StatelessWidget { theme: _.theme ?? theme ?? ThemeData.fallback(), darkTheme: darkTheme, themeMode: _.themeMode ?? themeMode ?? ThemeMode.system, - locale: locale, + locale: Get.locale ?? locale, localizationsDelegates: localizationsDelegates, localeListResolutionCallback: localeListResolutionCallback, localeResolutionCallback: localeResolutionCallback, @@ -332,4 +229,29 @@ class GetMaterialApp extends StatelessWidget { } } -ParseRouteTree _routeTree; +abstract class Translations { + Map> get keys; +} + +extension Trans on String { + String get tr { + if (Get.translations + .containsKey("${Get.locale.languageCode}_${Get.locale.countryCode}")) { + return Get.translations[ + "${Get.locale.languageCode}_${Get.locale.countryCode}"][this]; + } else if (Get.translations.containsKey(Get.locale.languageCode)) { + return Get.translations[Get.locale.languageCode][this]; + } + return Get.translations.values.first[this]; + } + + String trArgs([List args]) { + String key = tr; + if (args != null) { + args.forEach((arg) { + key = key.replaceFirst(RegExp(r'%s'), arg.toString()); + }); + } + return key; + } +} diff --git a/lib/src/routes/bindings_interface.dart b/lib/src/routes/bindings_interface.dart index 838cff39d..dfe10d834 100644 --- a/lib/src/routes/bindings_interface.dart +++ b/lib/src/routes/bindings_interface.dart @@ -1,5 +1,5 @@ abstract class Bindings { - void dependencies(); + dependencies(); } // abstract class INavigation {} diff --git a/lib/src/routes/custom_transition.dart b/lib/src/routes/custom_transition.dart new file mode 100644 index 000000000..d8face17f --- /dev/null +++ b/lib/src/routes/custom_transition.dart @@ -0,0 +1,10 @@ +import 'package:flutter/widgets.dart'; + +abstract class CustomTransition { + Widget buildTransition( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ); +} diff --git a/lib/src/routes/default_route.dart b/lib/src/routes/default_route.dart index 6a9035f3a..c3528e95a 100644 --- a/lib/src/routes/default_route.dart +++ b/lib/src/routes/default_route.dart @@ -1,509 +1,159 @@ import 'dart:math'; import 'dart:ui' show lerpDouble; - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:get/src/get_instance.dart'; import 'package:get/src/get_main.dart'; -import 'package:get/src/routes/bindings_interface.dart'; - -import '../platform/platform.dart'; +import 'package:get/src/platform/platform.dart'; +import 'bindings_interface.dart'; +import 'custom_transition.dart'; +import 'transitions_filter.dart'; import 'transitions_type.dart'; -const double _kBackGestureWidth = 20.0; -const double _kMinFlingVelocity = 1.0; -const int _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds. - -// The maximum time for a page to get reset to it's original position if the -// user releases a page mid swipe. -const int _kMaxPageBackAnimationTime = 300; +class GetPageRoute extends PageRouteBuilder { + //final TransitionComponent transitionComponent; + final Duration duration; + final bool popGesture; + final Transition transition; + final Curve curve; + final GetPageBuilder page; + final CustomTransition customTransition; + final Bindings binding; + final Map parameter; + final List bindings; -class GetRouteBase extends PageRoute { - /// The [builder], [maintainState], and [fullscreenDialog] arguments must not - /// be null. - GetRouteBase({ - @required this.page, - this.title, + GetPageRoute({ + // this.transitionComponent, RouteSettings settings, - this.maintainState = true, - this.curve = Curves.linear, - this.alignment, - this.parameter, + this.duration, + this.transition = Transition.native, this.binding, - this.route, + @required this.page, this.bindings, - this.customBuildPageTransitions, this.opaque = true, - this.transitionDuration = const Duration(milliseconds: 400), + this.parameter, + this.fullscreenDialog = false, + this.curve, this.popGesture, - this.transition, - // this.duration = const Duration(milliseconds: 400), - bool fullscreenDialog = false, - }) : // assert(page != null), - assert(maintainState != null), - assert(fullscreenDialog != null), - // assert(opaque), - super(settings: settings, fullscreenDialog: fullscreenDialog) { - /// prebuild dependencies - if (binding != null) { - binding.dependencies(); - } - if (bindings != null) { - bindings.forEach((element) => element.dependencies()); - } - } - - /// Builds the primary contents of the route. - final Widget page; - - final GetPageBuilder route; - - final Widget customBuildPageTransitions; - - final bool popGesture; - - final Bindings binding; - - final List bindings; - - // final Duration duration; - - final Map parameter; - - final String title; - - final Transition transition; - - final Curve curve; - - final Alignment alignment; - - ValueNotifier _previousTitle; - - /// The title string of the previous [GetRoute]. - /// - /// The [ValueListenable]'s value is readable after the route is installed - /// onto a [Navigator]. The [ValueListenable] will also notify its listeners - /// if the value changes (such as by replacing the previous route). - /// - /// The [ValueListenable] itself will be null before the route is installed. - /// Its content value will be null if the previous route has no title or - /// is not a [GetRoute]. - /// - /// See also: - /// - /// * [ValueListenableBuilder], which can be used to listen and rebuild - /// widgets based on a ValueListenable. - ValueListenable get previousTitle { - assert( - _previousTitle != null, - 'Cannot read the previousTitle for a route that has not yet been installed', - ); - return _previousTitle; - } + this.customTransition, + }) : super( + pageBuilder: (context, anim1, anim2) { + if (binding != null) { + binding.dependencies(); + } + if (bindings != null) { + for (Bindings element in bindings) { + element.dependencies(); + } + } + GetConfig.currentRoute = settings.name; + return page(); + }, + settings: settings, + ); - @override - void didChangePrevious(Route previousRoute) { - final String previousTitleString = - previousRoute is GetRouteBase ? previousRoute.title : null; - if (_previousTitle == null) { - _previousTitle = ValueNotifier(previousTitleString); - } else { - _previousTitle.value = previousTitleString; - } - super.didChangePrevious(previousRoute); - } - - @override - final bool maintainState; - - /// Allows you to set opaque to false to prevent route reconstruction. @override final bool opaque; @override - final Duration transitionDuration; + final bool fullscreenDialog; @override - Color get barrierColor => null; //Color(0x00FFFFFF); + Widget buildTransitions(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + if (this.customTransition != null) { + return this.customTransition.buildTransition( + context, + animation, + secondaryAnimation, + popGesture ?? Get.defaultPopGesture + ? _CupertinoBackGestureDetector( + enabledCallback: () => _isPopGestureEnabled(this), + onStartPopGesture: () => _startPopGesture(this), + child: child) + : child); + } - @override - String get barrierLabel => null; + if (transition == Transition.native) { + return Theme.of(context).pageTransitionsTheme.buildTransitions( + this, + context, + animation, + secondaryAnimation, + GetPlatform.isIOS + ? _CupertinoBackGestureDetector( + enabledCallback: () => _isPopGestureEnabled(this), + onStartPopGesture: () => _startPopGesture(this), + child: child) + : child); + } - @override - bool canTransitionTo(TransitionRoute nextRoute) { - // Don't perform outgoing animation if the next route is a fullscreen dialog. - return nextRoute is GetRouteBase && !nextRoute.fullscreenDialog; - } + final curvedAnimation = CurvedAnimation( + parent: animation, + curve: this.curve ?? Curves.linear, + ); - /// True if an iOS-style back swipe pop gesture is currently underway for [route]. - /// - /// This just check the route's [NavigatorState.userGestureInProgress]. - /// - /// See also: - /// - /// * [popGestureEnabled], which returns true if a user-triggered pop gesture - /// would be allowed. - static bool isPopGestureInProgress(PageRoute route) { - return route.navigator.userGestureInProgress; + return TransitionFilter.newTransitionComponent(transition) + .buildChildWithTransition( + context, + curvedAnimation, + secondaryAnimation, + popGesture ?? Get.defaultPopGesture + ? _CupertinoBackGestureDetector( + enabledCallback: () => _isPopGestureEnabled(this), + onStartPopGesture: () => _startPopGesture(this), + child: child) + : child); } - /// True if an iOS-style back swipe pop gesture is currently underway for this route. - /// - /// See also: - /// - /// * [isPopGestureInProgress], which returns true if a Cupertino pop gesture - /// is currently underway for specific route. - /// * [popGestureEnabled], which returns true if a user-triggered pop gesture - /// would be allowed. - bool get popGestureInProgress => isPopGestureInProgress(this); - - /// Whether a pop gesture can be started by the user. - /// - /// Returns true if the user can edge-swipe to a previous route. - /// - /// Returns false once [isPopGestureInProgress] is true, but - /// [isPopGestureInProgress] can only become true if [popGestureEnabled] was - /// true first. - /// - /// This should only be used between frames, not during build. - bool get popGestureEnabled => _isPopGestureEnabled(this); + @override + Duration get transitionDuration => + this.duration ?? Duration(milliseconds: 300); static bool _isPopGestureEnabled(PageRoute route) { - // If there's nothing to go back to, then obviously we don't support - // the back gesture. if (route.isFirst) return false; - // If the route wouldn't actually pop if we popped it, then the gesture - // would be really confusing (or would skip internal routes), so disallow it. + if (route.willHandlePopInternally) return false; - // If attempts to dismiss this route might be vetoed such as in a page - // with forms, then do not allow the user to dismiss the route with a swipe. + if (route.hasScopedWillPopCallback) return false; - // Fullscreen dialogs aren't dismissible by back swipe. + if (route.fullscreenDialog) return false; - // If we're in an animation already, we cannot be manually swiped. + if (route.animation.status != AnimationStatus.completed) return false; - // If we're being popped into, we also cannot be swiped until the pop above - // it completes. This translates to our secondary animation being - // dismissed. + if (route.secondaryAnimation.status != AnimationStatus.dismissed) return false; - // If we're in a gesture already, we cannot start another. + if (isPopGestureInProgress(route)) return false; - // Looks like a back gesture would be welcome! return true; } - @override - Widget buildPage(BuildContext context, Animation animation, - Animation secondaryAnimation) { - final Widget result = Semantics( - scopesRoute: true, - explicitChildNodes: true, - child: (route == null ? page : route()), - ); - assert(() { - if (route == null && page == null) { - throw FlutterError.fromParts([ - ErrorSummary( - 'The builder for route "${settings.name}" returned null.'), - ErrorDescription('Route builders must never return null.'), - ]); - } - return true; - }()); - return result; - } - - // Called by _CupertinoBackGestureDetector when a pop ("back") drag start - // gesture is detected. The returned controller handles all of the subsequent - // drag events. static _CupertinoBackGestureController _startPopGesture( PageRoute route) { assert(_isPopGestureEnabled(route)); return _CupertinoBackGestureController( navigator: route.navigator, - controller: route.controller, // protected access + controller: route.controller, ); } - /// Returns a [CupertinoFullscreenDialogTransition] if [route] is a full - /// screen dialog, otherwise a [CupertinoPageTransition] is returned. - /// - /// Used by [GetRoute.buildTransitions]. - /// - /// This method can be applied to any [PageRoute], not just - /// [GetRoute]. It's typically used to provide a Cupertino style - /// horizontal transition for material widgets when the target platform - /// is [TargetPlatform.iOS]. - /// - /// See also: - /// - /// * [CupertinoPageTransitionsBuilder], which uses this method to define a - /// [PageTransitionsBuilder] for the [PageTransitionsTheme]. - Widget buildPageTransitions( - PageRoute route, - BuildContext context, - bool popGesture, - Animation animation, - Animation secondaryAnimation, - Widget child, - Transition tr, - Curve curve, - Alignment alignment, - ) { - Transition transition = (tr ?? Get.defaultTransition); - - if (route.fullscreenDialog) { - final bool linearTransition = isPopGestureInProgress(route); - return CupertinoFullscreenDialogTransition( - primaryRouteAnimation: animation, - secondaryRouteAnimation: secondaryAnimation, - child: child, - linearTransition: linearTransition, - ); - } else { - switch (transition) { - case Transition.fade: - final PageTransitionsBuilder matchingBuilder = - FadeUpwardsPageTransitionsBuilder(); - return matchingBuilder.buildTransitions( - route, - context, - animation, - secondaryAnimation, - popGesture - ? _CupertinoBackGestureDetector( - enabledCallback: () => _isPopGestureEnabled(route), - onStartPopGesture: () => _startPopGesture(route), - child: child) - : child); - break; - case Transition.rightToLeft: - return SlideTransition( - transformHitTests: false, - position: new Tween( - begin: const Offset(1.0, 0.0), - end: Offset.zero, - ).animate(animation), - child: new SlideTransition( - position: new Tween( - begin: Offset.zero, - end: const Offset(-1.0, 0.0), - ).animate(secondaryAnimation), - child: popGesture - ? _CupertinoBackGestureDetector( - enabledCallback: () => _isPopGestureEnabled(route), - onStartPopGesture: () => _startPopGesture(route), - child: child) - : child), - ); - break; - case Transition.leftToRight: - return SlideTransition( - transformHitTests: false, - position: Tween( - begin: const Offset(-1.0, 0.0), - end: Offset.zero, - ).animate(animation), - child: new SlideTransition( - position: new Tween( - begin: Offset.zero, - end: const Offset(1.0, 0.0), - ).animate(secondaryAnimation), - child: popGesture - ? _CupertinoBackGestureDetector( - enabledCallback: () => _isPopGestureEnabled(route), - onStartPopGesture: () => _startPopGesture(route), - child: child) - : child), - ); - break; - case Transition.upToDown: - return SlideTransition( - transformHitTests: false, - position: Tween( - begin: const Offset(0.0, -1.0), - end: Offset.zero, - ).animate(animation), - child: new SlideTransition( - position: new Tween( - begin: Offset.zero, - end: const Offset(0.0, 1.0), - ).animate(secondaryAnimation), - child: popGesture - ? _CupertinoBackGestureDetector( - enabledCallback: () => _isPopGestureEnabled(route), - onStartPopGesture: () => _startPopGesture(route), - child: child) - : child), - ); - break; - case Transition.downToUp: - return SlideTransition( - transformHitTests: false, - position: Tween( - begin: const Offset(0.0, 1.0), - end: Offset.zero, - ).animate(animation), - child: new SlideTransition( - position: new Tween( - begin: Offset.zero, - end: const Offset(0.0, -1.0), - ).animate(secondaryAnimation), - child: popGesture - ? _CupertinoBackGestureDetector( - enabledCallback: () => _isPopGestureEnabled(route), - onStartPopGesture: () => _startPopGesture(route), - child: child) - : child), - ); - break; - case Transition.scale: - return ScaleTransition( - alignment: alignment, - scale: CurvedAnimation( - parent: animation, - curve: Interval( - 0.00, - 0.50, - curve: curve, - ), - ), - child: popGesture - ? _CupertinoBackGestureDetector( - enabledCallback: () => _isPopGestureEnabled(route), - onStartPopGesture: () => _startPopGesture(route), - child: child) - : child); - break; - case Transition.rotate: - return RotationTransition( - alignment: alignment, - turns: animation, - child: ScaleTransition( - alignment: alignment, - scale: animation, - child: FadeTransition( - opacity: animation, - child: popGesture - ? _CupertinoBackGestureDetector( - enabledCallback: () => _isPopGestureEnabled(route), - onStartPopGesture: () => _startPopGesture(route), - child: child) - : child), - ), - ); - break; - - case Transition.rightToLeftWithFade: - return SlideTransition( - position: Tween( - begin: const Offset(1.0, 0.0), - end: Offset.zero, - ).animate(animation), - child: FadeTransition( - opacity: animation, - child: SlideTransition( - position: Tween( - begin: Offset.zero, - end: const Offset(-1.0, 0.0), - ).animate(secondaryAnimation), - child: popGesture - ? _CupertinoBackGestureDetector( - enabledCallback: () => _isPopGestureEnabled(route), - onStartPopGesture: () => _startPopGesture(route), - child: child) - : child), - ), - ); - break; - case Transition.leftToRightWithFade: - return SlideTransition( - position: Tween( - begin: const Offset(-1.0, 0.0), - end: Offset.zero, - ).animate(animation), - child: FadeTransition( - opacity: animation, - child: SlideTransition( - position: Tween( - begin: Offset.zero, - end: const Offset(1.0, 0.0), - ).animate(secondaryAnimation), - child: popGesture - ? _CupertinoBackGestureDetector( - enabledCallback: () => _isPopGestureEnabled(route), - onStartPopGesture: () => _startPopGesture(route), - child: child) - : child), - ), - ); - break; - case Transition.cupertino: - return CupertinoPageTransition( - primaryRouteAnimation: animation, - secondaryRouteAnimation: secondaryAnimation, - // Check if the route has an animation that's currently participating - // in a back swipe gesture. - // - // In the middle of a back gesture drag, let the transition be linear to - // match finger motions. - linearTransition: isPopGestureInProgress(route), - child: _CupertinoBackGestureDetector( - enabledCallback: () => _isPopGestureEnabled(route), - onStartPopGesture: () => _startPopGesture(route), - child: child, - ), - ); - break; - default: - return CupertinoPageTransition( - primaryRouteAnimation: animation, - secondaryRouteAnimation: secondaryAnimation, - // Check if the route has an animation that's currently participating - // in a back swipe gesture. - // - // In the middle of a back gesture drag, let the transition be linear to - // match finger motions. - linearTransition: isPopGestureInProgress(route), - child: popGesture - ? _CupertinoBackGestureDetector( - enabledCallback: () => _isPopGestureEnabled(route), - onStartPopGesture: () => _startPopGesture(route), - child: child) - : child, - ); - } - } - } - - @override - Widget buildTransitions(BuildContext context, Animation animation, - Animation secondaryAnimation, Widget child) { - if (customBuildPageTransitions != null) { - return customBuildPageTransitions; - } else { - return buildPageTransitions( - this, - context, - popGesture ?? GetPlatform.isIOS, - animation, - secondaryAnimation, - child, - transition, - curve, - alignment); - } + static bool isPopGestureInProgress(PageRoute route) { + return route.navigator.userGestureInProgress; } - @override - String get debugLabel => '${super.debugLabel}(${settings.name})'; + bool get popGestureInProgress => isPopGestureInProgress(this); } +const double _kBackGestureWidth = 20.0; +const double _kMinFlingVelocity = 1.0; +const int _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds. + +// The maximum time for a page to get reset to it's original position if the +// user releases a page mid swipe. +const int _kMaxPageBackAnimationTime = 300; + class _CupertinoBackGestureDetector extends StatefulWidget { const _CupertinoBackGestureDetector({ Key key, @@ -655,10 +305,11 @@ class _CupertinoBackGestureController { // If the user releases the page before mid screen with sufficient velocity, // or after mid screen, we should animate the page out. Otherwise, the page // should be animated back in. - if (velocity.abs() >= _kMinFlingVelocity) + if (velocity.abs() >= _kMinFlingVelocity) { animateForward = velocity <= 0; - else + } else { animateForward = controller.value > 0.5; + } if (animateForward) { // The closer the panel is to dismissing, the shorter the animation is. @@ -704,3 +355,655 @@ class _CupertinoBackGestureController { } } } + +// import 'package:flutter/cupertino.dart'; +// import 'package:flutter/foundation.dart'; +// import 'package:flutter/gestures.dart'; +// import 'package:flutter/material.dart'; +// import 'package:get/src/get_main.dart'; +// import 'package:get/src/routes/bindings_interface.dart'; + +// import '../platform/platform.dart'; +// import 'transitions_type.dart'; + +// const double _kBackGestureWidth = 20.0; +// const double _kMinFlingVelocity = 1.0; +// const int _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds. + +// // The maximum time for a page to get reset to it's original position if the +// // user releases a page mid swipe. +// const int _kMaxPageBackAnimationTime = 300; + +// class GetPageRoute extends PageRoute { +// /// The [builder], [maintainState], and [fullscreenDialog] arguments must not +// /// be null. +// GetPageRoute({ +// @required this.page, +// this.title, +// RouteSettings settings, +// this.maintainState = true, +// this.curve = Curves.linear, +// this.alignment, +// this.parameter, +// this.binding, +// this.route, +// this.bindings, +// this.customBuildPageTransitions, +// this.opaque = true, +// this.transitionDuration = const Duration(milliseconds: 400), +// this.popGesture, +// this.transition, +// // this.duration = const Duration(milliseconds: 400), +// bool fullscreenDialog = false, +// }) : // assert(page != null), +// assert(maintainState != null), +// assert(fullscreenDialog != null), +// // assert(opaque), +// super(settings: settings, fullscreenDialog: fullscreenDialog) { +// /// prebuild dependencies +// if (binding != null) { +// binding.dependencies(); +// } +// if (bindings != null) { +// bindings.forEach((element) => element.dependencies()); +// } +// } + +// /// Builds the primary contents of the route. +// final Widget page; + +// final GetPageBuilder route; + +// final Widget customBuildPageTransitions; + +// final bool popGesture; + +// final Bindings binding; + +// final List bindings; + +// // final Duration duration; + +// final Map parameter; + +// final String title; + +// final Transition transition; + +// final Curve curve; + +// final Alignment alignment; + +// ValueNotifier _previousTitle; + +// /// The title string of the previous [GetRoute]. +// /// +// /// The [ValueListenable]'s value is readable after the route is installed +// /// onto a [Navigator]. The [ValueListenable] will also notify its listeners +// /// if the value changes (such as by replacing the previous route). +// /// +// /// The [ValueListenable] itself will be null before the route is installed. +// /// Its content value will be null if the previous route has no title or +// /// is not a [GetRoute]. +// /// +// /// See also: +// /// +// /// * [ValueListenableBuilder], which can be used to listen and rebuild +// /// widgets based on a ValueListenable. +// ValueListenable get previousTitle { +// assert( +// _previousTitle != null, +// 'Cannot read the previousTitle for a route that has not yet been installed', +// ); +// return _previousTitle; +// } + +// @override +// void didChangePrevious(Route previousRoute) { +// final String previousTitleString = +// previousRoute is GetPageRoute ? previousRoute.title : null; +// if (_previousTitle == null) { +// _previousTitle = ValueNotifier(previousTitleString); +// } else { +// _previousTitle.value = previousTitleString; +// } +// super.didChangePrevious(previousRoute); +// } + +// @override +// final bool maintainState; + +// /// Allows you to set opaque to false to prevent route reconstruction. +// @override +// final bool opaque; + +// @override +// final Duration transitionDuration; + +// @override +// Color get barrierColor => null; //Color(0x00FFFFFF); + +// @override +// String get barrierLabel => null; + +// @override +// bool canTransitionTo(TransitionRoute nextRoute) { +// // Don't perform outgoing animation if the next route is a fullscreen dialog. +// return nextRoute is GetPageRoute && !nextRoute.fullscreenDialog; +// } + +// /// True if an iOS-style back swipe pop gesture is currently underway for [route]. +// /// +// /// This just check the route's [NavigatorState.userGestureInProgress]. +// /// +// /// See also: +// /// +// /// * [popGestureEnabled], which returns true if a user-triggered pop gesture +// /// would be allowed. +// static bool isPopGestureInProgress(PageRoute route) { +// return route.navigator.userGestureInProgress; +// } + +// /// True if an iOS-style back swipe pop gesture is currently underway for this route. +// /// +// /// See also: +// /// +// /// * [isPopGestureInProgress], which returns true if a Cupertino pop gesture +// /// is currently underway for specific route. +// /// * [popGestureEnabled], which returns true if a user-triggered pop gesture +// /// would be allowed. +// bool get popGestureInProgress => isPopGestureInProgress(this); + +// /// Whether a pop gesture can be started by the user. +// /// +// /// Returns true if the user can edge-swipe to a previous route. +// /// +// /// Returns false once [isPopGestureInProgress] is true, but +// /// [isPopGestureInProgress] can only become true if [popGestureEnabled] was +// /// true first. +// /// +// /// This should only be used between frames, not during build. +// bool get popGestureEnabled => _isPopGestureEnabled(this); + +// static bool _isPopGestureEnabled(PageRoute route) { +// // If there's nothing to go back to, then obviously we don't support +// // the back gesture. +// if (route.isFirst) return false; +// // If the route wouldn't actually pop if we popped it, then the gesture +// // would be really confusing (or would skip internal routes), so disallow it. +// if (route.willHandlePopInternally) return false; +// // If attempts to dismiss this route might be vetoed such as in a page +// // with forms, then do not allow the user to dismiss the route with a swipe. +// if (route.hasScopedWillPopCallback) return false; +// // Fullscreen dialogs aren't dismissible by back swipe. +// if (route.fullscreenDialog) return false; +// // If we're in an animation already, we cannot be manually swiped. +// if (route.animation.status != AnimationStatus.completed) return false; +// // If we're being popped into, we also cannot be swiped until the pop above +// // it completes. This translates to our secondary animation being +// // dismissed. +// if (route.secondaryAnimation.status != AnimationStatus.dismissed) +// return false; +// // If we're in a gesture already, we cannot start another. +// if (isPopGestureInProgress(route)) return false; + +// // Looks like a back gesture would be welcome! +// return true; +// } + +// @override +// Widget buildPage(BuildContext context, Animation animation, +// Animation secondaryAnimation) { +// final Widget result = Semantics( +// scopesRoute: true, +// explicitChildNodes: true, +// child: (route == null ? page : route()), +// ); +// assert(() { +// if (route == null && page == null) { +// throw FlutterError.fromParts([ +// ErrorSummary( +// 'The builder for route "${settings.name}" returned null.'), +// ErrorDescription('Route builders must never return null.'), +// ]); +// } +// return true; +// }()); +// return result; +// } + +// // Called by _CupertinoBackGestureDetector when a pop ("back") drag start +// // gesture is detected. The returned controller handles all of the subsequent +// // drag events. +// static _CupertinoBackGestureController _startPopGesture( +// PageRoute route) { +// assert(_isPopGestureEnabled(route)); + +// return _CupertinoBackGestureController( +// navigator: route.navigator, +// controller: route.controller, // protected access +// ); +// } + +// /// Returns a [CupertinoFullscreenDialogTransition] if [route] is a full +// /// screen dialog, otherwise a [CupertinoPageTransition] is returned. +// /// +// /// Used by [GetRoute.buildTransitions]. +// /// +// /// This method can be applied to any [PageRoute], not just +// /// [GetRoute]. It's typically used to provide a Cupertino style +// /// horizontal transition for material widgets when the target platform +// /// is [TargetPlatform.iOS]. +// /// +// /// See also: +// /// +// /// * [CupertinoPageTransitionsBuilder], which uses this method to define a +// /// [PageTransitionsBuilder] for the [PageTransitionsTheme]. +// Widget buildPageTransitions( +// PageRoute route, +// BuildContext context, +// bool popGesture, +// Animation animation, +// Animation secondaryAnimation, +// Widget child, +// Transition tr, +// Curve curve, +// Alignment alignment, +// ) { +// Transition transition = (tr ?? Get.defaultTransition); + +// if (route.fullscreenDialog) { +// final bool linearTransition = isPopGestureInProgress(route); +// return CupertinoFullscreenDialogTransition( +// primaryRouteAnimation: animation, +// secondaryRouteAnimation: secondaryAnimation, +// child: child, +// linearTransition: linearTransition, +// ); +// } else { +// switch (transition) { +// case Transition.fade: +// final PageTransitionsBuilder matchingBuilder = +// FadeUpwardsPageTransitionsBuilder(); +// return matchingBuilder.buildTransitions( +// route, +// context, +// animation, +// secondaryAnimation, +// popGesture +// ? _CupertinoBackGestureDetector( +// enabledCallback: () => _isPopGestureEnabled(route), +// onStartPopGesture: () => _startPopGesture(route), +// child: child) +// : child); +// break; +// case Transition.rightToLeft: +// return SlideTransition( +// transformHitTests: false, +// position: new Tween( +// begin: const Offset(1.0, 0.0), +// end: Offset.zero, +// ).animate(animation), +// child: new SlideTransition( +// position: new Tween( +// begin: Offset.zero, +// end: const Offset(-1.0, 0.0), +// ).animate(secondaryAnimation), +// child: popGesture +// ? _CupertinoBackGestureDetector( +// enabledCallback: () => _isPopGestureEnabled(route), +// onStartPopGesture: () => _startPopGesture(route), +// child: child) +// : child), +// ); +// break; +// case Transition.leftToRight: +// return SlideTransition( +// transformHitTests: false, +// position: Tween( +// begin: const Offset(-1.0, 0.0), +// end: Offset.zero, +// ).animate(animation), +// child: new SlideTransition( +// position: new Tween( +// begin: Offset.zero, +// end: const Offset(1.0, 0.0), +// ).animate(secondaryAnimation), +// child: popGesture +// ? _CupertinoBackGestureDetector( +// enabledCallback: () => _isPopGestureEnabled(route), +// onStartPopGesture: () => _startPopGesture(route), +// child: child) +// : child), +// ); +// break; +// case Transition.upToDown: +// return SlideTransition( +// transformHitTests: false, +// position: Tween( +// begin: const Offset(0.0, -1.0), +// end: Offset.zero, +// ).animate(animation), +// child: new SlideTransition( +// position: new Tween( +// begin: Offset.zero, +// end: const Offset(0.0, 1.0), +// ).animate(secondaryAnimation), +// child: popGesture +// ? _CupertinoBackGestureDetector( +// enabledCallback: () => _isPopGestureEnabled(route), +// onStartPopGesture: () => _startPopGesture(route), +// child: child) +// : child), +// ); +// break; +// case Transition.downToUp: +// return SlideTransition( +// transformHitTests: false, +// position: Tween( +// begin: const Offset(0.0, 1.0), +// end: Offset.zero, +// ).animate(animation), +// child: new SlideTransition( +// position: new Tween( +// begin: Offset.zero, +// end: const Offset(0.0, -1.0), +// ).animate(secondaryAnimation), +// child: popGesture +// ? _CupertinoBackGestureDetector( +// enabledCallback: () => _isPopGestureEnabled(route), +// onStartPopGesture: () => _startPopGesture(route), +// child: child) +// : child), +// ); +// break; + +// case Transition.rightToLeftWithFade: +// return SlideTransition( +// position: Tween( +// begin: const Offset(1.0, 0.0), +// end: Offset.zero, +// ).animate(animation), +// child: FadeTransition( +// opacity: animation, +// child: SlideTransition( +// position: Tween( +// begin: Offset.zero, +// end: const Offset(-1.0, 0.0), +// ).animate(secondaryAnimation), +// child: popGesture +// ? _CupertinoBackGestureDetector( +// enabledCallback: () => _isPopGestureEnabled(route), +// onStartPopGesture: () => _startPopGesture(route), +// child: child) +// : child), +// ), +// ); +// break; +// case Transition.leftToRightWithFade: +// return SlideTransition( +// position: Tween( +// begin: const Offset(-1.0, 0.0), +// end: Offset.zero, +// ).animate(animation), +// child: FadeTransition( +// opacity: animation, +// child: SlideTransition( +// position: Tween( +// begin: Offset.zero, +// end: const Offset(1.0, 0.0), +// ).animate(secondaryAnimation), +// child: popGesture +// ? _CupertinoBackGestureDetector( +// enabledCallback: () => _isPopGestureEnabled(route), +// onStartPopGesture: () => _startPopGesture(route), +// child: child) +// : child), +// ), +// ); +// break; + +// default: +// return CupertinoPageTransition( +// primaryRouteAnimation: animation, +// secondaryRouteAnimation: secondaryAnimation, +// // Check if the route has an animation that's currently participating +// // in a back swipe gesture. +// // +// // In the middle of a back gesture drag, let the transition be linear to +// // match finger motions. +// linearTransition: isPopGestureInProgress(route), +// child: popGesture +// ? _CupertinoBackGestureDetector( +// enabledCallback: () => _isPopGestureEnabled(route), +// onStartPopGesture: () => _startPopGesture(route), +// child: child) +// : child, +// ); +// } +// } +// } + +// @override +// Widget buildTransitions(BuildContext context, Animation animation, +// Animation secondaryAnimation, Widget child) { +// if (customBuildPageTransitions != null) { +// return customBuildPageTransitions; +// } else { +// return buildPageTransitions( +// this, +// context, +// popGesture ?? GetPlatform.isIOS, +// animation, +// secondaryAnimation, +// child, +// transition, +// curve, +// alignment); +// } +// } + +// @override +// String get debugLabel => '${super.debugLabel}(${settings.name})'; +// } + +// class _CupertinoBackGestureDetector extends StatefulWidget { +// const _CupertinoBackGestureDetector({ +// Key key, +// @required this.enabledCallback, +// @required this.onStartPopGesture, +// @required this.child, +// }) : assert(enabledCallback != null), +// assert(onStartPopGesture != null), +// assert(child != null), +// super(key: key); + +// final Widget child; + +// final ValueGetter enabledCallback; + +// final ValueGetter<_CupertinoBackGestureController> onStartPopGesture; + +// @override +// _CupertinoBackGestureDetectorState createState() => +// _CupertinoBackGestureDetectorState(); +// } + +// class _CupertinoBackGestureDetectorState +// extends State<_CupertinoBackGestureDetector> { +// _CupertinoBackGestureController _backGestureController; + +// HorizontalDragGestureRecognizer _recognizer; + +// @override +// void initState() { +// super.initState(); +// _recognizer = HorizontalDragGestureRecognizer(debugOwner: this) +// ..onStart = _handleDragStart +// ..onUpdate = _handleDragUpdate +// ..onEnd = _handleDragEnd +// ..onCancel = _handleDragCancel; +// } + +// @override +// void dispose() { +// _recognizer.dispose(); +// super.dispose(); +// } + +// void _handleDragStart(DragStartDetails details) { +// assert(mounted); +// assert(_backGestureController == null); +// _backGestureController = widget.onStartPopGesture(); +// } + +// void _handleDragUpdate(DragUpdateDetails details) { +// assert(mounted); +// assert(_backGestureController != null); +// _backGestureController.dragUpdate( +// _convertToLogical(details.primaryDelta / context.size.width)); +// } + +// void _handleDragEnd(DragEndDetails details) { +// assert(mounted); +// assert(_backGestureController != null); +// _backGestureController.dragEnd(_convertToLogical( +// details.velocity.pixelsPerSecond.dx / context.size.width)); +// _backGestureController = null; +// } + +// void _handleDragCancel() { +// assert(mounted); +// // This can be called even if start is not called, paired with the "down" event +// // that we don't consider here. +// _backGestureController?.dragEnd(0.0); +// _backGestureController = null; +// } + +// void _handlePointerDown(PointerDownEvent event) { +// if (widget.enabledCallback()) _recognizer.addPointer(event); +// } + +// double _convertToLogical(double value) { +// switch (Directionality.of(context)) { +// case TextDirection.rtl: +// return -value; +// case TextDirection.ltr: +// return value; +// } +// return null; +// } + +// @override +// Widget build(BuildContext context) { +// assert(debugCheckHasDirectionality(context)); +// // For devices with notches, the drag area needs to be larger on the side +// // that has the notch. +// double dragAreaWidth = Directionality.of(context) == TextDirection.ltr +// ? MediaQuery.of(context).padding.left +// : MediaQuery.of(context).padding.right; +// dragAreaWidth = max(dragAreaWidth, _kBackGestureWidth); +// return Stack( +// fit: StackFit.passthrough, +// children: [ +// widget.child, +// PositionedDirectional( +// start: 0.0, +// width: dragAreaWidth, +// top: 0.0, +// bottom: 0.0, +// child: Listener( +// onPointerDown: _handlePointerDown, +// behavior: HitTestBehavior.translucent, +// ), +// ), +// ], +// ); +// } +// } + +// class _CupertinoBackGestureController { +// /// Creates a controller for an iOS-style back gesture. +// /// +// /// The [navigator] and [controller] arguments must not be null. +// _CupertinoBackGestureController({ +// @required this.navigator, +// @required this.controller, +// }) : assert(navigator != null), +// assert(controller != null) { +// navigator.didStartUserGesture(); +// } + +// final AnimationController controller; +// final NavigatorState navigator; + +// /// The drag gesture has changed by [fractionalDelta]. The total range of the +// /// drag should be 0.0 to 1.0. +// void dragUpdate(double delta) { +// controller.value -= delta; +// } + +// /// The drag gesture has ended with a horizontal motion of +// /// [fractionalVelocity] as a fraction of screen width per second. +// void dragEnd(double velocity) { +// // Fling in the appropriate direction. +// // AnimationController.fling is guaranteed to +// // take at least one frame. +// // +// // This curve has been determined through rigorously eyeballing native iOS +// // animations. +// const Curve animationCurve = Curves.fastLinearToSlowEaseIn; +// bool animateForward; + +// // If the user releases the page before mid screen with sufficient velocity, +// // or after mid screen, we should animate the page out. Otherwise, the page +// // should be animated back in. +// if (velocity.abs() >= _kMinFlingVelocity) +// animateForward = velocity <= 0; +// else +// animateForward = controller.value > 0.5; + +// if (animateForward) { +// // The closer the panel is to dismissing, the shorter the animation is. +// // We want to cap the animation time, but we want to use a linear curve +// // to determine it. +// final int droppedPageForwardAnimationTime = min( +// lerpDouble( +// _kMaxDroppedSwipePageForwardAnimationTime, 0, controller.value) +// .floor(), +// _kMaxPageBackAnimationTime, +// ); +// controller.animateTo(1.0, +// duration: Duration(milliseconds: droppedPageForwardAnimationTime), +// curve: animationCurve); +// } else { +// // This route is destined to pop at this point. Reuse navigator's pop. +// navigator.pop(); + +// // The popping may have finished inline if already at the target destination. +// if (controller.isAnimating) { +// // Otherwise, use a custom popping animation duration and curve. +// final int droppedPageBackAnimationTime = lerpDouble( +// 0, _kMaxDroppedSwipePageForwardAnimationTime, controller.value) +// .floor(); +// controller.animateBack(0.0, +// duration: Duration(milliseconds: droppedPageBackAnimationTime), +// curve: animationCurve); +// } +// } + +// if (controller.isAnimating) { +// // Keep the userGestureInProgress in true state so we don't change the +// // curve of the page transition mid-flight since CupertinoPageTransition +// // depends on userGestureInProgress. +// AnimationStatusListener animationStatusCallback; +// animationStatusCallback = (AnimationStatus status) { +// navigator.didStopUserGesture(); +// controller.removeStatusListener(animationStatusCallback); +// }; +// controller.addStatusListener(animationStatusCallback); +// } else { +// navigator.didStopUserGesture(); +// } +// } +// } diff --git a/lib/src/routes/default_transitions.dart b/lib/src/routes/default_transitions.dart new file mode 100644 index 000000000..14196b7b9 --- /dev/null +++ b/lib/src/routes/default_transitions.dart @@ -0,0 +1,208 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'transitions_component.dart'; + +class LeftToRightFadeTransition extends TransitionInterface { + LeftToRightFadeTransition({ + TransitionComponent transitionComponent, + }) : super(transitionComponent: transitionComponent); + + @override + Widget buildChildWithTransition( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { + return SlideTransition( + position: Tween( + begin: const Offset(-1.0, 0.0), + end: Offset.zero, + ).animate(animation), + child: FadeTransition( + opacity: animation, + child: SlideTransition( + position: Tween( + begin: Offset.zero, + end: const Offset(1.0, 0.0), + ).animate(secondaryAnimation), + child: child), + ), + ); + } +} + +class RightToLeftFadeTransition extends TransitionInterface { + RightToLeftFadeTransition({ + TransitionComponent transitionComponent, + }) : super(transitionComponent: transitionComponent); + + @override + Widget buildChildWithTransition( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { + return SlideTransition( + position: Tween( + begin: const Offset(1.0, 0.0), + end: Offset.zero, + ).animate(animation), + child: FadeTransition( + opacity: animation, + child: SlideTransition( + position: Tween( + begin: Offset.zero, + end: const Offset(-1.0, 0.0), + ).animate(secondaryAnimation), + child: child), + ), + ); + } +} + +class NoTransition extends TransitionInterface { + NoTransition({ + TransitionComponent transitionComponent, + }) : super(transitionComponent: transitionComponent); + + @override + Widget buildChildWithTransition( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { + return transitionComponent.buildChildWithTransition( + context, animation, secondaryAnimation, child); + } +} + +class FadeInTransition extends TransitionInterface { + FadeInTransition({ + TransitionComponent transitionComponent, + }) : super(transitionComponent: transitionComponent); + + @override + Widget buildChildWithTransition( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { + return FadeTransition( + opacity: animation, + child: transitionComponent.buildChildWithTransition( + context, animation, secondaryAnimation, child), + ); + } +} + +class SlideDownTransition extends TransitionInterface { + SlideDownTransition({ + TransitionComponent transitionComponent, + }) : super(transitionComponent: transitionComponent); + + @override + Widget buildChildWithTransition( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { + return SlideTransition( + position: Tween( + begin: Offset(0.0, 1.0), + end: Offset.zero, + ).animate(animation), + child: transitionComponent.buildChildWithTransition( + context, animation, secondaryAnimation, child), + ); + } +} + +class SlideLeftTransition extends TransitionInterface { + SlideLeftTransition({ + TransitionComponent transitionComponent, + }) : super(transitionComponent: transitionComponent); + + @override + Widget buildChildWithTransition( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { + return SlideTransition( + position: Tween( + begin: Offset(-1.0, 0.0), + end: Offset.zero, + ).animate(animation), + child: transitionComponent.buildChildWithTransition( + context, animation, secondaryAnimation, child), + ); + } +} + +class SlideRightTransition extends TransitionInterface { + SlideRightTransition({ + TransitionComponent transitionComponent, + }) : super(transitionComponent: transitionComponent); + + @override + Widget buildChildWithTransition( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { + return SlideTransition( + position: Tween( + begin: Offset(1.0, 0.0), + end: Offset.zero, + ).animate(animation), + child: transitionComponent.buildChildWithTransition( + context, animation, secondaryAnimation, child), + ); + } +} + +class SlideTopTransition extends TransitionInterface { + SlideTopTransition({ + TransitionComponent transitionComponent, + }) : super(transitionComponent: transitionComponent); + + @override + Widget buildChildWithTransition( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { + return SlideTransition( + position: Tween( + begin: Offset(0.0, -1.0), + end: Offset.zero, + ).animate(animation), + child: transitionComponent.buildChildWithTransition( + context, animation, secondaryAnimation, child), + ); + } +} + +class ZoomInTransition extends TransitionInterface { + ZoomInTransition({ + TransitionComponent transitionComponent, + }) : super(transitionComponent: transitionComponent); + + @override + Widget buildChildWithTransition( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { + return ScaleTransition( + scale: animation, + child: transitionComponent.buildChildWithTransition( + context, animation, secondaryAnimation, child), + ); + } +} + +abstract class TransitionInterface implements TransitionComponent { + TransitionComponent transitionComponent; + TransitionInterface({this.transitionComponent}); +} diff --git a/lib/src/routes/get_route.dart b/lib/src/routes/get_route.dart index 82522c4fa..7c0ac0fcc 100644 --- a/lib/src/routes/get_route.dart +++ b/lib/src/routes/get_route.dart @@ -2,47 +2,6 @@ import 'package:flutter/widgets.dart'; import 'package:get/src/routes/bindings_interface.dart'; import 'transitions_type.dart'; -class GetRoute { - final Widget page; - final String name; - final bool popGesture; - final Map parameter; - final String title; - final Transition transition; - final Curve curve; - final Alignment alignment; - final bool maintainState; - final GetPageBuilder route; - final bool opaque; - final Bindings binding; - final List bindings; - final Widget customTransition; - final Duration transitionDuration; - final bool fullscreenDialog; - final RouteSettings settings; - - const GetRoute({ - @required this.page, - this.title, - this.name, - this.settings, - this.maintainState = true, - this.curve = Curves.linear, - this.alignment, - this.route, - this.parameter, - this.opaque = true, - this.transitionDuration = const Duration(milliseconds: 400), - this.popGesture, - this.binding, - this.bindings, - this.transition, - this.customTransition, - this.fullscreenDialog = false, - }) : assert(page != null), - assert(maintainState != null), - assert(fullscreenDialog != null); -} class GetPage { final String name; diff --git a/lib/src/routes/transitions_component.dart b/lib/src/routes/transitions_component.dart new file mode 100644 index 000000000..d20921547 --- /dev/null +++ b/lib/src/routes/transitions_component.dart @@ -0,0 +1,11 @@ +import 'package:flutter/widgets.dart'; + +class TransitionComponent { + Widget buildChildWithTransition( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { + return child; + } +} diff --git a/lib/src/routes/transitions_filter.dart b/lib/src/routes/transitions_filter.dart new file mode 100644 index 000000000..902390e8b --- /dev/null +++ b/lib/src/routes/transitions_filter.dart @@ -0,0 +1,40 @@ +import 'default_transitions.dart'; +import 'transitions_component.dart'; +import 'transitions_type.dart'; + +class TransitionFilter { + static TransitionComponent newTransitionComponent( + Transition transition, + ) { + TransitionComponent transitionComponent = TransitionComponent(); + switch (transition) { + case Transition.leftToRight: + return SlideLeftTransition(transitionComponent: transitionComponent); + + case Transition.downToUp: + return SlideDownTransition(transitionComponent: transitionComponent); + + case Transition.upToDown: + return SlideTopTransition(transitionComponent: transitionComponent); + + case Transition.rightToLeft: + return SlideRightTransition(transitionComponent: transitionComponent); + + case Transition.zoom: + return ZoomInTransition(transitionComponent: transitionComponent); + + case Transition.fadeIn: + return FadeInTransition(transitionComponent: transitionComponent); + + case Transition.rightToLeftWithFade: + return RightToLeftFadeTransition( + transitionComponent: transitionComponent); + + case Transition.leftToRightWithFade: + return LeftToRightFadeTransition( + transitionComponent: transitionComponent); + default: + return FadeInTransition(transitionComponent: transitionComponent); + } + } +} diff --git a/lib/src/routes/transitions_type.dart b/lib/src/routes/transitions_type.dart index 86e1fe734..84f6c3ad6 100644 --- a/lib/src/routes/transitions_type.dart +++ b/lib/src/routes/transitions_type.dart @@ -2,17 +2,16 @@ import 'package:flutter/widgets.dart'; enum Transition { fade, + fadeIn, rightToLeft, leftToRight, upToDown, downToUp, - scale, - rotate, - size, rightToLeftWithFade, leftToRightWithFade, - cupertino, - custom + zoom, + noTransition, + native } typedef GetPageBuilder = Widget Function(); diff --git a/lib/src/routes/utils/parse_arguments.dart b/lib/src/routes/utils/parse_arguments.dart deleted file mode 100644 index 6609426c6..000000000 --- a/lib/src/routes/utils/parse_arguments.dart +++ /dev/null @@ -1,197 +0,0 @@ -import 'package:flutter/material.dart'; - -class ParseRoute { - final List _routeSplits = []; - - void addRoute(String routePath) { - String path = routePath; - - if (path == Navigator.defaultRouteName) { - var routeSplit = ParseRouteSplit(path, ParseRouteSplitType.component); - routeSplit.routes = [routePath]; - _routeSplits.add(routeSplit); - return; - } - if (path.startsWith("/")) { - path = path.substring(1); - } - List pathComponents = path.split('/'); - ParseRouteSplit parent; - for (int i = 0; i < pathComponents.length; i++) { - String component = pathComponents[i]; - ParseRouteSplit routeSplit = _routeSplitForComponent(component, parent); - if (routeSplit == null) { - ParseRouteSplitType type = _typeForComponent(component); - routeSplit = ParseRouteSplit(component, type); - routeSplit.parent = parent; - if (parent == null) { - _routeSplits.add(routeSplit); - } else { - parent.routeSplits.add(routeSplit); - } - } - if (i == pathComponents.length - 1) { - if (routeSplit.routes == null) { - routeSplit.routes = [routePath]; - } else { - routeSplit.routes.add(routePath); - } - } - parent = routeSplit; - } - } - - AppRouteMatch split(String path) { - String usePath = path; - if (usePath.startsWith("/")) { - usePath = path.substring(1); - } - List components = usePath.split("/"); - if (path == Navigator.defaultRouteName) { - components = ["/"]; - } - - Map routeSplitMatches = - {}; - List routeSplitsToCheck = _routeSplits; - for (String checkComponent in components) { - Map currentMatches = - {}; - List nextrouteSplits = []; - for (ParseRouteSplit routeSplit in routeSplitsToCheck) { - String pathPart = checkComponent; - Map queryMap = {}; - if (checkComponent.contains("?") && !checkComponent.contains("=")) { - var splitParam = checkComponent.split("?"); - pathPart = splitParam[0]; - queryMap = {pathPart: splitParam[1]}; - } else if (checkComponent.contains("?")) { - var splitParam = checkComponent.split("?"); - var splitParam2 = splitParam[1].split("="); - if (!splitParam2[1].contains("&")) { - pathPart = splitParam[0]; - queryMap = {splitParam2[0]: splitParam2[1]}; - } else { - pathPart = splitParam[0]; - final segunda = splitParam[1]; - var other = segunda.split(RegExp(r"[&,=]")); - for (var i = 0; i < (other.length - 1); i++) { - bool impar = (i % 2 == 0); - if (impar) { - queryMap.addAll({other[0 + i]: other[1 + i]}); - } - } - } - } - bool isMatch = - (routeSplit.part == pathPart || routeSplit.isParameter()); - if (isMatch) { - ParseRouteSplitMatch parentMatch = - routeSplitMatches[routeSplit.parent]; - ParseRouteSplitMatch match = - ParseRouteSplitMatch.fromMatch(parentMatch, routeSplit); - if (routeSplit.isParameter()) { - String paramKey = routeSplit.part.substring(1); - match.parameters[paramKey] = pathPart; - } - if (queryMap != null) { - match.parameters.addAll(queryMap); - } - - currentMatches[routeSplit] = match; - if (routeSplit.routeSplits != null) { - nextrouteSplits.addAll(routeSplit.routeSplits); - } - } - } - routeSplitMatches = currentMatches; - routeSplitsToCheck = nextrouteSplits; - if (currentMatches.values.length == 0) { - return null; - } - } - List matches = routeSplitMatches.values.toList(); - if (matches.length > 0) { - ParseRouteSplitMatch match = matches.first; - ParseRouteSplit routeSplitToUse = match.routeSplit; - - if (routeSplitToUse != null && - routeSplitToUse.routes != null && - routeSplitToUse.routes.length > 0) { - AppRouteMatch routeMatch = AppRouteMatch(); - routeMatch.parameters = match.parameters; - if (routeSplitToUse.isParameter()) { - routeMatch.route = match.routeSplit.parent.part; - } else { - routeMatch.route = match.routeSplit.part; - } - return routeMatch; - } - } - return null; - } - - ParseRouteSplit _routeSplitForComponent( - String component, ParseRouteSplit parent) { - List routeSplits = _routeSplits; - if (parent != null) { - routeSplits = parent.routeSplits; - } - for (ParseRouteSplit routeSplit in routeSplits) { - if (routeSplit.part == component) { - return routeSplit; - } - } - return null; - } - - ParseRouteSplitType _typeForComponent(String component) { - ParseRouteSplitType type = ParseRouteSplitType.component; - if (_isParameterComponent(component)) { - type = ParseRouteSplitType.parameter; - } - return type; - } - - bool _isParameterComponent(String component) { - return component.startsWith(":"); - } -} - -enum ParseRouteSplitType { - component, - parameter, -} - -class AppRouteMatch { - Map parameters = {}; - String route = '/'; -} - -class ParseRouteSplitMatch { - ParseRouteSplitMatch(this.routeSplit); - - ParseRouteSplitMatch.fromMatch(ParseRouteSplitMatch match, this.routeSplit) { - parameters = {}; - if (match != null) { - parameters.addAll(match.parameters); - } - } - - ParseRouteSplit routeSplit; - Map parameters = {}; -} - -class ParseRouteSplit { - ParseRouteSplit(this.part, this.type); - - String part; - ParseRouteSplitType type; - List routes = []; - List routeSplits = []; - ParseRouteSplit parent; - - bool isParameter() { - return type == ParseRouteSplitType.parameter; - } -} diff --git a/lib/src/rx/rx_callbacks.dart b/lib/src/rx/rx_callbacks.dart index 7da35f763..4b1dfe402 100644 --- a/lib/src/rx/rx_callbacks.dart +++ b/lib/src/rx/rx_callbacks.dart @@ -1 +1 @@ -typedef void ValueCallback(T v); +typedef ValueCallback = Function(T v); diff --git a/lib/src/rx/rx_getbuilder.dart b/lib/src/rx/rx_getbuilder.dart index 883d0f1b3..ccdb8a3ef 100644 --- a/lib/src/rx/rx_getbuilder.dart +++ b/lib/src/rx/rx_getbuilder.dart @@ -12,6 +12,7 @@ class GetX extends StatefulWidget { final bool autoRemove; final bool assignId; final void Function(State state) initState, dispose, didChangeDependencies; + final void Function(GetX oldWidget, State state) didUpdateWidget; final T init; const GetX({ this.builder, @@ -22,6 +23,7 @@ class GetX extends StatefulWidget { // this.stream, this.dispose, this.didChangeDependencies, + this.didUpdateWidget, this.init, // this.streamController }); @@ -65,6 +67,20 @@ class GetImplXState extends State> { super.initState(); } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (widget.didChangeDependencies != null) { + widget.didChangeDependencies(this); + } + } + + @override + void didUpdateWidget(GetX oldWidget) { + super.didUpdateWidget(oldWidget as GetX); + if (widget.didUpdateWidget != null) widget.didUpdateWidget(oldWidget, this); + } + @override void dispose() { if (widget.dispose != null) widget.dispose(this); diff --git a/lib/src/rx/rx_impl.dart b/lib/src/rx/rx_impl.dart index 300bc36d7..9a50fc638 100644 --- a/lib/src/rx/rx_impl.dart +++ b/lib/src/rx/rx_impl.dart @@ -51,25 +51,26 @@ class _RxImpl implements RxInterface { Stream map(R mapper(T data)) => stream.map(mapper); } -class StringX extends _RxImpl { - StringX([String initial]) { +class RxString extends _RxImpl { + RxString([String initial]) { _value = initial; } } -class IntX extends _RxImpl { - IntX([int initial]) { +class RxInt extends _RxImpl { + RxInt([int initial]) { _value = initial; } } -class MapX extends RxInterface implements Map { - MapX([Map initial]) { +class RxMap extends RxInterface implements Map { + RxMap([Map initial]) { _value = initial; } + @override StreamController subject = StreamController>.broadcast(); - Map>, StreamSubscription> _subscriptions = Map(); + final Map>, StreamSubscription> _subscriptions = {}; Map _value; Map get value { @@ -81,7 +82,8 @@ class MapX extends RxInterface implements Map { String get string => value.toString(); - close() { + @override + void close() { _subscriptions.forEach((observable, subscription) { subscription.cancel(); }); @@ -89,7 +91,8 @@ class MapX extends RxInterface implements Map { subject.close(); } - addListener(Stream rxGetx) { + @override + void addListener(Stream rxGetx) { if (_subscriptions.containsKey(rxGetx)) { return; } @@ -118,7 +121,7 @@ class MapX extends RxInterface implements Map { subject.add(_value); } - void addIf(/* bool | Condition */ condition, K key, V value) { + void addIf(condition, K key, V value) { if (condition is Condition) condition = condition(); if (condition is bool && condition) { _value[key] = value; @@ -126,7 +129,7 @@ class MapX extends RxInterface implements Map { } } - void addAllIf(/* bool | Condition */ condition, Map values) { + void addAllIf(condition, Map values) { if (condition is Condition) condition = condition(); if (condition is bool && condition) addAll(values); } @@ -234,8 +237,8 @@ class MapX extends RxInterface implements Map { } /// Create a list similar to `List` -class ListX extends Iterable implements RxInterface { - ListX([List initial]) { +class RxList extends Iterable implements RxInterface { + RxList([List initial]) { _list = initial; } @@ -411,20 +414,20 @@ typedef bool Condition(); typedef E ChildrenListComposer(S value); -class BoolX extends _RxImpl { - BoolX([bool initial]) { +class RxBool extends _RxImpl { + RxBool([bool initial]) { _value = initial; } } -class DoubleX extends _RxImpl { - DoubleX([double initial]) { +class RxDouble extends _RxImpl { + RxDouble([double initial]) { _value = initial; } } -class NumX extends _RxImpl { - NumX([num initial]) { +class RxNum extends _RxImpl { + RxNum([num initial]) { _value = initial; } } @@ -436,36 +439,36 @@ class Rx extends _RxImpl { } extension StringExtension on String { - StringX get obs => StringX(this); + RxString get obs => RxString(this); } extension IntExtension on int { - IntX get obs => IntX(this); + RxInt get obs => RxInt(this); } extension DoubleExtension on double { - DoubleX get obs => DoubleX(this); + RxDouble get obs => RxDouble(this); } extension BoolExtension on bool { - BoolX get obs => BoolX(this); + RxBool get obs => RxBool(this); } extension MapExtension on Map { - MapX get obs { + RxMap get obs { if (this != null) - return MapX({})..addAll(this); + return RxMap({})..addAll(this); else - return MapX(null); + return RxMap(null); } } extension ListExtension on List { - ListX get obs { + RxList get obs { if (this != null) - return ListX([])..addAllNonNull(this); + return RxList([])..addAllNonNull(this); else - return ListX(null); + return RxList(null); } } diff --git a/lib/src/rx/rx_interface.dart b/lib/src/rx/rx_interface.dart index aea2104a3..06f80aed9 100644 --- a/lib/src/rx/rx_interface.dart +++ b/lib/src/rx/rx_interface.dart @@ -25,17 +25,6 @@ abstract class RxInterface { // Stream map(S mapper(T data)); } -class RxController extends DisposableInterface { - @override - void onInit() async {} - - @override - void onReady() async {} - - @override - void onClose() async {} -} - abstract class DisposableInterface { /// Called at the exact moment that the widget is allocated in memory. /// Do not overwrite this method. @@ -53,5 +42,5 @@ abstract class DisposableInterface { /// Called before the onDelete method. onClose is used to close events /// before the controller is destroyed, such as closing streams, for example. - onClose() async {} + onClose() async {} } diff --git a/lib/src/rx/rx_listview_builder.dart b/lib/src/rx/rx_listview_builder.dart deleted file mode 100644 index 19e9d60d0..000000000 --- a/lib/src/rx/rx_listview_builder.dart +++ /dev/null @@ -1,84 +0,0 @@ -// import 'dart:async'; -// import 'package:flutter/widgets.dart'; -// import 'package:get/src/get_main.dart'; -// import 'rx_impl.dart'; -// import 'rx_interface.dart'; - -// class ListViewX extends StatefulWidget { -// final Widget Function(T, int) builder; -// final bool global; -// final ListX Function(T) list; -// final bool autoRemove; -// final void Function(State state) initState, dispose, didChangeDependencies; -// final T init; -// final int itemCount; -// ListViewX({ -// this.builder, -// this.global = true, -// this.autoRemove = true, -// this.initState, -// @required this.list, -// this.itemCount, -// this.dispose, -// this.didChangeDependencies, -// this.init, -// }); -// _ListViewXState createState() => _ListViewXState(); -// } - -// class _ListViewXState extends State> { -// RxInterface _observer; -// StreamSubscription _listenSubscription; -// T controller; - -// _ListViewXState() { -// _observer = ListX(); -// } - -// @override -// void initState() { -// if (widget.global) { -// if (Get.isRegistred()) { -// controller = Get.find(); -// } else { -// controller = widget.init; -// Get.put(controller); -// } -// } else { -// controller = widget.init; -// } -// if (widget.initState != null) widget.initState(this); -// try { -// controller?.onInit(); -// } catch (e) { -// if (Get.isLogEnable) print("Failure on call onInit"); -// } - -// _listenSubscription = widget.list.call(controller).listen((data) { -// setState(() {}); -// }); -// super.initState(); -// } - -// @override -// void dispose() { -// controller?.onClose(); -// _listenSubscription?.cancel(); -// _observer?.close(); -// super.dispose(); -// } - -// @override -// Widget build(BuildContext context) { -// _observer.close(); -// final observer = Get.obs; -// Get.obs = this._observer; -// final result = ListView.builder( -// itemCount: widget.itemCount ?? widget.list.call(controller).length, -// itemBuilder: (context, index) { -// return widget.builder(controller, index); -// }); -// Get.obs = observer; -// return result; -// } -// } diff --git a/lib/src/rx/utils/debouncer.dart b/lib/src/rx/utils/debouncer.dart index f05061ec5..ee71fdc1f 100644 --- a/lib/src/rx/utils/debouncer.dart +++ b/lib/src/rx/utils/debouncer.dart @@ -6,7 +6,7 @@ class Debouncer { Debouncer({this.delay}); - call(Function action) { + call(void Function() action) { _timer?.cancel(); _timer = Timer(delay, action); } diff --git a/lib/src/rx/utils/delegate_list.dart b/lib/src/rx/utils/delegate_list.dart deleted file mode 100644 index eb65523b6..000000000 --- a/lib/src/rx/utils/delegate_list.dart +++ /dev/null @@ -1,197 +0,0 @@ -import 'dart:math' as math; - -class DelegatingList extends DelegatingIterable implements List { - const DelegatingList(List base) : super(base); - - List get _listBase => _base; - - E operator [](int index) => _listBase[index]; - - void operator []=(int index, E value) { - _listBase[index] = value; - } - - List operator +(List other) => _listBase + other; - - void add(E value) { - _listBase.add(value); - } - - void addAll(Iterable iterable) { - _listBase.addAll(iterable); - } - - Map asMap() => _listBase.asMap(); - - List cast() => _listBase.cast(); - - void clear() { - _listBase.clear(); - } - - void fillRange(int start, int end, [E fillValue]) { - _listBase.fillRange(start, end, fillValue); - } - - set first(E value) { - if (this.isEmpty) throw RangeError.index(0, this); - this[0] = value; - } - - Iterable getRange(int start, int end) => _listBase.getRange(start, end); - - int indexOf(E element, [int start = 0]) => _listBase.indexOf(element, start); - - int indexWhere(bool test(E element), [int start = 0]) => - _listBase.indexWhere(test, start); - - void insert(int index, E element) { - _listBase.insert(index, element); - } - - insertAll(int index, Iterable iterable) { - _listBase.insertAll(index, iterable); - } - - set last(E value) { - if (this.isEmpty) throw RangeError.index(0, this); - this[this.length - 1] = value; - } - - int lastIndexOf(E element, [int start]) => - _listBase.lastIndexOf(element, start); - - int lastIndexWhere(bool test(E element), [int start]) => - _listBase.lastIndexWhere(test, start); - - set length(int newLength) { - _listBase.length = newLength; - } - - bool remove(Object value) => _listBase.remove(value); - - E removeAt(int index) => _listBase.removeAt(index); - - E removeLast() => _listBase.removeLast(); - - void removeRange(int start, int end) { - _listBase.removeRange(start, end); - } - - void removeWhere(bool test(E element)) { - _listBase.removeWhere(test); - } - - void replaceRange(int start, int end, Iterable iterable) { - _listBase.replaceRange(start, end, iterable); - } - - void retainWhere(bool test(E element)) { - _listBase.retainWhere(test); - } - - @deprecated - List retype() => cast(); - - Iterable get reversed => _listBase.reversed; - - void setAll(int index, Iterable iterable) { - _listBase.setAll(index, iterable); - } - - void setRange(int start, int end, Iterable iterable, [int skipCount = 0]) { - _listBase.setRange(start, end, iterable, skipCount); - } - - void shuffle([math.Random random]) { - _listBase.shuffle(random); - } - - void sort([int compare(E a, E b)]) { - _listBase.sort(compare); - } - - List sublist(int start, [int end]) => _listBase.sublist(start, end); -} - -class DelegatingIterable extends _DelegatingIterableBase { - final Iterable _base; - - const DelegatingIterable(Iterable base) : _base = base; -} - -abstract class _DelegatingIterableBase implements Iterable { - Iterable get _base; - - const _DelegatingIterableBase(); - - bool any(bool test(E element)) => _base.any(test); - - Iterable cast() => _base.cast(); - - bool contains(Object element) => _base.contains(element); - - E elementAt(int index) => _base.elementAt(index); - - bool every(bool test(E element)) => _base.every(test); - - Iterable expand(Iterable f(E element)) => _base.expand(f); - - E get first => _base.first; - - E firstWhere(bool test(E element), {E orElse()}) => - _base.firstWhere(test, orElse: orElse); - - T fold(T initialValue, T combine(T previousValue, E element)) => - _base.fold(initialValue, combine); - - Iterable followedBy(Iterable other) => _base.followedBy(other); - - void forEach(void f(E element)) => _base.forEach(f); - - bool get isEmpty => _base.isEmpty; - - bool get isNotEmpty => _base.isNotEmpty; - - Iterator get iterator => _base.iterator; - - String join([String separator = ""]) => _base.join(separator); - - E get last => _base.last; - - E lastWhere(bool test(E element), {E orElse()}) => - _base.lastWhere(test, orElse: orElse); - - int get length => _base.length; - - Iterable map(T f(E element)) => _base.map(f); - - E reduce(E combine(E value, E element)) => _base.reduce(combine); - - @deprecated - Iterable retype() => cast(); - - E get single => _base.single; - - E singleWhere(bool test(E element), {E orElse()}) { - return _base.singleWhere(test, orElse: orElse); - } - - Iterable skip(int n) => _base.skip(n); - - Iterable skipWhile(bool test(E value)) => _base.skipWhile(test); - - Iterable take(int n) => _base.take(n); - - Iterable takeWhile(bool test(E value)) => _base.takeWhile(test); - - List toList({bool growable = true}) => _base.toList(growable: growable); - - Set toSet() => _base.toSet(); - - Iterable where(bool test(E element)) => _base.where(test); - - Iterable whereType() => _base.whereType(); - - String toString() => _base.toString(); -} diff --git a/lib/src/snackbar/snack.dart b/lib/src/snackbar/snack.dart index 2a374fa0e..a79873dee 100644 --- a/lib/src/snackbar/snack.dart +++ b/lib/src/snackbar/snack.dart @@ -215,7 +215,7 @@ class GetBar extends StatefulWidget { Future show() async { _snackRoute = route.showSnack( snack: this, - ); + ) as SnackRoute; return await Get.key.currentState.push(_snackRoute); } diff --git a/lib/src/state/get_state.dart b/lib/src/state/get_state.dart index a922889c9..f3815122b 100644 --- a/lib/src/state/get_state.dart +++ b/lib/src/state/get_state.dart @@ -3,8 +3,8 @@ import 'package:get/src/get_instance.dart'; import 'package:get/src/root/smart_management.dart'; import 'package:get/src/rx/rx_interface.dart'; -class GetController extends DisposableInterface { - List _updaters = []; +class GetxController extends DisposableInterface { + final List _updaters = []; /// Update GetBuilder with update(); void update([List ids, bool condition = true]) { @@ -18,15 +18,17 @@ class GetController extends DisposableInterface { .forEach((rs) => rs.updater(() {})); } + @override void onInit() async {} + @override void onReady() async {} + @override void onClose() async {} } -class GetBuilder extends StatefulWidget { - @required +class GetBuilder extends StatefulWidget { final Widget Function(T) builder; final bool global; final String id; @@ -40,7 +42,7 @@ class GetBuilder extends StatefulWidget { Key key, this.init, this.global = true, - this.builder, + @required this.builder, this.autoRemove = true, this.assignId = false, this.initState, @@ -55,7 +57,7 @@ class GetBuilder extends StatefulWidget { _GetBuilderState createState() => _GetBuilderState(); } -class _GetBuilderState extends State> { +class _GetBuilderState extends State> { T controller; Updater real; bool isCreator = false; @@ -64,8 +66,8 @@ class _GetBuilderState extends State> { super.initState(); if (widget.global) { - bool isPrepared = GetInstance().isPrepared(tag: widget.tag); - bool isRegistred = GetInstance().isRegistred(tag: widget.tag); + final isPrepared = GetInstance().isPrepared(tag: widget.tag); + final isRegistred = GetInstance().isRegistred(tag: widget.tag); if (isPrepared) { if (GetConfig.smartManagement != SmartManagement.keepFactory) { @@ -116,13 +118,14 @@ class _GetBuilderState extends State> { @override void didChangeDependencies() { super.didChangeDependencies(); - if (widget.didChangeDependencies != null) + if (widget.didChangeDependencies != null) { widget.didChangeDependencies(this); + } } @override void didUpdateWidget(GetBuilder oldWidget) { - super.didUpdateWidget(oldWidget); + super.didUpdateWidget(oldWidget as GetBuilder); if (widget.didUpdateWidget != null) widget.didUpdateWidget(oldWidget, this); } diff --git a/lib/src/state/get_view.dart b/lib/src/state/get_view.dart new file mode 100644 index 000000000..502a3f10d --- /dev/null +++ b/lib/src/state/get_view.dart @@ -0,0 +1,10 @@ +import 'package:flutter/widgets.dart'; +import 'package:get/src/get_instance.dart'; + +abstract class GetView extends StatelessWidget { + const GetView({Key key}) : super(key: key); + T get controller => GetInstance().find(); + + @override + Widget build(BuildContext context); +} diff --git a/lib/src/state/mixin_state.dart b/lib/src/state/mixin_state.dart index 7283de031..a79398d41 100644 --- a/lib/src/state/mixin_state.dart +++ b/lib/src/state/mixin_state.dart @@ -2,7 +2,7 @@ import 'package:flutter/widgets.dart'; import 'package:get/src/rx/rx_obx.dart'; import 'get_state.dart'; -class MixinBuilder extends StatelessWidget { +class MixinBuilder extends StatelessWidget { @required final Widget Function(T) builder; final bool global; diff --git a/pubspec.lock b/pubspec.lock index 1b57d366b..ee9ad8e74 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -15,6 +15,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" charcode: dependency: transitive description: @@ -35,7 +42,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.12" + version: "1.14.13" fake_async: dependency: transitive description: @@ -59,7 +66,7 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.6" + version: "0.12.8" meta: dependency: transitive description: @@ -120,7 +127,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.16" + version: "0.2.17" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 28df866a5..95de52638 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: get description: Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with Get. -version: 2.14.0 +version: 3.0.1 homepage: https://github.com/jonataslaw/get environment: diff --git a/test/get_main_test.dart b/test/get_main_test.dart index 8f1abef6c..e727332a2 100644 --- a/test/get_main_test.dart +++ b/test/get_main_test.dart @@ -242,7 +242,7 @@ void main() { await tester.pumpWidget( Wrapper( child: Container(), - defaultTransition: Transition.cupertino, + defaultTransition: Transition.fadeIn, ), ); diff --git a/test/get_mixin_state_test.dart b/test/get_mixin_state_test.dart index 58ad961a3..148b0ea31 100644 --- a/test/get_mixin_state_test.dart +++ b/test/get_mixin_state_test.dart @@ -77,7 +77,7 @@ void main() { ); } -class Controller extends GetController { +class Controller extends GetxController { static Controller get to => Get.find(); int count = 0; var counter = 0.obs; diff --git a/test/get_obx_test.dart b/test/get_obx_test.dart index 7ffdfeb41..0e3d7744d 100644 --- a/test/get_obx_test.dart +++ b/test/get_obx_test.dart @@ -4,7 +4,7 @@ import 'package:get/get.dart'; void main() { Controller controller = Get.put(Controller()); - testWidgets("GetController smoke test", (tester) async { + testWidgets("GetxController smoke test", (tester) async { await tester.pumpWidget( MaterialApp( home: Column( @@ -65,7 +65,7 @@ void main() { }); } -class Controller extends RxController { +class Controller extends GetxController { static Controller get to => Get.find(); var counter = 0.obs; diff --git a/test/get_rxstate_test.dart b/test/get_rxstate_test.dart index 4b6009b93..6a0e93f11 100644 --- a/test/get_rxstate_test.dart +++ b/test/get_rxstate_test.dart @@ -4,7 +4,7 @@ import 'package:get/get.dart'; void main() { Get.lazyPut(() => Controller2()); - testWidgets("GetController smoke test", (tester) async { + testWidgets("GetxController smoke test", (tester) async { await tester.pumpWidget( MaterialApp( home: GetX( @@ -76,15 +76,15 @@ void main() { }); } -class Controller2 extends RxController { +class Controller2 extends GetxController { int lazy = 0; } -class ControllerNonGlobal extends RxController { +class ControllerNonGlobal extends GetxController { int nonGlobal = 0; } -class Controller extends RxController { +class Controller extends GetxController { static Controller get to => Get.find(); var counter = 0.obs; diff --git a/test/get_state_test.dart b/test/get_state_test.dart index f2d772131..9403d3edf 100644 --- a/test/get_state_test.dart +++ b/test/get_state_test.dart @@ -4,7 +4,7 @@ import 'package:get/get.dart'; void main() { Get.lazyPut(() => Controller2()); - testWidgets("GetController smoke test", (test) async { + testWidgets("GetxController smoke test", (test) async { await test.pumpWidget( MaterialApp( home: GetBuilder( @@ -81,7 +81,7 @@ void main() { ); } -class Controller extends GetController { +class Controller extends GetxController { static Controller get to => Get.find(); int counter = 0; @@ -96,10 +96,10 @@ class Controller extends GetController { } } -class Controller2 extends GetController { +class Controller2 extends GetxController { int test = 0; } -class ControllerNonGlobal extends GetController { +class ControllerNonGlobal extends GetxController { int nonGlobal = 0; } diff --git a/test/routes_test.dart b/test/routes_test.dart index 5c6323f84..0f00fdb0d 100644 --- a/test/routes_test.dart +++ b/test/routes_test.dart @@ -4,16 +4,18 @@ import 'package:get/src/routes/get_route.dart'; void main() { testWidgets( - "GetRoute page null", + 'GetPage page null', (WidgetTester testr) async { - expect(() => GetRoute(page: null), throwsAssertionError); + expect(() => GetPage(page: null, name: null), throwsAssertionError); }, ); testWidgets( "GetRoute maintainState null", (WidgetTester testr) async { - expect(() => GetRoute(page: Scaffold(), maintainState: null), + expect( + () => + GetPage(page: () => Scaffold(), maintainState: null, name: null), throwsAssertionError); }, ); @@ -21,7 +23,9 @@ void main() { testWidgets( "GetRoute fullscreenDialog null", (WidgetTester testr) async { - expect(() => GetRoute(page: Scaffold(), fullscreenDialog: null), + expect( + () => GetPage( + page: () => Scaffold(), fullscreenDialog: null, name: null), throwsAssertionError); }, ); diff --git a/test/rx_event_test.dart b/test/rx_event_test.dart index 8d0116391..1226ca555 100644 --- a/test/rx_event_test.dart +++ b/test/rx_event_test.dart @@ -4,9 +4,9 @@ import 'package:get/get.dart'; void main() { test('once', () async { final count = 0.obs; - int result = -1; + var result = -1; once(count, (_) { - result = _; + result = _ as int; }); count.value++; await Future.delayed(Duration.zero); @@ -21,9 +21,9 @@ void main() { test('ever', () async { final count = 0.obs; - int result = -1; + var result = -1; ever(count, (_) { - result = _; + result = _ as int; }); count.value++; await Future.delayed(Duration.zero); @@ -38,10 +38,10 @@ void main() { test('debounce', () async { final count = 0.obs; - int result = -1; + var result = -1; debounce(count, (_) { print(_); - result = _; + result = _ as int; }, time: Duration(milliseconds: 100)); count.value++; @@ -56,10 +56,10 @@ void main() { test('interval', () async { final count = 0.obs; - int result = -1; + var result = -1; interval(count, (_) { print(_); - result = _; + result = _ as int; }, time: Duration(milliseconds: 100)); count.value++;