first commit

This commit is contained in:
Hamza-Ayed
2026-06-09 08:40:31 +03:00
commit d8901e1a87
3161 changed files with 536187 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
/// GetX is an extra-light and powerful multiplatform framework.
/// It combines high performance state management, intelligent dependency
/// injection, and route management in a quick and practical way.
library get;
export 'get_common/get_reset.dart';
export 'get_connect/connect.dart';
export 'get_core/get_core.dart';
export 'get_instance/get_instance.dart';
export 'get_navigation/get_navigation.dart';
export 'get_rx/get_rx.dart';
export 'get_state_manager/get_state_manager.dart';
export 'get_utils/get_utils.dart';
export 'route_manager.dart';

View File

@@ -0,0 +1,11 @@
import '../get.dart';
extension GetResetExt on GetInterface {
void reset(
{@deprecated bool clearFactory = true, bool clearRouteBindings = true}) {
GetInstance().resetInstance(clearRouteBindings: clearRouteBindings);
Get.clearRouteTree();
Get.clearTranslations();
Get.resetRootNavigator();
}
}

View File

@@ -0,0 +1 @@
export 'get_connect/connect.dart';

View File

@@ -0,0 +1,396 @@
import '../get_instance/src/lifecycle.dart';
import 'http/src/certificates/certificates.dart';
import 'http/src/exceptions/exceptions.dart';
import 'http/src/http.dart';
import 'http/src/response/response.dart';
import 'sockets/sockets.dart';
export 'http/src/certificates/certificates.dart';
export 'http/src/http.dart';
export 'http/src/multipart/form_data.dart';
export 'http/src/multipart/multipart_file.dart';
export 'http/src/response/response.dart';
export 'sockets/sockets.dart';
abstract class GetConnectInterface with GetLifeCycleBase {
List<GetSocket>? sockets;
GetHttpClient get httpClient;
Future<Response<T>> get<T>(
String url, {
Map<String, String>? headers,
String? contentType,
Map<String, dynamic>? query,
Decoder<T>? decoder,
});
Future<Response<T>> request<T>(
String url,
String method, {
dynamic body,
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
});
Future<Response<T>> post<T>(
String url,
dynamic body, {
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
});
Future<Response<T>> put<T>(
String url,
dynamic body, {
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
});
Future<Response<T>> delete<T>(
String url, {
Map<String, String>? headers,
String? contentType,
Map<String, dynamic>? query,
Decoder<T>? decoder,
});
Future<Response<T>> patch<T>(
String url,
dynamic body, {
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
});
Future<GraphQLResponse<T>> query<T>(
String query, {
String? url,
Map<String, dynamic>? variables,
Map<String, String>? headers,
});
Future<GraphQLResponse<T>> mutation<T>(
String mutation, {
String? url,
Map<String, dynamic>? variables,
Map<String, String>? headers,
});
GetSocket socket(
String url, {
Duration ping = const Duration(seconds: 5),
});
}
class GetConnect extends GetConnectInterface {
GetConnect({
this.userAgent = 'getx-client',
this.timeout = const Duration(seconds: 5),
this.followRedirects = true,
this.maxRedirects = 5,
this.sendUserAgent = false,
this.maxAuthRetries = 1,
this.allowAutoSignedCert = false,
this.withCredentials = false,
}) {
$configureLifeCycle();
}
bool allowAutoSignedCert;
String userAgent;
bool sendUserAgent;
String? baseUrl;
String defaultContentType = 'application/json; charset=utf-8';
bool followRedirects;
int maxRedirects;
int maxAuthRetries;
Decoder? defaultDecoder;
Duration timeout;
List<TrustedCertificate>? trustedCertificates;
String Function(Uri url)? findProxy;
GetHttpClient? _httpClient;
List<GetSocket>? _sockets;
bool withCredentials;
@override
List<GetSocket> get sockets => _sockets ??= <GetSocket>[];
@override
GetHttpClient get httpClient => _httpClient ??= GetHttpClient(
userAgent: userAgent,
sendUserAgent: sendUserAgent,
timeout: timeout,
followRedirects: followRedirects,
maxRedirects: maxRedirects,
maxAuthRetries: maxAuthRetries,
allowAutoSignedCert: allowAutoSignedCert,
baseUrl: baseUrl,
trustedCertificates: trustedCertificates,
withCredentials: withCredentials,
findProxy: findProxy);
@override
Future<Response<T>> get<T>(
String url, {
Map<String, String>? headers,
String? contentType,
Map<String, dynamic>? query,
Decoder<T>? decoder,
}) {
_checkIfDisposed();
return httpClient.get<T>(
url,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
);
}
@override
Future<Response<T>> post<T>(
String? url,
dynamic body, {
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
}) {
_checkIfDisposed();
return httpClient.post<T>(
url,
body: body,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
uploadProgress: uploadProgress,
);
}
@override
Future<Response<T>> put<T>(
String url,
dynamic body, {
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
}) {
_checkIfDisposed();
return httpClient.put<T>(
url,
body: body,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
uploadProgress: uploadProgress,
);
}
@override
Future<Response<T>> patch<T>(
String url,
dynamic body, {
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
}) {
_checkIfDisposed();
return httpClient.patch<T>(
url,
body: body,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
uploadProgress: uploadProgress,
);
}
@override
Future<Response<T>> request<T>(
String url,
String method, {
dynamic body,
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
}) {
_checkIfDisposed();
return httpClient.request<T>(
url,
method,
body: body,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
uploadProgress: uploadProgress,
);
}
@override
Future<Response<T>> delete<T>(
String url, {
Map<String, String>? headers,
String? contentType,
Map<String, dynamic>? query,
Decoder<T>? decoder,
}) {
_checkIfDisposed();
return httpClient.delete(
url,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
);
}
@override
GetSocket socket(
String url, {
Duration ping = const Duration(seconds: 5),
}) {
_checkIfDisposed(isHttp: false);
final newSocket = GetSocket(_concatUrl(url)!, ping: ping);
sockets.add(newSocket);
return newSocket;
}
String? _concatUrl(String? url) {
if (url == null) return baseUrl;
return baseUrl == null ? url : baseUrl! + url;
}
/// query allow made GraphQL raw querys
/// final connect = GetConnect();
/// connect.baseUrl = 'https://countries.trevorblades.com/';
/// final response = await connect.query(
/// r"""
/// {
/// country(code: "BR") {
/// name
/// native
/// currency
/// languages {
/// code
/// name
/// }
/// }
///}
///""",
///);
///print(response.body);
@override
Future<GraphQLResponse<T>> query<T>(
String query, {
String? url,
Map<String, dynamic>? variables,
Map<String, String>? headers,
}) async {
try {
final res = await post(
url,
{'query': query, 'variables': variables},
headers: headers,
);
final listError = res.body['errors'];
if ((listError is List) && listError.isNotEmpty) {
return GraphQLResponse<T>(
graphQLErrors: listError
.map((e) => GraphQLError(
code: e['extensions']['code']?.toString(),
message: e['message']?.toString(),
))
.toList());
}
return GraphQLResponse<T>.fromResponse(res);
} on Exception catch (_) {
return GraphQLResponse<T>(graphQLErrors: [
GraphQLError(
code: null,
message: _.toString(),
)
]);
}
}
@override
Future<GraphQLResponse<T>> mutation<T>(
String mutation, {
String? url,
Map<String, dynamic>? variables,
Map<String, String>? headers,
}) async {
try {
final res = await post(
url,
{'query': mutation, 'variables': variables},
headers: headers,
);
final listError = res.body['errors'];
if ((listError is List) && listError.isNotEmpty) {
return GraphQLResponse<T>(
graphQLErrors: listError
.map((e) => GraphQLError(
code: e['extensions']['code']?.toString(),
message: e['message']?.toString(),
))
.toList());
}
return GraphQLResponse<T>.fromResponse(res);
} on Exception catch (_) {
return GraphQLResponse<T>(graphQLErrors: [
GraphQLError(
code: null,
message: _.toString(),
)
]);
}
}
bool _isDisposed = false;
bool get isDisposed => _isDisposed;
void _checkIfDisposed({bool isHttp = true}) {
if (_isDisposed) {
throw 'Can not emit events to disposed clients';
}
}
void dispose() {
if (_sockets != null) {
for (var socket in sockets) {
socket.close();
}
_sockets?.clear();
sockets = null;
}
if (_httpClient != null) {
httpClient.close();
_httpClient = null;
}
_isDisposed = true;
}
}

View File

@@ -0,0 +1,17 @@
List<int> fileToBytes(dynamic data) {
if (data is List<int>) {
return data;
} else {
throw const FormatException(
'File is not "File" or "String" or "List<int>"');
}
}
// void writeOnFile(List<int> bytes) {
// var blob = html.Blob(["data"], 'text/plain', 'native');
// var anchorElement = html.AnchorElement(
// href: html.Url.createObjectUrlFromBlob(blob).toString(),
// )
// ..setAttribute("download", "data.txt")
// ..click();
// }

View File

@@ -0,0 +1,117 @@
import 'dart:async';
// ignore: avoid_web_libraries_in_flutter
import 'dart:html';
import '../../certificates/certificates.dart';
import '../../exceptions/exceptions.dart';
import '../../request/request.dart';
import '../../response/response.dart';
import '../interface/request_base.dart';
import '../utils/body_decoder.dart';
/// A `dart:html` implementation of `HttpRequestBase`.
class HttpRequestImpl implements HttpRequestBase {
HttpRequestImpl({
bool allowAutoSignedCert = true,
List<TrustedCertificate>? trustedCertificates,
this.withCredentials = false,
String Function(Uri url)? findProxy,
});
/// The currently active XHRs.
final _xhrs = <HttpRequest>{};
///This option requires that you submit credentials for requests
///on different sites. The default is false
final bool withCredentials;
@override
Duration? timeout;
/// Sends an HTTP request and asynchronously returns the response.
@override
Future<Response<T>> send<T>(Request<T> request) async {
var bytes = await request.bodyBytes.toBytes();
HttpRequest xhr;
xhr = HttpRequest()
..timeout = timeout?.inMilliseconds
..open(request.method, '${request.url}', async: true); // check this
_xhrs.add(xhr);
xhr
..responseType = 'blob'
..withCredentials = withCredentials;
request.headers.forEach(xhr.setRequestHeader);
var completer = Completer<Response<T>>();
xhr.onLoad.first.then((_) {
var blob = xhr.response as Blob? ?? Blob([]);
var reader = FileReader();
reader.onLoad.first.then((_) async {
var bodyBytes = BodyBytesStream.fromBytes(reader.result as List<int>);
final stringBody =
await bodyBytesToString(bodyBytes, xhr.responseHeaders);
String? contentType;
if (xhr.responseHeaders.containsKey('content-type')) {
contentType = xhr.responseHeaders['content-type'];
} else {
contentType = 'application/json';
}
// xhr.responseHeaders.containsKey(key)
final body = bodyDecoded<T>(
request,
stringBody,
contentType,
);
final response = Response<T>(
bodyBytes: bodyBytes,
statusCode: xhr.status,
request: request,
headers: xhr.responseHeaders,
statusText: xhr.statusText,
body: body,
bodyString: stringBody,
);
completer.complete(response);
});
reader.onError.first.then((error) {
completer.completeError(
GetHttpException(error.toString(), request.url),
StackTrace.current,
);
});
reader.readAsArrayBuffer(blob);
});
xhr.onError.first.then((_) {
completer.completeError(
GetHttpException('XMLHttpRequest error.', request.url),
StackTrace.current);
});
xhr.send(bytes);
try {
return await completer.future;
} finally {
_xhrs.remove(xhr);
}
}
/// Closes the client and abort all active requests.
@override
void close() {
for (var xhr in _xhrs) {
xhr.abort();
}
}
}

View File

@@ -0,0 +1,20 @@
import 'dart:io';
List<int> fileToBytes(dynamic data) {
if (data is File) {
return data.readAsBytesSync();
} else if (data is String) {
if (File(data).existsSync()) {
return File(data).readAsBytesSync();
} else {
throw 'File $data not exists';
}
} else if (data is List<int>) {
return data;
} else {
throw const FormatException(
'File is not "File" or "String" or "List<int>"');
}
}
void writeOnFile(List<int> bytes) {}

View File

@@ -0,0 +1,99 @@
import 'dart:async';
import 'dart:io' as io;
import '../../certificates/certificates.dart';
import '../../exceptions/exceptions.dart';
import '../../request/request.dart';
import '../../response/response.dart';
import '../interface/request_base.dart';
import '../utils/body_decoder.dart';
/// A `dart:io` implementation of `HttpRequestBase`.
class HttpRequestImpl extends HttpRequestBase {
io.HttpClient? _httpClient;
io.SecurityContext? _securityContext;
HttpRequestImpl({
bool allowAutoSignedCert = true,
List<TrustedCertificate>? trustedCertificates,
bool withCredentials = false,
String Function(Uri url)? findProxy,
}) {
_httpClient = io.HttpClient();
if (trustedCertificates != null) {
_securityContext = io.SecurityContext();
for (final trustedCertificate in trustedCertificates) {
_securityContext!
.setTrustedCertificatesBytes(List.from(trustedCertificate.bytes));
}
}
_httpClient = io.HttpClient(context: _securityContext);
_httpClient!.badCertificateCallback = (_, __, ___) => allowAutoSignedCert;
_httpClient!.findProxy = findProxy;
}
@override
Future<Response<T>> send<T>(Request<T> request) async {
var stream = request.bodyBytes.asBroadcastStream();
io.HttpClientRequest? ioRequest;
try {
_httpClient!.connectionTimeout = timeout;
ioRequest = (await _httpClient!.openUrl(request.method, request.url))
..followRedirects = request.followRedirects
..persistentConnection = request.persistentConnection
..maxRedirects = request.maxRedirects
..contentLength = request.contentLength ?? -1;
request.headers.forEach(ioRequest.headers.set);
var response = timeout == null
? await stream.pipe(ioRequest) as io.HttpClientResponse
: await stream.pipe(ioRequest).timeout(timeout!)
as io.HttpClientResponse;
var headers = <String, String>{};
response.headers.forEach((key, values) {
headers[key] = values.join(',');
});
final bodyBytes = (response);
final stringBody = await bodyBytesToString(bodyBytes, headers);
final body = bodyDecoded<T>(
request,
stringBody,
response.headers.contentType?.mimeType,
);
return Response(
headers: headers,
request: request,
statusCode: response.statusCode,
statusText: response.reasonPhrase,
bodyBytes: bodyBytes,
body: body,
bodyString: stringBody,
);
} on TimeoutException catch (_) {
ioRequest?.abort();
rethrow;
} on io.HttpException catch (error) {
throw GetHttpException(error.message, error.uri);
}
}
/// Closes the HttpClient.
@override
void close() {
if (_httpClient != null) {
_httpClient!.close(force: true);
_httpClient = null;
}
}
}
// extension FileExt on io.FileSystemEntity {
// String get fileName {
// return this?.path?.split(io.Platform.pathSeparator)?.last;
// }
// }

View File

@@ -0,0 +1,5 @@
void writeOnFile(List<int> bytes) {}
List<int> fileToBytes(dynamic data) {
throw UnimplementedError();
}

View File

@@ -0,0 +1,20 @@
import '../../certificates/certificates.dart';
import '../../request/request.dart';
import '../../response/response.dart';
import '../interface/request_base.dart';
class HttpRequestImpl extends HttpRequestBase {
HttpRequestImpl({
bool allowAutoSignedCert = true,
List<TrustedCertificate>? trustedCertificates,
bool withCredentials = false,
String Function(Uri url)? findProxy,
});
@override
void close() {}
@override
Future<Response<T>> send<T>(Request<T> request) {
throw UnimplementedError();
}
}

View File

@@ -0,0 +1,19 @@
import '../../request/request.dart';
import '../../response/response.dart';
/// Abstract interface of [HttpRequestImpl].
abstract class HttpRequestBase {
/// Sends an HTTP [Request].
Future<Response<T>> send<T>(Request<T> request);
/// Closes the [Request] and cleans up any resources associated with it.
void close();
/// Gets and sets the timeout.
///
/// For mobile, this value will be applied for both connection and request
/// timeout.
///
/// For web, this value will be the request timeout.
Duration? timeout;
}

View File

@@ -0,0 +1,47 @@
import '../../request/request.dart';
import '../../response/response.dart';
import '../interface/request_base.dart';
import '../utils/body_decoder.dart';
typedef MockClientHandler = Future<Response> Function(Request request);
class MockClient extends HttpRequestBase {
/// The handler for than transforms request on response
final MockClientHandler _handler;
/// Creates a [MockClient] with a handler that receives [Request]s and sends
/// [Response]s.
MockClient(this._handler);
@override
Future<Response<T>> send<T>(Request<T> request) async {
var requestBody = await request.bodyBytes.toBytes();
var bodyBytes = BodyBytesStream.fromBytes(requestBody);
var response = await _handler(request);
final stringBody = await bodyBytesToString(bodyBytes, response.headers!);
var mimeType = response.headers!.containsKey('content-type')
? response.headers!['content-type']
: '';
final body = bodyDecoded<T>(
request,
stringBody,
mimeType,
);
return Response(
headers: response.headers,
request: request,
statusCode: response.statusCode,
statusText: response.statusText,
bodyBytes: bodyBytes,
body: body,
bodyString: stringBody,
);
}
@override
void close() {}
}

View File

@@ -0,0 +1,34 @@
import 'dart:convert';
import '../../../../../get_core/get_core.dart';
import '../../request/request.dart';
T? bodyDecoded<T>(Request<T> request, String stringBody, String? mimeType) {
T? body;
dynamic bodyToDecode;
if (mimeType != null && mimeType.contains('application/json')) {
try {
bodyToDecode = jsonDecode(stringBody);
} on FormatException catch (_) {
Get.log('Cannot decode server response to json');
bodyToDecode = stringBody;
}
} else {
bodyToDecode = stringBody;
}
try {
if (stringBody == '') {
body = null;
} else if (request.decoder == null) {
body = bodyToDecode as T?;
} else {
body = request.decoder!(bodyToDecode);
}
} on Exception catch (_) {
body = stringBody as T;
}
return body;
}

View File

@@ -0,0 +1,5 @@
class TrustedCertificate {
final List<int> bytes;
TrustedCertificate(this.bytes);
}

View File

@@ -0,0 +1,35 @@
class GetHttpException implements Exception {
final String message;
final Uri? uri;
GetHttpException(this.message, [this.uri]);
@override
String toString() => message;
}
class GraphQLError {
GraphQLError({this.code, this.message});
final String? message;
final String? code;
@override
String toString() => 'GETCONNECT ERROR:\n\tcode:$code\n\tmessage:$message';
}
class UnauthorizedException implements Exception {
@override
String toString() {
return 'Operation Unauthorized';
}
}
class UnexpectedFormat implements Exception {
final String message;
UnexpectedFormat(this.message);
@override
String toString() {
return 'Unexpected format: $message';
}
}

View File

@@ -0,0 +1,563 @@
import 'dart:async';
import 'dart:convert';
import '../src/certificates/certificates.dart';
import '../src/exceptions/exceptions.dart';
import '../src/multipart/form_data.dart';
import '../src/request/request.dart';
import '../src/response/response.dart';
import '../src/status/http_status.dart';
import '_http/_stub/_http_request_stub.dart'
if (dart.library.io) '_http/_io/_http_request_io.dart'
if (dart.library.html) '_http/_html/_http_request_html.dart' as platform;
import '_http/interface/request_base.dart';
import 'interceptors/get_modifiers.dart';
typedef Decoder<T> = T Function(dynamic data);
typedef Progress = Function(double percent);
class GetHttpClient {
String userAgent;
String? baseUrl;
String defaultContentType = 'application/json; charset=utf-8';
bool followRedirects;
int maxRedirects;
int maxAuthRetries;
bool sendUserAgent;
Decoder? defaultDecoder;
Duration timeout;
bool errorSafety = true;
final HttpRequestBase _httpClient;
final GetModifier _modifier;
String Function(Uri url)? findProxy;
GetHttpClient({
this.userAgent = 'getx-client',
this.timeout = const Duration(seconds: 8),
this.followRedirects = true,
this.maxRedirects = 5,
this.sendUserAgent = false,
this.maxAuthRetries = 1,
bool allowAutoSignedCert = false,
this.baseUrl,
List<TrustedCertificate>? trustedCertificates,
bool withCredentials = false,
String Function(Uri url)? findProxy,
}) : _httpClient = platform.HttpRequestImpl(
allowAutoSignedCert: allowAutoSignedCert,
trustedCertificates: trustedCertificates,
withCredentials: withCredentials,
findProxy: findProxy,
),
_modifier = GetModifier();
void addAuthenticator<T>(RequestModifier<T> auth) {
_modifier.authenticator = auth as RequestModifier;
}
void addRequestModifier<T>(RequestModifier<T> interceptor) {
_modifier.addRequestModifier<T>(interceptor);
}
void removeRequestModifier<T>(RequestModifier<T> interceptor) {
_modifier.removeRequestModifier(interceptor);
}
void addResponseModifier<T>(ResponseModifier<T> interceptor) {
_modifier.addResponseModifier(interceptor);
}
void removeResponseModifier<T>(ResponseModifier<T> interceptor) {
_modifier.removeResponseModifier<T>(interceptor);
}
Uri _createUri(String? url, Map<String, dynamic>? query) {
if (baseUrl != null) {
url = baseUrl! + url!;
}
final uri = Uri.parse(url!);
if (query != null) {
return uri.replace(queryParameters: query);
}
return uri;
}
Future<Request<T>> _requestWithBody<T>(
String? url,
String? contentType,
dynamic body,
String method,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
) async {
List<int>? bodyBytes;
Stream<List<int>>? bodyStream;
final headers = <String, String>{};
if (sendUserAgent) {
headers['user-agent'] = userAgent;
}
if (body is FormData) {
bodyBytes = await body.toBytes();
headers['content-length'] = bodyBytes.length.toString();
headers['content-type'] =
'multipart/form-data; boundary=${body.boundary}';
} else if (contentType != null &&
contentType.toLowerCase() == 'application/x-www-form-urlencoded' &&
body is Map) {
var parts = [];
(body as Map<String, dynamic>).forEach((key, value) {
parts.add('${Uri.encodeQueryComponent(key)}='
'${Uri.encodeQueryComponent(value.toString())}');
});
var formData = parts.join('&');
bodyBytes = utf8.encode(formData);
headers['content-length'] = bodyBytes.length.toString();
headers['content-type'] = contentType;
} else if (body is Map || body is List) {
var jsonString = json.encode(body);
bodyBytes = utf8.encode(jsonString);
headers['content-length'] = bodyBytes.length.toString();
headers['content-type'] = contentType ?? defaultContentType;
} else if (body is String) {
bodyBytes = utf8.encode(body);
headers['content-length'] = bodyBytes.length.toString();
headers['content-type'] = contentType ?? defaultContentType;
} else if (body == null) {
headers['content-type'] = contentType ?? defaultContentType;
headers['content-length'] = '0';
} else {
if (!errorSafety) {
throw UnexpectedFormat('body cannot be ${body.runtimeType}');
}
}
if (bodyBytes != null) {
bodyStream = _trackProgress(bodyBytes, uploadProgress);
}
final uri = _createUri(url, query);
return Request<T>(
method: method,
url: uri,
headers: headers,
bodyBytes: bodyStream,
contentLength: bodyBytes?.length ?? 0,
followRedirects: followRedirects,
maxRedirects: maxRedirects,
decoder: decoder,
);
}
Stream<List<int>> _trackProgress(
List<int> bodyBytes,
Progress? uploadProgress,
) {
var total = 0;
var length = bodyBytes.length;
var byteStream =
Stream.fromIterable(bodyBytes.map((i) => [i])).transform<List<int>>(
StreamTransformer.fromHandlers(handleData: (data, sink) {
total += data.length;
if (uploadProgress != null) {
var percent = total / length * 100;
uploadProgress(percent);
}
sink.add(data);
}),
);
return byteStream;
}
void _setSimpleHeaders(
Map<String, String> headers,
String? contentType,
) {
headers['content-type'] = contentType ?? defaultContentType;
if (sendUserAgent) {
headers['user-agent'] = userAgent;
}
}
Future<Response<T>> _performRequest<T>(
HandlerExecute<T> handler, {
bool authenticate = false,
int requestNumber = 1,
Map<String, String>? headers,
}) async {
var request = await handler();
headers?.forEach((key, value) {
request.headers[key] = value;
});
if (authenticate) await _modifier.authenticator!(request);
final newRequest = await _modifier.modifyRequest<T>(request);
_httpClient.timeout = timeout;
try {
var response = await _httpClient.send<T>(newRequest);
final newResponse =
await _modifier.modifyResponse<T>(newRequest, response);
if (HttpStatus.unauthorized == newResponse.statusCode &&
_modifier.authenticator != null &&
requestNumber <= maxAuthRetries) {
return _performRequest<T>(
handler,
authenticate: true,
requestNumber: requestNumber + 1,
headers: newRequest.headers,
);
} else if (HttpStatus.unauthorized == newResponse.statusCode) {
if (!errorSafety) {
throw UnauthorizedException();
} else {
return Response<T>(
request: newRequest,
headers: newResponse.headers,
statusCode: newResponse.statusCode,
body: newResponse.body,
bodyBytes: newResponse.bodyBytes,
bodyString: newResponse.bodyString,
statusText: newResponse.statusText,
);
}
}
return newResponse;
} on Exception catch (err) {
if (!errorSafety) {
throw GetHttpException(err.toString());
} else {
return Response<T>(
request: newRequest,
headers: null,
statusCode: null,
body: null,
statusText: "$err",
);
}
}
}
Future<Request<T>> _get<T>(
String url,
String? contentType,
Map<String, dynamic>? query,
Decoder<T>? decoder,
) {
final headers = <String, String>{};
_setSimpleHeaders(headers, contentType);
final uri = _createUri(url, query);
return Future.value(Request<T>(
method: 'get',
url: uri,
headers: headers,
decoder: decoder ?? (defaultDecoder as Decoder<T>?),
contentLength: 0,
followRedirects: followRedirects,
maxRedirects: maxRedirects,
));
}
Future<Request<T>> _request<T>(
String? url,
String method, {
String? contentType,
required dynamic body,
required Map<String, dynamic>? query,
Decoder<T>? decoder,
required Progress? uploadProgress,
}) {
return _requestWithBody<T>(
url,
contentType,
body,
method,
query,
decoder ?? (defaultDecoder as Decoder<T>?),
uploadProgress,
);
}
Request<T> _delete<T>(
String url,
String? contentType,
Map<String, dynamic>? query,
Decoder<T>? decoder,
) {
final headers = <String, String>{};
_setSimpleHeaders(headers, contentType);
final uri = _createUri(url, query);
return Request<T>(
method: 'delete',
url: uri,
headers: headers,
decoder: decoder ?? (defaultDecoder as Decoder<T>?),
);
}
Future<Response<T>> patch<T>(
String url, {
dynamic body,
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
// List<MultipartFile> files,
}) async {
try {
var response = await _performRequest<T>(
() => _request<T>(
url,
'patch',
contentType: contentType,
body: body,
query: query,
decoder: decoder,
uploadProgress: uploadProgress,
),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
statusText: 'Can not connect to server. Reason: $e',
));
}
}
Future<Response<T>> post<T>(
String? url, {
dynamic body,
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
// List<MultipartFile> files,
}) async {
try {
var response = await _performRequest<T>(
() => _request<T>(
url,
'post',
contentType: contentType,
body: body,
query: query,
decoder: decoder,
uploadProgress: uploadProgress,
),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
statusText: 'Can not connect to server. Reason: $e',
));
}
}
Future<Response<T>> request<T>(
String url,
String method, {
dynamic body,
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
}) async {
try {
var response = await _performRequest<T>(
() => _request<T>(
url,
method,
contentType: contentType,
query: query,
body: body,
decoder: decoder,
uploadProgress: uploadProgress,
),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
statusText: 'Can not connect to server. Reason: $e',
));
}
}
Future<Response<T>> put<T>(
String url, {
dynamic body,
String? contentType,
Map<String, String>? headers,
Map<String, dynamic>? query,
Decoder<T>? decoder,
Progress? uploadProgress,
}) async {
try {
var response = await _performRequest<T>(
() => _request<T>(
url,
'put',
contentType: contentType,
query: query,
body: body,
decoder: decoder,
uploadProgress: uploadProgress,
),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
statusText: 'Can not connect to server. Reason: $e',
));
}
}
Future<Response<T>> get<T>(
String url, {
Map<String, String>? headers,
String? contentType,
Map<String, dynamic>? query,
Decoder<T>? decoder,
}) async {
try {
var response = await _performRequest<T>(
() => _get<T>(url, contentType, query, decoder),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
statusText: 'Can not connect to server. Reason: $e',
));
}
}
// Future<Response<T>> download<T>(
// String url,
// String path, {
// Map<String, String> headers,
// String contentType = 'application/octet-stream',
// Map<String, dynamic> query,
// }) async {
// try {
// var response = await _performRequest<T>(
// () => _get<T>(url, contentType, query, null),
// headers: headers,
// );
// response.bodyBytes.listen((value) {});
// return response;
// } on Exception catch (e) {
// if (!errorSafety) {
// throw GetHttpException(e.toString());
// }
// return Future.value(Response<T>(
// statusText: 'Can not connect to server. Reason: $e',
// ));
// }
// int byteCount = 0;
// int totalBytes = httpResponse.contentLength;
// Directory appDocDir = await getApplicationDocumentsDirectory();
// String appDocPath = appDocDir.path;
// File file = File(path);
// var raf = file.openSync(mode: FileMode.write);
// Completer completer = Completer<String>();
// httpResponse.listen(
// (data) {
// byteCount += data.length;
// raf.writeFromSync(data);
// if (onDownloadProgress != null) {
// onDownloadProgress(byteCount, totalBytes);
// }
// },
// onDone: () {
// raf.closeSync();
// completer.complete(file.path);
// },
// onError: (e) {
// raf.closeSync();
// file.deleteSync();
// completer.completeError(e);
// },
// cancelOnError: true,
// );
// return completer.future;
// }
Future<Response<T>> delete<T>(
String url, {
Map<String, String>? headers,
String? contentType,
Map<String, dynamic>? query,
Decoder<T>? decoder,
}) async {
try {
var response = await _performRequest<T>(
() async => _delete<T>(url, contentType, query, decoder),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
statusText: 'Can not connect to server. Reason: $e',
));
}
}
void close() {
_httpClient.close();
}
}

View File

@@ -0,0 +1,56 @@
import 'dart:async';
import '../request/request.dart';
import '../response/response.dart';
typedef RequestModifier<T> = FutureOr<Request<T>> Function(Request<T?> request);
typedef ResponseModifier<T> = FutureOr Function(
Request<T?> request, Response<T?> response);
typedef HandlerExecute<T> = Future<Request<T>> Function();
class GetModifier<S> {
final _requestModifiers = <RequestModifier>[];
final _responseModifiers = <ResponseModifier>[];
RequestModifier? authenticator;
void addRequestModifier<T>(RequestModifier<T> interceptor) {
_requestModifiers.add(interceptor as RequestModifier);
}
void removeRequestModifier<T>(RequestModifier<T> interceptor) {
_requestModifiers.remove(interceptor);
}
void addResponseModifier<T>(ResponseModifier<T> interceptor) {
_responseModifiers.add(interceptor as ResponseModifier);
}
void removeResponseModifier<T>(ResponseModifier<T> interceptor) {
_requestModifiers.remove(interceptor);
}
Future<Request<T>> modifyRequest<T>(Request<T> request) async {
var newRequest = request;
if (_requestModifiers.isNotEmpty) {
for (var interceptor in _requestModifiers) {
newRequest = await interceptor(newRequest) as Request<T>;
}
}
return newRequest;
}
Future<Response<T>> modifyResponse<T>(
Request<T> request, Response<T> response) async {
var newResponse = response;
if (_responseModifiers.isNotEmpty) {
for (var interceptor in _responseModifiers) {
newResponse = await interceptor(request, response) as Response<T>;
}
}
return newResponse;
}
}

View File

@@ -0,0 +1,116 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import '../request/request.dart';
import '../utils/utils.dart';
import 'multipart_file.dart';
class FormData {
FormData(Map<String, dynamic> map) : boundary = _getBoundary() {
map.forEach((key, value) {
if (value == null) return;
if (value is MultipartFile) {
files.add(MapEntry(key, value));
} else if (value is List<MultipartFile>) {
files.addAll(value.map((e) => MapEntry(key, e)));
} else if (value is List) {
fields.addAll(value.map((e) => MapEntry(key, e.toString())));
} else {
fields.add(MapEntry(key, value.toString()));
}
});
}
static const int _maxBoundaryLength = 70;
static String _getBoundary() {
final random = Random();
var list = List<int>.generate(_maxBoundaryLength - GET_BOUNDARY.length,
(_) => boundaryCharacters[random.nextInt(boundaryCharacters.length)],
growable: false);
return '$GET_BOUNDARY${String.fromCharCodes(list)}';
}
final String boundary;
/// The form fields to send for this request.
final fields = <MapEntry<String, String>>[];
/// The files to send for this request
final files = <MapEntry<String, MultipartFile>>[];
/// Returns the header string for a field. The return value is guaranteed to
/// contain only ASCII characters.
String _fieldHeader(String name, String value) {
var header =
'content-disposition: form-data; name="${browserEncode(name)}"';
if (!isPlainAscii(value)) {
header = '$header\r\n'
'content-type: text/plain; charset=utf-8\r\n'
'content-transfer-encoding: binary';
}
return '$header\r\n\r\n';
}
/// Returns the header string for a file. The return value is guaranteed to
/// contain only ASCII characters.
String _fileHeader(MapEntry<String, MultipartFile> file) {
var header =
'content-disposition: form-data; name="${browserEncode(file.key)}"';
header = '$header; filename="${browserEncode(file.value.filename)}"';
header = '$header\r\n'
'content-type: ${file.value.contentType}';
return '$header\r\n\r\n';
}
/// The length of the request body from this [FormData]
int get length {
var length = 0;
for (final item in fields) {
length += '--'.length +
_maxBoundaryLength +
'\r\n'.length +
utf8.encode(_fieldHeader(item.key, item.value)).length +
utf8.encode(item.value).length +
'\r\n'.length;
}
for (var file in files) {
length += '--'.length +
_maxBoundaryLength +
'\r\n'.length +
utf8.encode(_fileHeader(file)).length +
file.value.length! +
'\r\n'.length;
}
return length + '--'.length + _maxBoundaryLength + '--\r\n'.length;
}
Future<List<int>> toBytes() {
return BodyBytesStream(_encode()).toBytes();
}
Stream<List<int>> _encode() async* {
const line = [13, 10];
final separator = utf8.encode('--$boundary\r\n');
final close = utf8.encode('--$boundary--\r\n');
for (var field in fields) {
yield separator;
yield utf8.encode(_fieldHeader(field.key, field.value));
yield utf8.encode(field.value);
yield line;
}
for (final file in files) {
yield separator;
yield utf8.encode(_fileHeader(file));
yield* file.value.stream!;
yield line;
}
yield close;
}
}

