Ravi Parmar
Committed by GitHub

Merge branch 'jonataslaw:master' into master

Too many changes to show.

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

No preview for this file type
## [4.5.1] - Big Update
Fix Snackbar when it have action and icon the same time
## [4.5.0] - Big Update
To have a context-free, page-agnostic snackbar, we used OverlayRoute to display a partial route.
However this had several problems:
1: There was no possibility to close the page without closing the snackbar
2: Get.back() could cause problems with tests of Get.isSnackbarOpen not being properly invoked
3: Sometimes when using iOS popGesture with an open snackbar, some visual inconsistency might appear.
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.
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.
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.
For backwards compatibility, Get.back() still works for closing routes and overlays, however two new commands have been added: Get.closeCurrentSnackbar() and Get.closeAllSnackbars().
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.
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.
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.
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.
Update locale also now returns a Future, allowing you to perform an action only when the language has already changed (@MHosssam)
We are very happy to announce that GetX is now documented in Japanese as well, thanks to (@toshi-kuji)
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)
@maxzod has also started translating the docs into Arabic, we hope the documentation will be complete soon.
Some remaining package logs have been moved to Get.log (@gairick-saha)
RxList.removeWhere received performance optimizations (@zuvola)
Optimizations in GetConnect and added the ability to modify all request items in GetConnect (@rodrigorahman)
The current route could be inconsistent if a dialog were opened after a transition, fixed by @xiangzy1
Fixed try/catch case missed in socket_notifier (@ShookLyngs)
Also we had fixes in the docs: @DeathGun3344 @pinguluk
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.
This update is a preparation update for version 5, which will be released later this year.
Breaking and Depreciation:
GetBar is now deprecated, use GetSnackbar instead.
dismissDirection now gets a DismissDirection, making the Snackbar more customizable.
## [4.3.8]
- Fix nav2 toNamed remove the route
... ...
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N)
... ...
... ... @@ -3,6 +3,9 @@
*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).*
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N)
... ...
... ... @@ -3,7 +3,9 @@
**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).**
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N)
... ...
... ... @@ -3,7 +3,9 @@
**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).**
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N)
... ...
... ... @@ -3,7 +3,9 @@
**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)**
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N)
... ...
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N)
... ...
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N)
... ... @@ -326,7 +328,7 @@ Text(controller.textFromApi);
### 종속성 관리에 대한 자세한 내용
**종속성 관리에 대한 더 제사한 사항은 [여기](./documentation/kr_KO/dependency_management.md)에 있습니다.**
**종속성 관리에 대한 더 자세한 사항은 [여기](./documentation/kr_KO/dependency_management.md)에 있습니다.**
# 기능들
... ... @@ -1090,6 +1092,73 @@ class SettingsService extends GetxService {
따라서 앱 실행중 절대로 유지되어야 하는 클래스 인스턴스가 필요하면
`GetxService`를 사용하세요.
### 테스트
당신은 당신의 컨트롤러들을 생성주기를 포함하여 다른 어떤 클래스처럼 테스트할 수 있습니다 :
```dart
class Controller extends GetxController {
@override
void onInit() {
super.onInit();
//name2로 값 변경
name.value = 'name2';
}
@override
void onClose() {
name.value = '';
super.onClose();
}
final name = 'name1'.obs;
void changeName() => name.value = 'name3';
}
void main() {
test('''
Test the state of the reactive variable "name" across all of its lifecycles''',
() {
/// 당신은 생성주기를 제외하고 컨트롤러를 테스트할 수 있습니다,
/// 그러나 당신이 사용하지 않는다면 추천되지 않습니다
/// GetX 종속성 주입
final controller = Controller();
expect(controller.name.value, 'name1');
/// 당신이 그것을 사용한다면, 당신은 모든 것을 테스트할 수 있습니다,
/// 각각의 생성주기 이후 어플리케이션의 상태를 포함하여.
Get.put(controller); // onInit was called
expect(controller.name.value, 'name2');
/// 당신의 함수를 테스트하세요
controller.changeName();
expect(controller.name.value, 'name3');
/// onClose 호출됨
Get.delete<Controller>();
expect(controller.name.value, '');
});
}
```
#### 팁들
##### Mockito 또는 mocktail
당신이 당신의 GetxController/GetxService를 모킹하려고 한다면, 당신은 GetxController를 extend 하고, Mock과 mixin 하라, 그렇게 되면
```dart
class NotificationServiceMock extends GetxService with Mock implements NotificationService {}
```
##### Get.reset() 사용하기
당신이 위젯 또는 테스트 그룹을 테스트하고 있다면, 당신의 테스트의 마지막 또는 해제 때 당신의 이전 테스트에서 모든 설정을 리셋하기 위해 Get.rest을 사용하십시오
##### Get.testMode
당신이 당신의 컨트롤러에서 당신의 네비게이션을 사용하고 있다면, 당신의 메인의 시작에 `Get.testMode = true` 를 사용하십시오.
# 2.0의 주요 변경점
1- Rx 타입들:
... ...
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N)
... ...
... ... @@ -3,6 +3,9 @@
*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)*
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N)
... ...
... ... @@ -3,7 +3,9 @@
**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)**
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N)
... ...
... ... @@ -3,6 +3,9 @@
_Языки: Русский (этот файл), [вьетнамский](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)._
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N)
... ...
... ... @@ -3,7 +3,9 @@
**🌎 اردو ( 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)**
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N)
... ...
... ... @@ -3,6 +3,9 @@
_语言: 中文, [英文](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)._
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](https://pub.dev/packages/get/score)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N)
... ...
// ignore_for_file: file_names
const Map<String, String> en_US = {
'covid': 'Corona Virus',
'total_confirmed': 'Total Confirmed',
... ...
// ignore_for_file: file_names
const Map<String, String> pt_BR = {
'covid': 'Corona Vírus',
'total_confirmed': 'Total confirmado',
... ... @@ -7,4 +9,4 @@ const Map<String, String> pt_BR = {
'total_infecteds': 'Total de infectados',
'details': 'Detalhes',
'total_recovered': 'Total de recuperados'
};
\ No newline at end of file
};
... ...
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'en_US.dart';
import 'pt_BR.dart';
import 'en_us.dart';
import 'pt_br.dart';
class TranslationService extends Translations {
static Locale? get locale => Get.deviceLocale;
... ...
... ... @@ -21,6 +21,12 @@ class HomeView extends GetView<HomeController> {
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar('title', 'message');
},
),
title: Text('covid'.tr),
backgroundColor: Colors.white10,
elevation: 0,
... ...
... ... @@ -3,7 +3,6 @@ import 'package:get/get.dart';
import '../controllers/dashboard_controller.dart';
class DashboardView extends GetView<DashboardController> {
@override
Widget build(BuildContext context) {
... ...
// ignore_for_file: non_constant_identifier_names
part of 'app_pages.dart';
// DO NOT EDIT. This is code generated via package:get_cli/get_cli.dart
abstract class Routes {
Routes._();
static const HOME = _Paths.HOME;
static const PROFILE = _Paths.HOME + _Paths.PROFILE;
static const PROFILE = _Paths.HOME + _Paths.PROFILE;
static const SETTINGS = _Paths.SETTINGS;
static const PRODUCTS = _Paths.HOME + _Paths.PRODUCTS;
static String PRODUCT_DETAILS(String productId) => '$PRODUCTS/$productId';
static const LOGIN = _Paths.LOGIN;
static const DASHBOARD = _Paths.HOME + _Paths.DASHBOARD;
Routes._();
static String LOGIN_THEN(String afterSuccessfulLogin) =>
'$LOGIN?then=${Uri.encodeQueryComponent(afterSuccessfulLogin)}';
static const DASHBOARD = _Paths.HOME + _Paths.DASHBOARD;
static String PRODUCT_DETAILS(String productId) => '$PRODUCTS/$productId';
}
abstract class _Paths {
... ...
... ... @@ -115,6 +115,7 @@ class GetConnect extends GetConnectInterface {
Decoder? defaultDecoder;
Duration timeout;
List<TrustedCertificate>? trustedCertificates;
String Function(Uri url)? findProxy;
GetHttpClient? _httpClient;
List<GetSocket>? _sockets;
bool withCredentials;
... ... @@ -134,6 +135,7 @@ class GetConnect extends GetConnectInterface {
baseUrl: baseUrl,
trustedCertificates: trustedCertificates,
withCredentials: withCredentials,
findProxy: findProxy
);
@override
... ...
... ... @@ -39,6 +39,8 @@ class GetHttpClient {
final GetModifier _modifier;
String Function(Uri url)? findProxy;
GetHttpClient({
this.userAgent = 'getx-client',
this.timeout = const Duration(seconds: 8),
... ... @@ -50,10 +52,12 @@ class GetHttpClient {
this.baseUrl,
List<TrustedCertificate>? trustedCertificates,
bool withCredentials = false,
String Function(Uri url)? findProxy,
}) : _httpClient = HttpRequestImpl(
allowAutoSignedCert: allowAutoSignedCert,
trustedCertificates: trustedCertificates,
withCredentials: withCredentials,
findProxy: findProxy,
),
_modifier = GetModifier();
... ... @@ -195,7 +199,6 @@ class GetHttpClient {
int requestNumber = 1,
Map<String, String>? headers,
}) async {
try {
var request = await handler();
headers?.forEach((key, value) {
... ... @@ -206,6 +209,7 @@ class GetHttpClient {
final newRequest = await _modifier.modifyRequest<T>(request);
_httpClient.timeout = timeout;
try {
var response = await _httpClient.send<T>(newRequest);
final newResponse =
... ... @@ -242,7 +246,7 @@ class GetHttpClient {
throw GetHttpException(err.toString());
} else {
return Response<T>(
request: null,
request: newRequest,
headers: null,
statusCode: null,
body: null,
... ... @@ -268,6 +272,8 @@ class GetHttpClient {
headers: headers,
decoder: decoder ?? (defaultDecoder as Decoder<T>?),
contentLength: 0,
followRedirects: followRedirects,
maxRedirects: maxRedirects,
));
}
... ...
... ... @@ -17,6 +17,7 @@ class HttpRequestImpl extends HttpRequestBase {
bool allowAutoSignedCert = true,
List<TrustedCertificate>? trustedCertificates,
bool withCredentials = false,
String Function(Uri url)? findProxy,
}) {
_httpClient = io.HttpClient();
if (trustedCertificates != null) {
... ... @@ -29,6 +30,7 @@ class HttpRequestImpl extends HttpRequestBase {
_httpClient = io.HttpClient(context: _securityContext);
_httpClient!.badCertificateCallback = (_, __, ___) => allowAutoSignedCert;
_httpClient!.findProxy = findProxy;
}
@override
... ...
... ... @@ -8,6 +8,7 @@ class HttpRequestImpl extends HttpRequestBase {
bool allowAutoSignedCert = true,
List<TrustedCertificate>? trustedCertificates,
bool withCredentials = false,
String Function(Uri url)? findProxy,
});
@override
void close() {}
... ...
... ... @@ -138,7 +138,11 @@ class HeaderValue {
stringBuffer.write(_value);
if (parameters != null && parameters!.isNotEmpty) {
_parameters!.forEach((name, value) {
stringBuffer..write('; ')..write(name)..write('=')..write(value);
stringBuffer
..write('; ')
..write(name)
..write('=')
..write(value);
});
}
return stringBuffer.toString();
... ...
import 'dart:convert';
/// Signature for [SocketNotifier.addCloses].
typedef CloseSocket = void Function(Close);
/// Signature for [SocketNotifier.addMessages].
typedef MessageSocket = void Function(dynamic val);
/// Signature for [SocketNotifier.open].
typedef OpenSocket = void Function();
/// Wrapper class to message and reason from SocketNotifier
class Close {
final String? message;
final int? reason;
... ... @@ -12,12 +22,8 @@ class Close {
}
}
typedef OpenSocket = void Function();
typedef CloseSocket = void Function(Close);
typedef MessageSocket = void Function(dynamic val);
/// This class manages the transmission of messages over websockets using
/// GetConnect
class SocketNotifier {
List<void Function(dynamic)>? _onMessages = <MessageSocket>[];
Map<String, void Function(dynamic)>? _onEvents = <String, MessageSocket>{};
... ... @@ -26,22 +32,42 @@ class SocketNotifier {
late OpenSocket open;
void addMessages(MessageSocket socket) {
_onMessages!.add((socket));
/// subscribe to close events
void addCloses(CloseSocket socket) {
_onCloses!.add(socket);
}
/// subscribe to error events
void addErrors(CloseSocket socket) {
_onErrors!.add((socket));
}
/// subscribe to named events
void addEvents(String event, MessageSocket socket) {
_onEvents![event] = socket;
}
void addCloses(CloseSocket socket) {
_onCloses!.add(socket);
/// subscribe to message events
void addMessages(MessageSocket socket) {
_onMessages!.add((socket));
}
void addErrors(CloseSocket socket) {
_onErrors!.add((socket));
/// Dispose messages, events, closes and errors subscriptions
void dispose() {
_onMessages = null;
_onEvents = null;
_onCloses = null;
_onErrors = null;
}
/// Notify all subscriptions on [addCloses]
void notifyClose(Close err) {
for (var item in _onCloses!) {
item(err);
}
}
/// Notify all subscriptions on [addMessages]
void notifyData(dynamic data) {
for (var item in _onMessages!) {
item(data);
... ... @@ -51,12 +77,7 @@ class SocketNotifier {
}
}
void notifyClose(Close err) {
for (var item in _onCloses!) {
item(err);
}
}
/// Notify all subscriptions on [addErrors]
void notifyError(Close err) {
// rooms.removeWhere((key, value) => value.contains(_ws));
for (var item in _onErrors!) {
... ... @@ -72,15 +93,9 @@ class SocketNotifier {
if (_onEvents!.containsKey(event)) {
_onEvents![event]!(data);
}
// ignore: avoid_catches_without_on_clauses
} catch (_) {
return;
}
}
void dispose() {
_onMessages = null;
_onEvents = null;
_onCloses = null;
_onErrors = null;
}
}
... ...
... ... @@ -4,15 +4,8 @@ import 'dart:convert';
import 'dart:html';
import '../../../get_core/get_core.dart';
import 'socket_notifier.dart';
enum ConnectionStatus {
connecting,
connected,
closed,
}
class BaseWebSocket {
String url;
WebSocket? socket;
... ... @@ -21,6 +14,8 @@ class BaseWebSocket {
bool isDisposed = false;
bool allowSelfSigned;
ConnectionStatus? connectionStatus;
Timer? _t;
BaseWebSocket(
this.url, {
this.ping = const Duration(seconds: 5),
... ... @@ -30,9 +25,12 @@ class BaseWebSocket {
? url.replaceAll('https:', 'wss:')
: url.replaceAll('http:', 'ws:');
}
ConnectionStatus? connectionStatus;
Timer? _t;
void close([int? status, String? reason]) {
socket?.close(status, reason);
}
// ignore: use_setters_to_change_properties
void connect() {
try {
connectionStatus = ConnectionStatus.connecting;
... ... @@ -68,9 +66,18 @@ class BaseWebSocket {
}
}
// ignore: use_setters_to_change_properties
void onOpen(OpenSocket fn) {
socketNotifier!.open = fn;
void dispose() {
socketNotifier!.dispose();
socketNotifier = null;
isDisposed = true;
}
void emit(String event, dynamic data) {
send(jsonEncode({'type': event, 'data': data}));
}
void on(String event, MessageSocket message) {
socketNotifier!.addEvents(event, message);
}
void onClose(CloseSocket fn) {
... ... @@ -85,12 +92,9 @@ class BaseWebSocket {
socketNotifier!.addMessages(fn);
}
void on(String event, MessageSocket message) {
socketNotifier!.addEvents(event, message);
}
void close([int? status, String? reason]) {
socket?.close(status, reason);
// ignore: use_setters_to_change_properties
void onOpen(OpenSocket fn) {
socketNotifier!.open = fn;
}
void send(dynamic data) {
... ... @@ -103,14 +107,10 @@ class BaseWebSocket {
Get.log('WebSocket not connected, message $data not sent');
}
}
}
void emit(String event, dynamic data) {
send(jsonEncode({'type': event, 'data': data}));
}
void dispose() {
socketNotifier!.dispose();
socketNotifier = null;
isDisposed = true;
}
enum ConnectionStatus {
connecting,
connected,
closed,
}
... ...
... ... @@ -4,30 +4,28 @@ import 'dart:io';
import 'dart:math';
import '../../../get_core/get_core.dart';
import 'socket_notifier.dart';
enum ConnectionStatus {
connecting,
connected,
closed,
}
class BaseWebSocket {
String url;
WebSocket? socket;
SocketNotifier? socketNotifier = SocketNotifier();
bool isDisposed = false;
Duration ping;
bool allowSelfSigned;
ConnectionStatus? connectionStatus;
BaseWebSocket(
this.url, {
this.ping = const Duration(seconds: 5),
this.allowSelfSigned = true,
});
Duration ping;
bool allowSelfSigned;
ConnectionStatus? connectionStatus;
void close([int? status, String? reason]) {
socket?.close(status, reason);
}
// ignore: use_setters_to_change_properties
Future connect() async {
if (isDisposed) {
socketNotifier = SocketNotifier();
... ... @@ -60,9 +58,18 @@ class BaseWebSocket {
}
}
// ignore: use_setters_to_change_properties
void onOpen(OpenSocket fn) {
socketNotifier!.open = fn;
void dispose() {
socketNotifier!.dispose();
socketNotifier = null;
isDisposed = true;
}
void emit(String event, dynamic data) {
send(jsonEncode({'type': event, 'data': data}));
}
void on(String event, MessageSocket message) {
socketNotifier!.addEvents(event, message);
}
void onClose(CloseSocket fn) {
... ... @@ -77,12 +84,9 @@ class BaseWebSocket {
socketNotifier!.addMessages(fn);
}
void on(String event, MessageSocket message) {
socketNotifier!.addEvents(event, message);
}
void close([int? status, String? reason]) {
socket?.close(status, reason);
// ignore: use_setters_to_change_properties
void onOpen(OpenSocket fn) {
socketNotifier!.open = fn;
}
void send(dynamic data) async {
... ... @@ -95,16 +99,6 @@ class BaseWebSocket {
}
}
void dispose() {
socketNotifier!.dispose();
socketNotifier = null;
isDisposed = true;
}
void emit(String event, dynamic data) {
send(jsonEncode({'type': event, 'data': data}));
}
Future<WebSocket> _connectForSelfSignedCert(String url) async {
try {
var r = Random();
... ... @@ -136,3 +130,9 @@ class BaseWebSocket {
}
}
}
enum ConnectionStatus {
connecting,
connected,
closed,
}
... ...
... ... @@ -239,7 +239,7 @@ class GetInstance {
final newKey = key ?? _getKey(S, tag);
if (_singl.containsKey(newKey)) {
final dep = _singl[newKey];
if (dep != null) {
if (dep != null && !dep.permanent) {
dep.isDirty = true;
}
}
... ...
... ... @@ -16,5 +16,5 @@ export 'src/routes/get_route.dart';
export 'src/routes/observers/route_observer.dart';
export 'src/routes/route_middleware.dart';
export 'src/routes/transitions_type.dart';
export 'src/snackbar/snack.dart';
export 'src/snackbar/snack_route.dart';
export 'src/snackbar/snackbar.dart';
export 'src/snackbar/snackbar_controller.dart';
... ...
... ... @@ -11,243 +11,56 @@ import 'dialog/dialog_route.dart';
import 'root/parse_route.dart';
import 'root/root_controller.dart';
import 'routes/transitions_type.dart';
import 'snackbar/snackbar_controller.dart';
extension ExtensionSnackbar on GetInterface {
void rawSnackbar({
String? title,
String? message,
Widget? titleText,
Widget? messageText,
Widget? icon,
bool instantInit = true,
bool shouldIconPulse = true,
double? maxWidth,
EdgeInsets margin = const EdgeInsets.all(0.0),
EdgeInsets padding = const EdgeInsets.all(16),
double borderRadius = 0.0,
Color? borderColor,
double borderWidth = 1.0,
Color backgroundColor = const Color(0xFF303030),
Color? leftBarIndicatorColor,
List<BoxShadow>? boxShadows,
Gradient? backgroundGradient,
Widget? mainButton,
OnTap? onTap,
Duration duration = const Duration(seconds: 3),
bool isDismissible = true,
SnackDismissDirection dismissDirection = SnackDismissDirection.VERTICAL,
bool showProgressIndicator = false,
AnimationController? progressIndicatorController,
Color? progressIndicatorBackgroundColor,
Animation<Color>? progressIndicatorValueColor,
SnackPosition snackPosition = SnackPosition.BOTTOM,
SnackStyle snackStyle = SnackStyle.FLOATING,
Curve forwardAnimationCurve = Curves.easeOutCirc,
Curve reverseAnimationCurve = Curves.easeOutCirc,
Duration animationDuration = const Duration(seconds: 1),
SnackbarStatusCallback? snackbarStatus,
double? barBlur = 0.0,
double overlayBlur = 0.0,
Color? overlayColor,
Form? userInputForm,
}) async {
final getBar = GetBar(
snackbarStatus: snackbarStatus,
title: title,
message: message,
titleText: titleText,
messageText: messageText,
snackPosition: snackPosition,
borderRadius: borderRadius,
margin: margin,
duration: duration,
barBlur: barBlur,
backgroundColor: backgroundColor,
icon: icon,
shouldIconPulse: shouldIconPulse,
maxWidth: maxWidth,
padding: padding,
borderColor: borderColor,
borderWidth: borderWidth,
leftBarIndicatorColor: leftBarIndicatorColor,
boxShadows: boxShadows,
backgroundGradient: backgroundGradient,
mainButton: mainButton,
onTap: onTap,
isDismissible: isDismissible,
dismissDirection: dismissDirection,
showProgressIndicator: showProgressIndicator,
progressIndicatorController: progressIndicatorController,
progressIndicatorBackgroundColor: progressIndicatorBackgroundColor,
progressIndicatorValueColor: progressIndicatorValueColor,
snackStyle: snackStyle,
forwardAnimationCurve: forwardAnimationCurve,
reverseAnimationCurve: reverseAnimationCurve,
animationDuration: animationDuration,
overlayBlur: overlayBlur,
overlayColor: overlayColor,
userInputForm: userInputForm,
);
if (instantInit) {
getBar.show();
} else {
SchedulerBinding.instance!.addPostFrameCallback((_) {
getBar.show();
});
}
}
Future<T?>? showSnackbar<T>(GetBar snackbar) {
return key.currentState?.push(SnackRoute<T>(snack: snackbar));
}
void snackbar<T>(
String title,
String message, {
Color? colorText,
Duration? duration,
/// It replaces the Flutter Navigator, but needs no context.
/// You can to use navigator.push(YourRoute()) rather
/// Navigator.push(context, YourRoute());
NavigatorState? get navigator => GetNavigation(Get).key.currentState;
/// with instantInit = false you can put snackbar on initState
bool instantInit = true,
SnackPosition? snackPosition,
Widget? titleText,
Widget? messageText,
Widget? icon,
bool? shouldIconPulse,
double? maxWidth,
EdgeInsets? margin,
EdgeInsets? padding,
double? borderRadius,
Color? borderColor,
double? borderWidth,
extension ExtensionBottomSheet on GetInterface {
Future<T?> bottomSheet<T>(
Widget bottomsheet, {
Color? backgroundColor,
Color? leftBarIndicatorColor,
List<BoxShadow>? boxShadows,
Gradient? backgroundGradient,
TextButton? mainButton,
OnTap? onTap,
bool? isDismissible,
bool? showProgressIndicator,
SnackDismissDirection? dismissDirection,
AnimationController? progressIndicatorController,
Color? progressIndicatorBackgroundColor,
Animation<Color>? progressIndicatorValueColor,
SnackStyle? snackStyle,
Curve? forwardAnimationCurve,
Curve? reverseAnimationCurve,
Duration? animationDuration,
double? barBlur,
double? overlayBlur,
SnackbarStatusCallback? snackbarStatus,
Color? overlayColor,
Form? userInputForm,
}) async {
final getBar = GetBar(
snackbarStatus: snackbarStatus,
titleText: titleText ??
Text(
title,
style: TextStyle(
color: colorText ?? iconColor ?? Colors.black,
fontWeight: FontWeight.w800,
fontSize: 16,
),
),
messageText: messageText ??
Text(
message,
style: TextStyle(
color: colorText ?? iconColor ?? Colors.black,
fontWeight: FontWeight.w300,
fontSize: 14,
),
),
snackPosition: snackPosition ?? SnackPosition.TOP,
borderRadius: borderRadius ?? 15,
margin: margin ?? EdgeInsets.symmetric(horizontal: 10),
duration: duration ?? Duration(seconds: 3),
barBlur: barBlur ?? 7.0,
backgroundColor: backgroundColor ?? Colors.grey.withOpacity(0.2),
icon: icon,
shouldIconPulse: shouldIconPulse ?? true,
maxWidth: maxWidth,
padding: padding ?? EdgeInsets.all(16),
borderColor: borderColor,
borderWidth: borderWidth,
leftBarIndicatorColor: leftBarIndicatorColor,
boxShadows: boxShadows,
backgroundGradient: backgroundGradient,
mainButton: mainButton,
onTap: onTap,
isDismissible: isDismissible ?? true,
dismissDirection: dismissDirection ?? SnackDismissDirection.VERTICAL,
showProgressIndicator: showProgressIndicator ?? false,
progressIndicatorController: progressIndicatorController,
progressIndicatorBackgroundColor: progressIndicatorBackgroundColor,
progressIndicatorValueColor: progressIndicatorValueColor,
snackStyle: snackStyle ?? SnackStyle.FLOATING,
forwardAnimationCurve: forwardAnimationCurve ?? Curves.easeOutCirc,
reverseAnimationCurve: reverseAnimationCurve ?? Curves.easeOutCirc,
animationDuration: animationDuration ?? Duration(seconds: 1),
overlayBlur: overlayBlur ?? 0.0,
overlayColor: overlayColor ?? Colors.transparent,
userInputForm: userInputForm);
if (instantInit) {
showSnackbar<T>(getBar);
} else {
routing.isSnackbar = true;
SchedulerBinding.instance!.addPostFrameCallback((_) {
showSnackbar<T>(getBar);
});
}
}
}
extension OverlayExt on GetInterface {
Future<T> showOverlay<T>({
required Future<T> Function() asyncFunction,
Color opacityColor = Colors.black,
Widget? loadingWidget,
double opacity = .5,
}) async {
final navigatorState =
Navigator.of(Get.overlayContext!, rootNavigator: false);
final overlayState = navigatorState.overlay!;
final overlayEntryOpacity = OverlayEntry(builder: (context) {
return Opacity(
opacity: opacity,
child: Container(
color: opacityColor,
));
});
final overlayEntryLoader = OverlayEntry(builder: (context) {
return loadingWidget ??
Center(
child: Container(
height: 90,
width: 90,
child: Text('Loading...'),
));
});
overlayState.insert(overlayEntryOpacity);
overlayState.insert(overlayEntryLoader);
T data;
double? elevation,
bool persistent = true,
ShapeBorder? shape,
Clip? clipBehavior,
Color? barrierColor,
bool? ignoreSafeArea,
bool isScrollControlled = false,
bool useRootNavigator = false,
bool isDismissible = true,
bool enableDrag = true,
RouteSettings? settings,
Duration? enterBottomSheetDuration,
Duration? exitBottomSheetDuration,
}) {
return Navigator.of(overlayContext!, rootNavigator: useRootNavigator)
.push(GetModalBottomSheetRoute<T>(
builder: (_) => bottomsheet,
isPersistent: persistent,
// theme: Theme.of(key.currentContext, shadowThemeOnly: true),
theme: Theme.of(key.currentContext!),
isScrollControlled: isScrollControlled,
try {
data = await asyncFunction();
} on Exception catch (_) {
overlayEntryLoader.remove();
overlayEntryOpacity.remove();
rethrow;
}
barrierLabel: MaterialLocalizations.of(key.currentContext!)
.modalBarrierDismissLabel,
overlayEntryLoader.remove();
overlayEntryOpacity.remove();
return data;
backgroundColor: backgroundColor ?? Colors.transparent,
elevation: elevation,
shape: shape,
removeTop: ignoreSafeArea ?? true,
clipBehavior: clipBehavior,
isDismissible: isDismissible,
modalBarrierColor: barrierColor,
settings: settings,
enableDrag: enableDrag,
enterBottomSheetDuration:
enterBottomSheetDuration ?? const Duration(milliseconds: 250),
exitBottomSheetDuration:
exitBottomSheetDuration ?? const Duration(milliseconds: 200),
));
}
}
... ... @@ -378,7 +191,7 @@ extension ExtensionDialog on GetInterface {
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 8),
shape: RoundedRectangleBorder(
side: BorderSide(
color: buttonColor ?? theme.accentColor,
color: buttonColor ?? theme.colorScheme.secondary,
width: 2,
style: BorderStyle.solid),
borderRadius: BorderRadius.circular(100)),
... ... @@ -389,7 +202,8 @@ extension ExtensionDialog on GetInterface {
},
child: Text(
textCancel ?? "Cancel",
style: TextStyle(color: cancelTextColor ?? theme.accentColor),
style: TextStyle(
color: cancelTextColor ?? theme.colorScheme.secondary),
),
));
}
... ... @@ -401,7 +215,7 @@ extension ExtensionDialog on GetInterface {
actions.add(TextButton(
style: TextButton.styleFrom(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
backgroundColor: buttonColor ?? theme.accentColor,
backgroundColor: buttonColor ?? theme.colorScheme.secondary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100)),
),
... ... @@ -416,94 +230,249 @@ extension ExtensionDialog on GetInterface {
}
}
Widget baseAlertDialog = AlertDialog(
titlePadding: titlePadding ?? EdgeInsets.all(8),
contentPadding: contentPadding ?? EdgeInsets.all(8),
Widget baseAlertDialog = AlertDialog(
titlePadding: titlePadding ?? EdgeInsets.all(8),
contentPadding: contentPadding ?? EdgeInsets.all(8),
backgroundColor: backgroundColor ?? theme.dialogBackgroundColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(radius))),
title: Text(title, textAlign: TextAlign.center, style: titleStyle),
content: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
content ??
Text(middleText,
textAlign: TextAlign.center, style: middleTextStyle),
SizedBox(height: 16),
ButtonTheme(
minWidth: 78.0,
height: 34.0,
child: Wrap(
alignment: WrapAlignment.center,
spacing: 8,
runSpacing: 8,
children: actions,
),
)
],
),
// actions: actions, // ?? <Widget>[cancelButton, confirmButton],
buttonPadding: EdgeInsets.zero,
);
return dialog<T>(
onWillPop != null
? WillPopScope(
onWillPop: onWillPop,
child: baseAlertDialog,
)
: baseAlertDialog,
barrierDismissible: barrierDismissible,
navigatorKey: navigatorKey,
);
}
}
extension ExtensionSnackbar on GetInterface {
SnackbarController rawSnackbar({
String? title,
String? message,
Widget? titleText,
Widget? messageText,
Widget? icon,
bool instantInit = true,
bool shouldIconPulse = true,
double? maxWidth,
EdgeInsets margin = const EdgeInsets.all(0.0),
EdgeInsets padding = const EdgeInsets.all(16),
double borderRadius = 0.0,
Color? borderColor,
double borderWidth = 1.0,
Color backgroundColor = const Color(0xFF303030),
Color? leftBarIndicatorColor,
List<BoxShadow>? boxShadows,
Gradient? backgroundGradient,
Widget? mainButton,
OnTap? onTap,
Duration? duration = const Duration(seconds: 3),
bool isDismissible = true,
DismissDirection? dismissDirection,
bool showProgressIndicator = false,
AnimationController? progressIndicatorController,
Color? progressIndicatorBackgroundColor,
Animation<Color>? progressIndicatorValueColor,
SnackPosition snackPosition = SnackPosition.BOTTOM,
SnackStyle snackStyle = SnackStyle.FLOATING,
Curve forwardAnimationCurve = Curves.easeOutCirc,
Curve reverseAnimationCurve = Curves.easeOutCirc,
Duration animationDuration = const Duration(seconds: 1),
SnackbarStatusCallback? snackbarStatus,
double barBlur = 0.0,
double overlayBlur = 0.0,
Color? overlayColor,
Form? userInputForm,
}) {
final getSnackBar = GetSnackBar(
snackbarStatus: snackbarStatus,
title: title,
message: message,
titleText: titleText,
messageText: messageText,
snackPosition: snackPosition,
borderRadius: borderRadius,
margin: margin,
duration: duration,
barBlur: barBlur,
backgroundColor: backgroundColor,
icon: icon,
shouldIconPulse: shouldIconPulse,
maxWidth: maxWidth,
padding: padding,
borderColor: borderColor,
borderWidth: borderWidth,
leftBarIndicatorColor: leftBarIndicatorColor,
boxShadows: boxShadows,
backgroundGradient: backgroundGradient,
mainButton: mainButton,
onTap: onTap,
isDismissible: isDismissible,
dismissDirection: dismissDirection,
showProgressIndicator: showProgressIndicator,
progressIndicatorController: progressIndicatorController,
progressIndicatorBackgroundColor: progressIndicatorBackgroundColor,
progressIndicatorValueColor: progressIndicatorValueColor,
snackStyle: snackStyle,
forwardAnimationCurve: forwardAnimationCurve,
reverseAnimationCurve: reverseAnimationCurve,
animationDuration: animationDuration,
overlayBlur: overlayBlur,
overlayColor: overlayColor,
userInputForm: userInputForm,
);
final controller = SnackbarController(getSnackBar);
backgroundColor: backgroundColor ?? theme.dialogBackgroundColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(radius))),
title: Text(title, textAlign: TextAlign.center, style: titleStyle),
content: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
content ??
Text(middleText,
textAlign: TextAlign.center, style: middleTextStyle),
SizedBox(height: 16),
ButtonTheme(
minWidth: 78.0,
height: 34.0,
child: Wrap(
alignment: WrapAlignment.center,
spacing: 8,
runSpacing: 8,
children: actions,
),
)
],
),
// actions: actions, // ?? <Widget>[cancelButton, confirmButton],
buttonPadding: EdgeInsets.zero,
);
if (instantInit) {
controller.show();
} else {
SchedulerBinding.instance!.addPostFrameCallback((_) {
controller.show();
});
}
return controller;
}
return dialog<T>(
onWillPop != null
? WillPopScope(
onWillPop: onWillPop,
child: baseAlertDialog,
)
: baseAlertDialog,
barrierDismissible: barrierDismissible,
navigatorKey: navigatorKey,
);
SnackbarController showSnackbar(GetSnackBar snackbar) {
final controller = SnackbarController(snackbar);
controller.show();
return controller;
}
}
extension ExtensionBottomSheet on GetInterface {
Future<T?> bottomSheet<T>(
Widget bottomsheet, {
SnackbarController snackbar(
String title,
String message, {
Color? colorText,
Duration? duration = const Duration(seconds: 3),
/// with instantInit = false you can put snackbar on initState
bool instantInit = true,
SnackPosition? snackPosition,
Widget? titleText,
Widget? messageText,
Widget? icon,
bool? shouldIconPulse,
double? maxWidth,
EdgeInsets? margin,
EdgeInsets? padding,
double? borderRadius,
Color? borderColor,
double? borderWidth,
Color? backgroundColor,
double? elevation,
bool persistent = true,
ShapeBorder? shape,
Clip? clipBehavior,
Color? barrierColor,
bool? ignoreSafeArea,
bool isScrollControlled = false,
bool useRootNavigator = false,
bool isDismissible = true,
bool enableDrag = true,
RouteSettings? settings,
Duration? enterBottomSheetDuration,
Duration? exitBottomSheetDuration,
Color? leftBarIndicatorColor,
List<BoxShadow>? boxShadows,
Gradient? backgroundGradient,
TextButton? mainButton,
OnTap? onTap,
bool? isDismissible,
bool? showProgressIndicator,
DismissDirection? dismissDirection,
AnimationController? progressIndicatorController,
Color? progressIndicatorBackgroundColor,
Animation<Color>? progressIndicatorValueColor,
SnackStyle? snackStyle,
Curve? forwardAnimationCurve,
Curve? reverseAnimationCurve,
Duration? animationDuration,
double? barBlur,
double? overlayBlur,
SnackbarStatusCallback? snackbarStatus,
Color? overlayColor,
Form? userInputForm,
}) {
return Navigator.of(overlayContext!, rootNavigator: useRootNavigator)
.push(GetModalBottomSheetRoute<T>(
builder: (_) => bottomsheet,
isPersistent: persistent,
// theme: Theme.of(key.currentContext, shadowThemeOnly: true),
theme: Theme.of(key.currentContext!),
isScrollControlled: isScrollControlled,
final getSnackBar = GetSnackBar(
snackbarStatus: snackbarStatus,
titleText: titleText ??
Text(
title,
style: TextStyle(
color: colorText ?? iconColor ?? Colors.black,
fontWeight: FontWeight.w800,
fontSize: 16,
),
),
messageText: messageText ??
Text(
message,
style: TextStyle(
color: colorText ?? iconColor ?? Colors.black,
fontWeight: FontWeight.w300,
fontSize: 14,
),
),
snackPosition: snackPosition ?? SnackPosition.TOP,
borderRadius: borderRadius ?? 15,
margin: margin ?? EdgeInsets.symmetric(horizontal: 10),
duration: duration,
barBlur: barBlur ?? 7.0,
backgroundColor: backgroundColor ?? Colors.grey.withOpacity(0.2),
icon: icon,
shouldIconPulse: shouldIconPulse ?? true,
maxWidth: maxWidth,
padding: padding ?? EdgeInsets.all(16),
borderColor: borderColor,
borderWidth: borderWidth,
leftBarIndicatorColor: leftBarIndicatorColor,
boxShadows: boxShadows,
backgroundGradient: backgroundGradient,
mainButton: mainButton,
onTap: onTap,
isDismissible: isDismissible ?? true,
dismissDirection: dismissDirection,
showProgressIndicator: showProgressIndicator ?? false,
progressIndicatorController: progressIndicatorController,
progressIndicatorBackgroundColor: progressIndicatorBackgroundColor,
progressIndicatorValueColor: progressIndicatorValueColor,
snackStyle: snackStyle ?? SnackStyle.FLOATING,
forwardAnimationCurve: forwardAnimationCurve ?? Curves.easeOutCirc,
reverseAnimationCurve: reverseAnimationCurve ?? Curves.easeOutCirc,
animationDuration: animationDuration ?? Duration(seconds: 1),
overlayBlur: overlayBlur ?? 0.0,
overlayColor: overlayColor ?? Colors.transparent,
userInputForm: userInputForm);
barrierLabel: MaterialLocalizations.of(key.currentContext!)
.modalBarrierDismissLabel,
final controller = SnackbarController(getSnackBar);
backgroundColor: backgroundColor ?? Colors.transparent,
elevation: elevation,
shape: shape,
removeTop: ignoreSafeArea ?? true,
clipBehavior: clipBehavior,
isDismissible: isDismissible,
modalBarrierColor: barrierColor,
settings: settings,
enableDrag: enableDrag,
enterBottomSheetDuration:
enterBottomSheetDuration ?? const Duration(milliseconds: 250),
exitBottomSheetDuration:
exitBottomSheetDuration ?? const Duration(milliseconds: 200),
));
if (instantInit) {
controller.show();
} else {
//routing.isSnackbar = true;
SchedulerBinding.instance!.addPostFrameCallback((_) {
controller.show();
});
}
return controller;
}
}
... ... @@ -826,11 +795,11 @@ you can only use widgets and widget functions here''';
/// Returns true if a Snackbar, Dialog or BottomSheet is currently OPEN
bool get isOverlaysOpen =>
(isSnackbarOpen! || isDialogOpen! || isBottomSheetOpen!);
(isSnackbarOpen || isDialogOpen! || isBottomSheetOpen!);
/// Returns true if there is no Snackbar, Dialog or BottomSheet open
bool get isOverlaysClosed =>
(!isSnackbarOpen! && !isDialogOpen! && !isBottomSheetOpen!);
(!isSnackbarOpen && !isDialogOpen! && !isBottomSheetOpen!);
/// **Navigation.popUntil()** shortcut.<br><br>
///
... ... @@ -850,9 +819,21 @@ you can only use widgets and widget functions here''';
bool canPop = true,
int? id,
}) {
//TODO: This code brings compatibility of the new snackbar with GetX 4,
// remove this code in version 5
if (isSnackbarOpen && !closeOverlays) {
closeCurrentSnackbar();
return;
}
if (closeOverlays && isOverlaysOpen) {
//TODO: This code brings compatibility of the new snackbar with GetX 4,
// remove this code in version 5
if (isSnackbarOpen) {
closeAllSnackbars();
}
navigator?.popUntil((route) {
return (isOverlaysClosed);
return (!isDialogOpen! && !isBottomSheetOpen!);
});
}
if (canPop) {
... ... @@ -1135,7 +1116,16 @@ you can only use widgets and widget functions here''';
String get previousRoute => routing.previous;
/// check if snackbar is open
bool? get isSnackbarOpen => routing.isSnackbar;
bool get isSnackbarOpen =>
SnackbarController.isSnackbarBeingShown; //routing.isSnackbar;
void closeAllSnackbars() {
SnackbarController.cancelAllSnackbars();
}
Future<void> closeCurrentSnackbar() async {
await SnackbarController.closeCurrentSnackbar();
}
/// check if dialog is open
bool? get isDialogOpen => routing.isDialog;
... ... @@ -1341,7 +1331,48 @@ extension NavTwoExt on GetInterface {
}
}
/// It replaces the Flutter Navigator, but needs no context.
/// You can to use navigator.push(YourRoute()) rather
/// Navigator.push(context, YourRoute());
NavigatorState? get navigator => GetNavigation(Get).key.currentState;
extension OverlayExt on GetInterface {
Future<T> showOverlay<T>({
required Future<T> Function() asyncFunction,
Color opacityColor = Colors.black,
Widget? loadingWidget,
double opacity = .5,
}) async {
final navigatorState =
Navigator.of(Get.overlayContext!, rootNavigator: false);
final overlayState = navigatorState.overlay!;
final overlayEntryOpacity = OverlayEntry(builder: (context) {
return Opacity(
opacity: opacity,
child: Container(
color: opacityColor,
));
});
final overlayEntryLoader = OverlayEntry(builder: (context) {
return loadingWidget ??
Center(
child: Container(
height: 90,
width: 90,
child: Text('Loading...'),
));
});
overlayState.insert(overlayEntryOpacity);
overlayState.insert(overlayEntryLoader);
T data;
try {
data = await asyncFunction();
} on Exception catch (_) {
overlayEntryLoader.remove();
overlayEntryOpacity.remove();
rethrow;
}
overlayEntryLoader.remove();
overlayEntryOpacity.remove();
return data;
}
}
... ...
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import '../../../get.dart';
class GetInformationParser extends RouteInformationParser<GetNavConfig> {
... ... @@ -14,7 +15,6 @@ class GetInformationParser extends RouteInformationParser<GetNavConfig> {
SynchronousFuture<GetNavConfig> parseRouteInformation(
RouteInformation routeInformation,
) {
Get.log('GetInformationParser: route location: ${routeInformation.location}');
var location = routeInformation.location;
if (location == '/') {
//check if there is a corresponding page
... ... @@ -24,6 +24,8 @@ class GetInformationParser extends RouteInformationParser<GetNavConfig> {
}
}
Get.log('GetInformationParser: route location: $location');
final matchResult = Get.routeTree.matchRoute(location ?? initialRoute);
return SynchronousFuture(
... ...
... ... @@ -2,50 +2,10 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../../get.dart';
import '../../../get_state_manager/src/simple/list_notifier.dart';
/// Enables the user to customize the intended pop behavior
///
/// Goes to either the previous history entry or the previous page entry
///
/// e.g. if the user navigates to these pages
/// 1) /home
/// 2) /home/products/1234
///
/// when popping on [History] mode, it will emulate a browser back button.
///
/// so the new history stack will be:
/// 1) /home
///
/// when popping on [Page] mode, it will only remove the last part of the route
/// so the new history stack will be:
/// 1) /home
/// 2) /home/products
///
/// another pop will change the history stack to:
/// 1) /home
enum PopMode {
History,
Page,
}
/// Enables the user to customize the behavior when pushing multiple routes that
/// shouldn't be duplicates
enum PreventDuplicateHandlingMode {
/// Removes the history entries until it reaches the old route
PopUntilOriginalRoute,
/// Simply don't push the new route
DoNothing,
/// Recommended - Moves the old route entry to the front
///
/// With this mode, you guarantee there will be only one
/// route entry for each location
ReorderRoutes
}
class GetDelegate extends RouterDelegate<GetNavConfig>
with ListenableMixin, ListNotifierMixin {
final List<GetNavConfig> history = <GetNavConfig>[];
... ... @@ -57,7 +17,7 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
final List<NavigatorObserver>? navigatorObservers;
final TransitionDelegate<dynamic>? transitionDelegate;
GlobalKey<NavigatorState> get navigatorKey => Get.key;
final _allCompleters = <GetPage, Completer>{};
GetDelegate({
GetPage? notFoundRoute,
... ... @@ -76,191 +36,72 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
Get.log('GetDelegate is created !');
}
Future<GetNavConfig?> runMiddleware(GetNavConfig config) async {
final middlewares = config.currentTreeBranch.last.middlewares;
if (middlewares == null) {
return config;
}
var iterator = config;
for (var item in middlewares) {
var redirectRes = await item.redirectDelegate(iterator);
if (redirectRes == null) return null;
iterator = redirectRes;
}
return iterator;
}
Future<void> _unsafeHistoryAdd(GetNavConfig config) async {
final res = await runMiddleware(config);
if (res == null) return;
history.add(res);
@override
GetNavConfig? get currentConfiguration {
if (history.isEmpty) return null;
final route = history.last;
return route;
}
Future<void> _unsafeHistoryRemove(GetNavConfig config) async {
var index = history.indexOf(config);
if (index >= 0) await _unsafeHistoryRemoveAt(index);
}
GlobalKey<NavigatorState> get navigatorKey => Get.key;
Future<GetNavConfig?> _unsafeHistoryRemoveAt(int index) async {
if (index == history.length - 1 && history.length > 1) {
//removing WILL update the current route
final toCheck = history[history.length - 2];
final resMiddleware = await runMiddleware(toCheck);
if (resMiddleware == null) return null;
history[history.length - 2] = resMiddleware;
}
return history.removeAt(index);
Map<String, String> get parameters {
return currentConfiguration?.currentPage?.parameters ?? {};
}
T arguments<T>() {
return currentConfiguration?.currentPage?.arguments as T;
}
Map<String, String> get parameters {
return currentConfiguration?.currentPage?.parameters ?? {};
}
// void _unsafeHistoryClear() {
// history.clear();
// }
/// Adds a new history entry and waits for the result
Future<void> pushHistory(
GetNavConfig config, {
bool rebuildStack = true,
/// Removes routes according to [PopMode]
/// until it reaches the specifc [fullRoute],
/// DOES NOT remove the [fullRoute]
Future<void> backUntil(
String fullRoute, {
PopMode popMode = PopMode.Page,
}) async {
//this changes the currentConfiguration
await _pushHistory(config);
if (rebuildStack) {
refresh();
// remove history or page entries until you meet route
var iterator = currentConfiguration;
while (_canPop(popMode) &&
iterator != null &&
iterator.location != fullRoute) {
await _pop(popMode);
// replace iterator
iterator = currentConfiguration;
}
refresh();
}
Future<void> _removeHistoryEntry(GetNavConfig entry) async {
await _unsafeHistoryRemove(entry);
}
Future<void> _pushHistory(GetNavConfig config) async {
if (config.currentPage!.preventDuplicates) {
final originalEntryIndex =
history.indexWhere((element) => element.location == config.location);
if (originalEntryIndex >= 0) {
switch (preventDuplicateHandlingMode) {
case PreventDuplicateHandlingMode.PopUntilOriginalRoute:
await backUntil(config.location!, popMode: PopMode.Page);
break;
case PreventDuplicateHandlingMode.ReorderRoutes:
await _unsafeHistoryRemoveAt(originalEntryIndex);
await _unsafeHistoryAdd(config);
break;
case PreventDuplicateHandlingMode.DoNothing:
default:
break;
}
return;
}
}
await _unsafeHistoryAdd(config);
@override
Widget build(BuildContext context) {
final pages = getVisualPages();
if (pages.length == 0) return SizedBox.shrink();
final extraObservers = navigatorObservers;
return GetNavigator(
key: navigatorKey,
onPopPage: _onPopVisualRoute,
pages: pages,
observers: [
GetObserver(),
if (extraObservers != null) ...extraObservers,
],
transitionDelegate:
transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(),
);
}
// GetPageRoute getPageRoute(RouteSettings? settings) {
// return PageRedirect(settings ?? RouteSettings(name: '/404'), _notFound())
// .page();
// void _unsafeHistoryClear() {
// history.clear();
// }
Future<GetNavConfig?> _popHistory() async {
if (!_canPopHistory()) return null;
return await _doPopHistory();
}
Future<GetNavConfig?> _doPopHistory() async {
return await _unsafeHistoryRemoveAt(history.length - 1);
}
Future<GetNavConfig?> _popPage() async {
if (!_canPopPage()) return null;
return await _doPopPage();
}
Future<GetNavConfig?> _pop(PopMode mode) async {
switch (mode) {
case PopMode.History:
return await _popHistory();
case PopMode.Page:
return await _popPage();
default:
return null;
}
}
// returns the popped page
Future<GetNavConfig?> _doPopPage() async {
final currentBranch = currentConfiguration?.currentTreeBranch;
if (currentBranch != null && currentBranch.length > 1) {
//remove last part only
final remaining = currentBranch.take(currentBranch.length - 1);
final prevHistoryEntry =
history.length > 1 ? history[history.length - 2] : null;
//check if current route is the same as the previous route
if (prevHistoryEntry != null) {
//if so, pop the entire history entry
final newLocation = remaining.last.name;
final prevLocation = prevHistoryEntry.location;
if (newLocation == prevLocation) {
//pop the entire history entry
return await _popHistory();
}
}
//create a new route with the remaining tree branch
final res = await _popHistory();
await _pushHistory(
GetNavConfig(
currentTreeBranch: remaining.toList(),
location: remaining.last.name,
state: null, //TOOD: persist state??
),
);
return res;
} else {
//remove entire entry
return await _popHistory();
}
}
Future<GetNavConfig?> popHistory() async {
return await _popHistory();
}
bool _canPopHistory() {
return history.length > 1;
}
Future<bool> canPopHistory() {
return SynchronousFuture(_canPopHistory());
}
bool _canPopPage() {
final currentTreeBranch = currentConfiguration?.currentTreeBranch;
if (currentTreeBranch == null) return false;
return currentTreeBranch.length > 1 ? true : _canPopHistory();
}
Future<bool> canPopPage() {
return SynchronousFuture(_canPopPage());
}
bool _canPop(PopMode mode) {
switch (mode) {
case PopMode.History:
return _canPopHistory();
case PopMode.Page:
default:
return _canPopPage();
}
}
/// gets the visual pages from the current history entry
///
/// visual pages must have [participatesInRootNavigator] set to true
... ... @@ -281,44 +122,104 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
}
}
// GetPageRoute getPageRoute(RouteSettings? settings) {
// return PageRedirect(settings ?? RouteSettings(name: '/404'), _notFound())
// .page();
// }
Future<bool> handlePopupRoutes({
Object? result,
}) async {
Route? currentRoute;
navigatorKey.currentState!.popUntil((route) {
currentRoute = route;
return true;
});
if (currentRoute is PopupRoute) {
return await navigatorKey.currentState!.maybePop(result);
}
return false;
}
Future<T?>? offAndToNamed<T>(
String page, {
dynamic arguments,
int? id,
dynamic result,
Map<String, String>? parameters,
PopMode popMode = PopMode.History,
}) async {
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
await popRoute(result: result);
return toNamed(page, arguments: arguments, parameters: parameters);
}
Future<T> offNamed<T>(
String page, {
dynamic arguments,
Map<String, String>? parameters,
}) async {
history.removeLast();
return toNamed<T>(page, arguments: arguments, parameters: parameters);
}
Future<GetNavConfig?> popHistory() async {
return await _popHistory();
}
// returns the popped page
@override
Widget build(BuildContext context) {
final pages = getVisualPages();
if (pages.length == 0) return SizedBox.shrink();
final extraObservers = navigatorObservers;
return GetNavigator(
key: navigatorKey,
onPopPage: _onPopVisualRoute,
pages: pages,
observers: [
GetObserver(),
if (extraObservers != null) ...extraObservers,
],
transitionDelegate:
transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(),
);
Future<bool> popRoute({
Object? result,
PopMode popMode = PopMode.Page,
}) async {
//Returning false will cause the entire app to be popped.
final wasPopup = await handlePopupRoutes(result: result);
if (wasPopup) return true;
final _popped = await _pop(popMode);
refresh();
if (_popped != null) {
//emulate the old pop with result
return true;
}
return false;
}
/// Adds a new history entry and waits for the result
Future<void> pushHistory(
GetNavConfig config, {
bool rebuildStack = true,
}) async {
//this changes the currentConfiguration
await _pushHistory(config);
if (rebuildStack) {
refresh();
}
}
Future<GetNavConfig?> runMiddleware(GetNavConfig config) async {
final middlewares = config.currentTreeBranch.last.middlewares;
if (middlewares == null) {
return config;
}
var iterator = config;
for (var item in middlewares) {
var redirectRes = await item.redirectDelegate(iterator);
if (redirectRes == null) return null;
iterator = redirectRes;
}
return iterator;
}
// @override
// Future<void> setInitialRoutePath(GetNavConfig configuration) async {
// //no need to clear history with Reorder route strategy
// // _unsafeHistoryClear();
// // _resultCompleter.clear();
// await pushHistory(configuration);
// }
@override
Future<void> setNewRoutePath(GetNavConfig configuration) async {
await pushHistory(configuration);
}
@override
GetNavConfig? get currentConfiguration {
if (history.isEmpty) return null;
final route = history.last;
return route;
}
Future<T> toNamed<T>(
String page, {
dynamic arguments,
... ... @@ -352,84 +253,73 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
}
}
Future<T?>? offAndToNamed<T>(
String page, {
dynamic arguments,
int? id,
dynamic result,
Map<String, String>? parameters,
PopMode popMode = PopMode.History,
}) async {
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
bool _canPop(PopMode mode) {
switch (mode) {
case PopMode.History:
return _canPopHistory();
case PopMode.Page:
default:
return _canPopPage();
}
await popRoute(result: result);
return toNamed(page, arguments: arguments, parameters: parameters);
}
Future<T> offNamed<T>(
String page, {
dynamic arguments,
Map<String, String>? parameters,
}) async {
history.removeLast();
return toNamed<T>(page, arguments: arguments, parameters: parameters);
bool _canPopHistory() {
return history.length > 1;
}
/// Removes routes according to [PopMode]
/// until it reaches the specifc [fullRoute],
/// DOES NOT remove the [fullRoute]
Future<void> backUntil(
String fullRoute, {
PopMode popMode = PopMode.Page,
}) async {
// remove history or page entries until you meet route
var iterator = currentConfiguration;
while (_canPop(popMode) &&
iterator != null &&
iterator.location != fullRoute) {
await _pop(popMode);
// replace iterator
iterator = currentConfiguration;
}
refresh();
bool _canPopPage() {
final currentTreeBranch = currentConfiguration?.currentTreeBranch;
if (currentTreeBranch == null) return false;
return currentTreeBranch.length > 1 ? true : _canPopHistory();
}
Future<bool> handlePopupRoutes({
Object? result,
}) async {
Route? currentRoute;
navigatorKey.currentState!.popUntil((route) {
currentRoute = route;
return true;
});
if (currentRoute is PopupRoute) {
return await navigatorKey.currentState!.maybePop(result);
}
return false;
Future<GetNavConfig?> _doPopHistory() async {
return await _unsafeHistoryRemoveAt(history.length - 1);
}
@override
Future<bool> popRoute({
Object? result,
PopMode popMode = PopMode.Page,
}) async {
//Returning false will cause the entire app to be popped.
final wasPopup = await handlePopupRoutes(result: result);
if (wasPopup) return true;
final _popped = await _pop(popMode);
refresh();
if (_popped != null) {
//emulate the old pop with result
return true;
// @override
// Future<void> setInitialRoutePath(GetNavConfig configuration) async {
// //no need to clear history with Reorder route strategy
// // _unsafeHistoryClear();
// // _resultCompleter.clear();
// await pushHistory(configuration);
// }
Future<GetNavConfig?> _doPopPage() async {
final currentBranch = currentConfiguration?.currentTreeBranch;
if (currentBranch != null && currentBranch.length > 1) {
//remove last part only
final remaining = currentBranch.take(currentBranch.length - 1);
final prevHistoryEntry =
history.length > 1 ? history[history.length - 2] : null;
//check if current route is the same as the previous route
if (prevHistoryEntry != null) {
//if so, pop the entire history entry
final newLocation = remaining.last.name;
final prevLocation = prevHistoryEntry.location;
if (newLocation == prevLocation) {
//pop the entire history entry
return await _popHistory();
}
}
//create a new route with the remaining tree branch
final res = await _popHistory();
await _pushHistory(
GetNavConfig(
currentTreeBranch: remaining.toList(),
location: remaining.last.name,
state: null, //TOOD: persist state??
),
);
return res;
} else {
//remove entire entry
return await _popHistory();
}
return false;
}
final _allCompleters = <GetPage, Completer>{};
bool _onPopVisualRoute(Route<dynamic> route, dynamic result) {
final didPop = route.didPop(result);
if (!didPop) {
... ... @@ -452,21 +342,89 @@ class GetDelegate extends RouterDelegate<GetNavConfig>
return true;
}
Future<GetNavConfig?> _pop(PopMode mode) async {
switch (mode) {
case PopMode.History:
return await _popHistory();
case PopMode.Page:
return await _popPage();
default:
return null;
}
}
Future<GetNavConfig?> _popHistory() async {
if (!_canPopHistory()) return null;
return await _doPopHistory();
}
Future<GetNavConfig?> _popPage() async {
if (!_canPopPage()) return null;
return await _doPopPage();
}
Future<void> _pushHistory(GetNavConfig config) async {
if (config.currentPage!.preventDuplicates) {
final originalEntryIndex =
history.indexWhere((element) => element.location == config.location);
if (originalEntryIndex >= 0) {
switch (preventDuplicateHandlingMode) {
case PreventDuplicateHandlingMode.PopUntilOriginalRoute:
await backUntil(config.location!, popMode: PopMode.Page);
break;
case PreventDuplicateHandlingMode.ReorderRoutes:
await _unsafeHistoryRemoveAt(originalEntryIndex);
await _unsafeHistoryAdd(config);
break;
case PreventDuplicateHandlingMode.DoNothing:
default:
break;
}
return;
}
}
await _unsafeHistoryAdd(config);
}
Future<void> _removeHistoryEntry(GetNavConfig entry) async {
await _unsafeHistoryRemove(entry);
}
Future<void> _unsafeHistoryAdd(GetNavConfig config) async {
final res = await runMiddleware(config);
if (res == null) return;
history.add(res);
}
Future<void> _unsafeHistoryRemove(GetNavConfig config) async {
var index = history.indexOf(config);
if (index >= 0) await _unsafeHistoryRemoveAt(index);
}
Future<GetNavConfig?> _unsafeHistoryRemoveAt(int index) async {
if (index == history.length - 1 && history.length > 1) {
//removing WILL update the current route
final toCheck = history[history.length - 2];
final resMiddleware = await runMiddleware(toCheck);
if (resMiddleware == null) return null;
history[history.length - 2] = resMiddleware;
}
return history.removeAt(index);
}
}
class GetNavigator extends Navigator {
GetNavigator(
{GlobalKey<NavigatorState>? key,
bool Function(Route<dynamic>, dynamic)? onPopPage,
required List<GetPage> pages,
List<NavigatorObserver>? observers,
bool reportsRouteUpdateToEngine = false,
TransitionDelegate? transitionDelegate,
String? initialRoute})
: super(
GetNavigator({
GlobalKey<NavigatorState>? key,
bool Function(Route<dynamic>, dynamic)? onPopPage,
required List<Page> pages,
List<NavigatorObserver>? observers,
bool reportsRouteUpdateToEngine = false,
TransitionDelegate? transitionDelegate,
}) : super(
//keys should be optional
key: key,
initialRoute: initialRoute ?? '/',
onPopPage: onPopPage ??
(route, result) {
final didPop = route.didPop(result);
... ... @@ -475,17 +433,6 @@ class GetNavigator extends Navigator {
}
return true;
},
onGenerateRoute: (RouteSettings settings) {
final selectedPageList =
pages.where((element) => element.name == settings.name);
if (selectedPageList.isNotEmpty) {
final selectedPage = selectedPageList.first;
return GetPageRoute(
page: selectedPage.page,
settings: settings,
);
}
},
reportsRouteUpdateToEngine: reportsRouteUpdateToEngine,
pages: pages,
observers: [
... ... @@ -496,3 +443,44 @@ class GetNavigator extends Navigator {
transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(),
);
}
/// Enables the user to customize the intended pop behavior
///
/// Goes to either the previous history entry or the previous page entry
///
/// e.g. if the user navigates to these pages
/// 1) /home
/// 2) /home/products/1234
///
/// when popping on [History] mode, it will emulate a browser back button.
///
/// so the new history stack will be:
/// 1) /home
///
/// when popping on [Page] mode, it will only remove the last part of the route
/// so the new history stack will be:
/// 1) /home
/// 2) /home/products
///
/// another pop will change the history stack to:
/// 1) /home
enum PopMode {
History,
Page,
}
/// Enables the user to customize the behavior when pushing multiple routes that
/// shouldn't be duplicates
enum PreventDuplicateHandlingMode {
/// Removes the history entries until it reaches the old route
PopUntilOriginalRoute,
/// Simply don't push the new route
DoNothing,
/// Recommended - Moves the old route entry to the front
///
/// With this mode, you guarantee there will be only one
/// route entry for each location
ReorderRoutes
}
... ...
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../../get_core/get_core.dart';
import '../../../get_instance/get_instance.dart';
import '../../../get_state_manager/get_state_manager.dart';
... ... @@ -9,6 +10,59 @@ import '../../get_navigation.dart';
import 'root_controller.dart';
class GetCupertinoApp extends StatelessWidget {
final GlobalKey<NavigatorState>? navigatorKey;
final Widget? home;
final Map<String, WidgetBuilder>? routes;
final String? initialRoute;
final RouteFactory? onGenerateRoute;
final InitialRouteListFactory? onGenerateInitialRoutes;
final RouteFactory? onUnknownRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionBuilder? builder;
final String title;
final GenerateAppTitle? onGenerateTitle;
final CustomTransition? customTransition;
final Color? color;
final Map<String, Map<String, String>>? translationsKeys;
final Translations? translations;
final TextDirection? textDirection;
final Locale? locale;
final Locale? fallbackLocale;
final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
final LocaleListResolutionCallback? localeListResolutionCallback;
final LocaleResolutionCallback? localeResolutionCallback;
final Iterable<Locale> supportedLocales;
final bool showPerformanceOverlay;
final bool checkerboardRasterCacheImages;
final bool checkerboardOffscreenLayers;
final bool showSemanticsDebugger;
final bool debugShowCheckedModeBanner;
final Map<LogicalKeySet, Intent>? shortcuts;
final ThemeData? highContrastTheme;
final ThemeData? highContrastDarkTheme;
final Map<Type, Action<Intent>>? actions;
final Function(Routing?)? routingCallback;
final Transition? defaultTransition;
final bool? opaqueRoute;
final VoidCallback? onInit;
final VoidCallback? onReady;
final VoidCallback? onDispose;
final bool? enableLog;
final LogWriterCallback? logWriterCallback;
final bool? popGesture;
final SmartManagement smartManagement;
final Bindings? initialBinding;
final Duration? transitionDuration;
final bool? defaultGlobalState;
final List<GetPage>? getPages;
final GetPage? unknownRoute;
final RouteInformationProvider? routeInformationProvider;
final RouteInformationParser<Object>? routeInformationParser;
final RouterDelegate<Object>? routerDelegate;
final BackButtonDispatcher? backButtonDispatcher;
final CupertinoThemeData? theme;
final bool useInheritedMediaQuery;
const GetCupertinoApp({
Key? key,
this.theme,
... ... @@ -46,6 +100,7 @@ class GetCupertinoApp extends StatelessWidget {
this.shortcuts,
this.smartManagement = SmartManagement.full,
this.initialBinding,
this.useInheritedMediaQuery = false,
this.unknownRoute,
this.routingCallback,
this.defaultTransition,
... ... @@ -66,58 +121,6 @@ class GetCupertinoApp extends StatelessWidget {
backButtonDispatcher = null,
super(key: key);
final GlobalKey<NavigatorState>? navigatorKey;
final Widget? home;
final Map<String, WidgetBuilder>? routes;
final String? initialRoute;
final RouteFactory? onGenerateRoute;
final InitialRouteListFactory? onGenerateInitialRoutes;
final RouteFactory? onUnknownRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionBuilder? builder;
final String title;
final GenerateAppTitle? onGenerateTitle;
final CustomTransition? customTransition;
final Color? color;
final Map<String, Map<String, String>>? translationsKeys;
final Translations? translations;
final TextDirection? textDirection;
final Locale? locale;
final Locale? fallbackLocale;
final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
final LocaleListResolutionCallback? localeListResolutionCallback;
final LocaleResolutionCallback? localeResolutionCallback;
final Iterable<Locale> supportedLocales;
final bool showPerformanceOverlay;
final bool checkerboardRasterCacheImages;
final bool checkerboardOffscreenLayers;
final bool showSemanticsDebugger;
final bool debugShowCheckedModeBanner;
final Map<LogicalKeySet, Intent>? shortcuts;
final ThemeData? highContrastTheme;
final ThemeData? highContrastDarkTheme;
final Map<Type, Action<Intent>>? actions;
final Function(Routing?)? routingCallback;
final Transition? defaultTransition;
final bool? opaqueRoute;
final VoidCallback? onInit;
final VoidCallback? onReady;
final VoidCallback? onDispose;
final bool? enableLog;
final LogWriterCallback? logWriterCallback;
final bool? popGesture;
final SmartManagement smartManagement;
final Bindings? initialBinding;
final Duration? transitionDuration;
final bool? defaultGlobalState;
final List<GetPage>? getPages;
final GetPage? unknownRoute;
final RouteInformationProvider? routeInformationProvider;
final RouteInformationParser<Object>? routeInformationParser;
final RouterDelegate<Object>? routerDelegate;
final BackButtonDispatcher? backButtonDispatcher;
final CupertinoThemeData? theme;
GetCupertinoApp.router({
Key? key,
this.theme,
... ... @@ -128,6 +131,7 @@ class GetCupertinoApp extends StatelessWidget {
this.builder,
this.title = '',
this.onGenerateTitle,
this.useInheritedMediaQuery = false,
this.color,
this.highContrastTheme,
this.highContrastDarkTheme,
... ... @@ -183,31 +187,6 @@ class GetCupertinoApp extends StatelessWidget {
Get.routeInformationParser = routeInformationParser;
}
Route<dynamic> generator(RouteSettings settings) {
return PageRedirect(settings: settings, unknownRoute: unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) {
return [
PageRedirect(
settings: RouteSettings(name: name),
unknownRoute: unknownRoute,
).page()
];
}
Widget defaultBuilder(BuildContext context, Widget? child) {
return Directionality(
textDirection: textDirection ??
(rtlLanguages.contains(Get.locale?.languageCode)
? TextDirection.rtl
: TextDirection.ltr),
child: builder == null
? (child ?? Material())
: builder!(context, child ?? Material()),
);
}
@override
Widget build(BuildContext context) => GetBuilder<GetMaterialController>(
init: Get.rootController,
... ... @@ -271,6 +250,7 @@ class GetCupertinoApp extends StatelessWidget {
showSemanticsDebugger: showSemanticsDebugger,
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
useInheritedMediaQuery: useInheritedMediaQuery,
)
: CupertinoApp(
key: _.unikey,
... ... @@ -310,7 +290,33 @@ class GetCupertinoApp extends StatelessWidget {
showSemanticsDebugger: showSemanticsDebugger,
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
useInheritedMediaQuery: useInheritedMediaQuery,
// actions: actions,
),
);
Widget defaultBuilder(BuildContext context, Widget? child) {
return Directionality(
textDirection: textDirection ??
(rtlLanguages.contains(Get.locale?.languageCode)
? TextDirection.rtl
: TextDirection.ltr),
child: builder == null
? (child ?? Material())
: builder!(context, child ?? Material()),
);
}
Route<dynamic> generator(RouteSettings settings) {
return PageRedirect(settings: settings, unknownRoute: unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) {
return [
PageRedirect(
settings: RouteSettings(name: name),
unknownRoute: unknownRoute,
).page()
];
}
}
... ...
... ... @@ -10,6 +10,64 @@ import '../../get_navigation.dart';
import 'root_controller.dart';
class GetMaterialApp extends StatelessWidget {
final GlobalKey<NavigatorState>? navigatorKey;
final GlobalKey<ScaffoldMessengerState>? scaffoldMessengerKey;
final Widget? home;
final Map<String, WidgetBuilder>? routes;
final String? initialRoute;
final RouteFactory? onGenerateRoute;
final InitialRouteListFactory? onGenerateInitialRoutes;
final RouteFactory? onUnknownRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionBuilder? builder;
final String title;
final GenerateAppTitle? onGenerateTitle;
final ThemeData? theme;
final ThemeData? darkTheme;
final ThemeMode themeMode;
final CustomTransition? customTransition;
final Color? color;
final Map<String, Map<String, String>>? translationsKeys;
final Translations? translations;
final TextDirection? textDirection;
final Locale? locale;
final Locale? fallbackLocale;
final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
final LocaleListResolutionCallback? localeListResolutionCallback;
final LocaleResolutionCallback? localeResolutionCallback;
final Iterable<Locale> supportedLocales;
final bool showPerformanceOverlay;
final bool checkerboardRasterCacheImages;
final bool checkerboardOffscreenLayers;
final bool showSemanticsDebugger;
final bool debugShowCheckedModeBanner;
final Map<LogicalKeySet, Intent>? shortcuts;
final ScrollBehavior? scrollBehavior;
final ThemeData? highContrastTheme;
final ThemeData? highContrastDarkTheme;
final Map<Type, Action<Intent>>? actions;
final bool debugShowMaterialGrid;
final ValueChanged<Routing?>? routingCallback;
final Transition? defaultTransition;
final bool? opaqueRoute;
final VoidCallback? onInit;
final VoidCallback? onReady;
final VoidCallback? onDispose;
final bool? enableLog;
final LogWriterCallback? logWriterCallback;
final bool? popGesture;
final SmartManagement smartManagement;
final Bindings? initialBinding;
final Duration? transitionDuration;
final bool? defaultGlobalState;
final List<GetPage>? getPages;
final GetPage? unknownRoute;
final RouteInformationProvider? routeInformationProvider;
final RouteInformationParser<Object>? routeInformationParser;
final RouterDelegate<Object>? routerDelegate;
final BackButtonDispatcher? backButtonDispatcher;
final bool useInheritedMediaQuery;
const GetMaterialApp({
Key? key,
this.navigatorKey,
... ... @@ -21,6 +79,7 @@ class GetMaterialApp extends StatelessWidget {
this.onGenerateRoute,
this.onGenerateInitialRoutes,
this.onUnknownRoute,
this.useInheritedMediaQuery = false,
List<NavigatorObserver> this.navigatorObservers =
const <NavigatorObserver>[],
this.builder,
... ... @@ -72,63 +131,6 @@ class GetMaterialApp extends StatelessWidget {
backButtonDispatcher = null,
super(key: key);
final GlobalKey<NavigatorState>? navigatorKey;
final GlobalKey<ScaffoldMessengerState>? scaffoldMessengerKey;
final Widget? home;
final Map<String, WidgetBuilder>? routes;
final String? initialRoute;
final RouteFactory? onGenerateRoute;
final InitialRouteListFactory? onGenerateInitialRoutes;
final RouteFactory? onUnknownRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionBuilder? builder;
final String title;
final GenerateAppTitle? onGenerateTitle;
final ThemeData? theme;
final ThemeData? darkTheme;
final ThemeMode themeMode;
final CustomTransition? customTransition;
final Color? color;
final Map<String, Map<String, String>>? translationsKeys;
final Translations? translations;
final TextDirection? textDirection;
final Locale? locale;
final Locale? fallbackLocale;
final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
final LocaleListResolutionCallback? localeListResolutionCallback;
final LocaleResolutionCallback? localeResolutionCallback;
final Iterable<Locale> supportedLocales;
final bool showPerformanceOverlay;
final bool checkerboardRasterCacheImages;
final bool checkerboardOffscreenLayers;
final bool showSemanticsDebugger;
final bool debugShowCheckedModeBanner;
final Map<LogicalKeySet, Intent>? shortcuts;
final ScrollBehavior? scrollBehavior;
final ThemeData? highContrastTheme;
final ThemeData? highContrastDarkTheme;
final Map<Type, Action<Intent>>? actions;
final bool debugShowMaterialGrid;
final ValueChanged<Routing?>? routingCallback;
final Transition? defaultTransition;
final bool? opaqueRoute;
final VoidCallback? onInit;
final VoidCallback? onReady;
final VoidCallback? onDispose;
final bool? enableLog;
final LogWriterCallback? logWriterCallback;
final bool? popGesture;
final SmartManagement smartManagement;
final Bindings? initialBinding;
final Duration? transitionDuration;
final bool? defaultGlobalState;
final List<GetPage>? getPages;
final GetPage? unknownRoute;
final RouteInformationProvider? routeInformationProvider;
final RouteInformationParser<Object>? routeInformationParser;
final RouterDelegate<Object>? routerDelegate;
final BackButtonDispatcher? backButtonDispatcher;
GetMaterialApp.router({
Key? key,
this.routeInformationProvider,
... ... @@ -142,6 +144,7 @@ class GetMaterialApp extends StatelessWidget {
this.color,
this.theme,
this.darkTheme,
this.useInheritedMediaQuery = false,
this.highContrastTheme,
this.highContrastDarkTheme,
this.themeMode = ThemeMode.system,
... ... @@ -200,31 +203,6 @@ class GetMaterialApp extends StatelessWidget {
Get.routeInformationParser = routeInformationParser;
}
Route<dynamic> generator(RouteSettings settings) {
return PageRedirect(settings: settings, unknownRoute: unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) {
return [
PageRedirect(
settings: RouteSettings(name: name),
unknownRoute: unknownRoute,
).page()
];
}
Widget defaultBuilder(BuildContext context, Widget? child) {
return Directionality(
textDirection: textDirection ??
(rtlLanguages.contains(Get.locale?.languageCode)
? TextDirection.rtl
: TextDirection.ltr),
child: builder == null
? (child ?? Material())
: builder!(context, child ?? Material()),
);
}
@override
Widget build(BuildContext context) => GetBuilder<GetMaterialController>(
init: Get.rootController,
... ... @@ -270,7 +248,6 @@ class GetMaterialApp extends StatelessWidget {
? MaterialApp.router(
routerDelegate: routerDelegate!,
routeInformationParser: routeInformationParser!,
scaffoldMessengerKey: scaffoldMessengerKey,
backButtonDispatcher: backButtonDispatcher,
routeInformationProvider: routeInformationProvider,
key: _.unikey,
... ... @@ -283,6 +260,8 @@ class GetMaterialApp extends StatelessWidget {
_.darkTheme ?? darkTheme ?? theme ?? ThemeData.fallback(),
themeMode: _.themeMode ?? themeMode,
locale: Get.locale ?? locale,
scaffoldMessengerKey:
scaffoldMessengerKey ?? _.scaffoldMessengerKey,
localizationsDelegates: localizationsDelegates,
localeListResolutionCallback: localeListResolutionCallback,
localeResolutionCallback: localeResolutionCallback,
... ... @@ -295,13 +274,15 @@ class GetMaterialApp extends StatelessWidget {
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
scrollBehavior: scrollBehavior,
useInheritedMediaQuery: useInheritedMediaQuery,
)
: MaterialApp(
key: _.unikey,
navigatorKey: (navigatorKey == null
? Get.key
: Get.addKey(navigatorKey!)),
scaffoldMessengerKey: scaffoldMessengerKey,
scaffoldMessengerKey:
scaffoldMessengerKey ?? _.scaffoldMessengerKey,
home: home,
routes: routes ?? const <String, WidgetBuilder>{},
initialRoute: initialRoute,
... ... @@ -340,7 +321,33 @@ class GetMaterialApp extends StatelessWidget {
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
scrollBehavior: scrollBehavior,
useInheritedMediaQuery: useInheritedMediaQuery,
// actions: actions,
),
);
Widget defaultBuilder(BuildContext context, Widget? child) {
return Directionality(
textDirection: textDirection ??
(rtlLanguages.contains(Get.locale?.languageCode)
? TextDirection.rtl
: TextDirection.ltr),
child: builder == null
? (child ?? Material())
: builder!(context, child ?? Material()),
);
}
Route<dynamic> generator(RouteSettings settings) {
return PageRedirect(settings: settings, unknownRoute: unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) {
return [
PageRedirect(
settings: RouteSettings(name: name),
unknownRoute: unknownRoute,
).page()
];
}
}
... ...
import 'package:flutter/material.dart';
import '../../../get.dart';
import '../../../get_state_manager/get_state_manager.dart';
import '../../../get_utils/get_utils.dart';
import '../routes/custom_transition.dart';
import '../routes/observers/route_observer.dart';
import '../routes/transitions_type.dart';
class GetMaterialController extends GetxController {
class GetMaterialController extends SuperController {
bool testMode = false;
Key? unikey;
ThemeData? theme;
ThemeData? darkTheme;
ThemeMode? themeMode;
final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
bool defaultPopGesture = GetPlatform.isIOS;
bool defaultOpaqueRoute = true;
... ... @@ -31,6 +35,8 @@ class GetMaterialController extends GetxController {
var _key = GlobalKey<NavigatorState>(debugLabel: 'Key Created by default');
Map<dynamic, GlobalKey<NavigatorState>> keys = {};
GlobalKey<NavigatorState> get key => _key;
GlobalKey<NavigatorState>? addKey(GlobalKey<NavigatorState> newKey) {
... ... @@ -38,7 +44,32 @@ class GetMaterialController extends GetxController {
return key;
}
Map<dynamic, GlobalKey<NavigatorState>> keys = {};
@override
void didChangeLocales(List<Locale>? locales) {
Get.asap(() {
final locale = Get.deviceLocale;
if (locale != null) {
Get.updateLocale(locale);
}
});
}
@override
void onDetached() {}
@override
void onInactive() {}
@override
void onPaused() {}
@override
void onResumed() {}
void restartApp() {
unikey = UniqueKey();
update();
}
void setTheme(ThemeData value) {
if (darkTheme == null) {
... ... @@ -57,9 +88,4 @@ class GetMaterialController extends GetxController {
themeMode = value;
update();
}
void restartApp() {
unikey = UniqueKey();
update();
}
}
... ...
... ... @@ -9,24 +9,6 @@ import '../../get_navigation.dart';
import 'custom_transition.dart';
import 'transitions_type.dart';
@immutable
class PathDecoded {
const PathDecoded(this.regex, this.keys);
final RegExp regex;
final List<String?> keys;
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is PathDecoded &&
other.regex == regex; // && listEquals(other.keys, keys);
}
@override
int get hashCode => regex.hashCode;
}
class GetPage<T> extends Page<T> {
final GetPageBuilder page;
final bool? popGesture;
... ... @@ -98,27 +80,6 @@ class GetPage<T> extends Page<T> {
);
// settings = RouteSettings(name: name, arguments: Get.arguments);
static PathDecoded _nameToRegex(String path) {
var keys = <String?>[];
String _replace(Match pattern) {
var buffer = StringBuffer('(?:');
if (pattern[1] != null) buffer.write('\.');
buffer.write('([\\w%+-._~!\$&\'()*,;=:@]+))');
if (pattern[3] != null) buffer.write('?');
keys.add(pattern[2]);
return "$buffer";
}
var stringPath = '$path/?'
.replaceAllMapped(RegExp(r'(\.)?:(\w+)(\?)?'), _replace)
.replaceAll('//', '/');
return PathDecoded(RegExp('^$stringPath\$'), keys);
}
GetPage<T> copy({
String? name,
GetPageBuilder? page,
... ... @@ -174,8 +135,6 @@ class GetPage<T> extends Page<T> {
);
}
late Future<T?> popped;
@override
Route<T> createRoute(BuildContext context) {
// return GetPageRoute<T>(settings: this, page: page);
... ... @@ -185,7 +144,45 @@ class GetPage<T> extends Page<T> {
unknownRoute: unknownRoute,
).getPageToRoute<T>(this, unknownRoute);
popped = _page.popped;
return _page;
}
static PathDecoded _nameToRegex(String path) {
var keys = <String?>[];
String _replace(Match pattern) {
var buffer = StringBuffer('(?:');
if (pattern[1] != null) buffer.write('\.');
buffer.write('([\\w%+-._~!\$&\'()*,;=:@]+))');
if (pattern[3] != null) buffer.write('?');
keys.add(pattern[2]);
return "$buffer";
}
var stringPath = '$path/?'
.replaceAllMapped(RegExp(r'(\.)?:(\w+)(\?)?'), _replace)
.replaceAll('//', '/');
return PathDecoded(RegExp('^$stringPath\$'), keys);
}
}
@immutable
class PathDecoded {
final RegExp regex;
final List<String?> keys;
const PathDecoded(this.regex, this.keys);
@override
int get hashCode => regex.hashCode;
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is PathDecoded &&
other.regex == regex; // && listEquals(other.keys, keys);
}
}
... ...
... ... @@ -5,111 +5,247 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import '../../../get.dart';
import '../../../get.dart';
import 'default_transitions.dart';
import 'transitions_type.dart';
const double _kBackGestureWidth = 20.0;
const double _kMinFlingVelocity = 1.0; // Screen widths per second.
const int _kMaxDroppedSwipePageForwardAnimationTime =
800; // Screen widths per second.
// An eyeballed value for the maximum time it takes
//for a page to animate forward
// if the user releases a page mid swipe.
const int _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds.
const int _kMaxPageBackAnimationTime = 300; // Milliseconds.
// The maximum time for a page to get reset to it's original position if the
// user releases a page mid swipe.
const int _kMaxPageBackAnimationTime = 300; // Milliseconds.
const double _kMinFlingVelocity = 1.0; // Milliseconds.
mixin GetPageRouteTransitionMixin<T> on PageRoute<T> {
/// Builds the primary contents of the route.
@protected
Widget buildContent(BuildContext context);
class CupertinoBackGestureController<T> {
final AnimationController controller;
/// {@template flutter.cupertino.CupertinoRouteTransitionMixin.title}
/// A title string for this route.
final NavigatorState navigator;
/// Creates a controller for an iOS-style back gesture.
///
/// Used to auto-populate [CupertinoNavigationBar] and
/// [CupertinoSliverNavigationBar]'s `middle`/`largeTitle` widgets when
/// one is not manually supplied.
/// {@endtemplate}
String? get title;
/// The [navigator] and [controller] arguments must not be null.
CupertinoBackGestureController({
required this.navigator,
required this.controller,
}) {
navigator.didStartUserGesture();
}
double Function(BuildContext context)? get gestureWidth;
/// The drag gesture has ended with a horizontal motion of
/// [fractionalVelocity] as a fraction of screen width per second.
void dragEnd(double velocity) {
// Fling in the appropriate direction.
// AnimationController.fling is guaranteed to
// take at least one frame.
//
// This curve has been determined through rigorously eyeballing native iOS
// animations.
const Curve animationCurve = Curves.fastLinearToSlowEaseIn;
final bool animateForward;
ValueNotifier<String?>? _previousTitle;
// If the user releases the page before mid screen with sufficient velocity,
// or after mid screen, we should animate the page out. Otherwise, the page
// should be animated back in.
if (velocity.abs() >= _kMinFlingVelocity) {
animateForward = velocity <= 0;
} else {
animateForward = controller.value > 0.5;
}
/// The title string of the previous [CupertinoPageRoute].
///
/// The [ValueListenable]'s value is readable after the route is installed
/// onto a [Navigator]. The [ValueListenable] will also notify its listeners
/// if the value changes (such as by replacing the previous route).
///
/// The [ValueListenable] itself will be null before the route is installed.
/// Its content value will be null if the previous route has no title or
/// is not a [CupertinoPageRoute].
///
/// See also:
///
/// * [ValueListenableBuilder], which can be used to listen and rebuild
/// widgets based on a ValueListenable.
ValueListenable<String?> get previousTitle {
assert(
_previousTitle != null,
'''
Cannot read the previousTitle for a route that has not yet been installed''',
);
return _previousTitle!;
}
if (animateForward) {
// The closer the panel is to dismissing, the shorter the animation is.
// We want to cap the animation time, but we want to use a linear curve
// to determine it.
final droppedPageForwardAnimationTime = min(
lerpDouble(
_kMaxDroppedSwipePageForwardAnimationTime, 0, controller.value)!
.floor(),
_kMaxPageBackAnimationTime,
);
controller.animateTo(1.0,
duration: Duration(milliseconds: droppedPageForwardAnimationTime),
curve: animationCurve);
} else {
// This route is destined to pop at this point. Reuse navigator's pop.
navigator.pop();
@override
void didChangePrevious(Route<dynamic>? previousRoute) {
final previousTitleString = previousRoute is CupertinoRouteTransitionMixin
? previousRoute.title
: null;
if (_previousTitle == null) {
_previousTitle = ValueNotifier<String?>(previousTitleString);
// The popping may have finished inline if already at the
// target destination.
if (controller.isAnimating) {
// Otherwise, use a custom popping animation duration and curve.
final droppedPageBackAnimationTime = lerpDouble(
0, _kMaxDroppedSwipePageForwardAnimationTime, controller.value)!
.floor();
controller.animateBack(0.0,
duration: Duration(milliseconds: droppedPageBackAnimationTime),
curve: animationCurve);
}
}
if (controller.isAnimating) {
// Keep the userGestureInProgress in true state so we don't change the
// curve of the page transition mid-flight since CupertinoPageTransition
// depends on userGestureInProgress.
late AnimationStatusListener animationStatusCallback;
animationStatusCallback = (status) {
navigator.didStopUserGesture();
controller.removeStatusListener(animationStatusCallback);
};
controller.addStatusListener(animationStatusCallback);
} else {
_previousTitle!.value = previousTitleString;
navigator.didStopUserGesture();
}
super.didChangePrevious(previousRoute);
}
@override
// A relatively rigorous eyeball estimation.
Duration get transitionDuration => const Duration(milliseconds: 400);
/// The drag gesture has changed by [fractionalDelta]. The total range of the
/// drag should be 0.0 to 1.0.
void dragUpdate(double delta) {
controller.value -= delta;
}
}
class CupertinoBackGestureDetector<T> extends StatefulWidget {
final Widget child;
final double gestureWidth;
final ValueGetter<bool> enabledCallback;
final ValueGetter<CupertinoBackGestureController<T>> onStartPopGesture;
const CupertinoBackGestureDetector({
Key? key,
required this.enabledCallback,
required this.onStartPopGesture,
required this.child,
required this.gestureWidth,
}) : super(key: key);
@override
Color? get barrierColor => null;
CupertinoBackGestureDetectorState<T> createState() =>
CupertinoBackGestureDetectorState<T>();
}
class CupertinoBackGestureDetectorState<T>
extends State<CupertinoBackGestureDetector<T>> {
CupertinoBackGestureController<T>? _backGestureController;
late HorizontalDragGestureRecognizer _recognizer;
@override
String? get barrierLabel => null;
Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context));
// For devices with notches, the drag area needs to be larger on the side
// that has the notch.
var dragAreaWidth = Directionality.of(context) == TextDirection.ltr
? MediaQuery.of(context).padding.left
: MediaQuery.of(context).padding.right;
dragAreaWidth = max(dragAreaWidth, widget.gestureWidth);
return Stack(
fit: StackFit.passthrough,
children: <Widget>[
widget.child,
PositionedDirectional(
start: 0.0,
width: dragAreaWidth,
top: 0.0,
bottom: 0.0,
child: Listener(
onPointerDown: _handlePointerDown,
behavior: HitTestBehavior.translucent,
),
),
],
);
}
bool get showCupertinoParallax;
@override
void dispose() {
_recognizer.dispose();
super.dispose();
}
@override
bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
// Don't perform outgoing animation if the next route is a
// fullscreen dialog.
void initState() {
super.initState();
_recognizer = HorizontalDragGestureRecognizer(debugOwner: this)
..onStart = _handleDragStart
..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd
..onCancel = _handleDragCancel;
}
return nextRoute is GetPageRouteTransitionMixin &&
!nextRoute.fullscreenDialog &&
nextRoute.showCupertinoParallax;
double _convertToLogical(double value) {
switch (Directionality.of(context)) {
case TextDirection.rtl:
return -value;
case TextDirection.ltr:
return value;
}
}
/// True if an iOS-style back swipe pop gesture is currently
/// underway for [route].
void _handleDragCancel() {
assert(mounted);
// This can be called even if start is not called, paired with
// the "down" event
// that we don't consider here.
_backGestureController?.dragEnd(0.0);
_backGestureController = null;
}
void _handleDragEnd(DragEndDetails details) {
assert(mounted);
assert(_backGestureController != null);
_backGestureController!.dragEnd(_convertToLogical(
details.velocity.pixelsPerSecond.dx / context.size!.width));
_backGestureController = null;
}
void _handleDragStart(DragStartDetails details) {
assert(mounted);
assert(_backGestureController == null);
_backGestureController = widget.onStartPopGesture();
}
void _handleDragUpdate(DragUpdateDetails details) {
assert(mounted);
assert(_backGestureController != null);
_backGestureController!.dragUpdate(
_convertToLogical(details.primaryDelta! / context.size!.width));
}
void _handlePointerDown(PointerDownEvent event) {
if (widget.enabledCallback()) _recognizer.addPointer(event);
}
}
mixin GetPageRouteTransitionMixin<T> on PageRoute<T> {
ValueNotifier<String?>? _previousTitle;
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
double Function(BuildContext context)? get gestureWidth;
/// Whether a pop gesture can be started by the user.
///
/// This just check the route's [NavigatorState.userGestureInProgress].
/// Returns true if the user can edge-swipe to a previous route.
///
/// See also:
/// Returns false once [isPopGestureInProgress] is true, but
/// [isPopGestureInProgress] can only become true if [popGestureEnabled] was
/// true first.
///
/// * [popGestureEnabled], which returns true if a user-triggered pop gesture
/// would be allowed.
static bool isPopGestureInProgress(PageRoute<dynamic> route) {
return route.navigator!.userGestureInProgress;
}
/// This should only be used between frames, not during build.
bool get popGestureEnabled => _isPopGestureEnabled(this);
/// True if an iOS-style back swipe pop gesture is currently
/// underway for this route.
... ... @@ -122,44 +258,47 @@ Cannot read the previousTitle for a route that has not yet been installed''',
/// would be allowed.
bool get popGestureInProgress => isPopGestureInProgress(this);
/// Whether a pop gesture can be started by the user.
/// The title string of the previous [CupertinoPageRoute].
///
/// Returns true if the user can edge-swipe to a previous route.
/// The [ValueListenable]'s value is readable after the route is installed
/// onto a [Navigator]. The [ValueListenable] will also notify its listeners
/// if the value changes (such as by replacing the previous route).
///
/// Returns false once [isPopGestureInProgress] is true, but
/// [isPopGestureInProgress] can only become true if [popGestureEnabled] was
/// true first.
/// The [ValueListenable] itself will be null before the route is installed.
/// Its content value will be null if the previous route has no title or
/// is not a [CupertinoPageRoute].
///
/// This should only be used between frames, not during build.
bool get popGestureEnabled => _isPopGestureEnabled(this);
/// See also:
///
/// * [ValueListenableBuilder], which can be used to listen and rebuild
/// widgets based on a ValueListenable.
ValueListenable<String?> get previousTitle {
assert(
_previousTitle != null,
'''
Cannot read the previousTitle for a route that has not yet been installed''',
);
return _previousTitle!;
}
static bool _isPopGestureEnabled<T>(PageRoute<T> route) {
// If there's nothing to go back to, then obviously we don't support
// the back gesture.
if (route.isFirst) return false;
// If the route wouldn't actually pop if we popped it, then the gesture
// would be really confusing (or would skip internal routes),
//so disallow it.
if (route.willHandlePopInternally) return false;
// If attempts to dismiss this route might be vetoed such as in a page
// with forms, then do not allow the user to dismiss the route with a swipe.
if (route.hasScopedWillPopCallback) return false;
// Fullscreen dialogs aren't dismissible by back swipe.
if (route.fullscreenDialog) return false;
// If we're in an animation already, we cannot be manually swiped.
if (route.animation!.status != AnimationStatus.completed) return false;
// If we're being popped into, we also cannot be swiped until the pop above
// it completes. This translates to our secondary animation being
// dismissed.
if (route.secondaryAnimation!.status != AnimationStatus.dismissed) {
return false;
}
// If we're in a gesture already, we cannot start another.
if (isPopGestureInProgress(route)) return false;
bool get showCupertinoParallax;
// Looks like a back gesture would be welcome!
return true;
}
/// {@template flutter.cupertino.CupertinoRouteTransitionMixin.title}
/// A title string for this route.
///
/// Used to auto-populate [CupertinoNavigationBar] and
/// [CupertinoSliverNavigationBar]'s `middle`/`largeTitle` widgets when
/// one is not manually supplied.
/// {@endtemplate}
String? get title;
@override
// A relatively rigorous eyeball estimation.
Duration get transitionDuration => const Duration(milliseconds: 400);
/// Builds the primary contents of the route.
@protected
Widget buildContent(BuildContext context);
@override
Widget buildPage(BuildContext context, Animation<double> animation,
... ... @@ -173,17 +312,36 @@ Cannot read the previousTitle for a route that has not yet been installed''',
return result;
}
// Called by CupertinoBackGestureDetector when a pop ("back") drag start
// gesture is detected. The returned controller handles all of the subsequent
// drag events.
static CupertinoBackGestureController<T> _startPopGesture<T>(
PageRoute<T> route) {
assert(_isPopGestureEnabled(route));
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return buildPageTransitions<T>(
this, context, animation, secondaryAnimation, child);
}
return CupertinoBackGestureController<T>(
navigator: route.navigator!,
controller: route.controller!, // protected access
);
@override
bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
// Don't perform outgoing animation if the next route is a
// fullscreen dialog.
return (nextRoute is GetPageRouteTransitionMixin &&
!nextRoute.fullscreenDialog &&
nextRoute.showCupertinoParallax) ||
(nextRoute is CupertinoRouteTransitionMixin &&
!nextRoute.fullscreenDialog);
}
@override
void didChangePrevious(Route<dynamic>? previousRoute) {
final previousTitleString = previousRoute is CupertinoRouteTransitionMixin
? previousRoute.title
: null;
if (_previousTitle == null) {
_previousTitle = ValueNotifier<String?>(previousTitleString);
} else {
_previousTitle!.value = previousTitleString;
}
super.didChangePrevious(previousRoute);
}
/// 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''',
}
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return buildPageTransitions<T>(
this, context, animation, secondaryAnimation, child);
}
}
class CupertinoBackGestureDetector<T> extends StatefulWidget {
const CupertinoBackGestureDetector({
Key? key,
required this.enabledCallback,
required this.onStartPopGesture,
required this.child,
required this.gestureWidth,
}) : super(key: key);
final Widget child;
final double gestureWidth;
final ValueGetter<bool> enabledCallback;
final ValueGetter<CupertinoBackGestureController<T>> onStartPopGesture;
@override
CupertinoBackGestureDetectorState<T> createState() =>
CupertinoBackGestureDetectorState<T>();
}
class CupertinoBackGestureDetectorState<T>
extends State<CupertinoBackGestureDetector<T>> {
CupertinoBackGestureController<T>? _backGestureController;
late HorizontalDragGestureRecognizer _recognizer;
@override
void initState() {
super.initState();
_recognizer = HorizontalDragGestureRecognizer(debugOwner: this)
..onStart = _handleDragStart
..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd
..onCancel = _handleDragCancel;
}
@override
void dispose() {
_recognizer.dispose();
super.dispose();
}
void _handleDragStart(DragStartDetails details) {
assert(mounted);
assert(_backGestureController == null);
_backGestureController = widget.onStartPopGesture();
}
void _handleDragUpdate(DragUpdateDetails details) {
assert(mounted);
assert(_backGestureController != null);
_backGestureController!.dragUpdate(
_convertToLogical(details.primaryDelta! / context.size!.width));
}
void _handleDragEnd(DragEndDetails details) {
assert(mounted);
assert(_backGestureController != null);
_backGestureController!.dragEnd(_convertToLogical(
details.velocity.pixelsPerSecond.dx / context.size!.width));
_backGestureController = null;
}
void _handleDragCancel() {
assert(mounted);
// This can be called even if start is not called, paired with
// the "down" event
// that we don't consider here.
_backGestureController?.dragEnd(0.0);
_backGestureController = null;
}
void _handlePointerDown(PointerDownEvent event) {
if (widget.enabledCallback()) _recognizer.addPointer(event);
}
double _convertToLogical(double value) {
switch (Directionality.of(context)) {
case TextDirection.rtl:
return -value;
case TextDirection.ltr:
return value;
}
}
@override
Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context));
// For devices with notches, the drag area needs to be larger on the side
// that has the notch.
var dragAreaWidth = Directionality.of(context) == TextDirection.ltr
? MediaQuery.of(context).padding.left
: MediaQuery.of(context).padding.right;
dragAreaWidth = max(dragAreaWidth, widget.gestureWidth);
return Stack(
fit: StackFit.passthrough,
children: <Widget>[
widget.child,
PositionedDirectional(
start: 0.0,
width: dragAreaWidth,
top: 0.0,
bottom: 0.0,
child: Listener(
onPointerDown: _handlePointerDown,
behavior: HitTestBehavior.translucent,
),
),
],
);
}
}
class CupertinoBackGestureController<T> {
/// Creates a controller for an iOS-style back gesture.
// Called by CupertinoBackGestureDetector when a pop ("back") drag start
// gesture is detected. The returned controller handles all of the subsequent
// drag events.
/// True if an iOS-style back swipe pop gesture is currently
/// underway for [route].
///
/// The [navigator] and [controller] arguments must not be null.
CupertinoBackGestureController({
required this.navigator,
required this.controller,
}) {
navigator.didStartUserGesture();
}
final AnimationController controller;
final NavigatorState navigator;
/// The drag gesture has changed by [fractionalDelta]. The total range of the
/// drag should be 0.0 to 1.0.
void dragUpdate(double delta) {
controller.value -= delta;
/// This just check the route's [NavigatorState.userGestureInProgress].
///
/// See also:
///
/// * [popGestureEnabled], which returns true if a user-triggered pop gesture
/// would be allowed.
static bool isPopGestureInProgress(PageRoute<dynamic> route) {
return route.navigator!.userGestureInProgress;
}
/// The drag gesture has ended with a horizontal motion of
/// [fractionalVelocity] as a fraction of screen width per second.
void dragEnd(double velocity) {
// Fling in the appropriate direction.
// AnimationController.fling is guaranteed to
// take at least one frame.
//
// This curve has been determined through rigorously eyeballing native iOS
// animations.
const Curve animationCurve = Curves.fastLinearToSlowEaseIn;
final bool animateForward;
// If the user releases the page before mid screen with sufficient velocity,
// or after mid screen, we should animate the page out. Otherwise, the page
// should be animated back in.
if (velocity.abs() >= _kMinFlingVelocity) {
animateForward = velocity <= 0;
} else {
animateForward = controller.value > 0.5;
static bool _isPopGestureEnabled<T>(PageRoute<T> route) {
// If there's nothing to go back to, then obviously we don't support
// the back gesture.
if (route.isFirst) return false;
// If the route wouldn't actually pop if we popped it, then the gesture
// would be really confusing (or would skip internal routes),
//so disallow it.
if (route.willHandlePopInternally) return false;
// If attempts to dismiss this route might be vetoed such as in a page
// with forms, then do not allow the user to dismiss the route with a swipe.
if (route.hasScopedWillPopCallback) return false;
// Fullscreen dialogs aren't dismissible by back swipe.
if (route.fullscreenDialog) return false;
// If we're in an animation already, we cannot be manually swiped.
if (route.animation!.status != AnimationStatus.completed) return false;
// If we're being popped into, we also cannot be swiped until the pop above
// it completes. This translates to our secondary animation being
// dismissed.
if (route.secondaryAnimation!.status != AnimationStatus.dismissed) {
return false;
}
// If we're in a gesture already, we cannot start another.
if (isPopGestureInProgress(route)) return false;
if (animateForward) {
// The closer the panel is to dismissing, the shorter the animation is.
// We want to cap the animation time, but we want to use a linear curve
// to determine it.
final droppedPageForwardAnimationTime = min(
lerpDouble(
_kMaxDroppedSwipePageForwardAnimationTime, 0, controller.value)!
.floor(),
_kMaxPageBackAnimationTime,
);
controller.animateTo(1.0,
duration: Duration(milliseconds: droppedPageForwardAnimationTime),
curve: animationCurve);
} else {
// This route is destined to pop at this point. Reuse navigator's pop.
navigator.pop();
// Looks like a back gesture would be welcome!
return true;
}
// The popping may have finished inline if already at the
// target destination.
if (controller.isAnimating) {
// Otherwise, use a custom popping animation duration and curve.
final droppedPageBackAnimationTime = lerpDouble(
0, _kMaxDroppedSwipePageForwardAnimationTime, controller.value)!
.floor();
controller.animateBack(0.0,
duration: Duration(milliseconds: droppedPageBackAnimationTime),
curve: animationCurve);
}
}
static CupertinoBackGestureController<T> _startPopGesture<T>(
PageRoute<T> route) {
assert(_isPopGestureEnabled(route));
if (controller.isAnimating) {
// Keep the userGestureInProgress in true state so we don't change the
// curve of the page transition mid-flight since CupertinoPageTransition
// depends on userGestureInProgress.
late AnimationStatusListener animationStatusCallback;
animationStatusCallback = (status) {
navigator.didStopUserGesture();
controller.removeStatusListener(animationStatusCallback);
};
controller.addStatusListener(animationStatusCallback);
} else {
navigator.didStopUserGesture();
}
return CupertinoBackGestureController<T>(
navigator: route.navigator!,
controller: route.controller!, // protected access
);
}
}
... ...
... ... @@ -5,37 +5,8 @@ import '../../../../instance_manager.dart';
import '../../../get_navigation.dart';
import '../../dialog/dialog_route.dart';
import '../../router_report.dart';
import '../../snackbar/snack_route.dart';
import '../default_route.dart';
class Routing {
String current;
String previous;
dynamic args;
String removed;
Route<dynamic>? route;
bool? isBack;
bool? isSnackbar;
bool? isBottomSheet;
bool? isDialog;
Routing({
this.current = '',
this.previous = '',
this.args,
this.removed = '',
this.route,
this.isBack,
this.isSnackbar,
this.isBottomSheet,
this.isDialog,
});
void update(void fn(Routing value)) {
fn(this);
}
}
/// Extracts the name of a route based on it's instance type
/// or null if not possible.
String? _extractRouteName(Route? route) {
... ... @@ -58,49 +29,70 @@ String? _extractRouteName(Route? route) {
return null;
}
/// This is basically a util for rules about 'what a route is'
class _RouteData {
final bool isGetPageRoute;
final bool isSnackbar;
final bool isBottomSheet;
final bool isDialog;
final String? name;
_RouteData({
required this.name,
required this.isGetPageRoute,
required this.isSnackbar,
required this.isBottomSheet,
required this.isDialog,
});
factory _RouteData.ofRoute(Route? route) {
return _RouteData(
name: _extractRouteName(route),
isGetPageRoute: route is GetPageRoute,
isSnackbar: route is SnackRoute,
isDialog: route is GetDialogRoute,
isBottomSheet: route is GetModalBottomSheetRoute,
);
}
}
class GetObserver extends NavigatorObserver {
final Function(Routing?)? routing;
final Routing? _routeSend;
GetObserver([this.routing, this._routeSend]);
final Routing? _routeSend;
@override
void didPop(Route route, Route? previousRoute) {
super.didPop(route, previousRoute);
final currentRoute = _RouteData.ofRoute(route);
final newRoute = _RouteData.ofRoute(previousRoute);
// if (currentRoute.isSnackbar) {
// // Get.log("CLOSE SNACKBAR ${currentRoute.name}");
// Get.log("CLOSE SNACKBAR");
// } else
if (currentRoute.isBottomSheet || currentRoute.isDialog) {
Get.log("CLOSE ${currentRoute.name}");
} else if (currentRoute.isGetPageRoute) {
Get.log("CLOSE TO ROUTE ${currentRoute.name}");
}
if (previousRoute != null) {
RouterReportManager.reportCurrentRoute(previousRoute);
}
// Here we use a 'inverse didPush set', meaning that we use
// previous route instead of 'route' because this is
// a 'inverse push'
_routeSend?.update((value) {
// Only PageRoute is allowed to change current value
if (previousRoute is PageRoute) {
value.current = _extractRouteName(previousRoute) ?? '';
value.previous = newRoute.name ?? '';
} else if (value.previous.isNotEmpty) {
value.current = value.previous;
}
value.args = previousRoute?.settings.arguments;
value.route = previousRoute;
value.isBack = true;
value.removed = '';
// value.isSnackbar = newRoute.isSnackbar;
value.isBottomSheet = newRoute.isBottomSheet;
value.isDialog = newRoute.isDialog;
});
// print('currentRoute.isDialog ${currentRoute.isDialog}');
routing?.call(_routeSend);
}
@override
void didPush(Route route, Route? previousRoute) {
super.didPush(route, previousRoute);
final newRoute = _RouteData.ofRoute(route);
if (newRoute.isSnackbar) {
// Get.log("OPEN SNACKBAR ${newRoute.name}");
Get.log("OPEN SNACKBAR");
} else if (newRoute.isBottomSheet || newRoute.isDialog) {
// if (newRoute.isSnackbar) {
// // Get.log("OPEN SNACKBAR ${newRoute.name}");
// Get.log("OPEN SNACKBAR");
// } else
if (newRoute.isBottomSheet || newRoute.isDialog) {
Get.log("OPEN ${newRoute.name}");
} else if (newRoute.isGetPageRoute) {
Get.log("GOING TO ROUTE ${newRoute.name}");
... ... @@ -121,7 +113,6 @@ class GetObserver extends NavigatorObserver {
value.route = route;
value.isBack = false;
value.removed = '';
value.isSnackbar = newRoute.isSnackbar ? true : value.isSnackbar ?? false;
value.isBottomSheet =
newRoute.isBottomSheet ? true : value.isBottomSheet ?? false;
value.isDialog = newRoute.isDialog ? true : value.isDialog ?? false;
... ... @@ -133,46 +124,27 @@ class GetObserver extends NavigatorObserver {
}
@override
void didPop(Route route, Route? previousRoute) {
super.didPop(route, previousRoute);
void didRemove(Route route, Route? previousRoute) {
super.didRemove(route, previousRoute);
final routeName = _extractRouteName(route);
final currentRoute = _RouteData.ofRoute(route);
final newRoute = _RouteData.ofRoute(previousRoute);
if (currentRoute.isSnackbar) {
// Get.log("CLOSE SNACKBAR ${currentRoute.name}");
Get.log("CLOSE SNACKBAR");
} else if (currentRoute.isBottomSheet || currentRoute.isDialog) {
Get.log("CLOSE ${currentRoute.name}");
} else if (currentRoute.isGetPageRoute) {
Get.log("CLOSE TO ROUTE ${currentRoute.name}");
}
if (previousRoute != null) {
RouterReportManager.reportCurrentRoute(previousRoute);
}
Get.log("REMOVING ROUTE $routeName");
// Here we use a 'inverse didPush set', meaning that we use
// previous route instead of 'route' because this is
// a 'inverse push'
_routeSend?.update((value) {
// Only PageRoute is allowed to change current value
if (previousRoute is PageRoute) {
value.current = _extractRouteName(previousRoute) ?? '';
value.previous = newRoute.name ?? '';
} else if (value.previous.isNotEmpty) {
value.current = value.previous;
}
value.args = previousRoute?.settings.arguments;
value.route = previousRoute;
value.isBack = true;
value.removed = '';
value.isSnackbar = newRoute.isSnackbar;
value.isBottomSheet = newRoute.isBottomSheet;
value.isDialog = newRoute.isDialog;
value.isBack = false;
value.removed = routeName ?? '';
value.previous = routeName ?? '';
// value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar;
value.isBottomSheet =
currentRoute.isBottomSheet ? false : value.isBottomSheet;
value.isDialog = currentRoute.isDialog ? false : value.isDialog;
});
// print('currentRoute.isDialog ${currentRoute.isDialog}');
if (route is GetPageRoute) {
RouterReportManager.reportRouteWillDispose(route);
}
routing?.call(_routeSend);
}
... ... @@ -201,7 +173,7 @@ class GetObserver extends NavigatorObserver {
value.isBack = false;
value.removed = '';
value.previous = '$oldName';
value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar;
// value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar;
value.isBottomSheet =
currentRoute.isBottomSheet ? false : value.isBottomSheet;
value.isDialog = currentRoute.isDialog ? false : value.isDialog;
... ... @@ -212,29 +184,59 @@ class GetObserver extends NavigatorObserver {
routing?.call(_routeSend);
}
}
@override
void didRemove(Route route, Route? previousRoute) {
super.didRemove(route, previousRoute);
final routeName = _extractRouteName(route);
final currentRoute = _RouteData.ofRoute(route);
class Routing {
String current;
String previous;
dynamic args;
String removed;
Route<dynamic>? route;
bool? isBack;
// bool? isSnackbar;
bool? isBottomSheet;
bool? isDialog;
Get.log("REMOVING ROUTE $routeName");
Routing({
this.current = '',
this.previous = '',
this.args,
this.removed = '',
this.route,
this.isBack,
// this.isSnackbar,
this.isBottomSheet,
this.isDialog,
});
_routeSend?.update((value) {
value.route = previousRoute;
value.isBack = false;
value.removed = routeName ?? '';
value.previous = routeName ?? '';
value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar;
value.isBottomSheet =
currentRoute.isBottomSheet ? false : value.isBottomSheet;
value.isDialog = currentRoute.isDialog ? false : value.isDialog;
});
void update(void fn(Routing value)) {
fn(this);
}
}
if (route is GetPageRoute) {
RouterReportManager.reportRouteWillDispose(route);
}
routing?.call(_routeSend);
/// This is basically a util for rules about 'what a route is'
class _RouteData {
final bool isGetPageRoute;
//final bool isSnackbar;
final bool isBottomSheet;
final bool isDialog;
final String? name;
_RouteData({
required this.name,
required this.isGetPageRoute,
// required this.isSnackbar,
required this.isBottomSheet,
required this.isDialog,
});
factory _RouteData.ofRoute(Route? route) {
return _RouteData(
name: _extractRouteName(route),
isGetPageRoute: route is GetPageRoute,
// isSnackbar: route is SnackRoute,
isDialog: route is GetDialogRoute,
isBottomSheet: route is GetModalBottomSheetRoute,
);
}
}
... ...
import 'dart:async';
import 'dart:ui';
import 'package:flutter/widgets.dart';
import '../../../get_core/get_core.dart';
import '../../get_navigation.dart';
import 'snack.dart';
class SnackRoute<T> extends OverlayRoute<T> {
late Animation<double> _filterBlurAnimation;
late Animation<Color?> _filterColorAnimation;
SnackRoute({
required this.snack,
RouteSettings? settings,
}) : super(settings: settings) {
_builder = Builder(builder: (_) {
return GestureDetector(
child: snack,
onTap: snack.onTap != null ? () => snack.onTap!(snack) : null,
);
});
_configureAlignment(snack.snackPosition);
_snackbarStatus = snack.snackbarStatus;
}
_configureAlignment(SnackPosition snackPosition) {
switch (snack.snackPosition) {
case SnackPosition.TOP:
{
_initialAlignment = Alignment(-1.0, -2.0);
_endAlignment = Alignment(-1.0, -1.0);
break;
}
case SnackPosition.BOTTOM:
{
_initialAlignment = Alignment(-1.0, 2.0);
_endAlignment = Alignment(-1.0, 1.0);
break;
}
}
}
GetBar snack;
Builder? _builder;
final Completer<T> _transitionCompleter = Completer<T>();
late SnackbarStatusCallback _snackbarStatus;
Alignment? _initialAlignment;
Alignment? _endAlignment;
bool _wasDismissedBySwipe = false;
bool _onTappedDismiss = false;
Timer? _timer;
bool get opaque => false;
@override
Iterable<OverlayEntry> createOverlayEntries() {
return <OverlayEntry>[
if (snack.overlayBlur > 0.0) ...[
OverlayEntry(
builder: (context) {
return GestureDetector(
onTap: () {
if (snack.isDismissible && !_onTappedDismiss) {
_onTappedDismiss = true;
Get.back();
}
},
child: AnimatedBuilder(
animation: _filterBlurAnimation,
builder: (context, child) {
return BackdropFilter(
filter: ImageFilter.blur(
sigmaX: _filterBlurAnimation.value,
sigmaY: _filterBlurAnimation.value),
child: Container(
constraints: BoxConstraints.expand(),
color: _filterColorAnimation.value,
),
);
},
),
);
},
maintainState: false,
opaque: opaque,
),
],
OverlayEntry(
builder: (context) {
final Widget annotatedChild = Semantics(
child: AlignTransition(
alignment: _animation!,
child: snack.isDismissible
? _getDismissibleSnack(_builder)
: _getSnack(),
),
focused: false,
container: true,
explicitChildNodes: true,
);
return annotatedChild;
},
maintainState: false,
opaque: opaque,
),
];
}
String dismissibleKeyGen = "";
Widget _getDismissibleSnack(Widget? child) {
return Dismissible(
direction: _getDismissDirection(),
resizeDuration: null,
confirmDismiss: (_) {
if (currentStatus == SnackbarStatus.OPENING ||
currentStatus == SnackbarStatus.CLOSING) {
return Future.value(false);
}
return Future.value(true);
},
key: Key(dismissibleKeyGen),
onDismissed: (_) {
dismissibleKeyGen += "1";
_cancelTimer();
_wasDismissedBySwipe = true;
if (isCurrent) {
navigator!.pop();
} else {
navigator!.removeRoute(this);
}
},
child: _getSnack(),
);
}
Widget _getSnack() {
return Container(
margin: snack.margin,
child: _builder,
);
}
DismissDirection _getDismissDirection() {
if (snack.dismissDirection == SnackDismissDirection.HORIZONTAL) {
return DismissDirection.horizontal;
} else {
if (snack.snackPosition == SnackPosition.TOP) {
return DismissDirection.up;
}
return DismissDirection.down;
}
}
@override
bool get finishedWhenPopped =>
_controller!.status == AnimationStatus.dismissed;
/// The animation that drives the route's transition and the previous route's
/// forward transition.
Animation<Alignment>? _animation;
/// The animation controller that the route uses to drive the transitions.
///
/// The animation itself is exposed by the [animation] property.
AnimationController? _controller;
/// Called to create the animation controller that will drive the transitions
/// to this route from the previous one, and back to the previous route
/// from this one.
AnimationController createAnimationController() {
assert(!_transitionCompleter.isCompleted,
'Cannot reuse a $runtimeType after disposing it.');
assert(snack.animationDuration >= Duration.zero);
return AnimationController(
duration: snack.animationDuration,
debugLabel: debugLabel,
vsync: navigator!,
);
}
/// Called to create the animation that exposes the current progress of
/// the transition controlled by the animation controller created by
/// `createAnimationController()`.
Animation<Alignment> createAnimation() {
assert(!_transitionCompleter.isCompleted,
'Cannot reuse a $runtimeType after disposing it.');
assert(_controller != null);
return AlignmentTween(begin: _initialAlignment, end: _endAlignment).animate(
CurvedAnimation(
parent: _controller!,
curve: snack.forwardAnimationCurve,
reverseCurve: snack.reverseAnimationCurve,
),
);
}
Animation<double> createBlurFilterAnimation() {
return Tween(begin: 0.0, end: snack.overlayBlur).animate(
CurvedAnimation(
parent: _controller!,
curve: Interval(
0.0,
0.35,
curve: Curves.easeInOutCirc,
),
),
);
}
Animation<Color?> createColorFilterAnimation() {
return ColorTween(begin: Color(0x00000000), end: snack.overlayColor)
.animate(
CurvedAnimation(
parent: _controller!,
curve: Interval(
0.0,
0.35,
curve: Curves.easeInOutCirc,
),
),
);
}
T? _result;
SnackbarStatus? currentStatus;
void _handleStatusChanged(AnimationStatus status) {
switch (status) {
case AnimationStatus.completed:
currentStatus = SnackbarStatus.OPEN;
_snackbarStatus(currentStatus);
if (overlayEntries.isNotEmpty) overlayEntries.first.opaque = opaque;
break;
case AnimationStatus.forward:
currentStatus = SnackbarStatus.OPENING;
_snackbarStatus(currentStatus);
break;
case AnimationStatus.reverse:
currentStatus = SnackbarStatus.CLOSING;
_snackbarStatus(currentStatus);
if (overlayEntries.isNotEmpty) overlayEntries.first.opaque = false;
break;
case AnimationStatus.dismissed:
assert(!overlayEntries.first.opaque);
// We might still be the current route if a subclass is controlling the
// the transition and hits the dismissed status. For example, the iOS
// back gesture drives this animation to the dismissed status before
// popping the navigator.
currentStatus = SnackbarStatus.CLOSED;
_snackbarStatus(currentStatus);
if (!isCurrent) {
navigator!.finalizeRoute(this);
// assert(overlayEntries.isEmpty);
}
break;
}
changedInternalState();
}
@override
void install() {
assert(!_transitionCompleter.isCompleted,
'Cannot install a $runtimeType after disposing it.');
_controller = createAnimationController();
assert(_controller != null,
'$runtimeType.createAnimationController() returned null.');
_filterBlurAnimation = createBlurFilterAnimation();
_filterColorAnimation = createColorFilterAnimation();
_animation = createAnimation();
assert(_animation != null, '$runtimeType.createAnimation() returned null.');
super.install();
}
@override
TickerFuture didPush() {
super.didPush();
assert(
_controller != null,
// ignore: lines_longer_than_80_chars
'$runtimeType.didPush called before calling install() or after calling dispose().',
);
assert(
!_transitionCompleter.isCompleted,
'Cannot reuse a $runtimeType after disposing it.',
);
_animation!.addStatusListener(_handleStatusChanged);
_configureTimer();
return _controller!.forward();
}
@override
void didReplace(Route<dynamic>? oldRoute) {
assert(
_controller != null,
// ignore: lines_longer_than_80_chars
'$runtimeType.didReplace called before calling install() or after calling dispose().',
);
assert(
!_transitionCompleter.isCompleted,
'Cannot reuse a $runtimeType after disposing it.',
);
if (oldRoute is SnackRoute) {
_controller!.value = oldRoute._controller!.value;
}
_animation!.addStatusListener(_handleStatusChanged);
super.didReplace(oldRoute);
}
@override
bool didPop(T? result) {
assert(
_controller != null,
// ignore: lines_longer_than_80_chars
'$runtimeType.didPop called before calling install() or after calling dispose().',
);
assert(
!_transitionCompleter.isCompleted,
'Cannot reuse a $runtimeType after disposing it.',
);
_result = result;
_cancelTimer();
if (_wasDismissedBySwipe) {
Timer(Duration(milliseconds: 200), () {
_controller!.reset();
});
_wasDismissedBySwipe = false;
} else {
_controller!.reverse();
}
return super.didPop(result);
}
void _configureTimer() {
if (snack.duration != null) {
if (_timer != null && _timer!.isActive) {
_timer!.cancel();
}
_timer = Timer(snack.duration!, () {
if (isCurrent) {
navigator!.pop();
} else if (isActive) {
navigator!.removeRoute(this);
}
});
} else {
if (_timer != null) {
_timer!.cancel();
}
}
}
void _cancelTimer() {
if (_timer != null && _timer!.isActive) {
_timer!.cancel();
}
}
/// Whether this route can perform a transition to the given route.
/// Subclasses can override this method to restrict the set of routes they
/// need to coordinate transitions with.
bool canTransitionTo(SnackRoute<dynamic> nextRoute) => true;
/// Whether this route can perform a transition from the given route.
///
/// Subclasses can override this method to restrict the set of routes they
/// need to coordinate transitions with.
bool canTransitionFrom(SnackRoute<dynamic> previousRoute) => true;
@override
void dispose() {
assert(!_transitionCompleter.isCompleted,
'Cannot dispose a $runtimeType twice.');
_controller?.dispose();
_transitionCompleter.complete(_result);
super.dispose();
}
/// A short description of this route useful for debugging.
String get debugLabel => '$runtimeType';
}