Ahmed Fwela

Merge branch 'master' of git://github.com/jonataslaw/getx

Showing 44 changed files with 2103 additions and 1865 deletions

Too many changes to show.

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

## [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)
... ...
![](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 {
... ...
... ... @@ -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,
}
... ...
... ... @@ -17,5 +17,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(
... ...
... ... @@ -10,6 +10,63 @@ 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;
const GetMaterialApp({
Key? key,
this.navigatorKey,
... ... @@ -72,63 +129,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,
... ... @@ -200,31 +200,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 +245,6 @@ class GetMaterialApp extends StatelessWidget {
? MaterialApp.router(
routerDelegate: routerDelegate!,
routeInformationParser: routeInformationParser!,
scaffoldMessengerKey: scaffoldMessengerKey,
backButtonDispatcher: backButtonDispatcher,
routeInformationProvider: routeInformationProvider,
key: _.unikey,
... ... @@ -283,6 +257,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,
... ... @@ -301,7 +277,8 @@ class GetMaterialApp extends StatelessWidget {
navigatorKey: (navigatorKey == null
? Get.key
: Get.addKey(navigatorKey!)),
scaffoldMessengerKey: scaffoldMessengerKey,
scaffoldMessengerKey:
scaffoldMessengerKey ?? _.scaffoldMessengerKey,
home: home,
routes: routes ?? const <String, WidgetBuilder>{},
initialRoute: initialRoute,
... ... @@ -343,4 +320,29 @@ class GetMaterialApp extends StatelessWidget {
// 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
... ... @@ -485,211 +643,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';
}
import 'dart:async';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import '../../../get_core/get_core.dart';
import '../../get_navigation.dart';
typedef OnTap = void Function(GetSnackBar snack);
typedef SnackbarStatusCallback = void Function(SnackbarStatus? status);
typedef OnTap = void Function(GetBar snack);
class GetBar<T extends Object> extends StatefulWidget {
@Deprecated('use GetSnackBar')
class GetBar extends GetSnackBar {
GetBar({
Key? key,
this.title,
this.message,
this.titleText,
this.messageText,
this.icon,
this.shouldIconPulse = true,
this.maxWidth,
this.margin = const EdgeInsets.all(0.0),
this.padding = const EdgeInsets.all(16),
this.borderRadius = 0.0,
this.borderColor,
this.borderWidth = 1.0,
this.backgroundColor = const Color(0xFF303030),
this.leftBarIndicatorColor,
this.boxShadows,
this.backgroundGradient,
this.mainButton,
this.onTap,
this.duration,
this.isDismissible = true,
this.dismissDirection = SnackDismissDirection.VERTICAL,
this.showProgressIndicator = false,
this.progressIndicatorController,
this.progressIndicatorBackgroundColor,
this.progressIndicatorValueColor,
this.snackPosition = SnackPosition.BOTTOM,
this.snackStyle = SnackStyle.FLOATING,
this.forwardAnimationCurve = Curves.easeOutCirc,
this.reverseAnimationCurve = Curves.easeOutCirc,
this.animationDuration = const Duration(seconds: 1),
this.barBlur = 0.0,
this.overlayBlur = 0.0,
this.overlayColor = Colors.transparent,
this.userInputForm,
String? title,
String? message,
Widget? titleText,
Widget? messageText,
Widget? icon,
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,
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),
double barBlur = 0.0,
double overlayBlur = 0.0,
Color overlayColor = Colors.transparent,
Form? userInputForm,
SnackbarStatusCallback? snackbarStatus,
}) : snackbarStatus = (snackbarStatus ?? (status) {}),
super(key: key);
}) : super(
key: key,
title: title,
message: message,
titleText: titleText,
messageText: messageText,
icon: icon,
shouldIconPulse: shouldIconPulse,
maxWidth: maxWidth,
margin: margin,
padding: padding,
borderRadius: borderRadius,
borderColor: borderColor,
borderWidth: borderWidth,
backgroundColor: backgroundColor,
leftBarIndicatorColor: leftBarIndicatorColor,
boxShadows: boxShadows,
backgroundGradient: backgroundGradient,
mainButton: mainButton,
onTap: onTap,
duration: duration,
isDismissible: isDismissible,
dismissDirection: dismissDirection,
showProgressIndicator: showProgressIndicator,
progressIndicatorController: progressIndicatorController,
progressIndicatorBackgroundColor: progressIndicatorBackgroundColor,
progressIndicatorValueColor: progressIndicatorValueColor,
snackPosition: snackPosition,
snackStyle: snackStyle,
forwardAnimationCurve: forwardAnimationCurve,
reverseAnimationCurve: reverseAnimationCurve,
animationDuration: animationDuration,
barBlur: barBlur,
overlayBlur: overlayBlur,
overlayColor: overlayColor,
userInputForm: userInputForm,
snackbarStatus: snackbarStatus,
);
}
class GetSnackBar extends StatefulWidget {
/// A callback for you to listen to the different Snack status
final SnackbarStatusCallback snackbarStatus;
final SnackbarStatusCallback? snackbarStatus;
/// The title displayed to the user
final String? title;
/// The direction in which the SnackBar can be dismissed.
///
/// Default is [DismissDirection.down] when
/// [snackPosition] == [SnackPosition.BOTTOM] and [DismissDirection.up]
/// when [snackPosition] == [SnackPosition.TOP]
final DismissDirection? dismissDirection;
/// The message displayed to the user.
final String? message;
... ... @@ -80,7 +129,8 @@ class GetBar<T extends Object> extends StatefulWidget {
/// Check (this example)[https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/shadows.dart]
final List<BoxShadow>? boxShadows;
/// Makes [backgroundColor] be ignored.
/// Give to GetSnackbar a gradient background.
/// It Makes [backgroundColor] be ignored.
final Gradient? backgroundGradient;
/// You can use any widget here, but I recommend [Icon] or [Image] as
... ... @@ -91,7 +141,10 @@ class GetBar<T extends Object> extends StatefulWidget {
/// An option to animate the icon (if present). Defaults to true.
final bool shouldIconPulse;
/// A [TextButton] widget if you need an action from the user.
/// (optional) An action that the user can take based on the snack bar.
///
/// For example, the snack bar might let the user undo the operation that
/// prompted the snackbar.
final Widget? mainButton;
/// A callback that registers the user's click anywhere.
... ... @@ -150,11 +203,6 @@ class GetBar<T extends Object> extends StatefulWidget {
/// [SnackPosition.BOTTOM] is the default.
final SnackPosition snackPosition;
/// [SnackDismissDirection.VERTICAL] by default.
/// Can also be [SnackDismissDirection.HORIZONTAL] in which case both left
/// and right dismiss are allowed.
final SnackDismissDirection dismissDirection;
/// Snack can be floating or be grounded to the edge of the screen.
/// If grounded, I do not recommend using [margin] or [borderRadius].
/// [SnackStyle.FLOATING] is the default
... ... @@ -176,7 +224,7 @@ class GetBar<T extends Object> extends StatefulWidget {
/// Default is 0.0. If different than 0.0, blurs only Snack's background.
/// To take effect, make sure your [backgroundColor] has some opacity.
/// The greater the value, the greater the blur.
final double? barBlur;
final double barBlur;
/// Default is 0.0. If different than 0.0, creates a blurred
/// overlay that prevents the user from interacting with the screen.
... ... @@ -192,22 +240,57 @@ class GetBar<T extends Object> extends StatefulWidget {
/// Every other widget is ignored if this is not null.
final Form? userInputForm;
const GetSnackBar({
Key? key,
this.title,
this.message,
this.titleText,
this.messageText,
this.icon,
this.shouldIconPulse = true,
this.maxWidth,
this.margin = const EdgeInsets.all(0.0),
this.padding = const EdgeInsets.all(16),
this.borderRadius = 0.0,
this.borderColor,
this.borderWidth = 1.0,
this.backgroundColor = const Color(0xFF303030),
this.leftBarIndicatorColor,
this.boxShadows,
this.backgroundGradient,
this.mainButton,
this.onTap,
this.duration,
this.isDismissible = true,
this.dismissDirection,
this.showProgressIndicator = false,
this.progressIndicatorController,
this.progressIndicatorBackgroundColor,
this.progressIndicatorValueColor,
this.snackPosition = SnackPosition.BOTTOM,
this.snackStyle = SnackStyle.FLOATING,
this.forwardAnimationCurve = Curves.easeOutCirc,
this.reverseAnimationCurve = Curves.easeOutCirc,
this.animationDuration = const Duration(seconds: 1),
this.barBlur = 0.0,
this.overlayBlur = 0.0,
this.overlayColor = Colors.transparent,
this.userInputForm,
this.snackbarStatus,
}) : super(key: key);
@override
State createState() => GetSnackBarState();
/// Show the snack. It's call [SnackbarStatus.OPENING] state
/// followed by [SnackbarStatus.OPEN]
Future<T?>? show<T>() async {
SnackbarController show() {
return Get.showSnackbar(this);
}
@override
State createState() {
return _GetBarState<T>();
}
}
class _GetBarState<K extends Object> extends State<GetBar>
class GetSnackBarState extends State<GetSnackBar>
with TickerProviderStateMixin {
SnackbarStatus? currentStatus;
AnimationController? _fadeController;
late Animation<double> _fadeAnimation;
... ... @@ -223,6 +306,101 @@ class _GetBarState<K extends Object> extends State<GetBar>
FocusScopeNode? _focusNode;
late FocusAttachment _focusAttachment;
final Completer<Size> _boxHeightCompleter = Completer<Size>();
late CurvedAnimation _progressAnimation;
final _backgroundBoxKey = GlobalKey();
double get buttonPadding {
if (widget.padding.right - 12 < 0) {
return 4;
} else {
return widget.padding.right - 12;
}
}
RowStyle get _rowStyle {
if (widget.mainButton != null && widget.icon == null) {
return RowStyle.action;
} else if (widget.mainButton == null && widget.icon != null) {
return RowStyle.icon;
} else if (widget.mainButton != null && widget.icon != null) {
return RowStyle.all;
} else {
return RowStyle.none;
}
}
@override
Widget build(BuildContext context) {
return Align(
heightFactor: 1.0,
child: Material(
color: widget.snackStyle == SnackStyle.FLOATING
? Colors.transparent
: widget.backgroundColor,
child: SafeArea(
minimum: widget.snackPosition == SnackPosition.BOTTOM
? EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom)
: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
bottom: widget.snackPosition == SnackPosition.BOTTOM,
top: widget.snackPosition == SnackPosition.TOP,
left: false,
right: false,
child: Stack(
children: [
FutureBuilder<Size>(
future: _boxHeightCompleter.future,
builder: (context, snapshot) {
if (snapshot.hasData) {
if (widget.barBlur == 0) {
return _emptyWidget;
}
return ClipRRect(
borderRadius: BorderRadius.circular(widget.borderRadius),
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: widget.barBlur, sigmaY: widget.barBlur),
child: Container(
height: snapshot.data!.height,
width: snapshot.data!.width,
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius:
BorderRadius.circular(widget.borderRadius),
),
),
),
);
} else {
return _emptyWidget;
}
},
),
if (widget.userInputForm != null)
_containerWithForm()
else
_containerWithoutForm()
],
),
),
),
);
}
@override
void dispose() {
_fadeController?.dispose();
widget.progressIndicatorController?.removeListener(_updateProgress);
widget.progressIndicatorController?.dispose();
_focusAttachment.detach();
_focusNode!.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
... ... @@ -231,9 +409,8 @@ class _GetBarState<K extends Object> extends State<GetBar>
widget.userInputForm != null ||
((widget.message != null && widget.message!.isNotEmpty) ||
widget.messageText != null),
"""
A message is mandatory if you are not using userInputForm.
Set either a message or messageText""");
'''
You need to either use message[String], or messageText[Widget] or define a userInputForm[Form] in GetSnackbar''');
_isTitlePresent = (widget.title != null || widget.titleText != null);
_messageTopMargin = _isTitlePresent ? 6.0 : widget.padding.top;
... ... @@ -250,25 +427,31 @@ Set either a message or messageText""");
_focusAttachment = _focusNode!.attach(context);
}
@override
void dispose() {
_fadeController?.dispose();
widget.progressIndicatorController?.removeListener(_progressListener);
widget.progressIndicatorController?.dispose();
_focusAttachment.detach();
_focusNode!.dispose();
super.dispose();
Widget _buildLeftBarIndicator() {
if (widget.leftBarIndicatorColor != null) {
return FutureBuilder<Size>(
future: _boxHeightCompleter.future,
builder: (buildContext, snapshot) {
if (snapshot.hasData) {
return Container(
color: widget.leftBarIndicatorColor,
width: 5.0,
height: snapshot.data!.height,
);
} else {
return _emptyWidget;
}
},
);
} else {
return _emptyWidget;
}
}
final Completer<Size> _boxHeightCompleter = Completer<Size>();
void _configureLeftBarFuture() {
SchedulerBinding.instance!.addPostFrameCallback(
(_) {
final keyContext = backgroundBoxKey.currentContext;
final keyContext = _backgroundBoxKey.currentContext;
if (keyContext != null) {
final box = keyContext.findRenderObject() as RenderBox;
_boxHeightCompleter.complete(box.size);
... ... @@ -277,6 +460,16 @@ Set either a message or messageText""");
);
}
void _configureProgressIndicatorAnimation() {
if (widget.showProgressIndicator &&
widget.progressIndicatorController != null) {
widget.progressIndicatorController!.addListener(_updateProgress);
_progressAnimation = CurvedAnimation(
curve: Curves.linear, parent: widget.progressIndicatorController!);
}
}
void _configurePulseAnimation() {
_fadeController =
AnimationController(vsync: this, duration: _pulseAnimationDuration);
... ... @@ -299,95 +492,9 @@ Set either a message or messageText""");
_fadeController!.forward();
}
late VoidCallback _progressListener;
void _configureProgressIndicatorAnimation() {
if (widget.showProgressIndicator &&
widget.progressIndicatorController != null) {
_progressListener = () {
setState(() {});
};
widget.progressIndicatorController!.addListener(_progressListener);
_progressAnimation = CurvedAnimation(
curve: Curves.linear, parent: widget.progressIndicatorController!);
}
}
@override
Widget build(BuildContext context) {
return Align(
heightFactor: 1.0,
child: Material(
color: widget.snackStyle == SnackStyle.FLOATING
? Colors.transparent
: widget.backgroundColor,
child: SafeArea(
minimum: widget.snackPosition == SnackPosition.BOTTOM
? EdgeInsets.only(
// bottom: (GetUtils.isGreaterThan(
// MediaQuery.of(context).viewInsets.bottom,
// MediaQuery.of(context).padding.bottom)
// ? MediaQuery.of(context).viewInsets.bottom
// : MediaQuery.of(context).padding.bottom))
bottom: MediaQuery.of(context).viewInsets.bottom)
: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
bottom: widget.snackPosition == SnackPosition.BOTTOM,
top: widget.snackPosition == SnackPosition.TOP,
left: false,
right: false,
child: _getSnack(),
),
),
);
}
Widget _getSnack() {
Widget snack;
if (widget.userInputForm != null) {
snack = _generateInputSnack();
} else {
snack = _generateSnack();
}
return Stack(
children: [
FutureBuilder<Size>(
future: _boxHeightCompleter.future,
builder: (context, snapshot) {
if (snapshot.hasData) {
if (widget.barBlur == 0) {
return _emptyWidget;
}
return ClipRRect(
borderRadius: BorderRadius.circular(widget.borderRadius),
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: widget.barBlur!, sigmaY: widget.barBlur!),
child: Container(
height: snapshot.data!.height,
width: snapshot.data!.width,
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(widget.borderRadius),
),
),
),
);
} else {
return _emptyWidget;
}
},
),
snack,
],
);
}
Widget _generateInputSnack() {
Widget _containerWithForm() {
return Container(
key: backgroundBoxKey,
key: _backgroundBoxKey,
constraints: widget.maxWidth != null
? BoxConstraints(maxWidth: widget.maxWidth!)
: null,
... ... @@ -397,7 +504,10 @@ Set either a message or messageText""");
boxShadow: widget.boxShadows,
borderRadius: BorderRadius.circular(widget.borderRadius),
border: widget.borderColor != null
? Border.all(color: widget.borderColor!, width: widget.borderWidth!)
? Border.all(
color: widget.borderColor!,
width: widget.borderWidth!,
)
: null,
),
child: Padding(
... ... @@ -412,12 +522,16 @@ Set either a message or messageText""");
);
}
late CurvedAnimation _progressAnimation;
GlobalKey backgroundBoxKey = GlobalKey();
Widget _generateSnack() {
Widget _containerWithoutForm() {
final iconPadding = widget.padding.left > 16.0 ? widget.padding.left : 0.0;
final left = _rowStyle == RowStyle.icon || _rowStyle == RowStyle.all
? 4.0
: widget.padding.left;
final right = _rowStyle == RowStyle.action || _rowStyle == RowStyle.all
? 8.0
: widget.padding.right;
return Container(
key: backgroundBoxKey,
key: _backgroundBoxKey,
constraints: widget.maxWidth != null
? BoxConstraints(maxWidth: widget.maxWidth!)
: null,
... ... @@ -444,192 +558,66 @@ Set either a message or messageText""");
: _emptyWidget,
Row(
mainAxisSize: MainAxisSize.max,
children: _getAppropriateRowLayout(),
),
],
),
);
}
List<Widget> _getAppropriateRowLayout() {
double buttonRightPadding;
var iconPadding = 0.0;
if (widget.padding.right - 12 < 0) {
buttonRightPadding = 4;
} else {
buttonRightPadding = widget.padding.right - 12;
}
if (widget.padding.left > 16.0) {
iconPadding = widget.padding.left;
}
if (widget.icon == null && widget.mainButton == null) {
return [
_buildLeftBarIndicator(),
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
(_isTitlePresent)
? Padding(
padding: EdgeInsets.only(
top: widget.padding.top,
left: widget.padding.left,
right: widget.padding.right,
),
child: _getTitleText(),
)
: _emptyWidget,
Padding(
padding: EdgeInsets.only(
top: _messageTopMargin,
left: widget.padding.left,
right: widget.padding.right,
bottom: widget.padding.bottom,
children: [
_buildLeftBarIndicator(),
if (_rowStyle == RowStyle.icon || _rowStyle == RowStyle.all)
ConstrainedBox(
constraints:
BoxConstraints.tightFor(width: 42.0 + iconPadding),
child: _getIcon(),
),
child: widget.messageText ?? _getDefaultNotificationText(),
),
],
),
),
];
} else if (widget.icon != null && widget.mainButton == null) {
return <Widget>[
_buildLeftBarIndicator(),
ConstrainedBox(
constraints: BoxConstraints.tightFor(width: 42.0 + iconPadding),
child: _getIcon(),
),
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
(_isTitlePresent)
? Padding(
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
if (_isTitlePresent)
Padding(
padding: EdgeInsets.only(
top: widget.padding.top,
left: left,
right: right,
),
child: widget.titleText ??
Text(
widget.title ?? "",
style: TextStyle(
fontSize: 16.0,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
)
else
_emptyWidget,
Padding(
padding: EdgeInsets.only(
top: widget.padding.top,
left: 4.0,
right: widget.padding.left,
top: _messageTopMargin,
left: left,
right: right,
bottom: widget.padding.bottom,
),
child: _getTitleText(),
)
: _emptyWidget,
Padding(
padding: EdgeInsets.only(
top: _messageTopMargin,
left: 4.0,
right: widget.padding.right,
bottom: widget.padding.bottom,
),
child: widget.messageText ?? _getDefaultNotificationText(),
),
],
),
),
];
} else if (widget.icon == null && widget.mainButton != null) {
return <Widget>[
_buildLeftBarIndicator(),
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
(_isTitlePresent)
? Padding(
padding: EdgeInsets.only(
top: widget.padding.top,
left: widget.padding.left,
right: widget.padding.right,
),
child: _getTitleText(),
)
: _emptyWidget,
Padding(
padding: EdgeInsets.only(
top: _messageTopMargin,
left: widget.padding.left,
right: 8.0,
bottom: widget.padding.bottom,
child: widget.messageText ??
Text(
widget.message ?? "",
style:
TextStyle(fontSize: 14.0, color: Colors.white),
),
),
],
),
child: widget.messageText ?? _getDefaultNotificationText(),
),
],
),
),
Padding(
padding: EdgeInsets.only(right: buttonRightPadding),
child: _getMainActionButton(),
),
];
} else {
return <Widget>[
_buildLeftBarIndicator(),
ConstrainedBox(
constraints: BoxConstraints.tightFor(width: 42.0 + iconPadding),
child: _getIcon(),
),
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
(_isTitlePresent)
? Padding(
padding: EdgeInsets.only(
top: widget.padding.top,
left: 4.0,
right: 8.0,
),
child: _getTitleText(),
)
: _emptyWidget,
Padding(
padding: EdgeInsets.only(
top: _messageTopMargin,
left: 4.0,
right: 8.0,
bottom: widget.padding.bottom,
if (_rowStyle == RowStyle.action || _rowStyle == RowStyle.all)
Padding(
padding: EdgeInsets.only(right: buttonPadding),
child: widget.mainButton,
),
child: widget.messageText ?? _getDefaultNotificationText(),
),
],
),
),
Padding(
padding: EdgeInsets.only(right: buttonRightPadding),
child: _getMainActionButton(),
),
];
}
}
Widget _buildLeftBarIndicator() {
if (widget.leftBarIndicatorColor != null) {
return FutureBuilder<Size>(
future: _boxHeightCompleter.future,
builder: (buildContext, snapshot) {
if (snapshot.hasData) {
return Container(
color: widget.leftBarIndicatorColor,
width: 5.0,
height: snapshot.data!.height,
);
} else {
return _emptyWidget;
}
},
);
} else {
return _emptyWidget;
}
],
),
);
}
Widget? _getIcon() {
... ... @@ -645,37 +633,15 @@ Set either a message or messageText""");
}
}
Widget _getTitleText() {
return widget.titleText ??
Text(
widget.title ?? "",
style: TextStyle(
fontSize: 16.0, color: Colors.white, fontWeight: FontWeight.bold),
);
}
Text _getDefaultNotificationText() {
return Text(
widget.message ?? "",
style: TextStyle(fontSize: 14.0, color: Colors.white),
);
}
Widget? _getMainActionButton() {
return widget.mainButton;
}
void _updateProgress() => setState(() {});
}
/// Indicates if snack is going to start at the [TOP] or at the [BOTTOM]
enum SnackPosition { TOP, BOTTOM }
/// Indicates if snack will be attached to the edge of the screen or not
enum SnackStyle { FLOATING, GROUNDED }
/// Indicates the direction in which it is possible to dismiss
/// If vertical, dismiss up will be allowed if [SnackPosition.TOP]
/// If vertical, dismiss down will be allowed if [SnackPosition.BOTTOM]
enum SnackDismissDirection { HORIZONTAL, VERTICAL }
enum RowStyle {
icon,
action,
all,
none,
}
/// Indicates Status of snackbar
/// [SnackbarStatus.OPEN] Snack is fully open, [SnackbarStatus.CLOSED] Snackbar
... ... @@ -686,3 +652,9 @@ enum SnackDismissDirection { HORIZONTAL, VERTICAL }
/// and ends
/// with the full snackbar dispose
enum SnackbarStatus { OPEN, CLOSED, OPENING, CLOSING }
/// Indicates if snack is going to start at the [TOP] or at the [BOTTOM]
enum SnackPosition { TOP, BOTTOM }
/// Indicates if snack will be attached to the edge of the screen or not
enum SnackStyle { FLOATING, GROUNDED }
... ...
import 'dart:async';
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
import '../../../get.dart';
class SnackbarController {
static final _snackBarQueue = _SnackBarQueue();
static bool get isSnackbarBeingShown => _snackBarQueue._isJobInProgress;
final key = GlobalKey<GetSnackBarState>();
late Animation<double> _filterBlurAnimation;
late Animation<Color?> _filterColorAnimation;
final GetSnackBar snackbar;
final _transitionCompleter = Completer();
late SnackbarStatusCallback? _snackbarStatus;
late final Alignment? _initialAlignment;
late final Alignment? _endAlignment;
bool _wasDismissedBySwipe = false;
bool _onTappedDismiss = false;
Timer? _timer;
/// The animation that drives the route's transition and the previous route's
/// forward transition.
late final Animation<Alignment> _animation;
/// The animation controller that the route uses to drive the transitions.
///
/// The animation itself is exposed by the [animation] property.
late final AnimationController _controller;
SnackbarStatus? _currentStatus;
final _overlayEntries = <OverlayEntry>[];
OverlayState? _overlayState;
SnackbarController(this.snackbar);
Future<void> get future => _transitionCompleter.future;
/// Close the snackbar with animation
Future<void> close({bool withAnimations = true}) async {
if (!withAnimations) {
_removeOverlay();
return;
}
_removeEntry();
await future;
}
/// Adds GetSnackbar to a view queue.
/// Only one GetSnackbar will be displayed at a time, and this method returns
/// a future to when the snackbar disappears.
Future<void> show() {
return _snackBarQueue._addJob(this);
}
void _cancelTimer() {
if (_timer != null && _timer!.isActive) {
_timer!.cancel();
}
}
// ignore: avoid_returning_this
void _configureAlignment(SnackPosition snackPosition) {
switch (snackbar.snackPosition) {
case SnackPosition.TOP:
{
_initialAlignment = const Alignment(-1.0, -2.0);
_endAlignment = const Alignment(-1.0, -1.0);
break;
}
case SnackPosition.BOTTOM:
{
_initialAlignment = const Alignment(-1.0, 2.0);
_endAlignment = const Alignment(-1.0, 1.0);
break;
}
}
}
void _configureOverlay() {
_overlayState = Overlay.of(Get.overlayContext!);
_overlayEntries.clear();
_overlayEntries.addAll(_createOverlayEntries(_getBodyWidget()));
_overlayState!.insertAll(_overlayEntries);
_configureSnackBarDisplay();
}
void _configureSnackBarDisplay() {
assert(!_transitionCompleter.isCompleted,
'Cannot configure a snackbar after disposing it.');
_controller = _createAnimationController();
_configureAlignment(snackbar.snackPosition);
_snackbarStatus = snackbar.snackbarStatus;
_filterBlurAnimation = _createBlurFilterAnimation();
_filterColorAnimation = _createColorOverlayColor();
_animation = _createAnimation();
_animation.addStatusListener(_handleStatusChanged);
_configureTimer();
_controller.forward();
}
void _configureTimer() {
if (snackbar.duration != null) {
if (_timer != null && _timer!.isActive) {
_timer!.cancel();
}
_timer = Timer(snackbar.duration!, _removeEntry);
} else {
if (_timer != null) {
_timer!.cancel();
}
}
}
/// 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 create a animation from a disposed snackbar');
return AlignmentTween(begin: _initialAlignment, end: _endAlignment).animate(
CurvedAnimation(
parent: _controller,
curve: snackbar.forwardAnimationCurve,
reverseCurve: snackbar.reverseAnimationCurve,
),
);
}
/// 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 create a animationController from a disposed snackbar');
assert(snackbar.animationDuration >= Duration.zero);
return AnimationController(
duration: snackbar.animationDuration,
debugLabel: '$runtimeType',
vsync: _overlayState!,
);
}
Animation<double> _createBlurFilterAnimation() {
return Tween(begin: 0.0, end: snackbar.overlayBlur).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(
0.0,
0.35,
curve: Curves.easeInOutCirc,
),
),
);
}
Animation<Color?> _createColorOverlayColor() {
return ColorTween(
begin: const Color(0x00000000), end: snackbar.overlayColor)
.animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(
0.0,
0.35,
curve: Curves.easeInOutCirc,
),
),
);
}
Iterable<OverlayEntry> _createOverlayEntries(Widget child) {
return <OverlayEntry>[
if (snackbar.overlayBlur > 0.0) ...[
OverlayEntry(
builder: (context) => GestureDetector(
onTap: () {
if (snackbar.isDismissible && !_onTappedDismiss) {
_onTappedDismiss = true;
close();
}
},
child: AnimatedBuilder(
animation: _filterBlurAnimation,
builder: (context, child) {
return BackdropFilter(
filter: ImageFilter.blur(
sigmaX: max(0.001, _filterBlurAnimation.value),
sigmaY: max(0.001, _filterBlurAnimation.value),
),
child: Container(
constraints: const BoxConstraints.expand(),
color: _filterColorAnimation.value,
),
);
},
),
),
maintainState: false,
opaque: false,
),
],
OverlayEntry(
builder: (context) => Semantics(
child: AlignTransition(
alignment: _animation,
child: snackbar.isDismissible
? _getDismissibleSnack(child)
: _getSnackbarContainer(child),
),
focused: false,
container: true,
explicitChildNodes: true,
),
maintainState: false,
opaque: false,
),
];
}
Widget _getBodyWidget() {
return Builder(builder: (_) {
return GestureDetector(
child: snackbar,
onTap: snackbar.onTap != null
? () => snackbar.onTap?.call(snackbar)
: null,
);
});
}
DismissDirection _getDefaultDismissDirection() {
if (snackbar.snackPosition == SnackPosition.TOP) {
return DismissDirection.up;
}
return DismissDirection.down;
}
Widget _getDismissibleSnack(Widget child) {
return Dismissible(
direction: snackbar.dismissDirection ?? _getDefaultDismissDirection(),
resizeDuration: null,
confirmDismiss: (_) {
if (_currentStatus == SnackbarStatus.OPENING ||
_currentStatus == SnackbarStatus.CLOSING) {
return Future.value(false);
}
return Future.value(true);
},
key: const Key('dismissible'),
onDismissed: (_) {
_wasDismissedBySwipe = true;
_removeEntry();
},
child: _getSnackbarContainer(child),
);
}
Widget _getSnackbarContainer(Widget child) {
return Container(
margin: snackbar.margin,
child: child,
);
}
void _handleStatusChanged(AnimationStatus status) {
switch (status) {
case AnimationStatus.completed:
_currentStatus = SnackbarStatus.OPEN;
_snackbarStatus?.call(_currentStatus);
if (_overlayEntries.isNotEmpty) _overlayEntries.first.opaque = false;
break;
case AnimationStatus.forward:
_currentStatus = SnackbarStatus.OPENING;
_snackbarStatus?.call(_currentStatus);
break;
case AnimationStatus.reverse:
_currentStatus = SnackbarStatus.CLOSING;
_snackbarStatus?.call(_currentStatus);
if (_overlayEntries.isNotEmpty) _overlayEntries.first.opaque = false;
break;
case AnimationStatus.dismissed:
assert(!_overlayEntries.first.opaque);
_currentStatus = SnackbarStatus.CLOSED;
_snackbarStatus?.call(_currentStatus);
_removeOverlay();
break;
}
}
void _removeEntry() {
assert(
!_transitionCompleter.isCompleted,
'Cannot remove entry from a disposed snackbar',
);
_cancelTimer();
if (_wasDismissedBySwipe) {
Timer(const Duration(milliseconds: 200), _controller.reset);
_wasDismissedBySwipe = false;
} else {
_controller.reverse();
}
}
void _removeOverlay() {
for (var element in _overlayEntries) {
element.remove();
}
assert(!_transitionCompleter.isCompleted,
'Cannot remove overlay from a disposed snackbar');
_controller.dispose();
_overlayEntries.clear();
_transitionCompleter.complete();
}
Future<void> _show() {
_configureOverlay();
return future;
}
static void cancelAllSnackbars() {
_snackBarQueue._cancelAllJobs();
}
static Future<void> closeCurrentSnackbar() async {
await _snackBarQueue._closeCurrentJob();
}
}
class _SnackBarQueue {
final _queue = GetQueue();
final _snackbarList = <SnackbarController>[];
SnackbarController? get _currentSnackbar {
if (_snackbarList.isEmpty) return null;
return _snackbarList.first;
}
bool get _isJobInProgress => _snackbarList.isNotEmpty;
Future<void> _addJob(SnackbarController job) async {
_snackbarList.add(job);
final data = await _queue.add(job._show);
_snackbarList.remove(job);
return data;
}
Future<void> _cancelAllJobs() async {
await _currentSnackbar?.close();
_queue.cancelAllJobs();
_snackbarList.clear();
}
Future<void> _closeCurrentJob() async {
if (_currentSnackbar == null) return;
await _currentSnackbar!.close();
}
}
... ...
... ... @@ -5,6 +5,8 @@ part of rx_types;
/// This interface is the contract that _RxImpl]<T> uses in all it's
/// subclass.
abstract class RxInterface<T> {
static RxInterface? proxy;
bool get canUpdate;
/// Adds a listener to stream
... ... @@ -13,8 +15,6 @@ abstract class RxInterface<T> {
/// Close the Rx Variable
void close();
static RxInterface? proxy;
/// Calls `callback` with current value, when the value changes.
StreamSubscription<T> listen(void Function(T event) onData,
{Function? onError, void Function()? onDone, bool? cancelOnError});
... ...
// ignore_for_file: lines_longer_than_80_chars
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import '../../get_state_manager.dart';
/// Used like `SingleTickerProviderMixin` but only with Get Controllers.
... ... @@ -7,6 +12,84 @@ import '../../get_state_manager.dart';
/// Example:
///```
///class SplashController extends GetxController with
/// GetSingleTickerProviderStateMixin {
/// AnimationController controller;
///
/// @override
/// void onInit() {
/// final duration = const Duration(seconds: 2);
/// controller =
/// AnimationController.unbounded(duration: duration, vsync: this);
/// controller.repeat();
/// controller.addListener(() =>
/// print("Animation Controller value: ${controller.value}"));
/// }
/// ...
/// ```
mixin GetSingleTickerProviderStateMixin on GetxController
implements TickerProvider {
Ticker? _ticker;
@override
Ticker createTicker(TickerCallback onTick) {
assert(() {
if (_ticker == null) return true;
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary(
'$runtimeType is a SingleTickerProviderStateMixin but multiple tickers were created.'),
ErrorDescription(
'A SingleTickerProviderStateMixin can only be used as a TickerProvider once.'),
ErrorHint(
'If a State is used for multiple AnimationController objects, or if it is passed to other '
'objects and those objects might use it more than one time in total, then instead of '
'mixing in a SingleTickerProviderStateMixin, use a regular TickerProviderStateMixin.',
),
]);
}());
_ticker =
Ticker(onTick, debugLabel: kDebugMode ? 'created by $this' : null);
// We assume that this is called from initState, build, or some sort of
// event handler, and that thus TickerMode.of(context) would return true. We
// can't actually check that here because if we're in initState then we're
// not allowed to do inheritance checks yet.
return _ticker!;
}
void didChangeDependencies(BuildContext context) {
if (_ticker != null) _ticker!.muted = !TickerMode.of(context);
}
@override
void onClose() {
assert(() {
if (_ticker == null || !_ticker!.isActive) return true;
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('$this was disposed with an active Ticker.'),
ErrorDescription(
'$runtimeType created a Ticker via its SingleTickerProviderStateMixin, but at the time '
'dispose() was called on the mixin, that Ticker was still active. The Ticker must '
'be disposed before calling super.dispose().',
),
ErrorHint(
'Tickers used by AnimationControllers '
'should be disposed by calling dispose() on the AnimationController itself. '
'Otherwise, the ticker will leak.',
),
_ticker!.describeForError('The offending ticker was'),
]);
}());
super.onClose();
}
}
@Deprecated('use GetSingleTickerProviderStateMixin')
/// Used like `SingleTickerProviderMixin` but only with Get Controllers.
/// Simplifies AnimationController creation inside GetxController.
///
/// Example:
///```
///class SplashController extends GetxController with
/// SingleGetTickerProviderMixin {
/// AnimationController _ac;
///
... ...
... ... @@ -75,14 +75,14 @@ mixin ScrollMixin on GetLifeCycleBase {
abstract class RxController extends DisposableInterface {}
abstract class SuperController<T> extends FullLifeCycleController
with FullLifeCycle, StateMixin<T> {}
with FullLifeCycleMixin, StateMixin<T> {}
abstract class FullLifeCycleController extends GetxController
with
// ignore: prefer_mixin
WidgetsBindingObserver {}
mixin FullLifeCycle on FullLifeCycleController {
mixin FullLifeCycleMixin on FullLifeCycleController {
@mustCallSuper
@override
void onInit() {
... ...
import 'dart:ui';
import '../../../get_core/get_core.dart';
class _IntlHost {
Locale? locale;
Locale? fallbackLocale;
Map<String, Map<String, String>> translations = {};
}
extension FirstWhereExt<T> on List<T> {
/// The first element satisfying [test], or `null` if there are none.
T? firstWhereOrNull(bool Function(T element) test) {
for (var element in this) {
if (test(element)) return element;
}
return null;
}
}
extension LocalesIntl on GetInterface {
static final _intlHost = _IntlHost();
Locale? get locale => _intlHost.locale;
Locale? get fallbackLocale => _intlHost.fallbackLocale;
set locale(Locale? newLocale) => _intlHost.locale = newLocale;
set fallbackLocale(Locale? newLocale) => _intlHost.fallbackLocale = newLocale;
Map<String, Map<String, String>> get translations => _intlHost.translations;
void addTranslations(Map<String, Map<String, String>> tr) {
translations.addAll(tr);
}
void clearTranslations() {
translations.clear();
}
void appendTranslations(Map<String, Map<String, String>> tr) {
tr.forEach((key, map) {
if (translations.containsKey(key)) {
translations[key]!.addAll(map);
} else {
translations[key] = map;
}
});
}
}
extension Trans on String {
// Checks whether the language code and country code are present, and
// whether the key is also present.
bool get _fullLocaleAndKey {
return Get.translations.containsKey(
"${Get.locale!.languageCode}_${Get.locale!.countryCode}") &&
Get.translations[
"${Get.locale!.languageCode}_${Get.locale!.countryCode}"]!
.containsKey(this);
}
// Checks if there is a callback language in the absence of the specific
// country, and if it contains that key.
Map<String, String>? get _getSimilarLanguageTranslation {
final translationsWithNoCountry = Get.translations
.map((key, value) => MapEntry(key.split("_").first, value));
final containsKey =
translationsWithNoCountry.containsKey(Get.locale!.languageCode);
if (!containsKey) {
return null;
}
return translationsWithNoCountry[Get.locale!.languageCode];
}
String get tr {
// print('language');
// print(Get.locale!.languageCode);
// print('contains');
// print(Get.translations.containsKey(Get.locale!.languageCode));
// print(Get.translations.keys);
// Returns the key if locale is null.
if (Get.locale?.languageCode == null) return this;
// Checks whether the language code and country code are present, and
// whether the key is also present.
if (Get.translations.containsKey(
"${Get.locale!.languageCode}_${Get.locale!.countryCode}") &&
Get.translations[
"${Get.locale!.languageCode}_${Get.locale!.countryCode}"]!
.containsKey(this)) {
if (_fullLocaleAndKey) {
return Get.translations[
"${Get.locale!.languageCode}_${Get.locale!.countryCode}"]![this]!;
// Checks if there is a callback language in the absence of the specific
// country, and if it contains that key.
} else if (Get.translations.containsKey(Get.locale!.languageCode) &&
Get.translations[Get.locale!.languageCode]!.containsKey(this)) {
return Get.translations[Get.locale!.languageCode]![this]!;
}
final similarTranslation = _getSimilarLanguageTranslation;
if (similarTranslation != null && similarTranslation.containsKey(this)) {
return similarTranslation[this]!;
// If there is no corresponding language or corresponding key, return
// the key.
} else if (Get.fallbackLocale != null) {
... ... @@ -70,43 +143,3 @@ extension Trans on String {
return i == 1 ? trParams(params) : pluralKey!.trParams(params);
}
}
class _IntlHost {
Locale? locale;
Locale? fallbackLocale;
Map<String, Map<String, String>> translations = {};
}
extension LocalesIntl on GetInterface {
static final _intlHost = _IntlHost();
Locale? get locale => _intlHost.locale;
Locale? get fallbackLocale => _intlHost.fallbackLocale;
set locale(Locale? newLocale) => _intlHost.locale = newLocale;
set fallbackLocale(Locale? newLocale) => _intlHost.fallbackLocale = newLocale;
Map<String, Map<String, String>> get translations => _intlHost.translations;
void addTranslations(Map<String, Map<String, String>> tr) {
translations.addAll(tr);
}
void clearTranslations() {
translations.clear();
}
void appendTranslations(Map<String, Map<String, String>> tr) {
tr.forEach((key, map) {
if (translations.containsKey(key)) {
translations[key]!.addAll(map);
} else {
translations[key] = map;
}
});
}
}
... ...
... ... @@ -4,8 +4,8 @@ class GetMicrotask {
int _version = 0;
int _microtask = 0;
int get version => _version;
int get microtask => _microtask;
int get version => _version;
void exec(Function callback) {
if (_microtask == _version) {
... ... @@ -23,6 +23,17 @@ class GetQueue {
final List<_Item> _queue = [];
bool _active = false;
Future<T> add<T>(Function job) {
var completer = Completer<T>();
_queue.add(_Item(completer, job));
_check();
return completer.future;
}
void cancelAllJobs() {
_queue.clear();
}
void _check() async {
if (!_active && _queue.isNotEmpty) {
_active = true;
... ... @@ -36,13 +47,6 @@ class GetQueue {
_check();
}
}
Future<T> add<T>(Function job) {
var completer = Completer<T>();
_queue.add(_Item(completer, job));
_check();
return completer.future;
}
}
class _Item {
... ...
name: get
description: Open screens/snackbars/dialogs without context, manage states and inject dependencies easily with GetX.
version: 4.3.8
version: 4.5.1
homepage: https://github.com/jonataslaw/getx
environment:
... ...
... ... @@ -40,12 +40,16 @@ void main() {
});
test('Get start and delete called just one time', () async {
Get..put(Controller())..put(Controller());
Get
..put(Controller())
..put(Controller());
final controller = Get.find<Controller>();
expect(controller.init, 1);
Get..delete<Controller>()..delete<Controller>();
Get
..delete<Controller>()
..delete<Controller>();
expect(controller.close, 1);
Get.reset();
});
... ...
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import '../navigation/utils/wrapper.dart';
void main() {
testWidgets("Get.defaultDialog smoke test", (tester) async {
await tester.pumpWidget(
Wrapper(child: Container()),
);
await tester.pumpAndSettle();
expect('covid'.tr, 'Corona Virus');
expect('total_confirmed'.tr, 'Total Confirmed');
expect('total_deaths'.tr, 'Total Deaths');
Get.updateLocale(Locale('pt', 'BR'));
await tester.pumpAndSettle();
expect('covid'.tr, 'Corona Vírus');
expect('total_confirmed'.tr, 'Total confirmado');
expect('total_deaths'.tr, 'Total de mortes');
Get.updateLocale(Locale('en', 'EN'));
await tester.pumpAndSettle();
expect('covid'.tr, 'Corona Virus');
expect('total_confirmed'.tr, 'Total Confirmed');
expect('total_deaths'.tr, 'Total Deaths');
});
}
... ...
... ... @@ -480,59 +480,6 @@ void main() {
expect(find.byType(FirstScreen), findsOneWidget);
});
testWidgets("Get.snackbar test", (tester) async {
await tester.pumpWidget(
GetMaterialApp(
popGesture: true,
home: ElevatedButton(
child: Text('Open Snackbar'),
onPressed: () {
Get.snackbar('title', "message", duration: Duration(seconds: 1));
},
),
),
);
expect(Get.isSnackbarOpen, false);
await tester.tap(find.text('Open Snackbar'));
expect(Get.isSnackbarOpen, true);
await tester.pump(const Duration(seconds: 1));
});
testWidgets("Get.rawSnackbar test", (tester) async {
await tester.pumpWidget(
Wrapper(
child: ElevatedButton(
child: Text('Open Snackbar'),
onPressed: () {
Get.rawSnackbar(
title: 'title',
message: "message",
onTap: (_) {
print('snackbar tapped');
},
duration: Duration(seconds: 1),
shouldIconPulse: true,
icon: Icon(Icons.alarm),
showProgressIndicator: true,
barBlur: null,
isDismissible: true,
leftBarIndicatorColor: Colors.amber,
overlayBlur: 1.0,
);
},
),
),
);
expect(Get.isSnackbarOpen, false);
await tester.tap(find.text('Open Snackbar'));
expect(Get.isSnackbarOpen, true);
await tester.pump(const Duration(seconds: 1));
});
}
class FirstScreen extends StatelessWidget {
... ...