View File

@@ -0,0 +1,30 @@
import '../_http/_stub/_file_decoder_stub.dart'
if (dart.library.html) '../_http/_html/_file_decoder_html.dart'
if (dart.library.io) '../_http/_io/_file_decoder_io.dart' as decoder;
import '../request/request.dart';
class MultipartFile {
MultipartFile(
dynamic data, {
required this.filename,
this.contentType = 'application/octet-stream',
}) : _bytes = decoder.fileToBytes(data) {
_length = _bytes.length;
_stream = BodyBytesStream.fromBytes(_bytes);
}
final List<int> _bytes;
final String contentType;
/// This stream will emit the file content of File.
Stream<List<int>>? _stream;
int? _length;
Stream<List<int>>? get stream => _stream;
int? get length => _length;
final String filename;
}

View File

@@ -0,0 +1,131 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import '../http.dart';
import '../multipart/form_data.dart';
class Request<T> {
/// Headers attach to this [Request]
final Map<String, String> headers;
/// The [Uri] from request
final Uri url;
final Decoder<T>? decoder;
/// The Http Method from this [Request]
/// ex: `GET`,`POST`,`PUT`,`DELETE`
final String method;
final int? contentLength;
/// The BodyBytesStream of body from this [Request]
final Stream<List<int>> bodyBytes;
/// When true, the client will follow redirects to resolves this [Request]
final bool followRedirects;
/// The maximum number of redirects if [followRedirects] is true.
final int maxRedirects;
final bool persistentConnection;
final FormData? files;
const Request._({
required this.method,
required this.bodyBytes,
required this.url,
required this.headers,
required this.contentLength,
required this.followRedirects,
required this.maxRedirects,
required this.files,
required this.persistentConnection,
required this.decoder,
});
factory Request({
required Uri url,
required String method,
required Map<String, String> headers,
Stream<List<int>>? bodyBytes,
bool followRedirects = true,
int maxRedirects = 4,
int? contentLength,
FormData? files,
bool persistentConnection = true,
Decoder<T>? decoder,
}) {
if (followRedirects) {
assert(maxRedirects > 0);
}
return Request._(
url: url,
method: method,
bodyBytes: bodyBytes ??= BodyBytesStream.fromBytes(const []),
headers: Map.from(headers),
followRedirects: followRedirects,
maxRedirects: maxRedirects,
contentLength: contentLength,
files: files,
persistentConnection: persistentConnection,
decoder: decoder,
);
}
Request<T> copyWith({
Uri? url,
String? method,
Map<String, String>? headers,
Stream<List<int>>? bodyBytes,
bool? followRedirects,
int? maxRedirects,
int? contentLength,
FormData? files,
bool? persistentConnection,
Decoder<T>? decoder,
bool appendHeader = true,
}) {
// If appendHeader is set to true, we will merge origin headers with that
if (appendHeader && headers != null) {
headers.addAll(this.headers);
}
return Request<T>._(
url: url ?? this.url,
method: method ?? this.method,
bodyBytes: bodyBytes ?? this.bodyBytes,
headers: headers == null ? this.headers : Map.from(headers),
followRedirects: followRedirects ?? this.followRedirects,
maxRedirects: maxRedirects ?? this.maxRedirects,
contentLength: contentLength ?? this.contentLength,
files: files ?? this.files,
persistentConnection: persistentConnection ?? this.persistentConnection,
decoder: decoder ?? this.decoder,
);
}
}
extension BodyBytesStream on Stream<List<int>> {
static Stream<List<int>> fromBytes(List<int> bytes) =>
Stream.fromIterable([bytes]);
Future<Uint8List> toBytes() {
var completer = Completer<Uint8List>();
var sink = ByteConversionSink.withCallback(
(bytes) => completer.complete(
Uint8List.fromList(bytes),
),
);
listen((val) => sink.add(val),
onError: completer.completeError,
onDone: sink.close,
cancelOnError: true);
return completer.future;
}
Future<String> bytesToString([Encoding encoding = utf8]) =>
encoding.decodeStream(this);
}

View File

@@ -0,0 +1,270 @@
import 'dart:collection';
import 'dart:convert';
import '../exceptions/exceptions.dart';
import '../request/request.dart';
import '../status/http_status.dart';
class GraphQLResponse<T> extends Response<T> {
final List<GraphQLError>? graphQLErrors;
GraphQLResponse({T? body, this.graphQLErrors}) : super(body: body);
GraphQLResponse.fromResponse(Response res)
: graphQLErrors = null,
super(
request: res.request,
statusCode: res.statusCode,
bodyBytes: res.bodyBytes,
bodyString: res.bodyString,
statusText: res.statusText,
headers: res.headers,
body: res.body['data'] as T?);
}
class Response<T> {
const Response({
this.request,
this.statusCode,
this.bodyBytes,
this.bodyString,
this.statusText = '',
this.headers = const {},
this.body,
});
/// The Http [Request] linked with this [Response].
final Request? request;
/// The response headers.
final Map<String, String>? headers;
/// The status code returned by the server.
final int? statusCode;
/// Human-readable context for [statusCode].
final String? statusText;
/// [HttpStatus] from [Response]. `status.connectionError` is true
/// when statusCode is null. `status.isUnauthorized` is true when
/// statusCode is equal `401`. `status.isNotFound` is true when
/// statusCode is equal `404`. `status.isServerError` is true when
/// statusCode is between `500` and `599`.
HttpStatus get status => HttpStatus(statusCode);
/// `hasError` is true when statusCode is not between 200 and 299.
bool get hasError => status.hasError;
/// `isOk` is true when statusCode is between 200 and 299.
bool get isOk => !hasError;
/// `unauthorized` is true when statusCode is equal `401`.
bool get unauthorized => status.isUnauthorized;
/// The response body as a Stream of Bytes.
final Stream<List<int>>? bodyBytes;
/// The response body as a Stream of Bytes.
final String? bodyString;
/// The decoded body of this [Response]. You can access the
/// body parameters as Map
/// Ex: `body['title'];`
final T? body;
}
Future<String> bodyBytesToString(
Stream<List<int>> bodyBytes, Map<String, String> headers) {
return bodyBytes.bytesToString(_encodingForHeaders(headers));
}
/// Returns the encoding to use for a response with the given headers.
///
/// Defaults to [utf8] if the headers don't specify a charset or if that
/// charset is unknown.
Encoding _encodingForHeaders(Map<String, String> headers) =>
_encodingForCharset(_contentTypeForHeaders(headers).parameters!['charset']);
/// Returns the [Encoding] that corresponds to [charset].
///
/// Returns [fallback] if [charset] is null or if no [Encoding] was found that
/// corresponds to [charset].
Encoding _encodingForCharset(String? charset, [Encoding fallback = utf8]) {
if (charset == null) return fallback;
return Encoding.getByName(charset) ?? fallback;
}
/// Returns the MediaType object for the given headers's content-type.
///
/// Defaults to `application/octet-stream`.
HeaderValue _contentTypeForHeaders(Map<String, String> headers) {
var contentType = headers['content-type'];
if (contentType != null) return HeaderValue.parse(contentType);
return HeaderValue('application/octet-stream');
}
class HeaderValue {
String _value;
Map<String, String?>? _parameters;
Map<String, String?>? _unmodifiableParameters;
HeaderValue([this._value = '', Map<String, String>? parameters]) {
if (parameters != null) {
_parameters = HashMap<String, String>.from(parameters);
}
}
static HeaderValue parse(String value,
{String parameterSeparator = ';',
String? valueSeparator,
bool preserveBackslash = false}) {
var result = HeaderValue();
result._parse(value, parameterSeparator, valueSeparator, preserveBackslash);
return result;
}
String get value => _value;
void _ensureParameters() {
_parameters ??= HashMap<String, String>();
}
Map<String, String?>? get parameters {
_ensureParameters();
_unmodifiableParameters ??= UnmodifiableMapView(_parameters!);
return _unmodifiableParameters;
}
@override
String toString() {
var stringBuffer = StringBuffer();
stringBuffer.write(_value);
if (parameters != null && parameters!.isNotEmpty) {
_parameters!.forEach((name, value) {
stringBuffer
..write('; ')
..write(name)
..write('=')
..write(value);
});
}
return stringBuffer.toString();
}
void _parse(String value, String parameterSeparator, String? valueSeparator,
bool preserveBackslash) {
var index = 0;
bool done() => index == value.length;
void bump() {
while (!done()) {
if (value[index] != ' ' && value[index] != '\t') return;
index++;
}
}
String parseValue() {
var start = index;
while (!done()) {
if (value[index] == ' ' ||
value[index] == '\t' ||
value[index] == valueSeparator ||
value[index] == parameterSeparator) {
break;
}
index++;
}
return value.substring(start, index);
}
void expect(String expected) {
if (done() || value[index] != expected) {
throw StateError('Failed to parse header value');
}
index++;
}
void maybeExpect(String expected) {
if (value[index] == expected) index++;
}
void parseParameters() {
var parameters = HashMap<String, String?>();
_parameters = UnmodifiableMapView(parameters);
String parseParameterName() {
var start = index;
while (!done()) {
if (value[index] == ' ' ||
value[index] == '\t' ||
value[index] == '=' ||
value[index] == parameterSeparator ||
value[index] == valueSeparator) {
break;
}
index++;
}
return value.substring(start, index).toLowerCase();
}
String? parseParameterValue() {
if (!done() && value[index] == '"') {
var stringBuffer = StringBuffer();
index++;
while (!done()) {
if (value[index] == '\\') {
if (index + 1 == value.length) {
throw StateError('Failed to parse header value');
}
if (preserveBackslash && value[index + 1] != '"') {
stringBuffer.write(value[index]);
}
index++;
} else if (value[index] == '"') {
index++;
break;
}
stringBuffer.write(value[index]);
index++;
}
return stringBuffer.toString();
} else {
var val = parseValue();
return val == '' ? null : val;
}
}
while (!done()) {
bump();
if (done()) return;
var name = parseParameterName();
bump();
if (done()) {
parameters[name] = null;
return;
}
maybeExpect('=');
bump();
if (done()) {
parameters[name] = null;
return;
}
var valueParameter = parseParameterValue();
if (name == 'charset' && valueParameter != null) {
valueParameter = valueParameter.toLowerCase();
}
parameters[name] = valueParameter;
bump();
if (done()) return;
if (value[index] == valueSeparator) return;
expect(parameterSeparator);
}
}
bump();
_value = parseValue();
bump();
if (done()) return;
maybeExpect(parameterSeparator);
parseParameters();
}
}

View File

@@ -0,0 +1,92 @@
class HttpStatus {
HttpStatus(this.code);
final int? code;
static const int continue_ = 100;
static const int switchingProtocols = 101;
static const int processing = 102;
static const int earlyHints = 103;
static const int ok = 200;
static const int created = 201;
static const int accepted = 202;
static const int nonAuthoritativeInformation = 203;
static const int noContent = 204;
static const int resetContent = 205;
static const int partialContent = 206;
static const int multiStatus = 207;
static const int alreadyReported = 208;
static const int imUsed = 226;
static const int multipleChoices = 300;
static const int movedPermanently = 301;
static const int found = 302;
static const int movedTemporarily = 302; // Common alias for found.
static const int seeOther = 303;
static const int notModified = 304;
static const int useProxy = 305;
static const int switchProxy = 306;
static const int temporaryRedirect = 307;
static const int permanentRedirect = 308;
static const int badRequest = 400;
static const int unauthorized = 401;
static const int paymentRequired = 402;
static const int forbidden = 403;
static const int notFound = 404;
static const int methodNotAllowed = 405;
static const int notAcceptable = 406;
static const int proxyAuthenticationRequired = 407;
static const int requestTimeout = 408;
static const int conflict = 409;
static const int gone = 410;
static const int lengthRequired = 411;
static const int preconditionFailed = 412;
static const int requestEntityTooLarge = 413;
static const int requestUriTooLong = 414;
static const int unsupportedMediaType = 415;
static const int requestedRangeNotSatisfiable = 416;
static const int expectationFailed = 417;
static const int imATeapot = 418;
static const int misdirectedRequest = 421;
static const int unprocessableEntity = 422;
static const int locked = 423;
static const int failedDependency = 424;
static const int tooEarly = 425;
static const int upgradeRequired = 426;
static const int preconditionRequired = 428;
static const int tooManyRequests = 429;
static const int requestHeaderFieldsTooLarge = 431;
static const int connectionClosedWithoutResponse = 444;
static const int unavailableForLegalReasons = 451;
static const int clientClosedRequest = 499;
static const int internalServerError = 500;
static const int notImplemented = 501;
static const int badGateway = 502;
static const int serviceUnavailable = 503;
static const int gatewayTimeout = 504;
static const int httpVersionNotSupported = 505;
static const int variantAlsoNegotiates = 506;
static const int insufficientStorage = 507;
static const int loopDetected = 508;
static const int notExtended = 510;
static const int networkAuthenticationRequired = 511;
static const int networkConnectTimeoutError = 599;
bool get connectionError => code == null;
bool get isUnauthorized => code == unauthorized;
bool get isForbidden => code == forbidden;
bool get isNotFound => code == notFound;
bool get isServerError =>
between(internalServerError, networkConnectTimeoutError);
bool between(int begin, int end) {
return !connectionError && code! >= begin && code! <= end;
}
bool get isOk => between(200, 299);
bool get hasError => !isOk;
}

View File

@@ -0,0 +1,139 @@
import 'dart:convert';
bool isTokenChar(int byte) {
return byte > 31 && byte < 128 && !SEPARATOR_MAP[byte];
}
bool isValueChar(int byte) {
return (byte > 31 && byte < 128) ||
(byte == CharCode.SP) ||
(byte == CharCode.HT);
}
class CharCode {
static const int HT = 9;
static const int LF = 10;
static const int CR = 13;
static const int SP = 32;
static const int COMMA = 44;
static const int SLASH = 47;
static const int ZERO = 48;
static const int ONE = 49;
static const int COLON = 58;
static const int SEMI_COLON = 59;
}
const bool F = false;
const bool T = true;
const SEPARATOR_MAP = [
F, F, F, F, F, F, F, F, F, T, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, T, F, T, F, F, F, F, F, T, T, F, F, T, F, F, T, //
F, F, F, F, F, F, F, F, F, F, T, T, T, T, T, T, T, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, T, T, T, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, T, F, T, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F
];
String validateField(String field) {
for (var i = 0; i < field.length; i++) {
if (!isTokenChar(field.codeUnitAt(i))) {
throw FormatException(
'Invalid HTTP header field name: ${json.encode(field)}', field, i);
}
}
return field.toLowerCase();
}
// Stream<List<int>> toBodyBytesStream(Stream<List<int>> stream) {
// return (stream);
// }
final _asciiOnly = RegExp(r'^[\x00-\x7F]+$');
final newlineRegExp = RegExp(r'\r\n|\r|\n');
/// Returns whether [string] is composed entirely of ASCII-compatible
/// characters.
bool isPlainAscii(String string) => _asciiOnly.hasMatch(string);
const String GET_BOUNDARY = 'getx-http-boundary-';
/// Encode [value] like browsers
String browserEncode(String value) {
return value.replaceAll(newlineRegExp, '%0D%0A').replaceAll('"', '%22');
}
const List<int> boundaryCharacters = <int>[
43,
95,
45,
46,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
109,
110,
111,
112,
113,
114,
115,
116,
117,
118,
119,
120,
121,
122
];

View File

@@ -0,0 +1,9 @@
import 'src/sockets_stub.dart'
if (dart.library.html) 'src/sockets_html.dart'
if (dart.library.io) 'src/sockets_io.dart';
class GetSocket extends BaseWebSocket {
GetSocket(String url,
{Duration ping = const Duration(seconds: 5), bool allowSelfSigned = true})
: super(url, ping: ping, allowSelfSigned: allowSelfSigned);
}

View File

@@ -0,0 +1,101 @@
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;
Close(this.message, this.reason);
@override
String toString() {
return 'Closed by server [$reason => $message]!';
}
}
/// 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>{};
List<void Function(Close)>? _onCloses = <CloseSocket>[];
List<void Function(Close)>? _onErrors = <CloseSocket>[];
late OpenSocket open;
/// 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;
}
/// subscribe to message events
void addMessages(MessageSocket socket) {
_onMessages!.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);
}
if (data is String) {
_tryOn(data);
}
}
/// Notify all subscriptions on [addErrors]
void notifyError(Close err) {
// rooms.removeWhere((key, value) => value.contains(_ws));
for (var item in _onErrors!) {
item(err);
}
}
void _tryOn(String message) {
try {
var msg = jsonDecode(message);
final event = msg['type'];
final data = msg['data'];
if (_onEvents!.containsKey(event)) {
_onEvents![event]!(data);
}
// ignore: avoid_catches_without_on_clauses
} catch (_) {
return;
}
}
}

View File

@@ -0,0 +1,116 @@
import 'dart:async';
import 'dart:convert';
// ignore: avoid_web_libraries_in_flutter
import 'dart:html';
import '../../../get_core/get_core.dart';
import 'socket_notifier.dart';
class BaseWebSocket {
String url;
WebSocket? socket;
SocketNotifier? socketNotifier = SocketNotifier();
Duration ping;
bool isDisposed = false;
bool allowSelfSigned;
ConnectionStatus? connectionStatus;
Timer? _t;
BaseWebSocket(
this.url, {
this.ping = const Duration(seconds: 5),
this.allowSelfSigned = true,
}) {
url = url.startsWith('https')
? url.replaceAll('https:', 'wss:')
: url.replaceAll('http:', 'ws:');
}
void close([int? status, String? reason]) {
socket?.close(status, reason);
}
// ignore: use_setters_to_change_properties
void connect() {
try {
connectionStatus = ConnectionStatus.connecting;
socket = WebSocket(url);
socket!.onOpen.listen((e) {
socketNotifier?.open();
_t = Timer?.periodic(ping, (t) {
socket!.send('');
});
connectionStatus = ConnectionStatus.connected;
});
socket!.onMessage.listen((event) {
socketNotifier!.notifyData(event.data);
});
socket!.onClose.listen((e) {
_t?.cancel();
connectionStatus = ConnectionStatus.closed;
socketNotifier!.notifyClose(Close(e.reason, e.code));
});
socket!.onError.listen((event) {
_t?.cancel();
socketNotifier!.notifyError(Close(event.toString(), 0));
connectionStatus = ConnectionStatus.closed;
});
} on Exception catch (e) {
_t?.cancel();
socketNotifier!.notifyError(Close(e.toString(), 500));
connectionStatus = ConnectionStatus.closed;
// close(500, e.toString());
}
}
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) {
socketNotifier!.addCloses(fn);
}
void onError(CloseSocket fn) {
socketNotifier!.addErrors(fn);
}
void onMessage(MessageSocket fn) {
socketNotifier!.addMessages(fn);
}
// ignore: use_setters_to_change_properties
void onOpen(OpenSocket fn) {
socketNotifier!.open = fn;
}
void send(dynamic data) {
if (connectionStatus == ConnectionStatus.closed) {
connect();
}
if (socket != null && socket!.readyState == WebSocket.OPEN) {
socket!.send(data);
} else {
Get.log('WebSocket not connected, message $data not sent');
}
}
}
enum ConnectionStatus {
connecting,
connected,
closed,
}

View File

@@ -0,0 +1,138 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import '../../../get_core/get_core.dart';
import 'socket_notifier.dart';
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,
});
void close([int? status, String? reason]) {
socket?.close(status, reason);
}
// ignore: use_setters_to_change_properties
Future connect() async {
if (isDisposed) {
socketNotifier = SocketNotifier();
}
try {
connectionStatus = ConnectionStatus.connecting;
socket = allowSelfSigned
? await _connectForSelfSignedCert(url)
: await WebSocket.connect(url);
socket!.pingInterval = ping;
socketNotifier?.open();
connectionStatus = ConnectionStatus.connected;
socket!.listen((data) {
socketNotifier!.notifyData(data);
}, onError: (err) {
socketNotifier!.notifyError(Close(err.toString(), 1005));
}, onDone: () {
connectionStatus = ConnectionStatus.closed;
socketNotifier!
.notifyClose(Close('Connection Closed', socket!.closeCode));
}, cancelOnError: true);
return;
} on SocketException catch (e) {
connectionStatus = ConnectionStatus.closed;
socketNotifier!
.notifyError(Close(e.osError!.message, e.osError!.errorCode));
return;
}
}
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) {
socketNotifier!.addCloses(fn);
}
void onError(CloseSocket fn) {
socketNotifier!.addErrors(fn);
}
void onMessage(MessageSocket fn) {
socketNotifier!.addMessages(fn);
}
// ignore: use_setters_to_change_properties
void onOpen(OpenSocket fn) {
socketNotifier!.open = fn;
}
void send(dynamic data) async {
if (connectionStatus == ConnectionStatus.closed) {
await connect();
}
if (socket != null) {
socket!.add(data);
}
}
Future<WebSocket> _connectForSelfSignedCert(String url) async {
try {
var r = Random();
var key = base64.encode(List<int>.generate(8, (_) => r.nextInt(255)));
var client = HttpClient(context: SecurityContext());
client.badCertificateCallback = (cert, host, port) {
Get.log(
'BaseWebSocket: Allow self-signed certificate => $host:$port. ');
return true;
};
var request = await client.getUrl(Uri.parse(url));
request.headers.add('Connection', 'Upgrade');
request.headers.add('Upgrade', 'websocket');
request.headers.add('Sec-WebSocket-Version', '13');
request.headers.add('Sec-WebSocket-Key', key.toLowerCase());
var response = await request.close();
// ignore: close_sinks
var socket = await response.detachSocket();
var webSocket = WebSocket.fromUpgradedSocket(
socket,
serverSide: false,
);
return webSocket;
} on Exception catch (_) {
rethrow;
}
}
}
enum ConnectionStatus {
connecting,
connected,
closed,
}

View File

@@ -0,0 +1,54 @@
import './socket_notifier.dart';
class BaseWebSocket {
String url;
Duration ping;
bool allowSelfSigned;
BaseWebSocket(
this.url, {
this.ping = const Duration(seconds: 5),
this.allowSelfSigned = true,
}) {
throw 'To use sockets you need dart:io or dart:html';
}
Future connect() async {
throw 'To use sockets you need dart:io or dart:html';
}
void onOpen(OpenSocket fn) {
throw 'To use sockets you need dart:io or dart:html';
}
void onClose(CloseSocket fn) {
throw 'To use sockets you need dart:io or dart:html';
}
void onError(CloseSocket fn) {
throw 'To use sockets you need dart:io or dart:html';
}
void onMessage(MessageSocket fn) {
throw 'To use sockets you need dart:io or dart:html';
}
void on(String event, MessageSocket message) {
throw 'To use sockets you need dart:io or dart:html';
}
void close([int? status, String? reason]) {
throw 'To use sockets you need dart:io or dart:html';
}
void send(dynamic data) async {
throw 'To use sockets you need dart:io or dart:html';
}
void dispose() {
throw 'To use sockets you need dart:io or dart:html';
}
void emit(String event, dynamic data) {
throw 'To use sockets you need dart:io or dart:html';
}
}

View File

@@ -0,0 +1,9 @@
library get_core;
export 'src/get_interface.dart';
export 'src/get_main.dart';
export 'src/log.dart';
export 'src/smart_management.dart';
export 'src/typedefs.dart';
T? ambiguate<T>(T? value) => value;

View File

@@ -0,0 +1,15 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'log.dart';
import 'smart_management.dart';
/// GetInterface allows any auxiliary package to be merged into the "Get"
/// class through extensions
abstract class GetInterface {
SmartManagement smartManagement = SmartManagement.full;
RouterDelegate? routerDelegate;
RouteInformationParser? routeInformationParser;
bool isLogEnable = kDebugMode;
LogWriterCallback log = defaultLogWriterCallback;
}

View File

@@ -0,0 +1,12 @@
import 'get_interface.dart';
///Use to instead of Navigator.push, off instead of Navigator.pushReplacement,
///offAll instead of Navigator.pushAndRemoveUntil. For named routes just
///add "named" after them. Example: toNamed, offNamed, and AllNamed.
///To return to the previous screen, use back().
///No need to pass any context to Get, just put the name of the route inside
///the parentheses and the magic will occur.
class _GetImpl extends GetInterface {}
// ignore: non_constant_identifier_names
final Get = _GetImpl();

View File

@@ -0,0 +1,10 @@
import 'dart:developer' as developer;
import 'get_main.dart';
///VoidCallback from logs
typedef LogWriterCallback = void Function(String text, {bool isError});
/// default logger from GetX
void defaultLogWriterCallback(String value, {bool isError = false}) {
if (isError || Get.isLogEnable) developer.log(value, name: 'GETX');
}

View File

@@ -0,0 +1,22 @@
/// GetX by default disposes unused controllers from memory,
/// Through different behaviors.
/// SmartManagement.full
/// [SmartManagement.full] is the default one. Dispose classes that are
/// not being used and were not set to be permanent. In the majority
/// of the cases you will want to keep this config untouched.
/// If you new to GetX then don't change this.
/// [SmartManagement.onlyBuilder] only controllers started in init:
/// or loaded into a Binding with Get.lazyPut() will be disposed. If you use
/// Get.put() or Get.putAsync() or any other approach, SmartManagement
/// will not have permissions to exclude this dependency. With the default
/// behavior, even widgets instantiated with "Get.put" will be removed,
/// unlike SmartManagement.onlyBuilders.
/// [SmartManagement.keepFactory]Just like SmartManagement.full,
/// it will remove it's dependencies when it's not being used anymore.
/// However, it will keep their factory, which means it will recreate
/// the dependency if you need that instance again.
enum SmartManagement {
full,
onlyBuilder,
keepFactory,
}

View File

@@ -0,0 +1 @@
typedef ValueUpdater<T> = T Function();

View File

@@ -0,0 +1,4 @@
export 'src/bindings_interface.dart';
export 'src/extension_instance.dart';
export 'src/get_instance.dart';
export 'src/lifecycle.dart';

View File

@@ -0,0 +1,65 @@
import 'get_instance.dart';
/// [Bindings] should be extended or implemented.
/// When using `GetMaterialApp`, all `GetPage`s and navigation
/// methods (like Get.to()) have a `binding` property that takes an
/// instance of Bindings to manage the
/// dependencies() (via Get.put()) for the Route you are opening.
// ignore: one_member_abstracts
abstract class Bindings {
void dependencies();
}
/// Simplifies Bindings generation from a single callback.
/// To avoid the creation of a custom Binding instance per route.
///
/// Example:
/// ```
/// GetPage(
/// name: '/',
/// page: () => Home(),
/// // This might cause you an error.
/// // binding: BindingsBuilder(() => Get.put(HomeController())),
/// binding: BindingsBuilder(() { Get.put(HomeController(); })),
/// // Using .lazyPut() works fine.
/// // binding: BindingsBuilder(() => Get.lazyPut(() => HomeController())),
/// ),
/// ```
class BindingsBuilder<T> extends Bindings {
/// Register your dependencies in the [builder] callback.
final BindingBuilderCallback builder;
/// Shortcut to register 1 Controller with Get.put(),
/// Prevents the issue of the fat arrow function with the constructor.
/// BindingsBuilder(() => Get.put(HomeController())),
///
/// Sample:
/// ```
/// GetPage(
/// name: '/',
/// page: () => Home(),
/// binding: BindingsBuilder.put(() => HomeController()),
/// ),
/// ```
factory BindingsBuilder.put(InstanceBuilderCallback<T> builder,
{String? tag, bool permanent = false}) {
return BindingsBuilder(
() => GetInstance().put<T>(builder(), tag: tag, permanent: permanent));
}
/// WARNING: don't use `()=> Get.put(Controller())`,
/// if only passing 1 callback use `BindingsBuilder.put(Controller())`
/// or `BindingsBuilder(()=> Get.lazyPut(Controller()))`
BindingsBuilder(this.builder);
@override
void dependencies() {
builder();
}
}
// abstract class INavigation {}
// typedef Snack = Function();
// typedef Modal = Function();
// typedef Route = Function();
typedef BindingBuilderCallback = void Function();

View File

@@ -0,0 +1,160 @@
import '../../route_manager.dart';
import 'get_instance.dart';
extension Inst on GetInterface {
/// Creates a new Instance<S> lazily from the `<S>builder()` callback.
///
/// The first time you call `Get.find()`, the `builder()` callback will create
/// the Instance and persisted as a Singleton (like you would use
/// `Get.put()`).
///
/// Using `Get.smartManagement` as [SmartManagement.keepFactory] has
/// the same outcome
/// as using `fenix:true` :
/// The internal register of `builder()` will remain in memory to recreate
/// the Instance if the Instance has been removed with `Get.delete()`.
/// Therefore, future calls to `Get.find()` will return the same Instance.
///
/// If you need to make use of GetxController's life-cycle
/// (`onInit(), onStart(), onClose()`)
/// [fenix] is a great choice to mix with `GetBuilder` and `GetX` widgets,
/// and/or [GetMaterialApp] Navigation.
///
/// You could use `Get.lazyPut(fenix:true)` in your app's `main()` instead of
/// `Bindings` for each [GetPage].
/// And the memory management will be similar.
///
/// Subsequent calls to `Get.lazyPut` with the same parameters
/// (`<S>` and optionally [tag] will **not** override the original).
void lazyPut<S>(InstanceBuilderCallback<S> builder,
{String? tag, bool fenix = false}) {
GetInstance().lazyPut<S>(builder, tag: tag, fenix: fenix);
}
// void printInstanceStack() {
// GetInstance().printInstanceStack();
// }
/// async version of `Get.put()`.
/// Awaits for the resolution of the Future from `builder()`parameter and
/// stores the Instance returned.
Future<S> putAsync<S>(AsyncInstanceBuilderCallback<S> builder,
{String? tag, bool permanent = false}) async =>
GetInstance().putAsync<S>(builder, tag: tag, permanent: permanent);
/// Creates a new Class Instance [S] from the builder callback[S].
/// Every time `find<S>()` is used, it calls the builder method to generate
/// a new Instance [S].
/// It also registers each `instance.onClose()` with the current
/// Route `GetConfig.currentRoute` to keep the lifecycle active.
/// Is important to know that the instances created are only stored per Route.
/// So, if you call `Get.delete<T>()` the "instance factory" used in this
/// method (`Get.create<T>()`) will be removed, but NOT the instances
/// already created by it.
/// Uses `tag` as the other methods.
///
/// Example:
///
/// ```create(() => Repl());
/// Repl a = find();
/// Repl b = find();
/// print(a==b); (false)```
void create<S>(InstanceBuilderCallback<S> builder,
{String? tag, bool permanent = true}) =>
GetInstance().create<S>(builder, tag: tag, permanent: permanent);
/// Finds a Instance of the required Class `<S>`(or [tag])
/// In the case of using `Get.create()`, it will generate an Instance
/// each time you call `Get.find()`.
S find<S>({String? tag}) => GetInstance().find<S>(tag: tag);
/// Injects an `Instance<S>` in memory.
///
/// No need to define the generic type `<[S]>` as it's inferred
/// from the [dependency] parameter.
///
/// - [dependency] The Instance to be injected.
/// - [tag] optionally, use a [tag] as an "id" to create multiple records
/// of the same `Type<S>` the [tag] does **not** conflict with the same tags
/// used by other dependencies Types.
/// - [permanent] keeps the Instance in memory and persist it,
/// not following `Get.smartManagement`
/// rules. Although, can be removed by `GetInstance.reset()`
/// and `Get.delete()`
/// - [builder] If defined, the [dependency] must be returned from here
S put<S>(S dependency,
{String? tag,
bool permanent = false,
InstanceBuilderCallback<S>? builder}) =>
GetInstance().put<S>(dependency, tag: tag, permanent: permanent);
/// Clears all registered instances (and/or tags).
/// Even the persistent ones.
///
/// - `clearFactory` clears the callbacks registered by `Get.lazyPut()`
/// - `clearRouteBindings` clears Instances associated with Routes when using
/// [GetMaterialApp].
// bool reset(
// {@deprecated bool clearFactory = true,
// @deprecated bool clearRouteBindings = true}) =>
// GetInstance().reset(
// // ignore: deprecated_member_use_from_same_package
// clearFactory: clearFactory,
// // ignore: deprecated_member_use_from_same_package
// clearRouteBindings: clearRouteBindings);
/// Deletes the `Instance<S>`, cleaning the memory and closes any open
/// controllers (`DisposableInterface`).
///
/// - [tag] Optional "tag" used to register the Instance
/// - [force] Will delete an Instance even if marked as `permanent`.
Future<bool> delete<S>({String? tag, bool force = false}) async =>
GetInstance().delete<S>(tag: tag, force: force);
/// Deletes all Instances, cleaning the memory and closes any open
/// controllers (`DisposableInterface`).
///
/// - [force] Will delete the Instances even if marked as `permanent`.
Future<void> deleteAll({bool force = false}) async =>
GetInstance().deleteAll(force: force);
void reloadAll({bool force = false}) => GetInstance().reloadAll(force: force);
void reload<S>({String? tag, String? key, bool force = false}) =>
GetInstance().reload<S>(tag: tag, key: key, force: force);
/// Checks if a Class `Instance<S>` (or [tag]) is registered in memory.
/// - [tag] optional, if you use a [tag] to register the Instance.
bool isRegistered<S>({String? tag}) =>
GetInstance().isRegistered<S>(tag: tag);
/// Checks if an `Instance<S>` (or [tag]) returned from a factory builder
/// `Get.lazyPut()`, is registered in memory.
/// - [tag] optional, if you use a [tag] to register the Instance.
bool isPrepared<S>({String? tag}) => GetInstance().isPrepared<S>(tag: tag);
/// Replace a parent instance of a class in dependency management
/// with a [child] instance
/// - [tag] optional, if you use a [tag] to register the Instance.
void replace<P>(P child, {String? tag}) {
final info = GetInstance().getInstanceInfo<P>(tag: tag);
final permanent = (info.isPermanent ?? false);
delete<P>(tag: tag, force: permanent);
put(child, tag: tag, permanent: permanent);
}
/// Replaces a parent instance with a new Instance<P> lazily from the
/// `<P>builder()` callback.
/// - [tag] optional, if you use a [tag] to register the Instance.
/// - [fenix] optional
///
/// Note: if fenix is not provided it will be set to true if
/// the parent instance was permanent
void lazyReplace<P>(InstanceBuilderCallback<P> builder,
{String? tag, bool? fenix}) {
final info = GetInstance().getInstanceInfo<P>(tag: tag);
final permanent = (info.isPermanent ?? false);
delete<P>(tag: tag, force: permanent);
lazyPut(builder, tag: tag, fenix: fenix ?? permanent);
}
}

