Committed by
GitHub
Merge branch 'jonataslaw:master' into master
Showing
41 changed files
with
1641 additions
and
1776 deletions
Too many changes to show.
To preserve performance only 41 of 41+ files are displayed.
No preview for this file type
1 | +## [4.5.1] - Big Update | ||
2 | +Fix Snackbar when it have action and icon the same time | ||
3 | + | ||
4 | +## [4.5.0] - Big Update | ||
5 | +To have a context-free, page-agnostic snackbar, we used OverlayRoute to display a partial route. | ||
6 | +However this had several problems: | ||
7 | + | ||
8 | +1: There was no possibility to close the page without closing the snackbar | ||
9 | +2: Get.back() could cause problems with tests of Get.isSnackbarOpen not being properly invoked | ||
10 | +3: Sometimes when using iOS popGesture with an open snackbar, some visual inconsistency might appear. | ||
11 | +4: When going to another route, the snackbar was not displayed on the new page, and if the user clicked on the new route as soon as he received a Snackbar, he could not read it. | ||
12 | + | ||
13 | +We remade the Snackbar from scratch, having its Api based on Overlay, and now opening a Snackbar won't be tied to a route, you can normally navigate routes while a Snackbar is shown at the top (or bottom), and even the PopGesture of the iOS is not influenced by it. | ||
14 | + | ||
15 | +Using Get.back() is handy, it's a small command, which closes routes, dialogs, snackbars, bottomsheets, etc, however Getx 5 will prioritize code safety, and splitting will reduce the check code as well. Currently we have to check if a snackbar is open, to close the snackbar and prevent the app from going back a page, all this boilerplate code will be removed, at the cost of having what it closes in front of Get.back command. | ||
16 | + | ||
17 | +For backwards compatibility, Get.back() still works for closing routes and overlays, however two new commands have been added: Get.closeCurrentSnackbar() and Get.closeAllSnackbars(). | ||
18 | +Maybe we will have a clearer api in GetX 5, and maybe Get.back() will continue to do everything like it does today. The community will be consulted about the desired api. However version 5 will definitely have commands like: Get.closeCurrentSnackbar, Get.closeCurrentDialog etc. There is also the possibility to close a specific snackbar using the return of Get.snackbar, which will no longer return a void, and now return a SnackbarController. | ||
19 | + | ||
20 | +Snackbars now also have a Queue, and no longer stack one on top of the other, preventing viewing. GetX now has flexible, customizable, route-independent, and completely stable Snackbars. | ||
21 | + | ||
22 | +Fixed bugs where the snackbar showed an error in debug mode for a fraction of a second. We found that Flutter has a bug with blur below 0.001, so we set the minimum overlayBlur value to this value if it is ==true. | ||
23 | + | ||
24 | +Errors with internationalization were also fixed, where if you are in UK, and the app had the en_US language, you didn't have American English by default. Now, if the country code is not present, it will automatically fetch the language code before fetching a fallbackLanguage. | ||
25 | + | ||
26 | +Update locale also now returns a Future, allowing you to perform an action only when the language has already changed (@MHosssam) | ||
27 | + | ||
28 | +We are very happy to announce that GetX is now documented in Japanese as well, thanks to (@toshi-kuji) | ||
29 | + | ||
30 | +GetX has always been focused on transparency. You can tell what's going on with your app just by reading the logs on the console. However, these logs shouldn't appear in production, so it now only appears in debug mode (@maxzod) | ||
31 | + | ||
32 | +@maxzod has also started translating the docs into Arabic, we hope the documentation will be complete soon. | ||
33 | + | ||
34 | +Some remaining package logs have been moved to Get.log (@gairick-saha) | ||
35 | + | ||
36 | +RxList.removeWhere received performance optimizations (@zuvola) | ||
37 | + | ||
38 | +Optimizations in GetConnect and added the ability to modify all request items in GetConnect (@rodrigorahman) | ||
39 | + | ||
40 | +The current route could be inconsistent if a dialog were opened after a transition, fixed by @xiangzy1 | ||
41 | + | ||
42 | +Fixed try/catch case missed in socket_notifier (@ShookLyngs) | ||
43 | + | ||
44 | +Also we had fixes in the docs: @DeathGun3344 @pinguluk | ||
45 | + | ||
46 | +GetX also surpassed the incredible mark of more than 7000 likes, being the most liked package in all pub.dev, went from 99% to 100% popularity, and has more than 5.3k stars on github. Documentation is now available in 12 languages, and we're happy for all the engagement from your community. | ||
47 | + | ||
48 | +This update is a preparation update for version 5, which will be released later this year. | ||
49 | + | ||
50 | +Breaking and Depreciation: | ||
51 | +GetBar is now deprecated, use GetSnackbar instead. | ||
52 | +dismissDirection now gets a DismissDirection, making the Snackbar more customizable. | ||
53 | + | ||
54 | + | ||
55 | + | ||
56 | + | ||
57 | + | ||
1 | ## [4.3.8] | 58 | ## [4.3.8] |
2 | - Fix nav2 toNamed remove the route | 59 | - Fix nav2 toNamed remove the route |
3 | 60 |
1 |  | 1 |  |
2 | 2 | ||
3 | [](https://pub.dev/packages/get) | 3 | [](https://pub.dev/packages/get) |
4 | +[](https://pub.dev/packages/sentry/score) | ||
4 | [](https://pub.dev/packages/get/score) | 5 | [](https://pub.dev/packages/get/score) |
6 | +[](https://pub.dev/packages/get/score) | ||
5 |  | 7 |  |
6 | [](https://pub.dev/packages/effective_dart) | 8 | [](https://pub.dev/packages/effective_dart) |
7 | [](https://discord.com/invite/9Hpt99N) | 9 | [](https://discord.com/invite/9Hpt99N) |
@@ -3,6 +3,9 @@ | @@ -3,6 +3,9 @@ | ||
3 | *Idiomas: Español (este archivo), [Vietnamita](README-vi.md), [Indonesio](README.id-ID.md), [Urdu](README.ur-PK.md), [Lengua china](README.zh-cn.md), [Inglés](README.md), [Portugués de Brasil](README.pt-br.md), [Ruso](README.ru.md), [Polaco](README.pl.md), [Coreano](README.ko-kr.md), [Francés](README-fr.md).* | 3 | *Idiomas: Español (este archivo), [Vietnamita](README-vi.md), [Indonesio](README.id-ID.md), [Urdu](README.ur-PK.md), [Lengua china](README.zh-cn.md), [Inglés](README.md), [Portugués de Brasil](README.pt-br.md), [Ruso](README.ru.md), [Polaco](README.pl.md), [Coreano](README.ko-kr.md), [Francés](README-fr.md).* |
4 | 4 | ||
5 | [](https://pub.dev/packages/get) | 5 | [](https://pub.dev/packages/get) |
6 | +[](https://pub.dev/packages/sentry/score) | ||
7 | +[](https://pub.dev/packages/get/score) | ||
8 | +[](https://pub.dev/packages/get/score) | ||
6 |  | 9 |  |
7 | [](https://pub.dev/packages/effective_dart) | 10 | [](https://pub.dev/packages/effective_dart) |
8 | [](https://discord.com/invite/9Hpt99N) | 11 | [](https://discord.com/invite/9Hpt99N) |
@@ -3,7 +3,9 @@ | @@ -3,7 +3,9 @@ | ||
3 | **Langues: Français (Ce fichier), [Anglais](README.md), [Vietnamien](README-vi.md), [Indonésien](README.id-ID.md), [Urdu](README.ur-PK.md), [Chinois](README.zh-cn.md), [Portuguais du Brésil](README.pt-br.md), [Espagnol](README-es.md), [Russe](README.ru.md), [Polonais](README.pl.md), [Koréen](README.ko-kr.md).** | 3 | **Langues: Français (Ce fichier), [Anglais](README.md), [Vietnamien](README-vi.md), [Indonésien](README.id-ID.md), [Urdu](README.ur-PK.md), [Chinois](README.zh-cn.md), [Portuguais du Brésil](README.pt-br.md), [Espagnol](README-es.md), [Russe](README.ru.md), [Polonais](README.pl.md), [Koréen](README.ko-kr.md).** |
4 | 4 | ||
5 | [](https://pub.dev/packages/get) | 5 | [](https://pub.dev/packages/get) |
6 | +[](https://pub.dev/packages/sentry/score) | ||
6 | [](https://pub.dev/packages/get/score) | 7 | [](https://pub.dev/packages/get/score) |
8 | +[](https://pub.dev/packages/get/score) | ||
7 |  | 9 |  |
8 | [](https://pub.dev/packages/effective_dart) | 10 | [](https://pub.dev/packages/effective_dart) |
9 | [](https://discord.com/invite/9Hpt99N) | 11 | [](https://discord.com/invite/9Hpt99N) |
@@ -3,7 +3,9 @@ | @@ -3,7 +3,9 @@ | ||
3 | **Ngôn ngữ: Tiếng Việt (file này), [English](README.md), [Indonesian](README.id-ID.md), [Urdu](README.ur-PK.md), [Chinese](README.zh-cn.md), [Brazilian Portuguese](README.pt-br.md), [Spanish](README-es.md), [Russian](README.ru.md), [Polish](README.pl.md), [Korean](README.ko-kr.md), [French](README-fr.md).** | 3 | **Ngôn ngữ: Tiếng Việt (file này), [English](README.md), [Indonesian](README.id-ID.md), [Urdu](README.ur-PK.md), [Chinese](README.zh-cn.md), [Brazilian Portuguese](README.pt-br.md), [Spanish](README-es.md), [Russian](README.ru.md), [Polish](README.pl.md), [Korean](README.ko-kr.md), [French](README-fr.md).** |
4 | 4 | ||
5 | [](https://pub.dev/packages/get) | 5 | [](https://pub.dev/packages/get) |
6 | +[](https://pub.dev/packages/sentry/score) | ||
6 | [](https://pub.dev/packages/get/score) | 7 | [](https://pub.dev/packages/get/score) |
8 | +[](https://pub.dev/packages/get/score) | ||
7 |  | 9 |  |
8 | [](https://pub.dev/packages/effective_dart) | 10 | [](https://pub.dev/packages/effective_dart) |
9 | [](https://discord.com/invite/9Hpt99N) | 11 | [](https://discord.com/invite/9Hpt99N) |
@@ -3,7 +3,9 @@ | @@ -3,7 +3,9 @@ | ||
3 | **Bahasa: Indonesia (file ini), [Inggris](README.md), [Orang Vietnam](README-vi.md), [Urdu](README.ur-PK.md), [China](README.zh-cn.md), [Portugis (Brazil)](README.pt-br.md), [Spanyol](README-es.md), [Russia](README.ru.md), [Polandia](README.pl.md), [Korea](README.ko-kr.md), [French](README-fr.md)** | 3 | **Bahasa: Indonesia (file ini), [Inggris](README.md), [Orang Vietnam](README-vi.md), [Urdu](README.ur-PK.md), [China](README.zh-cn.md), [Portugis (Brazil)](README.pt-br.md), [Spanyol](README-es.md), [Russia](README.ru.md), [Polandia](README.pl.md), [Korea](README.ko-kr.md), [French](README-fr.md)** |
4 | 4 | ||
5 | [](https://pub.dev/packages/get) | 5 | [](https://pub.dev/packages/get) |
6 | +[](https://pub.dev/packages/sentry/score) | ||
6 | [](https://pub.dev/packages/get/score) | 7 | [](https://pub.dev/packages/get/score) |
8 | +[](https://pub.dev/packages/get/score) | ||
7 |  | 9 |  |
8 | [](https://pub.dev/packages/effective_dart) | 10 | [](https://pub.dev/packages/effective_dart) |
9 | [](https://discord.com/invite/9Hpt99N) | 11 | [](https://discord.com/invite/9Hpt99N) |
1 |  | 1 |  |
2 | 2 | ||
3 | [](https://pub.dev/packages/get) | 3 | [](https://pub.dev/packages/get) |
4 | +[](https://pub.dev/packages/sentry/score) | ||
4 | [](https://pub.dev/packages/get/score) | 5 | [](https://pub.dev/packages/get/score) |
6 | +[](https://pub.dev/packages/get/score) | ||
5 |  | 7 |  |
6 | [](https://pub.dev/packages/effective_dart) | 8 | [](https://pub.dev/packages/effective_dart) |
7 | [](https://discord.com/invite/9Hpt99N) | 9 | [](https://discord.com/invite/9Hpt99N) |
1 |  | 1 |  |
2 | 2 | ||
3 | [](https://pub.dev/packages/get) | 3 | [](https://pub.dev/packages/get) |
4 | +[](https://pub.dev/packages/sentry/score) | ||
4 | [](https://pub.dev/packages/get/score) | 5 | [](https://pub.dev/packages/get/score) |
6 | +[](https://pub.dev/packages/get/score) | ||
5 |  | 7 |  |
6 | [](https://pub.dev/packages/effective_dart) | 8 | [](https://pub.dev/packages/effective_dart) |
7 | [](https://discord.com/invite/9Hpt99N) | 9 | [](https://discord.com/invite/9Hpt99N) |
@@ -326,7 +328,7 @@ Text(controller.textFromApi); | @@ -326,7 +328,7 @@ Text(controller.textFromApi); | ||
326 | 328 | ||
327 | ### 종속성 관리에 대한 자세한 내용 | 329 | ### 종속성 관리에 대한 자세한 내용 |
328 | 330 | ||
329 | -**종속성 관리에 대한 더 제사한 사항은 [여기](./documentation/kr_KO/dependency_management.md)에 있습니다.** | 331 | +**종속성 관리에 대한 더 자세한 사항은 [여기](./documentation/kr_KO/dependency_management.md)에 있습니다.** |
330 | 332 | ||
331 | # 기능들 | 333 | # 기능들 |
332 | 334 | ||
@@ -1090,6 +1092,73 @@ class SettingsService extends GetxService { | @@ -1090,6 +1092,73 @@ class SettingsService extends GetxService { | ||
1090 | 따라서 앱 실행중 절대로 유지되어야 하는 클래스 인스턴스가 필요하면 | 1092 | 따라서 앱 실행중 절대로 유지되어야 하는 클래스 인스턴스가 필요하면 |
1091 | `GetxService`를 사용하세요. | 1093 | `GetxService`를 사용하세요. |
1092 | 1094 | ||
1095 | +### 테스트 | ||
1096 | + | ||
1097 | +당신은 당신의 컨트롤러들을 생성주기를 포함하여 다른 어떤 클래스처럼 테스트할 수 있습니다 : | ||
1098 | + | ||
1099 | +```dart | ||
1100 | +class Controller extends GetxController { | ||
1101 | + @override | ||
1102 | + void onInit() { | ||
1103 | + super.onInit(); | ||
1104 | + //name2로 값 변경 | ||
1105 | + name.value = 'name2'; | ||
1106 | + } | ||
1107 | + | ||
1108 | + @override | ||
1109 | + void onClose() { | ||
1110 | + name.value = ''; | ||
1111 | + super.onClose(); | ||
1112 | + } | ||
1113 | + | ||
1114 | + final name = 'name1'.obs; | ||
1115 | + | ||
1116 | + void changeName() => name.value = 'name3'; | ||
1117 | +} | ||
1118 | + | ||
1119 | +void main() { | ||
1120 | + test(''' | ||
1121 | +Test the state of the reactive variable "name" across all of its lifecycles''', | ||
1122 | + () { | ||
1123 | + /// 당신은 생성주기를 제외하고 컨트롤러를 테스트할 수 있습니다, | ||
1124 | + /// 그러나 당신이 사용하지 않는다면 추천되지 않습니다 | ||
1125 | + /// GetX 종속성 주입 | ||
1126 | + final controller = Controller(); | ||
1127 | + expect(controller.name.value, 'name1'); | ||
1128 | + | ||
1129 | + /// 당신이 그것을 사용한다면, 당신은 모든 것을 테스트할 수 있습니다, | ||
1130 | + /// 각각의 생성주기 이후 어플리케이션의 상태를 포함하여. | ||
1131 | + Get.put(controller); // onInit was called | ||
1132 | + expect(controller.name.value, 'name2'); | ||
1133 | + | ||
1134 | + /// 당신의 함수를 테스트하세요 | ||
1135 | + controller.changeName(); | ||
1136 | + expect(controller.name.value, 'name3'); | ||
1137 | + | ||
1138 | + /// onClose 호출됨 | ||
1139 | + Get.delete<Controller>(); | ||
1140 | + | ||
1141 | + expect(controller.name.value, ''); | ||
1142 | + }); | ||
1143 | +} | ||
1144 | +``` | ||
1145 | + | ||
1146 | +#### 팁들 | ||
1147 | + | ||
1148 | +##### Mockito 또는 mocktail | ||
1149 | +당신이 당신의 GetxController/GetxService를 모킹하려고 한다면, 당신은 GetxController를 extend 하고, Mock과 mixin 하라, 그렇게 되면 | ||
1150 | + | ||
1151 | +```dart | ||
1152 | +class NotificationServiceMock extends GetxService with Mock implements NotificationService {} | ||
1153 | +``` | ||
1154 | + | ||
1155 | +##### Get.reset() 사용하기 | ||
1156 | +당신이 위젯 또는 테스트 그룹을 테스트하고 있다면, 당신의 테스트의 마지막 또는 해제 때 당신의 이전 테스트에서 모든 설정을 리셋하기 위해 Get.rest을 사용하십시오 | ||
1157 | + | ||
1158 | +##### Get.testMode | ||
1159 | +당신이 당신의 컨트롤러에서 당신의 네비게이션을 사용하고 있다면, 당신의 메인의 시작에 `Get.testMode = true` 를 사용하십시오. | ||
1160 | + | ||
1161 | + | ||
1093 | # 2.0의 주요 변경점 | 1162 | # 2.0의 주요 변경점 |
1094 | 1163 | ||
1095 | 1- Rx 타입들: | 1164 | 1- Rx 타입들: |
1 |  | 1 |  |
2 | 2 | ||
3 | [](https://pub.dev/packages/get) | 3 | [](https://pub.dev/packages/get) |
4 | +[](https://pub.dev/packages/sentry/score) | ||
4 | [](https://pub.dev/packages/get/score) | 5 | [](https://pub.dev/packages/get/score) |
6 | +[](https://pub.dev/packages/get/score) | ||
5 |  | 7 |  |
6 | [](https://pub.dev/packages/effective_dart) | 8 | [](https://pub.dev/packages/effective_dart) |
7 | [](https://discord.com/invite/9Hpt99N) | 9 | [](https://discord.com/invite/9Hpt99N) |
@@ -3,6 +3,9 @@ | @@ -3,6 +3,9 @@ | ||
3 | *Languages: [English](README.md), [Wietnamski](README-vi.md), [Indonezyjski](README.id-ID.md), [Urdu](README.ur-PK.md), [Język chiński](README.zh-cn.md), [Brazilian Portuguese](README.pt-br.md), [Spanish](README-es.md), [Russian](README.ru.md), Polish (Jesteś tu), [Koreański](README.ko-kr.md), [French](README-fr.md)* | 3 | *Languages: [English](README.md), [Wietnamski](README-vi.md), [Indonezyjski](README.id-ID.md), [Urdu](README.ur-PK.md), [Język chiński](README.zh-cn.md), [Brazilian Portuguese](README.pt-br.md), [Spanish](README-es.md), [Russian](README.ru.md), Polish (Jesteś tu), [Koreański](README.ko-kr.md), [French](README-fr.md)* |
4 | 4 | ||
5 | [](https://pub.dev/packages/get) | 5 | [](https://pub.dev/packages/get) |
6 | +[](https://pub.dev/packages/sentry/score) | ||
7 | +[](https://pub.dev/packages/get/score) | ||
8 | +[](https://pub.dev/packages/get/score) | ||
6 |  | 9 |  |
7 | [](https://pub.dev/packages/effective_dart) | 10 | [](https://pub.dev/packages/effective_dart) |
8 | [](https://discord.com/invite/9Hpt99N) | 11 | [](https://discord.com/invite/9Hpt99N) |
@@ -3,7 +3,9 @@ | @@ -3,7 +3,9 @@ | ||
3 | **Idiomas: [Inglês](README.md), [Vietnamita](README-vi.md), [Indonésia](README.id-ID.md), [Urdu](README.ur-PK.md), [Chinês](README.zh-cn.md), Português (este arquivo), [Espanhol](README-es.md), [Russo](README.ru.md), [Polonês](README.pl.md), [Coreano](README.ko-kr.md), [Francês](README-fr.md)** | 3 | **Idiomas: [Inglês](README.md), [Vietnamita](README-vi.md), [Indonésia](README.id-ID.md), [Urdu](README.ur-PK.md), [Chinês](README.zh-cn.md), Português (este arquivo), [Espanhol](README-es.md), [Russo](README.ru.md), [Polonês](README.pl.md), [Coreano](README.ko-kr.md), [Francês](README-fr.md)** |
4 | 4 | ||
5 | [](https://pub.dev/packages/get) | 5 | [](https://pub.dev/packages/get) |
6 | +[](https://pub.dev/packages/sentry/score) | ||
6 | [](https://pub.dev/packages/get/score) | 7 | [](https://pub.dev/packages/get/score) |
8 | +[](https://pub.dev/packages/get/score) | ||
7 |  | 9 |  |
8 | [](https://pub.dev/packages/effective_dart) | 10 | [](https://pub.dev/packages/effective_dart) |
9 | [](https://discord.com/invite/9Hpt99N) | 11 | [](https://discord.com/invite/9Hpt99N) |
@@ -3,6 +3,9 @@ | @@ -3,6 +3,9 @@ | ||
3 | _Языки: Русский (этот файл), [вьетнамский](README-vi.md), [индонезийский](README.id-ID.md), [урду](README.ur-PK.md), [Английский](README.md), [Китайский](README.zh-cn.md), [Бразильский Португальский](README.pt-br.md), [Испанский](README-es.md), [Польский](README.pl.md), [Kорейский](README.ko-kr.md), [French](README-fr.md)._ | 3 | _Языки: Русский (этот файл), [вьетнамский](README-vi.md), [индонезийский](README.id-ID.md), [урду](README.ur-PK.md), [Английский](README.md), [Китайский](README.zh-cn.md), [Бразильский Португальский](README.pt-br.md), [Испанский](README-es.md), [Польский](README.pl.md), [Kорейский](README.ko-kr.md), [French](README-fr.md)._ |
4 | 4 | ||
5 | [](https://pub.dev/packages/get) | 5 | [](https://pub.dev/packages/get) |
6 | +[](https://pub.dev/packages/sentry/score) | ||
7 | +[](https://pub.dev/packages/get/score) | ||
8 | +[](https://pub.dev/packages/get/score) | ||
6 |  | 9 |  |
7 | [](https://pub.dev/packages/effective_dart) | 10 | [](https://pub.dev/packages/effective_dart) |
8 | [](https://discord.com/invite/9Hpt99N) | 11 | [](https://discord.com/invite/9Hpt99N) |
@@ -3,7 +3,9 @@ | @@ -3,7 +3,9 @@ | ||
3 | **🌎 اردو ( Selected ✔) [| انگریزی |](README.md) [| ویتنامی |](README-vi.md) [| انڈونیشی |](README.id-ID.md) [چینی |](README.zh-cn.md) [برازیلی پرتگالی |](README.pt-br.md) [ہسپانوی |](README-es.md) [روسی |](README.ru.md) [پولش |](README.pl.md) [کورین |](README.ko-kr.md), [French](README-fr.md)** | 3 | **🌎 اردو ( Selected ✔) [| انگریزی |](README.md) [| ویتنامی |](README-vi.md) [| انڈونیشی |](README.id-ID.md) [چینی |](README.zh-cn.md) [برازیلی پرتگالی |](README.pt-br.md) [ہسپانوی |](README-es.md) [روسی |](README.ru.md) [پولش |](README.pl.md) [کورین |](README.ko-kr.md), [French](README-fr.md)** |
4 | 4 | ||
5 | [](https://pub.dev/packages/get) | 5 | [](https://pub.dev/packages/get) |
6 | +[](https://pub.dev/packages/sentry/score) | ||
6 | [](https://pub.dev/packages/get/score) | 7 | [](https://pub.dev/packages/get/score) |
8 | +[](https://pub.dev/packages/get/score) | ||
7 |  | 9 |  |
8 | [](https://pub.dev/packages/effective_dart) | 10 | [](https://pub.dev/packages/effective_dart) |
9 | [](https://discord.com/invite/9Hpt99N) | 11 | [](https://discord.com/invite/9Hpt99N) |
@@ -3,6 +3,9 @@ | @@ -3,6 +3,9 @@ | ||
3 | _语言: 中文, [英文](README.md), [越南文](README-vi.md), [印度尼西亚](README.id-ID.md), [乌尔都语](README.ur-PK.md), [巴西葡萄牙语](README.pt-br.md), [俄语](README.ru.md), [西班牙语](README-es.md), [波兰语](README.pl.md), [韩国语](README.ko-kr.md), [法语](README-fr.md), [French](README-fr.md)._ | 3 | _语言: 中文, [英文](README.md), [越南文](README-vi.md), [印度尼西亚](README.id-ID.md), [乌尔都语](README.ur-PK.md), [巴西葡萄牙语](README.pt-br.md), [俄语](README.ru.md), [西班牙语](README-es.md), [波兰语](README.pl.md), [韩国语](README.ko-kr.md), [法语](README-fr.md), [French](README-fr.md)._ |
4 | 4 | ||
5 | [](https://pub.dev/packages/get) | 5 | [](https://pub.dev/packages/get) |
6 | +[](https://pub.dev/packages/sentry/score) | ||
7 | +[](https://pub.dev/packages/get/score) | ||
8 | +[](https://pub.dev/packages/get/score) | ||
6 |  | 9 |  |
7 | [](https://pub.dev/packages/effective_dart) | 10 | [](https://pub.dev/packages/effective_dart) |
8 | [](https://discord.com/invite/9Hpt99N) | 11 | [](https://discord.com/invite/9Hpt99N) |
1 | import 'package:flutter/material.dart'; | 1 | import 'package:flutter/material.dart'; |
2 | import 'package:get/get.dart'; | 2 | import 'package:get/get.dart'; |
3 | 3 | ||
4 | -import 'en_US.dart'; | ||
5 | -import 'pt_BR.dart'; | 4 | +import 'en_us.dart'; |
5 | +import 'pt_br.dart'; | ||
6 | 6 | ||
7 | class TranslationService extends Translations { | 7 | class TranslationService extends Translations { |
8 | static Locale? get locale => Get.deviceLocale; | 8 | static Locale? get locale => Get.deviceLocale; |
@@ -21,6 +21,12 @@ class HomeView extends GetView<HomeController> { | @@ -21,6 +21,12 @@ class HomeView extends GetView<HomeController> { | ||
21 | child: Scaffold( | 21 | child: Scaffold( |
22 | backgroundColor: Colors.transparent, | 22 | backgroundColor: Colors.transparent, |
23 | appBar: AppBar( | 23 | appBar: AppBar( |
24 | + leading: IconButton( | ||
25 | + icon: Icon(Icons.add), | ||
26 | + onPressed: () { | ||
27 | + Get.snackbar('title', 'message'); | ||
28 | + }, | ||
29 | + ), | ||
24 | title: Text('covid'.tr), | 30 | title: Text('covid'.tr), |
25 | backgroundColor: Colors.white10, | 31 | backgroundColor: Colors.white10, |
26 | elevation: 0, | 32 | elevation: 0, |
@@ -3,7 +3,6 @@ import 'package:get/get.dart'; | @@ -3,7 +3,6 @@ import 'package:get/get.dart'; | ||
3 | 3 | ||
4 | import '../controllers/dashboard_controller.dart'; | 4 | import '../controllers/dashboard_controller.dart'; |
5 | 5 | ||
6 | - | ||
7 | class DashboardView extends GetView<DashboardController> { | 6 | class DashboardView extends GetView<DashboardController> { |
8 | @override | 7 | @override |
9 | Widget build(BuildContext context) { | 8 | Widget build(BuildContext context) { |
1 | +// ignore_for_file: non_constant_identifier_names | ||
2 | + | ||
1 | part of 'app_pages.dart'; | 3 | part of 'app_pages.dart'; |
2 | // DO NOT EDIT. This is code generated via package:get_cli/get_cli.dart | 4 | // DO NOT EDIT. This is code generated via package:get_cli/get_cli.dart |
3 | 5 | ||
4 | abstract class Routes { | 6 | abstract class Routes { |
5 | - Routes._(); | ||
6 | - | ||
7 | static const HOME = _Paths.HOME; | 7 | static const HOME = _Paths.HOME; |
8 | - static const PROFILE = _Paths.HOME + _Paths.PROFILE; | ||
9 | 8 | ||
9 | + static const PROFILE = _Paths.HOME + _Paths.PROFILE; | ||
10 | static const SETTINGS = _Paths.SETTINGS; | 10 | static const SETTINGS = _Paths.SETTINGS; |
11 | 11 | ||
12 | static const PRODUCTS = _Paths.HOME + _Paths.PRODUCTS; | 12 | static const PRODUCTS = _Paths.HOME + _Paths.PRODUCTS; |
13 | - static String PRODUCT_DETAILS(String productId) => '$PRODUCTS/$productId'; | 13 | + |
14 | static const LOGIN = _Paths.LOGIN; | 14 | static const LOGIN = _Paths.LOGIN; |
15 | + static const DASHBOARD = _Paths.HOME + _Paths.DASHBOARD; | ||
16 | + Routes._(); | ||
15 | static String LOGIN_THEN(String afterSuccessfulLogin) => | 17 | static String LOGIN_THEN(String afterSuccessfulLogin) => |
16 | '$LOGIN?then=${Uri.encodeQueryComponent(afterSuccessfulLogin)}'; | 18 | '$LOGIN?then=${Uri.encodeQueryComponent(afterSuccessfulLogin)}'; |
17 | - static const DASHBOARD = _Paths.HOME + _Paths.DASHBOARD; | 19 | + static String PRODUCT_DETAILS(String productId) => '$PRODUCTS/$productId'; |
18 | } | 20 | } |
19 | 21 | ||
20 | abstract class _Paths { | 22 | abstract class _Paths { |
@@ -115,6 +115,7 @@ class GetConnect extends GetConnectInterface { | @@ -115,6 +115,7 @@ class GetConnect extends GetConnectInterface { | ||
115 | Decoder? defaultDecoder; | 115 | Decoder? defaultDecoder; |
116 | Duration timeout; | 116 | Duration timeout; |
117 | List<TrustedCertificate>? trustedCertificates; | 117 | List<TrustedCertificate>? trustedCertificates; |
118 | + String Function(Uri url)? findProxy; | ||
118 | GetHttpClient? _httpClient; | 119 | GetHttpClient? _httpClient; |
119 | List<GetSocket>? _sockets; | 120 | List<GetSocket>? _sockets; |
120 | bool withCredentials; | 121 | bool withCredentials; |
@@ -134,6 +135,7 @@ class GetConnect extends GetConnectInterface { | @@ -134,6 +135,7 @@ class GetConnect extends GetConnectInterface { | ||
134 | baseUrl: baseUrl, | 135 | baseUrl: baseUrl, |
135 | trustedCertificates: trustedCertificates, | 136 | trustedCertificates: trustedCertificates, |
136 | withCredentials: withCredentials, | 137 | withCredentials: withCredentials, |
138 | + findProxy: findProxy | ||
137 | ); | 139 | ); |
138 | 140 | ||
139 | @override | 141 | @override |
@@ -39,6 +39,8 @@ class GetHttpClient { | @@ -39,6 +39,8 @@ class GetHttpClient { | ||
39 | 39 | ||
40 | final GetModifier _modifier; | 40 | final GetModifier _modifier; |
41 | 41 | ||
42 | + String Function(Uri url)? findProxy; | ||
43 | + | ||
42 | GetHttpClient({ | 44 | GetHttpClient({ |
43 | this.userAgent = 'getx-client', | 45 | this.userAgent = 'getx-client', |
44 | this.timeout = const Duration(seconds: 8), | 46 | this.timeout = const Duration(seconds: 8), |
@@ -50,10 +52,12 @@ class GetHttpClient { | @@ -50,10 +52,12 @@ class GetHttpClient { | ||
50 | this.baseUrl, | 52 | this.baseUrl, |
51 | List<TrustedCertificate>? trustedCertificates, | 53 | List<TrustedCertificate>? trustedCertificates, |
52 | bool withCredentials = false, | 54 | bool withCredentials = false, |
55 | + String Function(Uri url)? findProxy, | ||
53 | }) : _httpClient = HttpRequestImpl( | 56 | }) : _httpClient = HttpRequestImpl( |
54 | allowAutoSignedCert: allowAutoSignedCert, | 57 | allowAutoSignedCert: allowAutoSignedCert, |
55 | trustedCertificates: trustedCertificates, | 58 | trustedCertificates: trustedCertificates, |
56 | withCredentials: withCredentials, | 59 | withCredentials: withCredentials, |
60 | + findProxy: findProxy, | ||
57 | ), | 61 | ), |
58 | _modifier = GetModifier(); | 62 | _modifier = GetModifier(); |
59 | 63 | ||
@@ -195,7 +199,6 @@ class GetHttpClient { | @@ -195,7 +199,6 @@ class GetHttpClient { | ||
195 | int requestNumber = 1, | 199 | int requestNumber = 1, |
196 | Map<String, String>? headers, | 200 | Map<String, String>? headers, |
197 | }) async { | 201 | }) async { |
198 | - try { | ||
199 | var request = await handler(); | 202 | var request = await handler(); |
200 | 203 | ||
201 | headers?.forEach((key, value) { | 204 | headers?.forEach((key, value) { |
@@ -206,6 +209,7 @@ class GetHttpClient { | @@ -206,6 +209,7 @@ class GetHttpClient { | ||
206 | final newRequest = await _modifier.modifyRequest<T>(request); | 209 | final newRequest = await _modifier.modifyRequest<T>(request); |
207 | 210 | ||
208 | _httpClient.timeout = timeout; | 211 | _httpClient.timeout = timeout; |
212 | + try { | ||
209 | var response = await _httpClient.send<T>(newRequest); | 213 | var response = await _httpClient.send<T>(newRequest); |
210 | 214 | ||
211 | final newResponse = | 215 | final newResponse = |
@@ -242,7 +246,7 @@ class GetHttpClient { | @@ -242,7 +246,7 @@ class GetHttpClient { | ||
242 | throw GetHttpException(err.toString()); | 246 | throw GetHttpException(err.toString()); |
243 | } else { | 247 | } else { |
244 | return Response<T>( | 248 | return Response<T>( |
245 | - request: null, | 249 | + request: newRequest, |
246 | headers: null, | 250 | headers: null, |
247 | statusCode: null, | 251 | statusCode: null, |
248 | body: null, | 252 | body: null, |
@@ -268,6 +272,8 @@ class GetHttpClient { | @@ -268,6 +272,8 @@ class GetHttpClient { | ||
268 | headers: headers, | 272 | headers: headers, |
269 | decoder: decoder ?? (defaultDecoder as Decoder<T>?), | 273 | decoder: decoder ?? (defaultDecoder as Decoder<T>?), |
270 | contentLength: 0, | 274 | contentLength: 0, |
275 | + followRedirects: followRedirects, | ||
276 | + maxRedirects: maxRedirects, | ||
271 | )); | 277 | )); |
272 | } | 278 | } |
273 | 279 |
@@ -17,6 +17,7 @@ class HttpRequestImpl extends HttpRequestBase { | @@ -17,6 +17,7 @@ class HttpRequestImpl extends HttpRequestBase { | ||
17 | bool allowAutoSignedCert = true, | 17 | bool allowAutoSignedCert = true, |
18 | List<TrustedCertificate>? trustedCertificates, | 18 | List<TrustedCertificate>? trustedCertificates, |
19 | bool withCredentials = false, | 19 | bool withCredentials = false, |
20 | + String Function(Uri url)? findProxy, | ||
20 | }) { | 21 | }) { |
21 | _httpClient = io.HttpClient(); | 22 | _httpClient = io.HttpClient(); |
22 | if (trustedCertificates != null) { | 23 | if (trustedCertificates != null) { |
@@ -29,6 +30,7 @@ class HttpRequestImpl extends HttpRequestBase { | @@ -29,6 +30,7 @@ class HttpRequestImpl extends HttpRequestBase { | ||
29 | 30 | ||
30 | _httpClient = io.HttpClient(context: _securityContext); | 31 | _httpClient = io.HttpClient(context: _securityContext); |
31 | _httpClient!.badCertificateCallback = (_, __, ___) => allowAutoSignedCert; | 32 | _httpClient!.badCertificateCallback = (_, __, ___) => allowAutoSignedCert; |
33 | + _httpClient!.findProxy = findProxy; | ||
32 | } | 34 | } |
33 | 35 | ||
34 | @override | 36 | @override |
@@ -8,6 +8,7 @@ class HttpRequestImpl extends HttpRequestBase { | @@ -8,6 +8,7 @@ class HttpRequestImpl extends HttpRequestBase { | ||
8 | bool allowAutoSignedCert = true, | 8 | bool allowAutoSignedCert = true, |
9 | List<TrustedCertificate>? trustedCertificates, | 9 | List<TrustedCertificate>? trustedCertificates, |
10 | bool withCredentials = false, | 10 | bool withCredentials = false, |
11 | + String Function(Uri url)? findProxy, | ||
11 | }); | 12 | }); |
12 | @override | 13 | @override |
13 | void close() {} | 14 | void close() {} |
@@ -138,7 +138,11 @@ class HeaderValue { | @@ -138,7 +138,11 @@ class HeaderValue { | ||
138 | stringBuffer.write(_value); | 138 | stringBuffer.write(_value); |
139 | if (parameters != null && parameters!.isNotEmpty) { | 139 | if (parameters != null && parameters!.isNotEmpty) { |
140 | _parameters!.forEach((name, value) { | 140 | _parameters!.forEach((name, value) { |
141 | - stringBuffer..write('; ')..write(name)..write('=')..write(value); | 141 | + stringBuffer |
142 | + ..write('; ') | ||
143 | + ..write(name) | ||
144 | + ..write('=') | ||
145 | + ..write(value); | ||
142 | }); | 146 | }); |
143 | } | 147 | } |
144 | return stringBuffer.toString(); | 148 | return stringBuffer.toString(); |
1 | import 'dart:convert'; | 1 | import 'dart:convert'; |
2 | 2 | ||
3 | +/// Signature for [SocketNotifier.addCloses]. | ||
4 | +typedef CloseSocket = void Function(Close); | ||
5 | + | ||
6 | +/// Signature for [SocketNotifier.addMessages]. | ||
7 | +typedef MessageSocket = void Function(dynamic val); | ||
8 | + | ||
9 | +/// Signature for [SocketNotifier.open]. | ||
10 | +typedef OpenSocket = void Function(); | ||
11 | + | ||
12 | +/// Wrapper class to message and reason from SocketNotifier | ||
3 | class Close { | 13 | class Close { |
4 | final String? message; | 14 | final String? message; |
5 | final int? reason; | 15 | final int? reason; |
@@ -12,12 +22,8 @@ class Close { | @@ -12,12 +22,8 @@ class Close { | ||
12 | } | 22 | } |
13 | } | 23 | } |
14 | 24 | ||
15 | -typedef OpenSocket = void Function(); | ||
16 | - | ||
17 | -typedef CloseSocket = void Function(Close); | ||
18 | - | ||
19 | -typedef MessageSocket = void Function(dynamic val); | ||
20 | - | 25 | +/// This class manages the transmission of messages over websockets using |
26 | +/// GetConnect | ||
21 | class SocketNotifier { | 27 | class SocketNotifier { |
22 | List<void Function(dynamic)>? _onMessages = <MessageSocket>[]; | 28 | List<void Function(dynamic)>? _onMessages = <MessageSocket>[]; |
23 | Map<String, void Function(dynamic)>? _onEvents = <String, MessageSocket>{}; | 29 | Map<String, void Function(dynamic)>? _onEvents = <String, MessageSocket>{}; |
@@ -26,22 +32,42 @@ class SocketNotifier { | @@ -26,22 +32,42 @@ class SocketNotifier { | ||
26 | 32 | ||
27 | late OpenSocket open; | 33 | late OpenSocket open; |
28 | 34 | ||
29 | - void addMessages(MessageSocket socket) { | ||
30 | - _onMessages!.add((socket)); | 35 | + /// subscribe to close events |
36 | + void addCloses(CloseSocket socket) { | ||
37 | + _onCloses!.add(socket); | ||
38 | + } | ||
39 | + | ||
40 | + /// subscribe to error events | ||
41 | + void addErrors(CloseSocket socket) { | ||
42 | + _onErrors!.add((socket)); | ||
31 | } | 43 | } |
32 | 44 | ||
45 | + /// subscribe to named events | ||
33 | void addEvents(String event, MessageSocket socket) { | 46 | void addEvents(String event, MessageSocket socket) { |
34 | _onEvents![event] = socket; | 47 | _onEvents![event] = socket; |
35 | } | 48 | } |
36 | 49 | ||
37 | - void addCloses(CloseSocket socket) { | ||
38 | - _onCloses!.add(socket); | 50 | + /// subscribe to message events |
51 | + void addMessages(MessageSocket socket) { | ||
52 | + _onMessages!.add((socket)); | ||
39 | } | 53 | } |
40 | 54 | ||
41 | - void addErrors(CloseSocket socket) { | ||
42 | - _onErrors!.add((socket)); | 55 | + /// Dispose messages, events, closes and errors subscriptions |
56 | + void dispose() { | ||
57 | + _onMessages = null; | ||
58 | + _onEvents = null; | ||
59 | + _onCloses = null; | ||
60 | + _onErrors = null; | ||
43 | } | 61 | } |
44 | 62 | ||
63 | + /// Notify all subscriptions on [addCloses] | ||
64 | + void notifyClose(Close err) { | ||
65 | + for (var item in _onCloses!) { | ||
66 | + item(err); | ||
67 | + } | ||
68 | + } | ||
69 | + | ||
70 | + /// Notify all subscriptions on [addMessages] | ||
45 | void notifyData(dynamic data) { | 71 | void notifyData(dynamic data) { |
46 | for (var item in _onMessages!) { | 72 | for (var item in _onMessages!) { |
47 | item(data); | 73 | item(data); |
@@ -51,12 +77,7 @@ class SocketNotifier { | @@ -51,12 +77,7 @@ class SocketNotifier { | ||
51 | } | 77 | } |
52 | } | 78 | } |
53 | 79 | ||
54 | - void notifyClose(Close err) { | ||
55 | - for (var item in _onCloses!) { | ||
56 | - item(err); | ||
57 | - } | ||
58 | - } | ||
59 | - | 80 | + /// Notify all subscriptions on [addErrors] |
60 | void notifyError(Close err) { | 81 | void notifyError(Close err) { |
61 | // rooms.removeWhere((key, value) => value.contains(_ws)); | 82 | // rooms.removeWhere((key, value) => value.contains(_ws)); |
62 | for (var item in _onErrors!) { | 83 | for (var item in _onErrors!) { |
@@ -72,15 +93,9 @@ class SocketNotifier { | @@ -72,15 +93,9 @@ class SocketNotifier { | ||
72 | if (_onEvents!.containsKey(event)) { | 93 | if (_onEvents!.containsKey(event)) { |
73 | _onEvents![event]!(data); | 94 | _onEvents![event]!(data); |
74 | } | 95 | } |
96 | + // ignore: avoid_catches_without_on_clauses | ||
75 | } catch (_) { | 97 | } catch (_) { |
76 | return; | 98 | return; |
77 | } | 99 | } |
78 | } | 100 | } |
79 | - | ||
80 | - void dispose() { | ||
81 | - _onMessages = null; | ||
82 | - _onEvents = null; | ||
83 | - _onCloses = null; | ||
84 | - _onErrors = null; | ||
85 | - } | ||
86 | } | 101 | } |
@@ -4,15 +4,8 @@ import 'dart:convert'; | @@ -4,15 +4,8 @@ import 'dart:convert'; | ||
4 | import 'dart:html'; | 4 | import 'dart:html'; |
5 | 5 | ||
6 | import '../../../get_core/get_core.dart'; | 6 | import '../../../get_core/get_core.dart'; |
7 | - | ||
8 | import 'socket_notifier.dart'; | 7 | import 'socket_notifier.dart'; |
9 | 8 | ||
10 | -enum ConnectionStatus { | ||
11 | - connecting, | ||
12 | - connected, | ||
13 | - closed, | ||
14 | -} | ||
15 | - | ||
16 | class BaseWebSocket { | 9 | class BaseWebSocket { |
17 | String url; | 10 | String url; |
18 | WebSocket? socket; | 11 | WebSocket? socket; |
@@ -21,6 +14,8 @@ class BaseWebSocket { | @@ -21,6 +14,8 @@ class BaseWebSocket { | ||
21 | bool isDisposed = false; | 14 | bool isDisposed = false; |
22 | bool allowSelfSigned; | 15 | bool allowSelfSigned; |
23 | 16 | ||
17 | + ConnectionStatus? connectionStatus; | ||
18 | + Timer? _t; | ||
24 | BaseWebSocket( | 19 | BaseWebSocket( |
25 | this.url, { | 20 | this.url, { |
26 | this.ping = const Duration(seconds: 5), | 21 | this.ping = const Duration(seconds: 5), |
@@ -30,9 +25,12 @@ class BaseWebSocket { | @@ -30,9 +25,12 @@ class BaseWebSocket { | ||
30 | ? url.replaceAll('https:', 'wss:') | 25 | ? url.replaceAll('https:', 'wss:') |
31 | : url.replaceAll('http:', 'ws:'); | 26 | : url.replaceAll('http:', 'ws:'); |
32 | } | 27 | } |
33 | - ConnectionStatus? connectionStatus; | ||
34 | - Timer? _t; | ||
35 | 28 | ||
29 | + void close([int? status, String? reason]) { | ||
30 | + socket?.close(status, reason); | ||
31 | + } | ||
32 | + | ||
33 | + // ignore: use_setters_to_change_properties | ||
36 | void connect() { | 34 | void connect() { |
37 | try { | 35 | try { |
38 | connectionStatus = ConnectionStatus.connecting; | 36 | connectionStatus = ConnectionStatus.connecting; |
@@ -68,9 +66,18 @@ class BaseWebSocket { | @@ -68,9 +66,18 @@ class BaseWebSocket { | ||
68 | } | 66 | } |
69 | } | 67 | } |
70 | 68 | ||
71 | - // ignore: use_setters_to_change_properties | ||
72 | - void onOpen(OpenSocket fn) { | ||
73 | - socketNotifier!.open = fn; | 69 | + void dispose() { |
70 | + socketNotifier!.dispose(); | ||
71 | + socketNotifier = null; | ||
72 | + isDisposed = true; | ||
73 | + } | ||
74 | + | ||
75 | + void emit(String event, dynamic data) { | ||
76 | + send(jsonEncode({'type': event, 'data': data})); | ||
77 | + } | ||
78 | + | ||
79 | + void on(String event, MessageSocket message) { | ||
80 | + socketNotifier!.addEvents(event, message); | ||
74 | } | 81 | } |
75 | 82 | ||
76 | void onClose(CloseSocket fn) { | 83 | void onClose(CloseSocket fn) { |
@@ -85,12 +92,9 @@ class BaseWebSocket { | @@ -85,12 +92,9 @@ class BaseWebSocket { | ||
85 | socketNotifier!.addMessages(fn); | 92 | socketNotifier!.addMessages(fn); |
86 | } | 93 | } |
87 | 94 | ||
88 | - void on(String event, MessageSocket message) { | ||
89 | - socketNotifier!.addEvents(event, message); | ||
90 | - } | ||
91 | - | ||
92 | - void close([int? status, String? reason]) { | ||
93 | - socket?.close(status, reason); | 95 | + // ignore: use_setters_to_change_properties |
96 | + void onOpen(OpenSocket fn) { | ||
97 | + socketNotifier!.open = fn; | ||
94 | } | 98 | } |
95 | 99 | ||
96 | void send(dynamic data) { | 100 | void send(dynamic data) { |
@@ -103,14 +107,10 @@ class BaseWebSocket { | @@ -103,14 +107,10 @@ class BaseWebSocket { | ||
103 | Get.log('WebSocket not connected, message $data not sent'); | 107 | Get.log('WebSocket not connected, message $data not sent'); |
104 | } | 108 | } |
105 | } | 109 | } |
110 | +} | ||
106 | 111 | ||
107 | - void emit(String event, dynamic data) { | ||
108 | - send(jsonEncode({'type': event, 'data': data})); | ||
109 | - } | ||
110 | - | ||
111 | - void dispose() { | ||
112 | - socketNotifier!.dispose(); | ||
113 | - socketNotifier = null; | ||
114 | - isDisposed = true; | ||
115 | - } | 112 | +enum ConnectionStatus { |
113 | + connecting, | ||
114 | + connected, | ||
115 | + closed, | ||
116 | } | 116 | } |
@@ -4,30 +4,28 @@ import 'dart:io'; | @@ -4,30 +4,28 @@ import 'dart:io'; | ||
4 | import 'dart:math'; | 4 | import 'dart:math'; |
5 | 5 | ||
6 | import '../../../get_core/get_core.dart'; | 6 | import '../../../get_core/get_core.dart'; |
7 | - | ||
8 | import 'socket_notifier.dart'; | 7 | import 'socket_notifier.dart'; |
9 | 8 | ||
10 | -enum ConnectionStatus { | ||
11 | - connecting, | ||
12 | - connected, | ||
13 | - closed, | ||
14 | -} | ||
15 | - | ||
16 | class BaseWebSocket { | 9 | class BaseWebSocket { |
17 | String url; | 10 | String url; |
18 | WebSocket? socket; | 11 | WebSocket? socket; |
19 | SocketNotifier? socketNotifier = SocketNotifier(); | 12 | SocketNotifier? socketNotifier = SocketNotifier(); |
20 | bool isDisposed = false; | 13 | bool isDisposed = false; |
14 | + Duration ping; | ||
15 | + bool allowSelfSigned; | ||
16 | + ConnectionStatus? connectionStatus; | ||
17 | + | ||
21 | BaseWebSocket( | 18 | BaseWebSocket( |
22 | this.url, { | 19 | this.url, { |
23 | this.ping = const Duration(seconds: 5), | 20 | this.ping = const Duration(seconds: 5), |
24 | this.allowSelfSigned = true, | 21 | this.allowSelfSigned = true, |
25 | }); | 22 | }); |
26 | - Duration ping; | ||
27 | - bool allowSelfSigned; | ||
28 | 23 | ||
29 | - ConnectionStatus? connectionStatus; | 24 | + void close([int? status, String? reason]) { |
25 | + socket?.close(status, reason); | ||
26 | + } | ||
30 | 27 | ||
28 | + // ignore: use_setters_to_change_properties | ||
31 | Future connect() async { | 29 | Future connect() async { |
32 | if (isDisposed) { | 30 | if (isDisposed) { |
33 | socketNotifier = SocketNotifier(); | 31 | socketNotifier = SocketNotifier(); |
@@ -60,9 +58,18 @@ class BaseWebSocket { | @@ -60,9 +58,18 @@ class BaseWebSocket { | ||
60 | } | 58 | } |
61 | } | 59 | } |
62 | 60 | ||
63 | - // ignore: use_setters_to_change_properties | ||
64 | - void onOpen(OpenSocket fn) { | ||
65 | - socketNotifier!.open = fn; | 61 | + void dispose() { |
62 | + socketNotifier!.dispose(); | ||
63 | + socketNotifier = null; | ||
64 | + isDisposed = true; | ||
65 | + } | ||
66 | + | ||
67 | + void emit(String event, dynamic data) { | ||
68 | + send(jsonEncode({'type': event, 'data': data})); | ||
69 | + } | ||
70 | + | ||
71 | + void on(String event, MessageSocket message) { | ||
72 | + socketNotifier!.addEvents(event, message); | ||
66 | } | 73 | } |
67 | 74 | ||
68 | void onClose(CloseSocket fn) { | 75 | void onClose(CloseSocket fn) { |
@@ -77,12 +84,9 @@ class BaseWebSocket { | @@ -77,12 +84,9 @@ class BaseWebSocket { | ||
77 | socketNotifier!.addMessages(fn); | 84 | socketNotifier!.addMessages(fn); |
78 | } | 85 | } |
79 | 86 | ||
80 | - void on(String event, MessageSocket message) { | ||
81 | - socketNotifier!.addEvents(event, message); | ||
82 | - } | ||
83 | - | ||
84 | - void close([int? status, String? reason]) { | ||
85 | - socket?.close(status, reason); | 87 | + // ignore: use_setters_to_change_properties |
88 | + void onOpen(OpenSocket fn) { | ||
89 | + socketNotifier!.open = fn; | ||
86 | } | 90 | } |
87 | 91 | ||
88 | void send(dynamic data) async { | 92 | void send(dynamic data) async { |
@@ -95,16 +99,6 @@ class BaseWebSocket { | @@ -95,16 +99,6 @@ class BaseWebSocket { | ||
95 | } | 99 | } |
96 | } | 100 | } |
97 | 101 | ||
98 | - void dispose() { | ||
99 | - socketNotifier!.dispose(); | ||
100 | - socketNotifier = null; | ||
101 | - isDisposed = true; | ||
102 | - } | ||
103 | - | ||
104 | - void emit(String event, dynamic data) { | ||
105 | - send(jsonEncode({'type': event, 'data': data})); | ||
106 | - } | ||
107 | - | ||
108 | Future<WebSocket> _connectForSelfSignedCert(String url) async { | 102 | Future<WebSocket> _connectForSelfSignedCert(String url) async { |
109 | try { | 103 | try { |
110 | var r = Random(); | 104 | var r = Random(); |
@@ -136,3 +130,9 @@ class BaseWebSocket { | @@ -136,3 +130,9 @@ class BaseWebSocket { | ||
136 | } | 130 | } |
137 | } | 131 | } |
138 | } | 132 | } |
133 | + | ||
134 | +enum ConnectionStatus { | ||
135 | + connecting, | ||
136 | + connected, | ||
137 | + closed, | ||
138 | +} |
@@ -239,7 +239,7 @@ class GetInstance { | @@ -239,7 +239,7 @@ class GetInstance { | ||
239 | final newKey = key ?? _getKey(S, tag); | 239 | final newKey = key ?? _getKey(S, tag); |
240 | if (_singl.containsKey(newKey)) { | 240 | if (_singl.containsKey(newKey)) { |
241 | final dep = _singl[newKey]; | 241 | final dep = _singl[newKey]; |
242 | - if (dep != null) { | 242 | + if (dep != null && !dep.permanent) { |
243 | dep.isDirty = true; | 243 | dep.isDirty = true; |
244 | } | 244 | } |
245 | } | 245 | } |
@@ -16,5 +16,5 @@ export 'src/routes/get_route.dart'; | @@ -16,5 +16,5 @@ export 'src/routes/get_route.dart'; | ||
16 | export 'src/routes/observers/route_observer.dart'; | 16 | export 'src/routes/observers/route_observer.dart'; |
17 | export 'src/routes/route_middleware.dart'; | 17 | export 'src/routes/route_middleware.dart'; |
18 | export 'src/routes/transitions_type.dart'; | 18 | export 'src/routes/transitions_type.dart'; |
19 | -export 'src/snackbar/snack.dart'; | ||
20 | -export 'src/snackbar/snack_route.dart'; | 19 | +export 'src/snackbar/snackbar.dart'; |
20 | +export 'src/snackbar/snackbar_controller.dart'; |
@@ -11,243 +11,56 @@ import 'dialog/dialog_route.dart'; | @@ -11,243 +11,56 @@ import 'dialog/dialog_route.dart'; | ||
11 | import 'root/parse_route.dart'; | 11 | import 'root/parse_route.dart'; |
12 | import 'root/root_controller.dart'; | 12 | import 'root/root_controller.dart'; |
13 | import 'routes/transitions_type.dart'; | 13 | import 'routes/transitions_type.dart'; |
14 | +import 'snackbar/snackbar_controller.dart'; | ||
14 | 15 | ||
15 | -extension ExtensionSnackbar on GetInterface { | ||
16 | - void rawSnackbar({ | ||
17 | - String? title, | ||
18 | - String? message, | ||
19 | - Widget? titleText, | ||
20 | - Widget? messageText, | ||
21 | - Widget? icon, | ||
22 | - bool instantInit = true, | ||
23 | - bool shouldIconPulse = true, | ||
24 | - double? maxWidth, | ||
25 | - EdgeInsets margin = const EdgeInsets.all(0.0), | ||
26 | - EdgeInsets padding = const EdgeInsets.all(16), | ||
27 | - double borderRadius = 0.0, | ||
28 | - Color? borderColor, | ||
29 | - double borderWidth = 1.0, | ||
30 | - Color backgroundColor = const Color(0xFF303030), | ||
31 | - Color? leftBarIndicatorColor, | ||
32 | - List<BoxShadow>? boxShadows, | ||
33 | - Gradient? backgroundGradient, | ||
34 | - Widget? mainButton, | ||
35 | - OnTap? onTap, | ||
36 | - Duration duration = const Duration(seconds: 3), | ||
37 | - bool isDismissible = true, | ||
38 | - SnackDismissDirection dismissDirection = SnackDismissDirection.VERTICAL, | ||
39 | - bool showProgressIndicator = false, | ||
40 | - AnimationController? progressIndicatorController, | ||
41 | - Color? progressIndicatorBackgroundColor, | ||
42 | - Animation<Color>? progressIndicatorValueColor, | ||
43 | - SnackPosition snackPosition = SnackPosition.BOTTOM, | ||
44 | - SnackStyle snackStyle = SnackStyle.FLOATING, | ||
45 | - Curve forwardAnimationCurve = Curves.easeOutCirc, | ||
46 | - Curve reverseAnimationCurve = Curves.easeOutCirc, | ||
47 | - Duration animationDuration = const Duration(seconds: 1), | ||
48 | - SnackbarStatusCallback? snackbarStatus, | ||
49 | - double? barBlur = 0.0, | ||
50 | - double overlayBlur = 0.0, | ||
51 | - Color? overlayColor, | ||
52 | - Form? userInputForm, | ||
53 | - }) async { | ||
54 | - final getBar = GetBar( | ||
55 | - snackbarStatus: snackbarStatus, | ||
56 | - title: title, | ||
57 | - message: message, | ||
58 | - titleText: titleText, | ||
59 | - messageText: messageText, | ||
60 | - snackPosition: snackPosition, | ||
61 | - borderRadius: borderRadius, | ||
62 | - margin: margin, | ||
63 | - duration: duration, | ||
64 | - barBlur: barBlur, | ||
65 | - backgroundColor: backgroundColor, | ||
66 | - icon: icon, | ||
67 | - shouldIconPulse: shouldIconPulse, | ||
68 | - maxWidth: maxWidth, | ||
69 | - padding: padding, | ||
70 | - borderColor: borderColor, | ||
71 | - borderWidth: borderWidth, | ||
72 | - leftBarIndicatorColor: leftBarIndicatorColor, | ||
73 | - boxShadows: boxShadows, | ||
74 | - backgroundGradient: backgroundGradient, | ||
75 | - mainButton: mainButton, | ||
76 | - onTap: onTap, | ||
77 | - isDismissible: isDismissible, | ||
78 | - dismissDirection: dismissDirection, | ||
79 | - showProgressIndicator: showProgressIndicator, | ||
80 | - progressIndicatorController: progressIndicatorController, | ||
81 | - progressIndicatorBackgroundColor: progressIndicatorBackgroundColor, | ||
82 | - progressIndicatorValueColor: progressIndicatorValueColor, | ||
83 | - snackStyle: snackStyle, | ||
84 | - forwardAnimationCurve: forwardAnimationCurve, | ||
85 | - reverseAnimationCurve: reverseAnimationCurve, | ||
86 | - animationDuration: animationDuration, | ||
87 | - overlayBlur: overlayBlur, | ||
88 | - overlayColor: overlayColor, | ||
89 | - userInputForm: userInputForm, | ||
90 | - ); | ||
91 | - | ||
92 | - if (instantInit) { | ||
93 | - getBar.show(); | ||
94 | - } else { | ||
95 | - SchedulerBinding.instance!.addPostFrameCallback((_) { | ||
96 | - getBar.show(); | ||
97 | - }); | ||
98 | - } | ||
99 | - } | ||
100 | - | ||
101 | - Future<T?>? showSnackbar<T>(GetBar snackbar) { | ||
102 | - return key.currentState?.push(SnackRoute<T>(snack: snackbar)); | ||
103 | - } | ||
104 | - | ||
105 | - void snackbar<T>( | ||
106 | - String title, | ||
107 | - String message, { | ||
108 | - Color? colorText, | ||
109 | - Duration? duration, | 16 | +/// It replaces the Flutter Navigator, but needs no context. |
17 | +/// You can to use navigator.push(YourRoute()) rather | ||
18 | +/// Navigator.push(context, YourRoute()); | ||
19 | +NavigatorState? get navigator => GetNavigation(Get).key.currentState; | ||
110 | 20 | ||
111 | - /// with instantInit = false you can put snackbar on initState | ||
112 | - bool instantInit = true, | ||
113 | - SnackPosition? snackPosition, | ||
114 | - Widget? titleText, | ||
115 | - Widget? messageText, | ||
116 | - Widget? icon, | ||
117 | - bool? shouldIconPulse, | ||
118 | - double? maxWidth, | ||
119 | - EdgeInsets? margin, | ||
120 | - EdgeInsets? padding, | ||
121 | - double? borderRadius, | ||
122 | - Color? borderColor, | ||
123 | - double? borderWidth, | 21 | +extension ExtensionBottomSheet on GetInterface { |
22 | + Future<T?> bottomSheet<T>( | ||
23 | + Widget bottomsheet, { | ||
124 | Color? backgroundColor, | 24 | Color? backgroundColor, |
125 | - Color? leftBarIndicatorColor, | ||
126 | - List<BoxShadow>? boxShadows, | ||
127 | - Gradient? backgroundGradient, | ||
128 | - TextButton? mainButton, | ||
129 | - OnTap? onTap, | ||
130 | - bool? isDismissible, | ||
131 | - bool? showProgressIndicator, | ||
132 | - SnackDismissDirection? dismissDirection, | ||
133 | - AnimationController? progressIndicatorController, | ||
134 | - Color? progressIndicatorBackgroundColor, | ||
135 | - Animation<Color>? progressIndicatorValueColor, | ||
136 | - SnackStyle? snackStyle, | ||
137 | - Curve? forwardAnimationCurve, | ||
138 | - Curve? reverseAnimationCurve, | ||
139 | - Duration? animationDuration, | ||
140 | - double? barBlur, | ||
141 | - double? overlayBlur, | ||
142 | - SnackbarStatusCallback? snackbarStatus, | ||
143 | - Color? overlayColor, | ||
144 | - Form? userInputForm, | ||
145 | - }) async { | ||
146 | - final getBar = GetBar( | ||
147 | - snackbarStatus: snackbarStatus, | ||
148 | - titleText: titleText ?? | ||
149 | - Text( | ||
150 | - title, | ||
151 | - style: TextStyle( | ||
152 | - color: colorText ?? iconColor ?? Colors.black, | ||
153 | - fontWeight: FontWeight.w800, | ||
154 | - fontSize: 16, | ||
155 | - ), | ||
156 | - ), | ||
157 | - messageText: messageText ?? | ||
158 | - Text( | ||
159 | - message, | ||
160 | - style: TextStyle( | ||
161 | - color: colorText ?? iconColor ?? Colors.black, | ||
162 | - fontWeight: FontWeight.w300, | ||
163 | - fontSize: 14, | ||
164 | - ), | ||
165 | - ), | ||
166 | - snackPosition: snackPosition ?? SnackPosition.TOP, | ||
167 | - borderRadius: borderRadius ?? 15, | ||
168 | - margin: margin ?? EdgeInsets.symmetric(horizontal: 10), | ||
169 | - duration: duration ?? Duration(seconds: 3), | ||
170 | - barBlur: barBlur ?? 7.0, | ||
171 | - backgroundColor: backgroundColor ?? Colors.grey.withOpacity(0.2), | ||
172 | - icon: icon, | ||
173 | - shouldIconPulse: shouldIconPulse ?? true, | ||
174 | - maxWidth: maxWidth, | ||
175 | - padding: padding ?? EdgeInsets.all(16), | ||
176 | - borderColor: borderColor, | ||
177 | - borderWidth: borderWidth, | ||
178 | - leftBarIndicatorColor: leftBarIndicatorColor, | ||
179 | - boxShadows: boxShadows, | ||
180 | - backgroundGradient: backgroundGradient, | ||
181 | - mainButton: mainButton, | ||
182 | - onTap: onTap, | ||
183 | - isDismissible: isDismissible ?? true, | ||
184 | - dismissDirection: dismissDirection ?? SnackDismissDirection.VERTICAL, | ||
185 | - showProgressIndicator: showProgressIndicator ?? false, | ||
186 | - progressIndicatorController: progressIndicatorController, | ||
187 | - progressIndicatorBackgroundColor: progressIndicatorBackgroundColor, | ||
188 | - progressIndicatorValueColor: progressIndicatorValueColor, | ||
189 | - snackStyle: snackStyle ?? SnackStyle.FLOATING, | ||
190 | - forwardAnimationCurve: forwardAnimationCurve ?? Curves.easeOutCirc, | ||
191 | - reverseAnimationCurve: reverseAnimationCurve ?? Curves.easeOutCirc, | ||
192 | - animationDuration: animationDuration ?? Duration(seconds: 1), | ||
193 | - overlayBlur: overlayBlur ?? 0.0, | ||
194 | - overlayColor: overlayColor ?? Colors.transparent, | ||
195 | - userInputForm: userInputForm); | ||
196 | - | ||
197 | - if (instantInit) { | ||
198 | - showSnackbar<T>(getBar); | ||
199 | - } else { | ||
200 | - routing.isSnackbar = true; | ||
201 | - SchedulerBinding.instance!.addPostFrameCallback((_) { | ||
202 | - showSnackbar<T>(getBar); | ||
203 | - }); | ||
204 | - } | ||
205 | - } | ||
206 | -} | 25 | + double? elevation, |
26 | + bool persistent = true, | ||
27 | + ShapeBorder? shape, | ||
28 | + Clip? clipBehavior, | ||
29 | + Color? barrierColor, | ||
30 | + bool? ignoreSafeArea, | ||
31 | + bool isScrollControlled = false, | ||
32 | + bool useRootNavigator = false, | ||
33 | + bool isDismissible = true, | ||
34 | + bool enableDrag = true, | ||
35 | + RouteSettings? settings, | ||
36 | + Duration? enterBottomSheetDuration, | ||
37 | + Duration? exitBottomSheetDuration, | ||
38 | + }) { | ||
39 | + return Navigator.of(overlayContext!, rootNavigator: useRootNavigator) | ||
40 | + .push(GetModalBottomSheetRoute<T>( | ||
41 | + builder: (_) => bottomsheet, | ||
42 | + isPersistent: persistent, | ||
43 | + // theme: Theme.of(key.currentContext, shadowThemeOnly: true), | ||
44 | + theme: Theme.of(key.currentContext!), | ||
45 | + isScrollControlled: isScrollControlled, | ||
207 | 46 | ||
208 | -extension OverlayExt on GetInterface { | ||
209 | - Future<T> showOverlay<T>({ | ||
210 | - required Future<T> Function() asyncFunction, | ||
211 | - Color opacityColor = Colors.black, | ||
212 | - Widget? loadingWidget, | ||
213 | - double opacity = .5, | ||
214 | - }) async { | ||
215 | - final navigatorState = | ||
216 | - Navigator.of(Get.overlayContext!, rootNavigator: false); | ||
217 | - final overlayState = navigatorState.overlay!; | 47 | + barrierLabel: MaterialLocalizations.of(key.currentContext!) |
48 | + .modalBarrierDismissLabel, | ||
218 | 49 | ||
219 | - final overlayEntryOpacity = OverlayEntry(builder: (context) { | ||
220 | - return Opacity( | ||
221 | - opacity: opacity, | ||
222 | - child: Container( | ||
223 | - color: opacityColor, | ||
224 | - )); | ||
225 | - }); | ||
226 | - final overlayEntryLoader = OverlayEntry(builder: (context) { | ||
227 | - return loadingWidget ?? | ||
228 | - Center( | ||
229 | - child: Container( | ||
230 | - height: 90, | ||
231 | - width: 90, | ||
232 | - child: Text('Loading...'), | 50 | + backgroundColor: backgroundColor ?? Colors.transparent, |
51 | + elevation: elevation, | ||
52 | + shape: shape, | ||
53 | + removeTop: ignoreSafeArea ?? true, | ||
54 | + clipBehavior: clipBehavior, | ||
55 | + isDismissible: isDismissible, | ||
56 | + modalBarrierColor: barrierColor, | ||
57 | + settings: settings, | ||
58 | + enableDrag: enableDrag, | ||
59 | + enterBottomSheetDuration: | ||
60 | + enterBottomSheetDuration ?? const Duration(milliseconds: 250), | ||
61 | + exitBottomSheetDuration: | ||
62 | + exitBottomSheetDuration ?? const Duration(milliseconds: 200), | ||
233 | )); | 63 | )); |
234 | - }); | ||
235 | - overlayState.insert(overlayEntryOpacity); | ||
236 | - overlayState.insert(overlayEntryLoader); | ||
237 | - | ||
238 | - T data; | ||
239 | - | ||
240 | - try { | ||
241 | - data = await asyncFunction(); | ||
242 | - } on Exception catch (_) { | ||
243 | - overlayEntryLoader.remove(); | ||
244 | - overlayEntryOpacity.remove(); | ||
245 | - rethrow; | ||
246 | - } | ||
247 | - | ||
248 | - overlayEntryLoader.remove(); | ||
249 | - overlayEntryOpacity.remove(); | ||
250 | - return data; | ||
251 | } | 64 | } |
252 | } | 65 | } |
253 | 66 | ||
@@ -378,7 +191,7 @@ extension ExtensionDialog on GetInterface { | @@ -378,7 +191,7 @@ extension ExtensionDialog on GetInterface { | ||
378 | padding: EdgeInsets.symmetric(horizontal: 10, vertical: 8), | 191 | padding: EdgeInsets.symmetric(horizontal: 10, vertical: 8), |
379 | shape: RoundedRectangleBorder( | 192 | shape: RoundedRectangleBorder( |
380 | side: BorderSide( | 193 | side: BorderSide( |
381 | - color: buttonColor ?? theme.accentColor, | 194 | + color: buttonColor ?? theme.colorScheme.secondary, |
382 | width: 2, | 195 | width: 2, |
383 | style: BorderStyle.solid), | 196 | style: BorderStyle.solid), |
384 | borderRadius: BorderRadius.circular(100)), | 197 | borderRadius: BorderRadius.circular(100)), |
@@ -389,7 +202,8 @@ extension ExtensionDialog on GetInterface { | @@ -389,7 +202,8 @@ extension ExtensionDialog on GetInterface { | ||
389 | }, | 202 | }, |
390 | child: Text( | 203 | child: Text( |
391 | textCancel ?? "Cancel", | 204 | textCancel ?? "Cancel", |
392 | - style: TextStyle(color: cancelTextColor ?? theme.accentColor), | 205 | + style: TextStyle( |
206 | + color: cancelTextColor ?? theme.colorScheme.secondary), | ||
393 | ), | 207 | ), |
394 | )); | 208 | )); |
395 | } | 209 | } |
@@ -397,113 +211,268 @@ extension ExtensionDialog on GetInterface { | @@ -397,113 +211,268 @@ extension ExtensionDialog on GetInterface { | ||
397 | if (confirm != null) { | 211 | if (confirm != null) { |
398 | actions.add(confirm); | 212 | actions.add(confirm); |
399 | } else { | 213 | } else { |
400 | - if (leanConfirm) { | ||
401 | - actions.add(TextButton( | ||
402 | - style: TextButton.styleFrom( | ||
403 | - tapTargetSize: MaterialTapTargetSize.shrinkWrap, | ||
404 | - backgroundColor: buttonColor ?? theme.accentColor, | ||
405 | - shape: RoundedRectangleBorder( | ||
406 | - borderRadius: BorderRadius.circular(100)), | ||
407 | - ), | ||
408 | - child: Text( | ||
409 | - textConfirm ?? "Ok", | ||
410 | - style: | ||
411 | - TextStyle(color: confirmTextColor ?? theme.backgroundColor), | ||
412 | - ), | ||
413 | - onPressed: () { | ||
414 | - onConfirm?.call(); | ||
415 | - })); | 214 | + if (leanConfirm) { |
215 | + actions.add(TextButton( | ||
216 | + style: TextButton.styleFrom( | ||
217 | + tapTargetSize: MaterialTapTargetSize.shrinkWrap, | ||
218 | + backgroundColor: buttonColor ?? theme.colorScheme.secondary, | ||
219 | + shape: RoundedRectangleBorder( | ||
220 | + borderRadius: BorderRadius.circular(100)), | ||
221 | + ), | ||
222 | + child: Text( | ||
223 | + textConfirm ?? "Ok", | ||
224 | + style: | ||
225 | + TextStyle(color: confirmTextColor ?? theme.backgroundColor), | ||
226 | + ), | ||
227 | + onPressed: () { | ||
228 | + onConfirm?.call(); | ||
229 | + })); | ||
230 | + } | ||
231 | + } | ||
232 | + | ||
233 | + Widget baseAlertDialog = AlertDialog( | ||
234 | + titlePadding: titlePadding ?? EdgeInsets.all(8), | ||
235 | + contentPadding: contentPadding ?? EdgeInsets.all(8), | ||
236 | + | ||
237 | + backgroundColor: backgroundColor ?? theme.dialogBackgroundColor, | ||
238 | + shape: RoundedRectangleBorder( | ||
239 | + borderRadius: BorderRadius.all(Radius.circular(radius))), | ||
240 | + title: Text(title, textAlign: TextAlign.center, style: titleStyle), | ||
241 | + content: Column( | ||
242 | + crossAxisAlignment: CrossAxisAlignment.center, | ||
243 | + mainAxisSize: MainAxisSize.min, | ||
244 | + children: [ | ||
245 | + content ?? | ||
246 | + Text(middleText, | ||
247 | + textAlign: TextAlign.center, style: middleTextStyle), | ||
248 | + SizedBox(height: 16), | ||
249 | + ButtonTheme( | ||
250 | + minWidth: 78.0, | ||
251 | + height: 34.0, | ||
252 | + child: Wrap( | ||
253 | + alignment: WrapAlignment.center, | ||
254 | + spacing: 8, | ||
255 | + runSpacing: 8, | ||
256 | + children: actions, | ||
257 | + ), | ||
258 | + ) | ||
259 | + ], | ||
260 | + ), | ||
261 | + // actions: actions, // ?? <Widget>[cancelButton, confirmButton], | ||
262 | + buttonPadding: EdgeInsets.zero, | ||
263 | + ); | ||
264 | + | ||
265 | + return dialog<T>( | ||
266 | + onWillPop != null | ||
267 | + ? WillPopScope( | ||
268 | + onWillPop: onWillPop, | ||
269 | + child: baseAlertDialog, | ||
270 | + ) | ||
271 | + : baseAlertDialog, | ||
272 | + barrierDismissible: barrierDismissible, | ||
273 | + navigatorKey: navigatorKey, | ||
274 | + ); | ||
275 | + } | ||
276 | +} | ||
277 | + | ||
278 | +extension ExtensionSnackbar on GetInterface { | ||
279 | + SnackbarController rawSnackbar({ | ||
280 | + String? title, | ||
281 | + String? message, | ||
282 | + Widget? titleText, | ||
283 | + Widget? messageText, | ||
284 | + Widget? icon, | ||
285 | + bool instantInit = true, | ||
286 | + bool shouldIconPulse = true, | ||
287 | + double? maxWidth, | ||
288 | + EdgeInsets margin = const EdgeInsets.all(0.0), | ||
289 | + EdgeInsets padding = const EdgeInsets.all(16), | ||
290 | + double borderRadius = 0.0, | ||
291 | + Color? borderColor, | ||
292 | + double borderWidth = 1.0, | ||
293 | + Color backgroundColor = const Color(0xFF303030), | ||
294 | + Color? leftBarIndicatorColor, | ||
295 | + List<BoxShadow>? boxShadows, | ||
296 | + Gradient? backgroundGradient, | ||
297 | + Widget? mainButton, | ||
298 | + OnTap? onTap, | ||
299 | + Duration? duration = const Duration(seconds: 3), | ||
300 | + bool isDismissible = true, | ||
301 | + DismissDirection? dismissDirection, | ||
302 | + bool showProgressIndicator = false, | ||
303 | + AnimationController? progressIndicatorController, | ||
304 | + Color? progressIndicatorBackgroundColor, | ||
305 | + Animation<Color>? progressIndicatorValueColor, | ||
306 | + SnackPosition snackPosition = SnackPosition.BOTTOM, | ||
307 | + SnackStyle snackStyle = SnackStyle.FLOATING, | ||
308 | + Curve forwardAnimationCurve = Curves.easeOutCirc, | ||
309 | + Curve reverseAnimationCurve = Curves.easeOutCirc, | ||
310 | + Duration animationDuration = const Duration(seconds: 1), | ||
311 | + SnackbarStatusCallback? snackbarStatus, | ||
312 | + double barBlur = 0.0, | ||
313 | + double overlayBlur = 0.0, | ||
314 | + Color? overlayColor, | ||
315 | + Form? userInputForm, | ||
316 | + }) { | ||
317 | + final getSnackBar = GetSnackBar( | ||
318 | + snackbarStatus: snackbarStatus, | ||
319 | + title: title, | ||
320 | + message: message, | ||
321 | + titleText: titleText, | ||
322 | + messageText: messageText, | ||
323 | + snackPosition: snackPosition, | ||
324 | + borderRadius: borderRadius, | ||
325 | + margin: margin, | ||
326 | + duration: duration, | ||
327 | + barBlur: barBlur, | ||
328 | + backgroundColor: backgroundColor, | ||
329 | + icon: icon, | ||
330 | + shouldIconPulse: shouldIconPulse, | ||
331 | + maxWidth: maxWidth, | ||
332 | + padding: padding, | ||
333 | + borderColor: borderColor, | ||
334 | + borderWidth: borderWidth, | ||
335 | + leftBarIndicatorColor: leftBarIndicatorColor, | ||
336 | + boxShadows: boxShadows, | ||
337 | + backgroundGradient: backgroundGradient, | ||
338 | + mainButton: mainButton, | ||
339 | + onTap: onTap, | ||
340 | + isDismissible: isDismissible, | ||
341 | + dismissDirection: dismissDirection, | ||
342 | + showProgressIndicator: showProgressIndicator, | ||
343 | + progressIndicatorController: progressIndicatorController, | ||
344 | + progressIndicatorBackgroundColor: progressIndicatorBackgroundColor, | ||
345 | + progressIndicatorValueColor: progressIndicatorValueColor, | ||
346 | + snackStyle: snackStyle, | ||
347 | + forwardAnimationCurve: forwardAnimationCurve, | ||
348 | + reverseAnimationCurve: reverseAnimationCurve, | ||
349 | + animationDuration: animationDuration, | ||
350 | + overlayBlur: overlayBlur, | ||
351 | + overlayColor: overlayColor, | ||
352 | + userInputForm: userInputForm, | ||
353 | + ); | ||
354 | + | ||
355 | + final controller = SnackbarController(getSnackBar); | ||
356 | + | ||
357 | + if (instantInit) { | ||
358 | + controller.show(); | ||
359 | + } else { | ||
360 | + SchedulerBinding.instance!.addPostFrameCallback((_) { | ||
361 | + controller.show(); | ||
362 | + }); | ||
416 | } | 363 | } |
364 | + return controller; | ||
417 | } | 365 | } |
418 | 366 | ||
419 | - Widget baseAlertDialog = AlertDialog( | ||
420 | - titlePadding: titlePadding ?? EdgeInsets.all(8), | ||
421 | - contentPadding: contentPadding ?? EdgeInsets.all(8), | ||
422 | - | ||
423 | - backgroundColor: backgroundColor ?? theme.dialogBackgroundColor, | ||
424 | - shape: RoundedRectangleBorder( | ||
425 | - borderRadius: BorderRadius.all(Radius.circular(radius))), | ||
426 | - title: Text(title, textAlign: TextAlign.center, style: titleStyle), | ||
427 | - content: Column( | ||
428 | - crossAxisAlignment: CrossAxisAlignment.center, | ||
429 | - mainAxisSize: MainAxisSize.min, | ||
430 | - children: [ | ||
431 | - content ?? | ||
432 | - Text(middleText, | ||
433 | - textAlign: TextAlign.center, style: middleTextStyle), | ||
434 | - SizedBox(height: 16), | ||
435 | - ButtonTheme( | ||
436 | - minWidth: 78.0, | ||
437 | - height: 34.0, | ||
438 | - child: Wrap( | ||
439 | - alignment: WrapAlignment.center, | ||
440 | - spacing: 8, | ||
441 | - runSpacing: 8, | ||
442 | - children: actions, | ||
443 | - ), | ||
444 | - ) | ||
445 | - ], | ||
446 | - ), | ||
447 | - // actions: actions, // ?? <Widget>[cancelButton, confirmButton], | ||
448 | - buttonPadding: EdgeInsets.zero, | ||
449 | - ); | ||
450 | - | ||
451 | - return dialog<T>( | ||
452 | - onWillPop != null | ||
453 | - ? WillPopScope( | ||
454 | - onWillPop: onWillPop, | ||
455 | - child: baseAlertDialog, | ||
456 | - ) | ||
457 | - : baseAlertDialog, | ||
458 | - barrierDismissible: barrierDismissible, | ||
459 | - navigatorKey: navigatorKey, | ||
460 | - ); | 367 | + SnackbarController showSnackbar(GetSnackBar snackbar) { |
368 | + final controller = SnackbarController(snackbar); | ||
369 | + controller.show(); | ||
370 | + return controller; | ||
461 | } | 371 | } |
462 | -} | ||
463 | 372 | ||
464 | -extension ExtensionBottomSheet on GetInterface { | ||
465 | - Future<T?> bottomSheet<T>( | ||
466 | - Widget bottomsheet, { | 373 | + SnackbarController snackbar( |
374 | + String title, | ||
375 | + String message, { | ||
376 | + Color? colorText, | ||
377 | + Duration? duration = const Duration(seconds: 3), | ||
378 | + | ||
379 | + /// with instantInit = false you can put snackbar on initState | ||
380 | + bool instantInit = true, | ||
381 | + SnackPosition? snackPosition, | ||
382 | + Widget? titleText, | ||
383 | + Widget? messageText, | ||
384 | + Widget? icon, | ||
385 | + bool? shouldIconPulse, | ||
386 | + double? maxWidth, | ||
387 | + EdgeInsets? margin, | ||
388 | + EdgeInsets? padding, | ||
389 | + double? borderRadius, | ||
390 | + Color? borderColor, | ||
391 | + double? borderWidth, | ||
467 | Color? backgroundColor, | 392 | Color? backgroundColor, |
468 | - double? elevation, | ||
469 | - bool persistent = true, | ||
470 | - ShapeBorder? shape, | ||
471 | - Clip? clipBehavior, | ||
472 | - Color? barrierColor, | ||
473 | - bool? ignoreSafeArea, | ||
474 | - bool isScrollControlled = false, | ||
475 | - bool useRootNavigator = false, | ||
476 | - bool isDismissible = true, | ||
477 | - bool enableDrag = true, | ||
478 | - RouteSettings? settings, | ||
479 | - Duration? enterBottomSheetDuration, | ||
480 | - Duration? exitBottomSheetDuration, | 393 | + Color? leftBarIndicatorColor, |
394 | + List<BoxShadow>? boxShadows, | ||
395 | + Gradient? backgroundGradient, | ||
396 | + TextButton? mainButton, | ||
397 | + OnTap? onTap, | ||
398 | + bool? isDismissible, | ||
399 | + bool? showProgressIndicator, | ||
400 | + DismissDirection? dismissDirection, | ||
401 | + AnimationController? progressIndicatorController, | ||
402 | + Color? progressIndicatorBackgroundColor, | ||
403 | + Animation<Color>? progressIndicatorValueColor, | ||
404 | + SnackStyle? snackStyle, | ||
405 | + Curve? forwardAnimationCurve, | ||
406 | + Curve? reverseAnimationCurve, | ||
407 | + Duration? animationDuration, | ||
408 | + double? barBlur, | ||
409 | + double? overlayBlur, | ||
410 | + SnackbarStatusCallback? snackbarStatus, | ||
411 | + Color? overlayColor, | ||
412 | + Form? userInputForm, | ||
481 | }) { | 413 | }) { |
482 | - return Navigator.of(overlayContext!, rootNavigator: useRootNavigator) | ||
483 | - .push(GetModalBottomSheetRoute<T>( | ||
484 | - builder: (_) => bottomsheet, | ||
485 | - isPersistent: persistent, | ||
486 | - // theme: Theme.of(key.currentContext, shadowThemeOnly: true), | ||
487 | - theme: Theme.of(key.currentContext!), | ||
488 | - isScrollControlled: isScrollControlled, | 414 | + final getSnackBar = GetSnackBar( |
415 | + snackbarStatus: snackbarStatus, | ||
416 | + titleText: titleText ?? | ||
417 | + Text( | ||
418 | + title, | ||
419 | + style: TextStyle( | ||
420 | + color: colorText ?? iconColor ?? Colors.black, | ||
421 | + fontWeight: FontWeight.w800, | ||
422 | + fontSize: 16, | ||
423 | + ), | ||
424 | + ), | ||
425 | + messageText: messageText ?? | ||
426 | + Text( | ||
427 | + message, | ||
428 | + style: TextStyle( | ||
429 | + color: colorText ?? iconColor ?? Colors.black, | ||
430 | + fontWeight: FontWeight.w300, | ||
431 | + fontSize: 14, | ||
432 | + ), | ||
433 | + ), | ||
434 | + snackPosition: snackPosition ?? SnackPosition.TOP, | ||
435 | + borderRadius: borderRadius ?? 15, | ||
436 | + margin: margin ?? EdgeInsets.symmetric(horizontal: 10), | ||
437 | + duration: duration, | ||
438 | + barBlur: barBlur ?? 7.0, | ||
439 | + backgroundColor: backgroundColor ?? Colors.grey.withOpacity(0.2), | ||
440 | + icon: icon, | ||
441 | + shouldIconPulse: shouldIconPulse ?? true, | ||
442 | + maxWidth: maxWidth, | ||
443 | + padding: padding ?? EdgeInsets.all(16), | ||
444 | + borderColor: borderColor, | ||
445 | + borderWidth: borderWidth, | ||
446 | + leftBarIndicatorColor: leftBarIndicatorColor, | ||
447 | + boxShadows: boxShadows, | ||
448 | + backgroundGradient: backgroundGradient, | ||
449 | + mainButton: mainButton, | ||
450 | + onTap: onTap, | ||
451 | + isDismissible: isDismissible ?? true, | ||
452 | + dismissDirection: dismissDirection, | ||
453 | + showProgressIndicator: showProgressIndicator ?? false, | ||
454 | + progressIndicatorController: progressIndicatorController, | ||
455 | + progressIndicatorBackgroundColor: progressIndicatorBackgroundColor, | ||
456 | + progressIndicatorValueColor: progressIndicatorValueColor, | ||
457 | + snackStyle: snackStyle ?? SnackStyle.FLOATING, | ||
458 | + forwardAnimationCurve: forwardAnimationCurve ?? Curves.easeOutCirc, | ||
459 | + reverseAnimationCurve: reverseAnimationCurve ?? Curves.easeOutCirc, | ||
460 | + animationDuration: animationDuration ?? Duration(seconds: 1), | ||
461 | + overlayBlur: overlayBlur ?? 0.0, | ||
462 | + overlayColor: overlayColor ?? Colors.transparent, | ||
463 | + userInputForm: userInputForm); | ||
489 | 464 | ||
490 | - barrierLabel: MaterialLocalizations.of(key.currentContext!) | ||
491 | - .modalBarrierDismissLabel, | 465 | + final controller = SnackbarController(getSnackBar); |
492 | 466 | ||
493 | - backgroundColor: backgroundColor ?? Colors.transparent, | ||
494 | - elevation: elevation, | ||
495 | - shape: shape, | ||
496 | - removeTop: ignoreSafeArea ?? true, | ||
497 | - clipBehavior: clipBehavior, | ||
498 | - isDismissible: isDismissible, | ||
499 | - modalBarrierColor: barrierColor, | ||
500 | - settings: settings, | ||
501 | - enableDrag: enableDrag, | ||
502 | - enterBottomSheetDuration: | ||
503 | - enterBottomSheetDuration ?? const Duration(milliseconds: 250), | ||
504 | - exitBottomSheetDuration: | ||
505 | - exitBottomSheetDuration ?? const Duration(milliseconds: 200), | ||
506 | - )); | 467 | + if (instantInit) { |
468 | + controller.show(); | ||
469 | + } else { | ||
470 | + //routing.isSnackbar = true; | ||
471 | + SchedulerBinding.instance!.addPostFrameCallback((_) { | ||
472 | + controller.show(); | ||
473 | + }); | ||
474 | + } | ||
475 | + return controller; | ||
507 | } | 476 | } |
508 | } | 477 | } |
509 | 478 | ||
@@ -826,11 +795,11 @@ you can only use widgets and widget functions here'''; | @@ -826,11 +795,11 @@ you can only use widgets and widget functions here'''; | ||
826 | 795 | ||
827 | /// Returns true if a Snackbar, Dialog or BottomSheet is currently OPEN | 796 | /// Returns true if a Snackbar, Dialog or BottomSheet is currently OPEN |
828 | bool get isOverlaysOpen => | 797 | bool get isOverlaysOpen => |
829 | - (isSnackbarOpen! || isDialogOpen! || isBottomSheetOpen!); | 798 | + (isSnackbarOpen || isDialogOpen! || isBottomSheetOpen!); |
830 | 799 | ||
831 | /// Returns true if there is no Snackbar, Dialog or BottomSheet open | 800 | /// Returns true if there is no Snackbar, Dialog or BottomSheet open |
832 | bool get isOverlaysClosed => | 801 | bool get isOverlaysClosed => |
833 | - (!isSnackbarOpen! && !isDialogOpen! && !isBottomSheetOpen!); | 802 | + (!isSnackbarOpen && !isDialogOpen! && !isBottomSheetOpen!); |
834 | 803 | ||
835 | /// **Navigation.popUntil()** shortcut.<br><br> | 804 | /// **Navigation.popUntil()** shortcut.<br><br> |
836 | /// | 805 | /// |
@@ -850,9 +819,21 @@ you can only use widgets and widget functions here'''; | @@ -850,9 +819,21 @@ you can only use widgets and widget functions here'''; | ||
850 | bool canPop = true, | 819 | bool canPop = true, |
851 | int? id, | 820 | int? id, |
852 | }) { | 821 | }) { |
822 | + //TODO: This code brings compatibility of the new snackbar with GetX 4, | ||
823 | + // remove this code in version 5 | ||
824 | + if (isSnackbarOpen && !closeOverlays) { | ||
825 | + closeCurrentSnackbar(); | ||
826 | + return; | ||
827 | + } | ||
828 | + | ||
853 | if (closeOverlays && isOverlaysOpen) { | 829 | if (closeOverlays && isOverlaysOpen) { |
830 | + //TODO: This code brings compatibility of the new snackbar with GetX 4, | ||
831 | + // remove this code in version 5 | ||
832 | + if (isSnackbarOpen) { | ||
833 | + closeAllSnackbars(); | ||
834 | + } | ||
854 | navigator?.popUntil((route) { | 835 | navigator?.popUntil((route) { |
855 | - return (isOverlaysClosed); | 836 | + return (!isDialogOpen! && !isBottomSheetOpen!); |
856 | }); | 837 | }); |
857 | } | 838 | } |
858 | if (canPop) { | 839 | if (canPop) { |
@@ -1135,7 +1116,16 @@ you can only use widgets and widget functions here'''; | @@ -1135,7 +1116,16 @@ you can only use widgets and widget functions here'''; | ||
1135 | String get previousRoute => routing.previous; | 1116 | String get previousRoute => routing.previous; |
1136 | 1117 | ||
1137 | /// check if snackbar is open | 1118 | /// check if snackbar is open |
1138 | - bool? get isSnackbarOpen => routing.isSnackbar; | 1119 | + bool get isSnackbarOpen => |
1120 | + SnackbarController.isSnackbarBeingShown; //routing.isSnackbar; | ||
1121 | + | ||
1122 | + void closeAllSnackbars() { | ||
1123 | + SnackbarController.cancelAllSnackbars(); | ||
1124 | + } | ||
1125 | + | ||
1126 | + Future<void> closeCurrentSnackbar() async { | ||
1127 | + await SnackbarController.closeCurrentSnackbar(); | ||
1128 | + } | ||
1139 | 1129 | ||
1140 | /// check if dialog is open | 1130 | /// check if dialog is open |
1141 | bool? get isDialogOpen => routing.isDialog; | 1131 | bool? get isDialogOpen => routing.isDialog; |
@@ -1341,7 +1331,48 @@ extension NavTwoExt on GetInterface { | @@ -1341,7 +1331,48 @@ extension NavTwoExt on GetInterface { | ||
1341 | } | 1331 | } |
1342 | } | 1332 | } |
1343 | 1333 | ||
1344 | -/// It replaces the Flutter Navigator, but needs no context. | ||
1345 | -/// You can to use navigator.push(YourRoute()) rather | ||
1346 | -/// Navigator.push(context, YourRoute()); | ||
1347 | -NavigatorState? get navigator => GetNavigation(Get).key.currentState; | 1334 | +extension OverlayExt on GetInterface { |
1335 | + Future<T> showOverlay<T>({ | ||
1336 | + required Future<T> Function() asyncFunction, | ||
1337 | + Color opacityColor = Colors.black, | ||
1338 | + Widget? loadingWidget, | ||
1339 | + double opacity = .5, | ||
1340 | + }) async { | ||
1341 | + final navigatorState = | ||
1342 | + Navigator.of(Get.overlayContext!, rootNavigator: false); | ||
1343 | + final overlayState = navigatorState.overlay!; | ||
1344 | + | ||
1345 | + final overlayEntryOpacity = OverlayEntry(builder: (context) { | ||
1346 | + return Opacity( | ||
1347 | + opacity: opacity, | ||
1348 | + child: Container( | ||
1349 | + color: opacityColor, | ||
1350 | + )); | ||
1351 | + }); | ||
1352 | + final overlayEntryLoader = OverlayEntry(builder: (context) { | ||
1353 | + return loadingWidget ?? | ||
1354 | + Center( | ||
1355 | + child: Container( | ||
1356 | + height: 90, | ||
1357 | + width: 90, | ||
1358 | + child: Text('Loading...'), | ||
1359 | + )); | ||
1360 | + }); | ||
1361 | + overlayState.insert(overlayEntryOpacity); | ||
1362 | + overlayState.insert(overlayEntryLoader); | ||
1363 | + | ||
1364 | + T data; | ||
1365 | + | ||
1366 | + try { | ||
1367 | + data = await asyncFunction(); | ||
1368 | + } on Exception catch (_) { | ||
1369 | + overlayEntryLoader.remove(); | ||
1370 | + overlayEntryOpacity.remove(); | ||
1371 | + rethrow; | ||
1372 | + } | ||
1373 | + | ||
1374 | + overlayEntryLoader.remove(); | ||
1375 | + overlayEntryOpacity.remove(); | ||
1376 | + return data; | ||
1377 | + } | ||
1378 | +} |
1 | import 'package:flutter/foundation.dart'; | 1 | import 'package:flutter/foundation.dart'; |
2 | import 'package:flutter/widgets.dart'; | 2 | import 'package:flutter/widgets.dart'; |
3 | + | ||
3 | import '../../../get.dart'; | 4 | import '../../../get.dart'; |
4 | 5 | ||
5 | class GetInformationParser extends RouteInformationParser<GetNavConfig> { | 6 | class GetInformationParser extends RouteInformationParser<GetNavConfig> { |
@@ -14,7 +15,6 @@ class GetInformationParser extends RouteInformationParser<GetNavConfig> { | @@ -14,7 +15,6 @@ class GetInformationParser extends RouteInformationParser<GetNavConfig> { | ||
14 | SynchronousFuture<GetNavConfig> parseRouteInformation( | 15 | SynchronousFuture<GetNavConfig> parseRouteInformation( |
15 | RouteInformation routeInformation, | 16 | RouteInformation routeInformation, |
16 | ) { | 17 | ) { |
17 | - Get.log('GetInformationParser: route location: ${routeInformation.location}'); | ||
18 | var location = routeInformation.location; | 18 | var location = routeInformation.location; |
19 | if (location == '/') { | 19 | if (location == '/') { |
20 | //check if there is a corresponding page | 20 | //check if there is a corresponding page |
@@ -24,6 +24,8 @@ class GetInformationParser extends RouteInformationParser<GetNavConfig> { | @@ -24,6 +24,8 @@ class GetInformationParser extends RouteInformationParser<GetNavConfig> { | ||
24 | } | 24 | } |
25 | } | 25 | } |
26 | 26 | ||
27 | + Get.log('GetInformationParser: route location: $location'); | ||
28 | + | ||
27 | final matchResult = Get.routeTree.matchRoute(location ?? initialRoute); | 29 | final matchResult = Get.routeTree.matchRoute(location ?? initialRoute); |
28 | 30 | ||
29 | return SynchronousFuture( | 31 | return SynchronousFuture( |
@@ -2,50 +2,10 @@ import 'dart:async'; | @@ -2,50 +2,10 @@ import 'dart:async'; | ||
2 | 2 | ||
3 | import 'package:flutter/foundation.dart'; | 3 | import 'package:flutter/foundation.dart'; |
4 | import 'package:flutter/material.dart'; | 4 | import 'package:flutter/material.dart'; |
5 | + | ||
5 | import '../../../get.dart'; | 6 | import '../../../get.dart'; |
6 | import '../../../get_state_manager/src/simple/list_notifier.dart'; | 7 | import '../../../get_state_manager/src/simple/list_notifier.dart'; |
7 | 8 | ||
8 | -/// Enables the user to customize the intended pop behavior | ||
9 | -/// | ||
10 | -/// Goes to either the previous history entry or the previous page entry | ||
11 | -/// | ||
12 | -/// e.g. if the user navigates to these pages | ||
13 | -/// 1) /home | ||
14 | -/// 2) /home/products/1234 | ||
15 | -/// | ||
16 | -/// when popping on [History] mode, it will emulate a browser back button. | ||
17 | -/// | ||
18 | -/// so the new history stack will be: | ||
19 | -/// 1) /home | ||
20 | -/// | ||
21 | -/// when popping on [Page] mode, it will only remove the last part of the route | ||
22 | -/// so the new history stack will be: | ||
23 | -/// 1) /home | ||
24 | -/// 2) /home/products | ||
25 | -/// | ||
26 | -/// another pop will change the history stack to: | ||
27 | -/// 1) /home | ||
28 | -enum PopMode { | ||
29 | - History, | ||
30 | - Page, | ||
31 | -} | ||
32 | - | ||
33 | -/// Enables the user to customize the behavior when pushing multiple routes that | ||
34 | -/// shouldn't be duplicates | ||
35 | -enum PreventDuplicateHandlingMode { | ||
36 | - /// Removes the history entries until it reaches the old route | ||
37 | - PopUntilOriginalRoute, | ||
38 | - | ||
39 | - /// Simply don't push the new route | ||
40 | - DoNothing, | ||
41 | - | ||
42 | - /// Recommended - Moves the old route entry to the front | ||
43 | - /// | ||
44 | - /// With this mode, you guarantee there will be only one | ||
45 | - /// route entry for each location | ||
46 | - ReorderRoutes | ||
47 | -} | ||
48 | - | ||
49 | class GetDelegate extends RouterDelegate<GetNavConfig> | 9 | class GetDelegate extends RouterDelegate<GetNavConfig> |
50 | with ListenableMixin, ListNotifierMixin { | 10 | with ListenableMixin, ListNotifierMixin { |
51 | final List<GetNavConfig> history = <GetNavConfig>[]; | 11 | final List<GetNavConfig> history = <GetNavConfig>[]; |
@@ -57,7 +17,7 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | @@ -57,7 +17,7 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | ||
57 | final List<NavigatorObserver>? navigatorObservers; | 17 | final List<NavigatorObserver>? navigatorObservers; |
58 | final TransitionDelegate<dynamic>? transitionDelegate; | 18 | final TransitionDelegate<dynamic>? transitionDelegate; |
59 | 19 | ||
60 | - GlobalKey<NavigatorState> get navigatorKey => Get.key; | 20 | + final _allCompleters = <GetPage, Completer>{}; |
61 | 21 | ||
62 | GetDelegate({ | 22 | GetDelegate({ |
63 | GetPage? notFoundRoute, | 23 | GetPage? notFoundRoute, |
@@ -76,191 +36,72 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | @@ -76,191 +36,72 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | ||
76 | Get.log('GetDelegate is created !'); | 36 | Get.log('GetDelegate is created !'); |
77 | } | 37 | } |
78 | 38 | ||
79 | - Future<GetNavConfig?> runMiddleware(GetNavConfig config) async { | ||
80 | - final middlewares = config.currentTreeBranch.last.middlewares; | ||
81 | - if (middlewares == null) { | ||
82 | - return config; | ||
83 | - } | ||
84 | - var iterator = config; | ||
85 | - for (var item in middlewares) { | ||
86 | - var redirectRes = await item.redirectDelegate(iterator); | ||
87 | - if (redirectRes == null) return null; | ||
88 | - iterator = redirectRes; | ||
89 | - } | ||
90 | - return iterator; | ||
91 | - } | ||
92 | - | ||
93 | - Future<void> _unsafeHistoryAdd(GetNavConfig config) async { | ||
94 | - final res = await runMiddleware(config); | ||
95 | - if (res == null) return; | ||
96 | - history.add(res); | 39 | + @override |
40 | + GetNavConfig? get currentConfiguration { | ||
41 | + if (history.isEmpty) return null; | ||
42 | + final route = history.last; | ||
43 | + return route; | ||
97 | } | 44 | } |
98 | 45 | ||
99 | - Future<void> _unsafeHistoryRemove(GetNavConfig config) async { | ||
100 | - var index = history.indexOf(config); | ||
101 | - if (index >= 0) await _unsafeHistoryRemoveAt(index); | ||
102 | - } | 46 | + GlobalKey<NavigatorState> get navigatorKey => Get.key; |
103 | 47 | ||
104 | - Future<GetNavConfig?> _unsafeHistoryRemoveAt(int index) async { | ||
105 | - if (index == history.length - 1 && history.length > 1) { | ||
106 | - //removing WILL update the current route | ||
107 | - final toCheck = history[history.length - 2]; | ||
108 | - final resMiddleware = await runMiddleware(toCheck); | ||
109 | - if (resMiddleware == null) return null; | ||
110 | - history[history.length - 2] = resMiddleware; | ||
111 | - } | ||
112 | - return history.removeAt(index); | 48 | + Map<String, String> get parameters { |
49 | + return currentConfiguration?.currentPage?.parameters ?? {}; | ||
113 | } | 50 | } |
114 | 51 | ||
115 | T arguments<T>() { | 52 | T arguments<T>() { |
116 | return currentConfiguration?.currentPage?.arguments as T; | 53 | return currentConfiguration?.currentPage?.arguments as T; |
117 | } | 54 | } |
118 | 55 | ||
119 | - Map<String, String> get parameters { | ||
120 | - return currentConfiguration?.currentPage?.parameters ?? {}; | ||
121 | - } | ||
122 | - | ||
123 | - // void _unsafeHistoryClear() { | ||
124 | - // history.clear(); | ||
125 | - // } | ||
126 | - | ||
127 | - /// Adds a new history entry and waits for the result | ||
128 | - Future<void> pushHistory( | ||
129 | - GetNavConfig config, { | ||
130 | - bool rebuildStack = true, | 56 | + /// Removes routes according to [PopMode] |
57 | + /// until it reaches the specifc [fullRoute], | ||
58 | + /// DOES NOT remove the [fullRoute] | ||
59 | + Future<void> backUntil( | ||
60 | + String fullRoute, { | ||
61 | + PopMode popMode = PopMode.Page, | ||
131 | }) async { | 62 | }) async { |
132 | - //this changes the currentConfiguration | ||
133 | - await _pushHistory(config); | ||
134 | - if (rebuildStack) { | ||
135 | - refresh(); | ||
136 | - } | ||
137 | - } | ||
138 | - | ||
139 | - Future<void> _removeHistoryEntry(GetNavConfig entry) async { | ||
140 | - await _unsafeHistoryRemove(entry); | ||
141 | - } | ||
142 | - | ||
143 | - Future<void> _pushHistory(GetNavConfig config) async { | ||
144 | - if (config.currentPage!.preventDuplicates) { | ||
145 | - final originalEntryIndex = | ||
146 | - history.indexWhere((element) => element.location == config.location); | ||
147 | - if (originalEntryIndex >= 0) { | ||
148 | - switch (preventDuplicateHandlingMode) { | ||
149 | - case PreventDuplicateHandlingMode.PopUntilOriginalRoute: | ||
150 | - await backUntil(config.location!, popMode: PopMode.Page); | ||
151 | - break; | ||
152 | - case PreventDuplicateHandlingMode.ReorderRoutes: | ||
153 | - await _unsafeHistoryRemoveAt(originalEntryIndex); | ||
154 | - await _unsafeHistoryAdd(config); | ||
155 | - break; | ||
156 | - case PreventDuplicateHandlingMode.DoNothing: | ||
157 | - default: | ||
158 | - break; | ||
159 | - } | ||
160 | - return; | ||
161 | - } | ||
162 | - } | ||
163 | - await _unsafeHistoryAdd(config); | ||
164 | - } | ||
165 | - | ||
166 | - // GetPageRoute getPageRoute(RouteSettings? settings) { | ||
167 | - // return PageRedirect(settings ?? RouteSettings(name: '/404'), _notFound()) | ||
168 | - // .page(); | ||
169 | - // } | ||
170 | - | ||
171 | - Future<GetNavConfig?> _popHistory() async { | ||
172 | - if (!_canPopHistory()) return null; | ||
173 | - return await _doPopHistory(); | ||
174 | - } | ||
175 | - | ||
176 | - Future<GetNavConfig?> _doPopHistory() async { | ||
177 | - return await _unsafeHistoryRemoveAt(history.length - 1); | ||
178 | - } | ||
179 | - | ||
180 | - Future<GetNavConfig?> _popPage() async { | ||
181 | - if (!_canPopPage()) return null; | ||
182 | - return await _doPopPage(); | ||
183 | - } | ||
184 | - | ||
185 | - Future<GetNavConfig?> _pop(PopMode mode) async { | ||
186 | - switch (mode) { | ||
187 | - case PopMode.History: | ||
188 | - return await _popHistory(); | ||
189 | - case PopMode.Page: | ||
190 | - return await _popPage(); | ||
191 | - default: | ||
192 | - return null; | ||
193 | - } | ||
194 | - } | ||
195 | - | ||
196 | - // returns the popped page | ||
197 | - Future<GetNavConfig?> _doPopPage() async { | ||
198 | - final currentBranch = currentConfiguration?.currentTreeBranch; | ||
199 | - if (currentBranch != null && currentBranch.length > 1) { | ||
200 | - //remove last part only | ||
201 | - final remaining = currentBranch.take(currentBranch.length - 1); | ||
202 | - final prevHistoryEntry = | ||
203 | - history.length > 1 ? history[history.length - 2] : null; | ||
204 | - | ||
205 | - //check if current route is the same as the previous route | ||
206 | - if (prevHistoryEntry != null) { | ||
207 | - //if so, pop the entire history entry | ||
208 | - final newLocation = remaining.last.name; | ||
209 | - final prevLocation = prevHistoryEntry.location; | ||
210 | - if (newLocation == prevLocation) { | ||
211 | - //pop the entire history entry | ||
212 | - return await _popHistory(); | 63 | + // remove history or page entries until you meet route |
64 | + var iterator = currentConfiguration; | ||
65 | + while (_canPop(popMode) && | ||
66 | + iterator != null && | ||
67 | + iterator.location != fullRoute) { | ||
68 | + await _pop(popMode); | ||
69 | + // replace iterator | ||
70 | + iterator = currentConfiguration; | ||
213 | } | 71 | } |
72 | + refresh(); | ||
214 | } | 73 | } |
215 | 74 | ||
216 | - //create a new route with the remaining tree branch | ||
217 | - final res = await _popHistory(); | ||
218 | - await _pushHistory( | ||
219 | - GetNavConfig( | ||
220 | - currentTreeBranch: remaining.toList(), | ||
221 | - location: remaining.last.name, | ||
222 | - state: null, //TOOD: persist state?? | ||
223 | - ), | 75 | + @override |
76 | + Widget build(BuildContext context) { | ||
77 | + final pages = getVisualPages(); | ||
78 | + if (pages.length == 0) return SizedBox.shrink(); | ||
79 | + final extraObservers = navigatorObservers; | ||
80 | + return GetNavigator( | ||
81 | + key: navigatorKey, | ||
82 | + onPopPage: _onPopVisualRoute, | ||
83 | + pages: pages, | ||
84 | + observers: [ | ||
85 | + GetObserver(), | ||
86 | + if (extraObservers != null) ...extraObservers, | ||
87 | + ], | ||
88 | + transitionDelegate: | ||
89 | + transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(), | ||
224 | ); | 90 | ); |
225 | - return res; | ||
226 | - } else { | ||
227 | - //remove entire entry | ||
228 | - return await _popHistory(); | ||
229 | - } | ||
230 | } | 91 | } |
231 | 92 | ||
232 | - Future<GetNavConfig?> popHistory() async { | ||
233 | - return await _popHistory(); | ||
234 | - } | ||
235 | - | ||
236 | - bool _canPopHistory() { | ||
237 | - return history.length > 1; | ||
238 | - } | 93 | + // void _unsafeHistoryClear() { |
94 | + // history.clear(); | ||
95 | + // } | ||
239 | 96 | ||
240 | Future<bool> canPopHistory() { | 97 | Future<bool> canPopHistory() { |
241 | return SynchronousFuture(_canPopHistory()); | 98 | return SynchronousFuture(_canPopHistory()); |
242 | } | 99 | } |
243 | 100 | ||
244 | - bool _canPopPage() { | ||
245 | - final currentTreeBranch = currentConfiguration?.currentTreeBranch; | ||
246 | - if (currentTreeBranch == null) return false; | ||
247 | - return currentTreeBranch.length > 1 ? true : _canPopHistory(); | ||
248 | - } | ||
249 | - | ||
250 | Future<bool> canPopPage() { | 101 | Future<bool> canPopPage() { |
251 | return SynchronousFuture(_canPopPage()); | 102 | return SynchronousFuture(_canPopPage()); |
252 | } | 103 | } |
253 | 104 | ||
254 | - bool _canPop(PopMode mode) { | ||
255 | - switch (mode) { | ||
256 | - case PopMode.History: | ||
257 | - return _canPopHistory(); | ||
258 | - case PopMode.Page: | ||
259 | - default: | ||
260 | - return _canPopPage(); | ||
261 | - } | ||
262 | - } | ||
263 | - | ||
264 | /// gets the visual pages from the current history entry | 105 | /// gets the visual pages from the current history entry |
265 | /// | 106 | /// |
266 | /// visual pages must have [participatesInRootNavigator] set to true | 107 | /// visual pages must have [participatesInRootNavigator] set to true |
@@ -281,44 +122,104 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | @@ -281,44 +122,104 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | ||
281 | } | 122 | } |
282 | } | 123 | } |
283 | 124 | ||
125 | + // GetPageRoute getPageRoute(RouteSettings? settings) { | ||
126 | + // return PageRedirect(settings ?? RouteSettings(name: '/404'), _notFound()) | ||
127 | + // .page(); | ||
128 | + // } | ||
129 | + | ||
130 | + Future<bool> handlePopupRoutes({ | ||
131 | + Object? result, | ||
132 | + }) async { | ||
133 | + Route? currentRoute; | ||
134 | + navigatorKey.currentState!.popUntil((route) { | ||
135 | + currentRoute = route; | ||
136 | + return true; | ||
137 | + }); | ||
138 | + if (currentRoute is PopupRoute) { | ||
139 | + return await navigatorKey.currentState!.maybePop(result); | ||
140 | + } | ||
141 | + return false; | ||
142 | + } | ||
143 | + | ||
144 | + Future<T?>? offAndToNamed<T>( | ||
145 | + String page, { | ||
146 | + dynamic arguments, | ||
147 | + int? id, | ||
148 | + dynamic result, | ||
149 | + Map<String, String>? parameters, | ||
150 | + PopMode popMode = PopMode.History, | ||
151 | + }) async { | ||
152 | + if (parameters != null) { | ||
153 | + final uri = Uri(path: page, queryParameters: parameters); | ||
154 | + page = uri.toString(); | ||
155 | + } | ||
156 | + | ||
157 | + await popRoute(result: result); | ||
158 | + return toNamed(page, arguments: arguments, parameters: parameters); | ||
159 | + } | ||
160 | + | ||
161 | + Future<T> offNamed<T>( | ||
162 | + String page, { | ||
163 | + dynamic arguments, | ||
164 | + Map<String, String>? parameters, | ||
165 | + }) async { | ||
166 | + history.removeLast(); | ||
167 | + return toNamed<T>(page, arguments: arguments, parameters: parameters); | ||
168 | + } | ||
169 | + | ||
170 | + Future<GetNavConfig?> popHistory() async { | ||
171 | + return await _popHistory(); | ||
172 | + } | ||
173 | + | ||
174 | + // returns the popped page | ||
284 | @override | 175 | @override |
285 | - Widget build(BuildContext context) { | ||
286 | - final pages = getVisualPages(); | ||
287 | - if (pages.length == 0) return SizedBox.shrink(); | ||
288 | - final extraObservers = navigatorObservers; | ||
289 | - return GetNavigator( | ||
290 | - key: navigatorKey, | ||
291 | - onPopPage: _onPopVisualRoute, | ||
292 | - pages: pages, | ||
293 | - observers: [ | ||
294 | - GetObserver(), | ||
295 | - if (extraObservers != null) ...extraObservers, | ||
296 | - ], | ||
297 | - transitionDelegate: | ||
298 | - transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(), | ||
299 | - ); | 176 | + Future<bool> popRoute({ |
177 | + Object? result, | ||
178 | + PopMode popMode = PopMode.Page, | ||
179 | + }) async { | ||
180 | + //Returning false will cause the entire app to be popped. | ||
181 | + final wasPopup = await handlePopupRoutes(result: result); | ||
182 | + if (wasPopup) return true; | ||
183 | + final _popped = await _pop(popMode); | ||
184 | + refresh(); | ||
185 | + if (_popped != null) { | ||
186 | + //emulate the old pop with result | ||
187 | + return true; | ||
188 | + } | ||
189 | + return false; | ||
190 | + } | ||
191 | + | ||
192 | + /// Adds a new history entry and waits for the result | ||
193 | + Future<void> pushHistory( | ||
194 | + GetNavConfig config, { | ||
195 | + bool rebuildStack = true, | ||
196 | + }) async { | ||
197 | + //this changes the currentConfiguration | ||
198 | + await _pushHistory(config); | ||
199 | + if (rebuildStack) { | ||
200 | + refresh(); | ||
201 | + } | ||
202 | + } | ||
203 | + | ||
204 | + Future<GetNavConfig?> runMiddleware(GetNavConfig config) async { | ||
205 | + final middlewares = config.currentTreeBranch.last.middlewares; | ||
206 | + if (middlewares == null) { | ||
207 | + return config; | ||
208 | + } | ||
209 | + var iterator = config; | ||
210 | + for (var item in middlewares) { | ||
211 | + var redirectRes = await item.redirectDelegate(iterator); | ||
212 | + if (redirectRes == null) return null; | ||
213 | + iterator = redirectRes; | ||
214 | + } | ||
215 | + return iterator; | ||
300 | } | 216 | } |
301 | - | ||
302 | - // @override | ||
303 | - // Future<void> setInitialRoutePath(GetNavConfig configuration) async { | ||
304 | - // //no need to clear history with Reorder route strategy | ||
305 | - // // _unsafeHistoryClear(); | ||
306 | - // // _resultCompleter.clear(); | ||
307 | - // await pushHistory(configuration); | ||
308 | - // } | ||
309 | 217 | ||
310 | @override | 218 | @override |
311 | Future<void> setNewRoutePath(GetNavConfig configuration) async { | 219 | Future<void> setNewRoutePath(GetNavConfig configuration) async { |
312 | await pushHistory(configuration); | 220 | await pushHistory(configuration); |
313 | } | 221 | } |
314 | 222 | ||
315 | - @override | ||
316 | - GetNavConfig? get currentConfiguration { | ||
317 | - if (history.isEmpty) return null; | ||
318 | - final route = history.last; | ||
319 | - return route; | ||
320 | - } | ||
321 | - | ||
322 | Future<T> toNamed<T>( | 223 | Future<T> toNamed<T>( |
323 | String page, { | 224 | String page, { |
324 | dynamic arguments, | 225 | dynamic arguments, |
@@ -352,84 +253,73 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | @@ -352,84 +253,73 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | ||
352 | } | 253 | } |
353 | } | 254 | } |
354 | 255 | ||
355 | - Future<T?>? offAndToNamed<T>( | ||
356 | - String page, { | ||
357 | - dynamic arguments, | ||
358 | - int? id, | ||
359 | - dynamic result, | ||
360 | - Map<String, String>? parameters, | ||
361 | - PopMode popMode = PopMode.History, | ||
362 | - }) async { | ||
363 | - if (parameters != null) { | ||
364 | - final uri = Uri(path: page, queryParameters: parameters); | ||
365 | - page = uri.toString(); | 256 | + bool _canPop(PopMode mode) { |
257 | + switch (mode) { | ||
258 | + case PopMode.History: | ||
259 | + return _canPopHistory(); | ||
260 | + case PopMode.Page: | ||
261 | + default: | ||
262 | + return _canPopPage(); | ||
366 | } | 263 | } |
367 | - | ||
368 | - await popRoute(result: result); | ||
369 | - return toNamed(page, arguments: arguments, parameters: parameters); | ||
370 | } | 264 | } |
371 | 265 | ||
372 | - Future<T> offNamed<T>( | ||
373 | - String page, { | ||
374 | - dynamic arguments, | ||
375 | - Map<String, String>? parameters, | ||
376 | - }) async { | ||
377 | - history.removeLast(); | ||
378 | - return toNamed<T>(page, arguments: arguments, parameters: parameters); | 266 | + bool _canPopHistory() { |
267 | + return history.length > 1; | ||
379 | } | 268 | } |
380 | 269 | ||
381 | - /// Removes routes according to [PopMode] | ||
382 | - /// until it reaches the specifc [fullRoute], | ||
383 | - /// DOES NOT remove the [fullRoute] | ||
384 | - Future<void> backUntil( | ||
385 | - String fullRoute, { | ||
386 | - PopMode popMode = PopMode.Page, | ||
387 | - }) async { | ||
388 | - // remove history or page entries until you meet route | ||
389 | - var iterator = currentConfiguration; | ||
390 | - while (_canPop(popMode) && | ||
391 | - iterator != null && | ||
392 | - iterator.location != fullRoute) { | ||
393 | - await _pop(popMode); | ||
394 | - // replace iterator | ||
395 | - iterator = currentConfiguration; | 270 | + bool _canPopPage() { |
271 | + final currentTreeBranch = currentConfiguration?.currentTreeBranch; | ||
272 | + if (currentTreeBranch == null) return false; | ||
273 | + return currentTreeBranch.length > 1 ? true : _canPopHistory(); | ||
396 | } | 274 | } |
397 | - refresh(); | 275 | + |
276 | + Future<GetNavConfig?> _doPopHistory() async { | ||
277 | + return await _unsafeHistoryRemoveAt(history.length - 1); | ||
398 | } | 278 | } |
399 | 279 | ||
400 | - Future<bool> handlePopupRoutes({ | ||
401 | - Object? result, | ||
402 | - }) async { | ||
403 | - Route? currentRoute; | ||
404 | - navigatorKey.currentState!.popUntil((route) { | ||
405 | - currentRoute = route; | ||
406 | - return true; | ||
407 | - }); | ||
408 | - if (currentRoute is PopupRoute) { | ||
409 | - return await navigatorKey.currentState!.maybePop(result); | 280 | + // @override |
281 | + // Future<void> setInitialRoutePath(GetNavConfig configuration) async { | ||
282 | + // //no need to clear history with Reorder route strategy | ||
283 | + // // _unsafeHistoryClear(); | ||
284 | + // // _resultCompleter.clear(); | ||
285 | + // await pushHistory(configuration); | ||
286 | + // } | ||
287 | + | ||
288 | + Future<GetNavConfig?> _doPopPage() async { | ||
289 | + final currentBranch = currentConfiguration?.currentTreeBranch; | ||
290 | + if (currentBranch != null && currentBranch.length > 1) { | ||
291 | + //remove last part only | ||
292 | + final remaining = currentBranch.take(currentBranch.length - 1); | ||
293 | + final prevHistoryEntry = | ||
294 | + history.length > 1 ? history[history.length - 2] : null; | ||
295 | + | ||
296 | + //check if current route is the same as the previous route | ||
297 | + if (prevHistoryEntry != null) { | ||
298 | + //if so, pop the entire history entry | ||
299 | + final newLocation = remaining.last.name; | ||
300 | + final prevLocation = prevHistoryEntry.location; | ||
301 | + if (newLocation == prevLocation) { | ||
302 | + //pop the entire history entry | ||
303 | + return await _popHistory(); | ||
410 | } | 304 | } |
411 | - return false; | ||
412 | } | 305 | } |
413 | 306 | ||
414 | - @override | ||
415 | - Future<bool> popRoute({ | ||
416 | - Object? result, | ||
417 | - PopMode popMode = PopMode.Page, | ||
418 | - }) async { | ||
419 | - //Returning false will cause the entire app to be popped. | ||
420 | - final wasPopup = await handlePopupRoutes(result: result); | ||
421 | - if (wasPopup) return true; | ||
422 | - final _popped = await _pop(popMode); | ||
423 | - refresh(); | ||
424 | - if (_popped != null) { | ||
425 | - //emulate the old pop with result | ||
426 | - return true; | 307 | + //create a new route with the remaining tree branch |
308 | + final res = await _popHistory(); | ||
309 | + await _pushHistory( | ||
310 | + GetNavConfig( | ||
311 | + currentTreeBranch: remaining.toList(), | ||
312 | + location: remaining.last.name, | ||
313 | + state: null, //TOOD: persist state?? | ||
314 | + ), | ||
315 | + ); | ||
316 | + return res; | ||
317 | + } else { | ||
318 | + //remove entire entry | ||
319 | + return await _popHistory(); | ||
427 | } | 320 | } |
428 | - return false; | ||
429 | } | 321 | } |
430 | 322 | ||
431 | - final _allCompleters = <GetPage, Completer>{}; | ||
432 | - | ||
433 | bool _onPopVisualRoute(Route<dynamic> route, dynamic result) { | 323 | bool _onPopVisualRoute(Route<dynamic> route, dynamic result) { |
434 | final didPop = route.didPop(result); | 324 | final didPop = route.didPop(result); |
435 | if (!didPop) { | 325 | if (!didPop) { |
@@ -452,21 +342,89 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | @@ -452,21 +342,89 @@ class GetDelegate extends RouterDelegate<GetNavConfig> | ||
452 | 342 | ||
453 | return true; | 343 | return true; |
454 | } | 344 | } |
345 | + | ||
346 | + Future<GetNavConfig?> _pop(PopMode mode) async { | ||
347 | + switch (mode) { | ||
348 | + case PopMode.History: | ||
349 | + return await _popHistory(); | ||
350 | + case PopMode.Page: | ||
351 | + return await _popPage(); | ||
352 | + default: | ||
353 | + return null; | ||
354 | + } | ||
355 | + } | ||
356 | + | ||
357 | + Future<GetNavConfig?> _popHistory() async { | ||
358 | + if (!_canPopHistory()) return null; | ||
359 | + return await _doPopHistory(); | ||
360 | + } | ||
361 | + | ||
362 | + Future<GetNavConfig?> _popPage() async { | ||
363 | + if (!_canPopPage()) return null; | ||
364 | + return await _doPopPage(); | ||
365 | + } | ||
366 | + | ||
367 | + Future<void> _pushHistory(GetNavConfig config) async { | ||
368 | + if (config.currentPage!.preventDuplicates) { | ||
369 | + final originalEntryIndex = | ||
370 | + history.indexWhere((element) => element.location == config.location); | ||
371 | + if (originalEntryIndex >= 0) { | ||
372 | + switch (preventDuplicateHandlingMode) { | ||
373 | + case PreventDuplicateHandlingMode.PopUntilOriginalRoute: | ||
374 | + await backUntil(config.location!, popMode: PopMode.Page); | ||
375 | + break; | ||
376 | + case PreventDuplicateHandlingMode.ReorderRoutes: | ||
377 | + await _unsafeHistoryRemoveAt(originalEntryIndex); | ||
378 | + await _unsafeHistoryAdd(config); | ||
379 | + break; | ||
380 | + case PreventDuplicateHandlingMode.DoNothing: | ||
381 | + default: | ||
382 | + break; | ||
383 | + } | ||
384 | + return; | ||
385 | + } | ||
386 | + } | ||
387 | + await _unsafeHistoryAdd(config); | ||
388 | + } | ||
389 | + | ||
390 | + Future<void> _removeHistoryEntry(GetNavConfig entry) async { | ||
391 | + await _unsafeHistoryRemove(entry); | ||
392 | + } | ||
393 | + | ||
394 | + Future<void> _unsafeHistoryAdd(GetNavConfig config) async { | ||
395 | + final res = await runMiddleware(config); | ||
396 | + if (res == null) return; | ||
397 | + history.add(res); | ||
398 | + } | ||
399 | + | ||
400 | + Future<void> _unsafeHistoryRemove(GetNavConfig config) async { | ||
401 | + var index = history.indexOf(config); | ||
402 | + if (index >= 0) await _unsafeHistoryRemoveAt(index); | ||
403 | + } | ||
404 | + | ||
405 | + Future<GetNavConfig?> _unsafeHistoryRemoveAt(int index) async { | ||
406 | + if (index == history.length - 1 && history.length > 1) { | ||
407 | + //removing WILL update the current route | ||
408 | + final toCheck = history[history.length - 2]; | ||
409 | + final resMiddleware = await runMiddleware(toCheck); | ||
410 | + if (resMiddleware == null) return null; | ||
411 | + history[history.length - 2] = resMiddleware; | ||
412 | + } | ||
413 | + return history.removeAt(index); | ||
414 | + } | ||
455 | } | 415 | } |
456 | 416 | ||
457 | class GetNavigator extends Navigator { | 417 | class GetNavigator extends Navigator { |
458 | - GetNavigator( | ||
459 | - {GlobalKey<NavigatorState>? key, | 418 | + GetNavigator({ |
419 | + GlobalKey<NavigatorState>? key, | ||
460 | bool Function(Route<dynamic>, dynamic)? onPopPage, | 420 | bool Function(Route<dynamic>, dynamic)? onPopPage, |
461 | - required List<GetPage> pages, | 421 | + required List<Page> pages, |
462 | List<NavigatorObserver>? observers, | 422 | List<NavigatorObserver>? observers, |
463 | bool reportsRouteUpdateToEngine = false, | 423 | bool reportsRouteUpdateToEngine = false, |
464 | TransitionDelegate? transitionDelegate, | 424 | TransitionDelegate? transitionDelegate, |
465 | - String? initialRoute}) | ||
466 | - : super( | 425 | + }) : super( |
467 | //keys should be optional | 426 | //keys should be optional |
468 | key: key, | 427 | key: key, |
469 | - initialRoute: initialRoute ?? '/', | ||
470 | onPopPage: onPopPage ?? | 428 | onPopPage: onPopPage ?? |
471 | (route, result) { | 429 | (route, result) { |
472 | final didPop = route.didPop(result); | 430 | final didPop = route.didPop(result); |
@@ -475,17 +433,6 @@ class GetNavigator extends Navigator { | @@ -475,17 +433,6 @@ class GetNavigator extends Navigator { | ||
475 | } | 433 | } |
476 | return true; | 434 | return true; |
477 | }, | 435 | }, |
478 | - onGenerateRoute: (RouteSettings settings) { | ||
479 | - final selectedPageList = | ||
480 | - pages.where((element) => element.name == settings.name); | ||
481 | - if (selectedPageList.isNotEmpty) { | ||
482 | - final selectedPage = selectedPageList.first; | ||
483 | - return GetPageRoute( | ||
484 | - page: selectedPage.page, | ||
485 | - settings: settings, | ||
486 | - ); | ||
487 | - } | ||
488 | - }, | ||
489 | reportsRouteUpdateToEngine: reportsRouteUpdateToEngine, | 436 | reportsRouteUpdateToEngine: reportsRouteUpdateToEngine, |
490 | pages: pages, | 437 | pages: pages, |
491 | observers: [ | 438 | observers: [ |
@@ -496,3 +443,44 @@ class GetNavigator extends Navigator { | @@ -496,3 +443,44 @@ class GetNavigator extends Navigator { | ||
496 | transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(), | 443 | transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(), |
497 | ); | 444 | ); |
498 | } | 445 | } |
446 | + | ||
447 | +/// Enables the user to customize the intended pop behavior | ||
448 | +/// | ||
449 | +/// Goes to either the previous history entry or the previous page entry | ||
450 | +/// | ||
451 | +/// e.g. if the user navigates to these pages | ||
452 | +/// 1) /home | ||
453 | +/// 2) /home/products/1234 | ||
454 | +/// | ||
455 | +/// when popping on [History] mode, it will emulate a browser back button. | ||
456 | +/// | ||
457 | +/// so the new history stack will be: | ||
458 | +/// 1) /home | ||
459 | +/// | ||
460 | +/// when popping on [Page] mode, it will only remove the last part of the route | ||
461 | +/// so the new history stack will be: | ||
462 | +/// 1) /home | ||
463 | +/// 2) /home/products | ||
464 | +/// | ||
465 | +/// another pop will change the history stack to: | ||
466 | +/// 1) /home | ||
467 | +enum PopMode { | ||
468 | + History, | ||
469 | + Page, | ||
470 | +} | ||
471 | + | ||
472 | +/// Enables the user to customize the behavior when pushing multiple routes that | ||
473 | +/// shouldn't be duplicates | ||
474 | +enum PreventDuplicateHandlingMode { | ||
475 | + /// Removes the history entries until it reaches the old route | ||
476 | + PopUntilOriginalRoute, | ||
477 | + | ||
478 | + /// Simply don't push the new route | ||
479 | + DoNothing, | ||
480 | + | ||
481 | + /// Recommended - Moves the old route entry to the front | ||
482 | + /// | ||
483 | + /// With this mode, you guarantee there will be only one | ||
484 | + /// route entry for each location | ||
485 | + ReorderRoutes | ||
486 | +} |
1 | import 'package:flutter/cupertino.dart'; | 1 | import 'package:flutter/cupertino.dart'; |
2 | import 'package:flutter/foundation.dart'; | 2 | import 'package:flutter/foundation.dart'; |
3 | import 'package:flutter/material.dart'; | 3 | import 'package:flutter/material.dart'; |
4 | + | ||
4 | import '../../../get_core/get_core.dart'; | 5 | import '../../../get_core/get_core.dart'; |
5 | import '../../../get_instance/get_instance.dart'; | 6 | import '../../../get_instance/get_instance.dart'; |
6 | import '../../../get_state_manager/get_state_manager.dart'; | 7 | import '../../../get_state_manager/get_state_manager.dart'; |
@@ -9,6 +10,59 @@ import '../../get_navigation.dart'; | @@ -9,6 +10,59 @@ import '../../get_navigation.dart'; | ||
9 | import 'root_controller.dart'; | 10 | import 'root_controller.dart'; |
10 | 11 | ||
11 | class GetCupertinoApp extends StatelessWidget { | 12 | class GetCupertinoApp extends StatelessWidget { |
13 | + final GlobalKey<NavigatorState>? navigatorKey; | ||
14 | + | ||
15 | + final Widget? home; | ||
16 | + final Map<String, WidgetBuilder>? routes; | ||
17 | + final String? initialRoute; | ||
18 | + final RouteFactory? onGenerateRoute; | ||
19 | + final InitialRouteListFactory? onGenerateInitialRoutes; | ||
20 | + final RouteFactory? onUnknownRoute; | ||
21 | + final List<NavigatorObserver>? navigatorObservers; | ||
22 | + final TransitionBuilder? builder; | ||
23 | + final String title; | ||
24 | + final GenerateAppTitle? onGenerateTitle; | ||
25 | + final CustomTransition? customTransition; | ||
26 | + final Color? color; | ||
27 | + final Map<String, Map<String, String>>? translationsKeys; | ||
28 | + final Translations? translations; | ||
29 | + final TextDirection? textDirection; | ||
30 | + final Locale? locale; | ||
31 | + final Locale? fallbackLocale; | ||
32 | + final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates; | ||
33 | + final LocaleListResolutionCallback? localeListResolutionCallback; | ||
34 | + final LocaleResolutionCallback? localeResolutionCallback; | ||
35 | + final Iterable<Locale> supportedLocales; | ||
36 | + final bool showPerformanceOverlay; | ||
37 | + final bool checkerboardRasterCacheImages; | ||
38 | + final bool checkerboardOffscreenLayers; | ||
39 | + final bool showSemanticsDebugger; | ||
40 | + final bool debugShowCheckedModeBanner; | ||
41 | + final Map<LogicalKeySet, Intent>? shortcuts; | ||
42 | + final ThemeData? highContrastTheme; | ||
43 | + final ThemeData? highContrastDarkTheme; | ||
44 | + final Map<Type, Action<Intent>>? actions; | ||
45 | + final Function(Routing?)? routingCallback; | ||
46 | + final Transition? defaultTransition; | ||
47 | + final bool? opaqueRoute; | ||
48 | + final VoidCallback? onInit; | ||
49 | + final VoidCallback? onReady; | ||
50 | + final VoidCallback? onDispose; | ||
51 | + final bool? enableLog; | ||
52 | + final LogWriterCallback? logWriterCallback; | ||
53 | + final bool? popGesture; | ||
54 | + final SmartManagement smartManagement; | ||
55 | + final Bindings? initialBinding; | ||
56 | + final Duration? transitionDuration; | ||
57 | + final bool? defaultGlobalState; | ||
58 | + final List<GetPage>? getPages; | ||
59 | + final GetPage? unknownRoute; | ||
60 | + final RouteInformationProvider? routeInformationProvider; | ||
61 | + final RouteInformationParser<Object>? routeInformationParser; | ||
62 | + final RouterDelegate<Object>? routerDelegate; | ||
63 | + final BackButtonDispatcher? backButtonDispatcher; | ||
64 | + final CupertinoThemeData? theme; | ||
65 | + final bool useInheritedMediaQuery; | ||
12 | const GetCupertinoApp({ | 66 | const GetCupertinoApp({ |
13 | Key? key, | 67 | Key? key, |
14 | this.theme, | 68 | this.theme, |
@@ -46,6 +100,7 @@ class GetCupertinoApp extends StatelessWidget { | @@ -46,6 +100,7 @@ class GetCupertinoApp extends StatelessWidget { | ||
46 | this.shortcuts, | 100 | this.shortcuts, |
47 | this.smartManagement = SmartManagement.full, | 101 | this.smartManagement = SmartManagement.full, |
48 | this.initialBinding, | 102 | this.initialBinding, |
103 | + this.useInheritedMediaQuery = false, | ||
49 | this.unknownRoute, | 104 | this.unknownRoute, |
50 | this.routingCallback, | 105 | this.routingCallback, |
51 | this.defaultTransition, | 106 | this.defaultTransition, |
@@ -66,58 +121,6 @@ class GetCupertinoApp extends StatelessWidget { | @@ -66,58 +121,6 @@ class GetCupertinoApp extends StatelessWidget { | ||
66 | backButtonDispatcher = null, | 121 | backButtonDispatcher = null, |
67 | super(key: key); | 122 | super(key: key); |
68 | 123 | ||
69 | - final GlobalKey<NavigatorState>? navigatorKey; | ||
70 | - final Widget? home; | ||
71 | - final Map<String, WidgetBuilder>? routes; | ||
72 | - final String? initialRoute; | ||
73 | - final RouteFactory? onGenerateRoute; | ||
74 | - final InitialRouteListFactory? onGenerateInitialRoutes; | ||
75 | - final RouteFactory? onUnknownRoute; | ||
76 | - final List<NavigatorObserver>? navigatorObservers; | ||
77 | - final TransitionBuilder? builder; | ||
78 | - final String title; | ||
79 | - final GenerateAppTitle? onGenerateTitle; | ||
80 | - final CustomTransition? customTransition; | ||
81 | - final Color? color; | ||
82 | - final Map<String, Map<String, String>>? translationsKeys; | ||
83 | - final Translations? translations; | ||
84 | - final TextDirection? textDirection; | ||
85 | - final Locale? locale; | ||
86 | - final Locale? fallbackLocale; | ||
87 | - final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates; | ||
88 | - final LocaleListResolutionCallback? localeListResolutionCallback; | ||
89 | - final LocaleResolutionCallback? localeResolutionCallback; | ||
90 | - final Iterable<Locale> supportedLocales; | ||
91 | - final bool showPerformanceOverlay; | ||
92 | - final bool checkerboardRasterCacheImages; | ||
93 | - final bool checkerboardOffscreenLayers; | ||
94 | - final bool showSemanticsDebugger; | ||
95 | - final bool debugShowCheckedModeBanner; | ||
96 | - final Map<LogicalKeySet, Intent>? shortcuts; | ||
97 | - final ThemeData? highContrastTheme; | ||
98 | - final ThemeData? highContrastDarkTheme; | ||
99 | - final Map<Type, Action<Intent>>? actions; | ||
100 | - final Function(Routing?)? routingCallback; | ||
101 | - final Transition? defaultTransition; | ||
102 | - final bool? opaqueRoute; | ||
103 | - final VoidCallback? onInit; | ||
104 | - final VoidCallback? onReady; | ||
105 | - final VoidCallback? onDispose; | ||
106 | - final bool? enableLog; | ||
107 | - final LogWriterCallback? logWriterCallback; | ||
108 | - final bool? popGesture; | ||
109 | - final SmartManagement smartManagement; | ||
110 | - final Bindings? initialBinding; | ||
111 | - final Duration? transitionDuration; | ||
112 | - final bool? defaultGlobalState; | ||
113 | - final List<GetPage>? getPages; | ||
114 | - final GetPage? unknownRoute; | ||
115 | - final RouteInformationProvider? routeInformationProvider; | ||
116 | - final RouteInformationParser<Object>? routeInformationParser; | ||
117 | - final RouterDelegate<Object>? routerDelegate; | ||
118 | - final BackButtonDispatcher? backButtonDispatcher; | ||
119 | - final CupertinoThemeData? theme; | ||
120 | - | ||
121 | GetCupertinoApp.router({ | 124 | GetCupertinoApp.router({ |
122 | Key? key, | 125 | Key? key, |
123 | this.theme, | 126 | this.theme, |
@@ -128,6 +131,7 @@ class GetCupertinoApp extends StatelessWidget { | @@ -128,6 +131,7 @@ class GetCupertinoApp extends StatelessWidget { | ||
128 | this.builder, | 131 | this.builder, |
129 | this.title = '', | 132 | this.title = '', |
130 | this.onGenerateTitle, | 133 | this.onGenerateTitle, |
134 | + this.useInheritedMediaQuery = false, | ||
131 | this.color, | 135 | this.color, |
132 | this.highContrastTheme, | 136 | this.highContrastTheme, |
133 | this.highContrastDarkTheme, | 137 | this.highContrastDarkTheme, |
@@ -183,31 +187,6 @@ class GetCupertinoApp extends StatelessWidget { | @@ -183,31 +187,6 @@ class GetCupertinoApp extends StatelessWidget { | ||
183 | Get.routeInformationParser = routeInformationParser; | 187 | Get.routeInformationParser = routeInformationParser; |
184 | } | 188 | } |
185 | 189 | ||
186 | - Route<dynamic> generator(RouteSettings settings) { | ||
187 | - return PageRedirect(settings: settings, unknownRoute: unknownRoute).page(); | ||
188 | - } | ||
189 | - | ||
190 | - List<Route<dynamic>> initialRoutesGenerate(String name) { | ||
191 | - return [ | ||
192 | - PageRedirect( | ||
193 | - settings: RouteSettings(name: name), | ||
194 | - unknownRoute: unknownRoute, | ||
195 | - ).page() | ||
196 | - ]; | ||
197 | - } | ||
198 | - | ||
199 | - Widget defaultBuilder(BuildContext context, Widget? child) { | ||
200 | - return Directionality( | ||
201 | - textDirection: textDirection ?? | ||
202 | - (rtlLanguages.contains(Get.locale?.languageCode) | ||
203 | - ? TextDirection.rtl | ||
204 | - : TextDirection.ltr), | ||
205 | - child: builder == null | ||
206 | - ? (child ?? Material()) | ||
207 | - : builder!(context, child ?? Material()), | ||
208 | - ); | ||
209 | - } | ||
210 | - | ||
211 | @override | 190 | @override |
212 | Widget build(BuildContext context) => GetBuilder<GetMaterialController>( | 191 | Widget build(BuildContext context) => GetBuilder<GetMaterialController>( |
213 | init: Get.rootController, | 192 | init: Get.rootController, |
@@ -271,6 +250,7 @@ class GetCupertinoApp extends StatelessWidget { | @@ -271,6 +250,7 @@ class GetCupertinoApp extends StatelessWidget { | ||
271 | showSemanticsDebugger: showSemanticsDebugger, | 250 | showSemanticsDebugger: showSemanticsDebugger, |
272 | debugShowCheckedModeBanner: debugShowCheckedModeBanner, | 251 | debugShowCheckedModeBanner: debugShowCheckedModeBanner, |
273 | shortcuts: shortcuts, | 252 | shortcuts: shortcuts, |
253 | + useInheritedMediaQuery: useInheritedMediaQuery, | ||
274 | ) | 254 | ) |
275 | : CupertinoApp( | 255 | : CupertinoApp( |
276 | key: _.unikey, | 256 | key: _.unikey, |
@@ -310,7 +290,33 @@ class GetCupertinoApp extends StatelessWidget { | @@ -310,7 +290,33 @@ class GetCupertinoApp extends StatelessWidget { | ||
310 | showSemanticsDebugger: showSemanticsDebugger, | 290 | showSemanticsDebugger: showSemanticsDebugger, |
311 | debugShowCheckedModeBanner: debugShowCheckedModeBanner, | 291 | debugShowCheckedModeBanner: debugShowCheckedModeBanner, |
312 | shortcuts: shortcuts, | 292 | shortcuts: shortcuts, |
293 | + useInheritedMediaQuery: useInheritedMediaQuery, | ||
313 | // actions: actions, | 294 | // actions: actions, |
314 | ), | 295 | ), |
315 | ); | 296 | ); |
297 | + | ||
298 | + Widget defaultBuilder(BuildContext context, Widget? child) { | ||
299 | + return Directionality( | ||
300 | + textDirection: textDirection ?? | ||
301 | + (rtlLanguages.contains(Get.locale?.languageCode) | ||
302 | + ? TextDirection.rtl | ||
303 | + : TextDirection.ltr), | ||
304 | + child: builder == null | ||
305 | + ? (child ?? Material()) | ||
306 | + : builder!(context, child ?? Material()), | ||
307 | + ); | ||
308 | + } | ||
309 | + | ||
310 | + Route<dynamic> generator(RouteSettings settings) { | ||
311 | + return PageRedirect(settings: settings, unknownRoute: unknownRoute).page(); | ||
312 | + } | ||
313 | + | ||
314 | + List<Route<dynamic>> initialRoutesGenerate(String name) { | ||
315 | + return [ | ||
316 | + PageRedirect( | ||
317 | + settings: RouteSettings(name: name), | ||
318 | + unknownRoute: unknownRoute, | ||
319 | + ).page() | ||
320 | + ]; | ||
321 | + } | ||
316 | } | 322 | } |
@@ -10,6 +10,64 @@ import '../../get_navigation.dart'; | @@ -10,6 +10,64 @@ import '../../get_navigation.dart'; | ||
10 | import 'root_controller.dart'; | 10 | import 'root_controller.dart'; |
11 | 11 | ||
12 | class GetMaterialApp extends StatelessWidget { | 12 | class GetMaterialApp extends StatelessWidget { |
13 | + final GlobalKey<NavigatorState>? navigatorKey; | ||
14 | + | ||
15 | + final GlobalKey<ScaffoldMessengerState>? scaffoldMessengerKey; | ||
16 | + final Widget? home; | ||
17 | + final Map<String, WidgetBuilder>? routes; | ||
18 | + final String? initialRoute; | ||
19 | + final RouteFactory? onGenerateRoute; | ||
20 | + final InitialRouteListFactory? onGenerateInitialRoutes; | ||
21 | + final RouteFactory? onUnknownRoute; | ||
22 | + final List<NavigatorObserver>? navigatorObservers; | ||
23 | + final TransitionBuilder? builder; | ||
24 | + final String title; | ||
25 | + final GenerateAppTitle? onGenerateTitle; | ||
26 | + final ThemeData? theme; | ||
27 | + final ThemeData? darkTheme; | ||
28 | + final ThemeMode themeMode; | ||
29 | + final CustomTransition? customTransition; | ||
30 | + final Color? color; | ||
31 | + final Map<String, Map<String, String>>? translationsKeys; | ||
32 | + final Translations? translations; | ||
33 | + final TextDirection? textDirection; | ||
34 | + final Locale? locale; | ||
35 | + final Locale? fallbackLocale; | ||
36 | + final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates; | ||
37 | + final LocaleListResolutionCallback? localeListResolutionCallback; | ||
38 | + final LocaleResolutionCallback? localeResolutionCallback; | ||
39 | + final Iterable<Locale> supportedLocales; | ||
40 | + final bool showPerformanceOverlay; | ||
41 | + final bool checkerboardRasterCacheImages; | ||
42 | + final bool checkerboardOffscreenLayers; | ||
43 | + final bool showSemanticsDebugger; | ||
44 | + final bool debugShowCheckedModeBanner; | ||
45 | + final Map<LogicalKeySet, Intent>? shortcuts; | ||
46 | + final ScrollBehavior? scrollBehavior; | ||
47 | + final ThemeData? highContrastTheme; | ||
48 | + final ThemeData? highContrastDarkTheme; | ||
49 | + final Map<Type, Action<Intent>>? actions; | ||
50 | + final bool debugShowMaterialGrid; | ||
51 | + final ValueChanged<Routing?>? routingCallback; | ||
52 | + final Transition? defaultTransition; | ||
53 | + final bool? opaqueRoute; | ||
54 | + final VoidCallback? onInit; | ||
55 | + final VoidCallback? onReady; | ||
56 | + final VoidCallback? onDispose; | ||
57 | + final bool? enableLog; | ||
58 | + final LogWriterCallback? logWriterCallback; | ||
59 | + final bool? popGesture; | ||
60 | + final SmartManagement smartManagement; | ||
61 | + final Bindings? initialBinding; | ||
62 | + final Duration? transitionDuration; | ||
63 | + final bool? defaultGlobalState; | ||
64 | + final List<GetPage>? getPages; | ||
65 | + final GetPage? unknownRoute; | ||
66 | + final RouteInformationProvider? routeInformationProvider; | ||
67 | + final RouteInformationParser<Object>? routeInformationParser; | ||
68 | + final RouterDelegate<Object>? routerDelegate; | ||
69 | + final BackButtonDispatcher? backButtonDispatcher; | ||
70 | + final bool useInheritedMediaQuery; | ||
13 | const GetMaterialApp({ | 71 | const GetMaterialApp({ |
14 | Key? key, | 72 | Key? key, |
15 | this.navigatorKey, | 73 | this.navigatorKey, |
@@ -21,6 +79,7 @@ class GetMaterialApp extends StatelessWidget { | @@ -21,6 +79,7 @@ class GetMaterialApp extends StatelessWidget { | ||
21 | this.onGenerateRoute, | 79 | this.onGenerateRoute, |
22 | this.onGenerateInitialRoutes, | 80 | this.onGenerateInitialRoutes, |
23 | this.onUnknownRoute, | 81 | this.onUnknownRoute, |
82 | + this.useInheritedMediaQuery = false, | ||
24 | List<NavigatorObserver> this.navigatorObservers = | 83 | List<NavigatorObserver> this.navigatorObservers = |
25 | const <NavigatorObserver>[], | 84 | const <NavigatorObserver>[], |
26 | this.builder, | 85 | this.builder, |
@@ -72,63 +131,6 @@ class GetMaterialApp extends StatelessWidget { | @@ -72,63 +131,6 @@ class GetMaterialApp extends StatelessWidget { | ||
72 | backButtonDispatcher = null, | 131 | backButtonDispatcher = null, |
73 | super(key: key); | 132 | super(key: key); |
74 | 133 | ||
75 | - final GlobalKey<NavigatorState>? navigatorKey; | ||
76 | - final GlobalKey<ScaffoldMessengerState>? scaffoldMessengerKey; | ||
77 | - final Widget? home; | ||
78 | - final Map<String, WidgetBuilder>? routes; | ||
79 | - final String? initialRoute; | ||
80 | - final RouteFactory? onGenerateRoute; | ||
81 | - final InitialRouteListFactory? onGenerateInitialRoutes; | ||
82 | - final RouteFactory? onUnknownRoute; | ||
83 | - final List<NavigatorObserver>? navigatorObservers; | ||
84 | - final TransitionBuilder? builder; | ||
85 | - final String title; | ||
86 | - final GenerateAppTitle? onGenerateTitle; | ||
87 | - final ThemeData? theme; | ||
88 | - final ThemeData? darkTheme; | ||
89 | - final ThemeMode themeMode; | ||
90 | - final CustomTransition? customTransition; | ||
91 | - final Color? color; | ||
92 | - final Map<String, Map<String, String>>? translationsKeys; | ||
93 | - final Translations? translations; | ||
94 | - final TextDirection? textDirection; | ||
95 | - final Locale? locale; | ||
96 | - final Locale? fallbackLocale; | ||
97 | - final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates; | ||
98 | - final LocaleListResolutionCallback? localeListResolutionCallback; | ||
99 | - final LocaleResolutionCallback? localeResolutionCallback; | ||
100 | - final Iterable<Locale> supportedLocales; | ||
101 | - final bool showPerformanceOverlay; | ||
102 | - final bool checkerboardRasterCacheImages; | ||
103 | - final bool checkerboardOffscreenLayers; | ||
104 | - final bool showSemanticsDebugger; | ||
105 | - final bool debugShowCheckedModeBanner; | ||
106 | - final Map<LogicalKeySet, Intent>? shortcuts; | ||
107 | - final ScrollBehavior? scrollBehavior; | ||
108 | - final ThemeData? highContrastTheme; | ||
109 | - final ThemeData? highContrastDarkTheme; | ||
110 | - final Map<Type, Action<Intent>>? actions; | ||
111 | - final bool debugShowMaterialGrid; | ||
112 | - final ValueChanged<Routing?>? routingCallback; | ||
113 | - final Transition? defaultTransition; | ||
114 | - final bool? opaqueRoute; | ||
115 | - final VoidCallback? onInit; | ||
116 | - final VoidCallback? onReady; | ||
117 | - final VoidCallback? onDispose; | ||
118 | - final bool? enableLog; | ||
119 | - final LogWriterCallback? logWriterCallback; | ||
120 | - final bool? popGesture; | ||
121 | - final SmartManagement smartManagement; | ||
122 | - final Bindings? initialBinding; | ||
123 | - final Duration? transitionDuration; | ||
124 | - final bool? defaultGlobalState; | ||
125 | - final List<GetPage>? getPages; | ||
126 | - final GetPage? unknownRoute; | ||
127 | - final RouteInformationProvider? routeInformationProvider; | ||
128 | - final RouteInformationParser<Object>? routeInformationParser; | ||
129 | - final RouterDelegate<Object>? routerDelegate; | ||
130 | - final BackButtonDispatcher? backButtonDispatcher; | ||
131 | - | ||
132 | GetMaterialApp.router({ | 134 | GetMaterialApp.router({ |
133 | Key? key, | 135 | Key? key, |
134 | this.routeInformationProvider, | 136 | this.routeInformationProvider, |
@@ -142,6 +144,7 @@ class GetMaterialApp extends StatelessWidget { | @@ -142,6 +144,7 @@ class GetMaterialApp extends StatelessWidget { | ||
142 | this.color, | 144 | this.color, |
143 | this.theme, | 145 | this.theme, |
144 | this.darkTheme, | 146 | this.darkTheme, |
147 | + this.useInheritedMediaQuery = false, | ||
145 | this.highContrastTheme, | 148 | this.highContrastTheme, |
146 | this.highContrastDarkTheme, | 149 | this.highContrastDarkTheme, |
147 | this.themeMode = ThemeMode.system, | 150 | this.themeMode = ThemeMode.system, |
@@ -200,31 +203,6 @@ class GetMaterialApp extends StatelessWidget { | @@ -200,31 +203,6 @@ class GetMaterialApp extends StatelessWidget { | ||
200 | Get.routeInformationParser = routeInformationParser; | 203 | Get.routeInformationParser = routeInformationParser; |
201 | } | 204 | } |
202 | 205 | ||
203 | - Route<dynamic> generator(RouteSettings settings) { | ||
204 | - return PageRedirect(settings: settings, unknownRoute: unknownRoute).page(); | ||
205 | - } | ||
206 | - | ||
207 | - List<Route<dynamic>> initialRoutesGenerate(String name) { | ||
208 | - return [ | ||
209 | - PageRedirect( | ||
210 | - settings: RouteSettings(name: name), | ||
211 | - unknownRoute: unknownRoute, | ||
212 | - ).page() | ||
213 | - ]; | ||
214 | - } | ||
215 | - | ||
216 | - Widget defaultBuilder(BuildContext context, Widget? child) { | ||
217 | - return Directionality( | ||
218 | - textDirection: textDirection ?? | ||
219 | - (rtlLanguages.contains(Get.locale?.languageCode) | ||
220 | - ? TextDirection.rtl | ||
221 | - : TextDirection.ltr), | ||
222 | - child: builder == null | ||
223 | - ? (child ?? Material()) | ||
224 | - : builder!(context, child ?? Material()), | ||
225 | - ); | ||
226 | - } | ||
227 | - | ||
228 | @override | 206 | @override |
229 | Widget build(BuildContext context) => GetBuilder<GetMaterialController>( | 207 | Widget build(BuildContext context) => GetBuilder<GetMaterialController>( |
230 | init: Get.rootController, | 208 | init: Get.rootController, |
@@ -270,7 +248,6 @@ class GetMaterialApp extends StatelessWidget { | @@ -270,7 +248,6 @@ class GetMaterialApp extends StatelessWidget { | ||
270 | ? MaterialApp.router( | 248 | ? MaterialApp.router( |
271 | routerDelegate: routerDelegate!, | 249 | routerDelegate: routerDelegate!, |
272 | routeInformationParser: routeInformationParser!, | 250 | routeInformationParser: routeInformationParser!, |
273 | - scaffoldMessengerKey: scaffoldMessengerKey, | ||
274 | backButtonDispatcher: backButtonDispatcher, | 251 | backButtonDispatcher: backButtonDispatcher, |
275 | routeInformationProvider: routeInformationProvider, | 252 | routeInformationProvider: routeInformationProvider, |
276 | key: _.unikey, | 253 | key: _.unikey, |
@@ -283,6 +260,8 @@ class GetMaterialApp extends StatelessWidget { | @@ -283,6 +260,8 @@ class GetMaterialApp extends StatelessWidget { | ||
283 | _.darkTheme ?? darkTheme ?? theme ?? ThemeData.fallback(), | 260 | _.darkTheme ?? darkTheme ?? theme ?? ThemeData.fallback(), |
284 | themeMode: _.themeMode ?? themeMode, | 261 | themeMode: _.themeMode ?? themeMode, |
285 | locale: Get.locale ?? locale, | 262 | locale: Get.locale ?? locale, |
263 | + scaffoldMessengerKey: | ||
264 | + scaffoldMessengerKey ?? _.scaffoldMessengerKey, | ||
286 | localizationsDelegates: localizationsDelegates, | 265 | localizationsDelegates: localizationsDelegates, |
287 | localeListResolutionCallback: localeListResolutionCallback, | 266 | localeListResolutionCallback: localeListResolutionCallback, |
288 | localeResolutionCallback: localeResolutionCallback, | 267 | localeResolutionCallback: localeResolutionCallback, |
@@ -295,13 +274,15 @@ class GetMaterialApp extends StatelessWidget { | @@ -295,13 +274,15 @@ class GetMaterialApp extends StatelessWidget { | ||
295 | debugShowCheckedModeBanner: debugShowCheckedModeBanner, | 274 | debugShowCheckedModeBanner: debugShowCheckedModeBanner, |
296 | shortcuts: shortcuts, | 275 | shortcuts: shortcuts, |
297 | scrollBehavior: scrollBehavior, | 276 | scrollBehavior: scrollBehavior, |
277 | + useInheritedMediaQuery: useInheritedMediaQuery, | ||
298 | ) | 278 | ) |
299 | : MaterialApp( | 279 | : MaterialApp( |
300 | key: _.unikey, | 280 | key: _.unikey, |
301 | navigatorKey: (navigatorKey == null | 281 | navigatorKey: (navigatorKey == null |
302 | ? Get.key | 282 | ? Get.key |
303 | : Get.addKey(navigatorKey!)), | 283 | : Get.addKey(navigatorKey!)), |
304 | - scaffoldMessengerKey: scaffoldMessengerKey, | 284 | + scaffoldMessengerKey: |
285 | + scaffoldMessengerKey ?? _.scaffoldMessengerKey, | ||
305 | home: home, | 286 | home: home, |
306 | routes: routes ?? const <String, WidgetBuilder>{}, | 287 | routes: routes ?? const <String, WidgetBuilder>{}, |
307 | initialRoute: initialRoute, | 288 | initialRoute: initialRoute, |
@@ -340,7 +321,33 @@ class GetMaterialApp extends StatelessWidget { | @@ -340,7 +321,33 @@ class GetMaterialApp extends StatelessWidget { | ||
340 | debugShowCheckedModeBanner: debugShowCheckedModeBanner, | 321 | debugShowCheckedModeBanner: debugShowCheckedModeBanner, |
341 | shortcuts: shortcuts, | 322 | shortcuts: shortcuts, |
342 | scrollBehavior: scrollBehavior, | 323 | scrollBehavior: scrollBehavior, |
324 | + useInheritedMediaQuery: useInheritedMediaQuery, | ||
343 | // actions: actions, | 325 | // actions: actions, |
344 | ), | 326 | ), |
345 | ); | 327 | ); |
328 | + | ||
329 | + Widget defaultBuilder(BuildContext context, Widget? child) { | ||
330 | + return Directionality( | ||
331 | + textDirection: textDirection ?? | ||
332 | + (rtlLanguages.contains(Get.locale?.languageCode) | ||
333 | + ? TextDirection.rtl | ||
334 | + : TextDirection.ltr), | ||
335 | + child: builder == null | ||
336 | + ? (child ?? Material()) | ||
337 | + : builder!(context, child ?? Material()), | ||
338 | + ); | ||
339 | + } | ||
340 | + | ||
341 | + Route<dynamic> generator(RouteSettings settings) { | ||
342 | + return PageRedirect(settings: settings, unknownRoute: unknownRoute).page(); | ||
343 | + } | ||
344 | + | ||
345 | + List<Route<dynamic>> initialRoutesGenerate(String name) { | ||
346 | + return [ | ||
347 | + PageRedirect( | ||
348 | + settings: RouteSettings(name: name), | ||
349 | + unknownRoute: unknownRoute, | ||
350 | + ).page() | ||
351 | + ]; | ||
352 | + } | ||
346 | } | 353 | } |
1 | import 'package:flutter/material.dart'; | 1 | import 'package:flutter/material.dart'; |
2 | + | ||
3 | +import '../../../get.dart'; | ||
2 | import '../../../get_state_manager/get_state_manager.dart'; | 4 | import '../../../get_state_manager/get_state_manager.dart'; |
3 | import '../../../get_utils/get_utils.dart'; | 5 | import '../../../get_utils/get_utils.dart'; |
4 | import '../routes/custom_transition.dart'; | 6 | import '../routes/custom_transition.dart'; |
5 | import '../routes/observers/route_observer.dart'; | 7 | import '../routes/observers/route_observer.dart'; |
6 | import '../routes/transitions_type.dart'; | 8 | import '../routes/transitions_type.dart'; |
7 | 9 | ||
8 | -class GetMaterialController extends GetxController { | 10 | +class GetMaterialController extends SuperController { |
9 | bool testMode = false; | 11 | bool testMode = false; |
10 | Key? unikey; | 12 | Key? unikey; |
11 | ThemeData? theme; | 13 | ThemeData? theme; |
12 | ThemeData? darkTheme; | 14 | ThemeData? darkTheme; |
13 | ThemeMode? themeMode; | 15 | ThemeMode? themeMode; |
14 | 16 | ||
17 | + final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>(); | ||
18 | + | ||
15 | bool defaultPopGesture = GetPlatform.isIOS; | 19 | bool defaultPopGesture = GetPlatform.isIOS; |
16 | bool defaultOpaqueRoute = true; | 20 | bool defaultOpaqueRoute = true; |
17 | 21 | ||
@@ -31,6 +35,8 @@ class GetMaterialController extends GetxController { | @@ -31,6 +35,8 @@ class GetMaterialController extends GetxController { | ||
31 | 35 | ||
32 | var _key = GlobalKey<NavigatorState>(debugLabel: 'Key Created by default'); | 36 | var _key = GlobalKey<NavigatorState>(debugLabel: 'Key Created by default'); |
33 | 37 | ||
38 | + Map<dynamic, GlobalKey<NavigatorState>> keys = {}; | ||
39 | + | ||
34 | GlobalKey<NavigatorState> get key => _key; | 40 | GlobalKey<NavigatorState> get key => _key; |
35 | 41 | ||
36 | GlobalKey<NavigatorState>? addKey(GlobalKey<NavigatorState> newKey) { | 42 | GlobalKey<NavigatorState>? addKey(GlobalKey<NavigatorState> newKey) { |
@@ -38,7 +44,32 @@ class GetMaterialController extends GetxController { | @@ -38,7 +44,32 @@ class GetMaterialController extends GetxController { | ||
38 | return key; | 44 | return key; |
39 | } | 45 | } |
40 | 46 | ||
41 | - Map<dynamic, GlobalKey<NavigatorState>> keys = {}; | 47 | + @override |
48 | + void didChangeLocales(List<Locale>? locales) { | ||
49 | + Get.asap(() { | ||
50 | + final locale = Get.deviceLocale; | ||
51 | + if (locale != null) { | ||
52 | + Get.updateLocale(locale); | ||
53 | + } | ||
54 | + }); | ||
55 | + } | ||
56 | + | ||
57 | + @override | ||
58 | + void onDetached() {} | ||
59 | + | ||
60 | + @override | ||
61 | + void onInactive() {} | ||
62 | + | ||
63 | + @override | ||
64 | + void onPaused() {} | ||
65 | + | ||
66 | + @override | ||
67 | + void onResumed() {} | ||
68 | + | ||
69 | + void restartApp() { | ||
70 | + unikey = UniqueKey(); | ||
71 | + update(); | ||
72 | + } | ||
42 | 73 | ||
43 | void setTheme(ThemeData value) { | 74 | void setTheme(ThemeData value) { |
44 | if (darkTheme == null) { | 75 | if (darkTheme == null) { |
@@ -57,9 +88,4 @@ class GetMaterialController extends GetxController { | @@ -57,9 +88,4 @@ class GetMaterialController extends GetxController { | ||
57 | themeMode = value; | 88 | themeMode = value; |
58 | update(); | 89 | update(); |
59 | } | 90 | } |
60 | - | ||
61 | - void restartApp() { | ||
62 | - unikey = UniqueKey(); | ||
63 | - update(); | ||
64 | - } | ||
65 | } | 91 | } |
@@ -9,24 +9,6 @@ import '../../get_navigation.dart'; | @@ -9,24 +9,6 @@ import '../../get_navigation.dart'; | ||
9 | import 'custom_transition.dart'; | 9 | import 'custom_transition.dart'; |
10 | import 'transitions_type.dart'; | 10 | import 'transitions_type.dart'; |
11 | 11 | ||
12 | -@immutable | ||
13 | -class PathDecoded { | ||
14 | - const PathDecoded(this.regex, this.keys); | ||
15 | - final RegExp regex; | ||
16 | - final List<String?> keys; | ||
17 | - | ||
18 | - @override | ||
19 | - bool operator ==(Object other) { | ||
20 | - if (identical(this, other)) return true; | ||
21 | - | ||
22 | - return other is PathDecoded && | ||
23 | - other.regex == regex; // && listEquals(other.keys, keys); | ||
24 | - } | ||
25 | - | ||
26 | - @override | ||
27 | - int get hashCode => regex.hashCode; | ||
28 | -} | ||
29 | - | ||
30 | class GetPage<T> extends Page<T> { | 12 | class GetPage<T> extends Page<T> { |
31 | final GetPageBuilder page; | 13 | final GetPageBuilder page; |
32 | final bool? popGesture; | 14 | final bool? popGesture; |
@@ -98,27 +80,6 @@ class GetPage<T> extends Page<T> { | @@ -98,27 +80,6 @@ class GetPage<T> extends Page<T> { | ||
98 | ); | 80 | ); |
99 | // settings = RouteSettings(name: name, arguments: Get.arguments); | 81 | // settings = RouteSettings(name: name, arguments: Get.arguments); |
100 | 82 | ||
101 | - static PathDecoded _nameToRegex(String path) { | ||
102 | - var keys = <String?>[]; | ||
103 | - | ||
104 | - String _replace(Match pattern) { | ||
105 | - var buffer = StringBuffer('(?:'); | ||
106 | - | ||
107 | - if (pattern[1] != null) buffer.write('\.'); | ||
108 | - buffer.write('([\\w%+-._~!\$&\'()*,;=:@]+))'); | ||
109 | - if (pattern[3] != null) buffer.write('?'); | ||
110 | - | ||
111 | - keys.add(pattern[2]); | ||
112 | - return "$buffer"; | ||
113 | - } | ||
114 | - | ||
115 | - var stringPath = '$path/?' | ||
116 | - .replaceAllMapped(RegExp(r'(\.)?:(\w+)(\?)?'), _replace) | ||
117 | - .replaceAll('//', '/'); | ||
118 | - | ||
119 | - return PathDecoded(RegExp('^$stringPath\$'), keys); | ||
120 | - } | ||
121 | - | ||
122 | GetPage<T> copy({ | 83 | GetPage<T> copy({ |
123 | String? name, | 84 | String? name, |
124 | GetPageBuilder? page, | 85 | GetPageBuilder? page, |
@@ -174,8 +135,6 @@ class GetPage<T> extends Page<T> { | @@ -174,8 +135,6 @@ class GetPage<T> extends Page<T> { | ||
174 | ); | 135 | ); |
175 | } | 136 | } |
176 | 137 | ||
177 | - late Future<T?> popped; | ||
178 | - | ||
179 | @override | 138 | @override |
180 | Route<T> createRoute(BuildContext context) { | 139 | Route<T> createRoute(BuildContext context) { |
181 | // return GetPageRoute<T>(settings: this, page: page); | 140 | // return GetPageRoute<T>(settings: this, page: page); |
@@ -185,7 +144,45 @@ class GetPage<T> extends Page<T> { | @@ -185,7 +144,45 @@ class GetPage<T> extends Page<T> { | ||
185 | unknownRoute: unknownRoute, | 144 | unknownRoute: unknownRoute, |
186 | ).getPageToRoute<T>(this, unknownRoute); | 145 | ).getPageToRoute<T>(this, unknownRoute); |
187 | 146 | ||
188 | - popped = _page.popped; | ||
189 | return _page; | 147 | return _page; |
190 | } | 148 | } |
149 | + | ||
150 | + static PathDecoded _nameToRegex(String path) { | ||
151 | + var keys = <String?>[]; | ||
152 | + | ||
153 | + String _replace(Match pattern) { | ||
154 | + var buffer = StringBuffer('(?:'); | ||
155 | + | ||
156 | + if (pattern[1] != null) buffer.write('\.'); | ||
157 | + buffer.write('([\\w%+-._~!\$&\'()*,;=:@]+))'); | ||
158 | + if (pattern[3] != null) buffer.write('?'); | ||
159 | + | ||
160 | + keys.add(pattern[2]); | ||
161 | + return "$buffer"; | ||
162 | + } | ||
163 | + | ||
164 | + var stringPath = '$path/?' | ||
165 | + .replaceAllMapped(RegExp(r'(\.)?:(\w+)(\?)?'), _replace) | ||
166 | + .replaceAll('//', '/'); | ||
167 | + | ||
168 | + return PathDecoded(RegExp('^$stringPath\$'), keys); | ||
169 | + } | ||
170 | +} | ||
171 | + | ||
172 | +@immutable | ||
173 | +class PathDecoded { | ||
174 | + final RegExp regex; | ||
175 | + final List<String?> keys; | ||
176 | + const PathDecoded(this.regex, this.keys); | ||
177 | + | ||
178 | + @override | ||
179 | + int get hashCode => regex.hashCode; | ||
180 | + | ||
181 | + @override | ||
182 | + bool operator ==(Object other) { | ||
183 | + if (identical(this, other)) return true; | ||
184 | + | ||
185 | + return other is PathDecoded && | ||
186 | + other.regex == regex; // && listEquals(other.keys, keys); | ||
187 | + } | ||
191 | } | 188 | } |
@@ -5,111 +5,247 @@ import 'package:flutter/cupertino.dart'; | @@ -5,111 +5,247 @@ import 'package:flutter/cupertino.dart'; | ||
5 | import 'package:flutter/foundation.dart'; | 5 | import 'package:flutter/foundation.dart'; |
6 | import 'package:flutter/gestures.dart'; | 6 | import 'package:flutter/gestures.dart'; |
7 | import 'package:flutter/material.dart'; | 7 | import 'package:flutter/material.dart'; |
8 | -import '../../../get.dart'; | ||
9 | 8 | ||
9 | +import '../../../get.dart'; | ||
10 | import 'default_transitions.dart'; | 10 | import 'default_transitions.dart'; |
11 | import 'transitions_type.dart'; | 11 | import 'transitions_type.dart'; |
12 | 12 | ||
13 | const double _kBackGestureWidth = 20.0; | 13 | const double _kBackGestureWidth = 20.0; |
14 | -const double _kMinFlingVelocity = 1.0; // Screen widths per second. | 14 | +const int _kMaxDroppedSwipePageForwardAnimationTime = |
15 | + 800; // Screen widths per second. | ||
15 | 16 | ||
16 | // An eyeballed value for the maximum time it takes | 17 | // An eyeballed value for the maximum time it takes |
17 | //for a page to animate forward | 18 | //for a page to animate forward |
18 | // if the user releases a page mid swipe. | 19 | // if the user releases a page mid swipe. |
19 | -const int _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds. | 20 | +const int _kMaxPageBackAnimationTime = 300; // Milliseconds. |
20 | 21 | ||
21 | // The maximum time for a page to get reset to it's original position if the | 22 | // The maximum time for a page to get reset to it's original position if the |
22 | // user releases a page mid swipe. | 23 | // user releases a page mid swipe. |
23 | -const int _kMaxPageBackAnimationTime = 300; // Milliseconds. | 24 | +const double _kMinFlingVelocity = 1.0; // Milliseconds. |
24 | 25 | ||
25 | -mixin GetPageRouteTransitionMixin<T> on PageRoute<T> { | ||
26 | - /// Builds the primary contents of the route. | ||
27 | - @protected | ||
28 | - Widget buildContent(BuildContext context); | 26 | +class CupertinoBackGestureController<T> { |
27 | + final AnimationController controller; | ||
29 | 28 | ||
30 | - /// {@template flutter.cupertino.CupertinoRouteTransitionMixin.title} | ||
31 | - /// A title string for this route. | 29 | + final NavigatorState navigator; |
30 | + | ||
31 | + /// Creates a controller for an iOS-style back gesture. | ||
32 | /// | 32 | /// |
33 | - /// Used to auto-populate [CupertinoNavigationBar] and | ||
34 | - /// [CupertinoSliverNavigationBar]'s `middle`/`largeTitle` widgets when | ||
35 | - /// one is not manually supplied. | ||
36 | - /// {@endtemplate} | ||
37 | - String? get title; | 33 | + /// The [navigator] and [controller] arguments must not be null. |
34 | + CupertinoBackGestureController({ | ||
35 | + required this.navigator, | ||
36 | + required this.controller, | ||
37 | + }) { | ||
38 | + navigator.didStartUserGesture(); | ||
39 | + } | ||
38 | 40 | ||
39 | - double Function(BuildContext context)? get gestureWidth; | 41 | + /// The drag gesture has ended with a horizontal motion of |
42 | + /// [fractionalVelocity] as a fraction of screen width per second. | ||
43 | + void dragEnd(double velocity) { | ||
44 | + // Fling in the appropriate direction. | ||
45 | + // AnimationController.fling is guaranteed to | ||
46 | + // take at least one frame. | ||
47 | + // | ||
48 | + // This curve has been determined through rigorously eyeballing native iOS | ||
49 | + // animations. | ||
50 | + const Curve animationCurve = Curves.fastLinearToSlowEaseIn; | ||
51 | + final bool animateForward; | ||
40 | 52 | ||
41 | - ValueNotifier<String?>? _previousTitle; | 53 | + // If the user releases the page before mid screen with sufficient velocity, |
54 | + // or after mid screen, we should animate the page out. Otherwise, the page | ||
55 | + // should be animated back in. | ||
56 | + if (velocity.abs() >= _kMinFlingVelocity) { | ||
57 | + animateForward = velocity <= 0; | ||
58 | + } else { | ||
59 | + animateForward = controller.value > 0.5; | ||
60 | + } | ||
42 | 61 | ||
43 | - /// The title string of the previous [CupertinoPageRoute]. | ||
44 | - /// | ||
45 | - /// The [ValueListenable]'s value is readable after the route is installed | ||
46 | - /// onto a [Navigator]. The [ValueListenable] will also notify its listeners | ||
47 | - /// if the value changes (such as by replacing the previous route). | ||
48 | - /// | ||
49 | - /// The [ValueListenable] itself will be null before the route is installed. | ||
50 | - /// Its content value will be null if the previous route has no title or | ||
51 | - /// is not a [CupertinoPageRoute]. | ||
52 | - /// | ||
53 | - /// See also: | ||
54 | - /// | ||
55 | - /// * [ValueListenableBuilder], which can be used to listen and rebuild | ||
56 | - /// widgets based on a ValueListenable. | ||
57 | - ValueListenable<String?> get previousTitle { | ||
58 | - assert( | ||
59 | - _previousTitle != null, | ||
60 | - ''' | ||
61 | -Cannot read the previousTitle for a route that has not yet been installed''', | 62 | + if (animateForward) { |
63 | + // The closer the panel is to dismissing, the shorter the animation is. | ||
64 | + // We want to cap the animation time, but we want to use a linear curve | ||
65 | + // to determine it. | ||
66 | + final droppedPageForwardAnimationTime = min( | ||
67 | + lerpDouble( | ||
68 | + _kMaxDroppedSwipePageForwardAnimationTime, 0, controller.value)! | ||
69 | + .floor(), | ||
70 | + _kMaxPageBackAnimationTime, | ||
62 | ); | 71 | ); |
63 | - return _previousTitle!; | 72 | + controller.animateTo(1.0, |
73 | + duration: Duration(milliseconds: droppedPageForwardAnimationTime), | ||
74 | + curve: animationCurve); | ||
75 | + } else { | ||
76 | + // This route is destined to pop at this point. Reuse navigator's pop. | ||
77 | + navigator.pop(); | ||
78 | + | ||
79 | + // The popping may have finished inline if already at the | ||
80 | + // target destination. | ||
81 | + if (controller.isAnimating) { | ||
82 | + // Otherwise, use a custom popping animation duration and curve. | ||
83 | + final droppedPageBackAnimationTime = lerpDouble( | ||
84 | + 0, _kMaxDroppedSwipePageForwardAnimationTime, controller.value)! | ||
85 | + .floor(); | ||
86 | + controller.animateBack(0.0, | ||
87 | + duration: Duration(milliseconds: droppedPageBackAnimationTime), | ||
88 | + curve: animationCurve); | ||
89 | + } | ||
64 | } | 90 | } |
65 | 91 | ||
66 | - @override | ||
67 | - void didChangePrevious(Route<dynamic>? previousRoute) { | ||
68 | - final previousTitleString = previousRoute is CupertinoRouteTransitionMixin | ||
69 | - ? previousRoute.title | ||
70 | - : null; | ||
71 | - if (_previousTitle == null) { | ||
72 | - _previousTitle = ValueNotifier<String?>(previousTitleString); | 92 | + if (controller.isAnimating) { |
93 | + // Keep the userGestureInProgress in true state so we don't change the | ||
94 | + // curve of the page transition mid-flight since CupertinoPageTransition | ||
95 | + // depends on userGestureInProgress. | ||
96 | + late AnimationStatusListener animationStatusCallback; | ||
97 | + animationStatusCallback = (status) { | ||
98 | + navigator.didStopUserGesture(); | ||
99 | + controller.removeStatusListener(animationStatusCallback); | ||
100 | + }; | ||
101 | + controller.addStatusListener(animationStatusCallback); | ||
73 | } else { | 102 | } else { |
74 | - _previousTitle!.value = previousTitleString; | 103 | + navigator.didStopUserGesture(); |
75 | } | 104 | } |
76 | - super.didChangePrevious(previousRoute); | ||
77 | } | 105 | } |
78 | 106 | ||
79 | - @override | ||
80 | - // A relatively rigorous eyeball estimation. | ||
81 | - Duration get transitionDuration => const Duration(milliseconds: 400); | 107 | + /// The drag gesture has changed by [fractionalDelta]. The total range of the |
108 | + /// drag should be 0.0 to 1.0. | ||
109 | + void dragUpdate(double delta) { | ||
110 | + controller.value -= delta; | ||
111 | + } | ||
112 | +} | ||
113 | + | ||
114 | +class CupertinoBackGestureDetector<T> extends StatefulWidget { | ||
115 | + final Widget child; | ||
116 | + | ||
117 | + final double gestureWidth; | ||
118 | + final ValueGetter<bool> enabledCallback; | ||
119 | + | ||
120 | + final ValueGetter<CupertinoBackGestureController<T>> onStartPopGesture; | ||
121 | + | ||
122 | + const CupertinoBackGestureDetector({ | ||
123 | + Key? key, | ||
124 | + required this.enabledCallback, | ||
125 | + required this.onStartPopGesture, | ||
126 | + required this.child, | ||
127 | + required this.gestureWidth, | ||
128 | + }) : super(key: key); | ||
82 | 129 | ||
83 | @override | 130 | @override |
84 | - Color? get barrierColor => null; | 131 | + CupertinoBackGestureDetectorState<T> createState() => |
132 | + CupertinoBackGestureDetectorState<T>(); | ||
133 | +} | ||
134 | + | ||
135 | +class CupertinoBackGestureDetectorState<T> | ||
136 | + extends State<CupertinoBackGestureDetector<T>> { | ||
137 | + CupertinoBackGestureController<T>? _backGestureController; | ||
138 | + | ||
139 | + late HorizontalDragGestureRecognizer _recognizer; | ||
85 | 140 | ||
86 | @override | 141 | @override |
87 | - String? get barrierLabel => null; | 142 | + Widget build(BuildContext context) { |
143 | + assert(debugCheckHasDirectionality(context)); | ||
144 | + // For devices with notches, the drag area needs to be larger on the side | ||
145 | + // that has the notch. | ||
146 | + var dragAreaWidth = Directionality.of(context) == TextDirection.ltr | ||
147 | + ? MediaQuery.of(context).padding.left | ||
148 | + : MediaQuery.of(context).padding.right; | ||
149 | + dragAreaWidth = max(dragAreaWidth, widget.gestureWidth); | ||
150 | + return Stack( | ||
151 | + fit: StackFit.passthrough, | ||
152 | + children: <Widget>[ | ||
153 | + widget.child, | ||
154 | + PositionedDirectional( | ||
155 | + start: 0.0, | ||
156 | + width: dragAreaWidth, | ||
157 | + top: 0.0, | ||
158 | + bottom: 0.0, | ||
159 | + child: Listener( | ||
160 | + onPointerDown: _handlePointerDown, | ||
161 | + behavior: HitTestBehavior.translucent, | ||
162 | + ), | ||
163 | + ), | ||
164 | + ], | ||
165 | + ); | ||
166 | + } | ||
88 | 167 | ||
89 | - bool get showCupertinoParallax; | 168 | + @override |
169 | + void dispose() { | ||
170 | + _recognizer.dispose(); | ||
171 | + super.dispose(); | ||
172 | + } | ||
90 | 173 | ||
91 | @override | 174 | @override |
92 | - bool canTransitionTo(TransitionRoute<dynamic> nextRoute) { | ||
93 | - // Don't perform outgoing animation if the next route is a | ||
94 | - // fullscreen dialog. | 175 | + void initState() { |
176 | + super.initState(); | ||
177 | + _recognizer = HorizontalDragGestureRecognizer(debugOwner: this) | ||
178 | + ..onStart = _handleDragStart | ||
179 | + ..onUpdate = _handleDragUpdate | ||
180 | + ..onEnd = _handleDragEnd | ||
181 | + ..onCancel = _handleDragCancel; | ||
182 | + } | ||
95 | 183 | ||
96 | - return nextRoute is GetPageRouteTransitionMixin && | ||
97 | - !nextRoute.fullscreenDialog && | ||
98 | - nextRoute.showCupertinoParallax; | 184 | + double _convertToLogical(double value) { |
185 | + switch (Directionality.of(context)) { | ||
186 | + case TextDirection.rtl: | ||
187 | + return -value; | ||
188 | + case TextDirection.ltr: | ||
189 | + return value; | ||
190 | + } | ||
99 | } | 191 | } |
100 | 192 | ||
101 | - /// True if an iOS-style back swipe pop gesture is currently | ||
102 | - /// underway for [route]. | 193 | + void _handleDragCancel() { |
194 | + assert(mounted); | ||
195 | + // This can be called even if start is not called, paired with | ||
196 | + // the "down" event | ||
197 | + // that we don't consider here. | ||
198 | + _backGestureController?.dragEnd(0.0); | ||
199 | + _backGestureController = null; | ||
200 | + } | ||
201 | + | ||
202 | + void _handleDragEnd(DragEndDetails details) { | ||
203 | + assert(mounted); | ||
204 | + assert(_backGestureController != null); | ||
205 | + _backGestureController!.dragEnd(_convertToLogical( | ||
206 | + details.velocity.pixelsPerSecond.dx / context.size!.width)); | ||
207 | + _backGestureController = null; | ||
208 | + } | ||
209 | + | ||
210 | + void _handleDragStart(DragStartDetails details) { | ||
211 | + assert(mounted); | ||
212 | + assert(_backGestureController == null); | ||
213 | + _backGestureController = widget.onStartPopGesture(); | ||
214 | + } | ||
215 | + | ||
216 | + void _handleDragUpdate(DragUpdateDetails details) { | ||
217 | + assert(mounted); | ||
218 | + assert(_backGestureController != null); | ||
219 | + _backGestureController!.dragUpdate( | ||
220 | + _convertToLogical(details.primaryDelta! / context.size!.width)); | ||
221 | + } | ||
222 | + | ||
223 | + void _handlePointerDown(PointerDownEvent event) { | ||
224 | + if (widget.enabledCallback()) _recognizer.addPointer(event); | ||
225 | + } | ||
226 | +} | ||
227 | + | ||
228 | +mixin GetPageRouteTransitionMixin<T> on PageRoute<T> { | ||
229 | + ValueNotifier<String?>? _previousTitle; | ||
230 | + | ||
231 | + @override | ||
232 | + Color? get barrierColor => null; | ||
233 | + | ||
234 | + @override | ||
235 | + String? get barrierLabel => null; | ||
236 | + | ||
237 | + double Function(BuildContext context)? get gestureWidth; | ||
238 | + | ||
239 | + /// Whether a pop gesture can be started by the user. | ||
103 | /// | 240 | /// |
104 | - /// This just check the route's [NavigatorState.userGestureInProgress]. | 241 | + /// Returns true if the user can edge-swipe to a previous route. |
105 | /// | 242 | /// |
106 | - /// See also: | 243 | + /// Returns false once [isPopGestureInProgress] is true, but |
244 | + /// [isPopGestureInProgress] can only become true if [popGestureEnabled] was | ||
245 | + /// true first. | ||
107 | /// | 246 | /// |
108 | - /// * [popGestureEnabled], which returns true if a user-triggered pop gesture | ||
109 | - /// would be allowed. | ||
110 | - static bool isPopGestureInProgress(PageRoute<dynamic> route) { | ||
111 | - return route.navigator!.userGestureInProgress; | ||
112 | - } | 247 | + /// This should only be used between frames, not during build. |
248 | + bool get popGestureEnabled => _isPopGestureEnabled(this); | ||
113 | 249 | ||
114 | /// True if an iOS-style back swipe pop gesture is currently | 250 | /// True if an iOS-style back swipe pop gesture is currently |
115 | /// underway for this route. | 251 | /// underway for this route. |
@@ -122,44 +258,47 @@ Cannot read the previousTitle for a route that has not yet been installed''', | @@ -122,44 +258,47 @@ Cannot read the previousTitle for a route that has not yet been installed''', | ||
122 | /// would be allowed. | 258 | /// would be allowed. |
123 | bool get popGestureInProgress => isPopGestureInProgress(this); | 259 | bool get popGestureInProgress => isPopGestureInProgress(this); |
124 | 260 | ||
125 | - /// Whether a pop gesture can be started by the user. | 261 | + /// The title string of the previous [CupertinoPageRoute]. |
126 | /// | 262 | /// |
127 | - /// Returns true if the user can edge-swipe to a previous route. | 263 | + /// The [ValueListenable]'s value is readable after the route is installed |
264 | + /// onto a [Navigator]. The [ValueListenable] will also notify its listeners | ||
265 | + /// if the value changes (such as by replacing the previous route). | ||
128 | /// | 266 | /// |
129 | - /// Returns false once [isPopGestureInProgress] is true, but | ||
130 | - /// [isPopGestureInProgress] can only become true if [popGestureEnabled] was | ||
131 | - /// true first. | 267 | + /// The [ValueListenable] itself will be null before the route is installed. |
268 | + /// Its content value will be null if the previous route has no title or | ||
269 | + /// is not a [CupertinoPageRoute]. | ||
132 | /// | 270 | /// |
133 | - /// This should only be used between frames, not during build. | ||
134 | - bool get popGestureEnabled => _isPopGestureEnabled(this); | ||
135 | - | ||
136 | - static bool _isPopGestureEnabled<T>(PageRoute<T> route) { | ||
137 | - // If there's nothing to go back to, then obviously we don't support | ||
138 | - // the back gesture. | ||
139 | - if (route.isFirst) return false; | ||
140 | - // If the route wouldn't actually pop if we popped it, then the gesture | ||
141 | - // would be really confusing (or would skip internal routes), | ||
142 | - //so disallow it. | ||
143 | - if (route.willHandlePopInternally) return false; | ||
144 | - // If attempts to dismiss this route might be vetoed such as in a page | ||
145 | - // with forms, then do not allow the user to dismiss the route with a swipe. | ||
146 | - if (route.hasScopedWillPopCallback) return false; | ||
147 | - // Fullscreen dialogs aren't dismissible by back swipe. | ||
148 | - if (route.fullscreenDialog) return false; | ||
149 | - // If we're in an animation already, we cannot be manually swiped. | ||
150 | - if (route.animation!.status != AnimationStatus.completed) return false; | ||
151 | - // If we're being popped into, we also cannot be swiped until the pop above | ||
152 | - // it completes. This translates to our secondary animation being | ||
153 | - // dismissed. | ||
154 | - if (route.secondaryAnimation!.status != AnimationStatus.dismissed) { | ||
155 | - return false; | 271 | + /// See also: |
272 | + /// | ||
273 | + /// * [ValueListenableBuilder], which can be used to listen and rebuild | ||
274 | + /// widgets based on a ValueListenable. | ||
275 | + ValueListenable<String?> get previousTitle { | ||
276 | + assert( | ||
277 | + _previousTitle != null, | ||
278 | + ''' | ||
279 | +Cannot read the previousTitle for a route that has not yet been installed''', | ||
280 | + ); | ||
281 | + return _previousTitle!; | ||
156 | } | 282 | } |
157 | - // If we're in a gesture already, we cannot start another. | ||
158 | - if (isPopGestureInProgress(route)) return false; | ||
159 | 283 | ||
160 | - // Looks like a back gesture would be welcome! | ||
161 | - return true; | ||
162 | - } | 284 | + bool get showCupertinoParallax; |
285 | + | ||
286 | + /// {@template flutter.cupertino.CupertinoRouteTransitionMixin.title} | ||
287 | + /// A title string for this route. | ||
288 | + /// | ||
289 | + /// Used to auto-populate [CupertinoNavigationBar] and | ||
290 | + /// [CupertinoSliverNavigationBar]'s `middle`/`largeTitle` widgets when | ||
291 | + /// one is not manually supplied. | ||
292 | + /// {@endtemplate} | ||
293 | + String? get title; | ||
294 | + | ||
295 | + @override | ||
296 | + // A relatively rigorous eyeball estimation. | ||
297 | + Duration get transitionDuration => const Duration(milliseconds: 400); | ||
298 | + | ||
299 | + /// Builds the primary contents of the route. | ||
300 | + @protected | ||
301 | + Widget buildContent(BuildContext context); | ||
163 | 302 | ||
164 | @override | 303 | @override |
165 | Widget buildPage(BuildContext context, Animation<double> animation, | 304 | Widget buildPage(BuildContext context, Animation<double> animation, |
@@ -172,18 +311,37 @@ Cannot read the previousTitle for a route that has not yet been installed''', | @@ -172,18 +311,37 @@ Cannot read the previousTitle for a route that has not yet been installed''', | ||
172 | ); | 311 | ); |
173 | return result; | 312 | return result; |
174 | } | 313 | } |
175 | - | ||
176 | - // Called by CupertinoBackGestureDetector when a pop ("back") drag start | ||
177 | - // gesture is detected. The returned controller handles all of the subsequent | ||
178 | - // drag events. | ||
179 | - static CupertinoBackGestureController<T> _startPopGesture<T>( | ||
180 | - PageRoute<T> route) { | ||
181 | - assert(_isPopGestureEnabled(route)); | ||
182 | - | ||
183 | - return CupertinoBackGestureController<T>( | ||
184 | - navigator: route.navigator!, | ||
185 | - controller: route.controller!, // protected access | ||
186 | - ); | 314 | + |
315 | + @override | ||
316 | + Widget buildTransitions(BuildContext context, Animation<double> animation, | ||
317 | + Animation<double> secondaryAnimation, Widget child) { | ||
318 | + return buildPageTransitions<T>( | ||
319 | + this, context, animation, secondaryAnimation, child); | ||
320 | + } | ||
321 | + | ||
322 | + @override | ||
323 | + bool canTransitionTo(TransitionRoute<dynamic> nextRoute) { | ||
324 | + // Don't perform outgoing animation if the next route is a | ||
325 | + // fullscreen dialog. | ||
326 | + | ||
327 | + return (nextRoute is GetPageRouteTransitionMixin && | ||
328 | + !nextRoute.fullscreenDialog && | ||
329 | + nextRoute.showCupertinoParallax) || | ||
330 | + (nextRoute is CupertinoRouteTransitionMixin && | ||
331 | + !nextRoute.fullscreenDialog); | ||
332 | + } | ||
333 | + | ||
334 | + @override | ||
335 | + void didChangePrevious(Route<dynamic>? previousRoute) { | ||
336 | + final previousTitleString = previousRoute is CupertinoRouteTransitionMixin | ||
337 | + ? previousRoute.title | ||
338 | + : null; | ||
339 | + if (_previousTitle == null) { | ||
340 | + _previousTitle = ValueNotifier<String?>(previousTitleString); | ||
341 | + } else { | ||
342 | + _previousTitle!.value = previousTitleString; | ||
343 | + } | ||
344 | + super.didChangePrevious(previousRoute); | ||
187 | } | 345 | } |
188 | 346 | ||
189 | /// Returns a [CupertinoFullscreenDialogTransition] if [route] is a full | 347 | /// Returns a [CupertinoFullscreenDialogTransition] if [route] is a full |
@@ -501,211 +659,57 @@ Cannot read the previousTitle for a route that has not yet been installed''', | @@ -501,211 +659,57 @@ Cannot read the previousTitle for a route that has not yet been installed''', | ||
501 | } | 659 | } |
502 | } | 660 | } |
503 | 661 | ||
504 | - @override | ||
505 | - Widget buildTransitions(BuildContext context, Animation<double> animation, | ||
506 | - Animation<double> secondaryAnimation, Widget child) { | ||
507 | - return buildPageTransitions<T>( | ||
508 | - this, context, animation, secondaryAnimation, child); | ||
509 | - } | ||
510 | -} | ||
511 | - | ||
512 | -class CupertinoBackGestureDetector<T> extends StatefulWidget { | ||
513 | - const CupertinoBackGestureDetector({ | ||
514 | - Key? key, | ||
515 | - required this.enabledCallback, | ||
516 | - required this.onStartPopGesture, | ||
517 | - required this.child, | ||
518 | - required this.gestureWidth, | ||
519 | - }) : super(key: key); | ||
520 | - | ||
521 | - final Widget child; | ||
522 | - final double gestureWidth; | ||
523 | - | ||
524 | - final ValueGetter<bool> enabledCallback; | ||
525 | - | ||
526 | - final ValueGetter<CupertinoBackGestureController<T>> onStartPopGesture; | ||
527 | - | ||
528 | - @override | ||
529 | - CupertinoBackGestureDetectorState<T> createState() => | ||
530 | - CupertinoBackGestureDetectorState<T>(); | ||
531 | -} | ||
532 | - | ||
533 | -class CupertinoBackGestureDetectorState<T> | ||
534 | - extends State<CupertinoBackGestureDetector<T>> { | ||
535 | - CupertinoBackGestureController<T>? _backGestureController; | ||
536 | - | ||
537 | - late HorizontalDragGestureRecognizer _recognizer; | ||
538 | - | ||
539 | - @override | ||
540 | - void initState() { | ||
541 | - super.initState(); | ||
542 | - _recognizer = HorizontalDragGestureRecognizer(debugOwner: this) | ||
543 | - ..onStart = _handleDragStart | ||
544 | - ..onUpdate = _handleDragUpdate | ||
545 | - ..onEnd = _handleDragEnd | ||
546 | - ..onCancel = _handleDragCancel; | ||
547 | - } | ||
548 | - | ||
549 | - @override | ||
550 | - void dispose() { | ||
551 | - _recognizer.dispose(); | ||
552 | - super.dispose(); | ||
553 | - } | ||
554 | - | ||
555 | - void _handleDragStart(DragStartDetails details) { | ||
556 | - assert(mounted); | ||
557 | - assert(_backGestureController == null); | ||
558 | - _backGestureController = widget.onStartPopGesture(); | ||
559 | - } | ||
560 | - | ||
561 | - void _handleDragUpdate(DragUpdateDetails details) { | ||
562 | - assert(mounted); | ||
563 | - assert(_backGestureController != null); | ||
564 | - _backGestureController!.dragUpdate( | ||
565 | - _convertToLogical(details.primaryDelta! / context.size!.width)); | ||
566 | - } | ||
567 | - | ||
568 | - void _handleDragEnd(DragEndDetails details) { | ||
569 | - assert(mounted); | ||
570 | - assert(_backGestureController != null); | ||
571 | - _backGestureController!.dragEnd(_convertToLogical( | ||
572 | - details.velocity.pixelsPerSecond.dx / context.size!.width)); | ||
573 | - _backGestureController = null; | ||
574 | - } | ||
575 | - | ||
576 | - void _handleDragCancel() { | ||
577 | - assert(mounted); | ||
578 | - // This can be called even if start is not called, paired with | ||
579 | - // the "down" event | ||
580 | - // that we don't consider here. | ||
581 | - _backGestureController?.dragEnd(0.0); | ||
582 | - _backGestureController = null; | ||
583 | - } | ||
584 | - | ||
585 | - void _handlePointerDown(PointerDownEvent event) { | ||
586 | - if (widget.enabledCallback()) _recognizer.addPointer(event); | ||
587 | - } | ||
588 | - | ||
589 | - double _convertToLogical(double value) { | ||
590 | - switch (Directionality.of(context)) { | ||
591 | - case TextDirection.rtl: | ||
592 | - return -value; | ||
593 | - case TextDirection.ltr: | ||
594 | - return value; | ||
595 | - } | ||
596 | - } | ||
597 | - | ||
598 | - @override | ||
599 | - Widget build(BuildContext context) { | ||
600 | - assert(debugCheckHasDirectionality(context)); | ||
601 | - // For devices with notches, the drag area needs to be larger on the side | ||
602 | - // that has the notch. | ||
603 | - var dragAreaWidth = Directionality.of(context) == TextDirection.ltr | ||
604 | - ? MediaQuery.of(context).padding.left | ||
605 | - : MediaQuery.of(context).padding.right; | ||
606 | - dragAreaWidth = max(dragAreaWidth, widget.gestureWidth); | ||
607 | - return Stack( | ||
608 | - fit: StackFit.passthrough, | ||
609 | - children: <Widget>[ | ||
610 | - widget.child, | ||
611 | - PositionedDirectional( | ||
612 | - start: 0.0, | ||
613 | - width: dragAreaWidth, | ||
614 | - top: 0.0, | ||
615 | - bottom: 0.0, | ||
616 | - child: Listener( | ||
617 | - onPointerDown: _handlePointerDown, | ||
618 | - behavior: HitTestBehavior.translucent, | ||
619 | - ), | ||
620 | - ), | ||
621 | - ], | ||
622 | - ); | ||
623 | - } | ||
624 | -} | ||
625 | - | ||
626 | -class CupertinoBackGestureController<T> { | ||
627 | - /// Creates a controller for an iOS-style back gesture. | 662 | + // Called by CupertinoBackGestureDetector when a pop ("back") drag start |
663 | + // gesture is detected. The returned controller handles all of the subsequent | ||
664 | + // drag events. | ||
665 | + /// True if an iOS-style back swipe pop gesture is currently | ||
666 | + /// underway for [route]. | ||
628 | /// | 667 | /// |
629 | - /// The [navigator] and [controller] arguments must not be null. | ||
630 | - CupertinoBackGestureController({ | ||
631 | - required this.navigator, | ||
632 | - required this.controller, | ||
633 | - }) { | ||
634 | - navigator.didStartUserGesture(); | 668 | + /// This just check the route's [NavigatorState.userGestureInProgress]. |
669 | + /// | ||
670 | + /// See also: | ||
671 | + /// | ||
672 | + /// * [popGestureEnabled], which returns true if a user-triggered pop gesture | ||
673 | + /// would be allowed. | ||
674 | + static bool isPopGestureInProgress(PageRoute<dynamic> route) { | ||
675 | + return route.navigator!.userGestureInProgress; | ||
635 | } | 676 | } |
636 | 677 | ||
637 | - final AnimationController controller; | ||
638 | - final NavigatorState navigator; | ||
639 | - | ||
640 | - /// The drag gesture has changed by [fractionalDelta]. The total range of the | ||
641 | - /// drag should be 0.0 to 1.0. | ||
642 | - void dragUpdate(double delta) { | ||
643 | - controller.value -= delta; | 678 | + static bool _isPopGestureEnabled<T>(PageRoute<T> route) { |
679 | + // If there's nothing to go back to, then obviously we don't support | ||
680 | + // the back gesture. | ||
681 | + if (route.isFirst) return false; | ||
682 | + // If the route wouldn't actually pop if we popped it, then the gesture | ||
683 | + // would be really confusing (or would skip internal routes), | ||
684 | + //so disallow it. | ||
685 | + if (route.willHandlePopInternally) return false; | ||
686 | + // If attempts to dismiss this route might be vetoed such as in a page | ||
687 | + // with forms, then do not allow the user to dismiss the route with a swipe. | ||
688 | + if (route.hasScopedWillPopCallback) return false; | ||
689 | + // Fullscreen dialogs aren't dismissible by back swipe. | ||
690 | + if (route.fullscreenDialog) return false; | ||
691 | + // If we're in an animation already, we cannot be manually swiped. | ||
692 | + if (route.animation!.status != AnimationStatus.completed) return false; | ||
693 | + // If we're being popped into, we also cannot be swiped until the pop above | ||
694 | + // it completes. This translates to our secondary animation being | ||
695 | + // dismissed. | ||
696 | + if (route.secondaryAnimation!.status != AnimationStatus.dismissed) { | ||
697 | + return false; | ||
644 | } | 698 | } |
699 | + // If we're in a gesture already, we cannot start another. | ||
700 | + if (isPopGestureInProgress(route)) return false; | ||
645 | 701 | ||
646 | - /// The drag gesture has ended with a horizontal motion of | ||
647 | - /// [fractionalVelocity] as a fraction of screen width per second. | ||
648 | - void dragEnd(double velocity) { | ||
649 | - // Fling in the appropriate direction. | ||
650 | - // AnimationController.fling is guaranteed to | ||
651 | - // take at least one frame. | ||
652 | - // | ||
653 | - // This curve has been determined through rigorously eyeballing native iOS | ||
654 | - // animations. | ||
655 | - const Curve animationCurve = Curves.fastLinearToSlowEaseIn; | ||
656 | - final bool animateForward; | ||
657 | - | ||
658 | - // If the user releases the page before mid screen with sufficient velocity, | ||
659 | - // or after mid screen, we should animate the page out. Otherwise, the page | ||
660 | - // should be animated back in. | ||
661 | - if (velocity.abs() >= _kMinFlingVelocity) { | ||
662 | - animateForward = velocity <= 0; | ||
663 | - } else { | ||
664 | - animateForward = controller.value > 0.5; | 702 | + // Looks like a back gesture would be welcome! |
703 | + return true; | ||
665 | } | 704 | } |
666 | 705 | ||
667 | - if (animateForward) { | ||
668 | - // The closer the panel is to dismissing, the shorter the animation is. | ||
669 | - // We want to cap the animation time, but we want to use a linear curve | ||
670 | - // to determine it. | ||
671 | - final droppedPageForwardAnimationTime = min( | ||
672 | - lerpDouble( | ||
673 | - _kMaxDroppedSwipePageForwardAnimationTime, 0, controller.value)! | ||
674 | - .floor(), | ||
675 | - _kMaxPageBackAnimationTime, | ||
676 | - ); | ||
677 | - controller.animateTo(1.0, | ||
678 | - duration: Duration(milliseconds: droppedPageForwardAnimationTime), | ||
679 | - curve: animationCurve); | ||
680 | - } else { | ||
681 | - // This route is destined to pop at this point. Reuse navigator's pop. | ||
682 | - navigator.pop(); | ||
683 | - | ||
684 | - // The popping may have finished inline if already at the | ||
685 | - // target destination. | ||
686 | - if (controller.isAnimating) { | ||
687 | - // Otherwise, use a custom popping animation duration and curve. | ||
688 | - final droppedPageBackAnimationTime = lerpDouble( | ||
689 | - 0, _kMaxDroppedSwipePageForwardAnimationTime, controller.value)! | ||
690 | - .floor(); | ||
691 | - controller.animateBack(0.0, | ||
692 | - duration: Duration(milliseconds: droppedPageBackAnimationTime), | ||
693 | - curve: animationCurve); | ||
694 | - } | ||
695 | - } | 706 | + static CupertinoBackGestureController<T> _startPopGesture<T>( |
707 | + PageRoute<T> route) { | ||
708 | + assert(_isPopGestureEnabled(route)); | ||
696 | 709 | ||
697 | - if (controller.isAnimating) { | ||
698 | - // Keep the userGestureInProgress in true state so we don't change the | ||
699 | - // curve of the page transition mid-flight since CupertinoPageTransition | ||
700 | - // depends on userGestureInProgress. | ||
701 | - late AnimationStatusListener animationStatusCallback; | ||
702 | - animationStatusCallback = (status) { | ||
703 | - navigator.didStopUserGesture(); | ||
704 | - controller.removeStatusListener(animationStatusCallback); | ||
705 | - }; | ||
706 | - controller.addStatusListener(animationStatusCallback); | ||
707 | - } else { | ||
708 | - navigator.didStopUserGesture(); | ||
709 | - } | 710 | + return CupertinoBackGestureController<T>( |
711 | + navigator: route.navigator!, | ||
712 | + controller: route.controller!, // protected access | ||
713 | + ); | ||
710 | } | 714 | } |
711 | } | 715 | } |
@@ -5,37 +5,8 @@ import '../../../../instance_manager.dart'; | @@ -5,37 +5,8 @@ import '../../../../instance_manager.dart'; | ||
5 | import '../../../get_navigation.dart'; | 5 | import '../../../get_navigation.dart'; |
6 | import '../../dialog/dialog_route.dart'; | 6 | import '../../dialog/dialog_route.dart'; |
7 | import '../../router_report.dart'; | 7 | import '../../router_report.dart'; |
8 | -import '../../snackbar/snack_route.dart'; | ||
9 | import '../default_route.dart'; | 8 | import '../default_route.dart'; |
10 | 9 | ||
11 | -class Routing { | ||
12 | - String current; | ||
13 | - String previous; | ||
14 | - dynamic args; | ||
15 | - String removed; | ||
16 | - Route<dynamic>? route; | ||
17 | - bool? isBack; | ||
18 | - bool? isSnackbar; | ||
19 | - bool? isBottomSheet; | ||
20 | - bool? isDialog; | ||
21 | - | ||
22 | - Routing({ | ||
23 | - this.current = '', | ||
24 | - this.previous = '', | ||
25 | - this.args, | ||
26 | - this.removed = '', | ||
27 | - this.route, | ||
28 | - this.isBack, | ||
29 | - this.isSnackbar, | ||
30 | - this.isBottomSheet, | ||
31 | - this.isDialog, | ||
32 | - }); | ||
33 | - | ||
34 | - void update(void fn(Routing value)) { | ||
35 | - fn(this); | ||
36 | - } | ||
37 | -} | ||
38 | - | ||
39 | /// Extracts the name of a route based on it's instance type | 10 | /// Extracts the name of a route based on it's instance type |
40 | /// or null if not possible. | 11 | /// or null if not possible. |
41 | String? _extractRouteName(Route? route) { | 12 | String? _extractRouteName(Route? route) { |
@@ -58,49 +29,70 @@ String? _extractRouteName(Route? route) { | @@ -58,49 +29,70 @@ String? _extractRouteName(Route? route) { | ||
58 | return null; | 29 | return null; |
59 | } | 30 | } |
60 | 31 | ||
61 | -/// This is basically a util for rules about 'what a route is' | ||
62 | -class _RouteData { | ||
63 | - final bool isGetPageRoute; | ||
64 | - final bool isSnackbar; | ||
65 | - final bool isBottomSheet; | ||
66 | - final bool isDialog; | ||
67 | - final String? name; | 32 | +class GetObserver extends NavigatorObserver { |
33 | + final Function(Routing?)? routing; | ||
68 | 34 | ||
69 | - _RouteData({ | ||
70 | - required this.name, | ||
71 | - required this.isGetPageRoute, | ||
72 | - required this.isSnackbar, | ||
73 | - required this.isBottomSheet, | ||
74 | - required this.isDialog, | ||
75 | - }); | 35 | + final Routing? _routeSend; |
76 | 36 | ||
77 | - factory _RouteData.ofRoute(Route? route) { | ||
78 | - return _RouteData( | ||
79 | - name: _extractRouteName(route), | ||
80 | - isGetPageRoute: route is GetPageRoute, | ||
81 | - isSnackbar: route is SnackRoute, | ||
82 | - isDialog: route is GetDialogRoute, | ||
83 | - isBottomSheet: route is GetModalBottomSheetRoute, | ||
84 | - ); | 37 | + GetObserver([this.routing, this._routeSend]); |
38 | + | ||
39 | + @override | ||
40 | + void didPop(Route route, Route? previousRoute) { | ||
41 | + super.didPop(route, previousRoute); | ||
42 | + final currentRoute = _RouteData.ofRoute(route); | ||
43 | + final newRoute = _RouteData.ofRoute(previousRoute); | ||
44 | + | ||
45 | + // if (currentRoute.isSnackbar) { | ||
46 | + // // Get.log("CLOSE SNACKBAR ${currentRoute.name}"); | ||
47 | + // Get.log("CLOSE SNACKBAR"); | ||
48 | + // } else | ||
49 | + | ||
50 | + if (currentRoute.isBottomSheet || currentRoute.isDialog) { | ||
51 | + Get.log("CLOSE ${currentRoute.name}"); | ||
52 | + } else if (currentRoute.isGetPageRoute) { | ||
53 | + Get.log("CLOSE TO ROUTE ${currentRoute.name}"); | ||
54 | + } | ||
55 | + if (previousRoute != null) { | ||
56 | + RouterReportManager.reportCurrentRoute(previousRoute); | ||
85 | } | 57 | } |
86 | -} | ||
87 | 58 | ||
88 | -class GetObserver extends NavigatorObserver { | ||
89 | - final Function(Routing?)? routing; | 59 | + // Here we use a 'inverse didPush set', meaning that we use |
60 | + // previous route instead of 'route' because this is | ||
61 | + // a 'inverse push' | ||
62 | + _routeSend?.update((value) { | ||
63 | + // Only PageRoute is allowed to change current value | ||
64 | + if (previousRoute is PageRoute) { | ||
65 | + value.current = _extractRouteName(previousRoute) ?? ''; | ||
66 | + value.previous = newRoute.name ?? ''; | ||
67 | + } else if (value.previous.isNotEmpty) { | ||
68 | + value.current = value.previous; | ||
69 | + } | ||
90 | 70 | ||
91 | - GetObserver([this.routing, this._routeSend]); | 71 | + value.args = previousRoute?.settings.arguments; |
72 | + value.route = previousRoute; | ||
73 | + value.isBack = true; | ||
74 | + value.removed = ''; | ||
75 | + // value.isSnackbar = newRoute.isSnackbar; | ||
76 | + value.isBottomSheet = newRoute.isBottomSheet; | ||
77 | + value.isDialog = newRoute.isDialog; | ||
78 | + }); | ||
92 | 79 | ||
93 | - final Routing? _routeSend; | 80 | + // print('currentRoute.isDialog ${currentRoute.isDialog}'); |
81 | + | ||
82 | + routing?.call(_routeSend); | ||
83 | + } | ||
94 | 84 | ||
95 | @override | 85 | @override |
96 | void didPush(Route route, Route? previousRoute) { | 86 | void didPush(Route route, Route? previousRoute) { |
97 | super.didPush(route, previousRoute); | 87 | super.didPush(route, previousRoute); |
98 | final newRoute = _RouteData.ofRoute(route); | 88 | final newRoute = _RouteData.ofRoute(route); |
99 | 89 | ||
100 | - if (newRoute.isSnackbar) { | ||
101 | - // Get.log("OPEN SNACKBAR ${newRoute.name}"); | ||
102 | - Get.log("OPEN SNACKBAR"); | ||
103 | - } else if (newRoute.isBottomSheet || newRoute.isDialog) { | 90 | + // if (newRoute.isSnackbar) { |
91 | + // // Get.log("OPEN SNACKBAR ${newRoute.name}"); | ||
92 | + // Get.log("OPEN SNACKBAR"); | ||
93 | + // } else | ||
94 | + | ||
95 | + if (newRoute.isBottomSheet || newRoute.isDialog) { | ||
104 | Get.log("OPEN ${newRoute.name}"); | 96 | Get.log("OPEN ${newRoute.name}"); |
105 | } else if (newRoute.isGetPageRoute) { | 97 | } else if (newRoute.isGetPageRoute) { |
106 | Get.log("GOING TO ROUTE ${newRoute.name}"); | 98 | Get.log("GOING TO ROUTE ${newRoute.name}"); |
@@ -121,7 +113,6 @@ class GetObserver extends NavigatorObserver { | @@ -121,7 +113,6 @@ class GetObserver extends NavigatorObserver { | ||
121 | value.route = route; | 113 | value.route = route; |
122 | value.isBack = false; | 114 | value.isBack = false; |
123 | value.removed = ''; | 115 | value.removed = ''; |
124 | - value.isSnackbar = newRoute.isSnackbar ? true : value.isSnackbar ?? false; | ||
125 | value.isBottomSheet = | 116 | value.isBottomSheet = |
126 | newRoute.isBottomSheet ? true : value.isBottomSheet ?? false; | 117 | newRoute.isBottomSheet ? true : value.isBottomSheet ?? false; |
127 | value.isDialog = newRoute.isDialog ? true : value.isDialog ?? false; | 118 | value.isDialog = newRoute.isDialog ? true : value.isDialog ?? false; |
@@ -133,46 +124,27 @@ class GetObserver extends NavigatorObserver { | @@ -133,46 +124,27 @@ class GetObserver extends NavigatorObserver { | ||
133 | } | 124 | } |
134 | 125 | ||
135 | @override | 126 | @override |
136 | - void didPop(Route route, Route? previousRoute) { | ||
137 | - super.didPop(route, previousRoute); | 127 | + void didRemove(Route route, Route? previousRoute) { |
128 | + super.didRemove(route, previousRoute); | ||
129 | + final routeName = _extractRouteName(route); | ||
138 | final currentRoute = _RouteData.ofRoute(route); | 130 | final currentRoute = _RouteData.ofRoute(route); |
139 | - final newRoute = _RouteData.ofRoute(previousRoute); | ||
140 | 131 | ||
141 | - if (currentRoute.isSnackbar) { | ||
142 | - // Get.log("CLOSE SNACKBAR ${currentRoute.name}"); | ||
143 | - Get.log("CLOSE SNACKBAR"); | ||
144 | - } else if (currentRoute.isBottomSheet || currentRoute.isDialog) { | ||
145 | - Get.log("CLOSE ${currentRoute.name}"); | ||
146 | - } else if (currentRoute.isGetPageRoute) { | ||
147 | - Get.log("CLOSE TO ROUTE ${currentRoute.name}"); | ||
148 | - } | ||
149 | - if (previousRoute != null) { | ||
150 | - RouterReportManager.reportCurrentRoute(previousRoute); | ||
151 | - } | 132 | + Get.log("REMOVING ROUTE $routeName"); |
152 | 133 | ||
153 | - // Here we use a 'inverse didPush set', meaning that we use | ||
154 | - // previous route instead of 'route' because this is | ||
155 | - // a 'inverse push' | ||
156 | _routeSend?.update((value) { | 134 | _routeSend?.update((value) { |
157 | - // Only PageRoute is allowed to change current value | ||
158 | - if (previousRoute is PageRoute) { | ||
159 | - value.current = _extractRouteName(previousRoute) ?? ''; | ||
160 | - value.previous = newRoute.name ?? ''; | ||
161 | - } else if (value.previous.isNotEmpty) { | ||
162 | - value.current = value.previous; | ||
163 | - } | ||
164 | - | ||
165 | - value.args = previousRoute?.settings.arguments; | ||
166 | value.route = previousRoute; | 135 | value.route = previousRoute; |
167 | - value.isBack = true; | ||
168 | - value.removed = ''; | ||
169 | - value.isSnackbar = newRoute.isSnackbar; | ||
170 | - value.isBottomSheet = newRoute.isBottomSheet; | ||
171 | - value.isDialog = newRoute.isDialog; | 136 | + value.isBack = false; |
137 | + value.removed = routeName ?? ''; | ||
138 | + value.previous = routeName ?? ''; | ||
139 | + // value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar; | ||
140 | + value.isBottomSheet = | ||
141 | + currentRoute.isBottomSheet ? false : value.isBottomSheet; | ||
142 | + value.isDialog = currentRoute.isDialog ? false : value.isDialog; | ||
172 | }); | 143 | }); |
173 | 144 | ||
174 | - // print('currentRoute.isDialog ${currentRoute.isDialog}'); | ||
175 | - | 145 | + if (route is GetPageRoute) { |
146 | + RouterReportManager.reportRouteWillDispose(route); | ||
147 | + } | ||
176 | routing?.call(_routeSend); | 148 | routing?.call(_routeSend); |
177 | } | 149 | } |
178 | 150 | ||
@@ -201,7 +173,7 @@ class GetObserver extends NavigatorObserver { | @@ -201,7 +173,7 @@ class GetObserver extends NavigatorObserver { | ||
201 | value.isBack = false; | 173 | value.isBack = false; |
202 | value.removed = ''; | 174 | value.removed = ''; |
203 | value.previous = '$oldName'; | 175 | value.previous = '$oldName'; |
204 | - value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar; | 176 | + // value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar; |
205 | value.isBottomSheet = | 177 | value.isBottomSheet = |
206 | currentRoute.isBottomSheet ? false : value.isBottomSheet; | 178 | currentRoute.isBottomSheet ? false : value.isBottomSheet; |
207 | value.isDialog = currentRoute.isDialog ? false : value.isDialog; | 179 | value.isDialog = currentRoute.isDialog ? false : value.isDialog; |
@@ -212,29 +184,59 @@ class GetObserver extends NavigatorObserver { | @@ -212,29 +184,59 @@ class GetObserver extends NavigatorObserver { | ||
212 | 184 | ||
213 | routing?.call(_routeSend); | 185 | routing?.call(_routeSend); |
214 | } | 186 | } |
187 | +} | ||
215 | 188 | ||
216 | - @override | ||
217 | - void didRemove(Route route, Route? previousRoute) { | ||
218 | - super.didRemove(route, previousRoute); | ||
219 | - final routeName = _extractRouteName(route); | ||
220 | - final currentRoute = _RouteData.ofRoute(route); | ||
221 | - | ||
222 | - Get.log("REMOVING ROUTE $routeName"); | 189 | +class Routing { |
190 | + String current; | ||
191 | + String previous; | ||
192 | + dynamic args; | ||
193 | + String removed; | ||
194 | + Route<dynamic>? route; | ||
195 | + bool? isBack; | ||
196 | + // bool? isSnackbar; | ||
197 | + bool? isBottomSheet; | ||
198 | + bool? isDialog; | ||
223 | 199 | ||
224 | - _routeSend?.update((value) { | ||
225 | - value.route = previousRoute; | ||
226 | - value.isBack = false; | ||
227 | - value.removed = routeName ?? ''; | ||
228 | - value.previous = routeName ?? ''; | ||
229 | - value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar; | ||
230 | - value.isBottomSheet = | ||
231 | - currentRoute.isBottomSheet ? false : value.isBottomSheet; | ||
232 | - value.isDialog = currentRoute.isDialog ? false : value.isDialog; | 200 | + Routing({ |
201 | + this.current = '', | ||
202 | + this.previous = '', | ||
203 | + this.args, | ||
204 | + this.removed = '', | ||
205 | + this.route, | ||
206 | + this.isBack, | ||
207 | + // this.isSnackbar, | ||
208 | + this.isBottomSheet, | ||
209 | + this.isDialog, | ||
233 | }); | 210 | }); |
234 | 211 | ||
235 | - if (route is GetPageRoute) { | ||
236 | - RouterReportManager.reportRouteWillDispose(route); | 212 | + void update(void fn(Routing value)) { |
213 | + fn(this); | ||
237 | } | 214 | } |
238 | - routing?.call(_routeSend); | 215 | +} |
216 | + | ||
217 | +/// This is basically a util for rules about 'what a route is' | ||
218 | +class _RouteData { | ||
219 | + final bool isGetPageRoute; | ||
220 | + //final bool isSnackbar; | ||
221 | + final bool isBottomSheet; | ||
222 | + final bool isDialog; | ||
223 | + final String? name; | ||
224 | + | ||
225 | + _RouteData({ | ||
226 | + required this.name, | ||
227 | + required this.isGetPageRoute, | ||
228 | + // required this.isSnackbar, | ||
229 | + required this.isBottomSheet, | ||
230 | + required this.isDialog, | ||
231 | + }); | ||
232 | + | ||
233 | + factory _RouteData.ofRoute(Route? route) { | ||
234 | + return _RouteData( | ||
235 | + name: _extractRouteName(route), | ||
236 | + isGetPageRoute: route is GetPageRoute, | ||
237 | + // isSnackbar: route is SnackRoute, | ||
238 | + isDialog: route is GetDialogRoute, | ||
239 | + isBottomSheet: route is GetModalBottomSheetRoute, | ||
240 | + ); | ||
239 | } | 241 | } |
240 | } | 242 | } |
1 | -import 'dart:async'; | ||
2 | -import 'dart:ui'; | ||
3 | -import 'package:flutter/widgets.dart'; | ||
4 | -import '../../../get_core/get_core.dart'; | ||
5 | -import '../../get_navigation.dart'; | ||
6 | -import 'snack.dart'; | ||
7 | - | ||
8 | -class SnackRoute<T> extends OverlayRoute<T> { | ||
9 | - late Animation<double> _filterBlurAnimation; | ||
10 | - late Animation<Color?> _filterColorAnimation; | ||
11 | - | ||
12 | - SnackRoute({ | ||
13 | - required this.snack, | ||
14 | - RouteSettings? settings, | ||
15 | - }) : super(settings: settings) { | ||
16 | - _builder = Builder(builder: (_) { | ||
17 | - return GestureDetector( | ||
18 | - child: snack, | ||
19 | - onTap: snack.onTap != null ? () => snack.onTap!(snack) : null, | ||
20 | - ); | ||
21 | - }); | ||
22 | - | ||
23 | - _configureAlignment(snack.snackPosition); | ||
24 | - _snackbarStatus = snack.snackbarStatus; | ||
25 | - } | ||
26 | - | ||
27 | - _configureAlignment(SnackPosition snackPosition) { | ||
28 | - switch (snack.snackPosition) { | ||
29 | - case SnackPosition.TOP: | ||
30 | - { | ||
31 | - _initialAlignment = Alignment(-1.0, -2.0); | ||
32 | - _endAlignment = Alignment(-1.0, -1.0); | ||
33 | - break; | ||
34 | - } | ||
35 | - case SnackPosition.BOTTOM: | ||
36 | - { | ||
37 | - _initialAlignment = Alignment(-1.0, 2.0); | ||
38 | - _endAlignment = Alignment(-1.0, 1.0); | ||
39 | - break; | ||
40 | - } | ||
41 | - } | ||
42 | - } | ||
43 | - | ||
44 | - GetBar snack; | ||
45 | - Builder? _builder; | ||
46 | - | ||
47 | - final Completer<T> _transitionCompleter = Completer<T>(); | ||
48 | - | ||
49 | - late SnackbarStatusCallback _snackbarStatus; | ||
50 | - Alignment? _initialAlignment; | ||
51 | - Alignment? _endAlignment; | ||
52 | - bool _wasDismissedBySwipe = false; | ||
53 | - bool _onTappedDismiss = false; | ||
54 | - | ||
55 | - Timer? _timer; | ||
56 | - | ||
57 | - bool get opaque => false; | ||
58 | - | ||
59 | - @override | ||
60 | - Iterable<OverlayEntry> createOverlayEntries() { | ||
61 | - return <OverlayEntry>[ | ||
62 | - if (snack.overlayBlur > 0.0) ...[ | ||
63 | - OverlayEntry( | ||
64 | - builder: (context) { | ||
65 | - return GestureDetector( | ||
66 | - onTap: () { | ||
67 | - if (snack.isDismissible && !_onTappedDismiss) { | ||
68 | - _onTappedDismiss = true; | ||
69 | - Get.back(); | ||
70 | - } | ||
71 | - }, | ||
72 | - child: AnimatedBuilder( | ||
73 | - animation: _filterBlurAnimation, | ||
74 | - builder: (context, child) { | ||
75 | - return BackdropFilter( | ||
76 | - filter: ImageFilter.blur( | ||
77 | - sigmaX: _filterBlurAnimation.value, | ||
78 | - sigmaY: _filterBlurAnimation.value), | ||
79 | - child: Container( | ||
80 | - constraints: BoxConstraints.expand(), | ||
81 | - color: _filterColorAnimation.value, | ||
82 | - ), | ||
83 | - ); | ||
84 | - }, | ||
85 | - ), | ||
86 | - ); | ||
87 | - }, | ||
88 | - maintainState: false, | ||
89 | - opaque: opaque, | ||
90 | - ), | ||
91 | - ], | ||
92 | - OverlayEntry( | ||
93 | - builder: (context) { | ||
94 | - final Widget annotatedChild = Semantics( | ||
95 | - child: AlignTransition( | ||
96 | - alignment: _animation!, | ||
97 | - child: snack.isDismissible | ||
98 | - ? _getDismissibleSnack(_builder) | ||
99 | - : _getSnack(), | ||
100 | - ), | ||
101 | - focused: false, | ||
102 | - container: true, | ||
103 | - explicitChildNodes: true, | ||
104 | - ); | ||
105 | - return annotatedChild; | ||
106 | - }, | ||
107 | - maintainState: false, | ||
108 | - opaque: opaque, | ||
109 | - ), | ||
110 | - ]; | ||
111 | - } | ||
112 | - | ||
113 | - String dismissibleKeyGen = ""; | ||
114 | - | ||
115 | - Widget _getDismissibleSnack(Widget? child) { | ||
116 | - return Dismissible( | ||
117 | - direction: _getDismissDirection(), | ||
118 | - resizeDuration: null, | ||
119 | - confirmDismiss: (_) { | ||
120 | - if (currentStatus == SnackbarStatus.OPENING || | ||
121 | - currentStatus == SnackbarStatus.CLOSING) { | ||
122 | - return Future.value(false); | ||
123 | - } | ||
124 | - return Future.value(true); | ||
125 | - }, | ||
126 | - key: Key(dismissibleKeyGen), | ||
127 | - onDismissed: (_) { | ||
128 | - dismissibleKeyGen += "1"; | ||
129 | - _cancelTimer(); | ||
130 | - _wasDismissedBySwipe = true; | ||
131 | - | ||
132 | - if (isCurrent) { | ||
133 | - navigator!.pop(); | ||
134 | - } else { | ||
135 | - navigator!.removeRoute(this); | ||
136 | - } | ||
137 | - }, | ||
138 | - child: _getSnack(), | ||
139 | - ); | ||
140 | - } | ||
141 | - | ||
142 | - Widget _getSnack() { | ||
143 | - return Container( | ||
144 | - margin: snack.margin, | ||
145 | - child: _builder, | ||
146 | - ); | ||
147 | - } | ||
148 | - | ||
149 | - DismissDirection _getDismissDirection() { | ||
150 | - if (snack.dismissDirection == SnackDismissDirection.HORIZONTAL) { | ||
151 | - return DismissDirection.horizontal; | ||
152 | - } else { | ||
153 | - if (snack.snackPosition == SnackPosition.TOP) { | ||
154 | - return DismissDirection.up; | ||
155 | - } | ||
156 | - return DismissDirection.down; | ||
157 | - } | ||
158 | - } | ||
159 | - | ||
160 | - @override | ||
161 | - bool get finishedWhenPopped => | ||
162 | - _controller!.status == AnimationStatus.dismissed; | ||
163 | - | ||
164 | - /// The animation that drives the route's transition and the previous route's | ||
165 | - /// forward transition. | ||
166 | - Animation<Alignment>? _animation; | ||
167 | - | ||
168 | - /// The animation controller that the route uses to drive the transitions. | ||
169 | - /// | ||
170 | - /// The animation itself is exposed by the [animation] property. | ||
171 | - AnimationController? _controller; | ||
172 | - | ||
173 | - /// Called to create the animation controller that will drive the transitions | ||
174 | - /// to this route from the previous one, and back to the previous route | ||
175 | - /// from this one. | ||
176 | - AnimationController createAnimationController() { | ||
177 | - assert(!_transitionCompleter.isCompleted, | ||
178 | - 'Cannot reuse a $runtimeType after disposing it.'); | ||
179 | - assert(snack.animationDuration >= Duration.zero); | ||
180 | - return AnimationController( | ||
181 | - duration: snack.animationDuration, | ||
182 | - debugLabel: debugLabel, | ||
183 | - vsync: navigator!, | ||
184 | - ); | ||
185 | - } | ||
186 | - | ||
187 | - /// Called to create the animation that exposes the current progress of | ||
188 | - /// the transition controlled by the animation controller created by | ||
189 | - /// `createAnimationController()`. | ||
190 | - Animation<Alignment> createAnimation() { | ||
191 | - assert(!_transitionCompleter.isCompleted, | ||
192 | - 'Cannot reuse a $runtimeType after disposing it.'); | ||
193 | - assert(_controller != null); | ||
194 | - return AlignmentTween(begin: _initialAlignment, end: _endAlignment).animate( | ||
195 | - CurvedAnimation( | ||
196 | - parent: _controller!, | ||
197 | - curve: snack.forwardAnimationCurve, | ||
198 | - reverseCurve: snack.reverseAnimationCurve, | ||
199 | - ), | ||
200 | - ); | ||
201 | - } | ||
202 | - | ||
203 | - Animation<double> createBlurFilterAnimation() { | ||
204 | - return Tween(begin: 0.0, end: snack.overlayBlur).animate( | ||
205 | - CurvedAnimation( | ||
206 | - parent: _controller!, | ||
207 | - curve: Interval( | ||
208 | - 0.0, | ||
209 | - 0.35, | ||
210 | - curve: Curves.easeInOutCirc, | ||
211 | - ), | ||
212 | - ), | ||
213 | - ); | ||
214 | - } | ||
215 | - | ||
216 | - Animation<Color?> createColorFilterAnimation() { | ||
217 | - return ColorTween(begin: Color(0x00000000), end: snack.overlayColor) | ||
218 | - .animate( | ||
219 | - CurvedAnimation( | ||
220 | - parent: _controller!, | ||
221 | - curve: Interval( | ||
222 | - 0.0, | ||
223 | - 0.35, | ||
224 | - curve: Curves.easeInOutCirc, | ||
225 | - ), | ||
226 | - ), | ||
227 | - ); | ||
228 | - } | ||
229 | - | ||
230 | - T? _result; | ||
231 | - SnackbarStatus? currentStatus; | ||
232 | - | ||
233 | - void _handleStatusChanged(AnimationStatus status) { | ||
234 | - switch (status) { | ||
235 | - case AnimationStatus.completed: | ||
236 | - currentStatus = SnackbarStatus.OPEN; | ||
237 | - _snackbarStatus(currentStatus); | ||
238 | - if (overlayEntries.isNotEmpty) overlayEntries.first.opaque = opaque; | ||
239 | - | ||
240 | - break; | ||
241 | - case AnimationStatus.forward: | ||
242 | - currentStatus = SnackbarStatus.OPENING; | ||
243 | - _snackbarStatus(currentStatus); | ||
244 | - break; | ||
245 | - case AnimationStatus.reverse: | ||
246 | - currentStatus = SnackbarStatus.CLOSING; | ||
247 | - _snackbarStatus(currentStatus); | ||
248 | - if (overlayEntries.isNotEmpty) overlayEntries.first.opaque = false; | ||
249 | - break; | ||
250 | - case AnimationStatus.dismissed: | ||
251 | - assert(!overlayEntries.first.opaque); | ||
252 | - // We might still be the current route if a subclass is controlling the | ||
253 | - // the transition and hits the dismissed status. For example, the iOS | ||
254 | - // back gesture drives this animation to the dismissed status before | ||
255 | - // popping the navigator. | ||
256 | - currentStatus = SnackbarStatus.CLOSED; | ||
257 | - _snackbarStatus(currentStatus); | ||
258 | - | ||
259 | - if (!isCurrent) { | ||
260 | - navigator!.finalizeRoute(this); | ||
261 | - // assert(overlayEntries.isEmpty); | ||
262 | - } | ||
263 | - break; | ||
264 | - } | ||
265 | - changedInternalState(); | ||
266 | - } | ||
267 | - | ||
268 | - @override | ||
269 | - void install() { | ||
270 | - assert(!_transitionCompleter.isCompleted, | ||
271 | - 'Cannot install a $runtimeType after disposing it.'); | ||
272 | - _controller = createAnimationController(); | ||
273 | - assert(_controller != null, | ||
274 | - '$runtimeType.createAnimationController() returned null.'); | ||
275 | - _filterBlurAnimation = createBlurFilterAnimation(); | ||
276 | - _filterColorAnimation = createColorFilterAnimation(); | ||
277 | - _animation = createAnimation(); | ||
278 | - assert(_animation != null, '$runtimeType.createAnimation() returned null.'); | ||
279 | - super.install(); | ||
280 | - } | ||
281 | - | ||
282 | - @override | ||
283 | - TickerFuture didPush() { | ||
284 | - super.didPush(); | ||
285 | - assert( | ||
286 | - _controller != null, | ||
287 | - // ignore: lines_longer_than_80_chars | ||
288 | - '$runtimeType.didPush called before calling install() or after calling dispose().', | ||
289 | - ); | ||
290 | - assert( | ||
291 | - !_transitionCompleter.isCompleted, | ||
292 | - 'Cannot reuse a $runtimeType after disposing it.', | ||
293 | - ); | ||
294 | - _animation!.addStatusListener(_handleStatusChanged); | ||
295 | - _configureTimer(); | ||
296 | - return _controller!.forward(); | ||
297 | - } | ||
298 | - | ||
299 | - @override | ||
300 | - void didReplace(Route<dynamic>? oldRoute) { | ||
301 | - assert( | ||
302 | - _controller != null, | ||
303 | - // ignore: lines_longer_than_80_chars | ||
304 | - '$runtimeType.didReplace called before calling install() or after calling dispose().', | ||
305 | - ); | ||
306 | - assert( | ||
307 | - !_transitionCompleter.isCompleted, | ||
308 | - 'Cannot reuse a $runtimeType after disposing it.', | ||
309 | - ); | ||
310 | - | ||
311 | - if (oldRoute is SnackRoute) { | ||
312 | - _controller!.value = oldRoute._controller!.value; | ||
313 | - } | ||
314 | - _animation!.addStatusListener(_handleStatusChanged); | ||
315 | - super.didReplace(oldRoute); | ||
316 | - } | ||
317 | - | ||
318 | - @override | ||
319 | - bool didPop(T? result) { | ||
320 | - assert( | ||
321 | - _controller != null, | ||
322 | - // ignore: lines_longer_than_80_chars | ||
323 | - '$runtimeType.didPop called before calling install() or after calling dispose().', | ||
324 | - ); | ||
325 | - assert( | ||
326 | - !_transitionCompleter.isCompleted, | ||
327 | - 'Cannot reuse a $runtimeType after disposing it.', | ||
328 | - ); | ||
329 | - | ||
330 | - _result = result; | ||
331 | - _cancelTimer(); | ||
332 | - | ||
333 | - if (_wasDismissedBySwipe) { | ||
334 | - Timer(Duration(milliseconds: 200), () { | ||
335 | - _controller!.reset(); | ||
336 | - }); | ||
337 | - | ||
338 | - _wasDismissedBySwipe = false; | ||
339 | - } else { | ||
340 | - _controller!.reverse(); | ||
341 | - } | ||
342 | - | ||
343 | - return super.didPop(result); | ||
344 | - } | ||
345 | - | ||
346 | - void _configureTimer() { | ||
347 | - if (snack.duration != null) { | ||
348 | - if (_timer != null && _timer!.isActive) { | ||
349 | - _timer!.cancel(); | ||
350 | - } | ||
351 | - _timer = Timer(snack.duration!, () { | ||
352 | - if (isCurrent) { | ||
353 | - navigator!.pop(); | ||
354 | - } else if (isActive) { | ||
355 | - navigator!.removeRoute(this); | ||
356 | - } | ||
357 | - }); | ||
358 | - } else { | ||
359 | - if (_timer != null) { | ||
360 | - _timer!.cancel(); | ||
361 | - } | ||
362 | - } | ||
363 | - } | ||
364 | - | ||
365 | - void _cancelTimer() { | ||
366 | - if (_timer != null && _timer!.isActive) { | ||
367 | - _timer!.cancel(); | ||
368 | - } | ||
369 | - } | ||
370 | - | ||
371 | - /// Whether this route can perform a transition to the given route. | ||
372 | - /// Subclasses can override this method to restrict the set of routes they | ||
373 | - /// need to coordinate transitions with. | ||
374 | - bool canTransitionTo(SnackRoute<dynamic> nextRoute) => true; | ||
375 | - | ||
376 | - /// Whether this route can perform a transition from the given route. | ||
377 | - /// | ||
378 | - /// Subclasses can override this method to restrict the set of routes they | ||
379 | - /// need to coordinate transitions with. | ||
380 | - bool canTransitionFrom(SnackRoute<dynamic> previousRoute) => true; | ||
381 | - | ||
382 | - @override | ||
383 | - void dispose() { | ||
384 | - assert(!_transitionCompleter.isCompleted, | ||
385 | - 'Cannot dispose a $runtimeType twice.'); | ||
386 | - _controller?.dispose(); | ||
387 | - _transitionCompleter.complete(_result); | ||
388 | - super.dispose(); | ||
389 | - } | ||
390 | - | ||
391 | - /// A short description of this route useful for debugging. | ||
392 | - String get debugLabel => '$runtimeType'; | ||
393 | -} |
-
Please register or login to post a comment