diff --git a/example/.metadata b/example/.metadata index 02e9c7bfd..329f72cae 100644 --- a/example/.metadata +++ b/example/.metadata @@ -15,7 +15,7 @@ migration: - platform: root create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - - platform: web + - platform: android create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 diff --git a/example/lib/main.dart b/example/lib/main.dart index dae1c352a..efce4a049 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,34 +1,10 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'lang/translation_service.dart'; -import 'routes/app_pages.dart'; -import 'shared/logger/logger_utils.dart'; +// import 'lang/translation_service.dart'; +// import 'routes/app_pages.dart'; +// import 'shared/logger/logger_utils.dart'; -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return GetMaterialApp( - theme: ThemeData(useMaterial3: true), - debugShowCheckedModeBanner: false, - enableLog: true, - logWriterCallback: Logger.write, - initialRoute: AppPages.INITIAL, - getPages: AppPages.routes, - locale: TranslationService.locale, - fallbackLocale: TranslationService.fallbackLocale, - translations: TranslationService(), - ); - } -} - -// /// Nav 2 snippet // void main() { // runApp(const MyApp()); // } @@ -39,141 +15,172 @@ class MyApp extends StatelessWidget { // @override // Widget build(BuildContext context) { // return GetMaterialApp( -// getPages: [ -// GetPage( -// participatesInRootNavigator: true, -// name: '/first', -// page: () => const First()), -// GetPage( -// name: '/second', -// page: () => const Second(), -// transition: Transition.downToUp, -// ), -// GetPage( -// name: '/third', -// page: () => const Third(), -// ), -// ], +// theme: ThemeData(useMaterial3: true), // debugShowCheckedModeBanner: false, +// enableLog: true, +// logWriterCallback: Logger.write, +// initialRoute: AppPages.INITIAL, +// getPages: AppPages.routes, +// locale: TranslationService.locale, +// fallbackLocale: TranslationService.fallbackLocale, +// translations: TranslationService(), // ); // } // } -// class FirstController extends GetxController { -// @override -// void onClose() { -// print('on close first'); -// super.onClose(); -// } -// } +/// Nav 2 snippet +void main() { + runApp(const MyApp()); +} -// class First extends StatelessWidget { -// const First({Key? key}) : super(key: key); +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); -// @override -// Widget build(BuildContext context) { -// print('First rebuild'); -// Get.put(FirstController()); -// return Scaffold( -// appBar: AppBar( -// title: const Text('page one'), -// leading: IconButton( -// icon: const Icon(Icons.more), -// onPressed: () { -// Get.snackbar( -// 'title', -// "message", -// mainButton: -// TextButton(onPressed: () {}, child: const Text('button')), -// isDismissible: true, -// duration: Duration(seconds: 5), -// snackbarStatus: (status) => print(status), -// ); -// // print('THEME CHANGED'); -// // Get.changeTheme( -// // Get.isDarkMode ? ThemeData.light() : ThemeData.dark()); -// }, -// ), -// ), -// body: Center( -// child: SizedBox( -// height: 300, -// width: 300, -// child: ElevatedButton( -// onPressed: () { -// Get.toNamed('/second?id=123'); -// }, -// child: const Text('next screen'), -// ), -// ), -// ), -// ); -// } -// } + @override + Widget build(BuildContext context) { + return GetMaterialApp( + getPages: [ + GetPage( + participatesInRootNavigator: true, + name: '/first', + page: () => const First()), + GetPage( + name: '/second', + page: () => const Second(), + transition: Transition.downToUp, + ), + GetPage( + name: '/third', + page: () => const Third(), + ), + ], + debugShowCheckedModeBanner: false, + ); + } +} -// class SecondController extends GetxController { -// final textEdit = TextEditingController(); -// @override -// void onClose() { -// print('on close second'); -// textEdit.dispose(); -// super.onClose(); -// } -// } +class FirstController extends GetxController { + @override + void onClose() { + print('on close first'); + super.onClose(); + } +} -// class Second extends StatelessWidget { -// const Second({Key? key}) : super(key: key); +class First extends StatelessWidget { + const First({Key? key}) : super(key: key); -// @override -// Widget build(BuildContext context) { -// final controller = Get.put(SecondController()); -// print('second rebuild'); -// return Scaffold( -// appBar: AppBar( -// title: Text('page two ${Get.parameters["id"]}'), -// ), -// body: Center( -// child: Column( -// children: [ -// Expanded( -// child: TextField( -// controller: controller.textEdit, -// )), -// SizedBox( -// height: 300, -// width: 300, -// child: ElevatedButton( -// onPressed: () {}, -// child: const Text('next screen'), -// ), -// ), -// ], -// ), -// ), -// ); -// } -// } + @override + Widget build(BuildContext context) { + print('First rebuild'); + Get.put(FirstController()); + return Scaffold( + appBar: AppBar( + title: const Text('page one'), + leading: IconButton( + icon: const Icon(Icons.more), + onPressed: () { + Get.snackbar( + 'title', + "message", + mainButton: + TextButton(onPressed: () {}, child: const Text('button')), + isDismissible: true, + duration: Duration(seconds: 5), + snackbarStatus: (status) => print(status), + ); + // print('THEME CHANGED'); + // Get.changeTheme( + // Get.isDarkMode ? ThemeData.light() : ThemeData.dark()); + }, + ), + ), + body: Center( + child: SizedBox( + height: 300, + width: 300, + child: ElevatedButton( + onPressed: () { + Get.toNamed('/second?id=123'); + }, + child: const Text('next screen'), + ), + ), + ), + ); + } +} + +class SecondController extends GetxController { + final textEdit = TextEditingController(); + @override + void onClose() { + print('on close second'); + textEdit.dispose(); + super.onClose(); + } +} -// class Third extends StatelessWidget { -// const Third({Key? key}) : super(key: key); +class Second extends StatelessWidget { + const Second({Key? key}) : super(key: key); -// @override -// Widget build(BuildContext context) { -// return Scaffold( -// backgroundColor: Colors.red, -// appBar: AppBar( -// title: const Text('page three'), -// ), -// body: Center( -// child: SizedBox( -// height: 300, -// width: 300, -// child: ElevatedButton( -// onPressed: () {}, -// child: const Text('go to first screen'), -// ), -// ), -// ), -// ); -// } -// } + @override + Widget build(BuildContext context) { + final controller = Get.put(SecondController()); + print('second rebuild'); + return Scaffold( + appBar: AppBar( + title: Text('page two ${Get.parameters["id"]}'), + ), + body: Center( + child: Column( + children: [ + Expanded( + child: TextField( + controller: controller.textEdit, + )), + SizedBox( + height: 300, + width: 300, + child: ElevatedButton( + onPressed: () { + Get.toNamed('/third'); + }, + child: const Text('next screen'), + ), + ), + ], + ), + ), + ); + } +} + +class Third extends StatelessWidget { + const Third({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.red, + appBar: AppBar( + title: const Text('page three'), + ), + body: Center( + child: SizedBox( + height: 300, + width: 300, + child: ElevatedButton( + onPressed: () { + Get.until((route) { + print(Get.currentRoute); + return Get.currentRoute == '/first'; + }); + }, + child: const Text('go to first screen'), + ), + ), + ), + ); + } +} diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart deleted file mode 100644 index ac1e5fb1d..000000000 --- a/example/test/widget_test.dart +++ /dev/null @@ -1,29 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:get_demo/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} diff --git a/example_nav2/.metadata b/example_nav2/.metadata index caa269cf4..90eabcfff 100644 --- a/example_nav2/.metadata +++ b/example_nav2/.metadata @@ -1,11 +1,11 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled. +# This file should be version controlled and should not be manually edited. version: - revision: c07f7888888435fd9df505aa2efc38d3cf65681b - channel: stable + revision: "80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819" + channel: "stable" project_type: app @@ -13,26 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: c07f7888888435fd9df505aa2efc38d3cf65681b - base_revision: c07f7888888435fd9df505aa2efc38d3cf65681b + create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - platform: android - create_revision: c07f7888888435fd9df505aa2efc38d3cf65681b - base_revision: c07f7888888435fd9df505aa2efc38d3cf65681b + create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - platform: ios - create_revision: c07f7888888435fd9df505aa2efc38d3cf65681b - base_revision: c07f7888888435fd9df505aa2efc38d3cf65681b + create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - platform: linux - create_revision: c07f7888888435fd9df505aa2efc38d3cf65681b - base_revision: c07f7888888435fd9df505aa2efc38d3cf65681b + create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - platform: macos - create_revision: c07f7888888435fd9df505aa2efc38d3cf65681b - base_revision: c07f7888888435fd9df505aa2efc38d3cf65681b + create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - platform: web - create_revision: c07f7888888435fd9df505aa2efc38d3cf65681b - base_revision: c07f7888888435fd9df505aa2efc38d3cf65681b + create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - platform: windows - create_revision: c07f7888888435fd9df505aa2efc38d3cf65681b - base_revision: c07f7888888435fd9df505aa2efc38d3cf65681b + create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 # User provided section diff --git a/example_nav2/ios/RunnerTests/RunnerTests.swift b/example_nav2/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/example_nav2/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/example_nav2/macos/RunnerTests/RunnerTests.swift b/example_nav2/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..61f3bd1fc --- /dev/null +++ b/example_nav2/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/example_nav2/pubspec.yaml b/example_nav2/pubspec.yaml index 53f4ada67..f3d001ccf 100644 --- a/example_nav2/pubspec.yaml +++ b/example_nav2/pubspec.yaml @@ -3,7 +3,7 @@ version: 1.0.0+1 publish_to: none description: A new Flutter project. environment: - sdk: ">=2.19.2 <3.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: cupertino_icons: ^1.0.2 diff --git a/example_nav2/web/index.html b/example_nav2/web/index.html index 0bb814ebe..112ab3f37 100644 --- a/example_nav2/web/index.html +++ b/example_nav2/web/index.html @@ -10,8 +10,11 @@ For more details: * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base + + This is a placeholder for base href that will be replaced by the value of + the `--base-href` argument provided to `flutter build`. --> - + @@ -23,76 +26,13 @@ + + + example_nav2 - - + diff --git a/example_nav2/web/manifest.json b/example_nav2/web/manifest.json index 01c5d3881..5a42251e9 100644 --- a/example_nav2/web/manifest.json +++ b/example_nav2/web/manifest.json @@ -18,6 +18,18 @@ "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" } ] } diff --git a/lib/get_navigation/src/routes/get_router_delegate.dart b/lib/get_navigation/src/routes/get_router_delegate.dart index b8798cc4e..9e528133c 100644 --- a/lib/get_navigation/src/routes/get_router_delegate.dart +++ b/lib/get_navigation/src/routes/get_router_delegate.dart @@ -111,7 +111,16 @@ class GetDelegate extends RouterDelegate var iterator = config; for (var item in middlewares) { var redirectRes = await item.redirectDelegate(iterator); - if (redirectRes == null) return null; + + if (redirectRes == null) { + config.route?.completer?.complete(); + return null; + } + if (config != redirectRes) { + config.route?.completer?.complete(); + Get.log('Redirect to ${redirectRes.pageSettings?.name}'); + } + iterator = redirectRes; // Stop the iteration over the middleware if we changed page // and that redirectRes is not the same as the current config. @@ -627,7 +636,7 @@ class GetDelegate extends RouterDelegate @override void backUntil(bool Function(GetPage) predicate) { - while (_activePages.length <= 1 && !predicate(_activePages.last.route!)) { + while (_activePages.length > 1 && !predicate(_activePages.last.route!)) { _popWithResult(); } @@ -719,8 +728,9 @@ class GetDelegate extends RouterDelegate } Future _push(RouteDecoder decoder, {bool rebuildStack = true}) async { - var mid = await runMiddleware(decoder); - final res = mid ?? decoder; + var res = await runMiddleware(decoder); + if (res == null) return null; + // final res = mid ?? decoder; // if (res == null) res = decoder; final preventDuplicateHandlingMode = diff --git a/lib/get_navigation/src/routes/parse_route.dart b/lib/get_navigation/src/routes/parse_route.dart index 263f6557e..aa3a1e375 100644 --- a/lib/get_navigation/src/routes/parse_route.dart +++ b/lib/get_navigation/src/routes/parse_route.dart @@ -56,13 +56,13 @@ class RouteDecoder { } } - void replaceArguments(Object? arguments) { - final newRoute = route; - if (newRoute != null) { - final index = currentTreeBranch.indexOf(newRoute); - currentTreeBranch[index] = newRoute.copyWith(arguments: arguments); - } - } + // void replaceArguments(Object? arguments) { + // final newRoute = route; + // if (newRoute != null) { + // final index = currentTreeBranch.indexOf(newRoute); + // currentTreeBranch[index] = newRoute.copyWith(arguments: arguments); + // } + // } @override bool operator ==(Object other) { diff --git a/lib/get_navigation/src/routes/route_middleware.dart b/lib/get_navigation/src/routes/route_middleware.dart index b3464249b..58f0426d5 100644 --- a/lib/get_navigation/src/routes/route_middleware.dart +++ b/lib/get_navigation/src/routes/route_middleware.dart @@ -4,7 +4,13 @@ import 'package:flutter/cupertino.dart'; import '../../../get.dart'; -abstract class _RouteMiddleware { +/// The Page Middlewares. +/// The Functions will be called in this order +/// (( [redirect] -> [onPageCalled] -> [onBindingsStart] -> +/// [onPageBuildStart] -> [onPageBuilt] -> [onPageDispose] )) +abstract class GetMiddleware { + GetMiddleware({this.priority = 0}); + /// The Order of the Middlewares to run. /// /// {@tool snippet} @@ -19,7 +25,7 @@ abstract class _RouteMiddleware { /// ``` /// -8 => 2 => 4 => 5 /// {@end-tool} - int? priority; + final int priority; /// This function will be called when the page of /// the called route is being searched for. @@ -33,7 +39,7 @@ abstract class _RouteMiddleware { /// } /// ``` /// {@end-tool} - RouteSettings? redirect(String route); + RouteSettings? redirect(String? route) => null; /// Similar to [redirect], /// This function will be called when the router delegate changes the @@ -52,7 +58,7 @@ abstract class _RouteMiddleware { /// } /// ``` /// {@end-tool} - FutureOr redirectDelegate(RouteDecoder route); + FutureOr redirectDelegate(RouteDecoder route) => (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 @@ -64,7 +70,7 @@ abstract class _RouteMiddleware { /// } /// ``` /// {@end-tool} - GetPage? onPageCalled(GetPage page); + GetPage? onPageCalled(GetPage? page) => page; /// This function will be called right before the [BindingsInterface] are initialize. /// Here you can change [BindingsInterface] for this page @@ -79,49 +85,17 @@ abstract class _RouteMiddleware { /// } /// ``` /// {@end-tool} - List? onBindingsStart(List bindings); + List? onBindingsStart(List? bindings) => bindings; /// This function will be called right after the [BindingsInterface] are initialize. - GetPageBuilder? onPageBuildStart(GetPageBuilder page); + GetPageBuilder? onPageBuildStart(GetPageBuilder? page) => 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? onBindingsStart(List? bindings) => bindings; - - @override - GetPageBuilder? onPageBuildStart(GetPageBuilder? page) => page; - - @override Widget onPageBuilt(Widget page) => page; - @override void onPageDispose() {} - - @override - FutureOr redirectDelegate(RouteDecoder route) => (route); } class MiddlewareRunner { @@ -133,7 +107,7 @@ class MiddlewareRunner { final newMiddleware = _middlewares ?? []; return List.of(newMiddleware) ..sort( - (a, b) => (a.priority ?? 0).compareTo(b.priority ?? 0), + (a, b) => (a.priority).compareTo(b.priority), ); } @@ -152,7 +126,7 @@ class MiddlewareRunner { break; } } - Get.log('Redirect to $to'); + return to; } @@ -235,7 +209,6 @@ class PageRedirect { settings = route; } final match = context.delegate.matchRoute(settings!.name!); - // Get.parameters = match.parameters; // No Match found if (match.route == null) { @@ -247,10 +220,10 @@ class PageRedirect { route = runner.runOnPageCalled(match.route); addPageParameter(route!); - // No middlewares found return match. - if (match.route!.middlewares.isEmpty) { - return false; - } + // // No middlewares found return match. + // if (match.route!.middlewares.isEmpty) { + // return false; + // } final newSettings = runner.runRedirect(settings!.name); if (newSettings == null) { return false; diff --git a/lib/get_rx/src/rx_stream/get_stream.dart b/lib/get_rx/src/rx_stream/get_stream.dart deleted file mode 100644 index 422053ea8..000000000 --- a/lib/get_rx/src/rx_stream/get_stream.dart +++ /dev/null @@ -1,229 +0,0 @@ -// 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 { -// void Function()? onListen; -// void Function()? onPause; -// void Function()? onResume; -// FutureOr Function()? onCancel; - -// GetStream({this.onListen, this.onPause, this.onResume, this.onCancel}); - -// factory GetStream.fromValue(T value, -// {Function()? onListen, -// Function()? onPause, -// Function()? onResume, -// FutureOr Function()? onCancel}) { -// final valuedStream = GetStream( -// onListen: onListen, -// onPause: onPause, -// onResume: onResume, -// onCancel: onCancel) -// .._value = value; - -// return valuedStream; -// } - -// List>? _onData = >[]; - -// bool? _isBusy = false; - -// FutureOr removeSubscription(LightSubscription subs) async { -// if (!_isBusy!) { -// return _onData!.remove(subs); -// } else { -// await Future.delayed(Duration.zero); -// return _onData?.remove(subs); -// } -// } - -// FutureOr addSubscription(LightSubscription 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 = >[]; -// 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; -// } - -// late T _value; - -// T get value { -// // RxInterface.proxy?.addListener(this); -// return _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 listen(void Function(T event) onData, -// {Function? onError, void Function()? onDone, bool? cancelOnError}) { -// final subs = LightSubscription( -// removeSubscription, -// onPause: onPause, -// onResume: onResume, -// onCancel: onCancel, -// ) -// ..onData(onData) -// ..onError(onError) -// ..onDone(onDone) -// ..cancelOnError = cancelOnError; -// addSubscription(subs); -// onListen?.call(); -// return subs; -// } - -// Stream get stream => -// GetStreamTransformation(addSubscription, removeSubscription); -// } - -// class LightSubscription extends StreamSubscription { -// final RemoveSubscription _removeSubscription; -// LightSubscription(this._removeSubscription, -// {this.onPause, this.onResume, this.onCancel}); -// final void Function()? onPause; -// final void Function()? onResume; -// final FutureOr Function()? onCancel; - -// bool? cancelOnError = false; - -// @override -// Future cancel() { -// _removeSubscription(this); -// onCancel?.call(); -// return Future.value(); -// } - -// OnData? _data; - -// Function? _onError; - -// Callback? _onDone; - -// bool _isPaused = false; - -// @override -// void onData(OnData? handleData) => _data = handleData; - -// @override -// void onError(Function? handleError) => _onError = handleError; - -// @override -// void onDone(Callback? handleDone) => _onDone = handleDone; - -// @override -// void pause([Future? resumeSignal]) { -// _isPaused = true; -// onPause?.call(); -// } - -// @override -// void resume() { -// _isPaused = false; -// onResume?.call(); -// } - -// @override -// bool get isPaused => _isPaused; - -// @override -// Future asFuture([E? futureValue]) => Future.value(futureValue); -// } - -// class GetStreamTransformation extends Stream { -// final AddSubscription _addSubscription; -// final RemoveSubscription _removeSubscription; -// GetStreamTransformation(this._addSubscription, this._removeSubscription); - -// @override -// LightSubscription listen(void Function(T event)? onData, -// {Function? onError, void Function()? onDone, bool? cancelOnError}) { -// final subs = LightSubscription(_removeSubscription) -// ..onData(onData) -// ..onError(onError) -// ..onDone(onDone); -// _addSubscription(subs); -// return subs; -// } -// } - -// typedef RemoveSubscription = FutureOr Function( -// LightSubscription subs); - -// typedef AddSubscription = -//FutureOr Function(LightSubscription subs); diff --git a/lib/get_rx/src/rx_stream/mini_stream.dart b/lib/get_rx/src/rx_stream/mini_stream.dart index 838949373..28bbb3a2b 100644 --- a/lib/get_rx/src/rx_stream/mini_stream.dart +++ b/lib/get_rx/src/rx_stream/mini_stream.dart @@ -3,7 +3,8 @@ part of 'rx_stream.dart'; class Node { T? data; Node? next; - Node({this.data, this.next}); + Node? prev; + Node({this.data, this.next, this.prev}); } class MiniSubscription { @@ -74,51 +75,41 @@ class MiniStream { class FastList { Node>? _head; + Node>? _tail; + int _length = 0; void _notifyData(T data) { var currentNode = _head; - do { - currentNode?.data?.data(data); - currentNode = currentNode?.next; - } while (currentNode != null); + while (currentNode != null) { + currentNode.data?.data(data); + currentNode = currentNode.next; + } } void _notifyDone() { var currentNode = _head; - do { - currentNode?.data?.onDone?.call(); - currentNode = currentNode?.next; - } while (currentNode != null); + while (currentNode != null) { + currentNode.data?.onDone?.call(); + currentNode = currentNode.next; + } } void _notifyError(Object error, [StackTrace? stackTrace]) { var currentNode = _head; while (currentNode != null) { - currentNode.data!.onError?.call(error, stackTrace); + currentNode.data?.onError?.call(error, stackTrace); currentNode = currentNode.next; } } - /// Checks if this list is empty - bool get isEmpty => _head == null; - - bool get isNotEmpty => !isEmpty; + bool get isEmpty => _length == 0; - /// Returns the length of this list - int get length { - var length = 0; - var currentNode = _head; + bool get isNotEmpty => _length > 0; - while (currentNode != null) { - currentNode = currentNode.next; - length++; - } - return length; - } + int get length => _length; - /// Shows the element at position [position]. `null` for invalid positions. - MiniSubscription? _elementAt(int position) { - if (isEmpty || length < position || position < 0) return null; + MiniSubscription? elementAt(int position) { + if (isEmpty || position < 0 || position >= _length) return null; var node = _head; var current = 0; @@ -130,74 +121,57 @@ class FastList { return node!.data; } - /// Inserts [data] at the end of the list. void addListener(MiniSubscription data) { var newNode = Node(data: data); if (isEmpty) { - _head = newNode; + _head = _tail = newNode; } else { - var currentNode = _head!; - while (currentNode.next != null) { - currentNode = currentNode.next!; - } - currentNode.next = newNode; + _tail!.next = newNode; + newNode.prev = _tail; + _tail = newNode; } + _length++; } 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); - } + var currentNode = _head; + while (currentNode != null) { + if (currentNode.data == element) return true; + currentNode = currentNode.next; } return false; } void removeListener(MiniSubscription element) { - var length = this.length; - for (var i = 0; i < length; i++) { - if (_elementAt(i) == element) { - _removeAt(i); + var currentNode = _head; + while (currentNode != null) { + if (currentNode.data == element) { + _removeNode(currentNode); break; } + currentNode = currentNode.next; } } void clear() { - var length = this.length; - for (var i = 0; i < length; i++) { - _removeAt(i); - } + _head = _tail = null; + _length = 0; } - MiniSubscription? _removeAt(int position) { - var index = 0; - var currentNode = _head; - Node>? previousNode; - - if (isEmpty || length < position || position < 0) { - throw Exception('Invalid position'); - } else if (position == 0) { - _head = _head!.next; + void _removeNode(Node> node) { + if (node.prev == null) { + _head = node.next; } else { - while (index != position) { - previousNode = currentNode; - currentNode = currentNode!.next; - index++; - } - - if (previousNode == null) { - _head = null; - } else { - previousNode.next = currentNode!.next; - } + node.prev!.next = node.next; + } - currentNode!.next = null; + if (node.next == null) { + _tail = node.prev; + } else { + node.next!.prev = node.prev; } - return currentNode!.data; + _length--; } } diff --git a/test/navigation/get_main_test.dart b/test/navigation/get_main_test.dart index 240ae5a4d..00b391be8 100644 --- a/test/navigation/get_main_test.dart +++ b/test/navigation/get_main_test.dart @@ -268,6 +268,32 @@ void main() { expect(find.byType(ThirdScreen), findsOneWidget); }); + testWidgets("Get.until removes each route that meet the predicate", + (tester) async { + await tester.pumpWidget(WrapperNamed( + initialRoute: '/first', + namedRoutes: [ + GetPage(page: () => const FirstScreen(), name: '/first'), + GetPage(page: () => const SecondScreen(), name: '/second'), + GetPage(page: () => const ThirdScreen(), name: '/third') + ], + )); + + Get.toNamed('/second'); + await tester.pumpAndSettle(); + + Get.toNamed('/third'); + await tester.pumpAndSettle(); + + Get.until((route) => route.name == '/first'); + + await tester.pumpAndSettle(); + + expect(find.byType(FirstScreen), findsOneWidget); + expect(find.byType(SecondScreen), findsNothing); + expect(find.byType(ThirdScreen), findsNothing); + }); + testWidgets( "Get.offUntil removes previous routes if they don't match predicate", (tester) async { @@ -569,6 +595,16 @@ void main() { }); } +class Home extends StatelessWidget { + const Home({super.key}); + + @override + Widget build(BuildContext context) { + // ignore: avoid_unnecessary_containers + return Container(child: const Text('Home')); + } +} + class FirstScreen extends StatelessWidget { const FirstScreen({super.key}); @@ -596,3 +632,12 @@ class ThirdScreen extends StatelessWidget { return Container(); } } + +class FourthScreen extends StatelessWidget { + const FourthScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Container(); + } +} diff --git a/test/navigation/middleware_test.dart b/test/navigation/middleware_test.dart index d6c509376..0d982583e 100644 --- a/test/navigation/middleware_test.dart +++ b/test/navigation/middleware_test.dart @@ -1,81 +1,193 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import 'get_main_test.dart'; class RedirectMiddleware extends GetMiddleware { - // @override - // RouteSettings redirect(String? route) { - // return RouteSettings(name: '/second'); - // } - @override Future redirectDelegate(RouteDecoder route) async { return RouteDecoder.fromRoute('/second'); } } -class RedirectMiddlewareNull extends GetMiddleware { - // @override - // RouteSettings redirect(String? route) { - // return RouteSettings(name: '/second'); - // } +class Redirect2Middleware extends GetMiddleware { + @override + Future redirectDelegate(RouteDecoder route) async { + return RouteDecoder.fromRoute('/first'); + } +} +class RedirectMiddlewareNull extends GetMiddleware { @override Future redirectDelegate(RouteDecoder route) async { return null; } } +class RedirectBypassMiddleware extends GetMiddleware { + @override + Future redirectDelegate(RouteDecoder route) async { + return route; + } +} + void main() { - testWidgets("Middleware redirect smoke test", (tester) async { + tearDown(() { + Get.reset(); + }); + + testWidgets("Middleware should redirect to second screen", (tester) async { + // Test setup await tester.pumpWidget( GetMaterialApp( initialRoute: '/', getPages: [ - GetPage(name: '/', page: () => Container()), + GetPage(name: '/', page: () => const Home()), GetPage( - name: '/first', - page: () => const FirstScreen(), - middlewares: [ - RedirectMiddleware(), - ]), + name: '/first', + page: () => const FirstScreen(), + middlewares: [RedirectMiddleware()], + ), GetPage(name: '/second', page: () => const SecondScreen()), GetPage(name: '/third', page: () => const ThirdScreen()), ], ), ); + // Act Get.toNamed('/first'); - await tester.pumpAndSettle(); + + // Assert expect(find.byType(SecondScreen), findsOneWidget); + expect(find.byType(FirstScreen), findsNothing); + expect(Get.currentRoute, '/second'); }); - testWidgets("Middleware redirect null test", (tester) async { + testWidgets("Middleware should stop navigation", (tester) async { + // Test setup await tester.pumpWidget( GetMaterialApp( initialRoute: '/', getPages: [ - GetPage(name: '/', page: () => Container()), + GetPage(name: '/', page: () => const Home()), GetPage( - name: '/first', - page: () => const FirstScreen(), - middlewares: [ - RedirectMiddlewareNull(), - ]), + name: '/first', + page: () => const FirstScreen(), + middlewares: [RedirectMiddlewareNull()], + ), GetPage(name: '/second', page: () => const SecondScreen()), GetPage(name: '/third', page: () => const ThirdScreen()), ], ), ); - // await tester.pump(); - + // Act + await tester.pumpAndSettle(); Get.toNamed('/first'); + await tester.pumpAndSettle(); + + // Assert + expect(find.byType(Home), findsOneWidget); + expect(find.byType(FirstScreen), findsNothing); + expect(Get.currentRoute, '/'); + }); + testWidgets("Middleware should be bypassed", (tester) async { + // Test setup + await tester.pumpWidget( + GetMaterialApp( + initialRoute: '/', + getPages: [ + GetPage(name: '/', page: () => const Home()), + GetPage( + name: '/first', + page: () => const FirstScreen(), + middlewares: [RedirectBypassMiddleware()], + ), + GetPage(name: '/second', page: () => const SecondScreen()), + GetPage(name: '/third', page: () => const ThirdScreen()), + ], + ), + ); + + // Act + await tester.pumpAndSettle(); + Get.toNamed('/first'); await tester.pumpAndSettle(); + + // Assert expect(find.byType(FirstScreen), findsOneWidget); + expect(find.byType(SecondScreen), findsNothing); + expect(find.byType(Home), findsNothing); + expect(Get.currentRoute, '/first'); + }); + + testWidgets("Middleware should redirect twice", (tester) async { + // Test setup + await tester.pumpWidget( + GetMaterialApp( + initialRoute: '/', + getPages: [ + GetPage(name: '/', page: () => const Home()), + GetPage( + name: '/first', + page: () => const FirstScreen(), + middlewares: [RedirectMiddleware()], + ), + GetPage(name: '/second', page: () => const SecondScreen()), + GetPage(name: '/third', page: () => const ThirdScreen()), + GetPage( + name: '/fourth', + page: () => const FourthScreen(), + middlewares: [Redirect2Middleware()], + ), + ], + ), + ); + + // Act + Get.toNamed('/fourth'); + await tester.pumpAndSettle(); + + // Assert + expect(find.byType(SecondScreen), findsOneWidget); + expect(find.byType(FirstScreen), findsNothing); + expect(Get.currentRoute, '/second'); + }); + + testWidgets("Navigation history should be correct after redirects", + (tester) async { + // Test setup + await tester.pumpWidget( + GetMaterialApp( + initialRoute: '/', + getPages: [ + GetPage(name: '/', page: () => const Home()), + GetPage( + name: '/first', + page: () => const FirstScreen(), + middlewares: [RedirectMiddleware()], + ), + GetPage(name: '/second', page: () => const SecondScreen()), + ], + ), + ); + + // Act + Get.toNamed('/first'); + await tester.pumpAndSettle(); + + // Assert + expect(Get.currentRoute, '/second'); + expect(Get.previousRoute, '/'); + + // Act: go back + Get.back(); + await tester.pumpAndSettle(); + + // Assert + expect(find.byType(Home), findsOneWidget); + expect(Get.currentRoute, '/'); }); }