View File

@@ -0,0 +1,551 @@
import 'dart:async';
import '../../get_core/get_core.dart';
import '../../get_navigation/src/router_report.dart';
import 'lifecycle.dart';
class InstanceInfo {
final bool? isPermanent;
final bool? isSingleton;
bool get isCreate => !isSingleton!;
final bool isRegistered;
final bool isPrepared;
final bool? isInit;
const InstanceInfo({
required this.isPermanent,
required this.isSingleton,
required this.isRegistered,
required this.isPrepared,
required this.isInit,
});
}
class GetInstance {
factory GetInstance() => _getInstance ??= const GetInstance._();
const GetInstance._();
static GetInstance? _getInstance;
T call<T>() => find<T>();
/// Holds references to every registered Instance when using
/// `Get.put()`
static final Map<String, _InstanceBuilderFactory> _singl = {};
/// Holds a reference to every registered callback when using
/// `Get.lazyPut()`
// static final Map<String, _Lazy> _factory = {};
void injector<S>(
InjectorBuilderCallback<S> fn, {
String? tag,
bool fenix = false,
// bool permanent = false,
}) {
lazyPut(
() => fn(this),
tag: tag,
fenix: fenix,
// permanent: permanent,
);
}
/// async version of `Get.put()`.
/// Awaits for the resolution of the Future from `builder()` parameter and
/// stores the Instance returned.
Future<S> putAsync<S>(
AsyncInstanceBuilderCallback<S> builder, {
String? tag,
bool permanent = false,
}) async {
return put<S>(await builder(), tag: tag, permanent: permanent);
}
/// Injects an instance `<S>` in memory to be globally accessible.
///
/// No need to define the generic type `<S>` as it's inferred from
/// the [dependency]
///
/// - [dependency] The Instance to be injected.
/// - [tag] optionally, use a [tag] as an "id" to create multiple records of
/// the same Type<[S]>
/// - [permanent] keeps the Instance in memory, not following
/// `Get.smartManagement` rules.
S put<S>(
S dependency, {
String? tag,
bool permanent = false,
@deprecated InstanceBuilderCallback<S>? builder,
}) {
_insert(
isSingleton: true,
name: tag,
permanent: permanent,
builder: builder ?? (() => dependency));
return find<S>(tag: tag);
}
/// Creates a new Instance<S> lazily from the `<S>builder()` callback.
///
/// The first time you call `Get.find()`, the `builder()` callback will create
/// the Instance and persisted as a Singleton (like you would
/// use `Get.put()`).
///
/// Using `Get.smartManagement` as [SmartManagement.keepFactory] has
/// the same outcome as using `fenix:true` :
/// The internal register of `builder()` will remain in memory to recreate
/// the Instance if the Instance has been removed with `Get.delete()`.
/// Therefore, future calls to `Get.find()` will return the same Instance.
///
/// If you need to make use of GetxController's life-cycle
/// (`onInit(), onStart(), onClose()`) [fenix] is a great choice to mix with
/// `GetBuilder()` and `GetX()` widgets, and/or `GetMaterialApp` Navigation.
///
/// You could use `Get.lazyPut(fenix:true)` in your app's `main()` instead
/// of `Bindings()` for each `GetPage`.
/// And the memory management will be similar.
///
/// Subsequent calls to `Get.lazyPut()` with the same parameters
/// (<[S]> and optionally [tag] will **not** override the original).
void lazyPut<S>(
InstanceBuilderCallback<S> builder, {
String? tag,
bool? fenix,
bool permanent = false,
}) {
_insert(
isSingleton: true,
name: tag,
permanent: permanent,
builder: builder,
fenix: fenix ?? Get.smartManagement == SmartManagement.keepFactory,
);
}
/// Creates a new Class Instance [S] from the builder callback[S].
/// Every time [find]<[S]>() is used, it calls the builder method to generate
/// a new Instance [S].
/// It also registers each `instance.onClose()` with the current
/// Route `Get.reference` to keep the lifecycle active.
/// Is important to know that the instances created are only stored per Route.
/// So, if you call `Get.delete<T>()` the "instance factory" used in this
/// method (`Get.create<T>()`) will be removed, but NOT the instances
/// already created by it.
///
/// Example:
///
/// ```create(() => Repl());
/// Repl a = find();
/// Repl b = find();
/// print(a==b); (false)```
void create<S>(
InstanceBuilderCallback<S> builder, {
String? tag,
bool permanent = true,
}) {
_insert(
isSingleton: false,
name: tag,
builder: builder,
permanent: permanent,
);
}
/// Injects the Instance [S] builder into the `_singleton` HashMap.
void _insert<S>({
bool? isSingleton,
String? name,
bool permanent = false,
required InstanceBuilderCallback<S> builder,
bool fenix = false,
}) {
final key = _getKey(S, name);
if (_singl.containsKey(key)) {
final dep = _singl[key];
if (dep != null && dep.isDirty) {
_singl[key] = _InstanceBuilderFactory<S>(
isSingleton,
builder,
permanent,
false,
fenix,
name,
lateRemove: dep as _InstanceBuilderFactory<S>,
);
}
} else {
_singl[key] = _InstanceBuilderFactory<S>(
isSingleton,
builder,
permanent,
false,
fenix,
name,
);
}
}
/// Initializes the dependencies for a Class Instance [S] (or tag),
/// If its a Controller, it starts the lifecycle process.
/// Optionally associating the current Route to the lifetime of the instance,
/// if `Get.smartManagement` is marked as [SmartManagement.full] or
/// [SmartManagement.keepFactory]
/// Only flags `isInit` if it's using `Get.create()`
/// (not for Singletons access).
/// Returns the instance if not initialized, required for Get.create() to
/// work properly.
S? _initDependencies<S>({String? name}) {
final key = _getKey(S, name);
final isInit = _singl[key]!.isInit;
S? i;
if (!isInit) {
i = _startController<S>(tag: name);
if (_singl[key]!.isSingleton!) {
_singl[key]!.isInit = true;
if (Get.smartManagement != SmartManagement.onlyBuilder) {
RouterReportManager.reportDependencyLinkedToRoute(_getKey(S, name));
}
}
}
return i;
}
InstanceInfo getInstanceInfo<S>({String? tag}) {
final build = _getDependency<S>(tag: tag);
return InstanceInfo(
isPermanent: build?.permanent,
isSingleton: build?.isSingleton,
isRegistered: isRegistered<S>(tag: tag),
isPrepared: !(build?.isInit ?? true),
isInit: build?.isInit,
);
}
_InstanceBuilderFactory? _getDependency<S>({String? tag, String? key}) {
final newKey = key ?? _getKey(S, tag);
if (!_singl.containsKey(newKey)) {
Get.log('Instance "$newKey" is not registered.', isError: true);
return null;
} else {
return _singl[newKey];
}
}
void markAsDirty<S>({String? tag, String? key}) {
final newKey = key ?? _getKey(S, tag);
if (_singl.containsKey(newKey)) {
final dep = _singl[newKey];
if (dep != null && !dep.permanent) {
dep.isDirty = true;
}
}
}
/// Initializes the controller
S _startController<S>({String? tag}) {
final key = _getKey(S, tag);
final i = _singl[key]!.getDependency() as S;
if (i is GetLifeCycleBase) {
i.onStart();
if (tag == null) {
Get.log('Instance "$S" has been initialized');
} else {
Get.log('Instance "$S" with tag "$tag" has been initialized');
}
if (!_singl[key]!.isSingleton!) {
RouterReportManager.appendRouteByCreate(i);
}
}
return i;
}
S putOrFind<S>(InstanceBuilderCallback<S> dep, {String? tag}) {
final key = _getKey(S, tag);
if (_singl.containsKey(key)) {
return _singl[key]!.getDependency() as S;
} else {
return GetInstance().put(dep(), tag: tag);
}
}
/// Finds the registered type <[S]> (or [tag])
/// In case of using Get.[create] to register a type <[S]> or [tag],
/// it will create an instance each time you call [find].
/// If the registered type <[S]> (or [tag]) is a Controller,
/// it will initialize it's lifecycle.
S find<S>({String? tag}) {
final key = _getKey(S, tag);
if (isRegistered<S>(tag: tag)) {
final dep = _singl[key];
if (dep == null) {
if (tag == null) {
throw 'Class "$S" is not registered';
} else {
throw 'Class "$S" with tag "$tag" is not registered';
}
}
// if (dep.lateRemove != null) {
// dep.isDirty = true;
// if(dep.fenix)
// }
/// although dirty solution, the lifecycle starts inside
/// `initDependencies`, so we have to return the instance from there
/// to make it compatible with `Get.create()`.
final i = _initDependencies<S>(name: tag);
return i ?? dep.getDependency() as S;
} else {
// ignore: lines_longer_than_80_chars
throw '"$S" not found. You need to call "Get.put($S())" or "Get.lazyPut(()=>$S())"';
}
}
/// Generates the key based on [type] (and optionally a [name])
/// to register an Instance Builder in the hashmap.
String _getKey(Type type, String? name) {
return name == null ? type.toString() : type.toString() + name;
}
/// Clears all registered instances (and/or tags).
/// Even the persistent ones.
/// This should be used at the end or tearDown of unit tests.
///
/// `clearFactory` clears the callbacks registered by [lazyPut]
/// `clearRouteBindings` clears Instances associated with routes.
///
bool resetInstance(
{@deprecated bool clearFactory = true, bool clearRouteBindings = true}) {
// if (clearFactory) _factory.clear();
// deleteAll(force: true);
if (clearRouteBindings) RouterReportManager.clearRouteKeys();
_singl.clear();
return true;
}
/// Delete registered Class Instance [S] (or [tag]) and, closes any open
/// controllers `DisposableInterface`, cleans up the memory
///
/// /// Deletes the Instance<[S]>, cleaning the memory.
// ///
// /// - [tag] Optional "tag" used to register the Instance
// /// - [key] For internal usage, is the processed key used to register
// /// the Instance. **don't use** it unless you know what you are doing.
/// Deletes the Instance<[S]>, cleaning the memory and closes any open
/// controllers (`DisposableInterface`).
///
/// - [tag] Optional "tag" used to register the Instance
/// - [key] For internal usage, is the processed key used to register
/// the Instance. **don't use** it unless you know what you are doing.
/// - [force] Will delete an Instance even if marked as `permanent`.
bool delete<S>({String? tag, String? key, bool force = false}) {
final newKey = key ?? _getKey(S, tag);
if (!_singl.containsKey(newKey)) {
Get.log('Instance "$newKey" already removed.', isError: true);
return false;
}
final dep = _singl[newKey];
if (dep == null) return false;
final _InstanceBuilderFactory builder;
if (dep.isDirty) {
builder = dep.lateRemove ?? dep;
} else {
builder = dep;
}
if (builder.permanent && !force) {
Get.log(
// ignore: lines_longer_than_80_chars
'"$newKey" has been marked as permanent, SmartManagement is not authorized to delete it.',
isError: true,
);
return false;
}
final i = builder.dependency;
if (i is GetxServiceMixin && !force) {
return false;
}
if (i is GetLifeCycleBase) {
i.onDelete();
Get.log('"$newKey" onDelete() called');
}
if (builder.fenix) {
builder.dependency = null;
builder.isInit = false;
return true;
} else {
if (dep.lateRemove != null) {
dep.lateRemove = null;
Get.log('"$newKey" deleted from memory');
return false;
} else {
_singl.remove(newKey);
if (_singl.containsKey(newKey)) {
Get.log('Error removing object "$newKey"', isError: true);
} else {
Get.log('"$newKey" deleted from memory');
}
return true;
}
}
}
/// Delete all registered Class Instances and, closes any open
/// controllers `DisposableInterface`, cleans up the memory
///
/// - [force] Will delete the Instances even if marked as `permanent`.
void deleteAll({bool force = false}) {
final keys = _singl.keys.toList();
for (final key in keys) {
delete(key: key, force: force);
}
}
void reloadAll({bool force = false}) {
_singl.forEach((key, value) {
if (value.permanent && !force) {
Get.log('Instance "$key" is permanent. Skipping reload');
} else {
value.dependency = null;
value.isInit = false;
Get.log('Instance "$key" was reloaded.');
}
});
}
void reload<S>({
String? tag,
String? key,
bool force = false,
}) {
final newKey = key ?? _getKey(S, tag);
final builder = _getDependency<S>(tag: tag, key: newKey);
if (builder == null) return;
if (builder.permanent && !force) {
Get.log(
'''Instance "$newKey" is permanent. Use [force = true] to force the restart.''',
isError: true,
);
return;
}
final i = builder.dependency;
if (i is GetxServiceMixin && !force) {
return;
}
if (i is GetLifeCycleBase) {
i.onDelete();
Get.log('"$newKey" onDelete() called');
}
builder.dependency = null;
builder.isInit = false;
Get.log('Instance "$newKey" was restarted.');
}
/// Check if a Class Instance<[S]> (or [tag]) is registered in memory.
/// - [tag] is optional, if you used a [tag] to register the Instance.
bool isRegistered<S>({String? tag}) => _singl.containsKey(_getKey(S, tag));
/// Checks if a lazy factory callback `Get.lazyPut()` that returns an
/// Instance<[S]> is registered in memory.
/// - [tag] is optional, if you used a [tag] to register the lazy Instance.
bool isPrepared<S>({String? tag}) {
final newKey = _getKey(S, tag);
final builder = _getDependency<S>(tag: tag, key: newKey);
if (builder == null) {
return false;
}
if (!builder.isInit) {
return true;
}
return false;
}
}
typedef InstanceBuilderCallback<S> = S Function();
typedef InjectorBuilderCallback<S> = S Function(GetInstance);
typedef AsyncInstanceBuilderCallback<S> = Future<S> Function();
/// Internal class to register instances with `Get.put<S>()`.
class _InstanceBuilderFactory<S> {
/// Marks the Builder as a single instance.
/// For reusing [dependency] instead of [builderFunc]
bool? isSingleton;
/// When fenix mode is avaliable, when a new instance is need
/// Instance manager will recreate a new instance of S
bool fenix;
/// Stores the actual object instance when [isSingleton]=true.
S? dependency;
/// Generates (and regenerates) the instance when [isSingleton]=false.
/// Usually used by factory methods
InstanceBuilderCallback<S> builderFunc;
/// Flag to persist the instance in memory,
/// without considering `Get.smartManagement`
bool permanent = false;
bool isInit = false;
_InstanceBuilderFactory<S>? lateRemove;
bool isDirty = false;
String? tag;
_InstanceBuilderFactory(
this.isSingleton,
this.builderFunc,
this.permanent,
this.isInit,
this.fenix,
this.tag, {
this.lateRemove,
});
void _showInitLog() {
if (tag == null) {
Get.log('Instance "$S" has been created');
} else {
Get.log('Instance "$S" has been created with tag "$tag"');
}
}
/// Gets the actual instance by it's [builderFunc] or the persisted instance.
S getDependency() {
if (isSingleton!) {
if (dependency == null) {
_showInitLog();
dependency = builderFunc();
}
return dependency!;
} else {
return builderFunc();
}
}
}

View File

@@ -0,0 +1,104 @@
import '../../get_core/get_core.dart';
/// Special callable class to keep the contract of a regular method, and avoid
/// overrides if you extend the class that uses it, as Dart has no final
/// methods.
/// Used in `DisposableInterface` to avoid the danger of overriding onStart.
class InternalFinalCallback<T> {
ValueUpdater<T>? _callback;
InternalFinalCallback({ValueUpdater<T>? callback}) : _callback = callback;
T call() => _callback!.call();
}
/// The [GetLifeCycle]
///
/// ```dart
/// class SomeController with GetLifeCycle {
/// SomeController() {
/// configureLifeCycle();
/// }
/// }
/// ```
mixin GetLifeCycleBase {
/// Called at the exact moment the widget is allocated in memory.
/// It uses an internal "callable" type, to avoid any @overrides in subclases.
/// This method should be internal and is required to define the
/// lifetime cycle of the subclass.
final onStart = InternalFinalCallback<void>();
// /// The `configureLifeCycle` works as a constructor for the [GetLifeCycle]
// ///
// /// This method must be invoked in the constructor of the implementation
// void configureLifeCycle() {
// if (_initialized) return;
// }
/// Internal callback that starts the cycle of this controller.
final onDelete = InternalFinalCallback<void>();
/// Called immediately after the widget is allocated in memory.
/// You might use this to initialize something for the controller.
void onInit() {}
/// Called 1 frame after onInit(). It is the perfect place to enter
/// navigation events, like snackbar, dialogs, or a new route, or
/// async request.
void onReady() {}
/// Called before [onDelete] method. [onClose] might be used to
/// dispose resources used by the controller. Like closing events,
/// or streams before the controller is destroyed.
/// Or dispose objects that can potentially create some memory leaks,
/// like TextEditingControllers, AnimationControllers.
/// Might be useful as well to persist some data on disk.
void onClose() {}
bool _initialized = false;
/// Checks whether the controller has already been initialized.
bool get initialized => _initialized;
// Internal callback that starts the cycle of this controller.
void _onStart() {
if (_initialized) return;
onInit();
_initialized = true;
}
bool _isClosed = false;
/// Checks whether the controller has already been closed.
bool get isClosed => _isClosed;
// Internal callback that starts the cycle of this controller.
void _onDelete() {
if (_isClosed) return;
_isClosed = true;
onClose();
}
void $configureLifeCycle() {
_checkIfAlreadyConfigured();
onStart._callback = _onStart;
onDelete._callback = _onDelete;
}
void _checkIfAlreadyConfigured() {
if (_initialized) {
throw """You can only call configureLifeCycle once.
The proper place to insert it is in your class's constructor
that inherits GetLifeCycle.""";
}
}
}
abstract class GetLifeCycle with GetLifeCycleBase {
GetLifeCycle() {
$configureLifeCycle();
}
}
/// Allow track difference between GetxServices and GetxControllers
mixin GetxServiceMixin {}

View File

@@ -0,0 +1,20 @@
library get_navigation;
export 'src/bottomsheet/bottomsheet.dart';
export 'src/extension_navigation.dart';
export 'src/nav2/get_information_parser.dart';
export 'src/nav2/get_nav_config.dart';
export 'src/nav2/get_router_delegate.dart';
export 'src/nav2/router_outlet.dart';
export 'src/root/get_cupertino_app.dart';
export 'src/root/get_material_app.dart';
export 'src/root/internacionalization.dart';
export 'src/root/root_controller.dart';
export 'src/routes/custom_transition.dart';
export 'src/routes/default_route.dart';
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/snackbar.dart';
export 'src/snackbar/snackbar_controller.dart';

View File

@@ -0,0 +1,335 @@
import 'package:flutter/material.dart';
import '../../../get.dart';
import '../router_report.dart';
class GetModalBottomSheetRoute<T> extends PopupRoute<T> {
GetModalBottomSheetRoute({
this.builder,
this.theme,
this.barrierLabel,
this.backgroundColor,
this.isPersistent,
this.elevation,
this.shape,
this.removeTop = true,
this.clipBehavior,
this.modalBarrierColor,
this.isDismissible = true,
this.enableDrag = true,
required this.isScrollControlled,
RouteSettings? settings,
this.enterBottomSheetDuration = const Duration(milliseconds: 250),
this.exitBottomSheetDuration = const Duration(milliseconds: 200),
}) : super(settings: settings) {
RouterReportManager.reportCurrentRoute(this);
}
final bool? isPersistent;
final WidgetBuilder? builder;
final ThemeData? theme;
final bool isScrollControlled;
final Color? backgroundColor;
final double? elevation;
final ShapeBorder? shape;
final Clip? clipBehavior;
final Color? modalBarrierColor;
final bool isDismissible;
final bool enableDrag;
// final String name;
final Duration enterBottomSheetDuration;
final Duration exitBottomSheetDuration;
// remove safearea from top
final bool removeTop;
@override
Duration get transitionDuration => const Duration(milliseconds: 700);
@override
bool get barrierDismissible => isDismissible;
@override
final String? barrierLabel;
@override
Color get barrierColor => modalBarrierColor ?? Colors.black54;
AnimationController? _animationController;
@override
void dispose() {
RouterReportManager.reportRouteDispose(this);
super.dispose();
}
@override
AnimationController createAnimationController() {
assert(_animationController == null);
_animationController =
BottomSheet.createAnimationController(navigator!.overlay!);
_animationController!.duration = enterBottomSheetDuration;
_animationController!.reverseDuration = exitBottomSheetDuration;
return _animationController!;
}
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
final sheetTheme =
theme?.bottomSheetTheme ?? Theme.of(context).bottomSheetTheme;
// By definition, the bottom sheet is aligned to the bottom of the page
// and isn't exposed to the top padding of the MediaQuery.
Widget bottomSheet = MediaQuery.removePadding(
context: context,
removeTop: removeTop,
child: Padding(
padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: _GetModalBottomSheet<T>(
route: this,
backgroundColor: backgroundColor ??
sheetTheme.modalBackgroundColor ??
sheetTheme.backgroundColor,
elevation:
elevation ?? sheetTheme.modalElevation ?? sheetTheme.elevation,
shape: shape,
clipBehavior: clipBehavior,
isScrollControlled: isScrollControlled,
enableDrag: enableDrag,
),
),
);
if (theme != null) bottomSheet = Theme(data: theme!, child: bottomSheet);
return bottomSheet;
}
}
class _GetModalBottomSheet<T> extends StatefulWidget {
const _GetModalBottomSheet({
Key? key,
this.route,
this.backgroundColor,
this.elevation,
this.shape,
this.clipBehavior,
this.isScrollControlled = false,
this.enableDrag = true,
this.isPersistent = false,
}) : super(key: key);
final bool isPersistent;
final GetModalBottomSheetRoute<T>? route;
final bool isScrollControlled;
final Color? backgroundColor;
final double? elevation;
final ShapeBorder? shape;
final Clip? clipBehavior;
final bool enableDrag;
@override
_GetModalBottomSheetState<T> createState() => _GetModalBottomSheetState<T>();
}
class _GetModalBottomSheetState<T> extends State<_GetModalBottomSheet<T>> {
String _getRouteLabel(MaterialLocalizations localizations) {
if ((Theme.of(context).platform == TargetPlatform.android) ||
(Theme.of(context).platform == TargetPlatform.fuchsia)) {
return localizations.dialogLabel;
} else {
return '';
}
}
@override
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
assert(debugCheckHasMaterialLocalizations(context));
final mediaQuery = MediaQuery.of(context);
final localizations = MaterialLocalizations.of(context);
final routeLabel = _getRouteLabel(localizations);
return AnimatedBuilder(
animation: widget.route!.animation!,
builder: (context, child) {
// Disable the initial animation when accessible navigation is on so
// that the semantics are added to the tree at the correct time.
final animationValue = mediaQuery.accessibleNavigation
? 1.0
: widget.route!.animation!.value;
return Semantics(
scopesRoute: true,
namesRoute: true,
label: routeLabel,
explicitChildNodes: true,
child: ClipRect(
child: CustomSingleChildLayout(
delegate: _GetModalBottomSheetLayout(
animationValue, widget.isScrollControlled),
child: widget.isPersistent == false
? BottomSheet(
animationController: widget.route!._animationController,
onClosing: () {
if (widget.route!.isCurrent) {
Navigator.pop(context);
}
},
builder: widget.route!.builder!,
backgroundColor: widget.backgroundColor,
elevation: widget.elevation,
shape: widget.shape,
clipBehavior: widget.clipBehavior,
enableDrag: widget.enableDrag,
)
: Scaffold(
bottomSheet: BottomSheet(
animationController:
widget.route!._animationController,
onClosing: () {
// if (widget.route.isCurrent) {
// Navigator.pop(context);
// }
},
builder: widget.route!.builder!,
backgroundColor: widget.backgroundColor,
elevation: widget.elevation,
shape: widget.shape,
clipBehavior: widget.clipBehavior,
enableDrag: widget.enableDrag,
),
)),
),
);
},
);
}
}
class _GetPerModalBottomSheet<T> extends StatefulWidget {
const _GetPerModalBottomSheet({
Key? key,
this.route,
this.isPersistent,
this.backgroundColor,
this.elevation,
this.shape,
this.clipBehavior,
this.isScrollControlled = false,
this.enableDrag = true,
}) : super(key: key);
final bool? isPersistent;
final GetModalBottomSheetRoute<T>? route;
final bool isScrollControlled;
final Color? backgroundColor;
final double? elevation;
final ShapeBorder? shape;
final Clip? clipBehavior;
final bool enableDrag;
@override
// ignore: lines_longer_than_80_chars
_GetPerModalBottomSheetState<T> createState() =>
_GetPerModalBottomSheetState<T>();
}
// ignore: lines_longer_than_80_chars
class _GetPerModalBottomSheetState<T>
extends State<_GetPerModalBottomSheet<T>> {
String _getRouteLabel(MaterialLocalizations localizations) {
if ((Theme.of(context).platform == TargetPlatform.android) ||
(Theme.of(context).platform == TargetPlatform.fuchsia)) {
return localizations.dialogLabel;
} else {
return '';
}
}
@override
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
assert(debugCheckHasMaterialLocalizations(context));
final mediaQuery = MediaQuery.of(context);
final localizations = MaterialLocalizations.of(context);
final routeLabel = _getRouteLabel(localizations);
return AnimatedBuilder(
animation: widget.route!.animation!,
builder: (context, child) {
// Disable the initial animation when accessible navigation is on so
// that the semantics are added to the tree at the correct time.
final animationValue = mediaQuery.accessibleNavigation
? 1.0
: widget.route!.animation!.value;
return Semantics(
scopesRoute: true,
namesRoute: true,
label: routeLabel,
explicitChildNodes: true,
child: ClipRect(
child: CustomSingleChildLayout(
delegate: _GetModalBottomSheetLayout(
animationValue, widget.isScrollControlled),
child: widget.isPersistent == false
? BottomSheet(
animationController: widget.route!._animationController,
onClosing: () {
if (widget.route!.isCurrent) {
Navigator.pop(context);
}
},
builder: widget.route!.builder!,
backgroundColor: widget.backgroundColor,
elevation: widget.elevation,
shape: widget.shape,
clipBehavior: widget.clipBehavior,
enableDrag: widget.enableDrag,
)
: Scaffold(
bottomSheet: BottomSheet(
animationController:
widget.route!._animationController,
onClosing: () {
// if (widget.route.isCurrent) {
// Navigator.pop(context);
// }
},
builder: widget.route!.builder!,
backgroundColor: widget.backgroundColor,
elevation: widget.elevation,
shape: widget.shape,
clipBehavior: widget.clipBehavior,
enableDrag: widget.enableDrag,
),
)),
),
);
},
);
}
}
class _GetModalBottomSheetLayout extends SingleChildLayoutDelegate {
_GetModalBottomSheetLayout(this.progress, this.isScrollControlled);
final double progress;
final bool isScrollControlled;
@override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
return BoxConstraints(
minWidth: constraints.maxWidth,
maxWidth: constraints.maxWidth,
minHeight: 0.0,
maxHeight: isScrollControlled
? constraints.maxHeight
: constraints.maxHeight * 9.0 / 16.0,
);
}
@override
Offset getPositionForChild(Size size, Size childSize) {
return Offset(0.0, size.height - childSize.height * progress);
}
@override
bool shouldRelayout(_GetModalBottomSheetLayout oldDelegate) {
return progress != oldDelegate.progress;
}
}

View File

