sejun
Committed by GitHub

Merge branch 'jonataslaw:master' into master

Showing 59 changed files with 2150 additions and 1094 deletions

Too many changes to show.

To preserve performance only 59 of 59+ files are displayed.

No preview for this file type
... ... @@ -23,7 +23,7 @@ jobs:
# https://github.com/marketplace/actions/flutter-action
- uses: subosito/flutter-action@v1
with:
flutter-version: "2.0.2"
flutter-version: "2.2.3"
channel: "stable"
- run: flutter pub get
#- run: flutter analyze
... ...
## [4.2.4]
- Added anchorRoute and filterPages to
// anchorRoute: '/',
// filterPages:
## [4.2.4]
- Fix Get.offAll removing GetxServices from memory
## [4.2.3]
- Fix back button on navigator 2
- Added parameters and arguments to Get.rootDelegate
## [4.2.1]
- Remove [] from docs to try fix pub score
## [4.2.0] - Big update
This update fixes important bugs as well as integrates with Navigator 2. It also adds GetRouterOutlet, similar to angular RouterOutlet thanks to @ahmednfwela. Also, the documentation translation for Vietnamese (@khangahs) has been added, making the GetX documentation available for 11 different languages, which is just fantastic for any opensource project. GetX has achieved more than 5.4k likes from the pub, and more than 4k stars on github, has videos about it with 48k on youtube, and has communities in the 4 hemispheres of the earth, besides having a large list of contributors as you see bellow. We're all happy to facilitate development with dart and flutter, and that making programming hassle-free has been taken around the world.
Changes in this version:
- Fix: Navigating to the same page with Get.offNamed does not delete the controller from that page using Get.lazyPut.
- Fix Readme GetMiddleware typos
by @nivisi
- Fix url replace error
by @KevinZhang19870314
- Changed response default encoding from latin1 to utf8
by @heftekharm
- Add Duration in ExtensionBottomSheet
by @chanonpingpong
- Added compatibility with dart-lang/mockito
by @lifez
- Added extensions methods to convert value in percent value
by @kauemurakami
- Set darkTheme equal theme when darkTheme is null
by @eduardoFlorence
- Add padding to 'defaultDialog'
by @KevinZhang19870314
- GraphQLResponse inherit Response info
by @jasonlaw
- Fix Redundant concatenating base url
by @jasonlaw
- Add content type and length into the headers when the content type is 'application/x-www-form-urlencoded'
by @calvingit
- Make withCredentials configurable
by @jasonlaw
- Fix flutter 2.0 error
by @yunchiri
- Allow deleting all registered instances
by @lemps
- Refactor/rx interface notify children
@by kranfix
- Fixed parameter parsing and middleware sorting
by @ahmednfwela
- Improvements to router outlet
by @ahmednfwela
- Minor improvements and bug fixes
by @ahmednfwela
- Adding route guards and improving navigation
by @ahmednfwela
- Fix RxInterface.proxy losing its previous value on exception
by @WillowWisp
- Added dispose() for bottomSheet.
by @furkankurt
- Added Pull request template
by @unacorbatanegra
- Fix and update documentation:
@Farid566,
@galaxykhh,
@arslee07,
@GoStaRoff,
@BondarenkoArtur,
@denisrudnei,
@Charly6596,
@nateshmbhat,
@hrithikrtiwari,
@Undeadlol1,
@rws08,
@inuyashaaa,
@broccolism,
@aadarshadhakalg,
@ZeroMinJeon
## [4.1.4]
- Adjust operator + and - to RxInt (@eduardoflorence)
- Fix dark theme (@eduardoflorence)
... ...
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
**언어: [영어](README.md), [베트남어](README-vi.md), [인도네시아어](README.id-ID.md), [우르두어](README.ur-PK.md), [중국어](README.zh-cn.md), [브라질 포르투칼어](README.pt-br.md), [스페인어](README-es.md), [러시아어](README.ru.md), [폴란드어](README.pl.md), 한국어(이파일), [프랑스어](README-fr.md)**
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
... ... @@ -16,6 +14,26 @@
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png)
<div align="center">
**Languages:**
[![영어](https://img.shields.io/badge/Language-English-blueviolet?style=for-the-badge)](README.md)
[![베트남어](https://img.shields.io/badge/Language-Vietnamese-blueviolet?style=for-the-badge)](README-vi.md)
[![인도네시아어](https://img.shields.io/badge/Language-Indonesian-blueviolet?style=for-the-badge)](README.id-ID.md)
[![우르두어](https://img.shields.io/badge/Language-Urdu-blueviolet?style=for-the-badge)](README.ur-PK.md)
[![중국어](https://img.shields.io/badge/Language-Chinese-blueviolet?style=for-the-badge)](README.zh-cn.md)
[![포르투칼어](https://img.shields.io/badge/Language-Portuguese-blueviolet?style=for-the-badge)](README.pt-br.md)
[![스페인어](https://img.shields.io/badge/Language-Spanish-blueviolet?style=for-the-badge)](README-es.md)
[![러시아어](https://img.shields.io/badge/Language-Russian-blueviolet?style=for-the-badge)](README.ru.md)
[![폴란드어](https://img.shields.io/badge/Language-Polish-blueviolet?style=for-the-badge)](README.pl.md)
[![한국어](https://img.shields.io/badge/Language-Korean-blueviolet?style=for-the-badge)](README.ko-kr.md)
[![프랑스어](https://img.shields.io/badge/Language-French-blueviolet?style=for-the-badge)](README-fr.md)
</div>
- [Get에 대하여](#get에-대하여)
- [설치](#설치)
- [GetX를 사용한 Counter 앱](#getx를-사용한-counter-앱)
... ... @@ -53,6 +71,8 @@
- [ObxValue](#obxvalue)
- [유용한 팁](#유용한-팁)
- [GetView](#getview)
- [GetResponsiveView](#getresponsiveview)
- [사용 방법](#사용-방법)
- [GetWidget](#getwidget)
- [GetxService](#getxservice)
- [2.0의 주요 변경점](#20의-주요-변경점)
... ... @@ -71,9 +91,11 @@
- **성능:** GetX는 성능과 최소한의 리소스 소비에 중점을 둡니다. GetX는 Streams나 ChangeNotifier를 사용하지 않습니다.
- **생산성:** GetX는 쉽고 친숙한 구문을 사용합니다. 원하시는 것보다 Getx에는 항상 더 쉬운 방법이 있습니다. 개발 시간을 아끼고 애플리케이션을 최대 성능으로 제공할 수 있습니다.
일반적으로 개발자는 메모리에서 컨트롤러들을 제거하는 데 관심을 가져야합니다. GetX에서는 리소스가 기본적으로 사용되지 않으면 메모리에서 제거되므로 필요하지 않습니다. 만약 메모리에 유지하려면 종속성에서 "permanent : true"를 명시적으로 선언해야합니다. 이렇게하면 시간을 절약 할 수있을뿐만 아니라 불필요한 메모리 종속성이 발생할 위험이 줄어 듭니다. 종속성은 기본적으로 lazy로 로드됩니다.
- **조직화:** GetX는 화면, 프레젠테이션 로직, 비즈니스 로직, 종속성 주입 및 네비게이션을 완전히 분리 할 수 있습니다. 라우트간 전환을 하는데에 컨텍스트가 필요하지 않아 위젯 트리(시각객체)에 독립적입니다. inheritedWidget을 통해 컨트롤러/블록에 접근하는 데 컨텍스트가 필요하지 않아 시각화 계층에서 프레젠테이션 로직과 비즈니스 로직을 완전히 분리됩니다. 이 GetX는 자체 종속성 주입 기능을 사용하여 DI를 뷰에서 완전히 분리하기 때문에 다중 Provider를 통해 위젯 트리에서 컨트롤러/모델/블록으로 주입 할 필요가 없습니다.
GetX를 사용하면 기본적으로 클린 코드를 가지게 되어 애플리케이션의 각 기능을 쉽게 찾을 수있습니다. 이것은 유지 보수를 용이하게 하며 모듈의 공유가 가능하고 Flutter에서는 생각할 수 없었던 것들도 전부 가능합니다.
BLoC은 Flutter에서 코드를 구성하기 위한 시작점으로 비즈니스 로직과 시각객체를 분리합니다. Getx는 비즈니스 로직 뿐만 아니라 프레젠테이션 로직을 분리하는 자연스러운 진화입니다. 추가로 종속성 주입과 라우트 또한 분리되고 데이터 계층이 모두로부터 분리됩니다. Hello World를 구현하는 것보다 더 쉽게 모든 것이 어디 있는지 알수 있습니다.
Flutter SDK와 함께 GetX를 사용하면 가장 쉽고 실용적이며 확장 가능한 고성능 어플리케이션을 만들수 있습니다. 초보자에게는 쉬우며 전문가에게는 정확하고 완벽하게 동작하는 대규모 생태계가 함께합니다. 안전하고 안정적이며 업데이트되고 기본 Flutter SDK에 없는 광범위한 API 빌드를 제공합니다.
... ... @@ -85,7 +107,8 @@
**추가로 [Get CLI](https://github.com/jonataslaw/get_cli)를 프런트엔드와 서버 양쪽에서 사용하면 전체 개발 프로세스를 자동화 할 수 있습니다.**
**추가로 생산성 향상을 위해 [VSCode 확장](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets)과 [Android Studio/Intellij 확장](https://plugins.jetbrains.com/plugin/14975-getx-snippets)이 있습니다.**
**추가로 생산성 향상을 위해
[VSCode 확장](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets)[Android Studio/Intellij 확장](https://plugins.jetbrains.com/plugin/14975-getx-snippets)이 있습니다.**
# 설치
... ... @@ -114,7 +137,6 @@ void main() => runApp(GetMaterialApp(home: Home()));
```
- 주석: 이는 Flutter의 MaterialApp을 변경하지 않으며 GetMaterialApp 또한 수정 된 MaterialApp이 아니고, 기본 MaterialApp을 자식으로 갖는 사전 구성된 위젯 일뿐입니다. 수동으로 구성 할 수 있지만 반드시 필요한 것은 아닙니다. GetMaterialApp은 라우트를 생성하고 추가하며, 번역을 추가하고, 라우트 탐색에 필요한 모든 것을 추가합니다. 만약 상태 관리 또는 종속성 관리에만 Get을 사용하는 경우 GetMaterialApp을 사용할 필요가 없습니다. GetMaterialApp은 라우트, 스택바, 국제화, bottomSheets, 다이얼로그 및 컨텍스트 부재와 라우트에 연관된 상위 api들에 필요합니다.
- 주석²: 이 단계는 라우트 관리 (`Get.to ()`,`Get.back ()` 등)를 사용하려는 경우에만 필요합니다. 사용하지 않을 경우 1 단계를 수행 할 필요가 없습니다.
- 2 단계:
... ... @@ -339,6 +361,34 @@ class Messages extends Translations {
Text('title'.tr);
```
#### 단수와 복수의 번역 사용법
```dart
var products = [];
Text('singularKey'.trPlural('pluralKey', products.length, Args));
```
#### 파라미터로 번역 사용하는 방법
```dart
import 'package:get/get.dart';
Map<String, Map<String, String>> get keys => {
'en_US': {
'logged_in': 'logged in as @name with email @email',
},
'es_ES': {
'logged_in': 'iniciado sesión como @name con e-mail @email',
}
};
Text('logged_in'.trParams({
'name': 'Jhon',
'email': 'jhon@example.com'
}));
```
### 지역화
`GetMaterialApp`의 파라미터를 전달하여 지역과 번역어를 정의합니다.
... ... @@ -392,9 +442,11 @@ Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark());
`.darkmode`가 활성화 될때 _light theme_ 로 바뀔것 이고 _light theme_ 가 활성화되면 _dark theme_ 로 변경될 것입니다.
## GetConnect
GetConnect는 http나 websockets으로 프론트와 백엔드의 통신을 위한 쉬운 방법입니다.
### 기본 구성
GetConnect를 간단하게 확장하고 Rest API나 websockets의 GET/POST/PUT/DELETE/SOCKET 메서드를 사용할 수 있습니다.
```dart
... ... @@ -417,7 +469,9 @@ class UserProvider extends GetConnect {
}
}
```
### 커스텀 구성
GetConnect는 고도로 커스텀화 할 수 있습니다. base Url을 정의하고 응답자 및 요청을 수정하고 인증자를 정의할 수 있습니다. 그리고 인증 횟수까지 정의 할 수 있습니다. 더해서 추가 구성없이 모델로 응답을 변형시킬 수 있는 표준 디코더 정의도 가능합니다.
```dart
... ... @@ -427,7 +481,8 @@ class HomeProvider extends GetConnect {
// 모든 요청은 jsonEncode로 CasesModel.fromJson()를 거칩니다.
httpClient.defaultDecoder = CasesModel.fromJson;
httpClient.baseUrl = 'https://api.covid19api.com';
// baseUrl = 'https://api.covid19api.com'; // [httpClient] 인스턴트 없이 사용하는경우 Http와 websockets의 baseUrl 정의
// baseUrl = 'https://api.covid19api.com';
// [httpClient] 인스턴트 없이 사용하는경우 Http와 websockets의 baseUrl 정의
// 모든 요청의 헤더에 'apikey' 속성을 첨부합니다.
httpClient.addRequestModifier((request) {
... ... @@ -482,6 +537,7 @@ final middlewares = [
GetMiddleware(priority: -8),
];
```
이 Middleware는 다음 순서로 실행됩니다. **-8 => 2 => 4 => 5**
### Redirect
... ... @@ -761,7 +817,8 @@ ValueBuilder<bool>(
#### ObxValue
[`ValueBuilder`](#valuebuilder)와 비슷하지만 Rx 인스턴스(마법같은 .obs를 기억하세요)를 전달하고 자동적으로 업데이트되는 반응형 버전입니다... 놀랍지 않습니까?
[`ValueBuilder`](#valuebuilder)와 비슷하지만 Rx 인스턴스(마법같은 .obs를 기억하세요)를 전달하고
자동적으로 업데이트되는 반응형 버전입니다... 놀랍지 않습니까?
```dart
ObxValue((data) => Switch(
... ... @@ -877,6 +934,55 @@ user.update((value){
print( user );
```
## StateMixin
`UI` 상태를 처리하는 또 다른 방법은 `StateMixin<T>` 를 사용하는 것입니다.
이를 구현하려면 `with`를 사용하여 `StateMixin<T>`을 추가하고
T 모델을 허용하는 컨트롤러에 연결합니다.
``` dart
class Controller extends GetController with StateMixin<User>{}
```
`change()` 메소드는 우리가 원할 때마다 State를 변경합니다.
다음과 같이 데이터와 상태를 전달하면 됩니다:
```dart
change(data, status: RxStatus.success());
```
RxStatus는 다음 상태를 허용합니다:
``` dart
RxStatus.loading();
RxStatus.success();
RxStatus.empty();
RxStatus.error('message');
```
UI에서 사용하는 방법:
```dart
class OtherClass extends GetView<Controller> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: controller.obx(
(state)=>Text(state.name),
// 여기에 사용자 정의 로딩 표시기를 넣을 수 있지만
// 기본값은 Center(child:CircularProgressIndicator()) 입니다
onLoading: CustomLoadingIndicator(),
onEmpty: Text('No data found'),
// 여기에서도 자신의 오류 위젯을 설정할 수 있지만
// 기본값은 Center(child:Text(error)) 입니다
onError: (error)=>Text(error),
),
);
}
```
#### GetView
... ... @@ -901,6 +1007,29 @@ print( user );
}
```
#### GetResponsiveView
Extend this widget to build responsive view.
this widget contains the `screen` property that have all
information about the screen size and type.
##### 사용 방법
You have two options to build it.
- with `builder` method you return the widget to build.
- with methods `desktop`, `tablet`,`phone`, `watch`. the specific
method will be built when the screen type matches the method
when the screen is [ScreenType.Tablet] the `tablet` method
will be exuded and so on.
**Note:** If you use this method please set the property `alwaysUseBuilder` to `false`
With `settings` property you can set the width limit for the screen types.
![example](https://github.com/SchabanBo/get_page_example/blob/master/docs/Example.gif?raw=true)
Code to this screen
[code](https://github.com/SchabanBo/get_page_example/blob/master/lib/pages/responsive_example/responsive_view.dart)
#### GetWidget
대부분의 사람들이 이 위젯에대해 모르거나 사용법을 완전히 혼동합니다.
... ... @@ -911,7 +1040,8 @@ _cache_ 이기 때문에 `const Stateless`가 될 수 없습니다.
만약 **GetX**의 기능 중 또 다른 "흔하지 않은" 기능을 사용하는 경우:`Get.create()`
`Get.create(()=>Controller())`가 `Get.find<Controller>()`을 호출할 때마다 새로운 `Controller`를 생성할 것 입니다.
`Get.create(()=>Controller())`가 `Get.find<Controller>()`을 호출할 때마다
새로운 `Controller`를 생성할 것 입니다.
여기서 `GetWidget`이 빛나게 됩니다... 예를 들어 Todo 리스트를 유지하려고 사용할 때 입니다.
위젯이 "재구성"될때 동일한 controller 인스턴스를 유지할 것입니다.
... ... @@ -919,14 +1049,14 @@ _cache_ 이기 때문에 `const Stateless`가 될 수 없습니다.
#### GetxService
이 클래스틑 `GetxController`와 같이 동일한 생성주기(`onInit()`, `onReady()`, `onClose()`)를 공유합니다.
하지만 이안에 "로직"은 없습니다. 단지 **GetX** 종속성 주입 시스템이 하위클래스를 메모리에서 삭제할 수 없음을 알립니다.
하지만 이안에 "로직"은 없습니다. 단지 **GetX** 종속성 주입 시스템이 하위클래스를 메모리에서
**삭제할 수 없음**을 알립니다.
그래서 `Get.find()`로 활성화하고 항상 접근하는 "서비스들"을 유지하는데 매우 유용합니다. :
`ApiService`, `StorageService`, `CacheService`.
```dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized()
await initServices(); /// 서비스들 초기화를 기다림.
runApp(SomeApp());
}
... ... @@ -964,7 +1094,8 @@ class SettingsService extends GetxService {
```
`GetxService`를 실질적으로 지우는 한가지 방법은 앱의 "Hot Reboot"과 같은 `Get.reset()`뿐 입니다.
따라서 앱 실행중 절대로 유지되어야 하는 클래스 인스턴스가 필요하면 `GetxService`를 사용하세요.
따라서 앱 실행중 절대로 유지되어야 하는 클래스 인스턴스가 필요하면
`GetxService`를 사용하세요.
# 2.0의 주요 변경점
... ... @@ -1056,6 +1187,7 @@ _프로젝트에 기여하고 싶으신가요? 우리는 귀하를 우리의 협
## 기사 및 비디오
- [Flutter Getx EcoSystem package for arabic people](https://www.youtube.com/playlist?list=PLV1fXIAyjeuZ6M8m56zajMUwu4uE3-SL0) - Tutorial by [Pesa Coder](https://github.com/UsamaElgendy).
- [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr).
- [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Route management video by Amateur Coder.
- [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - State management video by Amateur Coder.
... ... @@ -1069,4 +1201,4 @@ _프로젝트에 기여하고 싶으신가요? 우리는 귀하를 우리의 협
- [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris.
- [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter.
- [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter.
- [A minimal example on dartpad](https://dartpad.dev/2b3d0d6f9d4e312c5fdbefc414c1727e?) - by [Roi Peker](https://github.com/roipeker)
... ...
... ... @@ -6,12 +6,13 @@
- [Get.putAsync](#getputasync)
- [Get.create](#getcreate)
- [Using instantiated methods/classes](#using-instantiated-methodsclasses)
- [Specifying an alternate instance](#specifying-an-alternate-instance)
- [Differences between methods](#differences-between-methods)
- [Bindings](#bindings)
- [How to use](#how-to-use)
- [Bindings class](#bindings-class)
- [BindingsBuilder](#bindingsbuilder)
- [SmartManagement](#smartmanagement)
- [How to change](#How-to-change)
- [How to change](#how-to-change)
- [SmartManagement.full](#smartmanagementfull)
- [SmartManagement.onlyBuilders](#smartmanagementonlybuilders)
- [SmartManagement.keepFactory](#smartmanagementkeepfactory)
... ... @@ -202,6 +203,25 @@ To remove an instance of Get:
Get.delete<Controller>(); //usually you don't need to do this because GetX already delete unused controllers
```
## Specifying an alternate instance
A currently inserted instance can be replaced with a similar or extended class instance by using the `replace` method. This can then be retrieved by using the original class.
```dart
class ParentClass {}
class ChildClass extends ParentClass {
bool isChild = true;
}
Get.put(ParentClass());
Get.replace<ParentClass, ChildClass>(ChildClass());
final instance = Get.find<ParentClass>();
print(instance is ChildClass); //true
```
## Differences between methods
First, let's of the `fenix` of Get.lazyPut and the `permanent` of the other methods.
... ...
... ... @@ -272,7 +272,7 @@ And now, all you need to do is use Get.toNamed() to navigate your named routes,
### Middleware
If you want listen Get events to trigger actions, you can to use routingCallback to it
If you want to listen Get events to trigger actions, you can to use routingCallback to it
```dart
GetMaterialApp(
... ...
... ... @@ -15,11 +15,11 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
return GetMaterialApp.router(
debugShowCheckedModeBanner: false,
enableLog: true,
logWriterCallback: Logger.write,
initialRoute: AppPages.INITIAL,
// initialRoute: AppPages.INITIAL,
getPages: AppPages.routes,
locale: TranslationService.locale,
fallbackLocale: TranslationService.fallbackLocale,
... ... @@ -28,33 +28,37 @@ class MyApp extends StatelessWidget {
}
}
// Navigator 2 example, WIP
// TODO: add all methods from NavigatorExtension to GetNav
/// Nav 2 snippet
// void main() {
// runApp(MyApp());
// }
// class MyApp extends StatelessWidget {
// MyApp({Key? key}) : super(key: key);
// final getNav = Get.put(
// GetNav(pages: [
// GetPage(name: '/first', page: () => First()),
// GetPage(name: '/second', page: () => Second()),
// GetPage(name: '/third', page: () => Third()),
// ]),
// );
// @override
// Widget build(BuildContext context) {
// return GetMaterialApp.router(
// getPages: [
// GetPage(
// participatesInRootNavigator: true,
// name: '/first',
// page: () => First()),
// GetPage(
// name: '/second',
// page: () => Second(),
// ),
// GetPage(
// name: '/third',
// page: () => Third(),
// ),
// ],
// debugShowCheckedModeBanner: false,
// routeInformationParser: getNav.routeInformationParser,
// routerDelegate: getNav.routerDelegate,
// );
// }
// }
// class First extends StatelessWidget {
// final GetNav getNav = Get.find();
// @override
// Widget build(BuildContext context) {
// return Scaffold(
... ... @@ -73,9 +77,7 @@ class MyApp extends StatelessWidget {
// height: 300,
// width: 300,
// child: ElevatedButton(
// onPressed: () {
// getNav.toNamed('/second?id=584305');
// },
// onPressed: () {},
// child: Text('next screen'),
// ),
// ),
... ... @@ -85,7 +87,6 @@ class MyApp extends StatelessWidget {
// }
// class Second extends StatelessWidget {
// final GetNav getNav = Get.find();
// @override
// Widget build(BuildContext context) {
// return Scaffold(
... ... @@ -97,9 +98,7 @@ class MyApp extends StatelessWidget {
// height: 300,
// width: 300,
// child: ElevatedButton(
// onPressed: () {
// getNav.toNamed('/third');
// },
// onPressed: () {},
// child: Text('next screen'),
// ),
// ),
... ... @@ -109,7 +108,6 @@ class MyApp extends StatelessWidget {
// }
// class Third extends StatelessWidget {
// final GetNav getNav = Get.find();
// @override
// Widget build(BuildContext context) {
// return Scaffold(
... ... @@ -122,9 +120,7 @@ class MyApp extends StatelessWidget {
// height: 300,
// width: 300,
// child: ElevatedButton(
// onPressed: () {
// getNav.offUntil('/first');
// },
// onPressed: () {},
// child: Text('go to first screen'),
// ),
// ),
... ...
... ... @@ -16,6 +16,15 @@ class HomeController extends SuperController<CasesModel> {
append(() => homeRepository.getCases);
}
Country getCountryById(String id) {
final index = int.tryParse(id);
if (index != null) {
return state!.countries[index];
}
return state!.countries.first;
}
@override
void onReady() {
print('The build method is done. '
... ...
... ... @@ -33,8 +33,9 @@ class CountryView extends GetView<HomeController> {
final country = controller.state!.countries[index];
return ListTile(
onTap: () {
Get.toNamed('/home/country/details',
arguments: country);
//Get.rootDelegate.toNamed('/home/country');
Get.rootDelegate
.offNamed('/home/country/details?id=$index');
},
trailing: CircleAvatar(
backgroundImage: NetworkImage(
... ...
... ... @@ -2,13 +2,13 @@ import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/home_controller.dart';
import '../../domain/entity/cases_model.dart';
class DetailsView extends StatelessWidget {
class DetailsView extends GetView<HomeController> {
@override
Widget build(BuildContext context) {
final country = Get.arguments as Country;
final parameter = Get.rootDelegate.parameters;
final country = controller.getCountryById(parameter['id'] ?? '');
return Container(
decoration: BoxDecoration(
image: DecorationImage(
... ...
... ... @@ -71,7 +71,7 @@ class HomeView extends GetView<HomeController> {
shape: StadiumBorder(),
),
onPressed: () {
Get.toNamed('/home/country');
Get.rootDelegate.toNamed('/home/country');
},
child: Text(
'fetch_country'.tr,
... ... @@ -80,6 +80,26 @@ class HomeView extends GetView<HomeController> {
color: Colors.black,
),
),
),
OutlinedButton(
style: OutlinedButton.styleFrom(
textStyle: TextStyle(color: Colors.black),
side: BorderSide(
color: Colors.deepPurple,
width: 3,
),
shape: StadiumBorder(),
),
onPressed: () {
Get.updateLocale(Locale('pt', 'BR'));
},
child: Text(
'Update language to Portuguese',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
)
],
);
... ...
import 'package:get/get.dart';
import '../../services/auth_service.dart';
import '../routes/app_pages.dart';
class EnsureAuthMiddleware extends GetMiddleware {
@override
Future<GetNavConfig?> redirectDelegate(GetNavConfig route) async {
// you can do whatever you want here
// but it's preferable to make this method fast
// await Future.delayed(Duration(milliseconds: 500));
if (!AuthService.to.isLoggedInValue) {
final newRoute = Routes.LOGIN_THEN(route.location!);
return GetNavConfig.fromRoute(newRoute);
}
return await super.redirectDelegate(route);
}
}
class EnsureNotAuthedMiddleware extends GetMiddleware {
@override
Future<GetNavConfig?> redirectDelegate(GetNavConfig route) async {
if (AuthService.to.isLoggedInValue) {
//NEVER navigate to auth screen, when user is already authed
return null;
//OR redirect user to another screen
//return GetNavConfig.fromRoute(Routes.PROFILE);
}
return await super.redirectDelegate(route);
}
}
... ...
import 'package:get/get.dart';
import '../controllers/dashboard_controller.dart';
class DashboardBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<DashboardController>(
() => DashboardController(),
);
}
}
... ...
import 'dart:async';
import 'package:get/get.dart';
class DashboardController extends GetxController {
final now = DateTime.now().obs;
@override
void onReady() {
super.onReady();
Timer.periodic(
Duration(seconds: 1),
(timer) {
now.value = DateTime.now();
},
);
}
}
... ...
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/home_controller.dart';
import '../controllers/dashboard_controller.dart';
class DashboardView extends GetView<HomeController> {
class DashboardView extends GetView<DashboardController> {
@override
Widget build(BuildContext context) {
return Scaffold(
... ... @@ -16,7 +17,7 @@ class DashboardView extends GetView<HomeController> {
'DashboardView is working',
style: TextStyle(fontSize: 20),
),
Text('Time: ${controller.now.value.toString()}')
Text('Time: ${controller.now.value.toString()}'),
],
),
),
... ...
import 'dart:async';
import 'package:get/get.dart';
class HomeController extends GetxController {
final now = DateTime.now().obs;
@override
void onReady() {
super.onReady();
Timer.periodic(
Duration(seconds: 1),
(timer) {
now.value = DateTime.now();
},
);
}
}
class HomeController extends GetxController {}
... ...
... ... @@ -3,7 +3,6 @@ import 'package:get/get.dart';
import '../../../routes/app_pages.dart';
import '../controllers/home_controller.dart';
import 'dashboard_view.dart';
class HomeView extends GetView<HomeController> {
@override
... ... @@ -21,23 +20,16 @@ class HomeView extends GetView<HomeController> {
}
return Scaffold(
body: GetRouterOutlet(
name: Routes.HOME,
emptyWidget: (delegate) => DashboardView(),
pickPages: (currentNavStack) {
print('Home RouterOutlet: $currentNavStack');
// will take any route after home
final res =
currentNavStack.currentTreeBranch.pickAfterRoute(Routes.HOME);
return res;
},
initialRoute: Routes.DASHBOARD,
// anchorRoute: Routes.HOME,
key: Get.nestedKey(Routes.HOME),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentIndex,
onTap: (value) {
switch (value) {
case 0:
delegate.until(Routes.HOME);
delegate.toNamed(Routes.HOME);
break;
case 1:
delegate.toNamed(Routes.PROFILE);
... ...
import 'package:get/get.dart';
import '../controllers/login_controller.dart';
class LoginBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<LoginController>(
() => LoginController(),
);
}
}
... ...
import 'package:get/get.dart';
class LoginController extends GetxController {}
... ...
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../services/auth_service.dart';
import '../../../routes/app_pages.dart';
import '../controllers/login_controller.dart';
class LoginView extends GetView<LoginController> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Obx(
() {
final isLoggedIn = AuthService.to.isLoggedInValue;
return Text(
'You are currently:'
' ${isLoggedIn ? "Logged In" : "Not Logged In"}'
"\nIt's impossible to enter this "
"route when you are logged in!",
);
},
),
MaterialButton(
child: Text(
'Do LOGIN !!',
style: TextStyle(color: Colors.blue, fontSize: 20),
),
onPressed: () {
AuthService.to.login();
final thenTo = Get.rootDelegate.currentConfiguration!
.currentPage!.parameters?['then'];
Get.rootDelegate.offNamed(thenTo ?? Routes.HOME);
},
),
],
),
),
);
}
}
... ...
... ... @@ -4,4 +4,15 @@ class ProductDetailsController extends GetxController {
final String productId;
ProductDetailsController(this.productId);
@override
void onInit() {
super.onInit();
Get.log('ProductDetailsController created with id: $productId');
}
@override
void onClose() {
Get.log('ProductDetailsController close with id: $productId');
super.onClose();
}
}
... ...
import 'package:get/get.dart';
import '../../../models/demo_product.dart';
import '../../../../models/demo_product.dart';
class ProductsController extends GetxController {
final products = <DemoProduct>[].obs;
... ...
... ... @@ -12,26 +12,37 @@ class ProductsView extends GetView<ProductsController> {
onPressed: controller.loadDemoProductsFromSomeWhere,
label: Text('Add'),
),
body: Obx(
() => RefreshIndicator(
onRefresh: () async {
controller.products.clear();
controller.loadDemoProductsFromSomeWhere();
},
child: ListView.builder(
itemCount: controller.products.length,
itemBuilder: (context, index) {
final item = controller.products[index];
return ListTile(
onTap: () {
Get.getDelegate()?.toNamed(Routes.PRODUCT_DETAILS(item.id));
body: Column(
children: [
Hero(
tag: 'heroLogo',
child: const FlutterLogo(),
),
Expanded(
child: Obx(
() => RefreshIndicator(
onRefresh: () async {
controller.products.clear();
controller.loadDemoProductsFromSomeWhere();
},
title: Text(item.name),
subtitle: Text(item.id),
);
},
child: ListView.builder(
itemCount: controller.products.length,
itemBuilder: (context, index) {
final item = controller.products[index];
return ListTile(
onTap: () {
Get.rootDelegate
.toNamed(Routes.PRODUCT_DETAILS(item.id));
},
title: Text(item.name),
subtitle: Text(item.id),
);
},
),
),
),
),
),
],
),
);
}
... ...
import 'package:get/get.dart';
class ProfileController extends GetxController {
//TODO: Implement ProfileController
final count = 0.obs;
@override
void onInit() {
super.onInit();
}
@override
void onReady() {
super.onReady();
}
@override
void onClose() {}
void increment() => count.value++;
}
class ProfileController extends GetxController {}
... ...
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/profile_controller.dart';
class ProfileView extends GetView<ProfileController> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(
'ProfileView is working',
style: TextStyle(fontSize: 20),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../routes/app_pages.dart';
import '../controllers/profile_controller.dart';
class ProfileView extends GetView<ProfileController> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.amber,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'ProfileView is working',
style: TextStyle(fontSize: 20),
),
Hero(
tag: 'heroLogo',
child: const FlutterLogo(),
),
MaterialButton(
child: Text('Show a test dialog'),
onPressed: () {
//shows a dialog
Get.defaultDialog(
title: 'Test Dialog !!',
barrierDismissible: true,
);
},
),
MaterialButton(
child: Text('Show a test dialog in Home router outlet'),
onPressed: () {
//shows a dialog
Get.defaultDialog(
title: 'Test Dialog In Home Outlet !!',
barrierDismissible: true,
navigatorKey: Get.nestedKey(Routes.HOME),
);
},
)
],
),
),
);
}
}
... ...
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../services/auth_service.dart';
import '../../../routes/app_pages.dart';
class DrawerWidget extends StatelessWidget {
... ... @@ -20,7 +21,7 @@ class DrawerWidget extends StatelessWidget {
ListTile(
title: Text('Home'),
onTap: () {
Get.getDelegate()?.toNamed(Routes.HOME);
Get.rootDelegate.toNamed(Routes.HOME);
//to close the drawer
Navigator.of(context).pop();
... ... @@ -29,12 +30,43 @@ class DrawerWidget extends StatelessWidget {
ListTile(
title: Text('Settings'),
onTap: () {
Get.getDelegate()?.toNamed(Routes.SETTINGS);
Get.rootDelegate.toNamed(Routes.SETTINGS);
//to close the drawer
Navigator.of(context).pop();
},
),
if (AuthService.to.isLoggedInValue)
ListTile(
title: Text(
'Logout',
style: TextStyle(
color: Colors.red,
),
),
onTap: () {
AuthService.to.logout();
Get.rootDelegate.toNamed(Routes.LOGIN);
//to close the drawer
Navigator.of(context).pop();
},
),
if (!AuthService.to.isLoggedInValue)
ListTile(
title: Text(
'Login',
style: TextStyle(
color: Colors.blue,
),
),
onTap: () {
Get.rootDelegate.toNamed(Routes.LOGIN);
//to close the drawer
Navigator.of(context).pop();
},
),
],
),
);
... ...
... ... @@ -9,8 +9,8 @@ class RootView extends GetView<RootController> {
@override
Widget build(BuildContext context) {
return GetRouterOutlet.builder(
builder: (context, rDelegate, currentRoute) {
final title = currentRoute?.location;
builder: (context, delegate, current) {
final title = current?.location;
return Scaffold(
drawer: DrawerWidget(),
appBar: AppBar(
... ... @@ -18,14 +18,11 @@ class RootView extends GetView<RootController> {
centerTitle: true,
),
body: GetRouterOutlet(
name: '/',
emptyPage: (delegate) =>
Get.routeTree.matchRoute(Routes.HOME).route!,
pickPages: (currentNavStack) {
//show all routes here except the root view
print('Root RouterOutlet: $currentNavStack');
return currentNavStack.currentTreeBranch.skip(1).take(1).toList();
},
initialRoute: Routes.HOME,
// anchorRoute: '/',
// filterPages: (afterAnchor) {
// return afterAnchor.take(1);
// },
),
);
},
... ...
import 'package:get/get.dart';
import 'package:get/get_navigation/src/nav2/router_outlet.dart';
import '../middleware/auth_middleware.dart';
import '../modules/dashboard/bindings/dashboard_binding.dart';
import '../modules/dashboard/views/dashboard_view.dart';
import '../modules/home/bindings/home_binding.dart';
import '../modules/home/views/home_view.dart';
import '../modules/login/bindings/login_binding.dart';
import '../modules/login/views/login_view.dart';
import '../modules/product_details/bindings/product_details_binding.dart';
import '../modules/product_details/views/product_details_view.dart';
import '../modules/products/bindings/products_binding.dart';
... ... @@ -25,14 +29,22 @@ class AppPages {
GetPage(
name: '/',
page: () => RootView(),
middlewares: [
RouterOutletContainerMiddleWare('/'),
],
binding: RootBinding(),
participatesInRootNavigator: true,
preventDuplicates: true,
children: [
GetPage(
name: _Paths.HOME,
middlewares: [
//only enter this route when not authed
EnsureNotAuthedMiddleware(),
],
name: _Paths.LOGIN,
page: () => LoginView(),
binding: LoginBinding(),
),
GetPage(
preventDuplicates: true,
name: _Paths.HOME,
page: () => HomeView(),
bindings: [
HomeBinding(),
... ... @@ -40,6 +52,15 @@ class AppPages {
title: null,
children: [
GetPage(
name: _Paths.DASHBOARD,
page: () => DashboardView(),
binding: DashboardBinding(),
),
GetPage(
middlewares: [
//only enter this route when authed
EnsureAuthMiddleware(),
],
name: _Paths.PROFILE,
page: () => ProfileView(),
title: 'Profile',
... ... @@ -57,6 +78,10 @@ class AppPages {
name: _Paths.PRODUCT_DETAILS,
page: () => ProductDetailsView(),
binding: ProductDetailsBinding(),
middlewares: [
//only enter this route when authed
EnsureAuthMiddleware(),
],
),
],
),
... ...
... ... @@ -11,6 +11,10 @@ abstract class Routes {
static const PRODUCTS = _Paths.HOME + _Paths.PRODUCTS;
static String PRODUCT_DETAILS(String productId) => '$PRODUCTS/$productId';
static const LOGIN = _Paths.LOGIN;
static String LOGIN_THEN(String afterSuccessfulLogin) =>
'$LOGIN?then=${Uri.encodeQueryComponent(afterSuccessfulLogin)}';
static const DASHBOARD = _Paths.HOME + _Paths.DASHBOARD;
}
abstract class _Paths {
... ... @@ -19,4 +23,6 @@ abstract class _Paths {
static const PROFILE = '/profile';
static const SETTINGS = '/settings';
static const PRODUCT_DETAILS = '/:productId';
static const LOGIN = '/login';
static const DASHBOARD = '/dashboard';
}
... ...
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_navigation/src/nav2/get_router_delegate.dart';
import 'app/routes/app_pages.dart';
void main() {
runApp(
GetMaterialApp.router(
title: "Application",
getPages: AppPages.routes,
routeInformationParser: GetInformationParser(
// initialRoute: Routes.HOME,
),
routerDelegate: GetDelegate(
backButtonPopMode: PopMode.History,
preventDuplicateHandlingMode:
PreventDuplicateHandlingMode.PopUntilOriginalRoute,
),
),
);
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'app/routes/app_pages.dart';
import 'services/auth_service.dart';
void main() {
runApp(
GetMaterialApp.router(
title: "Application",
initialBinding: BindingsBuilder(
() {
Get.put(AuthService());
},
),
getPages: AppPages.routes,
// routeInformationParser: GetInformationParser(
// // initialRoute: Routes.HOME,
// ),
// routerDelegate: GetDelegate(
// backButtonPopMode: PopMode.History,
// preventDuplicateHandlingMode:
// PreventDuplicateHandlingMode.ReorderRoutes,
// ),
),
);
}
... ...
class DemoProduct {
final String name;
final String id;
DemoProduct({
required this.name,
required this.id,
});
}
class DemoProduct {
final String name;
final String id;
DemoProduct({
required this.name,
required this.id,
});
}
... ...
import 'package:get/get.dart';
class AuthService extends GetxService {
static AuthService get to => Get.find();
/// Mocks a login process
final isLoggedIn = false.obs;
bool get isLoggedInValue => isLoggedIn.value;
void login() {
isLoggedIn.value = true;
}
void logout() {
isLoggedIn.value = false;
}
}
... ...
... ... @@ -2,6 +2,8 @@
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
... ...
... ... @@ -2,6 +2,8 @@
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
... ...
... ... @@ -33,6 +33,7 @@ abstract class GetConnectInterface with GetLifeCycleBase {
Map<String, dynamic>? query,
Decoder<T>? decoder,
});
Future<Response<T>> post<T>(
String url,
dynamic body, {
... ... @@ -95,6 +96,7 @@ class GetConnect extends GetConnectInterface {
this.timeout = const Duration(seconds: 5),
this.followRedirects = true,
this.maxRedirects = 5,
this.sendUserAgent = false,
this.maxAuthRetries = 1,
this.allowAutoSignedCert = false,
this.withCredentials = false,
... ... @@ -104,6 +106,7 @@ class GetConnect extends GetConnectInterface {
bool allowAutoSignedCert;
String userAgent;
bool sendUserAgent;
String? baseUrl;
String defaultContentType = 'application/json; charset=utf-8';
bool followRedirects;
... ... @@ -122,6 +125,7 @@ class GetConnect extends GetConnectInterface {
@override
GetHttpClient get httpClient => _httpClient ??= GetHttpClient(
userAgent: userAgent,
sendUserAgent: sendUserAgent,
timeout: timeout,
followRedirects: followRedirects,
maxRedirects: maxRedirects,
... ... @@ -310,7 +314,6 @@ class GetConnect extends GetConnectInterface {
final listError = res.body['errors'];
if ((listError is List) && listError.isNotEmpty) {
// return GraphQLResponse<T>(body: res.body['data'] as T);
return GraphQLResponse<T>(
graphQLErrors: listError
.map((e) => GraphQLError(
... ... @@ -346,7 +349,6 @@ class GetConnect extends GetConnectInterface {
final listError = res.body['errors'];
if ((listError is List) && listError.isNotEmpty) {
// return GraphQLResponse<T>(body: res.body['data'] as T);
return GraphQLResponse<T>(
graphQLErrors: listError
.map((e) => GraphQLError(
... ...
... ... @@ -27,6 +27,8 @@ class GetHttpClient {
int maxRedirects;
int maxAuthRetries;
bool sendUserAgent;
Decoder? defaultDecoder;
Duration timeout;
... ... @@ -42,6 +44,7 @@ class GetHttpClient {
this.timeout = const Duration(seconds: 8),
this.followRedirects = true,
this.maxRedirects = 5,
this.sendUserAgent = false,
this.maxAuthRetries = 1,
bool allowAutoSignedCert = false,
this.baseUrl,
... ... @@ -98,7 +101,9 @@ class GetHttpClient {
Stream<List<int>>? bodyStream;
final headers = <String, String>{};
headers['user-agent'] = userAgent;
if (sendUserAgent) {
headers['user-agent'] = userAgent;
}
if (body is FormData) {
bodyBytes = await body.toBytes();
... ... @@ -179,7 +184,9 @@ class GetHttpClient {
String? contentType,
) {
headers['content-type'] = contentType ?? defaultContentType;
headers['user-agent'] = userAgent;
if (sendUserAgent) {
headers['user-agent'] = userAgent;
}
}
Future<Response<T>> _performRequest<T>(
... ...
... ... @@ -4,7 +4,7 @@ List<int> fileToBytes(dynamic data) {
if (data is List<int>) {
return data;
} else {
throw FormatException('File is not [File] or [String] or [List<int>]');
throw FormatException('File is not "File" or "String" or "List<int>"');
}
}
... ...
... ... @@ -7,12 +7,12 @@ List<int> fileToBytes(dynamic data) {
if (File(data).existsSync()) {
return File(data).readAsBytesSync();
} else {
throw 'File [data] not exists';
throw 'File $data not exists';
}
} else if (data is List<int>) {
return data;
} else {
throw FormatException('File is not [File] or [String] or [List<int>]');
throw FormatException('File is not "File" or "String" or "List<int>"');
}
}
... ...
... ... @@ -34,7 +34,6 @@ class HttpRequestImpl extends HttpRequestBase {
@override
Future<Response<T>> send<T>(Request<T> request) async {
var stream = request.bodyBytes.asBroadcastStream();
//var stream = BodyBytesStream.fromBytes(requestBody ?? const []);
try {
var ioRequest = (await _httpClient!.openUrl(request.method, request.url))
... ...
... ... @@ -36,7 +36,7 @@ class FormData {
/// The form fields to send for this request.
final fields = <MapEntry<String, String>>[];
/// The [files] to send for this request
/// The files to send for this request
final files = <MapEntry<String, MultipartFile>>[];
/// Returns the header string for a field. The return value is guaranteed to
... ...
... ... @@ -66,7 +66,7 @@ class Response<T> {
/// The decoded body of this [Response]. You can access the
/// body parameters as Map
/// Ex: body['title'];
/// Ex: `body['title'];`
final T? body;
}
... ... @@ -91,7 +91,7 @@ Encoding _encodingForCharset(String? charset, [Encoding fallback = utf8]) {
return Encoding.getByName(charset) ?? fallback;
}
/// Returns the [MediaType] object for the given headers's content-type.
/// Returns the MediaType object for the given headers's content-type.
///
/// Defaults to `application/octet-stream`.
HeaderValue _contentTypeForHeaders(Map<String, String> headers) {
... ...
... ... @@ -8,6 +8,7 @@ import 'smart_management.dart';
abstract class GetInterface {
SmartManagement smartManagement = SmartManagement.full;
RouterDelegate? routerDelegate;
RouteInformationParser? routeInformationParser;
String? reference;
bool isLogEnable = true;
LogWriterCallback log = defaultLogWriterCallback;
... ...
... ... @@ -5,7 +5,7 @@
/// not being used and were not set to be permanent. In the majority
/// of the cases you will want to keep this config untouched.
/// If you new to GetX then don't change this.
/// [SmartManagement.onlyBuilders] only controllers started in init:
/// [SmartManagement.onlyBuilder] only controllers started in init:
/// or loaded into a Binding with Get.lazyPut() will be disposed. If you use
/// Get.put() or Get.putAsync() or any other approach, SmartManagement
/// will not have permissions to exclude this dependency. With the default
... ...
import 'get_instance.dart';
/// [Bindings] should be extended or implemented.
/// When using [GetMaterialApp], all [GetPage]s and navigation
/// methods (like Get.to()) have a [binding] property that takes an
/// When using `GetMaterialApp`, all `GetPage`s and navigation
/// methods (like Get.to()) have a `binding` property that takes an
/// instance of Bindings to manage the
/// dependencies() (via [Get.put()]) for the Route you are opening.
/// dependencies() (via Get.put()) for the Route you are opening.
// ignore: one_member_abstracts
abstract class Bindings {
void dependencies();
... ...
... ... @@ -3,30 +3,30 @@ import '../../route_manager.dart';
import 'get_instance.dart';
extension Inst on GetInterface {
/// Creates a new Instance<S> lazily from the [<S>builder()] callback.
/// Creates a new Instance<S> lazily from the `<S>builder()` callback.
///
/// The first time you call [Get.find()], the [builder()] callback will create
/// The first time you call `Get.find()`, the `builder()` callback will create
/// the Instance and persisted as a Singleton (like you would use
/// [Get.put()]).
/// `Get.put()`).
///
/// Using [Get.smartManagement] as [SmartManagement.keepFactory] has
/// Using `Get.smartManagement` as [SmartManagement.keepFactory] has
/// the same outcome
/// as using [fenix:true] :
/// The internal register of [builder()] will remain in memory to recreate
/// the Instance if the Instance has been removed with [Get.delete()].
/// Therefore, future calls to [Get.find()] will return the same Instance.
/// as using `fenix:true` :
/// The internal register of `builder()` will remain in memory to recreate
/// the Instance if the Instance has been removed with `Get.delete()`.
/// Therefore, future calls to `Get.find()` will return the same Instance.
///
/// If you need to make use of GetxController's life-cycle
/// ([onInit(), onStart(), onClose()])
/// [fenix] is a great choice to mix with [GetBuilder()] and [GetX()] widgets,
/// (`onInit(), onStart(), onClose()`)
/// [fenix] is a great choice to mix with `GetBuilder` and `GetX` widgets,
/// and/or [GetMaterialApp] Navigation.
///
/// You could use [Get.lazyPut(fenix:true)] in your app's [main()] instead of
/// [Bindings()] for each [GetPage].
/// You could use `Get.lazyPut(fenix:true)` in your app's `main()` instead of
/// `Bindings` for each [GetPage].
/// And the memory management will be similar.
///
/// Subsequent calls to [Get.lazyPut()] with the same parameters
/// (<[S]> and optionally [tag] will **not** override the original).
/// Subsequent calls to `Get.lazyPut` with the same parameters
/// (`<S>` and optionally [tag] will **not** override the original).
void lazyPut<S>(InstanceBuilderCallback<S> builder,
{String? tag, bool fenix = false}) {
GetInstance().lazyPut<S>(builder, tag: tag, fenix: fenix);
... ... @@ -36,21 +36,21 @@ extension Inst on GetInterface {
GetInstance().printInstanceStack();
}
/// async version of [Get.put()].
/// Awaits for the resolution of the Future from [builder()] parameter and
/// async version of `Get.put()`.
/// Awaits for the resolution of the Future from `builder()`parameter and
/// stores the Instance returned.
Future<S> putAsync<S>(AsyncInstanceBuilderCallback<S> builder,
{String? tag, bool permanent = false}) async =>
GetInstance().putAsync<S>(builder, tag: tag, permanent: permanent);
/// Creates a new Class Instance [S] from the builder callback[S].
/// Every time [find]<[S]>() is used, it calls the builder method to generate
/// Every time `find<S>()` is used, it calls the builder method to generate
/// a new Instance [S].
/// It also registers each [instance.onClose()] with the current
/// Route [GetConfig.currentRoute] to keep the lifecycle active.
/// It also registers each `instance.onClose()` with the current
/// Route `GetConfig.currentRoute` to keep the lifecycle active.
/// Is important to know that the instances created are only stored per Route.
/// So, if you call `Get.delete<T>()` the "instance factory" used in this
/// method ([Get.create<T>()]) will be removed, but NOT the instances
/// method (`Get.create<T>()`) will be removed, but NOT the instances
/// already created by it.
/// Uses `tag` as the other methods.
///
... ... @@ -64,24 +64,24 @@ extension Inst on GetInterface {
{String? tag, bool permanent = true}) =>
GetInstance().create<S>(builder, tag: tag, permanent: permanent);
/// Finds a Instance of the required Class <[S]>(or [tag])
/// In the case of using [Get.create()], it will generate an Instance
/// each time you call [Get.find()].
/// Finds a Instance of the required Class `<S>`(or [tag])
/// In the case of using `Get.create()`, it will generate an Instance
/// each time you call `Get.find()`.
S find<S>({String? tag}) => GetInstance().find<S>(tag: tag);
/// Injects an [Instance<S>] in memory.
/// Injects an `Instance<S>` in memory.
///
/// No need to define the generic type <[S]> as it's inferred
/// No need to define the generic type `<[S]>` as it's inferred
/// from the [dependency] parameter.
///
/// - [dependency] The Instance to be injected.
/// - [tag] optionally, use a [tag] as an "id" to create multiple records
/// of the same Type<[S]> the [tag] does **not** conflict with the same tags
/// used by other [dependencies] Types.
/// of the same `Type<S>` the [tag] does **not** conflict with the same tags
/// used by other dependencies Types.
/// - [permanent] keeps the Instance in memory and persist it,
/// not following [Get.smartManagement]
/// rules. Although, can be removed by [GetInstance.reset()]
/// and [Get.delete()]
/// not following `Get.smartManagement`
/// rules. Although, can be removed by `GetInstance.reset()`
/// and `Get.delete()`
/// - [builder] If defined, the [dependency] must be returned from here
S put<S>(S dependency,
{String? tag,
... ... @@ -92,25 +92,25 @@ extension Inst on GetInterface {
/// Clears all registered instances (and/or tags).
/// Even the persistent ones.
///
/// - [clearFactory] clears the callbacks registered by [Get.lazyPut()]
/// - [clearFactory] clears the callbacks registered by `Get.lazyPut()`
/// - [clearRouteBindings] clears Instances associated with Routes when using
/// [GetMaterialApp].
bool reset({bool clearFactory = true, bool clearRouteBindings = true}) =>
GetInstance().reset(
clearFactory: clearFactory, clearRouteBindings: clearRouteBindings);
/// Deletes the Instance<[S]>, cleaning the memory and closes any open
/// controllers ([DisposableInterface]).
/// Deletes the `Instance<S>`, cleaning the memory and closes any open
/// controllers (`DisposableInterface`).
///
/// - [tag] Optional "tag" used to register the Instance
/// - [force] Will delete an Instance even if marked as [permanent].
/// - [force] Will delete an Instance even if marked as `permanent`.
Future<bool> delete<S>({String? tag, bool force = false}) async =>
GetInstance().delete<S>(tag: tag, force: force);
/// Deletes all Instances, cleaning the memory and closes any open
/// controllers ([DisposableInterface]).
/// controllers (`DisposableInterface`).
///
/// - [force] Will delete the Instances even if marked as [permanent].
/// - [force] Will delete the Instances even if marked as `permanent`.
Future<void> deleteAll({bool force = false}) async =>
GetInstance().deleteAll(force: force);
... ... @@ -119,13 +119,38 @@ extension Inst on GetInterface {
void reload<S>({String? tag, String? key, bool force = false}) =>
GetInstance().reload<S>(tag: tag, key: key, force: force);
/// Checks if a Class Instance<[S]> (or [tag]) is registered in memory.
/// Checks if a Class `Instance<S>` (or [tag]) is registered in memory.
/// - [tag] optional, if you use a [tag] to register the Instance.
bool isRegistered<S>({String? tag}) =>
GetInstance().isRegistered<S>(tag: tag);
/// Checks if an Instance<[S]> (or [tag]) returned from a factory builder
/// [Get.lazyPut()], is registered in memory.
/// Checks if an `Instance<S>` (or [tag]) returned from a factory builder
/// `Get.lazyPut()`, is registered in memory.
/// - [tag] optional, if you use a [tag] to register the Instance.
bool isPrepared<S>({String? tag}) => GetInstance().isPrepared<S>(tag: tag);
/// Replace a parent instance of a class in dependency management
/// with a [child] instance
/// - [tag] optional, if you use a [tag] to register the Instance.
void replace<P>(P child, {String? tag}) {
final info = GetInstance().getInstanceInfo<P>(tag: tag);
final permanent = (info.isPermanent ?? false);
delete<P>(tag: tag, force: permanent);
put(child, tag: tag, permanent: permanent);
}
/// Replaces a parent instance with a new Instance<P> lazily from the
/// [<P>builder()] callback.
/// - [tag] optional, if you use a [tag] to register the Instance.
/// - [fenix] optional
///
/// Note: if fenix is not provided it will be set to true if
/// the parent instance was permanent
void lazyReplace<P>(InstanceBuilderCallback<P> builder,
{String? tag, bool? fenix}) {
final info = GetInstance().getInstanceInfo<P>(tag: tag);
final permanent = (info.isPermanent ?? false);
delete<P>(tag: tag, force: permanent);
lazyPut(builder, tag: tag, fenix: fenix ?? permanent);
}
}
... ...
... ... @@ -31,19 +31,19 @@ class GetInstance {
T call<T>() => find<T>();
/// Holds references to every registered Instance when using
/// [Get.put()]
/// `Get.put()`
static final Map<String, _InstanceBuilderFactory> _singl = {};
/// Holds a reference to every registered callback when using
/// [Get.lazyPut()]
/// `Get.lazyPut()`
// static final Map<String, _Lazy> _factory = {};
/// Holds a reference to [Get.reference] when the Instance was
/// Holds a reference to `Get.reference` when the Instance was
/// created to manage the memory.
static final Map<String, String?> _routesKey = {};
/// Stores the onClose() references of instances created with [Get.create()]
/// using the [Get.reference].
/// Stores the onClose() references of instances created with `Get.create()`
/// using the `Get.reference`.
/// Experimental feature to keep the lifecycle and memory management with
/// non-singleton instances.
static final Map<String?, HashSet<Function>> _routesByCreate = {};
... ... @@ -66,8 +66,8 @@ class GetInstance {
);
}
/// async version of [Get.put()].
/// Awaits for the resolution of the Future from [builder()] parameter and
/// async version of `Get.put()`.
/// Awaits for the resolution of the Future from `builder()` parameter and
/// stores the Instance returned.
Future<S> putAsync<S>(
AsyncInstanceBuilderCallback<S> builder, {
... ... @@ -77,16 +77,16 @@ class GetInstance {
return put<S>(await builder(), tag: tag, permanent: permanent);
}
/// Injects an instance <[S]> in memory to be globally accessible.
/// Injects an instance `<S>` in memory to be globally accessible.
///
/// No need to define the generic type <[S]> as it's inferred from
/// No need to define the generic type `<S>` as it's inferred from
/// the [dependency]
///
/// - [dependency] The Instance to be injected.
/// - [tag] optionally, use a [tag] as an "id" to create multiple records of
/// the same Type<[S]>
/// - [permanent] keeps the Instance in memory, not following
/// [Get.smartManagement] rules.
/// `Get.smartManagement` rules.
S put<S>(
S dependency, {
String? tag,
... ... @@ -101,27 +101,27 @@ class GetInstance {
return find<S>(tag: tag);
}
/// Creates a new Instance<S> lazily from the [<S>builder()] callback.
/// Creates a new Instance<S> lazily from the `<S>builder()` callback.
///
/// The first time you call [Get.find()], the [builder()] callback will create
/// The first time you call `Get.find()`, the `builder()` callback will create
/// the Instance and persisted as a Singleton (like you would
/// use [Get.put()]).
/// use `Get.put()`).
///
/// Using [Get.smartManagement] as [SmartManagement.keepFactory] has
/// the same outcome as using [fenix:true] :
/// The internal register of [builder()] will remain in memory to recreate
/// the Instance if the Instance has been removed with [Get.delete()].
/// Therefore, future calls to [Get.find()] will return the same Instance.
/// Using `Get.smartManagement` as [SmartManagement.keepFactory] has
/// the same outcome as using `fenix:true` :
/// The internal register of `builder()` will remain in memory to recreate
/// the Instance if the Instance has been removed with `Get.delete()`.
/// Therefore, future calls to `Get.find()` will return the same Instance.
///
/// If you need to make use of GetxController's life-cycle
/// ([onInit(), onStart(), onClose()]) [fenix] is a great choice to mix with
/// [GetBuilder()] and [GetX()] widgets, and/or [GetMaterialApp] Navigation.
/// (`onInit(), onStart(), onClose()`) [fenix] is a great choice to mix with
/// `GetBuilder()` and `GetX()` widgets, and/or `GetMaterialApp` Navigation.
///
/// You could use [Get.lazyPut(fenix:true)] in your app's [main()] instead
/// of [Bindings()] for each [GetPage].
/// You could use `Get.lazyPut(fenix:true)` in your app's `main()` instead
/// of `Bindings()` for each `GetPage`.
/// And the memory management will be similar.
///
/// Subsequent calls to [Get.lazyPut()] with the same parameters
/// Subsequent calls to `Get.lazyPut()` with the same parameters
/// (<[S]> and optionally [tag] will **not** override the original).
void lazyPut<S>(
InstanceBuilderCallback<S> builder, {
... ... @@ -141,11 +141,11 @@ class GetInstance {
/// Creates a new Class Instance [S] from the builder callback[S].
/// Every time [find]<[S]>() is used, it calls the builder method to generate
/// a new Instance [S].
/// It also registers each [instance.onClose()] with the current
/// Route [Get.reference] to keep the lifecycle active.
/// It also registers each `instance.onClose()` with the current
/// Route `Get.reference` to keep the lifecycle active.
/// Is important to know that the instances created are only stored per Route.
/// So, if you call `Get.delete<T>()` the "instance factory" used in this
/// method ([Get.create<T>()]) will be removed, but NOT the instances
/// method (`Get.create<T>()`) will be removed, but NOT the instances
/// already created by it.
///
/// Example:
... ... @@ -167,7 +167,7 @@ class GetInstance {
);
}
/// Injects the Instance [S] builder into the [_singleton] HashMap.
/// Injects the Instance [S] builder into the `_singleton` HashMap.
void _insert<S>({
bool? isSingleton,
String? name,
... ... @@ -190,9 +190,9 @@ class GetInstance {
}
/// Clears from memory registered Instances associated with [routeName] when
/// using [Get.smartManagement] as [SmartManagement.full] or
/// using `Get.smartManagement` as [SmartManagement.full] or
/// [SmartManagement.keepFactory]
/// Meant for internal usage of [GetPageRoute] and [GetDialogRoute]
/// Meant for internal usage of `GetPageRoute` and `GetDialogRoute`
void removeDependencyByRoute(String routeName) {
final keysToRemove = <String>[];
_routesKey.forEach((key, value) {
... ... @@ -201,11 +201,11 @@ class GetInstance {
}
});
/// Removes [Get.create()] instances registered in [routeName].
/// Removes `Get.create()` instances registered in `routeName`.
if (_routesByCreate.containsKey(routeName)) {
for (final onClose in _routesByCreate[routeName]!) {
// assure the [DisposableInterface] instance holding a reference
// to [onClose()] wasn't disposed.
// to onClose() wasn't disposed.
onClose();
}
_routesByCreate[routeName]!.clear();
... ... @@ -214,19 +214,44 @@ class GetInstance {
for (final element in keysToRemove) {
delete(key: element);
_routesKey.remove(element);
}
keysToRemove.clear();
}
void reloadDependencyByRoute(String routeName) {
final keysToRemove = <String>[];
_routesKey.forEach((key, value) {
if (value == routeName) {
keysToRemove.add(key);
}
});
/// Removes `Get.create()` instances registered in `routeName`.
if (_routesByCreate.containsKey(routeName)) {
for (final onClose in _routesByCreate[routeName]!) {
// assure the [DisposableInterface] instance holding a reference
// to onClose() wasn't disposed.
onClose();
}
_routesByCreate[routeName]!.clear();
_routesByCreate.remove(routeName);
}
for (final element in keysToRemove) {
_routesKey.remove(element);
reload(key: element);
//_routesKey.remove(element);
}
keysToRemove.clear();
}
/// Initializes the dependencies for a Class Instance [S] (or tag),
/// If its a Controller, it starts the lifecycle process.
/// Optionally associating the current Route to the lifetime of the instance,
/// if [Get.smartManagement] is marked as [SmartManagement.full] or
/// [Get.keepFactory]
/// if `Get.smartManagement` is marked as [SmartManagement.full] or
/// [SmartManagement.keepFactory]
/// Only flags `isInit` if it's using `Get.create()`
/// (not for Singletons access).
/// Returns the instance if not initialized, required for Get.create() to
... ... @@ -248,7 +273,7 @@ class GetInstance {
}
/// Links a Class instance [S] (or [tag]) to the current route.
/// Requires usage of [GetMaterialApp].
/// Requires usage of `GetMaterialApp`.
void _registerRouteInstance<S>({String? tag}) {
_routesKey.putIfAbsent(_getKey(S, tag), () => Get.reference);
}
... ... @@ -353,7 +378,7 @@ class GetInstance {
}
/// Delete registered Class Instance [S] (or [tag]) and, closes any open
/// controllers [DisposableInterface], cleans up the memory
/// controllers `DisposableInterface`, cleans up the memory
///
/// /// Deletes the Instance<[S]>, cleaning the memory.
// ///
... ... @@ -362,12 +387,12 @@ class GetInstance {
// /// the Instance. **don't use** it unless you know what you are doing.
/// Deletes the Instance<[S]>, cleaning the memory and closes any open
/// controllers ([DisposableInterface]).
/// controllers (`DisposableInterface`).
///
/// - [tag] Optional "tag" used to register the Instance
/// - [key] For internal usage, is the processed key used to register
/// the Instance. **don't use** it unless you know what you are doing.
/// - [force] Will delete an Instance even if marked as [permanent].
/// - [force] Will delete an Instance even if marked as `permanent`.
bool delete<S>({String? tag, String? key, bool force = false}) {
final newKey = key ?? _getKey(S, tag);
... ... @@ -413,9 +438,9 @@ class GetInstance {
}
/// Delete all registered Class Instances and, closes any open
/// controllers [DisposableInterface], cleans up the memory
/// controllers `DisposableInterface`, cleans up the memory
///
/// - [force] Will delete the Instances even if marked as [permanent].
/// - [force] Will delete the Instances even if marked as `permanent`.
void deleteAll({bool force = false}) {
_singl.forEach((key, value) {
delete(key: key, force: force);
... ... @@ -448,6 +473,17 @@ class GetInstance {
return;
}
final i = builder.dependency;
if (i is GetxServiceMixin && !force) {
return;
}
if (i is GetLifeCycleBase) {
i.onDelete();
Get.log('"$newKey" onDelete() called');
}
builder.dependency = null;
builder.isInit = false;
Get.log('Instance "$newKey" was restarted.');
... ... @@ -457,7 +493,7 @@ class GetInstance {
/// - [tag] is optional, if you used a [tag] to register the Instance.
bool isRegistered<S>({String? tag}) => _singl.containsKey(_getKey(S, tag));
/// Checks if a lazy factory callback ([Get.lazyPut()] that returns an
/// Checks if a lazy factory callback `Get.lazyPut()` that returns an
/// Instance<[S]> is registered in memory.
/// - [tag] is optional, if you used a [tag] to register the lazy Instance.
bool isPrepared<S>({String? tag}) {
... ... @@ -481,7 +517,7 @@ typedef InjectorBuilderCallback<S> = S Function(GetInstance);
typedef AsyncInstanceBuilderCallback<S> = Future<S> Function();
/// Internal class to register instances with Get.[put]<[S]>().
/// Internal class to register instances with `Get.put<S>()`.
class _InstanceBuilderFactory<S> {
/// Marks the Builder as a single instance.
/// For reusing [dependency] instead of [builderFunc]
... ... @@ -499,7 +535,7 @@ class _InstanceBuilderFactory<S> {
InstanceBuilderCallback<S> builderFunc;
/// Flag to persist the instance in memory,
/// without considering [Get.smartManagement]
/// without considering `Get.smartManagement`
bool permanent = false;
bool isInit = false;
... ...
... ... @@ -3,7 +3,7 @@ import '../../get_core/get_core.dart';
/// Special callable class to keep the contract of a regular method, and avoid
/// overrides if you extend the class that uses it, as Dart has no final
/// methods.
/// Used in [DisposableInterface] to avoid the danger of overriding onStart.
/// Used in `DisposableInterface` to avoid the danger of overriding onStart.
class InternalFinalCallback<T> {
ValueUpdater<T>? _callback;
... ...
... ... @@ -205,6 +205,52 @@ extension ExtensionSnackbar on GetInterface {
}
}
extension OverlayExt on GetInterface {
Future<T> showOverlay<T>({
required Future<T> Function() asyncFunction,
Color opacityColor = Colors.black,
Widget? loadingWidget,
double opacity = .5,
}) async {
final navigatorState =
Navigator.of(Get.overlayContext!, rootNavigator: false);
final overlayState = navigatorState.overlay!;
final overlayEntryOpacity = OverlayEntry(builder: (context) {
return Opacity(
opacity: opacity,
child: Container(
color: opacityColor,
));
});
final overlayEntryLoader = OverlayEntry(builder: (context) {
return loadingWidget ??
Center(
child: Container(
height: 90,
width: 90,
child: Text('Loading...'),
));
});
overlayState.insert(overlayEntryOpacity);
overlayState.insert(overlayEntryLoader);
T data;
try {
data = await asyncFunction();
} on Exception catch (_) {
overlayEntryLoader.remove();
overlayEntryOpacity.remove();
rethrow;
}
overlayEntryLoader.remove();
overlayEntryOpacity.remove();
return data;
}
}
extension ExtensionDialog on GetInterface {
/// Show a dialog.
/// You can pass a [transitionDuration] and/or [transitionCurve],
... ... @@ -215,7 +261,7 @@ extension ExtensionDialog on GetInterface {
bool barrierDismissible = true,
Color? barrierColor,
bool useSafeArea = true,
bool useRootNavigator = true,
GlobalKey<NavigatorState>? navigatorKey,
Object? arguments,
Duration? transitionDuration,
Curve? transitionCurve,
... ... @@ -250,7 +296,7 @@ extension ExtensionDialog on GetInterface {
child: child,
);
},
useRootNavigator: useRootNavigator,
navigatorKey: navigatorKey,
routeSettings:
routeSettings ?? RouteSettings(arguments: arguments, name: name),
);
... ... @@ -264,20 +310,25 @@ extension ExtensionDialog on GetInterface {
Color barrierColor = const Color(0x80000000),
Duration transitionDuration = const Duration(milliseconds: 200),
RouteTransitionsBuilder? transitionBuilder,
bool useRootNavigator = true,
GlobalKey<NavigatorState>? navigatorKey,
RouteSettings? routeSettings,
}) {
assert(!barrierDismissible || barrierLabel != null);
return Navigator.of(overlayContext!, rootNavigator: useRootNavigator)
.push<T>(GetDialogRoute<T>(
pageBuilder: pageBuilder,
barrierDismissible: barrierDismissible,
barrierLabel: barrierLabel,
barrierColor: barrierColor,
transitionDuration: transitionDuration,
transitionBuilder: transitionBuilder,
settings: routeSettings,
));
final nav = navigatorKey?.currentState ??
Navigator.of(overlayContext!,
rootNavigator:
true); //overlay context will always return the root navigator
return nav.push<T>(
GetDialogRoute<T>(
pageBuilder: pageBuilder,
barrierDismissible: barrierDismissible,
barrierLabel: barrierLabel,
barrierColor: barrierColor,
transitionDuration: transitionDuration,
transitionBuilder: transitionBuilder,
settings: routeSettings,
),
);
}
/// Custom UI Dialog.
... ... @@ -309,6 +360,9 @@ extension ExtensionDialog on GetInterface {
// onWillPop Scope
WillPopCallback? onWillPop,
// the navigator used to push the dialog
GlobalKey<NavigatorState>? navigatorKey,
}) {
var leanCancel = onCancel != null || textCancel != null;
var leanConfirm = onConfirm != null || textConfirm != null;
... ... @@ -394,19 +448,15 @@ extension ExtensionDialog on GetInterface {
buttonPadding: EdgeInsets.zero,
);
if (onWillPop != null) {
return dialog<T>(
WillPopScope(
onWillPop: onWillPop,
child: baseAlertDialog,
),
barrierDismissible: barrierDismissible,
);
}
return dialog<T>(
baseAlertDialog,
onWillPop != null
? WillPopScope(
onWillPop: onWillPop,
child: baseAlertDialog,
)
: baseAlertDialog,
barrierDismissible: barrierDismissible,
navigatorKey: navigatorKey,
);
}
}
... ... @@ -460,7 +510,7 @@ extension ExtensionBottomSheet on GetInterface {
extension GetNavigation on GetInterface {
/// **Navigation.push()** shortcut.<br><br>
///
/// Pushes a new [page] to the stack
/// Pushes a new `page` to the stack
///
/// It has the advantage of not needing context,
/// so you can call from your business logic
... ... @@ -494,6 +544,7 @@ extension GetNavigation on GetInterface {
Bindings? binding,
bool preventDuplicates = true,
bool? popGesture,
double gestureWidth = 20,
}) {
var routeName = "/${page.runtimeType.toString()}";
if (preventDuplicates && routeName == currentRoute) {
... ... @@ -504,6 +555,7 @@ extension GetNavigation on GetInterface {
opaque: opaque ?? true,
page: _resolve(page, 'to'),
routeName: routeName,
gestureWidth: gestureWidth,
settings: RouteSettings(
// name: forceRouteName ? '${a.runtimeType}' : '',
arguments: arguments,
... ... @@ -538,7 +590,7 @@ you can only use widgets and widget functions here''';
/// **Navigation.pushNamed()** shortcut.<br><br>
///
/// Pushes a new named [page] to the stack.
/// Pushes a new named `page` to the stack.
///
/// It has the advantage of not needing context, so you can call
/// from your business logic.
... ... @@ -576,7 +628,7 @@ you can only use widgets and widget functions here''';
/// **Navigation.pushReplacementNamed()** shortcut.<br><br>
///
/// Pop the current named [page] in the stack and push a new one in its place
/// Pop the current named `page` in the stack and push a new one in its place
///
/// It has the advantage of not needing context, so you can call
/// from your business logic.
... ... @@ -632,7 +684,7 @@ you can only use widgets and widget functions here''';
/// **Navigation.pushAndRemoveUntil()** shortcut.<br><br>
///
/// Push the given [page], and then pop several pages in the stack until
/// Push the given `page`, and then pop several pages in the stack until
/// [predicate] returns true
///
/// [id] is for when you are using nested navigation,
... ... @@ -656,7 +708,7 @@ you can only use widgets and widget functions here''';
/// **Navigation.pushNamedAndRemoveUntil()** shortcut.<br><br>
///
/// Push the given named [page], and then pop several pages in the stack
/// Push the given named `page`, and then pop several pages in the stack
/// until [predicate] returns true
///
/// You can send any type of value to the other route in the [arguments].
... ... @@ -693,7 +745,7 @@ you can only use widgets and widget functions here''';
/// **Navigation.popAndPushNamed()** shortcut.<br><br>
///
/// Pop the current named page and pushes a new [page] to the stack
/// Pop the current named page and pushes a new `page` to the stack
/// in its place
///
/// You can send any type of value to the other route in the [arguments].
... ... @@ -732,7 +784,7 @@ you can only use widgets and widget functions here''';
/// **Navigation.pushNamedAndRemoveUntil()** shortcut.<br><br>
///
/// Push a named [page] and pop several pages in the stack
/// Push a named `page` and pop several pages in the stack
/// until [predicate] returns true. [predicate] is optional
///
/// It has the advantage of not needing context, so you can
... ... @@ -827,7 +879,7 @@ you can only use widgets and widget functions here''';
/// **Navigation.pushReplacement()** shortcut .<br><br>
///
/// Pop the current page and pushes a new [page] to the stack
/// Pop the current page and pushes a new `page` to the stack
///
/// It has the advantage of not needing context,
/// so you can call from your business logic
... ... @@ -862,6 +914,7 @@ you can only use widgets and widget functions here''';
bool fullscreenDialog = false,
bool preventDuplicates = true,
Duration? duration,
double gestureWidth = 20,
}) {
var routeName = "/${page.runtimeType.toString()}";
if (preventDuplicates && routeName == currentRoute) {
... ... @@ -869,6 +922,7 @@ you can only use widgets and widget functions here''';
}
return global(id).currentState?.pushReplacement(GetPageRoute(
opaque: opaque,
gestureWidth: gestureWidth,
page: _resolve(page, 'off'),
binding: binding,
settings: RouteSettings(arguments: arguments),
... ... @@ -882,7 +936,7 @@ you can only use widgets and widget functions here''';
/// **Navigation.pushAndRemoveUntil()** shortcut .<br><br>
///
/// Push a [page] and pop several pages in the stack
/// Push a `page` and pop several pages in the stack
/// until [predicate] returns true. [predicate] is optional
///
/// It has the advantage of not needing context,
... ... @@ -923,6 +977,7 @@ you can only use widgets and widget functions here''';
Transition? transition,
Curve? curve,
Duration? duration,
double gestureWidth = 20,
}) {
var routeName = "/${page.runtimeType.toString()}";
... ... @@ -932,6 +987,7 @@ you can only use widgets and widget functions here''';
popGesture: popGesture ?? defaultPopGesture,
page: _resolve(page, 'offAll'),
binding: binding,
gestureWidth: gestureWidth,
settings: RouteSettings(arguments: arguments),
fullscreenDialog: fullscreenDialog,
routeName: routeName,
... ... @@ -942,23 +998,6 @@ you can only use widgets and widget functions here''';
predicate ?? (route) => false);
}
void registerRoutes(List<GetPage> getPages) {
//TODO: only replace if null???
routeTree = ParseRouteTree(routes: <GetPage>[]);
routeTree.addRoutes(getPages);
}
void addPages(List<GetPage>? getPages) {
if (getPages != null) {
registerRoutes(getPages);
}
}
void addPage(GetPage getPage) {
// routeTree = ParseRouteTree();
routeTree.addRoute(getPage);
}
/// change default config of Get
void config(
{bool? enableLog,
... ... @@ -1022,12 +1061,16 @@ you can only use widgets and widget functions here''';
}
GlobalKey<NavigatorState>? addKey(GlobalKey<NavigatorState> newKey) {
getxController.key = newKey;
return key;
return getxController.addKey(newKey);
}
GlobalKey<NavigatorState>? nestedKey(dynamic key) {
keys.putIfAbsent(key, () => GlobalKey<NavigatorState>());
keys.putIfAbsent(
key,
() => GlobalKey<NavigatorState>(
debugLabel: 'Getx nested key: ${key.toString()}',
),
);
return keys[key];
}
... ... @@ -1054,19 +1097,6 @@ you can only use widgets and widget functions here''';
return _key;
}
/// Casts the stored router delegate to a desired type
TDelegate? delegate<TDelegate extends RouterDelegate<TPage>, TPage>() =>
_routerDelegate as TDelegate?;
static RouterDelegate? _routerDelegate;
// ignore: use_setters_to_change_properties
void setDefaultDelegate(RouterDelegate? delegate) {
_routerDelegate = delegate;
}
GetDelegate? getDelegate() => delegate<GetDelegate, GetNavConfig>();
/// give current arguments
dynamic get arguments => routing.args;
... ... @@ -1207,9 +1237,6 @@ you can only use widgets and widget functions here''';
set parameters(Map<String, String?> newParameters) =>
getxController.parameters = newParameters;
ParseRouteTree get routeTree => getxController.routeTree;
set routeTree(ParseRouteTree tree) => getxController.routeTree = tree;
CustomTransition? get customTransition => getxController.customTransition;
set customTransition(CustomTransition? newTransition) =>
getxController.customTransition = newTransition;
... ... @@ -1220,6 +1247,65 @@ you can only use widgets and widget functions here''';
static GetMaterialController getxController = GetMaterialController();
}
extension NavTwoExt on GetInterface {
void addPages(List<GetPage> getPages) {
routeTree.addRoutes(getPages);
}
static late final _routeTree = ParseRouteTree(routes: []);
ParseRouteTree get routeTree => _routeTree;
void addPage(GetPage getPage) {
routeTree.addRoute(getPage);
}
/// Casts the stored router delegate to a desired type
TDelegate? delegate<TDelegate extends RouterDelegate<TPage>, TPage>() =>
routerDelegate as TDelegate?;
// // ignore: use_setters_to_change_properties
// void setDefaultDelegate(RouterDelegate? delegate) {
// _routerDelegate = delegate;
// }
// GetDelegate? getDelegate() => delegate<GetDelegate, GetNavConfig>();
GetInformationParser createInformationParser({String initialRoute = '/'}) {
if (routeInformationParser == null) {
return routeInformationParser = GetInformationParser(
initialRoute: initialRoute,
);
} else {
return routeInformationParser as GetInformationParser;
}
}
// static GetDelegate? _delegate;
GetDelegate get rootDelegate => createDelegate();
GetDelegate createDelegate({
GetPage<dynamic>? notFoundRoute,
List<NavigatorObserver>? navigatorObservers,
TransitionDelegate<dynamic>? transitionDelegate,
PopMode backButtonPopMode = PopMode.History,
PreventDuplicateHandlingMode preventDuplicateHandlingMode =
PreventDuplicateHandlingMode.ReorderRoutes,
}) {
if (routerDelegate == null) {
return routerDelegate = GetDelegate(
notFoundRoute: notFoundRoute,
navigatorObservers: navigatorObservers,
transitionDelegate: transitionDelegate,
backButtonPopMode: backButtonPopMode,
preventDuplicateHandlingMode: preventDuplicateHandlingMode,
);
} else {
return routerDelegate as GetDelegate;
}
}
}
/// It replaces the Flutter Navigator, but needs no context.
/// You can to use navigator.push(YourRoute()) rather
/// Navigator.push(context, YourRoute());
... ...
... ... @@ -7,7 +7,9 @@ class GetInformationParser extends RouteInformationParser<GetNavConfig> {
GetInformationParser({
this.initialRoute = '/',
});
}) {
Get.log('GetInformationParser is created !');
}
@override
SynchronousFuture<GetNavConfig> parseRouteInformation(
RouteInformation routeInformation,
... ...
... ... @@ -2,6 +2,22 @@ import 'package:flutter/widgets.dart';
import '../../../get.dart';
// class GetRouterState extends GetxController {
// GetRouterState({required this.currentTreeBranch});
// final List<GetPage> currentTreeBranch;
// GetPage? get currentPage => currentTreeBranch.last;
// static GetNavConfig? fromRoute(String route) {
// final res = Get.routeTree.matchRoute(route);
// if (res.treeBranch.isEmpty) return null;
// return GetNavConfig(
// currentTreeBranch: res.treeBranch,
// location: route,
// state: null,
// );
// }
// }
/// This config enables us to navigate directly to a sub-url
class GetNavConfig extends RouteInformation {
final List<GetPage> currentTreeBranch;
... ... @@ -18,7 +34,6 @@ class GetNavConfig extends RouteInformation {
GetNavConfig copyWith({
List<GetPage>? currentTreeBranch,
GetPage? currentPage,
required String? location,
required Object? state,
}) {
... ... @@ -29,6 +44,16 @@ class GetNavConfig extends RouteInformation {
);
}
static GetNavConfig? fromRoute(String route) {
final res = Get.routeTree.matchRoute(route);
if (res.treeBranch.isEmpty) return null;
return GetNavConfig(
currentTreeBranch: res.treeBranch,
location: route,
state: null,
);
}
@override
String toString() => '''
======GetNavConfig=====\ncurrentTreeBranch: $currentTreeBranch\ncurrentPage: $currentPage\n======GetNavConfig=====''';
... ...
... ... @@ -38,6 +38,12 @@ enum PreventDuplicateHandlingMode {
/// Simply don't push the new route
DoNothing,
/// Recommended - Moves the old route entry to the front
///
/// With this mode, you guarantee there will be only one
/// route entry for each location
ReorderRoutes
}
class GetDelegate extends RouterDelegate<GetNavConfig>
... ... @@ -46,58 +52,116 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
final PopMode backButtonPopMode;
final PreventDuplicateHandlingMode preventDuplicateHandlingMode;
GetPage? notFoundRoute;
final GetPage notFoundRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionDelegate<dynamic>? transitionDelegate;
final _resultCompleter = <GetNavConfig, Completer<Object?>>{};
GlobalKey<NavigatorState> get navigatorKey =>
GetNavigation.getxController.key;
GetDelegate({
this.notFoundRoute,
GetPage? notFoundRoute,
this.navigatorObservers,
this.transitionDelegate,
this.backButtonPopMode = PopMode.History,
this.preventDuplicateHandlingMode = PreventDuplicateHandlingMode.DoNothing,
});
this.preventDuplicateHandlingMode =
PreventDuplicateHandlingMode.ReorderRoutes,
}) : notFoundRoute = notFoundRoute ??
GetPage(
name: '/404',
page: () => Scaffold(
body: Text('Route not found'),
),
) {
Get.log('GetDelegate is created !');
}
Future<GetNavConfig?> runMiddleware(GetNavConfig config) async {
final middlewares = config.currentTreeBranch.last.middlewares;
if (middlewares == null) {
return config;
}
var iterator = config;
for (var item in middlewares) {
var redirectRes = await item.redirectDelegate(iterator);
if (redirectRes == null) return null;
iterator = redirectRes;
}
return iterator;
}
Future<void> _unsafeHistoryAdd(GetNavConfig config) async {
final res = await runMiddleware(config);
if (res == null) return;
history.add(res);
}
Future<void> _unsafeHistoryRemove(GetNavConfig config) async {
var index = history.indexOf(config);
if (index >= 0) await _unsafeHistoryRemoveAt(index);
}
Future<GetNavConfig?> _unsafeHistoryRemoveAt(int index) async {
if (index == history.length - 1 && history.length > 1) {
//removing WILL update the current route
final toCheck = history[history.length - 2];
final resMiddleware = await runMiddleware(toCheck);
if (resMiddleware == null) return null;
history[history.length - 2] = resMiddleware;
}
return history.removeAt(index);
}
T arguments<T>() {
return currentConfiguration?.currentPage?.arguments as T;
}
Map<String, String> get parameters {
return currentConfiguration?.currentPage?.parameters ?? {};
}
// void _unsafeHistoryClear() {
// history.clear();
// }
/// Adds a new history entry and waits for the result
Future<T?> pushHistory<T>(
Future<void> pushHistory(
GetNavConfig config, {
bool rebuildStack = true,
}) {
}) async {
//this changes the currentConfiguration
final completer = Completer<T?>();
_resultCompleter[config] = completer;
_pushHistory(config);
await _pushHistory(config);
if (rebuildStack) {
refresh();
}
return completer.future;
}
void _removeHistoryEntry(GetNavConfig entry) {
history.remove(entry);
final lastCompleter = _resultCompleter.remove(entry);
lastCompleter?.complete(entry);
Future<void> _removeHistoryEntry(GetNavConfig entry) async {
await _unsafeHistoryRemove(entry);
}
void _pushHistory(GetNavConfig config) {
Future<void> _pushHistory(GetNavConfig config) async {
if (config.currentPage!.preventDuplicates) {
if (history.any((element) => element.location == config.location)) {
final originalEntryIndex =
history.indexWhere((element) => element.location == config.location);
if (originalEntryIndex >= 0) {
switch (preventDuplicateHandlingMode) {
case PreventDuplicateHandlingMode.PopUntilOriginalRoute:
until(config.location!, popMode: PopMode.Page);
return;
await backUntil(config.location!, popMode: PopMode.Page);
break;
case PreventDuplicateHandlingMode.ReorderRoutes:
await _unsafeHistoryRemoveAt(originalEntryIndex);
await _unsafeHistoryAdd(config);
break;
case PreventDuplicateHandlingMode.DoNothing:
default:
return;
break;
}
return;
}
}
history.add(config);
await _unsafeHistoryAdd(config);
}
// GetPageRoute getPageRoute(RouteSettings? settings) {
... ... @@ -105,34 +169,33 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
// .page();
// }
GetNavConfig? _popHistory() {
Future<GetNavConfig?> _popHistory() async {
if (!_canPopHistory()) return null;
return _doPopHistory();
return await _doPopHistory();
}
GetNavConfig _doPopHistory() {
final res = history.removeLast();
return res;
Future<GetNavConfig?> _doPopHistory() async {
return await _unsafeHistoryRemoveAt(history.length - 1);
}
GetNavConfig? _popPage() {
Future<GetNavConfig?> _popPage() async {
if (!_canPopPage()) return null;
return _doPopPage();
return await _doPopPage();
}
GetNavConfig? _pop(PopMode mode) {
Future<GetNavConfig?> _pop(PopMode mode) async {
switch (mode) {
case PopMode.History:
return _popHistory();
return await _popHistory();
case PopMode.Page:
return _popPage();
return await _popPage();
default:
return null;
}
}
// returns the popped page
GetNavConfig? _doPopPage() {
Future<GetNavConfig?> _doPopPage() async {
final currentBranch = currentConfiguration?.currentTreeBranch;
if (currentBranch != null && currentBranch.length > 1) {
//remove last part only
... ... @@ -147,13 +210,13 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
final prevLocation = prevHistoryEntry.location;
if (newLocation == prevLocation) {
//pop the entire history entry
return _popHistory();
return await _popHistory();
}
}
//create a new route with the remaining tree branch
final res = _popHistory();
_pushHistory(
final res = await _popHistory();
await _pushHistory(
GetNavConfig(
currentTreeBranch: remaining.toList(),
location: remaining.last.name,
... ... @@ -163,16 +226,16 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
return res;
} else {
//remove entire entry
return _popHistory();
return await _popHistory();
}
}
Future<GetNavConfig?> popHistory() {
return SynchronousFuture(_popHistory());
Future<GetNavConfig?> popHistory() async {
return await _popHistory();
}
bool _canPopHistory() {
return history.length > 1;
return history.length > 0;
}
Future<bool> canPopHistory() {
... ... @@ -201,25 +264,30 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
/// gets the visual pages from the current history entry
///
/// visual pages must have the [RouterOutletContainerMiddleWare] middleware
/// with `stayAt` equal to the route name of the visual page
/// visual pages must have [participatesInRootNavigator] set to true
List<GetPage> getVisualPages() {
final currentHistory = currentConfiguration;
if (currentHistory == null) return <GetPage>[];
return currentHistory.currentTreeBranch.where((r) {
final mware =
(r.middlewares ?? []).whereType<RouterOutletContainerMiddleWare>();
if (mware.length == 0) return true;
return r.name == mware.first.stayAt;
}).toList();
final res = currentHistory.currentTreeBranch
.where((r) => r.participatesInRootNavigator != null);
if (res.length == 0) {
//default behavoir, all routes participate in root navigator
return history.map((e) => e.currentPage!).toList();
} else {
//user specified at least one participatesInRootNavigator
return res
.where((element) => element.participatesInRootNavigator == true)
.toList();
}
}
@override
Widget build(BuildContext context) {
final pages = getVisualPages();
if (pages.length == 0) return SizedBox.shrink();
final extraObservers = navigatorObservers;
return GetNavigator(
name: 'root',
key: navigatorKey,
onPopPage: _onPopVisualRoute,
pages: pages,
... ... @@ -232,12 +300,13 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
);
}
@override
Future<void> setInitialRoutePath(GetNavConfig configuration) async {
history.clear();
_resultCompleter.clear();
await pushHistory(configuration);
}
// @override
// Future<void> setInitialRoutePath(GetNavConfig configuration) async {
// //no need to clear history with Reorder route strategy
// // _unsafeHistoryClear();
// // _resultCompleter.clear();
// await pushHistory(configuration);
// }
@override
Future<void> setNewRoutePath(GetNavConfig configuration) async {
... ... @@ -251,46 +320,56 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
return route;
}
Future<T?> toNamed<T>(String fullRoute) {
final decoder = Get.routeTree.matchRoute(fullRoute);
return pushHistory<T>(
Future<void> toNamed(
String page, {
dynamic arguments,
Map<String, String>? parameters,
}) async {
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
final decoder = Get.routeTree.matchRoute(page, arguments: arguments);
decoder.replaceArguments(arguments);
await pushHistory(
GetNavConfig(
currentTreeBranch: decoder.treeBranch,
location: fullRoute,
location: page,
state: null, //TODO: persist state?
),
);
}
Future<void> offNamed(
String page, {
dynamic arguments,
Map<String, String>? parameters,
}) async {
await popHistory();
await toNamed(page, arguments: arguments, parameters: parameters);
}
/// Removes routes according to [PopMode]
/// until it reaches the specifc [fullRoute],
/// DOES NOT remove the [fullRoute]
void until(
Future<void> backUntil(
String fullRoute, {
PopMode popMode = PopMode.Page,
}) {
}) async {
// remove history or page entries until you meet route
var iterator = currentConfiguration;
while (_canPop(popMode) &&
iterator != null &&
iterator.location != fullRoute) {
_pop(popMode);
await _pop(popMode);
// replace iterator
iterator = currentConfiguration;
}
refresh();
}
// GetPage _notFound() {
// return notFoundRoute ??= GetPage(
// name: '/404',
// page: () => Scaffold(
// body: Text('not found'),
// ),
// );
// }
Future<bool> handlePopupRoutes({
Object? result,
}) async {
... ... @@ -313,15 +392,13 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
//Returning false will cause the entire app to be popped.
final wasPopup = await handlePopupRoutes(result: result);
if (wasPopup) return true;
final _popped = _pop(popMode);
final _popped = await _pop(popMode);
refresh();
if (_popped != null) {
//emulate the old pop with result
final lastCompleter = _resultCompleter.remove(_popped);
lastCompleter?.complete(result);
return Future.value(true);
return true;
}
return Future.value(false);
return false;
}
bool _onPopVisualRoute(Route<dynamic> route, dynamic result) {
... ... @@ -350,17 +427,23 @@ class GetNavigator extends Navigator {
bool Function(Route<dynamic>, dynamic)? onPopPage,
required List<Page> pages,
List<NavigatorObserver>? observers,
bool reportsRouteUpdateToEngine = false,
TransitionDelegate? transitionDelegate,
String? name,
}) : assert(key != null || name != null,
'GetNavigator should either have a key or a name set'),
super(
key: key ?? Get.nestedKey(name),
onPopPage: onPopPage,
reportsRouteUpdateToEngine: true,
}) : super(
//keys should be optional
key: key,
onPopPage: onPopPage ??
(route, result) {
final didPop = route.didPop(result);
if (!didPop) {
return false;
}
return true;
},
reportsRouteUpdateToEngine: reportsRouteUpdateToEngine,
pages: pages,
observers: [
GetObserver(),
// GetObserver(),
if (observers != null) ...observers,
],
transitionDelegate:
... ...
... ... @@ -21,21 +21,21 @@ class RouterOutlet<TDelegate extends RouterDelegate<T>, T extends Object>
RouterOutlet({
TDelegate? delegate,
required List<GetPage> Function(T currentNavStack) pickPages,
required Iterable<GetPage> Function(T currentNavStack) pickPages,
required Widget Function(
BuildContext context,
TDelegate,
GetPage? page,
Iterable<GetPage>? page,
)
pageBuilder,
}) : this.builder(
builder: (context, rDelegate, currentConfig) {
final picked =
currentConfig == null ? <GetPage>[] : pickPages(currentConfig);
if (picked.length == 0) {
return pageBuilder(context, rDelegate, null);
var picked =
currentConfig == null ? null : pickPages(currentConfig);
if (picked?.length == 0) {
picked = null;
}
return pageBuilder(context, rDelegate, picked.last);
return pageBuilder(context, rDelegate, picked);
},
delegate: delegate,
);
... ... @@ -76,65 +76,89 @@ class _RouterOutletState<TDelegate extends RouterDelegate<T>, T extends Object>
}
class GetRouterOutlet extends RouterOutlet<GetDelegate, GetNavConfig> {
GetRouterOutlet.builder({
required Widget Function(
BuildContext context,
GetDelegate delegate,
GetNavConfig? currentRoute,
)
builder,
GetDelegate? routerDelegate,
}) : super.builder(
builder: builder,
delegate: routerDelegate,
);
GetRouterOutlet({
String? anchorRoute,
required String initialRoute,
Iterable<GetPage> Function(Iterable<GetPage> afterAnchor)? filterPages,
GlobalKey<NavigatorState>? key,
}) : this.pickPages(
pickPages: (config) {
Iterable<GetPage<dynamic>> ret;
if (anchorRoute == null) {
// jump the ancestor path
final length = Uri.parse(initialRoute).pathSegments.length;
return config.currentTreeBranch
.skip(length)
.take(length)
.toList();
}
ret = config.currentTreeBranch.pickAfterRoute(anchorRoute);
if (filterPages != null) {
ret = filterPages(ret);
}
return ret;
},
emptyPage: (delegate) =>
Get.routeTree.matchRoute(initialRoute).route ??
delegate.notFoundRoute,
key: key,
);
GetRouterOutlet.pickPages({
Widget Function(GetDelegate delegate)? emptyWidget,
GetPage Function(GetDelegate delegate)? emptyPage,
required List<GetPage> Function(GetNavConfig currentNavStack) pickPages,
required Iterable<GetPage> Function(GetNavConfig currentNavStack) pickPages,
bool Function(Route<dynamic>, dynamic)? onPopPage,
required String name,
}) : assert(
(emptyPage == null && emptyWidget == null) ||
(emptyPage != null && emptyWidget == null) ||
(emptyPage == null && emptyWidget != null),
'Either use emptyPage or emptyWidget'),
super(
pageBuilder: (context, rDelegate, page) {
var pageRes = page ?? emptyPage?.call(rDelegate);
if (pageRes != null) {
GlobalKey<NavigatorState>? key,
}) : super(
pageBuilder: (context, rDelegate, pages) {
final pageRes = <GetPage?>[
...?pages,
if (pages == null || pages.length == 0)
emptyPage?.call(rDelegate),
].whereType<GetPage>();
if (pageRes.length > 0) {
return GetNavigator(
onPopPage: onPopPage ??
(a, c) {
(route, result) {
final didPop = route.didPop(result);
if (!didPop) {
return false;
}
return true;
},
pages: [pageRes],
name: name,
pages: pageRes.toList(),
key: key,
);
}
return (emptyWidget?.call(rDelegate) ?? SizedBox.shrink());
},
pickPages: pickPages,
delegate: Get.getDelegate(),
delegate: Get.rootDelegate,
);
}
/// A marker outlet to identify which pages are visual
/// (handled by the navigator) and which are logical
/// (handled by the delegate)
class RouterOutletContainerMiddleWare extends GetMiddleware {
final String stayAt;
RouterOutletContainerMiddleWare(this.stayAt);
GetRouterOutlet.builder({
required Widget Function(
BuildContext context,
GetDelegate delegate,
GetNavConfig? currentRoute,
)
builder,
GetDelegate? routerDelegate,
}) : super.builder(
builder: builder,
delegate: routerDelegate,
);
}
extension PagesListExt on List<GetPage> {
List<GetPage> pickAtRoute(String route) {
return skipWhile((value) => value.name != route).toList();
Iterable<GetPage> pickAtRoute(String route) {
return skipWhile((value) {
return value.name != route;
});
}
List<GetPage> pickAfterRoute(String route) {
return skipWhile((value) => value.name != route).skip(1).toList();
Iterable<GetPage> pickAfterRoute(String route) {
return pickAtRoute(route).skip(1);
}
}
... ...
... ... @@ -118,12 +118,12 @@ class GetCupertinoApp extends StatelessWidget {
final BackButtonDispatcher? backButtonDispatcher;
final CupertinoThemeData? theme;
const GetCupertinoApp.router({
GetCupertinoApp.router({
Key? key,
this.theme,
this.routeInformationProvider,
required RouteInformationParser<Object> this.routeInformationParser,
required RouterDelegate<Object> this.routerDelegate,
RouteInformationParser<Object>? routeInformationParser,
RouterDelegate<Object>? routerDelegate,
this.backButtonDispatcher,
this.builder,
this.title = '',
... ... @@ -163,7 +163,14 @@ class GetCupertinoApp extends StatelessWidget {
this.defaultGlobalState,
this.getPages,
this.unknownRoute,
}) : navigatorObservers = null,
}) : routerDelegate = routerDelegate ??= Get.createDelegate(
notFoundRoute: unknownRoute,
),
routeInformationParser =
routeInformationParser ??= Get.createInformationParser(
initialRoute: getPages?.first.name ?? '/',
),
navigatorObservers = null,
navigatorKey = null,
onGenerateRoute = null,
home = null,
... ... @@ -171,14 +178,23 @@ class GetCupertinoApp extends StatelessWidget {
onUnknownRoute = null,
routes = null,
initialRoute = null,
super(key: key);
super(key: key) {
Get.routerDelegate = routerDelegate;
Get.routeInformationParser = routeInformationParser;
}
Route<dynamic> generator(RouteSettings settings) {
return PageRedirect(settings, unknownRoute).page();
return PageRedirect(settings: settings, unknownRoute: unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) =>
[PageRedirect(RouteSettings(name: name), unknownRoute).page()];
List<Route<dynamic>> initialRoutesGenerate(String name) {
return [
PageRedirect(
settings: RouteSettings(name: name),
unknownRoute: unknownRoute,
).page()
];
}
@override
Widget build(BuildContext context) => GetBuilder<GetMaterialController>(
... ... @@ -203,7 +219,10 @@ class GetCupertinoApp extends StatelessWidget {
Get.customTransition = customTransition;
initialBinding?.dependencies();
Get.addPages(getPages);
if (getPages != null) {
Get.addPages(getPages!);
}
Get.smartManagement = smartManagement;
onInit?.call();
... ...
... ... @@ -13,6 +13,7 @@ class GetMaterialApp extends StatelessWidget {
const GetMaterialApp({
Key? key,
this.navigatorKey,
this.scaffoldMessengerKey,
this.home,
Map<String, Widget Function(BuildContext)> this.routes =
const <String, WidgetBuilder>{},
... ... @@ -43,6 +44,7 @@ class GetMaterialApp extends StatelessWidget {
this.showSemanticsDebugger = false,
this.debugShowCheckedModeBanner = true,
this.shortcuts,
this.scrollBehavior,
this.customTransition,
this.translationsKeys,
this.translations,
... ... @@ -71,6 +73,7 @@ class GetMaterialApp extends StatelessWidget {
super(key: key);
final GlobalKey<NavigatorState>? navigatorKey;
final GlobalKey<ScaffoldMessengerState>? scaffoldMessengerKey;
final Widget? home;
final Map<String, WidgetBuilder>? routes;
final String? initialRoute;
... ... @@ -101,6 +104,7 @@ class GetMaterialApp extends StatelessWidget {
final bool showSemanticsDebugger;
final bool debugShowCheckedModeBanner;
final Map<LogicalKeySet, Intent>? shortcuts;
final ScrollBehavior? scrollBehavior;
final ThemeData? highContrastTheme;
final ThemeData? highContrastDarkTheme;
final Map<Type, Action<Intent>>? actions;
... ... @@ -125,11 +129,12 @@ class GetMaterialApp extends StatelessWidget {
final RouterDelegate<Object>? routerDelegate;
final BackButtonDispatcher? backButtonDispatcher;
const GetMaterialApp.router({
GetMaterialApp.router({
Key? key,
this.routeInformationProvider,
required RouteInformationParser<Object> this.routeInformationParser,
required RouterDelegate<Object> this.routerDelegate,
this.scaffoldMessengerKey,
RouteInformationParser<Object>? routeInformationParser,
RouterDelegate<Object>? routerDelegate,
this.backButtonDispatcher,
this.builder,
this.title = '',
... ... @@ -152,6 +157,7 @@ class GetMaterialApp extends StatelessWidget {
this.showSemanticsDebugger = false,
this.debugShowCheckedModeBanner = true,
this.shortcuts,
this.scrollBehavior,
this.actions,
this.customTransition,
this.translationsKeys,
... ... @@ -172,8 +178,16 @@ class GetMaterialApp extends StatelessWidget {
this.transitionDuration,
this.defaultGlobalState,
this.getPages,
this.navigatorObservers,
this.unknownRoute,
}) : navigatorObservers = null,
}) : routerDelegate = routerDelegate ??= Get.createDelegate(
notFoundRoute: unknownRoute,
),
routeInformationParser =
routeInformationParser ??= Get.createInformationParser(
initialRoute: getPages?.first.name ?? '/',
),
//navigatorObservers = null,
navigatorKey = null,
onGenerateRoute = null,
home = null,
... ... @@ -181,13 +195,35 @@ class GetMaterialApp extends StatelessWidget {
onUnknownRoute = null,
routes = null,
initialRoute = null,
super(key: key);
super(key: key) {
Get.routerDelegate = routerDelegate;
Get.routeInformationParser = routeInformationParser;
}
Route<dynamic> generator(RouteSettings settings) {
return PageRedirect(settings: settings, unknownRoute: unknownRoute).page();
}
Route<dynamic> generator(RouteSettings settings) =>
PageRedirect(settings, unknownRoute).page();
List<Route<dynamic>> initialRoutesGenerate(String name) {
return [
PageRedirect(
settings: RouteSettings(name: name),
unknownRoute: unknownRoute,
).page()
];
}
List<Route<dynamic>> initialRoutesGenerate(String name) =>
[PageRedirect(RouteSettings(name: name), unknownRoute).page()];
Widget defaultBuilder(BuildContext context, Widget? child) {
return Directionality(
textDirection: textDirection ??
(rtlLanguages.contains(Get.locale?.languageCode)
? TextDirection.rtl
: TextDirection.ltr),
child: builder == null
? (child ?? SizedBox.shrink())
: builder!(context, child),
);
}
@override
Widget build(BuildContext context) => GetBuilder<GetMaterialController>(
... ... @@ -212,8 +248,11 @@ class GetMaterialApp extends StatelessWidget {
Get.customTransition = customTransition;
initialBinding?.dependencies();
Get.addPages(getPages);
Get.setDefaultDelegate(routerDelegate);
if (getPages != null) {
Get.addPages(getPages!);
}
//Get.setDefaultDelegate(routerDelegate);
Get.smartManagement = smartManagement;
onInit?.call();
... ... @@ -231,18 +270,11 @@ class GetMaterialApp extends StatelessWidget {
? MaterialApp.router(
routerDelegate: routerDelegate!,
routeInformationParser: routeInformationParser!,
scaffoldMessengerKey: scaffoldMessengerKey,
backButtonDispatcher: backButtonDispatcher,
routeInformationProvider: routeInformationProvider,
key: _.unikey,
builder: (context, child) {
return Directionality(
textDirection: textDirection ??
(rtlLanguages.contains(Get.locale?.languageCode)
? TextDirection.rtl
: TextDirection.ltr),
child: builder == null ? child! : builder!(context, child),
);
},
builder: defaultBuilder,
title: title,
onGenerateTitle: onGenerateTitle,
color: color,
... ... @@ -262,11 +294,13 @@ class GetMaterialApp extends StatelessWidget {
showSemanticsDebugger: showSemanticsDebugger,
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
scrollBehavior: scrollBehavior,
)
: MaterialApp(
key: _.unikey,
navigatorKey:
(navigatorKey == null ? Get.key : Get.addKey(navigatorKey!)),
scaffoldMessengerKey: scaffoldMessengerKey,
home: home,
routes: routes ?? const <String, WidgetBuilder>{},
initialRoute: initialRoute,
... ... @@ -283,15 +317,7 @@ class GetMaterialApp extends StatelessWidget {
GetObserver(routingCallback, Get.routing)
]
..addAll(navigatorObservers!)),
builder: (context, child) {
return Directionality(
textDirection: textDirection ??
(rtlLanguages.contains(Get.locale?.languageCode)
? TextDirection.rtl
: TextDirection.ltr),
child: builder == null ? child! : builder!(context, child),
);
},
builder: defaultBuilder,
title: title,
onGenerateTitle: onGenerateTitle,
color: color,
... ... @@ -311,6 +337,7 @@ class GetMaterialApp extends StatelessWidget {
showSemanticsDebugger: showSemanticsDebugger,
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
scrollBehavior: scrollBehavior,
// actions: actions,
));
}
... ...
... ... @@ -5,10 +5,27 @@ class RouteDecoder {
final List<GetPage> treeBranch;
GetPage? get route => treeBranch.isEmpty ? null : treeBranch.last;
final Map<String, String> parameters;
final Object? arguments;
const RouteDecoder(
this.treeBranch,
this.parameters,
this.arguments,
);
void replaceArguments(Object? arguments) {
final _route = route;
if (_route != null) {
final index = treeBranch.indexOf(_route);
treeBranch[index] = _route.copy(arguments: arguments);
}
}
void replaceParameters(Object? arguments) {
final _route = route;
if (_route != null) {
final index = treeBranch.indexOf(_route);
treeBranch[index] = _route.copy(parameters: parameters);
}
}
}
class ParseRouteTree {
... ... @@ -18,7 +35,7 @@ class ParseRouteTree {
final List<GetPage> routes;
RouteDecoder matchRoute(String name) {
RouteDecoder matchRoute(String name, {Object? arguments}) {
final uri = Uri.parse(name);
// /home/profile/123 => home,profile,123 => /,/home,/home/profile,/home/profile/123
final split = uri.path.split('/').where((element) => element.isNotEmpty);
... ... @@ -53,8 +70,8 @@ class ParseRouteTree {
final mappedTreeBranch = treeBranch
.map(
(e) => e.value.copy(
parameter: {
if (e.value.parameter != null) ...e.value.parameter!,
parameters: {
if (e.value.parameters != null) ...e.value.parameters!,
...params,
},
name: e.key,
... ... @@ -64,6 +81,7 @@ class ParseRouteTree {
return RouteDecoder(
mappedTreeBranch,
params,
arguments,
);
}
... ... @@ -71,6 +89,7 @@ class ParseRouteTree {
return RouteDecoder(
treeBranch.map((e) => e.value).toList(),
params,
arguments,
);
}
... ... @@ -91,21 +110,35 @@ class ParseRouteTree {
List<GetPage> _flattenPage(GetPage route) {
final result = <GetPage>[];
if (route.children == null || route.children!.isEmpty) {
if (route.children.isEmpty) {
return result;
}
final parentPath = route.name;
for (var page in route.children!) {
for (var page in route.children) {
// Add Parent middlewares to children
final pageMiddlewares = page.middlewares ?? <GetMiddleware>[];
pageMiddlewares.addAll(route.middlewares ?? <GetMiddleware>[]);
result.add(_addChild(page, parentPath, pageMiddlewares));
final parentMiddlewares = [
if (page.middlewares != null) ...page.middlewares!,
if (route.middlewares != null) ...route.middlewares!
];
result.add(
_addChild(
page,
parentPath,
parentMiddlewares,
),
);
final children = _flattenPage(page);
for (var child in children) {
pageMiddlewares.addAll(child.middlewares ?? <GetMiddleware>[]);
result.add(_addChild(child, parentPath, pageMiddlewares));
result.add(_addChild(
child,
parentPath,
[
...parentMiddlewares,
if (child.middlewares != null) ...child.middlewares!,
],
));
}
}
return result;
... ... @@ -114,24 +147,9 @@ class ParseRouteTree {
/// Change the Path for a [GetPage]
GetPage _addChild(
GetPage origin, String parentPath, List<GetMiddleware> middlewares) =>
GetPage(
name: (parentPath + origin.name).replaceAll(r'//', '/'),
page: origin.page,
title: origin.title,
alignment: origin.alignment,
transition: origin.transition,
binding: origin.binding,
bindings: origin.bindings,
curve: origin.curve,
customTransition: origin.customTransition,
fullscreenDialog: origin.fullscreenDialog,
maintainState: origin.maintainState,
opaque: origin.opaque,
parameter: origin.parameter,
popGesture: origin.popGesture,
preventDuplicates: origin.preventDuplicates,
transitionDuration: origin.transitionDuration,
origin.copy(
middlewares: middlewares,
name: (parentPath + origin.name).replaceAll(r'//', '/'),
);
GetPage? _findRoute(String name) {
... ...
... ... @@ -4,7 +4,6 @@ import '../../../get_utils/get_utils.dart';
import '../routes/custom_transition.dart';
import '../routes/observers/route_observer.dart';
import '../routes/transitions_type.dart';
import 'parse_route.dart';
class GetMaterialController extends GetxController {
bool testMode = false;
... ... @@ -28,11 +27,16 @@ class GetMaterialController extends GetxController {
Map<String, String?> parameters = {};
late ParseRouteTree routeTree;
CustomTransition? customTransition;
GlobalKey<NavigatorState> key = GlobalKey<NavigatorState>();
var _key = GlobalKey<NavigatorState>(debugLabel: 'Key Created by default');
GlobalKey<NavigatorState> get key => _key;
GlobalKey<NavigatorState>? addKey(GlobalKey<NavigatorState> newKey) {
_key = newKey;
return key;
}
Map<dynamic, GlobalKey<NavigatorState>> keys = {};
... ...
import 'dart:math';
import 'dart:ui' show lerpDouble;
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import '../../../get_core/get_core.dart';
import '../../../get_instance/get_instance.dart';
import '../../get_navigation.dart';
import '../../../get.dart';
import 'custom_transition.dart';
import 'default_transitions.dart';
import 'get_transition_mixin.dart';
import 'route_middleware.dart';
import 'transitions_type.dart';
class GetPageRoute<T> extends PageRoute<T> {
class GetPageRoute<T> extends PageRoute<T> with GetPageRouteTransitionMixin<T> {
/// Creates a page route for use in an iOS designed app.
///
/// The [builder], [maintainState], and [fullscreenDialog] arguments must not
/// be null.
GetPageRoute({
RouteSettings? settings,
this.transitionDuration = const Duration(milliseconds: 300),
this.opaque = true,
this.parameter,
this.gestureWidth = 20.0,
this.curve,
this.alignment,
this.transition,
... ... @@ -27,6 +28,7 @@ class GetPageRoute<T> extends PageRoute<T> {
this.bindings,
this.routeName,
this.page,
this.title,
this.barrierLabel,
this.maintainState = true,
bool fullscreenDialog = false,
... ... @@ -36,35 +38,23 @@ class GetPageRoute<T> extends PageRoute<T> {
@override
final Duration transitionDuration;
final GetPageBuilder? page;
final String? routeName;
final String reference;
final CustomTransition? customTransition;
final Bindings? binding;
final Map<String, String>? parameter;
final List<Bindings>? bindings;
@override
final bool opaque;
final bool? popGesture;
@override
final bool barrierDismissible;
final Transition? transition;
final Curve? curve;
final Alignment? alignment;
final List<GetMiddleware>? middlewares;
@override
... ... @@ -77,308 +67,6 @@ class GetPageRoute<T> extends PageRoute<T> {
final bool maintainState;
@override
bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
// Don't perform outgoing animation if the next route is a
// fullscreen dialog.
return nextRoute is PageRoute && !nextRoute.fullscreenDialog;
}
static bool _isPopGestureEnabled<T>(PageRoute<T> route) {
// ignore: lines_longer_than_80_chars
if (route.isFirst ||
route.willHandlePopInternally ||
route.hasScopedWillPopCallback ||
route.fullscreenDialog ||
route.animation!.status != AnimationStatus.completed ||
route.secondaryAnimation!.status != AnimationStatus.dismissed ||
isPopGestureInProgress(route)) return false;
return true;
}
static _CupertinoBackGestureController<T> _startPopGesture<T>(
PageRoute<T> route) {
assert(_isPopGestureEnabled(route));
return _CupertinoBackGestureController<T>(
navigator: route.navigator!,
controller: route.controller!,
);
}
@override
Widget buildPage(
BuildContext? context,
Animation<double>? animation,
Animation<double>? secondaryAnimation,
) {
// Get.reference = settings.name ?? routeName;
Get.reference = reference;
final middlewareRunner = MiddlewareRunner(middlewares);
final bindingsToBind = middlewareRunner.runOnBindingsStart(bindings);
binding?.dependencies();
if (bindingsToBind != null) {
for (final binding in bindingsToBind) {
binding.dependencies();
}
}
final pageToBuild = middlewareRunner.runOnPageBuildStart(page)!;
return middlewareRunner.runOnPageBuilt(pageToBuild());
}
static bool isPopGestureInProgress(PageRoute<dynamic> route) {
return route.navigator!.userGestureInProgress;
}
bool get popGestureInProgress => isPopGestureInProgress(this);
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
final finalCurve = curve ?? Get.defaultTransitionCurve;
final hasCurve = curve != null;
if (fullscreenDialog && transition == null) {
/// by default, if no curve is defined, use Cupertino transition in the
/// default way (no linearTransition)... otherwise take the curve passed.
return CupertinoFullscreenDialogTransition(
primaryRouteAnimation: hasCurve
? CurvedAnimation(parent: animation, curve: finalCurve)
: animation,
secondaryRouteAnimation: secondaryAnimation,
child: child,
linearTransition: hasCurve);
}
if (customTransition != null) {
return customTransition!.buildTransition(
context,
finalCurve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child,
);
}
/// Apply the curve by default...
final iosAnimation = animation;
animation = CurvedAnimation(parent: animation, curve: finalCurve);
switch (transition ?? Get.defaultTransition) {
case Transition.leftToRight:
return SlideLeftTransition().buildTransitions(
context,
curve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.downToUp:
return SlideDownTransition().buildTransitions(
context,
curve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.upToDown:
return SlideTopTransition().buildTransitions(
context,
curve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.noTransition:
return popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child;
case Transition.rightToLeft:
return SlideRightTransition().buildTransitions(
context,
curve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.zoom:
return ZoomInTransition().buildTransitions(
context,
curve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.fadeIn:
return FadeInTransition().buildTransitions(
context,
curve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.rightToLeftWithFade:
return RightToLeftFadeTransition().buildTransitions(
context,
curve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.leftToRightWithFade:
return LeftToRightFadeTransition().buildTransitions(
context,
curve,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.cupertino:
return CupertinoPageTransitionsBuilder().buildTransitions(
this,
context,
iosAnimation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.size:
return SizeTransitions().buildTransitions(
context,
curve!,
alignment,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.fade:
return FadeUpwardsPageTransitionsBuilder().buildTransitions(
this,
context,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.topLevel:
return ZoomPageTransitionsBuilder().buildTransitions(
this,
context,
animation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
case Transition.native:
return PageTransitionsTheme().buildTransitions(
this,
context,
iosAnimation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
default:
if (Get.customTransition != null) {
return Get.customTransition!.buildTransition(
context, curve, alignment, animation, secondaryAnimation, child);
}
return PageTransitionsTheme().buildTransitions(
this,
context,
iosAnimation,
secondaryAnimation,
popGesture ?? Get.defaultPopGesture
? _CupertinoBackGestureDetector<T>(
enabledCallback: () => _isPopGestureEnabled<T>(this),
onStartPopGesture: () => _startPopGesture<T>(this),
child: child)
: child);
}
}
@override
void dispose() {
super.dispose();
if (Get.smartManagement != SmartManagement.onlyBuilder) {
... ... @@ -396,220 +84,30 @@ class GetPageRoute<T> extends PageRoute<T> {
final middlewareRunner = MiddlewareRunner(middlewares);
middlewareRunner.runOnPageDispose();
}
}
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<T> extends StatefulWidget {
const _CupertinoBackGestureDetector({
Key? key,
required this.enabledCallback,
required this.onStartPopGesture,
required this.child,
}) : super(key: key);
final Widget child;
final ValueGetter<bool> enabledCallback;
final ValueGetter<_CupertinoBackGestureController<T>> onStartPopGesture;
@override
_CupertinoBackGestureDetectorState<T> createState() =>
_CupertinoBackGestureDetectorState<T>();
}
class _CupertinoBackGestureDetectorState<T>
extends State<_CupertinoBackGestureDetector<T>> {
_CupertinoBackGestureController<T>? _backGestureController;
Widget buildContent(BuildContext context) {
Get.reference = reference;
final middlewareRunner = MiddlewareRunner(middlewares);
final bindingsToBind = middlewareRunner.runOnBindingsStart(bindings);
late HorizontalDragGestureRecognizer _recognizer;
binding?.dependencies();
if (bindingsToBind != null) {
for (final binding in bindingsToBind) {
binding.dependencies();
}
}
@override
void initState() {
super.initState();
_recognizer = HorizontalDragGestureRecognizer(debugOwner: this)
..onStart = _handleDragStart
..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd
..onCancel = _handleDragCancel;
final pageToBuild = middlewareRunner.runOnPageBuildStart(page)!;
return middlewareRunner.runOnPageBuilt(pageToBuild());
}
@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;
default:
return value;
}
}
final String? title;
@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.
var 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>[
widget.child,
PositionedDirectional(
start: 0.0,
width: dragAreaWidth,
top: 0.0,
bottom: 0.0,
child: Listener(
onPointerDown: _handlePointerDown,
behavior: HitTestBehavior.translucent,
),
),
],
);
}
}
class _CupertinoBackGestureController<T> {
/// 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,
}) {
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;
}
String get debugLabel => '${super.debugLabel}(${settings.name})';
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 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 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.
late AnimationStatusListener animationStatusCallback;
animationStatusCallback = (status) {
navigator.didStopUserGesture();
controller.removeStatusListener(animationStatusCallback);
};
controller.addStatusListener(animationStatusCallback);
} else {
navigator.didStopUserGesture();
}
}
@override
final double gestureWidth;
}
... ...
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
... ... @@ -29,13 +30,15 @@ class PathDecoded {
class GetPage<T> extends Page<T> {
final GetPageBuilder page;
final bool? popGesture;
final Map<String, String>? parameter;
final Map<String, String>? parameters;
final String? title;
final Transition? transition;
final Curve curve;
final bool? participatesInRootNavigator;
final Alignment? alignment;
final bool maintainState;
final bool opaque;
final double gestureWidth;
final Bindings? binding;
final List<Bindings> bindings;
final CustomTransition? customTransition;
... ... @@ -49,12 +52,12 @@ class GetPage<T> extends Page<T> {
// RouteSettings get settings => this;
@override
Object? get arguments => Get.arguments;
final Object? arguments;
@override
final String name;
final List<GetPage>? children;
final List<GetPage> children;
final List<GetMiddleware>? middlewares;
final PathDecoded path;
final GetPage? unknownRoute;
... ... @@ -63,11 +66,13 @@ class GetPage<T> extends Page<T> {
required this.name,
required this.page,
this.title,
this.participatesInRootNavigator,
this.gestureWidth = 20,
// RouteSettings settings,
this.maintainState = true,
this.curve = Curves.linear,
this.alignment,
this.parameter,
this.parameters,
this.opaque = true,
this.transitionDuration,
this.popGesture,
... ... @@ -76,10 +81,11 @@ class GetPage<T> extends Page<T> {
this.transition,
this.customTransition,
this.fullscreenDialog = false,
this.children,
this.children = const <GetPage>[],
this.middlewares,
this.unknownRoute,
this.preventDuplicates = false,
this.arguments,
this.preventDuplicates = true,
}) : path = _nameToRegex(name),
super(
key: ValueKey(name),
... ... @@ -109,11 +115,11 @@ class GetPage<T> extends Page<T> {
return PathDecoded(RegExp('^$stringPath\$'), keys);
}
GetPage copy({
GetPage<T> copy({
String? name,
GetPageBuilder? page,
bool? popGesture,
Map<String, String>? parameter,
Map<String, String>? parameters,
String? title,
Transition? transition,
Curve? curve,
... ... @@ -130,13 +136,18 @@ class GetPage<T> extends Page<T> {
GetPage? unknownRoute,
List<GetMiddleware>? middlewares,
bool? preventDuplicates,
double? gestureWidth,
bool? participatesInRootNavigator,
Object? arguments,
}) {
return GetPage(
participatesInRootNavigator:
participatesInRootNavigator ?? this.participatesInRootNavigator,
preventDuplicates: preventDuplicates ?? this.preventDuplicates,
name: name ?? this.name,
page: page ?? this.page,
popGesture: popGesture ?? this.popGesture,
parameter: parameter ?? this.parameter,
parameters: parameters ?? this.parameters,
title: title ?? this.title,
transition: transition ?? this.transition,
curve: curve ?? this.curve,
... ... @@ -151,14 +162,18 @@ class GetPage<T> extends Page<T> {
children: children ?? this.children,
unknownRoute: unknownRoute ?? this.unknownRoute,
middlewares: middlewares ?? this.middlewares,
gestureWidth: gestureWidth ?? this.gestureWidth,
arguments: arguments ?? this.arguments,
);
}
@override
Route<T> createRoute(BuildContext context) {
// return GetPageRoute<T>(settings: this, page: page);
return PageRedirect(
this,
unknownRoute,
).page<T>();
route: this,
settings: this,
unknownRoute: unknownRoute,
).getPageToRoute<T>(this, unknownRoute);
}
}
... ...
import 'dart:math';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import '../../../get.dart';
import 'default_transitions.dart';
import 'transitions_type.dart';
//const double _kBackGestureWidth = 20.0;
const double _kMinFlingVelocity = 1.0; // Screen widths per second.
// An eyeballed value for the maximum time it takes
//for a page to animate forward
// if the user releases a page mid swipe.
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; // Milliseconds.
mixin GetPageRouteTransitionMixin<T> on PageRoute<T> {
/// Builds the primary contents of the route.
@protected
Widget buildContent(BuildContext context);
/// {@template flutter.cupertino.CupertinoRouteTransitionMixin.title}
/// A title string for this route.
///
/// Used to auto-populate [CupertinoNavigationBar] and
/// [CupertinoSliverNavigationBar]'s `middle`/`largeTitle` widgets when
/// one is not manually supplied.
/// {@endtemplate}
String? get title;
double get gestureWidth;
ValueNotifier<String?>? _previousTitle;
/// The title string of the previous [CupertinoPageRoute].
///
/// 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 [CupertinoPageRoute].
///
/// See also:
///
/// * [ValueListenableBuilder], which can be used to listen and rebuild
/// widgets based on a ValueListenable.
ValueListenable<String?> get previousTitle {
assert(
_previousTitle != null,
'''
Cannot read the previousTitle for a route that has not yet been installed''',
);
return _previousTitle!;
}
@override
void didChangePrevious(Route<dynamic>? previousRoute) {
final previousTitleString = previousRoute is CupertinoRouteTransitionMixin
? previousRoute.title
: null;
if (_previousTitle == null) {
_previousTitle = ValueNotifier<String?>(previousTitleString);
} else {
_previousTitle!.value = previousTitleString;
}
super.didChangePrevious(previousRoute);
}
@override
// A relatively rigorous eyeball estimation.
Duration get transitionDuration => const Duration(milliseconds: 400);
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
// Don't perform outgoing animation if the next route is a
// fullscreen dialog.
return nextRoute is CupertinoRouteTransitionMixin &&
!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<dynamic> 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<T>(PageRoute<T> 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<double> animation,
Animation<double> secondaryAnimation) {
final child = buildContent(context);
final Widget result = Semantics(
scopesRoute: true,
explicitChildNodes: true,
child: child,
);
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<T> _startPopGesture<T>(
PageRoute<T> route) {
assert(_isPopGestureEnabled(route));
return CupertinoBackGestureController<T>(
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 [CupertinoPageRoute.buildTransitions].
///
/// This method can be applied to any [PageRoute], not just
/// [CupertinoPageRoute]. 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].
static Widget buildPageTransitions<T>(
PageRoute<T> rawRoute,
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
// 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.
final route = rawRoute as GetPageRoute<T>;
final linearTransition = isPopGestureInProgress(route);
final finalCurve = route.curve ?? Get.defaultTransitionCurve;
final hasCurve = route.curve != null;
if (route.fullscreenDialog && route.transition == null) {
return CupertinoFullscreenDialogTransition(
primaryRouteAnimation: hasCurve
? CurvedAnimation(parent: animation, curve: finalCurve)
: animation,
secondaryRouteAnimation: secondaryAnimation,
child: child,
linearTransition: linearTransition,
);
} else {
if (route.customTransition != null) {
return route.customTransition!.buildTransition(
context,
finalCurve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child,
);
}
/// Apply the curve by default...
final iosAnimation = animation;
animation = CurvedAnimation(parent: animation, curve: finalCurve);
switch (route.transition ?? Get.defaultTransition) {
case Transition.leftToRight:
return SlideLeftTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.downToUp:
return SlideDownTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.upToDown:
return SlideTopTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.noTransition:
return route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child;
case Transition.rightToLeft:
return SlideRightTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.zoom:
return ZoomInTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.fadeIn:
return FadeInTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.rightToLeftWithFade:
return RightToLeftFadeTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.leftToRightWithFade:
return LeftToRightFadeTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.cupertino:
return CupertinoPageTransition(
primaryRouteAnimation: animation,
secondaryRouteAnimation: secondaryAnimation,
linearTransition: linearTransition,
child: CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
);
case Transition.size:
return SizeTransitions().buildTransitions(
context,
route.curve!,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.fade:
return FadeUpwardsPageTransitionsBuilder().buildTransitions(
route,
context,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.topLevel:
return ZoomPageTransitionsBuilder().buildTransitions(
route,
context,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.native:
return PageTransitionsTheme().buildTransitions(
route,
context,
iosAnimation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
default:
if (Get.customTransition != null) {
return Get.customTransition!.buildTransition(context, route.curve,
route.alignment, animation, secondaryAnimation, child);
}
return PageTransitionsTheme().buildTransitions(
route,
context,
iosAnimation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
}
}
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return buildPageTransitions<T>(
this, context, animation, secondaryAnimation, child);
}
}
class CupertinoBackGestureDetector<T> extends StatefulWidget {
const CupertinoBackGestureDetector({
Key? key,
required this.enabledCallback,
required this.onStartPopGesture,
required this.child,
required this.gestureWidth,
}) : super(key: key);
final Widget child;
final double gestureWidth;
final ValueGetter<bool> enabledCallback;
final ValueGetter<CupertinoBackGestureController<T>> onStartPopGesture;
@override
CupertinoBackGestureDetectorState<T> createState() =>
CupertinoBackGestureDetectorState<T>();
}
class CupertinoBackGestureDetectorState<T>
extends State<CupertinoBackGestureDetector<T>> {
CupertinoBackGestureController<T>? _backGestureController;
late 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;
}
}
@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.
var dragAreaWidth = Directionality.of(context) == TextDirection.ltr
? MediaQuery.of(context).padding.left
: MediaQuery.of(context).padding.right;
dragAreaWidth = max(dragAreaWidth, widget.gestureWidth);
return Stack(
fit: StackFit.passthrough,
children: <Widget>[
widget.child,
PositionedDirectional(
start: 0.0,
width: dragAreaWidth,
top: 0.0,
bottom: 0.0,
child: Listener(
onPointerDown: _handlePointerDown,
behavior: HitTestBehavior.translucent,
),
),
],
);
}
}
class CupertinoBackGestureController<T> {
/// 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,
}) {
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;
final 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 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 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.
late AnimationStatusListener animationStatusCallback;
animationStatusCallback = (status) {
navigator.didStopUserGesture();
controller.removeStatusListener(animationStatusCallback);
};
controller.addStatusListener(animationStatusCallback);
} else {
navigator.didStopUserGesture();
}
}
}
... ...