first commit
This commit is contained in:
14
siro_rider/packages/get/lib/get.dart
Normal file
14
siro_rider/packages/get/lib/get.dart
Normal 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';
|
||||
11
siro_rider/packages/get/lib/get_common/get_reset.dart
Normal file
11
siro_rider/packages/get/lib/get_common/get_reset.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
1
siro_rider/packages/get/lib/get_connect.dart
Normal file
1
siro_rider/packages/get/lib/get_connect.dart
Normal file
@@ -0,0 +1 @@
|
||||
export 'get_connect/connect.dart';
|
||||
396
siro_rider/packages/get/lib/get_connect/connect.dart
Normal file
396
siro_rider/packages/get/lib/get_connect/connect.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
// }
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
@@ -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;
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,5 @@
|
||||
void writeOnFile(List<int> bytes) {}
|
||||
|
||||
List<int> fileToBytes(dynamic data) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class TrustedCertificate {
|
||||
final List<int> bytes;
|
||||
|
||||
TrustedCertificate(this.bytes);
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
563
siro_rider/packages/get/lib/get_connect/http/src/http.dart
Normal file
563
siro_rider/packages/get/lib/get_connect/http/src/http.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
];
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
9
siro_rider/packages/get/lib/get_core/get_core.dart
Normal file
9
siro_rider/packages/get/lib/get_core/get_core.dart
Normal 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;
|
||||
15
siro_rider/packages/get/lib/get_core/src/get_interface.dart
Normal file
15
siro_rider/packages/get/lib/get_core/src/get_interface.dart
Normal 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;
|
||||
}
|
||||
12
siro_rider/packages/get/lib/get_core/src/get_main.dart
Normal file
12
siro_rider/packages/get/lib/get_core/src/get_main.dart
Normal 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();
|
||||
10
siro_rider/packages/get/lib/get_core/src/log.dart
Normal file
10
siro_rider/packages/get/lib/get_core/src/log.dart
Normal 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');
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
1
siro_rider/packages/get/lib/get_core/src/typedefs.dart
Normal file
1
siro_rider/packages/get/lib/get_core/src/typedefs.dart
Normal file
@@ -0,0 +1 @@
|
||||
typedef ValueUpdater<T> = T Function();
|
||||
@@ -0,0 +1,4 @@
|
||||
export 'src/bindings_interface.dart';
|
||||
export 'src/extension_instance.dart';
|
||||
export 'src/get_instance.dart';
|
||||
export 'src/lifecycle.dart';
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
551
siro_rider/packages/get/lib/get_instance/src/get_instance.dart
Normal file
551
siro_rider/packages/get/lib/get_instance/src/get_instance.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
104
siro_rider/packages/get/lib/get_instance/src/lifecycle.dart
Normal file
104
siro_rider/packages/get/lib/get_instance/src/lifecycle.dart
Normal 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 {}
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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=====''';
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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 }
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
5
siro_rider/packages/get/lib/get_rx/get_rx.dart
Normal file
5
siro_rider/packages/get/lib/get_rx/get_rx.dart
Normal 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';
|
||||
209
siro_rider/packages/get/lib/get_rx/src/rx_stream/get_stream.dart
Normal file
209
siro_rider/packages/get/lib/get_rx/src/rx_stream/get_stream.dart
Normal 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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
library rx_stream;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import '../rx_typedefs/rx_typedefs.dart';
|
||||
|
||||
part 'get_stream.dart';
|
||||
part 'mini_stream.dart';
|
||||
@@ -0,0 +1,3 @@
|
||||
typedef Condition = bool Function();
|
||||
typedef OnData<T> = void Function(T data);
|
||||
typedef Callback = void Function();
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
1354
siro_rider/packages/get/lib/get_rx/src/rx_types/rx_core/rx_num.dart
Normal file
1354
siro_rider/packages/get/lib/get_rx/src/rx_types/rx_core/rx_num.dart
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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!),
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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);
|
||||
// }
|
||||
// }
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
4
siro_rider/packages/get/lib/get_utils/get_utils.dart
Normal file
4
siro_rider/packages/get/lib/get_utils/get_utils.dart
Normal 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';
|
||||
@@ -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!;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user