@@ -0,0 +1,73 @@
import 'package:flutter/widgets.dart';
import '../router_report.dart';
class GetDialogRoute<T> extends PopupRoute<T> {
GetDialogRoute({
required RoutePageBuilder pageBuilder,
bool barrierDismissible = true,
String? barrierLabel,
Color barrierColor = const Color(0x80000000),
Duration transitionDuration = const Duration(milliseconds: 200),
RouteTransitionsBuilder? transitionBuilder,
RouteSettings? settings,
}) : widget = pageBuilder,
_barrierDismissible = barrierDismissible,
_barrierLabel = barrierLabel,
_barrierColor = barrierColor,
_transitionDuration = transitionDuration,
_transitionBuilder = transitionBuilder,
super(settings: settings) {
RouterReportManager.reportCurrentRoute(this);
}
final RoutePageBuilder widget;
@override
bool get barrierDismissible => _barrierDismissible;
final bool _barrierDismissible;
@override
void dispose() {
RouterReportManager.reportRouteDispose(this);
super.dispose();
}
@override
String? get barrierLabel => _barrierLabel;
final String? _barrierLabel;
@override
Color get barrierColor => _barrierColor;
final Color _barrierColor;
@override
Duration get transitionDuration => _transitionDuration;
final Duration _transitionDuration;
final RouteTransitionsBuilder? _transitionBuilder;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return Semantics(
scopesRoute: true,
explicitChildNodes: true,
child: widget(context, animation, secondaryAnimation),
);
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
if (_transitionBuilder == null) {
return FadeTransition(
opacity: CurvedAnimation(
parent: animation,
curve: Curves.linear,
),
child: child);
} // Some default transition
return _transitionBuilder!(context, animation, secondaryAnimation, child);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import '../../../get.dart';
class GetInformationParser extends RouteInformationParser<GetNavConfig> {
final String initialRoute;
GetInformationParser({
this.initialRoute = '/',
}) {
Get.log('GetInformationParser is created !');
}
@override
SynchronousFuture<GetNavConfig> parseRouteInformation(
RouteInformation routeInformation,
) {
var location = routeInformation.location;
if (location == '/') {
//check if there is a corresponding page
//if not, relocate to initialRoute
if (!Get.routeTree.routes.any((element) => element.name == '/')) {
location = initialRoute;
}
}
Get.log('GetInformationParser: route location: $location');
final matchResult = Get.routeTree.matchRoute(location ?? initialRoute);
return SynchronousFuture(
GetNavConfig(
currentTreeBranch: matchResult.treeBranch,
location: location,
state: routeInformation.state,
),
);
}
@override
RouteInformation restoreRouteInformation(GetNavConfig configuration) {
return RouteInformation(
location: configuration.location,
state: configuration.state,
);
}
}

View File

@@ -0,0 +1,60 @@
import 'package:flutter/widgets.dart';
import '../../../get.dart';
// class GetRouterState extends GetxController {
// GetRouterState({required this.currentTreeBranch});
// final List<GetPage> currentTreeBranch;
// GetPage? get currentPage => currentTreeBranch.last;
// static GetNavConfig? fromRoute(String route) {
// final res = Get.routeTree.matchRoute(route);
// if (res.treeBranch.isEmpty) return null;
// return GetNavConfig(
// currentTreeBranch: res.treeBranch,
// location: route,
// state: null,
// );
// }
// }
/// This config enables us to navigate directly to a sub-url
class GetNavConfig extends RouteInformation {
final List<GetPage> currentTreeBranch;
GetPage? get currentPage => currentTreeBranch.last;
GetNavConfig({
required this.currentTreeBranch,
required String? location,
required Object? state,
}) : super(
location: location,
state: state,
);
GetNavConfig copyWith({
List<GetPage>? currentTreeBranch,
required String? location,
required Object? state,
}) {
return GetNavConfig(
currentTreeBranch: currentTreeBranch ?? this.currentTreeBranch,
location: location ?? this.location,
state: state ?? this.state,
);
}
static GetNavConfig? fromRoute(String route) {
final res = Get.routeTree.matchRoute(route);
if (res.treeBranch.isEmpty) return null;
return GetNavConfig(
currentTreeBranch: res.treeBranch,
location: route,
state: null,
);
}
@override
String toString() => '''
======GetNavConfig=====\ncurrentTreeBranch: $currentTreeBranch\ncurrentPage: $currentPage\n======GetNavConfig=====''';
}

View File

@@ -0,0 +1,486 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../../get.dart';
import '../../../get_state_manager/src/simple/list_notifier.dart';
class GetDelegate extends RouterDelegate<GetNavConfig>
with ListenableMixin, ListNotifierMixin {
final List<GetNavConfig> history = <GetNavConfig>[];
final PopMode backButtonPopMode;
final PreventDuplicateHandlingMode preventDuplicateHandlingMode;
final GetPage notFoundRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionDelegate<dynamic>? transitionDelegate;
final _allCompleters = <GetPage, Completer>{};
GetDelegate({
GetPage? notFoundRoute,
this.navigatorObservers,
this.transitionDelegate,
this.backButtonPopMode = PopMode.History,
this.preventDuplicateHandlingMode =
PreventDuplicateHandlingMode.ReorderRoutes,
}) : notFoundRoute = notFoundRoute ??
GetPage(
name: '/404',
page: () => const Scaffold(
body: Text('Route not found'),
),
) {
Get.log('GetDelegate is created !');
}
@override
GetNavConfig? get currentConfiguration {
if (history.isEmpty) return null;
final route = history.last;
return route;
}
GlobalKey<NavigatorState> get navigatorKey => Get.key;
Map<String, String> get parameters {
return currentConfiguration?.currentPage?.parameters ?? {};
}
T arguments<T>() {
return currentConfiguration?.currentPage?.arguments as T;
}
/// Removes routes according to [PopMode]
/// until it reaches the specifc [fullRoute],
/// DOES NOT remove the [fullRoute]
Future<void> backUntil(
String fullRoute, {
PopMode popMode = PopMode.Page,
}) async {
// remove history or page entries until you meet route
var iterator = currentConfiguration;
while (_canPop(popMode) &&
iterator != null &&
iterator.location != fullRoute) {
await _pop(popMode);
// replace iterator
iterator = currentConfiguration;
}
refresh();
}
@override
Widget build(BuildContext context) {
final pages = getVisualPages();
if (pages.isEmpty) return const SizedBox.shrink();
final extraObservers = navigatorObservers;
return GetNavigator(
key: navigatorKey,
onPopPage: _onPopVisualRoute,
pages: pages,
observers: [
GetObserver(),
if (extraObservers != null) ...extraObservers,
],
transitionDelegate:
transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(),
);
}
// void _unsafeHistoryClear() {
// history.clear();
// }
Future<bool> canPopHistory() {
return SynchronousFuture(_canPopHistory());
}
Future<bool> canPopPage() {
return SynchronousFuture(_canPopPage());
}
/// gets the visual pages from the current history entry
///
/// visual pages must have [participatesInRootNavigator] set to true
List<GetPage> getVisualPages() {
final currentHistory = currentConfiguration;
if (currentHistory == null) return <GetPage>[];
final res = currentHistory.currentTreeBranch
.where((r) => r.participatesInRootNavigator != null);
if (res.isEmpty) {
//default behavoir, all routes participate in root navigator
return history.map((e) => e.currentPage!).toList();
} else {
//user specified at least one participatesInRootNavigator
return res
.where((element) => element.participatesInRootNavigator == true)
.toList();
}
}
// GetPageRoute getPageRoute(RouteSettings? settings) {
// return PageRedirect(settings ?? RouteSettings(name: '/404'), _notFound())
// .page();
// }
Future<bool> handlePopupRoutes({
Object? result,
}) async {
Route? currentRoute;
navigatorKey.currentState!.popUntil((route) {
currentRoute = route;
return true;
});
if (currentRoute is PopupRoute) {
return await navigatorKey.currentState!.maybePop(result);
}
return false;
}
Future<T?>? offAndToNamed<T>(
String page, {
dynamic arguments,
int? id,
dynamic result,
Map<String, String>? parameters,
PopMode popMode = PopMode.History,
}) async {
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
await popRoute(result: result);
return toNamed(page, arguments: arguments, parameters: parameters);
}
Future<T> offNamed<T>(
String page, {
dynamic arguments,
Map<String, String>? parameters,
}) async {
history.removeLast();
return toNamed<T>(page, arguments: arguments, parameters: parameters);
}
Future<GetNavConfig?> popHistory() async {
return await _popHistory();
}
// returns the popped page
@override
Future<bool> popRoute({
Object? result,
PopMode popMode = PopMode.Page,
}) async {
//Returning false will cause the entire app to be popped.
final wasPopup = await handlePopupRoutes(result: result);
if (wasPopup) return true;
final popped = await _pop(popMode);
refresh();
if (popped != null) {
//emulate the old pop with result
return true;
}
return false;
}
/// Adds a new history entry and waits for the result
Future<void> pushHistory(
GetNavConfig config, {
bool rebuildStack = true,
}) async {
//this changes the currentConfiguration
await _pushHistory(config);
if (rebuildStack) {
refresh();
}
}
Future<GetNavConfig?> runMiddleware(GetNavConfig config) async {
final middlewares = config.currentTreeBranch.last.middlewares;
if (middlewares == null) {
return config;
}
var iterator = config;
for (var item in middlewares) {
var redirectRes = await item.redirectDelegate(iterator);
if (redirectRes == null) return null;
iterator = redirectRes;
}
return iterator;
}
@override
Future<void> setNewRoutePath(GetNavConfig configuration) async {
await pushHistory(configuration);
}
Future<T> toNamed<T>(
String page, {
dynamic arguments,
Map<String, String>? parameters,
}) async {
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
final decoder = Get.routeTree.matchRoute(page, arguments: arguments);
decoder.replaceArguments(arguments);
final completer = Completer<T>();
if (decoder.route != null) {
_allCompleters[decoder.route!] = completer;
await pushHistory(
GetNavConfig(
currentTreeBranch: decoder.treeBranch,
location: page,
state: null, //TODO: persist state?
),
);
return completer.future;
} else {
///TODO: IMPLEMENT ROUTE NOT FOUND
return Future.value();
}
}
bool _canPop(PopMode mode) {
switch (mode) {
case PopMode.History:
return _canPopHistory();
case PopMode.Page:
default:
return _canPopPage();
}
}
bool _canPopHistory() {
return history.length > 1;
}
bool _canPopPage() {
final currentTreeBranch = currentConfiguration?.currentTreeBranch;
if (currentTreeBranch == null) return false;
return currentTreeBranch.length > 1 ? true : _canPopHistory();
}
Future<GetNavConfig?> _doPopHistory() async {
return await _unsafeHistoryRemoveAt(history.length - 1);
}
// @override
// Future<void> setInitialRoutePath(GetNavConfig configuration) async {
// //no need to clear history with Reorder route strategy
// // _unsafeHistoryClear();
// // _resultCompleter.clear();
// await pushHistory(configuration);
// }
Future<GetNavConfig?> _doPopPage() async {
final currentBranch = currentConfiguration?.currentTreeBranch;
if (currentBranch != null && currentBranch.length > 1) {
//remove last part only
final remaining = currentBranch.take(currentBranch.length - 1);
final prevHistoryEntry =
history.length > 1 ? history[history.length - 2] : null;
//check if current route is the same as the previous route
if (prevHistoryEntry != null) {
//if so, pop the entire history entry
final newLocation = remaining.last.name;
final prevLocation = prevHistoryEntry.location;
if (newLocation == prevLocation) {
//pop the entire history entry
return await _popHistory();
}
}
//create a new route with the remaining tree branch
final res = await _popHistory();
await _pushHistory(
GetNavConfig(
currentTreeBranch: remaining.toList(),
location: remaining.last.name,
state: null, //TOOD: persist state??
),
);
return res;
} else {
//remove entire entry
return await _popHistory();
}
}
bool _onPopVisualRoute(Route<dynamic> route, dynamic result) {
final didPop = route.didPop(result);
if (!didPop) {
return false;
}
final settings = route.settings;
if (settings is GetPage) {
final config = history.cast<GetNavConfig?>().firstWhere(
(element) => element?.currentPage == settings,
orElse: () => null,
);
if (config != null) {
_removeHistoryEntry(config);
}
if (_allCompleters.containsKey(settings)) {
_allCompleters[settings]?.complete(route.popped);
}
}
refresh();
return true;
}
Future<GetNavConfig?> _pop(PopMode mode) async {
switch (mode) {
case PopMode.History:
return await _popHistory();
case PopMode.Page:
return await _popPage();
default:
return null;
}
}
Future<GetNavConfig?> _popHistory() async {
if (!_canPopHistory()) return null;
return await _doPopHistory();
}
Future<GetNavConfig?> _popPage() async {
if (!_canPopPage()) return null;
return await _doPopPage();
}
Future<void> _pushHistory(GetNavConfig config) async {
if (config.currentPage!.preventDuplicates) {
final originalEntryIndex =
history.indexWhere((element) => element.location == config.location);
if (originalEntryIndex >= 0) {
switch (preventDuplicateHandlingMode) {
case PreventDuplicateHandlingMode.PopUntilOriginalRoute:
await backUntil(config.location!, popMode: PopMode.Page);
break;
case PreventDuplicateHandlingMode.ReorderRoutes:
await _unsafeHistoryRemoveAt(originalEntryIndex);
await _unsafeHistoryAdd(config);
break;
case PreventDuplicateHandlingMode.DoNothing:
default:
break;
}
return;
}
}
await _unsafeHistoryAdd(config);
}
Future<void> _removeHistoryEntry(GetNavConfig entry) async {
await _unsafeHistoryRemove(entry);
}
Future<void> _unsafeHistoryAdd(GetNavConfig config) async {
final res = await runMiddleware(config);
if (res == null) return;
history.add(res);
}
Future<void> _unsafeHistoryRemove(GetNavConfig config) async {
var index = history.indexOf(config);
if (index >= 0) await _unsafeHistoryRemoveAt(index);
}
Future<GetNavConfig?> _unsafeHistoryRemoveAt(int index) async {
if (index == history.length - 1 && history.length > 1) {
//removing WILL update the current route
final toCheck = history[history.length - 2];
final resMiddleware = await runMiddleware(toCheck);
if (resMiddleware == null) return null;
history[history.length - 2] = resMiddleware;
}
return history.removeAt(index);
}
}
class GetNavigator extends Navigator {
GetNavigator({
GlobalKey<NavigatorState>? key,
bool Function(Route<dynamic>, dynamic)? onPopPage,
required List<Page> pages,
List<NavigatorObserver>? observers,
bool reportsRouteUpdateToEngine = false,
TransitionDelegate? transitionDelegate,
}) : super(
//keys should be optional
key: key,
onPopPage: onPopPage ??
(route, result) {
final didPop = route.didPop(result);
if (!didPop) {
return false;
}
return true;
},
reportsRouteUpdateToEngine: reportsRouteUpdateToEngine,
pages: pages,
observers: [
// GetObserver(),
if (observers != null) ...observers,
],
transitionDelegate:
transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(),
);
}
/// Enables the user to customize the intended pop behavior
///
/// Goes to either the previous history entry or the previous page entry
///
/// e.g. if the user navigates to these pages
/// 1) /home
/// 2) /home/products/1234
///
/// when popping on [History] mode, it will emulate a browser back button.
///
/// so the new history stack will be:
/// 1) /home
///
/// when popping on [Page] mode, it will only remove the last part of the route
/// so the new history stack will be:
/// 1) /home
/// 2) /home/products
///
/// another pop will change the history stack to:
/// 1) /home
enum PopMode {
History,
Page,
}
/// Enables the user to customize the behavior when pushing multiple routes that
/// shouldn't be duplicates
enum PreventDuplicateHandlingMode {
/// Removes the history entries until it reaches the old route
PopUntilOriginalRoute,
/// Simply don't push the new route
DoNothing,
/// Recommended - Moves the old route entry to the front
///
/// With this mode, you guarantee there will be only one
/// route entry for each location
ReorderRoutes
}

View File

@@ -0,0 +1,168 @@
import 'package:flutter/material.dart';
import '../../../get.dart';
class RouterOutlet<TDelegate extends RouterDelegate<T>, T extends Object>
extends StatefulWidget {
final TDelegate routerDelegate;
final Widget Function(
BuildContext context,
TDelegate delegate,
T? currentRoute,
) builder;
//keys
RouterOutlet.builder({
Key? key,
TDelegate? delegate,
required this.builder,
}) : routerDelegate = delegate ?? Get.delegate<TDelegate, T>()!,
super(key: key);
RouterOutlet({
Key? key,
TDelegate? delegate,
required Iterable<GetPage> Function(T currentNavStack) pickPages,
required Widget Function(
BuildContext context,
TDelegate,
Iterable<GetPage>? page,
)
pageBuilder,
}) : this.builder(
builder: (context, rDelegate, currentConfig) {
var picked =
currentConfig == null ? null : pickPages(currentConfig);
if (picked?.isEmpty ?? false) {
picked = null;
}
return pageBuilder(context, rDelegate, picked);
},
delegate: delegate,
);
@override
RouterOutletState<TDelegate, T> createState() =>
RouterOutletState<TDelegate, T>();
}
class RouterOutletState<TDelegate extends RouterDelegate<T>, T extends Object>
extends State<RouterOutlet<TDelegate, T>> {
TDelegate get delegate => widget.routerDelegate;
@override
void initState() {
super.initState();
_getCurrentRoute();
delegate.addListener(onRouterDelegateChanged);
}
@override
void dispose() {
delegate.removeListener(onRouterDelegateChanged);
super.dispose();
}
T? currentRoute;
void _getCurrentRoute() {
currentRoute = delegate.currentConfiguration;
}
void onRouterDelegateChanged() {
setState(_getCurrentRoute);
}
@override
Widget build(BuildContext context) {
return widget.builder(context, delegate, currentRoute);
}
}
class GetRouterOutlet extends RouterOutlet<GetDelegate, GetNavConfig> {
GetRouterOutlet({
String? anchorRoute,
required String initialRoute,
Iterable<GetPage> Function(Iterable<GetPage> afterAnchor)? filterPages,
GlobalKey<NavigatorState>? key,
GetDelegate? delegate,
}) : this.pickPages(
pickPages: (config) {
Iterable<GetPage<dynamic>> ret;
if (anchorRoute == null) {
// jump the ancestor path
final length = Uri.parse(initialRoute).pathSegments.length;
return config.currentTreeBranch
.skip(length)
.take(length)
.toList();
}
ret = config.currentTreeBranch.pickAfterRoute(anchorRoute);
if (filterPages != null) {
ret = filterPages(ret);
}
return ret;
},
emptyPage: (delegate) =>
Get.routeTree.matchRoute(initialRoute).route ??
delegate.notFoundRoute,
key: key,
delegate: delegate,
);
GetRouterOutlet.pickPages({
Widget Function(GetDelegate delegate)? emptyWidget,
GetPage Function(GetDelegate delegate)? emptyPage,
required Iterable<GetPage> Function(GetNavConfig currentNavStack) pickPages,
bool Function(Route<dynamic>, dynamic)? onPopPage,
GlobalKey<NavigatorState>? key,
GetDelegate? delegate,
}) : super(
pageBuilder: (context, rDelegate, pages) {
final pageRes = <GetPage?>[
...?pages,
if (pages == null || pages.isEmpty) emptyPage?.call(rDelegate),
].whereType<GetPage>();
if (pageRes.isNotEmpty) {
return GetNavigator(
onPopPage: onPopPage ??
(route, result) {
final didPop = route.didPop(result);
if (!didPop) {
return false;
}
return true;
},
pages: pageRes.toList(),
key: key,
);
}
return (emptyWidget?.call(rDelegate) ?? const SizedBox.shrink());
},
pickPages: pickPages,
delegate: delegate ?? Get.rootDelegate,
);
GetRouterOutlet.builder({
required Widget Function(
BuildContext context,
GetDelegate delegate,
GetNavConfig? currentRoute,
)
builder,
GetDelegate? routerDelegate,
}) : super.builder(
builder: builder,
delegate: routerDelegate,
);
}
extension PagesListExt on List<GetPage> {
Iterable<GetPage> pickAtRoute(String route) {
return skipWhile((value) {
return value.name != route;
});
}
Iterable<GetPage> pickAfterRoute(String route) {
return pickAtRoute(route).skip(1);
}
}

View File

@@ -0,0 +1,321 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../../get_core/get_core.dart';
import '../../../get_instance/get_instance.dart';
import '../../../get_state_manager/get_state_manager.dart';
import '../../../get_utils/get_utils.dart';
import '../../get_navigation.dart';
class GetCupertinoApp extends StatelessWidget {
final GlobalKey<NavigatorState>? navigatorKey;
final Widget? home;
final Map<String, WidgetBuilder>? routes;
final String? initialRoute;
final RouteFactory? onGenerateRoute;
final InitialRouteListFactory? onGenerateInitialRoutes;
final RouteFactory? onUnknownRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionBuilder? builder;
final String title;
final GenerateAppTitle? onGenerateTitle;
final CustomTransition? customTransition;
final Color? color;
final Map<String, Map<String, String>>? translationsKeys;
final Translations? translations;
final TextDirection? textDirection;
final Locale? locale;
final Locale? fallbackLocale;
final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
final LocaleListResolutionCallback? localeListResolutionCallback;
final LocaleResolutionCallback? localeResolutionCallback;
final Iterable<Locale> supportedLocales;
final bool showPerformanceOverlay;
final bool checkerboardRasterCacheImages;
final bool checkerboardOffscreenLayers;
final bool showSemanticsDebugger;
final bool debugShowCheckedModeBanner;
final Map<LogicalKeySet, Intent>? shortcuts;
final ThemeData? highContrastTheme;
final ThemeData? highContrastDarkTheme;
final Map<Type, Action<Intent>>? actions;
final Function(Routing?)? routingCallback;
final Transition? defaultTransition;
final bool? opaqueRoute;
final VoidCallback? onInit;
final VoidCallback? onReady;
final VoidCallback? onDispose;
final bool? enableLog;
final LogWriterCallback? logWriterCallback;
final bool? popGesture;
final SmartManagement smartManagement;
final Bindings? initialBinding;
final Duration? transitionDuration;
final bool? defaultGlobalState;
final List<GetPage>? getPages;
final GetPage? unknownRoute;
final RouteInformationProvider? routeInformationProvider;
final RouteInformationParser<Object>? routeInformationParser;
final RouterDelegate<Object>? routerDelegate;
final BackButtonDispatcher? backButtonDispatcher;
final CupertinoThemeData? theme;
final bool useInheritedMediaQuery;
const GetCupertinoApp({
Key? key,
this.theme,
this.navigatorKey,
this.home,
Map<String, Widget Function(BuildContext)> this.routes =
const <String, WidgetBuilder>{},
this.initialRoute,
this.onGenerateRoute,
this.onGenerateInitialRoutes,
this.onUnknownRoute,
List<NavigatorObserver> this.navigatorObservers =
const <NavigatorObserver>[],
this.builder,
this.translationsKeys,
this.translations,
this.textDirection,
this.title = '',
this.onGenerateTitle,
this.color,
this.customTransition,
this.onInit,
this.onDispose,
this.locale,
this.fallbackLocale,
this.localizationsDelegates,
this.localeListResolutionCallback,
this.localeResolutionCallback,
this.supportedLocales = const <Locale>[Locale('en', 'US')],
this.showPerformanceOverlay = false,
this.checkerboardRasterCacheImages = false,
this.checkerboardOffscreenLayers = false,
this.showSemanticsDebugger = false,
this.debugShowCheckedModeBanner = true,
this.shortcuts,
this.smartManagement = SmartManagement.full,
this.initialBinding,
this.useInheritedMediaQuery = false,
this.unknownRoute,
this.routingCallback,
this.defaultTransition,
this.onReady,
this.getPages,
this.opaqueRoute,
this.enableLog = kDebugMode,
this.logWriterCallback,
this.popGesture,
this.transitionDuration,
this.defaultGlobalState,
this.highContrastTheme,
this.highContrastDarkTheme,
this.actions,
}) : routeInformationProvider = null,
routeInformationParser = null,
routerDelegate = null,
backButtonDispatcher = null,
super(key: key);
GetCupertinoApp.router({
Key? key,
this.theme,
this.routeInformationProvider,
RouteInformationParser<Object>? routeInformationParser,
RouterDelegate<Object>? routerDelegate,
this.backButtonDispatcher,
this.builder,
this.title = '',
this.onGenerateTitle,
this.useInheritedMediaQuery = false,
this.color,
this.highContrastTheme,
this.highContrastDarkTheme,
this.locale,
this.localizationsDelegates,
this.localeListResolutionCallback,
this.localeResolutionCallback,
this.supportedLocales = const <Locale>[Locale('en', 'US')],
this.showPerformanceOverlay = false,
this.checkerboardRasterCacheImages = false,
this.checkerboardOffscreenLayers = false,
this.showSemanticsDebugger = false,
this.debugShowCheckedModeBanner = true,
this.shortcuts,
this.actions,
this.customTransition,
this.translationsKeys,
this.translations,
this.textDirection,
this.fallbackLocale,
this.routingCallback,
this.defaultTransition,
this.opaqueRoute,
this.onInit,
this.onReady,
this.onDispose,
this.enableLog = kDebugMode,
this.logWriterCallback,
this.popGesture,
this.smartManagement = SmartManagement.full,
this.initialBinding,
this.transitionDuration,
this.defaultGlobalState,
this.getPages,
this.unknownRoute,
}) : routerDelegate = routerDelegate ??= Get.createDelegate(
notFoundRoute: unknownRoute,
),
routeInformationParser =
routeInformationParser ??= Get.createInformationParser(
initialRoute: getPages?.first.name ?? '/',
),
navigatorObservers = null,
navigatorKey = null,
onGenerateRoute = null,
home = null,
onGenerateInitialRoutes = null,
onUnknownRoute = null,
routes = null,
initialRoute = null,
super(key: key) {
Get.routerDelegate = routerDelegate;
Get.routeInformationParser = routeInformationParser;
}
@override
Widget build(BuildContext context) => GetBuilder<GetMaterialController>(
init: Get.rootController,
dispose: (d) {
onDispose?.call();
},
initState: (i) {
Get.engine.addPostFrameCallback((timeStamp) {
onReady?.call();
});
if (locale != null) Get.locale = locale;
if (fallbackLocale != null) Get.fallbackLocale = fallbackLocale;
if (translations != null) {
Get.addTranslations(translations!.keys);
} else if (translationsKeys != null) {
Get.addTranslations(translationsKeys!);
}
Get.customTransition = customTransition;
initialBinding?.dependencies();
if (getPages != null) {
Get.addPages(getPages!);
}
Get.smartManagement = smartManagement;
onInit?.call();
Get.config(
enableLog: enableLog ?? Get.isLogEnable,
logWriterCallback: logWriterCallback,
defaultTransition: defaultTransition ?? Get.defaultTransition,
defaultOpaqueRoute: opaqueRoute ?? Get.isOpaqueRouteDefault,
defaultPopGesture: popGesture ?? Get.isPopGestureEnable,
defaultDurationTransition:
transitionDuration ?? Get.defaultTransitionDuration,
);
},
builder: (_) => routerDelegate != null
? CupertinoApp.router(
routerDelegate: routerDelegate!,
routeInformationParser: routeInformationParser!,
backButtonDispatcher: backButtonDispatcher,
routeInformationProvider: routeInformationProvider,
key: _.unikey,
theme: theme,
builder: defaultBuilder,
title: title,
onGenerateTitle: onGenerateTitle,
color: color,
locale: Get.locale ?? locale,
localizationsDelegates: localizationsDelegates,
localeListResolutionCallback: localeListResolutionCallback,
localeResolutionCallback: localeResolutionCallback,
supportedLocales: supportedLocales,
showPerformanceOverlay: showPerformanceOverlay,
checkerboardRasterCacheImages: checkerboardRasterCacheImages,
checkerboardOffscreenLayers: checkerboardOffscreenLayers,
showSemanticsDebugger: showSemanticsDebugger,
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
useInheritedMediaQuery: useInheritedMediaQuery,
)
: CupertinoApp(
key: _.unikey,
theme: theme,
navigatorKey: (navigatorKey == null
? Get.key
: Get.addKey(navigatorKey!)),
home: home,
routes: routes ?? const <String, WidgetBuilder>{},
initialRoute: initialRoute,
onGenerateRoute:
(getPages != null ? generator : onGenerateRoute),
onGenerateInitialRoutes: (getPages == null || home != null)
? onGenerateInitialRoutes
: initialRoutesGenerate,
onUnknownRoute: onUnknownRoute,
navigatorObservers: (navigatorObservers == null
? <NavigatorObserver>[
GetObserver(routingCallback, Get.routing)
]
: <NavigatorObserver>[
GetObserver(routingCallback, Get.routing)
]
..addAll(navigatorObservers!)),
builder: defaultBuilder,
title: title,
onGenerateTitle: onGenerateTitle,
color: color,
locale: Get.locale ?? locale,
localizationsDelegates: localizationsDelegates,
localeListResolutionCallback: localeListResolutionCallback,
localeResolutionCallback: localeResolutionCallback,
supportedLocales: supportedLocales,
showPerformanceOverlay: showPerformanceOverlay,
checkerboardRasterCacheImages: checkerboardRasterCacheImages,
checkerboardOffscreenLayers: checkerboardOffscreenLayers,
showSemanticsDebugger: showSemanticsDebugger,
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
useInheritedMediaQuery: useInheritedMediaQuery,
// actions: actions,
),
);
Widget defaultBuilder(BuildContext context, Widget? child) {
return Directionality(
textDirection: textDirection ??
(rtlLanguages.contains(Get.locale?.languageCode)
? TextDirection.rtl
: TextDirection.ltr),
child: builder == null
? (child ?? Material())
: builder!(context, child ?? Material()),
);
}
Route<dynamic> generator(RouteSettings settings) {
return PageRedirect(settings: settings, unknownRoute: unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) {
return [
PageRedirect(
settings: RouteSettings(name: name),
unknownRoute: unknownRoute,
).page()
];
}
}

View File

@@ -0,0 +1,351 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../../get_core/get_core.dart';
import '../../../get_instance/get_instance.dart';
import '../../../get_state_manager/get_state_manager.dart';
import '../../../get_utils/get_utils.dart';
import '../../get_navigation.dart';
class GetMaterialApp extends StatelessWidget {
final GlobalKey<NavigatorState>? navigatorKey;
final GlobalKey<ScaffoldMessengerState>? scaffoldMessengerKey;
final Widget? home;
final Map<String, WidgetBuilder>? routes;
final String? initialRoute;
final RouteFactory? onGenerateRoute;
final InitialRouteListFactory? onGenerateInitialRoutes;
final RouteFactory? onUnknownRoute;
final List<NavigatorObserver>? navigatorObservers;
final TransitionBuilder? builder;
final String title;
final GenerateAppTitle? onGenerateTitle;
final ThemeData? theme;
final ThemeData? darkTheme;
final ThemeMode themeMode;
final CustomTransition? customTransition;
final Color? color;
final Map<String, Map<String, String>>? translationsKeys;
final Translations? translations;
final TextDirection? textDirection;
final Locale? locale;
final Locale? fallbackLocale;
final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
final LocaleListResolutionCallback? localeListResolutionCallback;
final LocaleResolutionCallback? localeResolutionCallback;
final Iterable<Locale> supportedLocales;
final bool showPerformanceOverlay;
final bool checkerboardRasterCacheImages;
final bool checkerboardOffscreenLayers;
final bool showSemanticsDebugger;
final bool debugShowCheckedModeBanner;
final Map<LogicalKeySet, Intent>? shortcuts;
final ScrollBehavior? scrollBehavior;
final ThemeData? highContrastTheme;
final ThemeData? highContrastDarkTheme;
final Map<Type, Action<Intent>>? actions;
final bool debugShowMaterialGrid;
final ValueChanged<Routing?>? routingCallback;
final Transition? defaultTransition;
final bool? opaqueRoute;
final VoidCallback? onInit;
final VoidCallback? onReady;
final VoidCallback? onDispose;
final bool? enableLog;
final LogWriterCallback? logWriterCallback;
final bool? popGesture;
final SmartManagement smartManagement;
final Bindings? initialBinding;
final Duration? transitionDuration;
final bool? defaultGlobalState;
final List<GetPage>? getPages;
final GetPage? unknownRoute;
final RouteInformationProvider? routeInformationProvider;
final RouteInformationParser<Object>? routeInformationParser;
final RouterDelegate<Object>? routerDelegate;
final BackButtonDispatcher? backButtonDispatcher;
final bool useInheritedMediaQuery;
const GetMaterialApp({
Key? key,
this.navigatorKey,
this.scaffoldMessengerKey,
this.home,
Map<String, Widget Function(BuildContext)> this.routes =
const <String, WidgetBuilder>{},
this.initialRoute,
this.onGenerateRoute,
this.onGenerateInitialRoutes,
this.onUnknownRoute,
this.useInheritedMediaQuery = false,
List<NavigatorObserver> this.navigatorObservers =
const <NavigatorObserver>[],
this.builder,
this.textDirection,
this.title = '',
this.onGenerateTitle,
this.color,
this.theme,
this.darkTheme,
this.themeMode = ThemeMode.system,
this.locale,
this.fallbackLocale,
this.localizationsDelegates,
this.localeListResolutionCallback,
this.localeResolutionCallback,
this.supportedLocales = const <Locale>[Locale('en', 'US')],
this.debugShowMaterialGrid = false,
this.showPerformanceOverlay = false,
this.checkerboardRasterCacheImages = false,
this.checkerboardOffscreenLayers = false,
this.showSemanticsDebugger = false,
this.debugShowCheckedModeBanner = true,
this.shortcuts,
this.scrollBehavior,
this.customTransition,
this.translationsKeys,
this.translations,
this.onInit,
this.onReady,
this.onDispose,
this.routingCallback,
this.defaultTransition,
this.getPages,
this.opaqueRoute,
this.enableLog = kDebugMode,
this.logWriterCallback,
this.popGesture,
this.transitionDuration,
this.defaultGlobalState,
this.smartManagement = SmartManagement.full,
this.initialBinding,
this.unknownRoute,
this.highContrastTheme,
this.highContrastDarkTheme,
this.actions,
}) : routeInformationProvider = null,
routeInformationParser = null,
routerDelegate = null,
backButtonDispatcher = null,
super(key: key);
GetMaterialApp.router({
Key? key,
this.routeInformationProvider,
this.scaffoldMessengerKey,
RouteInformationParser<Object>? routeInformationParser,
RouterDelegate<Object>? routerDelegate,
this.backButtonDispatcher,
this.builder,
this.title = '',
this.onGenerateTitle,
this.color,
this.theme,
this.darkTheme,
this.useInheritedMediaQuery = false,
this.highContrastTheme,
this.highContrastDarkTheme,
this.themeMode = ThemeMode.system,
this.locale,
this.localizationsDelegates,
this.localeListResolutionCallback,
this.localeResolutionCallback,
this.supportedLocales = const <Locale>[Locale('en', 'US')],
this.debugShowMaterialGrid = false,
this.showPerformanceOverlay = false,
this.checkerboardRasterCacheImages = false,
this.checkerboardOffscreenLayers = false,
this.showSemanticsDebugger = false,
this.debugShowCheckedModeBanner = true,
this.shortcuts,
this.scrollBehavior,
this.actions,
this.customTransition,
this.translationsKeys,
this.translations,
this.textDirection,
this.fallbackLocale,
this.routingCallback,
this.defaultTransition,
this.opaqueRoute,
this.onInit,
this.onReady,
this.onDispose,
this.enableLog = kDebugMode,
this.logWriterCallback,
this.popGesture,
this.smartManagement = SmartManagement.full,
this.initialBinding,
this.transitionDuration,
this.defaultGlobalState,
this.getPages,
this.navigatorObservers,
this.unknownRoute,
}) : routerDelegate = routerDelegate ??= Get.createDelegate(
notFoundRoute: unknownRoute,
),
routeInformationParser =
routeInformationParser ??= Get.createInformationParser(
initialRoute: getPages?.first.name ?? '/',
),
//navigatorObservers = null,
navigatorKey = null,
onGenerateRoute = null,
home = null,
onGenerateInitialRoutes = null,
onUnknownRoute = null,
routes = null,
initialRoute = null,
super(key: key) {
Get.routerDelegate = routerDelegate;
Get.routeInformationParser = routeInformationParser;
}
@override
Widget build(BuildContext context) => GetBuilder<GetMaterialController>(
init: Get.rootController,
dispose: (d) {
onDispose?.call();
},
initState: (i) {
Get.engine.addPostFrameCallback((timeStamp) {
onReady?.call();
});
if (locale != null) Get.locale = locale;
if (fallbackLocale != null) Get.fallbackLocale = fallbackLocale;
if (translations != null) {
Get.addTranslations(translations!.keys);
} else if (translationsKeys != null) {
Get.addTranslations(translationsKeys!);
}
Get.customTransition = customTransition;
initialBinding?.dependencies();
if (getPages != null) {
Get.addPages(getPages!);
}
//Get.setDefaultDelegate(routerDelegate);
Get.smartManagement = smartManagement;
onInit?.call();
Get.config(
enableLog: enableLog ?? Get.isLogEnable,
logWriterCallback: logWriterCallback,
defaultTransition: defaultTransition ?? Get.defaultTransition,
defaultOpaqueRoute: opaqueRoute ?? Get.isOpaqueRouteDefault,
defaultPopGesture: popGesture ?? Get.isPopGestureEnable,
defaultDurationTransition:
transitionDuration ?? Get.defaultTransitionDuration,
);
},
builder: (_) => routerDelegate != null
? MaterialApp.router(
routerDelegate: routerDelegate!,
routeInformationParser: routeInformationParser!,
backButtonDispatcher: backButtonDispatcher,
routeInformationProvider: routeInformationProvider,
key: _.unikey,
builder: defaultBuilder,
title: title,
onGenerateTitle: onGenerateTitle,
color: color,
theme: _.theme ?? theme ?? ThemeData.fallback(),
darkTheme:
_.darkTheme ?? darkTheme ?? theme ?? ThemeData.fallback(),
themeMode: _.themeMode ?? themeMode,
locale: Get.locale ?? locale,
scaffoldMessengerKey:
scaffoldMessengerKey ?? _.scaffoldMessengerKey,
localizationsDelegates: localizationsDelegates,
localeListResolutionCallback: localeListResolutionCallback,
localeResolutionCallback: localeResolutionCallback,
supportedLocales: supportedLocales,
debugShowMaterialGrid: debugShowMaterialGrid,
showPerformanceOverlay: showPerformanceOverlay,
checkerboardRasterCacheImages: checkerboardRasterCacheImages,
checkerboardOffscreenLayers: checkerboardOffscreenLayers,
showSemanticsDebugger: showSemanticsDebugger,
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
scrollBehavior: scrollBehavior,
useInheritedMediaQuery: useInheritedMediaQuery,
)
: MaterialApp(
key: _.unikey,
navigatorKey: (navigatorKey == null
? Get.key
: Get.addKey(navigatorKey!)),
scaffoldMessengerKey:
scaffoldMessengerKey ?? _.scaffoldMessengerKey,
home: home,
routes: routes ?? const <String, WidgetBuilder>{},
initialRoute: initialRoute,
onGenerateRoute:
(getPages != null ? generator : onGenerateRoute),
onGenerateInitialRoutes: (getPages == null || home != null)
? onGenerateInitialRoutes
: initialRoutesGenerate,
onUnknownRoute: onUnknownRoute,
navigatorObservers: (navigatorObservers == null
? <NavigatorObserver>[
GetObserver(routingCallback, Get.routing)
]
: <NavigatorObserver>[
GetObserver(routingCallback, Get.routing)
]
..addAll(navigatorObservers!)),
builder: defaultBuilder,
title: title,
onGenerateTitle: onGenerateTitle,
color: color,
theme: _.theme ?? theme ?? ThemeData.fallback(),
darkTheme:
_.darkTheme ?? darkTheme ?? theme ?? ThemeData.fallback(),
themeMode: _.themeMode ?? themeMode,
locale: Get.locale ?? locale,
localizationsDelegates: localizationsDelegates,
localeListResolutionCallback: localeListResolutionCallback,
localeResolutionCallback: localeResolutionCallback,
supportedLocales: supportedLocales,
debugShowMaterialGrid: debugShowMaterialGrid,
showPerformanceOverlay: showPerformanceOverlay,
checkerboardRasterCacheImages: checkerboardRasterCacheImages,
checkerboardOffscreenLayers: checkerboardOffscreenLayers,
showSemanticsDebugger: showSemanticsDebugger,
debugShowCheckedModeBanner: debugShowCheckedModeBanner,
shortcuts: shortcuts,
scrollBehavior: scrollBehavior,
useInheritedMediaQuery: useInheritedMediaQuery,
// actions: actions,
),
);
Widget defaultBuilder(BuildContext context, Widget? child) {
return Directionality(
textDirection: textDirection ??
(rtlLanguages.contains(Get.locale?.languageCode)
? TextDirection.rtl
: TextDirection.ltr),
child: builder == null
? (child ?? Material())
: builder!(context, child ?? Material()),
);
}
Route<dynamic> generator(RouteSettings settings) {
return PageRedirect(settings: settings, unknownRoute: unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) {
return [
PageRedirect(
settings: RouteSettings(name: name),
unknownRoute: unknownRoute,
).page()
];
}
}

View File

@@ -0,0 +1,11 @@
const List<String> rtlLanguages = <String>[
'ar', // Arabic
'fa', // Farsi
'he', // Hebrew
'ps', // Pashto
'ur',
];
abstract class Translations {
Map<String, Map<String, String>> get keys;
}

View File

@@ -0,0 +1,188 @@
import '../../get_navigation.dart';
class RouteDecoder {
final List<GetPage> treeBranch;
GetPage? get route => treeBranch.isEmpty ? null : treeBranch.last;
final Map<String, String> parameters;
final Object? arguments;
const RouteDecoder(
this.treeBranch,
this.parameters,
this.arguments,
);
void replaceArguments(Object? arguments) {
final _route = route;
if (_route != null) {
final index = treeBranch.indexOf(_route);
treeBranch[index] = _route.copy(arguments: arguments);
}
}
void replaceParameters(Object? arguments) {
final _route = route;
if (_route != null) {
final index = treeBranch.indexOf(_route);
treeBranch[index] = _route.copy(parameters: parameters);
}
}
}
class ParseRouteTree {
ParseRouteTree({
required this.routes,
});
final List<GetPage> routes;
RouteDecoder matchRoute(String name, {Object? arguments}) {
final uri = Uri.parse(name);
// /home/profile/123 => home,profile,123 => /,/home,/home/profile,/home/profile/123
final split = uri.path.split('/').where((element) => element.isNotEmpty);
var curPath = '/';
final cumulativePaths = <String>[
'/',
];
for (var item in split) {
if (curPath.endsWith('/')) {
curPath += '$item';
} else {
curPath += '/$item';
}
cumulativePaths.add(curPath);
}
final treeBranch = cumulativePaths
.map((e) => MapEntry(e, _findRoute(e)))
.where((element) => element.value != null)
.map((e) => MapEntry(e.key, e.value!))
.toList();
final params = Map<String, String>.from(uri.queryParameters);
if (treeBranch.isNotEmpty) {
//route is found, do further parsing to get nested query params
final lastRoute = treeBranch.last;
final parsedParams = _parseParams(name, lastRoute.value.path);
if (parsedParams.isNotEmpty) {
params.addAll(parsedParams);
}
//copy parameters to all pages.
final mappedTreeBranch = treeBranch
.map(
(e) => e.value.copy(
parameters: {
if (e.value.parameters != null) ...e.value.parameters!,
...params,
},
name: e.key,
),
)
.toList();
return RouteDecoder(
mappedTreeBranch,
params,
arguments,
);
}
//route not found
return RouteDecoder(
treeBranch.map((e) => e.value).toList(),
params,
arguments,
);
}
void addRoutes(List<GetPage> getPages) {
for (final route in getPages) {
addRoute(route);
}
}
void addRoute(GetPage route) {
routes.add(route);
// Add Page children.
for (var page in _flattenPage(route)) {
addRoute(page);
}
}
List<GetPage> _flattenPage(GetPage route) {
final result = <GetPage>[];
if (route.children.isEmpty) {
return result;
}
final parentPath = route.name;
for (var page in route.children) {
// Add Parent middlewares to children
final parentMiddlewares = [
if (page.middlewares != null) ...page.middlewares!,
if (route.middlewares != null) ...route.middlewares!
];
result.add(
_addChild(
page,
parentPath,
parentMiddlewares,
),
);
final children = _flattenPage(page);
for (var child in children) {
result.add(_addChild(
child,
parentPath,
[
...parentMiddlewares,
if (child.middlewares != null) ...child.middlewares!,
],
));
}
}
return result;
}
/// Change the Path for a [GetPage]
GetPage _addChild(
GetPage origin, String parentPath, List<GetMiddleware> middlewares) =>
origin.copy(
middlewares: middlewares,
name: (parentPath + origin.name).replaceAll(r'//', '/'),
);
GetPage? _findRoute(String name) {
return routes.firstWhereOrNull(
(route) => route.path.regex.hasMatch(name),
);
}
Map<String, String> _parseParams(String path, PathDecoded routePath) {
final params = <String, String>{};
var idx = path.indexOf('?');
if (idx > -1) {
path = path.substring(0, idx);
final uri = Uri.tryParse(path);
if (uri != null) {
params.addAll(uri.queryParameters);
}
}
var paramsMatch = routePath.regex.firstMatch(path);
for (var i = 0; i < routePath.keys.length; i++) {
var param = Uri.decodeQueryComponent(paramsMatch![i + 1]!);
params[routePath.keys[i]!] = param;
}
return params;
}
}
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;
}
}

View File

@@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import '../../../get.dart';
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;
Transition? defaultTransition;
Duration defaultTransitionDuration = Duration(milliseconds: 300);
Curve defaultTransitionCurve = Curves.easeOutQuad;
Curve defaultDialogTransitionCurve = Curves.easeOutQuad;
Duration defaultDialogTransitionDuration = Duration(milliseconds: 300);
final routing = Routing();
Map<String, String?> parameters = {};
CustomTransition? customTransition;
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) {
_key = newKey;
return key;
}
@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) {
theme = value;
} else {
if (value.brightness == Brightness.light) {
theme = value;
} else {
darkTheme = value;
}
}
update();
}
void setThemeMode(ThemeMode value) {
themeMode = value;
update();
}
}

View File

@@ -0,0 +1,113 @@
import 'dart:collection';
import 'package:flutter/widgets.dart';
import '../../get.dart';
class RouterReportManager<T> {
/// Holds a reference to `Get.reference` when the Instance was
/// created to manage the memory.
static final Map<Route?, List<String>> _routesKey = {};
/// Stores the onClose() references of instances created with `Get.create()`
/// using the `Get.reference`.
/// Experimental feature to keep the lifecycle and memory management with
/// non-singleton instances.
static final Map<Route?, HashSet<Function>> _routesByCreate = {};
void printInstanceStack() {
Get.log(_routesKey.toString());
}
static Route? _current;
// ignore: use_setters_to_change_properties
static void reportCurrentRoute(Route newRoute) {
_current = newRoute;
}
/// Links a Class instance [S] (or [tag]) to the current route.
/// Requires usage of `GetMaterialApp`.
static void reportDependencyLinkedToRoute(String depedencyKey) {
if (_current == null) return;
if (_routesKey.containsKey(_current)) {
_routesKey[_current!]!.add(depedencyKey);
} else {
_routesKey[_current] = <String>[depedencyKey];
}
}
static void clearRouteKeys() {
_routesKey.clear();
_routesByCreate.clear();
}
static void appendRouteByCreate(GetLifeCycleBase i) {
_routesByCreate[_current] ??= HashSet<Function>();
// _routesByCreate[Get.reference]!.add(i.onDelete as Function);
_routesByCreate[_current]!.add(i.onDelete);
}
static void reportRouteDispose(Route disposed) {
if (Get.smartManagement != SmartManagement.onlyBuilder) {
ambiguate(WidgetsBinding.instance)?.addPostFrameCallback((_) {
_removeDependencyByRoute(disposed);
});
}
}
static void reportRouteWillDispose(Route disposed) {
final keysToRemove = <String>[];
_routesKey[disposed]?.forEach(keysToRemove.add);
/// Removes `Get.create()` instances registered in `routeName`.
if (_routesByCreate.containsKey(disposed)) {
for (final onClose in _routesByCreate[disposed]!) {
// assure the [DisposableInterface] instance holding a reference
// to onClose() wasn't disposed.
onClose();
}
_routesByCreate[disposed]!.clear();
_routesByCreate.remove(disposed);
}
for (final element in keysToRemove) {
GetInstance().markAsDirty(key: element);
//_routesKey.remove(element);
}
keysToRemove.clear();
}
/// Clears from memory registered Instances associated with [routeName] when
/// using `Get.smartManagement` as [SmartManagement.full] or
/// [SmartManagement.keepFactory]
/// Meant for internal usage of `GetPageRoute` and `GetDialogRoute`
static void _removeDependencyByRoute(Route routeName) {
final keysToRemove = <String>[];
_routesKey[routeName]?.forEach(keysToRemove.add);
/// Removes `Get.create()` instances registered in `routeName`.
if (_routesByCreate.containsKey(routeName)) {
for (final onClose in _routesByCreate[routeName]!) {
// assure the [DisposableInterface] instance holding a reference
// to onClose() wasn't disposed.
onClose();
}
_routesByCreate[routeName]!.clear();
_routesByCreate.remove(routeName);
}
for (final element in keysToRemove) {
final value = GetInstance().delete(key: element);
if (value) {
_routesKey[routeName]?.remove(element);
}
}
keysToRemove.clear();
}
}

View File

@@ -0,0 +1,46 @@
import 'dart:math' show sqrt, max;
import 'dart:ui' show lerpDouble;
import 'package:flutter/material.dart';
class CircularRevealClipper extends CustomClipper<Path> {
final double fraction;
final Alignment? centerAlignment;
final Offset? centerOffset;
final double? minRadius;
final double? maxRadius;
CircularRevealClipper({
required this.fraction,
this.centerAlignment,
this.centerOffset,
this.minRadius,
this.maxRadius,
});
@override
Path getClip(Size size) {
final center = centerAlignment?.alongSize(size) ??
centerOffset ??
Offset(size.width / 2, size.height / 2);
final minRadius = this.minRadius ?? 0;
final maxRadius = this.maxRadius ?? calcMaxRadius(size, center);
return Path()
..addOval(
Rect.fromCircle(
center: center,
radius: lerpDouble(minRadius, maxRadius, fraction)!,
),
);
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => true;
static double calcMaxRadius(Size size, Offset center) {
final w = max(center.dx, size.width - center.dx);
final h = max(center.dy, size.height - center.dy);
return sqrt(w * w + h * h);
}
}

View File

@@ -0,0 +1,13 @@
import 'package:flutter/widgets.dart';
// ignore: one_member_abstracts
abstract class CustomTransition {
Widget buildTransition(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
);
}

View File

@@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import '../../../get.dart';
import '../router_report.dart';
import 'get_transition_mixin.dart';
mixin PageRouteReportMixin<T> on Route<T> {
@override
void install() {
super.install();
RouterReportManager.reportCurrentRoute(this);
}
@override
void dispose() {
super.dispose();
RouterReportManager.reportRouteDispose(this);
}
}
class GetPageRoute<T> extends PageRoute<T>
with GetPageRouteTransitionMixin<T>, PageRouteReportMixin {
/// Creates a page route for use in an iOS designed app.
///
/// The [builder], [maintainState], and [fullscreenDialog] arguments must not
/// be null.
GetPageRoute({
RouteSettings? settings,
this.transitionDuration = const Duration(milliseconds: 300),
this.opaque = true,
this.parameter,
this.gestureWidth,
this.curve,
this.alignment,
this.transition,
this.popGesture,
this.customTransition,
this.barrierDismissible = false,
this.barrierColor,
this.binding,
this.bindings,
this.routeName,
this.page,
this.title,
this.showCupertinoParallax = true,
this.barrierLabel,
this.maintainState = true,
bool fullscreenDialog = false,
this.middlewares,
}) : super(settings: settings, fullscreenDialog: fullscreenDialog);
@override
final Duration transitionDuration;
final GetPageBuilder? page;
final String? routeName;
//final String reference;
final CustomTransition? customTransition;
final Bindings? binding;
final Map<String, String>? parameter;
final List<Bindings>? bindings;
@override
final bool showCupertinoParallax;
@override
final bool opaque;
final bool? popGesture;
@override
final bool barrierDismissible;
final Transition? transition;
final Curve? curve;
final Alignment? alignment;
final List<GetMiddleware>? middlewares;
@override
final Color? barrierColor;
@override
final String? barrierLabel;
@override
final bool maintainState;
@override
void dispose() {
super.dispose();
final middlewareRunner = MiddlewareRunner(middlewares);
middlewareRunner.runOnPageDispose();
}
Widget? _child;
Widget _getChild() {
if (_child != null) return _child!;
final middlewareRunner = MiddlewareRunner(middlewares);
final localbindings = [
if (bindings != null) ...bindings!,
if (binding != null) ...[binding!]
];
final bindingsToBind = middlewareRunner.runOnBindingsStart(localbindings);
if (bindingsToBind != null) {
for (final binding in bindingsToBind) {
binding.dependencies();
}
}
final pageToBuild = middlewareRunner.runOnPageBuildStart(page)!;
_child = middlewareRunner.runOnPageBuilt(pageToBuild());
return _child!;
}
@override
Widget buildContent(BuildContext context) {
return _getChild();
}
@override
final String? title;
@override
String get debugLabel => '${super.debugLabel}(${settings.name})';
@override
final double Function(BuildContext context)? gestureWidth;
}

View File

@@ -0,0 +1,209 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'circular_reveal_clipper.dart';
class LeftToRightFadeTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: FadeTransition(
opacity: animation,
child: SlideTransition(
position: Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.0, 0.0),
).animate(secondaryAnimation),
child: child),
),
);
}
}
class RightToLeftFadeTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: FadeTransition(
opacity: animation,
child: SlideTransition(
position: Tween<Offset>(
begin: Offset.zero,
end: const Offset(-1.0, 0.0),
).animate(secondaryAnimation),
child: child),
),
);
}
}
class NoTransition {
Widget buildTransitions(
BuildContext context,
Curve curve,
Alignment alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return child;
}
}
class FadeInTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return FadeTransition(opacity: animation, child: child);
}
}
class SlideDownTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: Offset(0.0, 1.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
}
class SlideLeftTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: Offset(-1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
}
class SlideRightTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
}
class SlideTopTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: Offset(0.0, -1.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
}
class ZoomInTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return ScaleTransition(
scale: animation,
child: child,
);
}
}
class SizeTransitions {
Widget buildTransitions(
BuildContext context,
Curve curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return Align(
alignment: Alignment.center,
child: SizeTransition(
sizeFactor: CurvedAnimation(
parent: animation,
curve: curve,
),
child: child,
),
);
}
}
class CircularRevealTransition {
Widget buildTransitions(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return ClipPath(
clipper: CircularRevealClipper(
fraction: animation.value,
centerAlignment: Alignment.center,
centerOffset: Offset.zero,
minRadius: 0,
maxRadius: 800,
),
child: child,
);
}
}

View File

@@ -0,0 +1,184 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../../get_core/src/get_main.dart';
import '../../../get_instance/get_instance.dart';
import '../../get_navigation.dart';
class GetPage<T> extends Page<T> {
final GetPageBuilder page;
final bool? popGesture;
final Map<String, String>? parameters;
final String? title;
final Transition? transition;
final Curve curve;
final bool? participatesInRootNavigator;
final Alignment? alignment;
final bool maintainState;
final bool opaque;
final double Function(BuildContext context)? gestureWidth;
final Bindings? binding;
final List<Bindings> bindings;
final CustomTransition? customTransition;
final Duration? transitionDuration;
final bool fullscreenDialog;
final bool preventDuplicates;
// @override
// final LocalKey? key;
// @override
// RouteSettings get settings => this;
@override
final Object? arguments;
@override
final String name;
final List<GetPage> children;
final List<GetMiddleware>? middlewares;
final PathDecoded path;
final GetPage? unknownRoute;
final bool showCupertinoParallax;
GetPage({
required this.name,
required this.page,
this.title,
this.participatesInRootNavigator,
this.gestureWidth,
// RouteSettings settings,
this.maintainState = true,
this.curve = Curves.linear,
this.alignment,
this.parameters,
this.opaque = true,
this.transitionDuration,
this.popGesture,
this.binding,
this.bindings = const [],
this.transition,
this.customTransition,
this.fullscreenDialog = false,
this.children = const <GetPage>[],
this.middlewares,
this.unknownRoute,
this.arguments,
this.showCupertinoParallax = true,
this.preventDuplicates = true,
}) : path = _nameToRegex(name),
assert(name.startsWith('/'),
'It is necessary to start route name [$name] with a slash: /$name'),
super(
key: ValueKey(name),
name: name,
arguments: Get.arguments,
);
// settings = RouteSettings(name: name, arguments: Get.arguments);
GetPage<T> copy({
String? name,
GetPageBuilder? page,
bool? popGesture,
Map<String, String>? parameters,
String? title,
Transition? transition,
Curve? curve,
Alignment? alignment,
bool? maintainState,
bool? opaque,
Bindings? binding,
List<Bindings>? bindings,
CustomTransition? customTransition,
Duration? transitionDuration,
bool? fullscreenDialog,
RouteSettings? settings,
List<GetPage>? children,
GetPage? unknownRoute,
List<GetMiddleware>? middlewares,
bool? preventDuplicates,
final double Function(BuildContext context)? gestureWidth,
bool? participatesInRootNavigator,
Object? arguments,
bool? showCupertinoParallax,
}) {
return GetPage(
participatesInRootNavigator:
participatesInRootNavigator ?? this.participatesInRootNavigator,
preventDuplicates: preventDuplicates ?? this.preventDuplicates,
name: name ?? this.name,
page: page ?? this.page,
popGesture: popGesture ?? this.popGesture,
parameters: parameters ?? this.parameters,
title: title ?? this.title,
transition: transition ?? this.transition,
curve: curve ?? this.curve,
alignment: alignment ?? this.alignment,
maintainState: maintainState ?? this.maintainState,
opaque: opaque ?? this.opaque,
binding: binding ?? this.binding,
bindings: bindings ?? this.bindings,
customTransition: customTransition ?? this.customTransition,
transitionDuration: transitionDuration ?? this.transitionDuration,
fullscreenDialog: fullscreenDialog ?? this.fullscreenDialog,
children: children ?? this.children,
unknownRoute: unknownRoute ?? this.unknownRoute,
middlewares: middlewares ?? this.middlewares,
gestureWidth: gestureWidth ?? this.gestureWidth,
arguments: arguments ?? this.arguments,
showCupertinoParallax:
showCupertinoParallax ?? this.showCupertinoParallax,
);
}
@override
Route<T> createRoute(BuildContext context) {
// return GetPageRoute<T>(settings: this, page: page);
final _page = PageRedirect(
route: this,
settings: this,
unknownRoute: unknownRoute,
).getPageToRoute<T>(this, unknownRoute);
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);
}
}

View File

@@ -0,0 +1,714 @@
import 'dart:math';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import '../../../get.dart';
import 'default_transitions.dart';
const double _kBackGestureWidth = 20.0;
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 _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 double _kMinFlingVelocity = 1.0; // Milliseconds.
class CupertinoBackGestureController<T> {
final AnimationController controller;
final NavigatorState navigator;
/// Creates a controller for an iOS-style back gesture.
///
/// The [navigator] and [controller] arguments must not be null.
CupertinoBackGestureController({
required this.navigator,
required this.controller,
}) {
navigator.didStartUserGesture();
}
/// The drag gesture has ended with a horizontal motion of
/// [fractionalVelocity] as a fraction of screen width per second.
void dragEnd(double velocity) {
// Fling in the appropriate direction.
// AnimationController.fling is guaranteed to
// take at least one frame.
//
// This curve has been determined through rigorously eyeballing native iOS
// animations.
const Curve animationCurve = Curves.fastLinearToSlowEaseIn;
final bool animateForward;
// If the user releases the page before mid screen with sufficient velocity,
// or after mid screen, we should animate the page out. Otherwise, the page
// should be animated back in.
if (velocity.abs() >= _kMinFlingVelocity) {
animateForward = velocity <= 0;
} else {
animateForward = controller.value > 0.5;
}
if (animateForward) {
// The closer the panel is to dismissing, the shorter the animation is.
// We want to cap the animation time, but we want to use a linear curve
// to determine it.
final droppedPageForwardAnimationTime = min(
lerpDouble(
_kMaxDroppedSwipePageForwardAnimationTime, 0, controller.value)!
.floor(),
_kMaxPageBackAnimationTime,
);
controller.animateTo(1.0,
duration: Duration(milliseconds: droppedPageForwardAnimationTime),
curve: animationCurve);
} else {
// This route is destined to pop at this point. Reuse navigator's pop.
navigator.pop();
// The popping may have finished inline if already at the
// target destination.
if (controller.isAnimating) {
// Otherwise, use a custom popping animation duration and curve.
final droppedPageBackAnimationTime = lerpDouble(
0, _kMaxDroppedSwipePageForwardAnimationTime, controller.value)!
.floor();
controller.animateBack(0.0,
duration: Duration(milliseconds: droppedPageBackAnimationTime),
curve: animationCurve);
}
}
if (controller.isAnimating) {
// Keep the userGestureInProgress in true state so we don't change the
// curve of the page transition mid-flight since CupertinoPageTransition
// depends on userGestureInProgress.
late AnimationStatusListener animationStatusCallback;
animationStatusCallback = (status) {
navigator.didStopUserGesture();
controller.removeStatusListener(animationStatusCallback);
};
controller.addStatusListener(animationStatusCallback);
} else {
navigator.didStopUserGesture();
}
}
/// 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
CupertinoBackGestureDetectorState<T> createState() =>
CupertinoBackGestureDetectorState<T>();
}
class CupertinoBackGestureDetectorState<T>
extends State<CupertinoBackGestureDetector<T>> {
CupertinoBackGestureController<T>? _backGestureController;
late HorizontalDragGestureRecognizer _recognizer;
@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,
),
),
],
);
}
@override
void dispose() {
_recognizer.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
_recognizer = HorizontalDragGestureRecognizer(debugOwner: this)
..onStart = _handleDragStart
..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd
..onCancel = _handleDragCancel;
}
double _convertToLogical(double value) {
switch (Directionality.of(context)) {
case TextDirection.rtl:
return -value;
case TextDirection.ltr:
return value;
}
}
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.
///
/// Returns true if the user can edge-swipe to a previous route.
///
/// Returns false once [isPopGestureInProgress] is true, but
/// [isPopGestureInProgress] can only become true if [popGestureEnabled] was
/// true first.
///
/// This should only be used between frames, not during build.
bool get popGestureEnabled => _isPopGestureEnabled(this);
/// True if an iOS-style back swipe pop gesture is currently
/// underway for this route.
///
/// See also:
///
/// * [isPopGestureInProgress], which returns true if a Cupertino pop gesture
/// is currently underway for specific route.
/// * [popGestureEnabled], which returns true if a user-triggered pop gesture
/// would be allowed.
bool get popGestureInProgress => isPopGestureInProgress(this);
/// 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!;
}
bool get showCupertinoParallax;
/// {@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,
Animation<double> secondaryAnimation) {
final child = buildContent(context);
final Widget result = Semantics(
scopesRoute: true,
explicitChildNodes: true,
child: child,
);
return result;
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return buildPageTransitions<T>(
this, context, animation, secondaryAnimation, child);
}
@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
/// screen dialog, otherwise a [CupertinoPageTransition] is returned.
///
/// Used by [CupertinoPageRoute.buildTransitions].
///
/// This method can be applied to any [PageRoute], not just
/// [CupertinoPageRoute]. It's typically used to provide a Cupertino style
/// horizontal transition for material widgets when the target platform
/// is [TargetPlatform.iOS].
///
/// See also:
///
/// * [CupertinoPageTransitionsBuilder], which uses this method to define a
/// [PageTransitionsBuilder] for the [PageTransitionsTheme].
static Widget buildPageTransitions<T>(
PageRoute<T> rawRoute,
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
// Check if the route has an animation that's currently participating
// in a back swipe gesture.
//
// In the middle of a back gesture drag, let the transition be linear to
// match finger motions.
final route = rawRoute as GetPageRoute<T>;
final linearTransition = isPopGestureInProgress(route);
final finalCurve = route.curve ?? Get.defaultTransitionCurve;
final hasCurve = route.curve != null;
if (route.fullscreenDialog && route.transition == null) {
return CupertinoFullscreenDialogTransition(
primaryRouteAnimation: hasCurve
? CurvedAnimation(parent: animation, curve: finalCurve)
: animation,
secondaryRouteAnimation: secondaryAnimation,
child: child,
linearTransition: linearTransition,
);
} else {
if (route.customTransition != null) {
return route.customTransition!.buildTransition(
context,
finalCurve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth:
route.gestureWidth?.call(context) ?? _kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child,
);
}
/// Apply the curve by default...
final iosAnimation = animation;
animation = CurvedAnimation(parent: animation, curve: finalCurve);
switch (route.transition ?? Get.defaultTransition) {
case Transition.leftToRight:
return SlideLeftTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.downToUp:
return SlideDownTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.upToDown:
return SlideTopTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.noTransition:
return route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth:
route.gestureWidth?.call(context) ?? _kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child;
case Transition.rightToLeft:
return SlideRightTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.zoom:
return ZoomInTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.fadeIn:
return FadeInTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.rightToLeftWithFade:
return RightToLeftFadeTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.leftToRightWithFade:
return LeftToRightFadeTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.cupertino:
return CupertinoPageTransition(
primaryRouteAnimation: animation,
secondaryRouteAnimation: secondaryAnimation,
linearTransition: linearTransition,
child: CupertinoBackGestureDetector<T>(
gestureWidth:
route.gestureWidth?.call(context) ?? _kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child,
),
);
case Transition.size:
return SizeTransitions().buildTransitions(
context,
route.curve!,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.fade:
return FadeUpwardsPageTransitionsBuilder().buildTransitions(
route,
context,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.topLevel:
return ZoomPageTransitionsBuilder().buildTransitions(
route,
context,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.native:
return PageTransitionsTheme().buildTransitions(
route,
context,
iosAnimation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
case Transition.circularReveal:
return CircularRevealTransition().buildTransitions(
context,
route.curve,
route.alignment,
animation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
default:
if (Get.customTransition != null) {
return Get.customTransition!.buildTransition(context, route.curve,
route.alignment, animation, secondaryAnimation, child);
}
return PageTransitionsTheme().buildTransitions(
route,
context,
iosAnimation,
secondaryAnimation,
route.popGesture ?? Get.defaultPopGesture
? CupertinoBackGestureDetector<T>(
gestureWidth: route.gestureWidth?.call(context) ??
_kBackGestureWidth,
enabledCallback: () => _isPopGestureEnabled<T>(route),
onStartPopGesture: () => _startPopGesture<T>(route),
child: child)
: child);
}
}
}
// 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].
///
/// 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;
}
static bool _isPopGestureEnabled<T>(PageRoute<T> route) {
// If there's nothing to go back to, then obviously we don't support
// the back gesture.
if (route.isFirst) return false;
// If the route wouldn't actually pop if we popped it, then the gesture
// would be really confusing (or would skip internal routes),
//so disallow it.
if (route.willHandlePopInternally) return false;
// If attempts to dismiss this route might be vetoed such as in a page
// with forms, then do not allow the user to dismiss the route with a swipe.
if (route.hasScopedWillPopCallback) return false;
// Fullscreen dialogs aren't dismissible by back swipe.
if (route.fullscreenDialog) return false;
// If we're in an animation already, we cannot be manually swiped.
if (route.animation!.status != AnimationStatus.completed) return false;
// If we're being popped into, we also cannot be swiped until the pop above
// it completes. This translates to our secondary animation being
// dismissed.
if (route.secondaryAnimation!.status != AnimationStatus.dismissed) {
return false;
}
// If we're in a gesture already, we cannot start another.
if (isPopGestureInProgress(route)) return false;
// Looks like a back gesture would be welcome!
return true;
}
static CupertinoBackGestureController<T> _startPopGesture<T>(
PageRoute<T> route) {
assert(_isPopGestureEnabled(route));
return CupertinoBackGestureController<T>(
navigator: route.navigator!,
controller: route.controller!, // protected access
);
}
}

View File

@@ -0,0 +1,241 @@
import 'package:flutter/widgets.dart';
import '../../../../get_core/get_core.dart';
import '../../../../instance_manager.dart';
import '../../../get_navigation.dart';
import '../../dialog/dialog_route.dart';
import '../../router_report.dart';
/// Extracts the name of a route based on it's instance type
/// or null if not possible.
String? _extractRouteName(Route? route) {
if (route?.settings.name != null) {
return route!.settings.name;
}
if (route is GetPageRoute) {
return route.routeName;
}
if (route is GetDialogRoute) {
return 'DIALOG ${route.hashCode}';
}
if (route is GetModalBottomSheetRoute) {
return 'BOTTOMSHEET ${route.hashCode}';
}
return null;
}
class GetObserver extends NavigatorObserver {
final Function(Routing?)? routing;
final Routing? _routeSend;
GetObserver([this.routing, this._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) {
Get.log("OPEN ${newRoute.name}");
} else if (newRoute.isGetPageRoute) {
Get.log("GOING TO ROUTE ${newRoute.name}");
}
RouterReportManager.reportCurrentRoute(route);
_routeSend?.update((value) {
// Only PageRoute is allowed to change current value
if (route is PageRoute) {
value.current = newRoute.name ?? '';
}
final previousRouteName = _extractRouteName(previousRoute);
if (previousRouteName != null) {
value.previous = previousRouteName;
}
value.args = route.settings.arguments;
value.route = route;
value.isBack = false;
value.removed = '';
value.isBottomSheet =
newRoute.isBottomSheet ? true : value.isBottomSheet ?? false;
value.isDialog = newRoute.isDialog ? true : value.isDialog ?? false;
});
if (routing != null) {
routing!(_routeSend);
}
}
@override
void didRemove(Route route, Route? previousRoute) {
super.didRemove(route, previousRoute);
final routeName = _extractRouteName(route);
final currentRoute = _RouteData.ofRoute(route);
Get.log("REMOVING ROUTE $routeName");
_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;
});
if (route is GetPageRoute) {
RouterReportManager.reportRouteWillDispose(route);
}
routing?.call(_routeSend);
}
@override
void didReplace({Route? newRoute, Route? oldRoute}) {
super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
final newName = _extractRouteName(newRoute);
final oldName = _extractRouteName(oldRoute);
final currentRoute = _RouteData.ofRoute(oldRoute);
Get.log("REPLACE ROUTE $oldName");
Get.log("NEW ROUTE $newName");
if (newRoute != null) {
RouterReportManager.reportCurrentRoute(newRoute);
}
_routeSend?.update((value) {
// Only PageRoute is allowed to change current value
if (newRoute is PageRoute) {
value.current = newName ?? '';
}
value.args = newRoute?.settings.arguments;
value.route = newRoute;
value.isBack = false;
value.removed = '';
value.previous = '$oldName';
// value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar;
value.isBottomSheet =
currentRoute.isBottomSheet ? false : value.isBottomSheet;
value.isDialog = currentRoute.isDialog ? false : value.isDialog;
});
if (oldRoute is GetPageRoute) {
RouterReportManager.reportRouteWillDispose(oldRoute);
}
routing?.call(_routeSend);
}
}
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);
}
}
/// 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,
);
}
}

View File

@@ -0,0 +1,291 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import '../../../get.dart';
abstract class _RouteMiddleware {
/// The Order of the Middlewares to run.
///
/// {@tool snippet}
/// This Middewares will be called in this order.
/// ```dart
/// final middlewares = [
/// GetMiddleware(priority: 2),
/// GetMiddleware(priority: 5),
/// GetMiddleware(priority: 4),
/// GetMiddleware(priority: -8),
/// ];
/// ```
/// -8 => 2 => 4 => 5
/// {@end-tool}
int? priority;
/// This function will be called when the page of
/// the called route is being searched for.
/// It take RouteSettings as a result an redirect to the new settings or
/// give it null and there will be no redirecting.
/// {@tool snippet}
/// ```dart
/// GetPage redirect(String route) {
/// final authService = Get.find<AuthService>();
/// return authService.authed.value ? null : RouteSettings(name: '/login');
/// }
/// ```
/// {@end-tool}
RouteSettings? redirect(String route);
/// Similar to [redirect],
/// This function will be called when the router delegate changes the
/// current route.
///
/// The default implmentation is to navigate to
/// the input route, with no redirection.
///
/// if this returns null, the navigation is stopped,
/// and no new routes are pushed.
/// {@tool snippet}
/// ```dart
/// GetNavConfig? redirect(GetNavConfig route) {
/// final authService = Get.find<AuthService>();
/// return authService.authed.value ? null : RouteSettings(name: '/login');
/// }
/// ```
/// {@end-tool}
Future<GetNavConfig?> redirectDelegate(GetNavConfig route);
/// This function will be called when this Page is called
/// you can use it to change something about the page or give it new page
/// {@tool snippet}
/// ```dart
/// GetPage onPageCalled(GetPage page) {
/// final authService = Get.find<AuthService>();
/// return page.copyWith(title: 'Welcome ${authService.UserName}');
/// }
/// ```
/// {@end-tool}
GetPage? onPageCalled(GetPage page);
/// This function will be called right before the [Bindings] are initialize.
/// Here you can change [Bindings] for this page
/// {@tool snippet}
/// ```dart
/// List<Bindings> onBindingsStart(List<Bindings> bindings) {
/// final authService = Get.find<AuthService>();
/// if (authService.isAdmin) {
/// bindings.add(AdminBinding());
/// }
/// return bindings;
/// }
/// ```
/// {@end-tool}
List<Bindings>? onBindingsStart(List<Bindings> bindings);
/// This function will be called right after the [Bindings] are initialize.
GetPageBuilder? onPageBuildStart(GetPageBuilder page);
/// This function will be called right after the
/// GetPage.page function is called and will give you the result
/// of the function. and take the widget that will be showed.
Widget onPageBuilt(Widget page);
void onPageDispose();
}
/// The Page Middlewares.
/// The Functions will be called in this order
/// (( [redirect] -> [onPageCalled] -> [onBindingsStart] ->
/// [onPageBuildStart] -> [onPageBuilt] -> [onPageDispose] ))
class GetMiddleware implements _RouteMiddleware {
@override
int? priority = 0;
GetMiddleware({this.priority});
@override
RouteSettings? redirect(String? route) => null;
@override
GetPage? onPageCalled(GetPage? page) => page;
@override
List<Bindings>? onBindingsStart(List<Bindings>? bindings) => bindings;
@override
GetPageBuilder? onPageBuildStart(GetPageBuilder? page) => page;
@override
Widget onPageBuilt(Widget page) => page;
@override
void onPageDispose() {}
@override
Future<GetNavConfig?> redirectDelegate(GetNavConfig route) =>
SynchronousFuture(route);
}
class MiddlewareRunner {
MiddlewareRunner(this._middlewares);
final List<GetMiddleware>? _middlewares;
List<GetMiddleware> _getMiddlewares() {
final _m = _middlewares ?? <GetMiddleware>[];
return _m
..sort(
(a, b) => (a.priority ?? 0).compareTo(b.priority ?? 0),
);
}
GetPage? runOnPageCalled(GetPage? page) {
_getMiddlewares().forEach((element) {
page = element.onPageCalled(page);
});
return page;
}
RouteSettings? runRedirect(String? route) {
RouteSettings? to;
for (final element in _getMiddlewares()) {
to = element.redirect(route);
if (to != null) {
break;
}
}
Get.log('Redirect to $to');
return to;
}
List<Bindings>? runOnBindingsStart(List<Bindings>? bindings) {
_getMiddlewares().forEach((element) {
bindings = element.onBindingsStart(bindings);
});
return bindings;
}
GetPageBuilder? runOnPageBuildStart(GetPageBuilder? page) {
_getMiddlewares().forEach((element) {
page = element.onPageBuildStart(page);
});
return page;
}
Widget runOnPageBuilt(Widget page) {
_getMiddlewares().forEach((element) {
page = element.onPageBuilt(page);
});
return page;
}
void runOnPageDispose() =>
_getMiddlewares().forEach((element) => element.onPageDispose());
}
class PageRedirect {
GetPage? route;
GetPage? unknownRoute;
RouteSettings? settings;
bool isUnknown;
PageRedirect({
this.route,
this.unknownRoute,
this.isUnknown = false,
this.settings,
});
// redirect all pages that needes redirecting
GetPageRoute<T> page<T>() {
while (needRecheck()) {}
final _r = (isUnknown ? unknownRoute : route)!;
return GetPageRoute<T>(
page: _r.page,
parameter: _r.parameters,
settings: isUnknown
? RouteSettings(
name: _r.name,
arguments: settings!.arguments,
)
: settings,
curve: _r.curve,
opaque: _r.opaque,
showCupertinoParallax: _r.showCupertinoParallax,
gestureWidth: _r.gestureWidth,
customTransition: _r.customTransition,
binding: _r.binding,
bindings: _r.bindings,
transitionDuration:
_r.transitionDuration ?? Get.defaultTransitionDuration,
transition: _r.transition,
popGesture: _r.popGesture,
fullscreenDialog: _r.fullscreenDialog,
middlewares: _r.middlewares,
);
}
// redirect all pages that needes redirecting
GetPageRoute<T> getPageToRoute<T>(GetPage rou, GetPage? unk) {
while (needRecheck()) {}
final _r = (isUnknown ? unk : rou)!;
return GetPageRoute<T>(
page: _r.page,
parameter: _r.parameters,
alignment: _r.alignment,
title: _r.title,
maintainState: _r.maintainState,
routeName: _r.name,
settings: _r,
curve: _r.curve,
showCupertinoParallax: _r.showCupertinoParallax,
gestureWidth: _r.gestureWidth,
opaque: _r.opaque,
customTransition: _r.customTransition,
binding: _r.binding,
bindings: _r.bindings,
transitionDuration:
_r.transitionDuration ?? Get.defaultTransitionDuration,
transition: _r.transition,
popGesture: _r.popGesture,
fullscreenDialog: _r.fullscreenDialog,
middlewares: _r.middlewares,
);
}
/// check if redirect is needed
bool needRecheck() {
if (settings == null && route != null) {
settings = route;
}
final match = Get.routeTree.matchRoute(settings!.name!);
Get.parameters = match.parameters;
// No Match found
if (match.route == null) {
isUnknown = true;
return false;
}
final runner = MiddlewareRunner(match.route!.middlewares);
route = runner.runOnPageCalled(match.route);
addPageParameter(route!);
// No middlewares found return match.
if (match.route!.middlewares == null || match.route!.middlewares!.isEmpty) {
return false;
}
final newSettings = runner.runRedirect(settings!.name);
if (newSettings == null) {
return false;
}
settings = newSettings;
return true;
}
void addPageParameter(GetPage route) {
if (route.parameters == null) return;
final parameters = Get.parameters;
parameters.addEntries(route.parameters!.entries);
Get.parameters = parameters;
}
}

View File

@@ -0,0 +1,22 @@
import 'package:flutter/widgets.dart';
enum Transition {
fade,
fadeIn,
rightToLeft,
leftToRight,
upToDown,
downToUp,
rightToLeftWithFade,
leftToRightWithFade,
zoom,
topLevel,
noTransition,
cupertino,
cupertinoDialog,
size,
circularReveal,
native,
}
typedef GetPageBuilder = Widget Function();

View File

@@ -0,0 +1,660 @@
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);
@Deprecated('use GetSnackBar')
class GetBar extends GetSnackBar {
GetBar({
Key? key,
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,
}) : 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;
/// 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;
/// Replaces [title]. Although this accepts a [Widget], it is meant
/// to receive [Text] or [RichText]
final Widget? titleText;
/// Replaces [message]. Although this accepts a [Widget], it is meant
/// to receive [Text] or [RichText]
final Widget? messageText;
/// Will be ignored if [backgroundGradient] is not null
final Color backgroundColor;
/// If not null, shows a left vertical colored bar on notification.
/// It is not possible to use it with a [Form] and I do not recommend
/// using it with [LinearProgressIndicator]
final Color? leftBarIndicatorColor;
/// [boxShadows] The shadows generated by Snack. Leave it null
/// if you don't want a shadow.
/// You can use more than one if you feel the need.
/// Check (this example)[https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/shadows.dart]
final List<BoxShadow>? boxShadows;
/// 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
/// indication of what kind
/// of message you are displaying. Other widgets may break the layout
final Widget? icon;
/// An option to animate the icon (if present). Defaults to true.
final bool shouldIconPulse;
/// (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.
/// An alternative to [mainButton]
final OnTap? onTap;
/// How long until Snack will hide itself (be dismissed).
/// To make it indefinite, leave it null.
final Duration? duration;
/// True if you want to show a [LinearProgressIndicator].
final bool showProgressIndicator;
/// An optional [AnimationController] when you want to control the
/// progress of your [LinearProgressIndicator].
final AnimationController? progressIndicatorController;
/// A [LinearProgressIndicator] configuration parameter.
final Color? progressIndicatorBackgroundColor;
/// A [LinearProgressIndicator] configuration parameter.
final Animation<Color>? progressIndicatorValueColor;
/// Determines if the user can swipe or click the overlay
/// (if [overlayBlur] > 0) to dismiss.
/// It is recommended that you set [duration] != null if this is false.
/// If the user swipes to dismiss or clicks the overlay, no value
/// will be returned.
final bool isDismissible;
/// Used to limit Snack width (usually on large screens)
final double? maxWidth;
/// Adds a custom margin to Snack
final EdgeInsets margin;
/// Adds a custom padding to Snack
/// The default follows material design guide line
final EdgeInsets padding;
/// Adds a radius to all corners of Snack. Best combined with [margin].
/// I do not recommend using it with [showProgressIndicator]
/// or [leftBarIndicatorColor].
final double borderRadius;
/// Adds a border to every side of Snack
/// I do not recommend using it with [showProgressIndicator]
/// or [leftBarIndicatorColor].
final Color? borderColor;
/// Changes the width of the border if [borderColor] is specified
final double? borderWidth;
/// Snack can be based on [SnackPosition.TOP] or on [SnackPosition.BOTTOM]
/// of your screen.
/// [SnackPosition.BOTTOM] is the default.
final SnackPosition snackPosition;
/// 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
/// If grounded, I do not recommend using a [backgroundColor] with
/// transparency or [barBlur]
final SnackStyle snackStyle;
/// The [Curve] animation used when show() is called.
/// [Curves.easeOut] is default
final Curve forwardAnimationCurve;
/// The [Curve] animation used when dismiss() is called.
/// [Curves.fastOutSlowIn] is default
final Curve reverseAnimationCurve;
/// Use it to speed up or slow down the animation duration
final Duration animationDuration;
/// 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;
/// Default is 0.0. If different than 0.0, creates a blurred
/// overlay that prevents the user from interacting with the screen.
/// The greater the value, the greater the blur.
final double overlayBlur;
/// Default is [Colors.transparent]. Only takes effect if [overlayBlur] > 0.0.
/// Make sure you use a color with transparency here e.g.
/// Colors.grey[600].withOpacity(0.2).
final Color? overlayColor;
/// A [TextFormField] in case you want a simple user input.
/// 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]
SnackbarController show() {
return Get.showSnackbar(this);
}
}
class GetSnackBarState extends State<GetSnackBar>
with TickerProviderStateMixin {
AnimationController? _fadeController;
late Animation<double> _fadeAnimation;
final Widget _emptyWidget = SizedBox(width: 0.0, height: 0.0);
final double _initialOpacity = 1.0;
final double _finalOpacity = 0.4;
final Duration _pulseAnimationDuration = Duration(seconds: 1);
late bool _isTitlePresent;
late double _messageTopMargin;
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();
assert(
widget.userInputForm != null ||
((widget.message != null && widget.message!.isNotEmpty) ||
widget.messageText != null),
'''
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;
_configureLeftBarFuture();
_configureProgressIndicatorAnimation();
if (widget.icon != null && widget.shouldIconPulse) {
_configurePulseAnimation();
_fadeController?.forward();
}
_focusNode = FocusScopeNode();
_focusAttachment = _focusNode!.attach(context);
}
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;
}
}
void _configureLeftBarFuture() {
ambiguate(SchedulerBinding.instance)?.addPostFrameCallback(
(_) {
final keyContext = _backgroundBoxKey.currentContext;
if (keyContext != null) {
final box = keyContext.findRenderObject() as RenderBox;
_boxHeightCompleter.complete(box.size);
}
},
);
}
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);
_fadeAnimation = Tween(begin: _initialOpacity, end: _finalOpacity).animate(
CurvedAnimation(
parent: _fadeController!,
curve: Curves.linear,
),
);
_fadeController!.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_fadeController!.reverse();
}
if (status == AnimationStatus.dismissed) {
_fadeController!.forward();
}
});
_fadeController!.forward();
}
Widget _containerWithForm() {
return Container(
key: _backgroundBoxKey,
constraints: widget.maxWidth != null
? BoxConstraints(maxWidth: widget.maxWidth!)
: null,
decoration: BoxDecoration(
color: widget.backgroundColor,
gradient: widget.backgroundGradient,
boxShadow: widget.boxShadows,
borderRadius: BorderRadius.circular(widget.borderRadius),
border: widget.borderColor != null
? Border.all(
color: widget.borderColor!,
width: widget.borderWidth!,
)
: null,
),
child: Padding(
padding: const EdgeInsets.only(
left: 8.0, right: 8.0, bottom: 8.0, top: 16.0),
child: FocusScope(
child: widget.userInputForm!,
node: _focusNode,
autofocus: true,
),
),
);
}
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,
constraints: widget.maxWidth != null
? BoxConstraints(maxWidth: widget.maxWidth!)
: null,
decoration: BoxDecoration(
color: widget.backgroundColor,
gradient: widget.backgroundGradient,
boxShadow: widget.boxShadows,
borderRadius: BorderRadius.circular(widget.borderRadius),
border: widget.borderColor != null
? Border.all(color: widget.borderColor!, width: widget.borderWidth!)
: null,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
widget.showProgressIndicator
? LinearProgressIndicator(
value: widget.progressIndicatorController != null
? _progressAnimation.value
: null,
backgroundColor: widget.progressIndicatorBackgroundColor,
valueColor: widget.progressIndicatorValueColor,
)
: _emptyWidget,
Row(
mainAxisSize: MainAxisSize.max,
children: [
_buildLeftBarIndicator(),
if (_rowStyle == RowStyle.icon || _rowStyle == RowStyle.all)
ConstrainedBox(
constraints:
BoxConstraints.tightFor(width: 42.0 + iconPadding),
child: _getIcon(),
),
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: _messageTopMargin,
left: left,
right: right,
bottom: widget.padding.bottom,
),
child: widget.messageText ??
Text(
widget.message ?? "",
style:
TextStyle(fontSize: 14.0, color: Colors.white),
),
),
],
),
),
if (_rowStyle == RowStyle.action || _rowStyle == RowStyle.all)
Padding(
padding: EdgeInsets.only(right: buttonPadding),
child: widget.mainButton,
),
],
),
],
),
);
}
Widget? _getIcon() {
if (widget.icon != null && widget.icon is Icon && widget.shouldIconPulse) {
return FadeTransition(
opacity: _fadeAnimation,
child: widget.icon,
);
} else if (widget.icon != null) {
return widget.icon;
} else {
return _emptyWidget;
}
}
void _updateProgress() => setState(() {});
}
enum RowStyle {
icon,
action,
all,
none,
}
/// Indicates Status of snackbar
/// [SnackbarStatus.OPEN] Snack is fully open, [SnackbarStatus.CLOSED] Snackbar
/// has closed,
/// [SnackbarStatus.OPENING] Starts with the opening animation and ends
/// with the full
/// snackbar display, [SnackbarStatus.CLOSING] Starts with the closing animation
/// 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 }

View File

@@ -0,0 +1,372 @@
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();
}
}

View File

@@ -0,0 +1,5 @@
library get_rx;
export 'src/rx_stream/rx_stream.dart';
export 'src/rx_types/rx_types.dart';
export 'src/rx_workers/rx_workers.dart';

View File

@@ -0,0 +1,209 @@
part of rx_stream;
/// [GetStream] is the lightest and most performative way of working
/// with events at Dart. You sintaxe is like StreamController, but it works
/// with simple callbacks. In this way, every event calls only one function.
/// There is no buffering, to very low memory consumption.
/// event [add] will add a object to stream. [addError] will add a error
/// to stream. [listen] is a very light StreamSubscription interface.
/// Is possible take the last value with [value] property.
class GetStream<T> {
void Function()? onListen;
void Function()? onPause;
void Function()? onResume;
FutureOr<void> Function()? onCancel;
GetStream({this.onListen, this.onPause, this.onResume, this.onCancel});
List<LightSubscription<T>>? _onData = <LightSubscription<T>>[];
bool? _isBusy = false;
FutureOr<bool?> removeSubscription(LightSubscription<T> subs) async {
if (!_isBusy!) {
return _onData!.remove(subs);
} else {
await Future.delayed(Duration.zero);
return _onData?.remove(subs);
}
}
FutureOr<void> addSubscription(LightSubscription<T> subs) async {
if (!_isBusy!) {
return _onData!.add(subs);
} else {
await Future.delayed(Duration.zero);
return _onData!.add(subs);
}
}
int? get length => _onData?.length;
bool get hasListeners => _onData!.isNotEmpty;
void _notifyData(T data) {
_isBusy = true;
for (final item in _onData!) {
if (!item.isPaused) {
item._data?.call(data);
}
}
_isBusy = false;
}
void _notifyError(Object error, [StackTrace? stackTrace]) {
assert(!isClosed, 'You cannot add errors to a closed stream.');
_isBusy = true;
var itemsToRemove = <LightSubscription<T>>[];
for (final item in _onData!) {
if (!item.isPaused) {
if (stackTrace != null) {
item._onError?.call(error, stackTrace);
} else {
item._onError?.call(error);
}
if (item.cancelOnError ?? false) {
//item.cancel?.call();
itemsToRemove.add(item);
item.pause();
item._onDone?.call();
}
}
}
for (final item in itemsToRemove) {
_onData!.remove(item);
}
_isBusy = false;
}
void _notifyDone() {
assert(!isClosed, 'You cannot close a closed stream.');
_isBusy = true;
for (final item in _onData!) {
if (!item.isPaused) {
item._onDone?.call();
}
}
_isBusy = false;
}
T? _value;
T? get value => _value;
void add(T event) {
assert(!isClosed, 'You cannot add event to closed Stream');
_value = event;
_notifyData(event);
}
bool get isClosed => _onData == null;
void addError(Object error, [StackTrace? stackTrace]) {
assert(!isClosed, 'You cannot add error to closed Stream');
_notifyError(error, stackTrace);
}
void close() {
assert(!isClosed, 'You cannot close a closed Stream');
_notifyDone();
_onData = null;
_isBusy = null;
_value = null;
}
LightSubscription<T> listen(void Function(T event) onData,
{Function? onError, void Function()? onDone, bool? cancelOnError}) {
final subs = LightSubscription<T>(
removeSubscription,
onPause: onPause,
onResume: onResume,
onCancel: onCancel,
)
..onData(onData)
..onError(onError)
..onDone(onDone)
..cancelOnError = cancelOnError;
addSubscription(subs);
onListen?.call();
return subs;
}
Stream<T> get stream =>
GetStreamTransformation(addSubscription, removeSubscription);
}
class LightSubscription<T> extends StreamSubscription<T> {
final RemoveSubscription<T> _removeSubscription;
LightSubscription(this._removeSubscription,
{this.onPause, this.onResume, this.onCancel});
final void Function()? onPause;
final void Function()? onResume;
final FutureOr<void> Function()? onCancel;
bool? cancelOnError = false;
@override
Future<void> cancel() {
_removeSubscription(this);
onCancel?.call();
return Future.value();
}
OnData<T>? _data;
Function? _onError;
Callback? _onDone;
bool _isPaused = false;
@override
void onData(OnData<T>? handleData) => _data = handleData;
@override
void onError(Function? handleError) => _onError = handleError;
@override
void onDone(Callback? handleDone) => _onDone = handleDone;
@override
void pause([Future<void>? resumeSignal]) {
_isPaused = true;
onPause?.call();
}
@override
void resume() {
_isPaused = false;
onResume?.call();
}
@override
bool get isPaused => _isPaused;
@override
Future<E> asFuture<E>([E? futureValue]) => Future.value(futureValue);
}
class GetStreamTransformation<T> extends Stream<T> {
final AddSubscription<T> _addSubscription;
final RemoveSubscription<T> _removeSubscription;
GetStreamTransformation(this._addSubscription, this._removeSubscription);
@override
LightSubscription<T> listen(void Function(T event)? onData,
{Function? onError, void Function()? onDone, bool? cancelOnError}) {
final subs = LightSubscription<T>(_removeSubscription)
..onData(onData)
..onError(onError)
..onDone(onDone);
_addSubscription(subs);
return subs;
}
}
typedef RemoveSubscription<T> = FutureOr<bool?> Function(
LightSubscription<T> subs);
typedef AddSubscription<T> = FutureOr<void> Function(LightSubscription<T> subs);

View File

@@ -0,0 +1,203 @@
part of rx_stream;
class Node<T> {
T? data;
Node<T>? next;
Node({this.data, this.next});
}
class MiniSubscription<T> {
const MiniSubscription(
this.data, this.onError, this.onDone, this.cancelOnError, this.listener);
final OnData<T> data;
final Function? onError;
final Callback? onDone;
final bool cancelOnError;
Future<void> cancel() async => listener.removeListener(this);
final FastList<T> listener;
}
class MiniStream<T> {
FastList<T> listenable = FastList<T>();
late T _value;
T get value => _value;
set value(T val) {
add(val);
}
void add(T event) {
_value = event;
listenable._notifyData(event);
}
void addError(Object error, [StackTrace? stackTrace]) {
listenable._notifyError(error, stackTrace);
}
int get length => listenable.length;
bool get hasListeners => listenable.isNotEmpty;
bool get isClosed => _isClosed;
MiniSubscription<T> listen(void Function(T event) onData,
{Function? onError,
void Function()? onDone,
bool cancelOnError = false}) {
final subs = MiniSubscription<T>(
onData,
onError,
onDone,
cancelOnError,
listenable,
);
listenable.addListener(subs);
return subs;
}
bool _isClosed = false;
void close() {
if (_isClosed) {
throw 'You can not close a closed Stream';
}
listenable._notifyDone();
listenable.clear();
_isClosed = true;
}
}
class FastList<T> {
Node<MiniSubscription<T>>? _head;
void _notifyData(T data) {
var currentNode = _head;
do {
currentNode?.data?.data(data);
currentNode = currentNode?.next;
} while (currentNode != null);
}
void _notifyDone() {
var currentNode = _head;
do {
currentNode?.data?.onDone?.call();
currentNode = currentNode?.next;
} while (currentNode != null);
}
void _notifyError(Object error, [StackTrace? stackTrace]) {
var currentNode = _head;
while (currentNode != null) {
currentNode.data!.onError?.call(error, stackTrace);
currentNode = currentNode.next;
}
}
/// Checks if this list is empty
bool get isEmpty => _head == null;
bool get isNotEmpty => !isEmpty;
/// Returns the length of this list
int get length {
var length = 0;
var currentNode = _head;
while (currentNode != null) {
currentNode = currentNode.next;
length++;
}
return length;
}
/// Shows the element at position [position]. `null` for invalid positions.
MiniSubscription<T>? _elementAt(int position) {
if (isEmpty || length < position || position < 0) return null;
var node = _head;
var current = 0;
while (current != position) {
node = node!.next;
current++;
}
return node!.data;
}
/// Inserts [data] at the end of the list.
void addListener(MiniSubscription<T> data) {
var newNode = Node(data: data);
if (isEmpty) {
_head = newNode;
} else {
var currentNode = _head!;
while (currentNode.next != null) {
currentNode = currentNode.next!;
}
currentNode.next = newNode;
}
}
bool contains(T element) {
var length = this.length;
for (var i = 0; i < length; i++) {
if (_elementAt(i) == element) return true;
if (length != this.length) {
throw ConcurrentModificationError(this);
}
}
return false;
}
void removeListener(MiniSubscription<T> element) {
var length = this.length;
for (var i = 0; i < length; i++) {
if (_elementAt(i) == element) {
_removeAt(i);
break;
}
}
}
void clear() {
var length = this.length;
for (var i = 0; i < length; i++) {
_removeAt(i);
}
}
MiniSubscription<T>? _removeAt(int position) {
var index = 0;
var currentNode = _head;
Node<MiniSubscription<T>>? previousNode;
if (isEmpty || length < position || position < 0) {
throw Exception('Invalid position');
} else if (position == 0) {
_head = _head!.next;
} else {
while (index != position) {
previousNode = currentNode;
currentNode = currentNode!.next;
index++;
}
if (previousNode == null) {
_head = null;
} else {
previousNode.next = currentNode!.next;
}
currentNode!.next = null;
}
return currentNode!.data;
}
}

View File

@@ -0,0 +1,8 @@
library rx_stream;
import 'dart:async';
import '../rx_typedefs/rx_typedefs.dart';
part 'get_stream.dart';
part 'mini_stream.dart';

View File

@@ -0,0 +1,3 @@
typedef Condition = bool Function();
typedef OnData<T> = void Function(T data);
typedef Callback = void Function();

View File

@@ -0,0 +1,394 @@
part of rx_types;
/// global object that registers against `GetX` and `Obx`, and allows the
/// reactivity
/// of those `Widgets` and Rx values.
mixin RxObjectMixin<T> on NotifyManager<T> {
late T _value;
/// Makes a direct update of [value] adding it to the Stream
/// useful when you make use of Rx for custom Types to referesh your UI.
///
/// Sample:
/// ```
/// class Person {
/// String name, last;
/// int age;
/// Person({this.name, this.last, this.age});
/// @override
/// String toString() => '$name $last, $age years old';
/// }
///
/// final person = Person(name: 'John', last: 'Doe', age: 18).obs;
/// person.value.name = 'Roi';
/// person.refresh();
/// print( person );
/// ```
void refresh() {
subject.add(value);
}
/// updates the value to `null` and adds it to the Stream.
/// Even with null-safety coming, is still an important feature to support, as
/// `call()` doesn't accept `null` values. For instance,
/// `InputDecoration.errorText` has to be null to not show the "error state".
///
/// Sample:
/// ```
/// final inputError = ''.obs..nil();
/// print('${inputError.runtimeType}: $inputError'); // outputs > RxString: null
/// ```
// void nil() {
// subject.add(_value = null);
// }
/// Makes this Rx looks like a function so you can update a new
/// value using `rx(someOtherValue)`. Practical to assign the Rx directly
/// to some Widget that has a signature ::onChange( value )
///
/// Example:
/// ```
/// final myText = 'GetX rocks!'.obs;
///
/// // in your Constructor, just to check it works :P
/// ever( myText, print ) ;
///
/// // in your build(BuildContext) {
/// TextField(
/// onChanged: myText,
/// ),
///```
T call([T? v]) {
if (v != null) {
value = v;
}
return value;
}
bool firstRebuild = true;
bool sentToStream = false;
/// Same as `toString()` but using a getter.
String get string => value.toString();
@override
String toString() => value.toString();
/// Returns the json representation of `value`.
dynamic toJson() => value;
/// This equality override works for _RxImpl instances and the internal
/// values.
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
bool operator ==(Object o) {
// Todo, find a common implementation for the hashCode of different Types.
if (o is T) return value == o;
if (o is RxObjectMixin<T>) return value == o.value;
return false;
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => _value.hashCode;
/// Updates the [value] and adds it to the stream, updating the observer
/// Widget, only if it's different from the previous value.
set value(T val) {
if (subject.isClosed) return;
sentToStream = false;
if (_value == val && !firstRebuild) return;
firstRebuild = false;
_value = val;
sentToStream = true;
subject.add(_value);
}
/// Returns the current [value]
T get value {
RxInterface.proxy?.addListener(subject);
return _value;
}
Stream<T> get stream => subject.stream;
/// Returns a [StreamSubscription] similar to [listen], but with the
/// added benefit that it primes the stream with the current [value], rather
/// than waiting for the next [value]. This should not be called in [onInit]
/// or anywhere else during the build process.
StreamSubscription<T> listenAndPump(void Function(T event) onData,
{Function? onError, void Function()? onDone, bool? cancelOnError}) {
final subscription = listen(
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
subject.add(value);
return subscription;
}
/// Binds an existing `Stream<T>` to this Rx<T> to keep the values in sync.
/// You can bind multiple sources to update the value.
/// Closing the subscription will happen automatically when the observer
/// Widget (`GetX` or `Obx`) gets unmounted from the Widget tree.
void bindStream(Stream<T> stream) {
final listSubscriptions =
_subscriptions[subject] ??= <StreamSubscription>[];
listSubscriptions.add(stream.listen((va) => value = va));
}
}
class RxNotifier<T> = RxInterface<T> with NotifyManager<T>;
mixin NotifyManager<T> {
GetStream<T> subject = GetStream<T>();
final _subscriptions = <GetStream, List<StreamSubscription>>{};
bool get canUpdate => _subscriptions.isNotEmpty;
/// This is an internal method.
/// Subscribe to changes on the inner stream.
void addListener(GetStream<T> rxGetx) {
if (!_subscriptions.containsKey(rxGetx)) {
final subs = rxGetx.listen((data) {
if (!subject.isClosed) subject.add(data);
});
final listSubscriptions =
_subscriptions[rxGetx] ??= <StreamSubscription>[];
listSubscriptions.add(subs);
}
}
StreamSubscription<T> listen(
void Function(T) onData, {
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) =>
subject.listen(
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError ?? false,
);
/// Closes the subscriptions for this Rx, releasing the resources.
void close() {
_subscriptions.forEach((getStream, _subscriptions) {
for (final subscription in _subscriptions) {
subscription.cancel();
}
});
_subscriptions.clear();
subject.close();
}
}
/// Base Rx class that manages all the stream logic for any Type.
abstract class _RxImpl<T> extends RxNotifier<T> with RxObjectMixin<T> {
_RxImpl(T initial) {
_value = initial;
}
void addError(Object error, [StackTrace? stackTrace]) {
subject.addError(error, stackTrace);
}
Stream<R> map<R>(R mapper(T? data)) => stream.map(mapper);
/// Uses a callback to update [value] internally, similar to [refresh],
/// but provides the current value as the argument.
/// Makes sense for custom Rx types (like Models).
///
/// Sample:
/// ```
/// class Person {
/// String name, last;
/// int age;
/// Person({this.name, this.last, this.age});
/// @override
/// String toString() => '$name $last, $age years old';
/// }
///
/// final person = Person(name: 'John', last: 'Doe', age: 18).obs;
/// person.update((person) {
/// person.name = 'Roi';
/// });
/// print( person );
/// ```
void update(void fn(T? val)) {
fn(_value);
subject.add(_value);
}
/// Following certain practices on Rx data, we might want to react to certain
/// listeners when a value has been provided, even if the value is the same.
/// At the moment, we ignore part of the process if we `.call(value)` with
/// the same value since it holds the value and there's no real
/// need triggering the entire process for the same value inside, but
/// there are other situations where we might be interested in
/// triggering this.
///
/// For example, supposed we have a `int seconds = 2` and we want to animate
/// from invisible to visible a widget in two seconds:
/// RxEvent<int>.call(seconds);
/// then after a click happens, you want to call a RxEvent<int>.call(seconds).
/// By doing `call(seconds)`, if the value being held is the same,
/// the listeners won't trigger, hence we need this new `trigger` function.
/// This will refresh the listener of an AnimatedWidget and will keep
/// the value if the Rx is kept in memory.
/// Sample:
/// ```
/// Rx<Int> secondsRx = RxInt();
/// secondsRx.listen((value) => print("$value seconds set"));
///
/// secondsRx.call(2); // This won't trigger any listener, since the value is the same
/// secondsRx.trigger(2); // This will trigger the listener independently from the value.
/// ```
///
void trigger(T v) {
var firstRebuild = this.firstRebuild;
value = v;
// If it's not the first rebuild, the listeners have been called already
// So we won't call them again.
if (!firstRebuild && !sentToStream) {
subject.add(v);
}
}
}
class RxBool extends Rx<bool> {
RxBool(bool initial) : super(initial);
@override
String toString() {
return value ? "true" : "false";
}
}
class RxnBool extends Rx<bool?> {
RxnBool([bool? initial]) : super(initial);
@override
String toString() {
return "$value";
}
}
extension RxBoolExt on Rx<bool> {
bool get isTrue => value;
bool get isFalse => !isTrue;
bool operator &(bool other) => other && value;
bool operator |(bool other) => other || value;
bool operator ^(bool other) => !other == value;
/// Toggles the bool [value] between false and true.
/// A shortcut for `flag.value = !flag.value;`
/// FIXME: why return this? fluent interface is not
/// not really a dart thing since we have '..' operator
// ignore: avoid_returning_this
Rx<bool> toggle() {
subject.add(_value = !_value);
return this;
}
}
extension RxnBoolExt on Rx<bool?> {
bool? get isTrue => value;
bool? get isFalse {
if (value != null) return !isTrue!;
return null;
}
bool? operator &(bool other) {
if (value != null) {
return other && value!;
}
return null;
}
bool? operator |(bool other) {
if (value != null) {
return other || value!;
}
return null;
}
bool? operator ^(bool other) => !other == value;
/// Toggles the bool [value] between false and true.
/// A shortcut for `flag.value = !flag.value;`
/// FIXME: why return this? fluent interface is not
/// not really a dart thing since we have '..' operator
// ignore: avoid_returning_this
Rx<bool?>? toggle() {
if (_value != null) {
subject.add(_value = !_value!);
return this;
}
return null;
}
}
/// Foundation class used for custom `Types` outside the common native Dart
/// types.
/// For example, any custom "Model" class, like User().obs will use `Rx` as
/// wrapper.
class Rx<T> extends _RxImpl<T> {
Rx(T initial) : super(initial);
@override
dynamic toJson() {
try {
return (value as dynamic)?.toJson();
} on Exception catch (_) {
throw '$T has not method [toJson]';
}
}
}
class Rxn<T> extends Rx<T?> {
Rxn([T? initial]) : super(initial);
@override
dynamic toJson() {
try {
return (value as dynamic)?.toJson();
} on Exception catch (_) {
throw '$T has not method [toJson]';
}
}
}
extension StringExtension on String {
/// Returns a `RxString` with [this] `String` as initial value.
RxString get obs => RxString(this);
}
extension IntExtension on int {
/// Returns a `RxInt` with [this] `int` as initial value.
RxInt get obs => RxInt(this);
}
extension DoubleExtension on double {
/// Returns a `RxDouble` with [this] `double` as initial value.
RxDouble get obs => RxDouble(this);
}
extension BoolExtension on bool {
/// Returns a `RxBool` with [this] `bool` as initial value.
RxBool get obs => RxBool(this);
}
extension RxT<T> on T {
/// Returns a `Rx` instance with [this] `T` as initial value.
Rx<T> get obs => Rx<T>(this);
}

View File

@@ -0,0 +1,41 @@
part of rx_types;
/// This class is the foundation for all reactive (Rx) classes that makes Get
/// so powerful.
/// 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
void addListener(GetStream<T> rxGetx);
/// Close the Rx Variable
void close();
/// Calls `callback` with current value, when the value changes.
StreamSubscription<T> listen(void Function(T event) onData,
{Function? onError, void Function()? onDone, bool? cancelOnError});
/// Avoids an unsafe usage of the `proxy`
static T notifyChildren<T>(RxNotifier observer, ValueGetter<T> builder) {
final _observer = RxInterface.proxy;
RxInterface.proxy = observer;
final result = builder();
if (!observer.canUpdate) {
RxInterface.proxy = _observer;
throw """
[Get] the improper use of a GetX has been detected.
You should only use GetX or Obx for the specific widget that will be updated.
If you are seeing this error, you probably did not insert any observable variables into GetX/Obx
or insert them outside the scope that GetX considers suitable for an update
(example: GetX => HeavyWidget => variableObservable).
If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
""";
}
RxInterface.proxy = _observer;
return result;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,289 @@
part of rx_types;
extension RxStringExt on Rx<String> {
String operator +(String val) => _value + val;
int compareTo(String other) {
return value.compareTo(other);
}
/// Returns true if this string ends with [other]. For example:
///
/// 'Dart'.endsWith('t'); // true
bool endsWith(String other) {
return value.endsWith(other);
}
/// Returns true if this string starts with a match of [pattern].
bool startsWith(Pattern pattern, [int index = 0]) {
return value.startsWith(pattern, index);
}
/// Returns the position of the first match of [pattern] in this string
int indexOf(Pattern pattern, [int start = 0]) {
return value.indexOf(pattern, start);
}
/// Returns the starting position of the last match [pattern] in this string,
/// searching backward starting at [start], inclusive:
int lastIndexOf(Pattern pattern, [int? start]) {
return value.lastIndexOf(pattern, start);
}
/// Returns true if this string is empty.
bool get isEmpty => value.isEmpty;
/// Returns true if this string is not empty.
bool get isNotEmpty => !isEmpty;
/// Returns the substring of this string that extends from [startIndex],
/// inclusive, to [endIndex], exclusive
String substring(int startIndex, [int? endIndex]) {
return value.substring(startIndex, endIndex);
}
/// Returns the string without any leading and trailing whitespace.
String trim() {
return value.trim();
}
/// Returns the string without any leading whitespace.
///
/// As [trim], but only removes leading whitespace.
String trimLeft() {
return value.trimLeft();
}
/// Returns the string without any trailing whitespace.
///
/// As [trim], but only removes trailing whitespace.
String trimRight() {
return value.trimRight();
}
/// Pads this string on the left if it is shorter than [width].
///
/// Return a new string that prepends [padding] onto this string
/// one time for each position the length is less than [width].
String padLeft(int width, [String padding = ' ']) {
return value.padLeft(width, padding);
}
/// Pads this string on the right if it is shorter than [width].
/// Return a new string that appends [padding] after this string
/// one time for each position the length is less than [width].
String padRight(int width, [String padding = ' ']) {
return value.padRight(width, padding);
}
/// Returns true if this string contains a match of [other]:
bool contains(Pattern other, [int startIndex = 0]) {
return value.contains(other, startIndex);
}
/// Replaces all substrings that match [from] with [replace].
String replaceAll(Pattern from, String replace) {
return value.replaceAll(from, replace);
}
/// Splits the string at matches of [pattern] and returns a list
/// of substrings.
List<String> split(Pattern pattern) {
return value.split(pattern);
}
/// Returns an unmodifiable list of the UTF-16 code units of this string.
List<int> get codeUnits => value.codeUnits;
/// Returns an [Iterable] of Unicode code-points of this string.
///
/// If the string contains surrogate pairs, they are combined and returned
/// as one integer by this iterator. Unmatched surrogate halves are treated
/// like valid 16-bit code-units.
Runes get runes => value.runes;
/// Converts all characters in this string to lower case.
/// If the string is already in all lower case, this method returns `this`.
String toLowerCase() {
return value.toLowerCase();
}
/// Converts all characters in this string to upper case.
/// If the string is already in all upper case, this method returns `this`.
String toUpperCase() {
return value.toUpperCase();
}
Iterable<Match> allMatches(String string, [int start = 0]) {
return value.allMatches(string, start);
}
Match? matchAsPrefix(String string, [int start = 0]) {
return value.matchAsPrefix(string, start);
}
}
extension RxnStringExt on Rx<String?> {
String operator +(String val) => (_value ?? '') + val;
int? compareTo(String other) {
return value?.compareTo(other);
}
/// Returns true if this string ends with [other]. For example:
///
/// 'Dart'.endsWith('t'); // true
bool? endsWith(String other) {
return value?.endsWith(other);
}
/// Returns true if this string starts with a match of [pattern].
bool? startsWith(Pattern pattern, [int index = 0]) {
return value?.startsWith(pattern, index);
}
/// Returns the position of the first match of [pattern] in this string
int? indexOf(Pattern pattern, [int start = 0]) {
return value?.indexOf(pattern, start);
}
/// Returns the starting position of the last match [pattern] in this string,
/// searching backward starting at [start], inclusive:
int? lastIndexOf(Pattern pattern, [int? start]) {
return value?.lastIndexOf(pattern, start);
}
/// Returns true if this string is empty.
bool? get isEmpty => value?.isEmpty;
/// Returns true if this string is not empty.
bool? get isNotEmpty => value?.isNotEmpty;
/// Returns the substring of this string that extends from [startIndex],
/// inclusive, to [endIndex], exclusive
String? substring(int startIndex, [int? endIndex]) {
return value?.substring(startIndex, endIndex);
}
/// Returns the string without any leading and trailing whitespace.
String? trim() {
return value?.trim();
}
/// Returns the string without any leading whitespace.
///
/// As [trim], but only removes leading whitespace.
String? trimLeft() {
return value?.trimLeft();
}
/// Returns the string without any trailing whitespace.
///
/// As [trim], but only removes trailing whitespace.
String? trimRight() {
return value?.trimRight();
}
/// Pads this string on the left if it is shorter than [width].
///
/// Return a new string that prepends [padding] onto this string
/// one time for each position the length is less than [width].
String? padLeft(int width, [String padding = ' ']) {
return value?.padLeft(width, padding);
}
/// Pads this string on the right if it is shorter than [width].
/// Return a new string that appends [padding] after this string
/// one time for each position the length is less than [width].
String? padRight(int width, [String padding = ' ']) {
return value?.padRight(width, padding);
}
/// Returns true if this string contains a match of [other]:
bool? contains(Pattern other, [int startIndex = 0]) {
return value?.contains(other, startIndex);
}
/// Replaces all substrings that match [from] with [replace].
String? replaceAll(Pattern from, String replace) {
return value?.replaceAll(from, replace);
}
/// Splits the string at matches of [pattern] and returns a list
/// of substrings.
List<String>? split(Pattern pattern) {
return value?.split(pattern);
}
/// Returns an unmodifiable list of the UTF-16 code units of this string.
List<int>? get codeUnits => value?.codeUnits;
/// Returns an [Iterable] of Unicode code-points of this string.
///
/// If the string contains surrogate pairs, they are combined and returned
/// as one integer by this iterator. Unmatched surrogate halves are treated
/// like valid 16-bit code-units.
Runes? get runes => value?.runes;
/// Converts all characters in this string to lower case.
/// If the string is already in all lower case, this method returns `this`.
String? toLowerCase() {
return value?.toLowerCase();
}
/// Converts all characters in this string to upper case.
/// If the string is already in all upper case, this method returns `this`.
String? toUpperCase() {
return value?.toUpperCase();
}
Iterable<Match>? allMatches(String string, [int start = 0]) {
return value?.allMatches(string, start);
}
Match? matchAsPrefix(String string, [int start = 0]) {
return value?.matchAsPrefix(string, start);
}
}
/// Rx class for `String` Type.
class RxString extends Rx<String> implements Comparable<String>, Pattern {
RxString(String initial) : super(initial);
@override
Iterable<Match> allMatches(String string, [int start = 0]) {
return value.allMatches(string, start);
}
@override
Match? matchAsPrefix(String string, [int start = 0]) {
return value.matchAsPrefix(string, start);
}
@override
int compareTo(String other) {
return value.compareTo(other);
}
}
/// Rx class for `String` Type.
class RxnString extends Rx<String?> implements Comparable<String>, Pattern {
RxnString([String? initial]) : super(initial);
@override
Iterable<Match> allMatches(String string, [int start = 0]) {
return value!.allMatches(string, start);
}
@override
Match? matchAsPrefix(String string, [int start = 0]) {
return value!.matchAsPrefix(string, start);
}
@override
int compareTo(String other) {
return value!.compareTo(other);
}
}

View File

@@ -0,0 +1,172 @@
part of rx_types;
/// Create a list similar to `List<T>`
class RxList<E> extends ListMixin<E>
with NotifyManager<List<E>>, RxObjectMixin<List<E>>
implements RxInterface<List<E>> {
RxList([List<E> initial = const []]) {
_value = List.from(initial);
}
factory RxList.filled(int length, E fill, {bool growable = false}) {
return RxList(List.filled(length, fill, growable: growable));
}
factory RxList.empty({bool growable = false}) {
return RxList(List.empty(growable: growable));
}
/// Creates a list containing all [elements].
factory RxList.from(Iterable elements, {bool growable = true}) {
return RxList(List.from(elements, growable: growable));
}
/// Creates a list from [elements].
factory RxList.of(Iterable<E> elements, {bool growable = true}) {
return RxList(List.of(elements, growable: growable));
}
/// Generates a list of values.
factory RxList.generate(int length, E generator(int index),
{bool growable = true}) {
return RxList(List.generate(length, generator, growable: growable));
}
/// Creates an unmodifiable list containing all [elements].
factory RxList.unmodifiable(Iterable elements) {
return RxList(List.unmodifiable(elements));
}
@override
Iterator<E> get iterator => value.iterator;
@override
void operator []=(int index, E val) {
_value[index] = val;
refresh();
}
/// Special override to push() element(s) in a reactive way
/// inside the List,
@override
RxList<E> operator +(Iterable<E> val) {
addAll(val);
refresh();
return this;
}
@override
E operator [](int index) {
return value[index];
}
@override
void add(E item) {
_value.add(item);
refresh();
}
@override
void addAll(Iterable<E> item) {
_value.addAll(item);
refresh();
}
@override
void removeWhere(bool test(E element)) {
_value.removeWhere(test);
refresh();
}
@override
void retainWhere(bool test(E element)) {
_value.retainWhere(test);
refresh();
}
@override
int get length => value.length;
@override
@protected
List<E> get value {
RxInterface.proxy?.addListener(subject);
return _value;
}
@override
set length(int newLength) {
_value.length = newLength;
refresh();
}
@override
void insertAll(int index, Iterable<E> iterable) {
_value.insertAll(index, iterable);
refresh();
}
@override
Iterable<E> get reversed => value.reversed;
@override
Iterable<E> where(bool Function(E) test) {
return value.where(test);
}
@override
Iterable<T> whereType<T>() {
return value.whereType<T>();
}
@override
void sort([int compare(E a, E b)?]) {
_value.sort(compare);
refresh();
}
}
extension ListExtension<E> on List<E> {
RxList<E> get obs => RxList<E>(this);
/// Add [item] to [List<E>] only if [item] is not null.
void addNonNull(E item) {
if (item != null) add(item);
}
// /// Add [Iterable<E>] to [List<E>] only if [Iterable<E>] is not null.
// void addAllNonNull(Iterable<E> item) {
// if (item != null) addAll(item);
// }
/// Add [item] to List<E> only if [condition] is true.
void addIf(dynamic condition, E item) {
if (condition is Condition) condition = condition();
if (condition is bool && condition) add(item);
}
/// Adds [Iterable<E>] to [List<E>] only if [condition] is true.
void addAllIf(dynamic condition, Iterable<E> items) {
if (condition is Condition) condition = condition();
if (condition is bool && condition) addAll(items);
}
/// Replaces all existing items of this list with [item]
void assign(E item) {
// if (this is RxList) {
// (this as RxList)._value;
// }
clear();
add(item);
}
/// Replaces all existing items of this list with [items]
void assignAll(Iterable<E> items) {
// if (this is RxList) {
// (this as RxList)._value;
// }
clear();
addAll(items);
}
}

View File

@@ -0,0 +1,108 @@
part of rx_types;
class RxMap<K, V> extends MapMixin<K, V>
with NotifyManager<Map<K, V>>, RxObjectMixin<Map<K, V>>
implements RxInterface<Map<K, V>> {
RxMap([Map<K, V> initial = const {}]) {
_value = Map.from(initial);
}
factory RxMap.from(Map<K, V> other) {
return RxMap(Map.from(other));
}
/// Creates a [LinkedHashMap] with the same keys and values as [other].
factory RxMap.of(Map<K, V> other) {
return RxMap(Map.of(other));
}
///Creates an unmodifiable hash based map containing the entries of [other].
factory RxMap.unmodifiable(Map<dynamic, dynamic> other) {
return RxMap(Map.unmodifiable(other));
}
/// Creates an identity map with the default implementation, [LinkedHashMap].
factory RxMap.identity() {
return RxMap(Map.identity());
}
@override
V? operator [](Object? key) {
return value[key as K];
}
@override
void operator []=(K key, V value) {
_value[key] = value;
refresh();
}
@override
void clear() {
_value.clear();
refresh();
}
@override
Iterable<K> get keys => value.keys;
@override
V? remove(Object? key) {
final val = _value.remove(key);
refresh();
return val;
}
@override
@protected
Map<K, V> get value {
RxInterface.proxy?.addListener(subject);
return _value;
}
}
extension MapExtension<K, V> on Map<K, V> {
RxMap<K, V> get obs {
return RxMap<K, V>(this);
}
void addIf(dynamic condition, K key, V value) {
if (condition is Condition) condition = condition();
if (condition is bool && condition) {
this[key] = value;
}
}
void addAllIf(dynamic condition, Map<K, V> values) {
if (condition is Condition) condition = condition();
if (condition is bool && condition) addAll(values);
}
void assign(K key, V val) {
if (this is RxMap) {
final map = (this as RxMap);
// map._value;
map._value.clear();
this[key] = val;
} else {
clear();
this[key] = val;
}
}
void assignAll(Map<K, V> val) {
if (val is RxMap && this is RxMap) {
if ((val as RxMap)._value == (this as RxMap)._value) return;
}
if (this is RxMap) {
final map = (this as RxMap);
if (map._value == val) return;
map._value = val;
map.refresh();
} else {
if (this == val) return;
clear();
addAll(val);
}
}
}

View File

@@ -0,0 +1,151 @@
part of rx_types;
class RxSet<E> extends SetMixin<E>
with NotifyManager<Set<E>>, RxObjectMixin<Set<E>>
implements RxInterface<Set<E>> {
RxSet([Set<E> initial = const {}]) {
_value = Set.from(initial);
}
/// Special override to push() element(s) in a reactive way
/// inside the List,
RxSet<E> operator +(Set<E> val) {
addAll(val);
refresh();
return this;
}
void update(void fn(Iterable<E>? value)) {
fn(value);
refresh();
}
@override
@protected
Set<E> get value {
RxInterface.proxy?.addListener(subject);
return _value;
}
@override
@protected
set value(Set<E> val) {
if (_value == val) return;
_value = val;
refresh();
}
@override
bool add(E value) {
final val = _value.add(value);
refresh();
return val;
}
@override
bool contains(Object? element) {
return value.contains(element);
}
@override
Iterator<E> get iterator => value.iterator;
@override
int get length => value.length;
@override
E? lookup(Object? object) {
return value.lookup(object);
}
@override
bool remove(Object? item) {
var hasRemoved = _value.remove(item);
if (hasRemoved) {
refresh();
}
return hasRemoved;
}
@override
Set<E> toSet() {
return value.toSet();
}
@override
void addAll(Iterable<E> item) {
_value.addAll(item);
refresh();
}
@override
void clear() {
_value.clear();
refresh();
}
@override
void removeAll(Iterable<Object?> elements) {
_value.removeAll(elements);
refresh();
}
@override
void retainAll(Iterable<Object?> elements) {
_value.retainAll(elements);
refresh();
}
@override
void retainWhere(bool Function(E) E) {
_value.retainWhere(E);
refresh();
}
}
extension SetExtension<E> on Set<E> {
RxSet<E> get obs {
return RxSet<E>(<E>{})..addAll(this);
}
// /// Add [item] to [List<E>] only if [item] is not null.
// void addNonNull(E item) {
// if (item != null) add(item);
// }
// /// Add [Iterable<E>] to [List<E>] only if [Iterable<E>] is not null.
// void addAllNonNull(Iterable<E> item) {
// if (item != null) addAll(item);
// }
/// Add [item] to [List<E>] only if [condition] is true.
void addIf(dynamic condition, E item) {
if (condition is Condition) condition = condition();
if (condition is bool && condition) add(item);
}
/// Adds [Iterable<E>] to [List<E>] only if [condition] is true.
void addAllIf(dynamic condition, Iterable<E> items) {
if (condition is Condition) condition = condition();
if (condition is bool && condition) addAll(items);
}
/// Replaces all existing items of this list with [item]
void assign(E item) {
// if (this is RxSet) {
// (this as RxSet)._value;
// }
clear();
add(item);
}
/// Replaces all existing items of this list with [items]
void assignAll(Iterable<E> items) {
// if (this is RxSet) {
// (this as RxSet)._value;
// }
clear();
addAll(items);
}
}

View File

@@ -0,0 +1,17 @@
library rx_types;
import 'dart:async';
import 'dart:collection';
import 'package:flutter/foundation.dart';
import '../rx_stream/rx_stream.dart';
import '../rx_typedefs/rx_typedefs.dart';
part 'rx_core/rx_impl.dart';
part 'rx_core/rx_interface.dart';
part 'rx_core/rx_num.dart';
part 'rx_core/rx_string.dart';
part 'rx_iterables/rx_list.dart';
part 'rx_iterables/rx_set.dart';
part 'rx_iterables/rx_map.dart';

View File

@@ -0,0 +1,273 @@
import 'dart:async';
import '../../../get_core/get_core.dart';
import '../rx_types/rx_types.dart';
import 'utils/debouncer.dart';
bool _conditional(dynamic condition) {
if (condition == null) return true;
if (condition is bool) return condition;
if (condition is bool Function()) return condition();
return true;
}
typedef WorkerCallback<T> = Function(T callback);
class Workers {
Workers(this.workers);
final List<Worker> workers;
void dispose() {
for (final worker in workers) {
if (!worker._disposed) {
worker.dispose();
}
}
}
}
///
/// Called every time [listener] changes. As long as the [condition]
/// returns true.
///
/// Sample:
/// Every time increment() is called, ever() will process the [condition]
/// (can be a [bool] expression or a `bool Function()`), and only call
/// the callback when [condition] is true.
/// In our case, only when count is bigger to 5. In order to "dispose"
/// this Worker
/// that will run forever, we made a `worker` variable. So, when the count value
/// reaches 10, the worker gets disposed, and releases any memory resources.
///
/// ```
/// // imagine some counter widget...
///
/// class _CountController extends GetxController {
/// final count = 0.obs;
/// Worker worker;
///
/// void onInit() {
/// worker = ever(count, (value) {
/// print('counter changed to: $value');
/// if (value == 10) worker.dispose();
/// }, condition: () => count > 5);
/// }
///
/// void increment() => count + 1;
/// }
/// ```
Worker ever<T>(
RxInterface<T> listener,
WorkerCallback<T> callback, {
dynamic condition = true,
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) {
StreamSubscription sub = listener.listen(
(event) {
if (_conditional(condition)) callback(event);
},
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
return Worker(sub.cancel, '[ever]');
}
/// Similar to [ever], but takes a list of [listeners], the condition
/// for the [callback] is common to all [listeners],
/// and the [callback] is executed to each one of them. The [Worker] is
/// common to all, so `worker.dispose()` will cancel all streams.
Worker everAll(
List<RxInterface> listeners,
WorkerCallback callback, {
dynamic condition = true,
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) {
final evers = <StreamSubscription>[];
for (var i in listeners) {
final sub = i.listen(
(event) {
if (_conditional(condition)) callback(event);
},
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
evers.add(sub);
}
Future<void> cancel() {
for (var i in evers) {
i.cancel();
}
return Future.value(() {});
}
return Worker(cancel, '[everAll]');
}
/// `once()` will execute only 1 time when [condition] is met and cancel
/// the subscription to the [listener] stream right after that.
/// [condition] defines when [callback] is called, and
/// can be a [bool] or a `bool Function()`.
///
/// Sample:
/// ```
/// class _CountController extends GetxController {
/// final count = 0.obs;
/// Worker worker;
///
/// @override
/// Future<void> onInit() async {
/// worker = once(count, (value) {
/// print("counter reached $value before 3 seconds.");
/// }, condition: () => count() > 2);
/// 3.delay(worker.dispose);
/// }
/// void increment() => count + 1;
/// }
///```
Worker once<T>(
RxInterface<T> listener,
WorkerCallback<T> callback, {
dynamic condition = true,
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) {
late Worker ref;
StreamSubscription? sub;
sub = listener.listen(
(event) {
if (!_conditional(condition)) return;
ref._disposed = true;
ref._log('called');
sub?.cancel();
callback(event);
},
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
ref = Worker(sub.cancel, '[once]');
return ref;
}
/// Ignore all changes in [listener] during [time] (1 sec by default) or until
/// [condition] is met (can be a [bool] expression or a `bool Function()`),
/// It brings the 1st "value" since the period of time, so
/// if you click a counter button 3 times in 1 sec, it will show you "1"
/// (after 1 sec of the first press)
/// click counter 3 times in 1 sec, it will show you "4" (after 1 sec)
/// click counter 2 times in 1 sec, it will show you "7" (after 1 sec).
///
/// Sample:
/// // wait 1 sec each time an event starts, only if counter is lower than 20.
/// worker = interval(
/// count,
/// (value) => print(value),
/// time: 1.seconds,
/// condition: () => count < 20,
/// );
/// ```
Worker interval<T>(
RxInterface<T> listener,
WorkerCallback<T> callback, {
Duration time = const Duration(seconds: 1),
dynamic condition = true,
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) {
var debounceActive = false;
StreamSubscription sub = listener.listen(
(event) async {
if (debounceActive || !_conditional(condition)) return;
debounceActive = true;
await Future.delayed(time);
debounceActive = false;
callback(event);
},
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
return Worker(sub.cancel, '[interval]');
}
/// [debounce] is similar to [interval], but sends the last value.
/// Useful for Anti DDos, every time the user stops typing for 1 second,
/// for instance.
/// When [listener] emits the last "value", when [time] hits,
/// it calls [callback] with the last "value" emitted.
///
/// Sample:
///
/// ```
/// worker = debounce(
/// count,
/// (value) {
/// print(value);
/// if( value > 20 ) worker.dispose();
/// },
/// time: 1.seconds,
/// );
/// }
/// ```
Worker debounce<T>(
RxInterface<T> listener,
WorkerCallback<T> callback, {
Duration? time,
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) {
final _debouncer =
Debouncer(delay: time ?? const Duration(milliseconds: 800));
StreamSubscription sub = listener.listen(
(event) {
_debouncer(() {
callback(event);
});
},
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
return Worker(sub.cancel, '[debounce]');
}
class Worker {
Worker(this.worker, this.type);
/// subscription.cancel() callback
final Future<void> Function() worker;
/// type of worker (debounce, interval, ever)..
final String type;
bool _disposed = false;
bool get disposed => _disposed;
//final bool _verbose = true;
void _log(String msg) {
// if (!_verbose) return;
Get.log('$runtimeType $type $msg');
}
void dispose() {
if (_disposed) {
_log('already disposed');
return;
}
_disposed = true;
worker();
_log('disposed');
}
void call() => dispose();
}

View File

@@ -0,0 +1,27 @@
import 'dart:async';
/// This "function" class is the implementation of `debouncer()` Worker.
/// It calls the function passed after specified [delay] parameter.
/// Example:
/// ```
/// final delayed = Debouncer( delay: Duration( seconds: 1 )) ;
/// print( 'the next function will be called after 1 sec' );
/// delayed( () => print( 'called after 1 sec' ));
/// ```
class Debouncer {
final Duration? delay;
Timer? _timer;
Debouncer({this.delay});
void call(void Function() action) {
_timer?.cancel();
_timer = Timer(delay!, action);
}
/// Notifies if the delayed call is active.
bool get isRunning => _timer?.isActive ?? false;
/// Cancel the current delayed call.
void cancel() => _timer?.cancel();
}

View File

@@ -0,0 +1,13 @@
library get_state_manager;
export 'src/rx_flutter/rx_disposable.dart';
export 'src/rx_flutter/rx_getx_widget.dart';
export 'src/rx_flutter/rx_notifier.dart';
export 'src/rx_flutter/rx_obx_widget.dart';
export 'src/rx_flutter/rx_ticket_provider_mixin.dart';
export 'src/simple/get_controllers.dart';
export 'src/simple/get_responsive.dart';
export 'src/simple/get_state.dart';
export 'src/simple/get_view.dart';
export 'src/simple/mixin_state.dart';
export 'src/simple/simple_builder.dart';

View File

@@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import '../../../get.dart';
/// Unlike GetxController, which serves to control events on each of its pages,
/// GetxService is not automatically disposed (nor can be removed with
/// Get.delete()).
/// It is ideal for situations where, once started, that service will
/// remain in memory, such as Auth control for example. Only way to remove
/// it is Get.reset().
abstract class GetxService extends DisposableInterface with GetxServiceMixin {}
abstract class DisposableInterface extends GetLifeCycle {
/// Called immediately after the widget is allocated in memory.
/// You might use this to initialize something for the controller.
@override
@mustCallSuper
void onInit() {
super.onInit();
Get.engine.addPostFrameCallback((_) => onReady());
}
/// Called 1 frame after onInit(). It is the perfect place to enter
/// navigation events, like snackbar, dialogs, or a new route, or
/// async request.
@override
void onReady() {
super.onReady();
}
/// Called before [onDelete] method. [onClose] might be used to
/// dispose resources used by the controller. Like closing events,
/// or streams before the controller is destroyed.
/// Or dispose objects that can potentially create some memory leaks,
/// like TextEditingControllers, AnimationControllers.
/// Might be useful as well to persist some data on disk.
@override
void onClose() {
super.onClose();
}
}

View File

@@ -0,0 +1,133 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import '../../../get_core/get_core.dart';
import '../../../get_instance/src/get_instance.dart';
import '../../../get_rx/src/rx_types/rx_types.dart';
import '../../get_state_manager.dart';
typedef GetXControllerBuilder<T extends DisposableInterface> = Widget Function(
T controller);
class GetX<T extends DisposableInterface> extends StatefulWidget {
final GetXControllerBuilder<T> builder;
final bool global;
// final Stream Function(T) stream;
// final StreamController Function(T) streamController;
final bool autoRemove;
final bool assignId;
final void Function(GetXState<T> state)? initState,
dispose,
didChangeDependencies;
final void Function(GetX oldWidget, GetXState<T> state)? didUpdateWidget;
final T? init;
final String? tag;
const GetX({
this.tag,
required this.builder,
this.global = true,
this.autoRemove = true,
this.initState,
this.assignId = false,
// this.stream,
this.dispose,
this.didChangeDependencies,
this.didUpdateWidget,
this.init,
// this.streamController
});
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(
DiagnosticsProperty<T>('controller', init),
)
..add(DiagnosticsProperty<String>('tag', tag))
..add(
ObjectFlagProperty<GetXControllerBuilder<T>>.has('builder', builder));
}
@override
GetXState<T> createState() => GetXState<T>();
}
class GetXState<T extends DisposableInterface> extends State<GetX<T>> {
final _observer = RxNotifier();
T? controller;
bool? _isCreator = false;
late StreamSubscription _subs;
@override
void initState() {
// var isPrepared = GetInstance().isPrepared<T>(tag: widget.tag);
final isRegistered = GetInstance().isRegistered<T>(tag: widget.tag);
if (widget.global) {
if (isRegistered) {
_isCreator = GetInstance().isPrepared<T>(tag: widget.tag);
controller = GetInstance().find<T>(tag: widget.tag);
} else {
controller = widget.init;
_isCreator = true;
GetInstance().put<T>(controller!, tag: widget.tag);
}
} else {
controller = widget.init;
_isCreator = true;
controller?.onStart();
}
widget.initState?.call(this);
if (widget.global && Get.smartManagement == SmartManagement.onlyBuilder) {
controller?.onStart();
}
_subs = _observer.listen((data) => setState(() {}), cancelOnError: false);
super.initState();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (widget.didChangeDependencies != null) {
widget.didChangeDependencies!(this);
}
}
@override
void didUpdateWidget(GetX oldWidget) {
super.didUpdateWidget(oldWidget as GetX<T>);
widget.didUpdateWidget?.call(oldWidget, this);
}
@override
void dispose() {
if (widget.dispose != null) widget.dispose!(this);
if (_isCreator! || widget.assignId) {
if (widget.autoRemove && GetInstance().isRegistered<T>(tag: widget.tag)) {
GetInstance().delete<T>(tag: widget.tag);
}
}
_subs.cancel();
_observer.close();
controller = null;
_isCreator = null;
super.dispose();
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<T>('controller', controller));
}
@override
Widget build(BuildContext context) => RxInterface.notifyChildren(
_observer,
() => widget.builder(controller!),
);
}

View File

@@ -0,0 +1,197 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import '../../../instance_manager.dart';
import '../../get_state_manager.dart';
import '../simple/list_notifier.dart';
mixin StateMixin<T> on ListNotifierMixin {
T? _value;
RxStatus? _status;
bool _isNullOrEmpty(dynamic val) {
if (val == null) return true;
var result = false;
if (val is Iterable) {
result = val.isEmpty;
} else if (val is String) {
result = val.isEmpty;
} else if (val is Map) {
result = val.isEmpty;
}
return result;
}
void _fillEmptyStatus() {
_status = _isNullOrEmpty(_value) ? RxStatus.loading() : RxStatus.success();
}
RxStatus get status {
notifyChildrens();
return _status ??= _status = RxStatus.loading();
}
T? get state => value;
@protected
T? get value {
notifyChildrens();
return _value;
}
@protected
set value(T? newValue) {
if (_value == newValue) return;
_value = newValue;
refresh();
}
@protected
void change(T? newState, {RxStatus? status}) {
var _canUpdate = false;
if (status != null) {
_status = status;
_canUpdate = true;
}
if (newState != _value) {
_value = newState;
_canUpdate = true;
}
if (_canUpdate) {
refresh();
}
}
void append(Future<T> Function() body(), {String? errorMessage}) {
final compute = body();
compute().then((newValue) {
change(newValue, status: RxStatus.success());
}, onError: (err) {
change(state, status: RxStatus.error(errorMessage ?? err.toString()));
});
}
}
class Value<T> extends ListNotifier
with StateMixin<T>
implements ValueListenable<T?> {
Value(T val) {
_value = val;
_fillEmptyStatus();
}
@override
T? get value {
notifyChildrens();
return _value;
}
@override
set value(T? newValue) {
if (_value == newValue) return;
_value = newValue;
refresh();
}
T? call([T? v]) {
if (v != null) {
value = v;
}
return value;
}
void update(void fn(T? value)) {
fn(value);
refresh();
}
@override
String toString() => value.toString();
dynamic toJson() => (value as dynamic)?.toJson();
}
extension ReactiveT<T> on T {
Value<T> get reactive => Value<T>(this);
}
typedef Condition = bool Function();
abstract class GetNotifier<T> extends Value<T> with GetLifeCycleBase {
GetNotifier(T initial) : super(initial) {
$configureLifeCycle();
}
@override
@mustCallSuper
void onInit() {
super.onInit();
ambiguate(SchedulerBinding.instance)
?.addPostFrameCallback((_) => onReady());
}
}
extension StateExt<T> on StateMixin<T> {
Widget obx(
NotifierBuilder<T?> widget, {
Widget Function(String? error)? onError,
Widget? onLoading,
Widget? onEmpty,
}) {
return SimpleBuilder(builder: (_) {
if (status.isLoading) {
return onLoading ?? const Center(child: CircularProgressIndicator());
} else if (status.isError) {
return onError != null
? onError(status.errorMessage)
: Center(child: Text('A error occurred: ${status.errorMessage}'));
} else if (status.isEmpty) {
return onEmpty != null
? onEmpty
: SizedBox.shrink(); // Also can be widget(null); but is risky
}
return widget(value);
});
}
}
class RxStatus {
final bool isLoading;
final bool isError;
final bool isSuccess;
final bool isEmpty;
final bool isLoadingMore;
final String? errorMessage;
RxStatus._({
this.isEmpty = false,
this.isLoading = false,
this.isError = false,
this.isSuccess = false,
this.errorMessage,
this.isLoadingMore = false,
});
factory RxStatus.loading() {
return RxStatus._(isLoading: true);
}
factory RxStatus.loadingMore() {
return RxStatus._(isSuccess: true, isLoadingMore: true);
}
factory RxStatus.success() {
return RxStatus._(isSuccess: true);
}
factory RxStatus.error([String? message]) {
return RxStatus._(isError: true, errorMessage: message);
}
factory RxStatus.empty() {
return RxStatus._(isEmpty: true);
}
}
typedef NotifierBuilder<T> = Widget Function(T state);

View File

@@ -0,0 +1,92 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import '../../../get_rx/src/rx_types/rx_types.dart';
typedef WidgetCallback = Widget Function();
/// The [ObxWidget] is the base for all GetX reactive widgets
///
/// See also:
/// - [Obx]
/// - [ObxValue]
abstract class ObxWidget extends StatefulWidget {
const ObxWidget({Key? key}) : super(key: key);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties..add(ObjectFlagProperty<Function>.has('builder', build));
}
@override
_ObxState createState() => _ObxState();
@protected
Widget build();
}
class _ObxState extends State<ObxWidget> {
final _observer = RxNotifier();
late StreamSubscription subs;
@override
void initState() {
super.initState();
subs = _observer.listen(_updateTree, cancelOnError: false);
}
void _updateTree(_) {
if (mounted) {
setState(() {});
}
}
@override
void dispose() {
subs.cancel();
_observer.close();
super.dispose();
}
@override
Widget build(BuildContext context) =>
RxInterface.notifyChildren(_observer, widget.build);
}
/// The simplest reactive widget in GetX.
///
/// Just pass your Rx variable in the root scope of the callback to have it
/// automatically registered for changes.
///
/// final _name = "GetX".obs;
/// Obx(() => Text( _name.value )),... ;
class Obx extends ObxWidget {
final WidgetCallback builder;
const Obx(this.builder);
@override
Widget build() => builder();
}
/// Similar to Obx, but manages a local state.
/// Pass the initial data in constructor.
/// Useful for simple local states, like toggles, visibility, themes,
/// button states, etc.
/// Sample:
/// ObxValue((data) => Switch(
/// value: data.value,
/// onChanged: (flag) => data.value = flag,
/// ),
/// false.obs,
/// ),
class ObxValue<T extends RxInterface> extends ObxWidget {
final Widget Function(T) builder;
final T data;
const ObxValue(this.builder, this.data, {Key? key}) : super(key: key);
@override
Widget build() => builder(data);
}

View File

@@ -0,0 +1,203 @@
// 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.
/// Simplifies AnimationController creation inside GetxController.
///
/// 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 GetSingleTickerProviderStateMixin but multiple tickers were created.'),
ErrorDescription(
'A GetSingleTickerProviderStateMixin 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 GetSingleTickerProviderStateMixin, use a regular GetTickerProviderStateMixin.',
),
]);
}());
_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 GetSingleTickerProviderStateMixin, 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();
}
}
/// Used like `TickerProviderMixin` but only with Get Controllers.
/// Simplifies multiple AnimationController creation inside GetxController.
///
/// Example:
///```
///class SplashController extends GetxController with
/// GetTickerProviderStateMixin {
/// AnimationController first_controller;
/// AnimationController second_controller;
///
/// @override
/// void onInit() {
/// final duration = const Duration(seconds: 2);
/// first_controller =
/// AnimationController.unbounded(duration: duration, vsync: this);
/// second_controller =
/// AnimationController.unbounded(duration: duration, vsync: this);
/// first_controller.repeat();
/// first_controller.addListener(() =>
/// print("Animation Controller value: ${first_controller.value}"));
/// second_controller.addListener(() =>
/// print("Animation Controller value: ${second_controller.value}"));
/// }
/// ...
/// ```
mixin GetTickerProviderStateMixin on GetxController implements TickerProvider {
Set<Ticker>? _tickers;
@override
Ticker createTicker(TickerCallback onTick) {
_tickers ??= <_WidgetTicker>{};
final result = _WidgetTicker(onTick, this,
debugLabel: kDebugMode ? 'created by ${describeIdentity(this)}' : null);
_tickers!.add(result);
return result;
}
void _removeTicker(_WidgetTicker ticker) {
assert(_tickers != null);
assert(_tickers!.contains(ticker));
_tickers!.remove(ticker);
}
void didChangeDependencies(BuildContext context) {
final muted = !TickerMode.of(context);
if (_tickers != null) {
for (final ticker in _tickers!) {
ticker.muted = muted;
}
}
}
@override
void onClose() {
assert(() {
if (_tickers != null) {
for (final ticker in _tickers!) {
if (ticker.isActive) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('$this was disposed with an active Ticker.'),
ErrorDescription(
'$runtimeType created a Ticker via its GetTickerProviderStateMixin, but at the time '
'dispose() was called on the mixin, that Ticker was still active. All Tickers 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'),
]);
}
}
}
return true;
}());
super.onClose();
}
}
class _WidgetTicker extends Ticker {
_WidgetTicker(TickerCallback onTick, this._creator, {String? debugLabel})
: super(onTick, debugLabel: debugLabel);
final GetTickerProviderStateMixin _creator;
@override
void dispose() {
_creator._removeTicker(this);
super.dispose();
}
}
@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;
///
/// @override
/// void onInit() {
/// final dur = const Duration(seconds: 2);
/// _ac = AnimationController.unbounded(duration: dur, vsync: this);
/// _ac.repeat();
/// _ac.addListener(() => print("Animation Controller value: ${_ac.value}"));
/// }
/// ...
/// ```
mixin SingleGetTickerProviderMixin on DisposableInterface
implements TickerProvider {
@override
Ticker createTicker(TickerCallback onTick) => Ticker(onTick);
}

View File

@@ -0,0 +1,123 @@
// ignore: prefer_mixin
import 'package:flutter/widgets.dart';
import '../../../instance_manager.dart';
import '../rx_flutter/rx_disposable.dart';
import '../rx_flutter/rx_notifier.dart';
import 'list_notifier.dart';
// ignore: prefer_mixin
abstract class GetxController extends DisposableInterface
with ListenableMixin, ListNotifierMixin {
/// Rebuilds `GetBuilder` each time you call `update()`;
/// Can take a List of [ids], that will only update the matching
/// `GetBuilder( id: )`,
/// [ids] can be reused among `GetBuilders` like group tags.
/// The update will only notify the Widgets, if [condition] is true.
void update([List<Object>? ids, bool condition = true]) {
if (!condition) {
return;
}
if (ids == null) {
refresh();
} else {
for (final id in ids) {
refreshGroup(id);
}
}
}
}
mixin ScrollMixin on GetLifeCycleBase {
final ScrollController scroll = ScrollController();
@override
void onInit() {
super.onInit();
scroll.addListener(_listener);
}
bool _canFetchBottom = true;
bool _canFetchTop = true;
void _listener() {
if (scroll.position.atEdge) {
_checkIfCanLoadMore();
}
}
Future<void> _checkIfCanLoadMore() async {
if (scroll.position.pixels == 0) {
if (!_canFetchTop) return;
_canFetchTop = false;
await onTopScroll();
_canFetchTop = true;
} else {
if (!_canFetchBottom) return;
_canFetchBottom = false;
await onEndScroll();
_canFetchBottom = true;
}
}
Future<void> onEndScroll();
Future<void> onTopScroll();
@override
void onClose() {
scroll.removeListener(_listener);
super.onClose();
}
}
abstract class RxController extends DisposableInterface {}
abstract class SuperController<T> extends FullLifeCycleController
with FullLifeCycleMixin, StateMixin<T> {}
abstract class FullLifeCycleController extends GetxController
with
// ignore: prefer_mixin
WidgetsBindingObserver {}
mixin FullLifeCycleMixin on FullLifeCycleController {
@mustCallSuper
@override
void onInit() {
super.onInit();
ambiguate(WidgetsBinding.instance)?.addObserver(this);
}
@mustCallSuper
@override
void onClose() {
ambiguate(WidgetsBinding.instance)?.removeObserver(this);
super.onClose();
}
@mustCallSuper
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.resumed:
onResumed();
break;
case AppLifecycleState.inactive:
onInactive();
break;
case AppLifecycleState.paused:
onPaused();
break;
case AppLifecycleState.detached:
onDetached();
break;
}
}
void onResumed();
void onPaused();
void onInactive();
void onDetached();
}

View File

@@ -0,0 +1,172 @@
import 'package:flutter/widgets.dart';
import '../../../get.dart';
mixin GetResponsiveMixin on Widget {
ResponsiveScreen get screen;
bool get alwaysUseBuilder;
@protected
Widget build(BuildContext context) {
screen.context = context;
Widget? widget;
if (alwaysUseBuilder) {
widget = builder();
if (widget != null) return widget;
}
if (screen.isDesktop) {
widget = desktop() ?? widget;
if (widget != null) return widget;
}
if (screen.isTablet) {
widget = tablet() ?? desktop();
if (widget != null) return widget;
}
if (screen.isPhone) {
widget = phone() ?? tablet() ?? desktop();
if (widget != null) return widget;
}
return watch() ?? phone() ?? tablet() ?? desktop() ?? builder()!;
}
Widget? builder() => null;
Widget? desktop() => null;
Widget? phone() => null;
Widget? tablet() => null;
Widget? watch() => null;
}
/// Extend this widget to build responsive view.
/// this widget contains the `screen` property that have all
/// information about the screen size and type.
/// You have two options to build it.
/// 1- with `builder` method you return the widget to build.
/// 2- with methods `desktop`, `tablet`,`phone`, `watch`. the specific
/// method will be built when the screen type matches the method
/// when the screen is [ScreenType.Tablet] the `tablet` method
/// will be exuded and so on.
/// Note if you use this method please set the
/// property `alwaysUseBuilder` to false
/// With `settings` property you can set the width limit for the screen types.
class GetResponsiveView<T> extends GetView<T> with GetResponsiveMixin {
@override
final bool alwaysUseBuilder;
@override
final ResponsiveScreen screen;
GetResponsiveView({
this.alwaysUseBuilder = false,
ResponsiveScreenSettings settings = const ResponsiveScreenSettings(),
Key? key,
}) : screen = ResponsiveScreen(settings),
super(key: key);
}
class GetResponsiveWidget<T extends GetLifeCycleBase?> extends GetWidget<T>
with GetResponsiveMixin {
@override
final bool alwaysUseBuilder;
@override
final ResponsiveScreen screen;
GetResponsiveWidget({
this.alwaysUseBuilder = false,
ResponsiveScreenSettings settings = const ResponsiveScreenSettings(),
Key? key,
}) : screen = ResponsiveScreen(settings),
super(key: key);
}
class ResponsiveScreenSettings {
/// When the width is greater als this value
/// the display will be set as [ScreenType.Desktop]
final double desktopChangePoint;
/// When the width is greater als this value
/// the display will be set as [ScreenType.Tablet]
/// or when width greater als [watchChangePoint] and smaller als this value
/// the display will be [ScreenType.Phone]
final double tabletChangePoint;
/// When the width is smaller als this value
/// the display will be set as [ScreenType.Watch]
/// or when width greater als this value and smaller als [tabletChangePoint]
/// the display will be [ScreenType.Phone]
final double watchChangePoint;
const ResponsiveScreenSettings(
{this.desktopChangePoint = 1200,
this.tabletChangePoint = 600,
this.watchChangePoint = 300});
}
class ResponsiveScreen {
late BuildContext context;
final ResponsiveScreenSettings settings;
late bool _isPaltformDesktop;
ResponsiveScreen(this.settings) {
_isPaltformDesktop = GetPlatform.isDesktop;
}
double get height => context.height;
double get width => context.width;
/// Is [screenType] [ScreenType.Desktop]
bool get isDesktop => (screenType == ScreenType.Desktop);
/// Is [screenType] [ScreenType.Tablet]
bool get isTablet => (screenType == ScreenType.Tablet);
/// Is [screenType] [ScreenType.Phone]
bool get isPhone => (screenType == ScreenType.Phone);
/// Is [screenType] [ScreenType.Watch]
bool get isWatch => (screenType == ScreenType.Watch);
double get _getdeviceWidth {
if (_isPaltformDesktop) {
return width;
}
return context.mediaQueryShortestSide;
}
ScreenType get screenType {
final deviceWidth = _getdeviceWidth;
if (deviceWidth >= settings.desktopChangePoint) return ScreenType.Desktop;
if (deviceWidth >= settings.tabletChangePoint) return ScreenType.Tablet;
if (deviceWidth < settings.watchChangePoint) return ScreenType.Watch;
return ScreenType.Phone;
}
/// Return widget according to screen type
/// if the [screenType] is [ScreenType.Desktop] and
/// `desktop` object is null the `tablet` object will be returned
/// and if `tablet` object is null the `mobile` object will be returned
/// and if `mobile` object is null the `watch` object will be returned
/// also when it is null.
T? responsiveValue<T>({
T? mobile,
T? tablet,
T? desktop,
T? watch,
}) {
if (isDesktop && desktop != null) return desktop;
if (isTablet && tablet != null) return tablet;
if (isPhone && mobile != null) return mobile;
return watch;
}
}
enum ScreenType {
Watch,
Phone,
Tablet,
Desktop,
}

View File

@@ -0,0 +1,226 @@
import 'package:flutter/material.dart';
import '../../../get_instance/src/get_instance.dart';
import '../../../instance_manager.dart';
import '../../get_state_manager.dart';
import 'list_notifier.dart';
/// Complies with `GetStateUpdater`
///
/// This mixin's function represents a `GetStateUpdater`, and might be used
/// by `GetBuilder()`, `SimpleBuilder()` (or similar) to comply
/// with [GetStateUpdate] signature. REPLACING the [StateSetter].
/// Avoids the potential (but extremely unlikely) issue of having
/// the Widget in a dispose() state, and abstracts the
/// API from the ugly fn((){}).
mixin GetStateUpdaterMixin<T extends StatefulWidget> on State<T> {
// To avoid the creation of an anonym function to be GC later.
// ignore: prefer_function_declarations_over_variables
/// Experimental method to replace setState((){});
/// Used with GetStateUpdate.
void getUpdate() {
if (mounted) setState(() {});
}
}
typedef GetControllerBuilder<T extends DisposableInterface> = Widget Function(
T controller);
// class _InheritedGetxController<T extends GetxController>
// extends InheritedWidget {
// final T model;
// final int version;
// _InheritedGetxController({
// Key key,
// @required Widget child,
// @required this.model,
// }) : version = model.notifierVersion,
// super(key: key, child: child);
// @override
// bool updateShouldNotify(_InheritedGetxController<T> oldWidget) =>
// (oldWidget.version != version);
// }
// extension WatchEtx on GetxController {
// T watch<T extends GetxController>() {
// final instance = Get.find<T>();
// _GetBuilderState._currentState.watch(instance.update);
// return instance;
// }
// }
class GetBuilder<T extends GetxController> extends StatefulWidget {
final GetControllerBuilder<T> builder;
final bool global;
final Object? id;
final String? tag;
final bool autoRemove;
final bool assignId;
final Object Function(T value)? filter;
final void Function(GetBuilderState<T> state)? initState,
dispose,
didChangeDependencies;
final void Function(GetBuilder oldWidget, GetBuilderState<T> state)?
didUpdateWidget;
final T? init;
const GetBuilder({
Key? key,
this.init,
this.global = true,
required this.builder,
this.autoRemove = true,
this.assignId = false,
this.initState,
this.filter,
this.tag,
this.dispose,
this.id,
this.didChangeDependencies,
this.didUpdateWidget,
}) : super(key: key);
// static T of<T extends GetxController>(
// BuildContext context, {
// bool rebuild = false,
// }) {
// var widget = rebuild
// ? context
// .dependOnInheritedWidgetOfExactType<_InheritedGetxController<T>>()
// : context
// .getElementForInheritedWidgetOfExactType<
// _InheritedGetxController<T>>()
// ?.widget;
// if (widget == null) {
// throw 'Error: Could not find the correct dependency.';
// } else {
// return (widget as _InheritedGetxController<T>).model;
// }
// }
@override
GetBuilderState<T> createState() => GetBuilderState<T>();
}
class GetBuilderState<T extends GetxController> extends State<GetBuilder<T>>
with GetStateUpdaterMixin {
T? controller;
bool? _isCreator = false;
VoidCallback? _remove;
Object? _filter;
@override
void initState() {
// _GetBuilderState._currentState = this;
super.initState();
widget.initState?.call(this);
var isRegistered = GetInstance().isRegistered<T>(tag: widget.tag);
if (widget.global) {
if (isRegistered) {
if (GetInstance().isPrepared<T>(tag: widget.tag)) {
_isCreator = true;
} else {
_isCreator = false;
}
controller = GetInstance().find<T>(tag: widget.tag);
} else {
controller = widget.init;
_isCreator = true;
GetInstance().put<T>(controller!, tag: widget.tag);
}
} else {
controller = widget.init;
_isCreator = true;
controller?.onStart();
}
if (widget.filter != null) {
_filter = widget.filter!(controller!);
}
_subscribeToController();
}
/// Register to listen Controller's events.
/// It gets a reference to the remove() callback, to delete the
/// setState "link" from the Controller.
void _subscribeToController() {
_remove?.call();
_remove = (widget.id == null)
? controller?.addListener(
_filter != null ? _filterUpdate : getUpdate,
)
: controller?.addListenerId(
widget.id,
_filter != null ? _filterUpdate : getUpdate,
);
}
void _filterUpdate() {
var newFilter = widget.filter!(controller!);
if (newFilter != _filter) {
_filter = newFilter;
getUpdate();
}
}
@override
void dispose() {
super.dispose();
widget.dispose?.call(this);
if (_isCreator! || widget.assignId) {
if (widget.autoRemove && GetInstance().isRegistered<T>(tag: widget.tag)) {
GetInstance().delete<T>(tag: widget.tag);
}
}
_remove?.call();
controller = null;
_isCreator = null;
_remove = null;
_filter = null;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
widget.didChangeDependencies?.call(this);
}
@override
void didUpdateWidget(GetBuilder oldWidget) {
super.didUpdateWidget(oldWidget as GetBuilder<T>);
// to avoid conflicts when modifying a "grouped" id list.
if (oldWidget.id != widget.id) {
_subscribeToController();
}
widget.didUpdateWidget?.call(oldWidget, this);
}
@override
Widget build(BuildContext context) {
// return _InheritedGetxController<T>(
// model: controller,
// child: widget.builder(controller),
// );
return widget.builder(controller!);
}
}
// extension FindExt on BuildContext {
// T find<T extends GetxController>() {
// return GetBuilder.of<T>(this, rebuild: false);
// }
// }
// extension ObserverEtx on BuildContext {
// T obs<T extends GetxController>() {
// return GetBuilder.of<T>(this, rebuild: true);
// }
// }

View File

@@ -0,0 +1,105 @@
import 'package:flutter/widgets.dart';
import '../../../instance_manager.dart';
import '../../../utils.dart';
import 'get_widget_cache.dart';
/// GetView is a great way of quickly access your Controller
/// without having to call Get.find<AwesomeController>() yourself.
///
/// Sample:
/// ```
/// class AwesomeController extends GetxController {
/// final String title = 'My Awesome View';
/// }
///
/// class AwesomeView extends GetView<AwesomeController> {
/// /// if you need you can pass the tag for
/// /// Get.find<AwesomeController>(tag:"myTag");
/// @override
/// final String tag = "myTag";
///
/// AwesomeView({Key key}):super(key:key);
///
/// @override
/// Widget build(BuildContext context) {
/// return Container(
/// padding: EdgeInsets.all(20),
/// child: Text( controller.title ),
/// );
/// }
/// }
///``
abstract class GetView<T> extends StatelessWidget {
const GetView({Key? key}) : super(key: key);
final String? tag = null;
T get controller => GetInstance().find<T>(tag: tag)!;
@override
Widget build(BuildContext context);
}
/// GetWidget is a great way of quickly access your individual Controller
/// without having to call Get.find<AwesomeController>() yourself.
/// Get save you controller on cache, so, you can to use Get.create() safely
/// GetWidget is perfect to multiples instance of a same controller. Each
/// GetWidget will have your own controller, and will be call events as `onInit`
/// and `onClose` when the controller get in/get out on memory.
abstract class GetWidget<S extends GetLifeCycleBase?> extends GetWidgetCache {
const GetWidget({Key? key}) : super(key: key);
@protected
final String? tag = null;
S get controller => GetWidget._cache[this] as S;
// static final _cache = <GetWidget, GetLifeCycleBase>{};
static final _cache = Expando<GetLifeCycleBase>();
@protected
Widget build(BuildContext context);
@override
WidgetCache createWidgetCache() => _GetCache<S>();
}
class _GetCache<S extends GetLifeCycleBase?> extends WidgetCache<GetWidget<S>> {
S? _controller;
bool _isCreator = false;
InstanceInfo? info;
@override
void onInit() {
info = GetInstance().getInstanceInfo<S>(tag: widget!.tag);
_isCreator = info!.isPrepared && info!.isCreate;
if (info!.isRegistered) {
_controller = Get.find<S>(tag: widget!.tag);
}
GetWidget._cache[widget!] = _controller;
super.onInit();
}
@override
void onClose() {
if (_isCreator) {
Get.asap(() {
widget!.controller!.onDelete();
Get.log('"${widget!.controller.runtimeType}" onClose() called');
Get.log('"${widget!.controller.runtimeType}" deleted from memory');
GetWidget._cache[widget!] = null;
});
}
info = null;
super.onClose();
}
@override
Widget build(BuildContext context) {
return widget!.build(context);
}
}

View File

@@ -0,0 +1,71 @@
import 'package:flutter/widgets.dart';
abstract class GetWidgetCache extends Widget {
const GetWidgetCache({Key? key}) : super(key: key);
@override
GetWidgetCacheElement createElement() => GetWidgetCacheElement(this);
@protected
@factory
WidgetCache createWidgetCache();
}
class GetWidgetCacheElement extends ComponentElement {
GetWidgetCacheElement(GetWidgetCache widget)
: cache = widget.createWidgetCache(),
super(widget) {
cache._element = this;
cache._widget = widget;
}
@override
void mount(Element? parent, dynamic newSlot) {
cache.onInit();
super.mount(parent, newSlot);
}
@override
Widget build() => cache.build(this);
final WidgetCache<GetWidgetCache> cache;
@override
void performRebuild() {
super.performRebuild();
}
@override
void activate() {
super.activate();
markNeedsBuild();
}
@override
void unmount() {
super.unmount();
cache.onClose();
cache._element = null;
}
}
@optionalTypeArgs
abstract class WidgetCache<T extends GetWidgetCache> {
T? get widget => _widget;
T? _widget;
BuildContext? get context => _element;
GetWidgetCacheElement? _element;
@protected
@mustCallSuper
void onInit() {}
@protected
@mustCallSuper
void onClose() {}
@protected
Widget build(BuildContext context);
}

View File

@@ -0,0 +1,175 @@
import 'dart:collection';
import 'package:flutter/widgets.dart';
// This callback remove the listener on addListener function
typedef Disposer = void Function();
// replacing StateSetter, return if the Widget is mounted for extra validation.
// if it brings overhead the extra call,
typedef GetStateUpdate = void Function();
class ListNotifier extends Listenable with ListenableMixin, ListNotifierMixin {}
mixin ListenableMixin implements Listenable {}
mixin ListNotifierMixin on ListenableMixin {
// int _version = 0;
// int _microtask = 0;
// int get notifierVersion => _version;
// int get notifierMicrotask => _microtask;
List<GetStateUpdate?>? _updaters = <GetStateUpdate?>[];
HashMap<Object?, List<GetStateUpdate>>? _updatersGroupIds =
HashMap<Object?, List<GetStateUpdate>>();
@protected
void refresh() {
assert(_debugAssertNotDisposed());
/// This debounce the call to update.
/// It prevent errors and duplicates builds
// if (_microtask == _version) {
// _microtask++;
// scheduleMicrotask(() {
// _version++;
// _microtask = _version;
_notifyUpdate();
// });
// }
}
void _notifyUpdate() {
for (var element in _updaters!) {
element!();
}
}
void _notifyIdUpdate(Object id) {
if (_updatersGroupIds!.containsKey(id)) {
final listGroup = _updatersGroupIds![id]!;
for (var item in listGroup) {
item();
}
}
}
@protected
void refreshGroup(Object id) {
assert(_debugAssertNotDisposed());
// /// This debounce the call to update.
// /// It prevent errors and duplicates builds
// if (_microtask == _version) {
// _microtask++;
// scheduleMicrotask(() {
// _version++;
// _microtask = _version;
_notifyIdUpdate(id);
// });
// }
}
bool _debugAssertNotDisposed() {
assert(() {
if (_updaters == null) {
throw FlutterError('''A $runtimeType was used after being disposed.\n
'Once you have called dispose() on a $runtimeType, it can no longer be used.''');
}
return true;
}());
return true;
}
@protected
void notifyChildrens() {
TaskManager.instance.notify(_updaters);
}
bool get hasListeners {
assert(_debugAssertNotDisposed());
return _updaters!.isNotEmpty;
}
int get listeners {
assert(_debugAssertNotDisposed());
return _updaters!.length;
}
@override
void removeListener(VoidCallback listener) {
assert(_debugAssertNotDisposed());
_updaters!.remove(listener);
}
void removeListenerId(Object id, VoidCallback listener) {
assert(_debugAssertNotDisposed());
if (_updatersGroupIds!.containsKey(id)) {
_updatersGroupIds![id]!.remove(listener);
}
_updaters!.remove(listener);
}
@mustCallSuper
void dispose() {
assert(_debugAssertNotDisposed());
_updaters = null;
_updatersGroupIds = null;
}
@override
Disposer addListener(GetStateUpdate listener) {
assert(_debugAssertNotDisposed());
_updaters!.add(listener);
return () => _updaters!.remove(listener);
}
Disposer addListenerId(Object? key, GetStateUpdate listener) {
_updatersGroupIds![key] ??= <GetStateUpdate>[];
_updatersGroupIds![key]!.add(listener);
return () => _updatersGroupIds![key]!.remove(listener);
}
/// To dispose an [id] from future updates(), this ids are registered
/// by `GetBuilder()` or similar, so is a way to unlink the state change with
/// the Widget from the Controller.
void disposeId(Object id) {
_updatersGroupIds!.remove(id);
}
}
class TaskManager {
TaskManager._();
static TaskManager? _instance;
static TaskManager get instance => _instance ??= TaskManager._();
GetStateUpdate? _setter;
List<VoidCallback>? _remove;
void notify(List<GetStateUpdate?>? _updaters) {
if (_setter != null) {
if (!_updaters!.contains(_setter)) {
_updaters.add(_setter);
_remove!.add(() => _updaters.remove(_setter));
}
}
}
Widget exchange(
List<VoidCallback> disposers,
GetStateUpdate setState,
Widget Function(BuildContext) builder,
BuildContext context,
) {
_remove = disposers;
_setter = setState;
final result = builder(context);
_remove = null;
_setter = null;
return result;
}
}

View File

@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import '../../get_state_manager.dart';
class MixinBuilder<T extends GetxController> extends StatelessWidget {
@required
final Widget Function(T) builder;
final bool global;
final String? id;
final bool autoRemove;
final void Function(State state)? initState, dispose, didChangeDependencies;
final void Function(GetBuilder oldWidget, State state)? didUpdateWidget;
final T? init;
const MixinBuilder({
Key? key,
this.init,
this.global = true,
required this.builder,
this.autoRemove = true,
this.initState,
this.dispose,
this.id,
this.didChangeDependencies,
this.didUpdateWidget,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetBuilder<T>(
init: init,
global: global,
autoRemove: autoRemove,
initState: initState,
dispose: dispose,
id: id,
didChangeDependencies: didChangeDependencies,
didUpdateWidget: didUpdateWidget,
builder: (controller) => Obx(() => builder.call(controller)));
}
}

View File

@@ -0,0 +1,108 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'get_state.dart';
import 'list_notifier.dart';
typedef ValueBuilderUpdateCallback<T> = void Function(T snapshot);
typedef ValueBuilderBuilder<T> = Widget Function(
T snapshot, ValueBuilderUpdateCallback<T> updater);
/// Manages a local state like ObxValue, but uses a callback instead of
/// a Rx value.
///
/// Example:
/// ```
/// ValueBuilder<bool>(
/// initialValue: false,
/// builder: (value, update) => Switch(
/// value: value,
/// onChanged: (flag) {
/// update( flag );
/// },),
/// onUpdate: (value) => print("Value updated: $value"),
/// ),
/// ```
class ValueBuilder<T> extends StatefulWidget {
final T? initialValue;
final ValueBuilderBuilder<T> builder;
final void Function()? onDispose;
final void Function(T)? onUpdate;
const ValueBuilder({
Key? key,
this.initialValue,
this.onDispose,
this.onUpdate,
required this.builder,
}) : super(key: key);
@override
_ValueBuilderState<T> createState() => _ValueBuilderState<T>();
}
class _ValueBuilderState<T> extends State<ValueBuilder<T?>> {
T? value;
@override
void initState() {
super.initState();
value = widget.initialValue;
}
@override
Widget build(BuildContext context) => widget.builder(value, updater);
void updater(T? newValue) {
if (widget.onUpdate != null) {
widget.onUpdate!(newValue);
}
setState(() {
value = newValue;
});
}
@override
void dispose() {
super.dispose();
widget.onDispose?.call();
if (value is ChangeNotifier) {
(value as ChangeNotifier?)?.dispose();
} else if (value is StreamController) {
(value as StreamController?)?.close();
}
value = null;
}
}
// It's a experimental feature
class SimpleBuilder extends StatefulWidget {
final Widget Function(BuildContext) builder;
const SimpleBuilder({Key? key, required this.builder}) : super(key: key);
@override
_SimpleBuilderState createState() => _SimpleBuilderState();
}
class _SimpleBuilderState extends State<SimpleBuilder>
with GetStateUpdaterMixin {
final disposers = <Disposer>[];
@override
void dispose() {
super.dispose();
for (final disposer in disposers) {
disposer();
}
}
@override
Widget build(BuildContext context) {
return TaskManager.instance.exchange(
disposers,
getUpdate,
widget.builder,
context,
);
}
}

View File

@@ -0,0 +1,4 @@
export 'src/extensions/export.dart';
export 'src/get_utils/get_utils.dart';
export 'src/platform/platform.dart';
export 'src/queue/get_queue.dart';

View File

@@ -0,0 +1,141 @@
import 'package:flutter/material.dart';
import '../platform/platform.dart';
extension ContextExtensionss on BuildContext {
/// The same of [MediaQuery.of(context).size]
Size get mediaQuerySize => MediaQuery.of(this).size;
/// The same of [MediaQuery.of(context).size.height]
/// Note: updates when you rezise your screen (like on a browser or
/// desktop window)
double get height => mediaQuerySize.height;
/// The same of [MediaQuery.of(context).size.width]
/// Note: updates when you rezise your screen (like on a browser or
/// desktop window)
double get width => mediaQuerySize.width;
/// Gives you the power to get a portion of the height.
/// Useful for responsive applications.
///
/// [dividedBy] is for when you want to have a portion of the value you
/// would get like for example: if you want a value that represents a third
/// of the screen you can set it to 3, and you will get a third of the height
///
/// [reducedBy] is a percentage value of how much of the height you want
/// if you for example want 46% of the height, then you reduce it by 56%.
double heightTransformer({double dividedBy = 1, double reducedBy = 0.0}) {
return (mediaQuerySize.height -
((mediaQuerySize.height / 100) * reducedBy)) /
dividedBy;
}
/// Gives you the power to get a portion of the width.
/// Useful for responsive applications.
///
/// [dividedBy] is for when you want to have a portion of the value you
/// would get like for example: if you want a value that represents a third
/// of the screen you can set it to 3, and you will get a third of the width
///
/// [reducedBy] is a percentage value of how much of the width you want
/// if you for example want 46% of the width, then you reduce it by 56%.
double widthTransformer({double dividedBy = 1, double reducedBy = 0.0}) {
return (mediaQuerySize.width - ((mediaQuerySize.width / 100) * reducedBy)) /
dividedBy;
}
/// Divide the height proportionally by the given value
double ratio({
double dividedBy = 1,
double reducedByW = 0.0,
double reducedByH = 0.0,
}) {
return heightTransformer(dividedBy: dividedBy, reducedBy: reducedByH) /
widthTransformer(dividedBy: dividedBy, reducedBy: reducedByW);
}
/// similar to [MediaQuery.of(context).padding]
ThemeData get theme => Theme.of(this);
/// Check if dark mode theme is enable
bool get isDarkMode => (theme.brightness == Brightness.dark);
/// give access to Theme.of(context).iconTheme.color
Color? get iconColor => theme.iconTheme.color;
/// similar to [MediaQuery.of(context).padding]
TextTheme get textTheme => Theme.of(this).textTheme;
/// similar to [MediaQuery.of(context).padding]
EdgeInsets get mediaQueryPadding => MediaQuery.of(this).padding;
/// similar to [MediaQuery.of(context).padding]
MediaQueryData get mediaQuery => MediaQuery.of(this);
/// similar to [MediaQuery.of(context).viewPadding]
EdgeInsets get mediaQueryViewPadding => MediaQuery.of(this).viewPadding;
/// similar to [MediaQuery.of(context).viewInsets]
EdgeInsets get mediaQueryViewInsets => MediaQuery.of(this).viewInsets;
/// similar to [MediaQuery.of(context).orientation]
Orientation get orientation => MediaQuery.of(this).orientation;
/// check if device is on landscape mode
bool get isLandscape => orientation == Orientation.landscape;
/// check if device is on portrait mode
bool get isPortrait => orientation == Orientation.portrait;
/// similar to [MediaQuery.of(this).devicePixelRatio]
double get devicePixelRatio => MediaQuery.of(this).devicePixelRatio;
/// similar to [MediaQuery.of(this).textScaleFactor]
double get textScaleFactor => MediaQuery.of(this).textScaleFactor;
/// get the shortestSide from screen
double get mediaQueryShortestSide => mediaQuerySize.shortestSide;
/// True if width be larger than 800
bool get showNavbar => (width > 800);
/// True if the shortestSide is smaller than 600p
bool get isPhone => (mediaQueryShortestSide < 600);
/// True if the shortestSide is largest than 600p
bool get isSmallTablet => (mediaQueryShortestSide >= 600);
/// True if the shortestSide is largest than 720p
bool get isLargeTablet => (mediaQueryShortestSide >= 720);
/// True if the current device is Tablet
bool get isTablet => isSmallTablet || isLargeTablet;
/// Returns a specific value according to the screen size
/// if the device width is higher than or equal to 1200 return
/// [desktop] value. if the device width is higher than or equal to 600
/// and less than 1200 return [tablet] value.
/// if the device width is less than 300 return [watch] value.
/// in other cases return [mobile] value.
T responsiveValue<T>({
T? mobile,
T? tablet,
T? desktop,
T? watch,
}) {
var deviceWidth = mediaQuerySize.shortestSide;
if (GetPlatform.isDesktop) {
deviceWidth = mediaQuerySize.width;
}
if (deviceWidth >= 1200 && desktop != null) {
return desktop;
} else if (deviceWidth >= 600 && tablet != null) {
return tablet;
} else if (deviceWidth < 300 && watch != null) {
return watch;
} else {
return mobile!;
}
}
}

View File

@@ -0,0 +1,8 @@
import 'dart:math';
extension Precision on double {
double toPrecision(int fractionDigits) {
var mod = pow(10, fractionDigits.toDouble()).toDouble();
return ((this * mod).round().toDouble() / mod);
}
}

View File

@@ -0,0 +1,20 @@
import 'dart:async';
/// Duration utilities.
extension GetDurationUtils on Duration {
/// Utility to delay some callback (or code execution).
///
/// Sample:
/// ```
/// void main() async {
/// final _delay = 3.seconds;
/// print('+ wait $_delay');
/// await _delay.delay();
/// print('- finish wait $_delay');
/// print('+ callback in 700ms');
/// await 0.7.seconds.delay(() {
/// }
///```
Future delay([FutureOr Function()? callback]) async =>
Future.delayed(this, callback);
}

View File

@@ -0,0 +1,23 @@
import '../get_utils/get_utils.dart';
extension GetDynamicUtils on dynamic {
@Deprecated('isNull is deprecated and cannot be used, use "==" operator')
bool get isNull => GetUtils.isNull(this);
bool? get isBlank => GetUtils.isBlank(this);
@Deprecated(
'isNullOrBlank is deprecated and cannot be used, use "isBlank" instead')
bool? get isNullOrBlank => GetUtils.isNullOrBlank(this);
void printError(
{String info = '', Function logFunction = GetUtils.printFunction}) =>
// ignore: unnecessary_this
logFunction('Error: ${this.runtimeType}', this, info, isError: true);
void printInfo(
{String info = '',
Function printFunction = GetUtils.printFunction}) =>
// ignore: unnecessary_this
printFunction('Info: ${this.runtimeType}', this, info);
}

View File

@@ -0,0 +1,23 @@
import 'dart:async';
import '../../../get_core/src/get_interface.dart';
extension LoopEventsExt on GetInterface {
Future<T> toEnd<T>(FutureOr<T> Function() computation) async {
await Future.delayed(Duration.zero);
final val = computation();
return val;
}
FutureOr<T> asap<T>(T Function() computation,
{bool Function()? condition}) async {
T val;
if (condition == null || !condition()) {
await Future.delayed(Duration.zero);
val = computation();
} else {
val = computation();
}
return val;
}
}

View File

@@ -0,0 +1,10 @@
export 'context_extensions.dart';
export 'double_extensions.dart';
export 'duration_extensions.dart';
export 'dynamic_extensions.dart';
export 'event_loop_extensions.dart';
export 'internacionalization.dart';
export 'iterable_extensions.dart';
export 'num_extensions.dart';
export 'string_extensions.dart';
export 'widget_extensions.dart';

Some files were not shown because too many files have changed in this diff Show More