diff --git a/.dart_tool/extension_discovery/README.md b/.dart_tool/extension_discovery/README.md deleted file mode 100644 index 9dc6757b..00000000 --- a/.dart_tool/extension_discovery/README.md +++ /dev/null @@ -1,31 +0,0 @@ -Extension Discovery Cache -========================= - -This folder is used by `package:extension_discovery` to cache lists of -packages that contains extensions for other packages. - -DO NOT USE THIS FOLDER ----------------------- - - * Do not read (or rely) the contents of this folder. - * Do write to this folder. - -If you're interested in the lists of extensions stored in this folder use the -API offered by package `extension_discovery` to get this information. - -If this package doesn't work for your use-case, then don't try to read the -contents of this folder. It may change, and will not remain stable. - -Use package `extension_discovery` ---------------------------------- - -If you want to access information from this folder. - -Feel free to delete this folder -------------------------------- - -Files in this folder act as a cache, and the cache is discarded if the files -are older than the modification time of `.dart_tool/package_config.json`. - -Hence, it should never be necessary to clear this cache manually, if you find a -need to do please file a bug. diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json deleted file mode 100644 index 1bacf584..00000000 --- a/.dart_tool/package_config.json +++ /dev/null @@ -1,934 +0,0 @@ -{ - "configVersion": 2, - "packages": [ - { - "name": "_fe_analyzer_shared", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/_fe_analyzer_shared-82.0.0", - "packageUri": "lib/", - "languageVersion": "3.5" - }, - { - "name": "alice", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/alice-0.4.2", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "analyzer", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/analyzer-7.4.5", - "packageUri": "lib/", - "languageVersion": "3.5" - }, - { - "name": "args", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/args-2.4.0", - "packageUri": "lib/", - "languageVersion": "2.18" - }, - { - "name": "asn1lib", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/asn1lib-1.6.4", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "async", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/async-2.13.0", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "auto_injector", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/auto_injector-2.1.0", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "bloc", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/bloc-8.1.4", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "boolean_selector", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/boolean_selector-2.1.2", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "buffer", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/buffer-1.2.3", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "characters", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/characters-1.4.0", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "charcode", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/charcode-1.3.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "checked_yaml", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/checked_yaml-2.0.4", - "packageUri": "lib/", - "languageVersion": "3.8" - }, - { - "name": "chopper", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/chopper-7.4.0", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "cli_config", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/cli_config-0.2.0", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "clock", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/clock-1.1.2", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "collection", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/collection-1.19.1", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "convert", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/convert-3.1.2", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "coverage", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/coverage-1.14.0", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "cross_file", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/cross_file-0.3.4+2", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "crypto", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/crypto-3.0.6", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "crypto_keys", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/crypto_keys-0.3.0+2", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "dartz", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/dartz-0.10.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "dbus", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/dbus-0.7.11", - "packageUri": "lib/", - "languageVersion": "2.17" - }, - { - "name": "dio", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/dio-5.8.0+1", - "packageUri": "lib/", - "languageVersion": "2.18" - }, - { - "name": "dio_web_adapter", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/dio_web_adapter-2.1.1", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "equatable", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/equatable-2.0.7", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "fake_async", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/fake_async-1.3.3", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "ffi", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/ffi-2.1.4", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "file", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/file-6.1.4", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "fixnum", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/fixnum-1.1.1", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "flutter", - "rootUri": "file:///C:/flutter/packages/flutter", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "flutter_lints", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_lints-2.0.3", - "packageUri": "lib/", - "languageVersion": "2.19" - }, - { - "name": "flutter_local_notifications", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_local_notifications-17.2.4", - "packageUri": "lib/", - "languageVersion": "2.17" - }, - { - "name": "flutter_local_notifications_linux", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_local_notifications_linux-4.0.1", - "packageUri": "lib/", - "languageVersion": "2.17" - }, - { - "name": "flutter_local_notifications_platform_interface", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_local_notifications_platform_interface-7.2.0", - "packageUri": "lib/", - "languageVersion": "2.17" - }, - { - "name": "flutter_mobx", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_mobx-2.3.0", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "flutter_test", - "rootUri": "file:///C:/flutter/packages/flutter_test", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "flutter_triple", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_triple-2.2.0", - "packageUri": "lib/", - "languageVersion": "2.18" - }, - { - "name": "flutter_web_plugins", - "rootUri": "file:///C:/flutter/packages/flutter_web_plugins", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "flutterando_analysis", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutterando_analysis-0.0.2", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "fpdart", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/fpdart-0.0.14", - "packageUri": "lib/", - "languageVersion": "2.13" - }, - { - "name": "frontend_server_client", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/frontend_server_client-4.0.0", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "glob", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/glob-2.1.3", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "hotreloader", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/hotreloader-4.3.0", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "http", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/http-1.4.0", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "http_multi_server", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/http_multi_server-3.2.2", - "packageUri": "lib/", - "languageVersion": "3.2" - }, - { - "name": "http_parser", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/http_parser-4.0.2", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "io", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/io-1.0.4", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "jose", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/jose-0.3.4", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "js", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/js-0.7.2", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "json_annotation", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/json_annotation-4.9.0", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "leak_tracker", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/leak_tracker-10.0.9", - "packageUri": "lib/", - "languageVersion": "3.2" - }, - { - "name": "leak_tracker_flutter_testing", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.9", - "packageUri": "lib/", - "languageVersion": "3.2" - }, - { - "name": "leak_tracker_testing", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/leak_tracker_testing-3.0.1", - "packageUri": "lib/", - "languageVersion": "3.2" - }, - { - "name": "lints", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/lints-2.1.1", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "logging", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/logging-1.3.0", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "matcher", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/matcher-0.12.17", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "material_color_utilities", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/material_color_utilities-0.11.1", - "packageUri": "lib/", - "languageVersion": "2.17" - }, - { - "name": "meta", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/meta-1.16.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "mime", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/mime-1.0.6", - "packageUri": "lib/", - "languageVersion": "3.2" - }, - { - "name": "mobx", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/mobx-2.5.0", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "mocktail", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/mocktail-1.0.4", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "nested", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/nested-1.0.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "node_preamble", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/node_preamble-2.0.2", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "open_filex", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/open_filex-4.7.0", - "packageUri": "lib/", - "languageVersion": "2.17" - }, - { - "name": "package_config", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/package_config-2.2.0", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "package_info_plus", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/package_info_plus-6.0.0", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "package_info_plus_platform_interface", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/package_info_plus_platform_interface-2.0.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "path", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path-1.9.1", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "path_provider", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider-2.1.5", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "path_provider_android", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider_android-2.2.17", - "packageUri": "lib/", - "languageVersion": "3.6" - }, - { - "name": "path_provider_foundation", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider_foundation-2.4.1", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "path_provider_linux", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider_linux-2.2.1", - "packageUri": "lib/", - "languageVersion": "2.19" - }, - { - "name": "path_provider_platform_interface", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider_platform_interface-2.1.2", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "path_provider_windows", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider_windows-2.3.0", - "packageUri": "lib/", - "languageVersion": "3.2" - }, - { - "name": "pedantic", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/pedantic-1.11.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "permission_handler", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler-11.4.0", - "packageUri": "lib/", - "languageVersion": "3.5" - }, - { - "name": "permission_handler_android", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler_android-12.1.0", - "packageUri": "lib/", - "languageVersion": "3.5" - }, - { - "name": "permission_handler_apple", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler_apple-9.4.7", - "packageUri": "lib/", - "languageVersion": "2.18" - }, - { - "name": "permission_handler_html", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler_html-0.1.3+5", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "permission_handler_platform_interface", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler_platform_interface-4.3.0", - "packageUri": "lib/", - "languageVersion": "3.5" - }, - { - "name": "permission_handler_windows", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler_windows-0.2.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "petitparser", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/petitparser-6.1.0", - "packageUri": "lib/", - "languageVersion": "3.5" - }, - { - "name": "platform", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/platform-3.1.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "plugin_platform_interface", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/plugin_platform_interface-2.1.8", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "pointycastle", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/pointycastle-4.0.0", - "packageUri": "lib/", - "languageVersion": "3.2" - }, - { - "name": "pool", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/pool-1.5.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "postgres", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/postgres-2.6.4", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "provider", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/provider-6.1.5", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "pub_semver", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/pub_semver-2.2.0", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "pubspec_parse", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/pubspec_parse-1.5.0", - "packageUri": "lib/", - "languageVersion": "3.6" - }, - { - "name": "qs_dart", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/qs_dart-1.3.7+1", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "quiver", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/quiver-3.2.1", - "packageUri": "lib/", - "languageVersion": "2.17" - }, - { - "name": "recursive_regex", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/recursive_regex-1.0.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "redis_dart", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/redis_dart-0.1.0+4", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "result_dart", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/result_dart-2.1.0", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "rx_notifier", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/rx_notifier-2.3.0", - "packageUri": "lib/", - "languageVersion": "2.17" - }, - { - "name": "rx_notifier_annotation", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/rx_notifier_annotation-1.0.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "rxdart", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/rxdart-0.27.7", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "sasl_scram", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/sasl_scram-0.1.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "saslprep", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/saslprep-1.0.3", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "sensors_plus", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/sensors_plus-5.0.1", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "sensors_plus_platform_interface", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/sensors_plus_platform_interface-1.2.0", - "packageUri": "lib/", - "languageVersion": "2.18" - }, - { - "name": "share_plus", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/share_plus-9.0.0", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "share_plus_platform_interface", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/share_plus_platform_interface-4.0.0", - "packageUri": "lib/", - "languageVersion": "2.18" - }, - { - "name": "shelf", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/shelf-1.4.1", - "packageUri": "lib/", - "languageVersion": "2.17" - }, - { - "name": "shelf_packages_handler", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/shelf_packages_handler-3.0.2", - "packageUri": "lib/", - "languageVersion": "2.17" - }, - { - "name": "shelf_static", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/shelf_static-1.1.3", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "shelf_web_socket", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/shelf_web_socket-1.0.4", - "packageUri": "lib/", - "languageVersion": "2.17" - }, - { - "name": "sky_engine", - "rootUri": "file:///C:/flutter/bin/cache/pkg/sky_engine", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "source_map_stack_trace", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/source_map_stack_trace-2.1.2", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "source_maps", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/source_maps-0.10.13", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "source_span", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/source_span-1.10.1", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "sprintf", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/sprintf-7.0.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "stack_trace", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/stack_trace-1.12.1", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "stream_channel", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/stream_channel-2.1.4", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "stream_transform", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/stream_transform-2.1.1", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "string_scanner", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/string_scanner-1.4.1", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "term_glyph", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/term_glyph-1.2.2", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "test", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/test-1.25.15", - "packageUri": "lib/", - "languageVersion": "3.5" - }, - { - "name": "test_api", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/test_api-0.7.4", - "packageUri": "lib/", - "languageVersion": "3.5" - }, - { - "name": "test_core", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/test_core-0.6.8", - "packageUri": "lib/", - "languageVersion": "3.5" - }, - { - "name": "timezone", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/timezone-0.9.4", - "packageUri": "lib/", - "languageVersion": "2.19" - }, - { - "name": "triple", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/triple-2.1.0", - "packageUri": "lib/", - "languageVersion": "2.18" - }, - { - "name": "typed_data", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/typed_data-1.3.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "unorm_dart", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/unorm_dart-0.3.0", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "url_launcher", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher-6.3.1", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "url_launcher_android", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_android-6.3.16", - "packageUri": "lib/", - "languageVersion": "3.6" - }, - { - "name": "url_launcher_ios", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_ios-6.3.3", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "url_launcher_linux", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_linux-3.2.1", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "url_launcher_macos", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_macos-3.2.2", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "url_launcher_platform_interface", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_platform_interface-2.3.2", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "url_launcher_web", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_web-2.4.1", - "packageUri": "lib/", - "languageVersion": "3.6" - }, - { - "name": "url_launcher_windows", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_windows-3.1.4", - "packageUri": "lib/", - "languageVersion": "3.4" - }, - { - "name": "uuid", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/uuid-4.5.1", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "vector_math", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/vector_math-2.1.4", - "packageUri": "lib/", - "languageVersion": "2.14" - }, - { - "name": "vm_service", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/vm_service-15.0.0", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "watcher", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/watcher-1.1.1", - "packageUri": "lib/", - "languageVersion": "3.1" - }, - { - "name": "weak_map", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/weak_map-4.0.1", - "packageUri": "lib/", - "languageVersion": "2.19" - }, - { - "name": "web", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/web-0.5.1", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "web_socket_channel", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/web_socket_channel-2.4.5", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "webkit_inspection_protocol", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/webkit_inspection_protocol-1.2.1", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "win32", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/win32-5.13.0", - "packageUri": "lib/", - "languageVersion": "3.7" - }, - { - "name": "x509", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/x509-0.2.4+3", - "packageUri": "lib/", - "languageVersion": "3.0" - }, - { - "name": "xdg_directories", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/xdg_directories-1.1.0", - "packageUri": "lib/", - "languageVersion": "3.3" - }, - { - "name": "xml", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/xml-6.5.0", - "packageUri": "lib/", - "languageVersion": "3.2" - }, - { - "name": "yaml", - "rootUri": "file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/yaml-3.1.1", - "packageUri": "lib/", - "languageVersion": "2.12" - }, - { - "name": "flutter_modular_workspace", - "rootUri": "../", - "packageUri": "lib/", - "languageVersion": "3.6" - }, - { - "name": "modular_core", - "rootUri": "../modular_core", - "packageUri": "lib/", - "languageVersion": "3.6" - }, - { - "name": "flutter_modular", - "rootUri": "../flutter_modular", - "packageUri": "lib/", - "languageVersion": "3.6" - }, - { - "name": "shelf_modular", - "rootUri": "../shelf_modular", - "packageUri": "lib/", - "languageVersion": "3.6" - }, - { - "name": "flutter_modular_example", - "rootUri": "../flutter_modular/example", - "packageUri": "lib/", - "languageVersion": "3.6" - }, - { - "name": "shelf_modular_example", - "rootUri": "../shelf_modular/example", - "packageUri": "lib/", - "languageVersion": "3.6" - } - ], - "generator": "pub", - "generatorVersion": "3.8.1", - "flutterRoot": "file:///C:/flutter", - "flutterVersion": "3.32.1", - "pubCache": "file:///C:/Users/jacob/.puro/shared/pub_cache" -} diff --git a/.dart_tool/package_config_subset b/.dart_tool/package_config_subset deleted file mode 100644 index 103bf28f..00000000 --- a/.dart_tool/package_config_subset +++ /dev/null @@ -1,617 +0,0 @@ -_fe_analyzer_shared -3.5 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/_fe_analyzer_shared-82.0.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/_fe_analyzer_shared-82.0.0/lib/ -alice -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/alice-0.4.2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/alice-0.4.2/lib/ -analyzer -3.5 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/analyzer-7.4.5/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/analyzer-7.4.5/lib/ -args -2.18 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/args-2.4.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/args-2.4.0/lib/ -asn1lib -3.7 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/asn1lib-1.6.4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/asn1lib-1.6.4/lib/ -async -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/async-2.13.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/async-2.13.0/lib/ -auto_injector -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/auto_injector-2.1.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/auto_injector-2.1.0/lib/ -bloc -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/bloc-8.1.4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/bloc-8.1.4/lib/ -boolean_selector -3.1 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/boolean_selector-2.1.2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/boolean_selector-2.1.2/lib/ -buffer -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/buffer-1.2.3/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/buffer-1.2.3/lib/ -characters -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/characters-1.4.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/characters-1.4.0/lib/ -charcode -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/charcode-1.3.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/charcode-1.3.1/lib/ -checked_yaml -3.8 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/checked_yaml-2.0.4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/checked_yaml-2.0.4/lib/ -chopper -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/chopper-7.4.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/chopper-7.4.0/lib/ -cli_config -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/cli_config-0.2.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/cli_config-0.2.0/lib/ -clock -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/clock-1.1.2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/clock-1.1.2/lib/ -collection -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/collection-1.19.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/collection-1.19.1/lib/ -convert -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/convert-3.1.2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/convert-3.1.2/lib/ -coverage -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/coverage-1.14.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/coverage-1.14.0/lib/ -cross_file -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/cross_file-0.3.4+2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/cross_file-0.3.4+2/lib/ -crypto -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/crypto-3.0.6/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/crypto-3.0.6/lib/ -crypto_keys -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/crypto_keys-0.3.0+2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/crypto_keys-0.3.0+2/lib/ -dartz -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/dartz-0.10.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/dartz-0.10.1/lib/ -dbus -2.17 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/dbus-0.7.11/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/dbus-0.7.11/lib/ -dio -2.18 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/dio-5.8.0+1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/dio-5.8.0+1/lib/ -dio_web_adapter -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/dio_web_adapter-2.1.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/dio_web_adapter-2.1.1/lib/ -equatable -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/equatable-2.0.7/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/equatable-2.0.7/lib/ -fake_async -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/fake_async-1.3.3/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/fake_async-1.3.3/lib/ -ffi -3.7 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/ffi-2.1.4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/ffi-2.1.4/lib/ -file -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/file-6.1.4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/file-6.1.4/lib/ -fixnum -3.1 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/fixnum-1.1.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/fixnum-1.1.1/lib/ -flutter_lints -2.19 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_lints-2.0.3/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_lints-2.0.3/lib/ -flutter_local_notifications -2.17 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_local_notifications-17.2.4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_local_notifications-17.2.4/lib/ -flutter_local_notifications_linux -2.17 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_local_notifications_linux-4.0.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_local_notifications_linux-4.0.1/lib/ -flutter_local_notifications_platform_interface -2.17 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_local_notifications_platform_interface-7.2.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_local_notifications_platform_interface-7.2.0/lib/ -flutter_mobx -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_mobx-2.3.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_mobx-2.3.0/lib/ -flutter_triple -2.18 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_triple-2.2.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutter_triple-2.2.0/lib/ -flutterando_analysis -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutterando_analysis-0.0.2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/flutterando_analysis-0.0.2/lib/ -fpdart -2.13 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/fpdart-0.0.14/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/fpdart-0.0.14/lib/ -frontend_server_client -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/frontend_server_client-4.0.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/frontend_server_client-4.0.0/lib/ -glob -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/glob-2.1.3/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/glob-2.1.3/lib/ -hotreloader -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/hotreloader-4.3.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/hotreloader-4.3.0/lib/ -http -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/http-1.4.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/http-1.4.0/lib/ -http_multi_server -3.2 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/http_multi_server-3.2.2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/http_multi_server-3.2.2/lib/ -http_parser -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/http_parser-4.0.2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/http_parser-4.0.2/lib/ -io -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/io-1.0.4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/io-1.0.4/lib/ -jose -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/jose-0.3.4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/jose-0.3.4/lib/ -js -3.7 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/js-0.7.2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/js-0.7.2/lib/ -json_annotation -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/json_annotation-4.9.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/json_annotation-4.9.0/lib/ -leak_tracker -3.2 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/leak_tracker-10.0.9/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/leak_tracker-10.0.9/lib/ -leak_tracker_flutter_testing -3.2 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.9/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.9/lib/ -leak_tracker_testing -3.2 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/leak_tracker_testing-3.0.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/leak_tracker_testing-3.0.1/lib/ -lints -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/lints-2.1.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/lints-2.1.1/lib/ -logging -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/logging-1.3.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/logging-1.3.0/lib/ -matcher -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/matcher-0.12.17/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/matcher-0.12.17/lib/ -material_color_utilities -2.17 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/material_color_utilities-0.11.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/material_color_utilities-0.11.1/lib/ -meta -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/meta-1.16.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/meta-1.16.0/lib/ -mime -3.2 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/mime-1.0.6/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/mime-1.0.6/lib/ -mobx -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/mobx-2.5.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/mobx-2.5.0/lib/ -mocktail -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/mocktail-1.0.4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/mocktail-1.0.4/lib/ -nested -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/nested-1.0.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/nested-1.0.0/lib/ -node_preamble -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/node_preamble-2.0.2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/node_preamble-2.0.2/lib/ -open_filex -2.17 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/open_filex-4.7.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/open_filex-4.7.0/lib/ -package_config -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/package_config-2.2.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/package_config-2.2.0/lib/ -package_info_plus -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/package_info_plus-6.0.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/package_info_plus-6.0.0/lib/ -package_info_plus_platform_interface -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/package_info_plus_platform_interface-2.0.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/package_info_plus_platform_interface-2.0.1/lib/ -path -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path-1.9.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path-1.9.1/lib/ -path_provider -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider-2.1.5/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider-2.1.5/lib/ -path_provider_android -3.6 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider_android-2.2.17/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider_android-2.2.17/lib/ -path_provider_foundation -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider_foundation-2.4.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider_foundation-2.4.1/lib/ -path_provider_linux -2.19 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider_linux-2.2.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider_linux-2.2.1/lib/ -path_provider_platform_interface -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider_platform_interface-2.1.2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider_platform_interface-2.1.2/lib/ -path_provider_windows -3.2 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider_windows-2.3.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/path_provider_windows-2.3.0/lib/ -pedantic -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/pedantic-1.11.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/pedantic-1.11.1/lib/ -permission_handler -3.5 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler-11.4.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler-11.4.0/lib/ -permission_handler_android -3.5 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler_android-12.1.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler_android-12.1.0/lib/ -permission_handler_apple -2.18 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler_apple-9.4.7/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler_apple-9.4.7/lib/ -permission_handler_html -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler_html-0.1.3+5/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler_html-0.1.3+5/lib/ -permission_handler_platform_interface -3.5 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler_platform_interface-4.3.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler_platform_interface-4.3.0/lib/ -permission_handler_windows -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler_windows-0.2.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/permission_handler_windows-0.2.1/lib/ -petitparser -3.5 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/petitparser-6.1.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/petitparser-6.1.0/lib/ -platform -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/platform-3.1.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/platform-3.1.0/lib/ -plugin_platform_interface -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/plugin_platform_interface-2.1.8/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/plugin_platform_interface-2.1.8/lib/ -pointycastle -3.2 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/pointycastle-4.0.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/pointycastle-4.0.0/lib/ -pool -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/pool-1.5.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/pool-1.5.1/lib/ -postgres -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/postgres-2.6.4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/postgres-2.6.4/lib/ -provider -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/provider-6.1.5/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/provider-6.1.5/lib/ -pub_semver -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/pub_semver-2.2.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/pub_semver-2.2.0/lib/ -pubspec_parse -3.6 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/pubspec_parse-1.5.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/pubspec_parse-1.5.0/lib/ -qs_dart -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/qs_dart-1.3.7+1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/qs_dart-1.3.7+1/lib/ -quiver -2.17 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/quiver-3.2.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/quiver-3.2.1/lib/ -recursive_regex -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/recursive_regex-1.0.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/recursive_regex-1.0.0/lib/ -redis_dart -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/redis_dart-0.1.0+4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/redis_dart-0.1.0+4/lib/ -result_dart -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/result_dart-2.1.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/result_dart-2.1.0/lib/ -rx_notifier -2.17 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/rx_notifier-2.3.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/rx_notifier-2.3.0/lib/ -rx_notifier_annotation -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/rx_notifier_annotation-1.0.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/rx_notifier_annotation-1.0.0/lib/ -rxdart -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/rxdart-0.27.7/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/rxdart-0.27.7/lib/ -sasl_scram -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/sasl_scram-0.1.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/sasl_scram-0.1.1/lib/ -saslprep -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/saslprep-1.0.3/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/saslprep-1.0.3/lib/ -sensors_plus -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/sensors_plus-5.0.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/sensors_plus-5.0.1/lib/ -sensors_plus_platform_interface -2.18 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/sensors_plus_platform_interface-1.2.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/sensors_plus_platform_interface-1.2.0/lib/ -share_plus -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/share_plus-9.0.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/share_plus-9.0.0/lib/ -share_plus_platform_interface -2.18 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/share_plus_platform_interface-4.0.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/share_plus_platform_interface-4.0.0/lib/ -shelf -2.17 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/shelf-1.4.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/shelf-1.4.1/lib/ -shelf_packages_handler -2.17 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/shelf_packages_handler-3.0.2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/shelf_packages_handler-3.0.2/lib/ -shelf_static -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/shelf_static-1.1.3/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/shelf_static-1.1.3/lib/ -shelf_web_socket -2.17 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/shelf_web_socket-1.0.4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/shelf_web_socket-1.0.4/lib/ -source_map_stack_trace -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/source_map_stack_trace-2.1.2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/source_map_stack_trace-2.1.2/lib/ -source_maps -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/source_maps-0.10.13/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/source_maps-0.10.13/lib/ -source_span -3.1 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/source_span-1.10.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/source_span-1.10.1/lib/ -sprintf -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/sprintf-7.0.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/sprintf-7.0.0/lib/ -stack_trace -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/stack_trace-1.12.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/stack_trace-1.12.1/lib/ -stream_channel -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/stream_channel-2.1.4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/stream_channel-2.1.4/lib/ -stream_transform -3.1 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/stream_transform-2.1.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/stream_transform-2.1.1/lib/ -string_scanner -3.1 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/string_scanner-1.4.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/string_scanner-1.4.1/lib/ -term_glyph -3.1 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/term_glyph-1.2.2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/term_glyph-1.2.2/lib/ -test -3.5 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/test-1.25.15/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/test-1.25.15/lib/ -test_api -3.5 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/test_api-0.7.4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/test_api-0.7.4/lib/ -test_core -3.5 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/test_core-0.6.8/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/test_core-0.6.8/lib/ -timezone -2.19 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/timezone-0.9.4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/timezone-0.9.4/lib/ -triple -2.18 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/triple-2.1.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/triple-2.1.0/lib/ -typed_data -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/typed_data-1.3.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/typed_data-1.3.1/lib/ -unorm_dart -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/unorm_dart-0.3.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/unorm_dart-0.3.0/lib/ -url_launcher -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher-6.3.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher-6.3.1/lib/ -url_launcher_android -3.6 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_android-6.3.16/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_android-6.3.16/lib/ -url_launcher_ios -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_ios-6.3.3/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_ios-6.3.3/lib/ -url_launcher_linux -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_linux-3.2.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_linux-3.2.1/lib/ -url_launcher_macos -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_macos-3.2.2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_macos-3.2.2/lib/ -url_launcher_platform_interface -3.1 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_platform_interface-2.3.2/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_platform_interface-2.3.2/lib/ -url_launcher_web -3.6 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_web-2.4.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_web-2.4.1/lib/ -url_launcher_windows -3.4 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_windows-3.1.4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/url_launcher_windows-3.1.4/lib/ -uuid -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/uuid-4.5.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/uuid-4.5.1/lib/ -vector_math -2.14 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/vector_math-2.1.4/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/vector_math-2.1.4/lib/ -vm_service -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/vm_service-15.0.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/vm_service-15.0.0/lib/ -watcher -3.1 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/watcher-1.1.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/watcher-1.1.1/lib/ -weak_map -2.19 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/weak_map-4.0.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/weak_map-4.0.1/lib/ -web -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/web-0.5.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/web-0.5.1/lib/ -web_socket_channel -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/web_socket_channel-2.4.5/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/web_socket_channel-2.4.5/lib/ -webkit_inspection_protocol -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/webkit_inspection_protocol-1.2.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/webkit_inspection_protocol-1.2.1/lib/ -win32 -3.7 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/win32-5.13.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/win32-5.13.0/lib/ -x509 -3.0 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/x509-0.2.4+3/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/x509-0.2.4+3/lib/ -xdg_directories -3.3 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/xdg_directories-1.1.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/xdg_directories-1.1.0/lib/ -xml -3.2 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/xml-6.5.0/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/xml-6.5.0/lib/ -yaml -2.12 -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/yaml-3.1.1/ -file:///C:/Users/jacob/.puro/shared/pub_cache/hosted/pub.dev/yaml-3.1.1/lib/ -flutter_modular_workspace -3.6 -file:///C:/Users/jacob/Projects/modular/ -file:///C:/Users/jacob/Projects/modular/lib/ -flutter_modular -3.6 -file:///C:/Users/jacob/Projects/modular/flutter_modular/ -file:///C:/Users/jacob/Projects/modular/flutter_modular/lib/ -flutter_modular_example -3.6 -file:///C:/Users/jacob/Projects/modular/flutter_modular/example/ -file:///C:/Users/jacob/Projects/modular/flutter_modular/example/lib/ -modular_core -3.6 -file:///C:/Users/jacob/Projects/modular/modular_core/ -file:///C:/Users/jacob/Projects/modular/modular_core/lib/ -shelf_modular -3.6 -file:///C:/Users/jacob/Projects/modular/shelf_modular/ -file:///C:/Users/jacob/Projects/modular/shelf_modular/lib/ -shelf_modular_example -3.6 -file:///C:/Users/jacob/Projects/modular/shelf_modular/example/ -file:///C:/Users/jacob/Projects/modular/shelf_modular/example/lib/ -sky_engine -3.7 -file:///C:/flutter/bin/cache/pkg/sky_engine/ -file:///C:/flutter/bin/cache/pkg/sky_engine/lib/ -flutter -3.7 -file:///C:/flutter/packages/flutter/ -file:///C:/flutter/packages/flutter/lib/ -flutter_test -3.7 -file:///C:/flutter/packages/flutter_test/ -file:///C:/flutter/packages/flutter_test/lib/ -flutter_web_plugins -3.7 -file:///C:/flutter/packages/flutter_web_plugins/ -file:///C:/flutter/packages/flutter_web_plugins/lib/ -2 diff --git a/.dart_tool/package_graph.json b/.dart_tool/package_graph.json deleted file mode 100644 index 533c7384..00000000 --- a/.dart_tool/package_graph.json +++ /dev/null @@ -1,1419 +0,0 @@ -{ - "roots": [ - "flutter_modular", - "flutter_modular_example", - "flutter_modular_workspace", - "modular_core", - "shelf_modular", - "shelf_modular_example" - ], - "packages": [ - { - "name": "flutter_modular_workspace", - "version": "0.0.0", - "dependencies": [], - "devDependencies": [] - }, - { - "name": "modular_core", - "version": "3.4.0", - "dependencies": [ - "auto_injector", - "characters", - "meta" - ], - "devDependencies": [ - "flutterando_analysis", - "test" - ] - }, - { - "name": "flutter_modular", - "version": "6.4.0", - "dependencies": [ - "flutter", - "flutter_web_plugins", - "meta", - "modular_core", - "result_dart" - ], - "devDependencies": [ - "flutter_test", - "flutterando_analysis", - "mocktail" - ] - }, - { - "name": "shelf_modular", - "version": "3.0.1", - "dependencies": [ - "http_parser", - "meta", - "mime", - "modular_core", - "result_dart", - "shelf", - "shelf_web_socket", - "web_socket_channel" - ], - "devDependencies": [ - "flutterando_analysis", - "mocktail", - "test" - ] - }, - { - "name": "flutter_modular_example", - "version": "1.0.0+1", - "dependencies": [ - "alice", - "bloc", - "dartz", - "flutter", - "flutter_mobx", - "flutter_modular", - "flutter_triple", - "http" - ], - "devDependencies": [ - "flutter_lints", - "flutter_test", - "mocktail" - ] - }, - { - "name": "shelf_modular_example", - "version": "1.0.0", - "dependencies": [ - "fpdart", - "hotreloader", - "jose", - "postgres", - "redis_dart", - "shelf", - "shelf_modular", - "uuid" - ], - "devDependencies": [ - "pedantic", - "test" - ] - }, - { - "name": "test", - "version": "1.25.15", - "dependencies": [ - "analyzer", - "async", - "boolean_selector", - "collection", - "coverage", - "http_multi_server", - "io", - "js", - "matcher", - "node_preamble", - "package_config", - "path", - "pool", - "shelf", - "shelf_packages_handler", - "shelf_static", - "shelf_web_socket", - "source_span", - "stack_trace", - "stream_channel", - "test_api", - "test_core", - "typed_data", - "web_socket_channel", - "webkit_inspection_protocol", - "yaml" - ] - }, - { - "name": "pedantic", - "version": "1.11.1", - "dependencies": [] - }, - { - "name": "uuid", - "version": "4.5.1", - "dependencies": [ - "crypto", - "fixnum", - "meta", - "sprintf" - ] - }, - { - "name": "redis_dart", - "version": "0.1.0+4", - "dependencies": [ - "async" - ] - }, - { - "name": "jose", - "version": "0.3.4", - "dependencies": [ - "asn1lib", - "collection", - "crypto_keys", - "http", - "http_parser", - "meta", - "typed_data", - "x509" - ] - }, - { - "name": "postgres", - "version": "2.6.4", - "dependencies": [ - "async", - "buffer", - "charcode", - "collection", - "crypto", - "meta", - "pool", - "sasl_scram", - "stack_trace", - "stream_channel" - ] - }, - { - "name": "fpdart", - "version": "0.0.14", - "dependencies": [] - }, - { - "name": "shelf", - "version": "1.4.1", - "dependencies": [ - "async", - "collection", - "http_parser", - "path", - "stack_trace", - "stream_channel" - ] - }, - { - "name": "hotreloader", - "version": "4.3.0", - "dependencies": [ - "collection", - "logging", - "path", - "stream_transform", - "vm_service", - "watcher" - ] - }, - { - "name": "mocktail", - "version": "1.0.4", - "dependencies": [ - "collection", - "matcher", - "test_api" - ] - }, - { - "name": "flutter_test", - "version": "0.0.0", - "dependencies": [ - "async", - "boolean_selector", - "characters", - "clock", - "collection", - "fake_async", - "flutter", - "leak_tracker", - "leak_tracker_flutter_testing", - "leak_tracker_testing", - "matcher", - "material_color_utilities", - "meta", - "path", - "source_span", - "stack_trace", - "stream_channel", - "string_scanner", - "term_glyph", - "test_api", - "vector_math", - "vm_service" - ] - }, - { - "name": "flutter_lints", - "version": "2.0.3", - "dependencies": [ - "lints" - ] - }, - { - "name": "flutter", - "version": "0.0.0", - "dependencies": [ - "characters", - "collection", - "material_color_utilities", - "meta", - "sky_engine", - "vector_math" - ] - }, - { - "name": "bloc", - "version": "8.1.4", - "dependencies": [ - "meta" - ] - }, - { - "name": "alice", - "version": "0.4.2", - "dependencies": [ - "chopper", - "collection", - "dio", - "flutter", - "flutter_local_notifications", - "http", - "open_filex", - "package_info_plus", - "path_provider", - "permission_handler", - "rxdart", - "sensors_plus", - "share_plus", - "url_launcher" - ] - }, - { - "name": "http", - "version": "1.4.0", - "dependencies": [ - "async", - "http_parser", - "meta", - "web" - ] - }, - { - "name": "flutter_triple", - "version": "2.2.0", - "dependencies": [ - "collection", - "flutter", - "rx_notifier", - "triple" - ] - }, - { - "name": "flutter_mobx", - "version": "2.3.0", - "dependencies": [ - "flutter", - "mobx", - "provider" - ] - }, - { - "name": "dartz", - "version": "0.10.1", - "dependencies": [] - }, - { - "name": "flutterando_analysis", - "version": "0.0.2", - "dependencies": [] - }, - { - "name": "result_dart", - "version": "2.1.0", - "dependencies": [ - "meta" - ] - }, - { - "name": "shelf_web_socket", - "version": "1.0.4", - "dependencies": [ - "shelf", - "stream_channel", - "web_socket_channel" - ] - }, - { - "name": "web_socket_channel", - "version": "2.4.5", - "dependencies": [ - "async", - "crypto", - "stream_channel", - "web" - ] - }, - { - "name": "http_parser", - "version": "4.0.2", - "dependencies": [ - "collection", - "source_span", - "string_scanner", - "typed_data" - ] - }, - { - "name": "mime", - "version": "1.0.6", - "dependencies": [] - }, - { - "name": "meta", - "version": "1.16.0", - "dependencies": [] - }, - { - "name": "flutter_web_plugins", - "version": "0.0.0", - "dependencies": [ - "characters", - "collection", - "flutter", - "material_color_utilities", - "meta", - "vector_math" - ] - }, - { - "name": "characters", - "version": "1.4.0", - "dependencies": [] - }, - { - "name": "auto_injector", - "version": "2.1.0", - "dependencies": [ - "meta", - "uuid" - ] - }, - { - "name": "yaml", - "version": "3.1.1", - "dependencies": [ - "collection", - "source_span", - "string_scanner" - ] - }, - { - "name": "webkit_inspection_protocol", - "version": "1.2.1", - "dependencies": [ - "logging" - ] - }, - { - "name": "typed_data", - "version": "1.3.1", - "dependencies": [ - "collection" - ] - }, - { - "name": "test_core", - "version": "0.6.8", - "dependencies": [ - "analyzer", - "args", - "async", - "boolean_selector", - "collection", - "coverage", - "frontend_server_client", - "glob", - "io", - "meta", - "package_config", - "path", - "pool", - "source_map_stack_trace", - "source_maps", - "source_span", - "stack_trace", - "stream_channel", - "test_api", - "vm_service", - "yaml" - ] - }, - { - "name": "test_api", - "version": "0.7.4", - "dependencies": [ - "async", - "boolean_selector", - "collection", - "meta", - "source_span", - "stack_trace", - "stream_channel", - "string_scanner", - "term_glyph" - ] - }, - { - "name": "stream_channel", - "version": "2.1.4", - "dependencies": [ - "async" - ] - }, - { - "name": "stack_trace", - "version": "1.12.1", - "dependencies": [ - "path" - ] - }, - { - "name": "source_span", - "version": "1.10.1", - "dependencies": [ - "collection", - "path", - "term_glyph" - ] - }, - { - "name": "shelf_static", - "version": "1.1.3", - "dependencies": [ - "convert", - "http_parser", - "mime", - "path", - "shelf" - ] - }, - { - "name": "shelf_packages_handler", - "version": "3.0.2", - "dependencies": [ - "path", - "shelf", - "shelf_static" - ] - }, - { - "name": "pool", - "version": "1.5.1", - "dependencies": [ - "async", - "stack_trace" - ] - }, - { - "name": "path", - "version": "1.9.1", - "dependencies": [] - }, - { - "name": "package_config", - "version": "2.2.0", - "dependencies": [ - "path" - ] - }, - { - "name": "node_preamble", - "version": "2.0.2", - "dependencies": [] - }, - { - "name": "matcher", - "version": "0.12.17", - "dependencies": [ - "async", - "meta", - "stack_trace", - "term_glyph", - "test_api" - ] - }, - { - "name": "js", - "version": "0.7.2", - "dependencies": [] - }, - { - "name": "io", - "version": "1.0.4", - "dependencies": [ - "meta", - "path", - "string_scanner" - ] - }, - { - "name": "http_multi_server", - "version": "3.2.2", - "dependencies": [ - "async" - ] - }, - { - "name": "coverage", - "version": "1.14.0", - "dependencies": [ - "args", - "cli_config", - "glob", - "logging", - "meta", - "package_config", - "path", - "pubspec_parse", - "source_maps", - "stack_trace", - "vm_service" - ] - }, - { - "name": "collection", - "version": "1.19.1", - "dependencies": [] - }, - { - "name": "boolean_selector", - "version": "2.1.2", - "dependencies": [ - "source_span", - "string_scanner" - ] - }, - { - "name": "async", - "version": "2.13.0", - "dependencies": [ - "collection", - "meta" - ] - }, - { - "name": "analyzer", - "version": "7.4.5", - "dependencies": [ - "_fe_analyzer_shared", - "collection", - "convert", - "crypto", - "glob", - "meta", - "package_config", - "path", - "pub_semver", - "source_span", - "watcher", - "yaml" - ] - }, - { - "name": "fixnum", - "version": "1.1.1", - "dependencies": [] - }, - { - "name": "sprintf", - "version": "7.0.0", - "dependencies": [] - }, - { - "name": "crypto", - "version": "3.0.6", - "dependencies": [ - "typed_data" - ] - }, - { - "name": "asn1lib", - "version": "1.6.4", - "dependencies": [] - }, - { - "name": "x509", - "version": "0.2.4+3", - "dependencies": [ - "asn1lib", - "crypto_keys", - "quiver" - ] - }, - { - "name": "crypto_keys", - "version": "0.3.0+2", - "dependencies": [ - "collection", - "meta", - "pointycastle", - "quiver" - ] - }, - { - "name": "charcode", - "version": "1.3.1", - "dependencies": [] - }, - { - "name": "sasl_scram", - "version": "0.1.1", - "dependencies": [ - "buffer", - "collection", - "crypto", - "saslprep" - ] - }, - { - "name": "buffer", - "version": "1.2.3", - "dependencies": [] - }, - { - "name": "watcher", - "version": "1.1.1", - "dependencies": [ - "async", - "path" - ] - }, - { - "name": "vm_service", - "version": "15.0.0", - "dependencies": [] - }, - { - "name": "stream_transform", - "version": "2.1.1", - "dependencies": [] - }, - { - "name": "logging", - "version": "1.3.0", - "dependencies": [] - }, - { - "name": "term_glyph", - "version": "1.2.2", - "dependencies": [] - }, - { - "name": "string_scanner", - "version": "1.4.1", - "dependencies": [ - "source_span" - ] - }, - { - "name": "material_color_utilities", - "version": "0.11.1", - "dependencies": [ - "collection" - ] - }, - { - "name": "leak_tracker_testing", - "version": "3.0.1", - "dependencies": [ - "leak_tracker", - "matcher", - "meta" - ] - }, - { - "name": "leak_tracker", - "version": "10.0.9", - "dependencies": [ - "clock", - "collection", - "meta", - "path", - "vm_service" - ] - }, - { - "name": "leak_tracker_flutter_testing", - "version": "3.0.9", - "dependencies": [ - "flutter", - "leak_tracker", - "leak_tracker_testing", - "matcher", - "meta" - ] - }, - { - "name": "vector_math", - "version": "2.1.4", - "dependencies": [] - }, - { - "name": "clock", - "version": "1.1.2", - "dependencies": [] - }, - { - "name": "fake_async", - "version": "1.3.3", - "dependencies": [ - "clock", - "collection" - ] - }, - { - "name": "lints", - "version": "2.1.1", - "dependencies": [] - }, - { - "name": "sky_engine", - "version": "0.0.0", - "dependencies": [] - }, - { - "name": "url_launcher", - "version": "6.3.1", - "dependencies": [ - "flutter", - "url_launcher_android", - "url_launcher_ios", - "url_launcher_linux", - "url_launcher_macos", - "url_launcher_platform_interface", - "url_launcher_web", - "url_launcher_windows" - ] - }, - { - "name": "chopper", - "version": "7.4.0", - "dependencies": [ - "equatable", - "http", - "logging", - "meta", - "qs_dart" - ] - }, - { - "name": "share_plus", - "version": "9.0.0", - "dependencies": [ - "cross_file", - "ffi", - "file", - "flutter", - "flutter_web_plugins", - "meta", - "mime", - "share_plus_platform_interface", - "url_launcher_linux", - "url_launcher_platform_interface", - "url_launcher_web", - "url_launcher_windows", - "web", - "win32" - ] - }, - { - "name": "sensors_plus", - "version": "5.0.1", - "dependencies": [ - "flutter", - "flutter_web_plugins", - "sensors_plus_platform_interface" - ] - }, - { - "name": "open_filex", - "version": "4.7.0", - "dependencies": [ - "ffi", - "flutter" - ] - }, - { - "name": "package_info_plus", - "version": "6.0.0", - "dependencies": [ - "ffi", - "flutter", - "flutter_web_plugins", - "http", - "meta", - "package_info_plus_platform_interface", - "path", - "web", - "win32" - ] - }, - { - "name": "permission_handler", - "version": "11.4.0", - "dependencies": [ - "flutter", - "meta", - "permission_handler_android", - "permission_handler_apple", - "permission_handler_html", - "permission_handler_platform_interface", - "permission_handler_windows" - ] - }, - { - "name": "path_provider", - "version": "2.1.5", - "dependencies": [ - "flutter", - "path_provider_android", - "path_provider_foundation", - "path_provider_linux", - "path_provider_platform_interface", - "path_provider_windows" - ] - }, - { - "name": "rxdart", - "version": "0.27.7", - "dependencies": [] - }, - { - "name": "flutter_local_notifications", - "version": "17.2.4", - "dependencies": [ - "clock", - "flutter", - "flutter_local_notifications_linux", - "flutter_local_notifications_platform_interface", - "timezone" - ] - }, - { - "name": "dio", - "version": "5.8.0+1", - "dependencies": [ - "async", - "collection", - "dio_web_adapter", - "http_parser", - "meta", - "path" - ] - }, - { - "name": "web", - "version": "0.5.1", - "dependencies": [] - }, - { - "name": "rx_notifier", - "version": "2.3.0", - "dependencies": [ - "collection", - "flutter", - "rx_notifier_annotation" - ] - }, - { - "name": "triple", - "version": "2.1.0", - "dependencies": [ - "async", - "meta" - ] - }, - { - "name": "provider", - "version": "6.1.5", - "dependencies": [ - "collection", - "flutter", - "nested" - ] - }, - { - "name": "mobx", - "version": "2.5.0", - "dependencies": [ - "collection", - "meta" - ] - }, - { - "name": "source_maps", - "version": "0.10.13", - "dependencies": [ - "source_span" - ] - }, - { - "name": "source_map_stack_trace", - "version": "2.1.2", - "dependencies": [ - "path", - "source_maps", - "stack_trace" - ] - }, - { - "name": "glob", - "version": "2.1.3", - "dependencies": [ - "async", - "collection", - "file", - "path", - "string_scanner" - ] - }, - { - "name": "frontend_server_client", - "version": "4.0.0", - "dependencies": [ - "async", - "path" - ] - }, - { - "name": "args", - "version": "2.4.0", - "dependencies": [] - }, - { - "name": "convert", - "version": "3.1.2", - "dependencies": [ - "typed_data" - ] - }, - { - "name": "pubspec_parse", - "version": "1.5.0", - "dependencies": [ - "checked_yaml", - "collection", - "json_annotation", - "pub_semver", - "yaml" - ] - }, - { - "name": "cli_config", - "version": "0.2.0", - "dependencies": [ - "args", - "yaml" - ] - }, - { - "name": "pub_semver", - "version": "2.2.0", - "dependencies": [ - "collection" - ] - }, - { - "name": "_fe_analyzer_shared", - "version": "82.0.0", - "dependencies": [ - "meta" - ] - }, - { - "name": "quiver", - "version": "3.2.1", - "dependencies": [ - "matcher" - ] - }, - { - "name": "pointycastle", - "version": "4.0.0", - "dependencies": [ - "collection", - "convert" - ] - }, - { - "name": "saslprep", - "version": "1.0.3", - "dependencies": [ - "unorm_dart" - ] - }, - { - "name": "url_launcher_windows", - "version": "3.1.4", - "dependencies": [ - "flutter", - "url_launcher_platform_interface" - ] - }, - { - "name": "url_launcher_web", - "version": "2.4.1", - "dependencies": [ - "flutter", - "flutter_web_plugins", - "url_launcher_platform_interface", - "web" - ] - }, - { - "name": "url_launcher_platform_interface", - "version": "2.3.2", - "dependencies": [ - "flutter", - "plugin_platform_interface" - ] - }, - { - "name": "url_launcher_macos", - "version": "3.2.2", - "dependencies": [ - "flutter", - "url_launcher_platform_interface" - ] - }, - { - "name": "url_launcher_linux", - "version": "3.2.1", - "dependencies": [ - "flutter", - "url_launcher_platform_interface" - ] - }, - { - "name": "url_launcher_ios", - "version": "6.3.3", - "dependencies": [ - "flutter", - "url_launcher_platform_interface" - ] - }, - { - "name": "url_launcher_android", - "version": "6.3.16", - "dependencies": [ - "flutter", - "url_launcher_platform_interface" - ] - }, - { - "name": "qs_dart", - "version": "1.3.7+1", - "dependencies": [ - "collection", - "equatable", - "meta", - "recursive_regex", - "weak_map" - ] - }, - { - "name": "equatable", - "version": "2.0.7", - "dependencies": [ - "collection", - "meta" - ] - }, - { - "name": "win32", - "version": "5.13.0", - "dependencies": [ - "ffi" - ] - }, - { - "name": "ffi", - "version": "2.1.4", - "dependencies": [] - }, - { - "name": "file", - "version": "6.1.4", - "dependencies": [ - "meta", - "path" - ] - }, - { - "name": "share_plus_platform_interface", - "version": "4.0.0", - "dependencies": [ - "cross_file", - "flutter", - "meta", - "mime", - "path_provider", - "plugin_platform_interface", - "uuid" - ] - }, - { - "name": "cross_file", - "version": "0.3.4+2", - "dependencies": [ - "meta", - "web" - ] - }, - { - "name": "sensors_plus_platform_interface", - "version": "1.2.0", - "dependencies": [ - "flutter", - "logging", - "meta", - "plugin_platform_interface" - ] - }, - { - "name": "package_info_plus_platform_interface", - "version": "2.0.1", - "dependencies": [ - "flutter", - "meta", - "plugin_platform_interface" - ] - }, - { - "name": "permission_handler_platform_interface", - "version": "4.3.0", - "dependencies": [ - "flutter", - "meta", - "plugin_platform_interface" - ] - }, - { - "name": "permission_handler_windows", - "version": "0.2.1", - "dependencies": [ - "flutter", - "permission_handler_platform_interface" - ] - }, - { - "name": "permission_handler_html", - "version": "0.1.3+5", - "dependencies": [ - "flutter", - "flutter_web_plugins", - "permission_handler_platform_interface", - "web" - ] - }, - { - "name": "permission_handler_apple", - "version": "9.4.7", - "dependencies": [ - "flutter", - "permission_handler_platform_interface" - ] - }, - { - "name": "permission_handler_android", - "version": "12.1.0", - "dependencies": [ - "flutter", - "permission_handler_platform_interface" - ] - }, - { - "name": "path_provider_windows", - "version": "2.3.0", - "dependencies": [ - "ffi", - "flutter", - "path", - "path_provider_platform_interface" - ] - }, - { - "name": "path_provider_platform_interface", - "version": "2.1.2", - "dependencies": [ - "flutter", - "platform", - "plugin_platform_interface" - ] - }, - { - "name": "path_provider_linux", - "version": "2.2.1", - "dependencies": [ - "ffi", - "flutter", - "path", - "path_provider_platform_interface", - "xdg_directories" - ] - }, - { - "name": "path_provider_foundation", - "version": "2.4.1", - "dependencies": [ - "flutter", - "path_provider_platform_interface" - ] - }, - { - "name": "path_provider_android", - "version": "2.2.17", - "dependencies": [ - "flutter", - "path_provider_platform_interface" - ] - }, - { - "name": "timezone", - "version": "0.9.4", - "dependencies": [ - "path" - ] - }, - { - "name": "flutter_local_notifications_platform_interface", - "version": "7.2.0", - "dependencies": [ - "flutter", - "plugin_platform_interface" - ] - }, - { - "name": "flutter_local_notifications_linux", - "version": "4.0.1", - "dependencies": [ - "dbus", - "ffi", - "flutter", - "flutter_local_notifications_platform_interface", - "path", - "xdg_directories" - ] - }, - { - "name": "dio_web_adapter", - "version": "2.1.1", - "dependencies": [ - "dio", - "http_parser", - "meta", - "web" - ] - }, - { - "name": "rx_notifier_annotation", - "version": "1.0.0", - "dependencies": [] - }, - { - "name": "nested", - "version": "1.0.0", - "dependencies": [ - "flutter" - ] - }, - { - "name": "json_annotation", - "version": "4.9.0", - "dependencies": [ - "meta" - ] - }, - { - "name": "checked_yaml", - "version": "2.0.4", - "dependencies": [ - "json_annotation", - "source_span", - "yaml" - ] - }, - { - "name": "unorm_dart", - "version": "0.3.0", - "dependencies": [] - }, - { - "name": "plugin_platform_interface", - "version": "2.1.8", - "dependencies": [ - "meta" - ] - }, - { - "name": "weak_map", - "version": "4.0.1", - "dependencies": [] - }, - { - "name": "recursive_regex", - "version": "1.0.0", - "dependencies": [ - "meta" - ] - }, - { - "name": "platform", - "version": "3.1.0", - "dependencies": [] - }, - { - "name": "xdg_directories", - "version": "1.1.0", - "dependencies": [ - "meta", - "path" - ] - }, - { - "name": "dbus", - "version": "0.7.11", - "dependencies": [ - "args", - "ffi", - "meta", - "xml" - ] - }, - { - "name": "xml", - "version": "6.5.0", - "dependencies": [ - "collection", - "meta", - "petitparser" - ] - }, - { - "name": "petitparser", - "version": "6.1.0", - "dependencies": [ - "collection", - "meta" - ] - } - ], - "configVersion": 1 -} \ No newline at end of file diff --git a/.dart_tool/pub/workspace_ref.json b/.dart_tool/pub/workspace_ref.json deleted file mode 100644 index bcb27ed1..00000000 --- a/.dart_tool/pub/workspace_ref.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "workspaceRoot": "..\\.." -} diff --git a/.dart_tool/version b/.dart_tool/version deleted file mode 100644 index c058d50e..00000000 --- a/.dart_tool/version +++ /dev/null @@ -1 +0,0 @@ -3.32.1 \ No newline at end of file diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies deleted file mode 100644 index 12798fc4..00000000 --- a/.flutter-plugins-dependencies +++ /dev/null @@ -1 +0,0 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_local_notifications","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\flutter_local_notifications-14.1.5\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_filex","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\open_filex-4.7.0\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\package_info_plus-4.2.0\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.4.1\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"permission_handler_apple","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\permission_handler_apple-9.1.4\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sensors_plus","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\sensors_plus-3.1.0\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\share_plus-7.2.2\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_ios","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\url_launcher_ios-6.3.3\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"flutter_local_notifications","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\flutter_local_notifications-14.1.5\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"open_filex","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\open_filex-4.7.0\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\package_info_plus-4.2.0\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\path_provider_android-2.2.17\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"permission_handler_android","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\permission_handler_android-10.3.6\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"sensors_plus","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\sensors_plus-3.1.0\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\share_plus-7.2.2\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_android","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\url_launcher_android-6.3.16\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"flutter_local_notifications","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\flutter_local_notifications-14.1.5\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\package_info_plus-4.2.0\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.4.1\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\share_plus-7.2.2\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\url_launcher_macos-3.2.2\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"flutter_local_notifications_linux","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\flutter_local_notifications_linux-4.0.1\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"package_info_plus","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\package_info_plus-4.2.0\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\path_provider_linux-2.2.1\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\share_plus-7.2.2\\\\","native_build":false,"dependencies":["url_launcher_linux"],"dev_dependency":false},{"name":"url_launcher_linux","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\url_launcher_linux-3.2.1\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"package_info_plus","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\package_info_plus-4.2.0\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\path_provider_windows-2.3.0\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"permission_handler_windows","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\permission_handler_windows-0.1.3\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\share_plus-7.2.2\\\\","native_build":true,"dependencies":["url_launcher_windows"],"dev_dependency":false},{"name":"url_launcher_windows","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\url_launcher_windows-3.1.4\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"package_info_plus","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\package_info_plus-4.2.0\\\\","dependencies":[],"dev_dependency":false},{"name":"sensors_plus","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\sensors_plus-3.1.0\\\\","dependencies":[],"dev_dependency":false},{"name":"share_plus","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\share_plus-7.2.2\\\\","dependencies":["url_launcher_web"],"dev_dependency":false},{"name":"url_launcher_web","path":"C:\\\\Users\\\\jacob\\\\.puro\\\\shared\\\\pub_cache\\\\hosted\\\\pub.dev\\\\url_launcher_web-2.4.1\\\\","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"flutter_local_notifications","dependencies":["flutter_local_notifications_linux"]},{"name":"flutter_local_notifications_linux","dependencies":[]},{"name":"open_filex","dependencies":[]},{"name":"package_info_plus","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":["permission_handler_android","permission_handler_apple","permission_handler_windows"]},{"name":"permission_handler_android","dependencies":[]},{"name":"permission_handler_apple","dependencies":[]},{"name":"permission_handler_windows","dependencies":[]},{"name":"sensors_plus","dependencies":[]},{"name":"share_plus","dependencies":["url_launcher_web","url_launcher_windows","url_launcher_linux"]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2025-06-10 13:34:22.468685","version":"3.32.1","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/---feature-request.md b/.github/ISSUE_TEMPLATE/---feature-request.md index f4f71b6c..ee250583 100644 --- a/.github/ISSUE_TEMPLATE/---feature-request.md +++ b/.github/ISSUE_TEMPLATE/---feature-request.md @@ -7,9 +7,6 @@ assignees: '' --- -**Project Name** -flutter_modular, shelf_modular, etc... - **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 058e9d7a..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,7 +0,0 @@ -version: 2 -enable-beta-ecosystems: true -updates: - - package-ecosystem: "pub" - directory: "/" - schedule: - interval: "daily" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0dc15990..8feea153 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -13,8 +13,8 @@ relevant checkboxes (`[x]`). This will ensure a smooth and quick review process. - [ ] The title of my PR starts with a [Conventional Commit] prefix (`fix:`, `feat:`, `docs:` etc). - [ ] I have read the [Contributor Guide] and followed the process outlined for submitting PRs. - [ ] I have updated/added tests for ALL new/updated/fixed functionality. -- [ ] I have updated/added relevant documentation in `docs` and added dartdoc comments with `///`. -- [ ] I have updated/added relevant examples in `examples`. +- [ ] I have updated/added relevant documentation in `doc` and added dartdoc comments with `///`. +- [ ] I have updated/added relevant examples in `example`. ## Breaking Change diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml deleted file mode 100644 index d13ea193..00000000 --- a/.github/workflows/cd.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Publish package to pub.dev -on: - pull_request: - branches: - - production -jobs: - build: - runs-on: ubuntu-latest - container: - image: google/dart:latest - steps: - - uses: actions/checkout@v1 - - name: Setup credentials - run: | - mkdir -p ~/.pub-cache - cat < ~/.pub-cache/credentials.json - { - "accessToken":"${{ secrets.OAUTH_ACCESS_TOKEN }}", - "refreshToken":"${{ secrets.OAUTH_REFRESH_TOKEN }}", - "tokenEndpoint":"https://accounts.google.com/o/oauth2/token", - "scopes": [ "openid", "https://www.googleapis.com/auth/userinfo.email" ], - "expiration": 1570721159347 - } - EOF - - name: Publish package - run: pub publish -f - working-directory: flutter_modular diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..68a5c13d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + analyze-and-test: + name: Analyze & Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: subosito/flutter-action@v2 + with: + channel: stable + flutter-version: '3.41.7' + + - name: Install dependencies + run: flutter pub get + + - name: Verify formatting + run: dart format --output=none --set-exit-if-changed lib test + + - name: Analyze package + run: flutter analyze + + - name: Test package + run: flutter test diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml deleted file mode 100644 index ae10fad4..00000000 --- a/.github/workflows/dart.yml +++ /dev/null @@ -1,85 +0,0 @@ -name: CI - -on: - pull_request: - branches: - - master -jobs: - flutter_modular: - name: flutter modular - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v1 - with: - java-version: '12.x' - - uses: subosito/flutter-action@v1 - with: - flutter-version: '3.10.6' - - - name: get packages - run: flutter pub get - working-directory: flutter_modular - - - name: Flutter Test - run: flutter test --coverage --coverage-path ../coverage/lcov.info - working-directory: flutter_modular - - - name: Codecov GitHub Action - uses: codecov/codecov-action@v2.0.3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - - modular_core: - name: modular core - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v1 - with: - java-version: '12.x' - - uses: subosito/flutter-action@v1 - with: - flutter-version: '3.10.6' - - - name: get packages - run: flutter pub get - working-directory: modular_core - - - name: Flutter Test - run: flutter test --coverage --coverage-path ../coverage/lcov.info - working-directory: modular_core - - - name: Codecov GitHub Action - uses: codecov/codecov-action@v2.0.3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - - shelf_modular: - name: shelf modular - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v1 - with: - java-version: '12.x' - - uses: subosito/flutter-action@v1 - with: - flutter-version: '3.10.6' - - - name: get packages - run: flutter pub get - working-directory: shelf_modular - - - name: Flutter Test - run: flutter test --coverage --coverage-path ../coverage/lcov.info - working-directory: shelf_modular - - - name: Codecov GitHub Action - uses: codecov/codecov-action@v2.0.3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..a8a71cec --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,19 @@ +name: Publish to pub.dev + +# Publishes on a version tag (e.g. v7.0.0, v7.0.0-dev.1) via pub.dev's automated +# publishing (GitHub OIDC — no stored credentials). One-time setup required on +# pub.dev: package Admin → Automated publishing → enable GitHub Actions for +# repo `Flutterando/modular` with tag pattern `v{{version}}`. +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+*' + +jobs: + publish: + permissions: + id-token: write # required for OIDC token exchange with pub.dev + # Flutter package → the reusable workflow must use the Flutter SDK. + uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 + with: + flutter: true diff --git a/.gitignore b/.gitignore index 671e1c91..2d81a932 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,29 @@ +# Miscellaneous +*.class +*.log +*.swp +.DS_Store +.atom/ +.history +.svn/ + +# IntelliJ / VS Code +*.iml +*.ipr +*.iws +.idea/ +.vscode/ + +# Flutter / Dart / Pub +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ +coverage/ + +# A library package should not commit a resolved lockfile or local overrides. +pubspec.lock pubspec_overrides.yaml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index f7c124cb..00000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -modular \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 5679a82c..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/melos_bootstrap.xml b/.idea/runConfigurations/melos_bootstrap.xml deleted file mode 100644 index f0407084..00000000 --- a/.idea/runConfigurations/melos_bootstrap.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/.idea/runConfigurations/melos_clean.xml b/.idea/runConfigurations/melos_clean.xml deleted file mode 100644 index 452618ae..00000000 --- a/.idea/runConfigurations/melos_clean.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/.idea/runConfigurations/melos_flutter_run_example.xml b/.idea/runConfigurations/melos_flutter_run_example.xml deleted file mode 100644 index 80174f7e..00000000 --- a/.idea/runConfigurations/melos_flutter_run_example.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/melos_flutter_run_flutter_modular_example.xml b/.idea/runConfigurations/melos_flutter_run_flutter_modular_example.xml deleted file mode 100644 index ec520993..00000000 --- a/.idea/runConfigurations/melos_flutter_run_flutter_modular_example.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/melos_flutter_run_modular_bloc_bind_example.xml b/.idea/runConfigurations/melos_flutter_run_modular_bloc_bind_example.xml deleted file mode 100644 index 622958f7..00000000 --- a/.idea/runConfigurations/melos_flutter_run_modular_bloc_bind_example.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/melos_flutter_run_modular_triple_bind_example.xml b/.idea/runConfigurations/melos_flutter_run_modular_triple_bind_example.xml deleted file mode 100644 index 4191511e..00000000 --- a/.idea/runConfigurations/melos_flutter_run_modular_triple_bind_example.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/melos_flutter_test_flutter_modular.xml b/.idea/runConfigurations/melos_flutter_test_flutter_modular.xml deleted file mode 100644 index 0bc62646..00000000 --- a/.idea/runConfigurations/melos_flutter_test_flutter_modular.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/melos_run_clean.xml b/.idea/runConfigurations/melos_run_clean.xml deleted file mode 100644 index 98e2bfe1..00000000 --- a/.idea/runConfigurations/melos_run_clean.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/.idea/runConfigurations/melos_run_pubget.xml b/.idea/runConfigurations/melos_run_pubget.xml deleted file mode 100644 index 3d1dda58..00000000 --- a/.idea/runConfigurations/melos_run_pubget.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/.idea/runConfigurations/melos_run_tests.xml b/.idea/runConfigurations/melos_run_tests.xml deleted file mode 100644 index 90e1f197..00000000 --- a/.idea/runConfigurations/melos_run_tests.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/.pubignore b/.pubignore new file mode 100644 index 00000000..1e18f790 --- /dev/null +++ b/.pubignore @@ -0,0 +1,36 @@ +# What reaches pub.dev is only the package essentials: +# pubspec.yaml, LICENSE, README.md, CHANGELOG.md, analysis_options.yaml, +# lib/, and example/README.md (the snippet shown on the Example tab). +# Everything listed below is repo-only. +# +# NOTE: when a .pubignore exists, pub uses it INSTEAD of .gitignore at this +# level — so build/tooling artifacts must be re-listed here too. + +# Build / tooling artifacts +build/ +.dart_tool/ +coverage/ +*.log +*.lock +.DS_Store + +# Tests are not needed by consumers of the package. +test/ + +# From the example, pub.dev only needs the README; the rest of the demo app +# (lib/, test/, pubspec.yaml, platform config) is repo-only. +example/* +!example/README.md + +# Docs site, CI, IDE, contributor and project-meta files. +doc/ +.github/ +.idea/ +.vscode/ +.gitignore +.pubignore +.metadata +.all-contributorsrc +devtools_options.yaml +flutter_modular.png +CONTRIBUTING.md diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 3d19795b..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "dart.flutterSdkPath": "/Users/jacob/.puro/envs/stable/flutter", - "dart.lineLength": 80, - "editor.formatOnSave": true, - "dart.sdkPath": "/Users/jacob/.puro/envs/stable/flutter/bin/cache/dart-sdk" -} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..89fca0ae --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,34 @@ +# Changelog + +## 7.0.0-dev.1 + +Ground-up rewrite of flutter_modular. **Breaking:** the v6 API (`Module` with +`List get binds` / `List get routes`, `Bind`, `ChildRoute`, +`ModuleRoute`, the global `Modular` facade, and the `modular_core` engine) is +replaced. v7 is a single, self-contained Flutter package (depends directly on +`auto_injector` + `web`; `modular_core` is gone). + +- **Modules are DI + Routes only**, declared functionally with + `createModule(register:)` and a flat `ModularContext` (`addSingleton`/`add*`, + `route(path, child:, provide:, children:, guards:, transition:)`, + `module(value, {at})` to include shared deps or mount submodules). Deduped by + identity; path-less modules are root-owned (shared), path-bearing modules are + features with their own DI lifecycle (bound on first route entry, disposed on + last exit). +- **Navigator 2.0**, fully declarative: hierarchical route matching with + `/:params`, `RouterOutlet` for persistent shells with their own nested stack, + guards/redirects, transitions. `context.pushNamed`/`navigate`/`replace`/`pop` + (+ `popUntil`/`popAndPushNamed`/`pushNamedAndRemoveUntil`). URL mirrors the + stack base; pushes stay out of the URL by design. Relative routes resolve + against the current location. +- **Page-scoped state** via `provide`: `addChangeNotifier` / `addStream` / + `addDisposable` build state 1:1 with the view in a page-local injector and + dispose it when the route leaves. Read with `context.watch`/`read`/`select` + and the `Consumer`/`Selector` widgets. App-scoped state goes on + `ModularApp(provide:)` (above `MaterialApp`). +- **`context.routeState()`** — reactive access to the current `RouteState` + (uri + resolved params + arguments) for route-aware chrome. +- `inject()` for runtime resolution where a constructor can't inject (e.g. + route guards). + +See [`example/`](example/) for a complete app exercising every feature. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b027e51..db5f7f47 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,9 +41,9 @@ For a contribution to be accepted: - Documentation should always be updated or added.* - Examples should always be updated or added.* - Tests should always be updated or added.* -- Format the Dart code accordingly with `flutter format`. -- Your code should pass the analyzer checks `melos run analyze`. -- Your code should pass all tests `melos run test`. +- Format the Dart code accordingly with `dart format .`. +- Your code should pass the analyzer checks `flutter analyze`. +- Your code should pass all tests `flutter test`. - Start your PR title with a [conventional commit] type (`feat:`, `fix:` etc). @@ -61,27 +61,25 @@ and it will be automatically reflected in the PR. (e.g. `git clone git@github.com:/modular.git`). ### Environment Setup -Modular uses [Melos] to manage the project and dependencies. +`flutter_modular` is a single Flutter package with its demo in `example/`, wired together as a +[pub workspace] — one `flutter pub get` at the root resolves both. -To install Melos, run the following command from your terminal: +At the root of your locally cloned repository: ```bash -flutter pub global activate melos +flutter pub get # resolves the package + example +flutter analyze # static analysis +flutter test # the package test suite ``` -Next, at the root of your locally cloned repository bootstrap the projects dependencies: +To run the checks for the demo app: ```bash -melos bootstrap +cd example +flutter analyze +flutter test ``` -The bootstrap command locally links all dependencies within the project without having to -provide manual [`dependency_overrides`][pubspec doc]. This allows all -plugins, examples and tests to build from the local clone project. You should only need to run this -command once. - -> You do not need to run `flutter pub get` once bootstrap has been completed. - ### Performing changes - Create a new local branch from `main` (e.g. `git checkout -b my-new-feature`) - Make your changes. @@ -119,6 +117,5 @@ Examples of PR titles: [PRs]: https://github.com/Flutterando/modular/pulls [fork guide]: https://guides.github.com/activities/forking/#fork [Discord]: https://discord.gg/7qvSXRMMYw -[Melos]: https://github.com/invertase/melos -[pubspec doc]: https://dart.dev/tools/pub/pubspec +[pub workspace]: https://dart.dev/tools/pub/workspaces [conventional commit]: https://www.conventionalcommits.org diff --git a/README.md b/README.md index 8c2bd827..46a090f5 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,11 @@ If it's for the Flutterando version of the template just send a message to us (o
  • Usage
    1. -
    2. Starting a project
    3. -
    4. The ModularApp
    5. -
    6. Creating the Main Module
    7. +
    8. Install
    9. +
    10. Declare a module
    11. +
    12. Bootstrap with ModularApp
    13. +
    14. Navigate
    15. +
    16. Page-scoped state
  • Contributing
  • @@ -178,118 +180,74 @@ Go to the next topic and start your journey towards an intelligent structure. ##
    ✨ Usage
    -**flutter_modular** was built using the engine of **modular_core** that's responsible for the dependency injection system and route management. The routing system emulates a tree of modules, just like Flutter does in it's widget trees. Therefore we can add one module inside another one by creating links to the parent module. +> **flutter_modular 7 is a ground-up rewrite.** A **Module** is now exactly the two things that couple a Flutter app — **Dependency Injection + Routes** — declared with a small functional API. State is **page-scoped**, tied to the route lifecycle, so ownership and disposal stop being your problem and the durable truth lives in a repository/service registered in DI. Full, runnable demonstrations (nested routes, `RouterOutlet` shells, route guards, per-module DI lifecycle, `arguments`/pop-results) live in [`example/`](example/). -##
    Starting a project
    +### Install -Our first goal will be the creation of a simple app with no defined structure or architecture yet, so that we can study the initial components of **flutter_modular** - -Create a new Flutter project: -``` -flutter create my_smart_app -``` - -Now add the **flutter_modular** to pubspec.yaml: ```yaml - dependencies: - flutter_modular: any - + flutter_modular: ^7.0.0-dev.1 ``` -If that succeeded, we are ready to move on! - ->**💡 TIP:** Flutter's CLI has a tool that makes package installation easier in the project. Use the command: ->`(flutter pub add flutter_modular)` - -##
    The ModularApp
    +or run `flutter pub add flutter_modular`. -We need to add a **ModularApp** Widget in the root of our project. MainModule and MainWidget will be created in the next steps, but for now let's change our **main.dart** file: +### Declare a module -```dart title="lib/main.dart" +A module groups routes and dependency injection. Shared dependencies are registered with `addSingleton`/`add*`, routes with `route(...)`, and submodules with `module(...)`. -import 'package:flutter/material.dart'; - -void main(){ - return runApp(ModularApp(module: /**/, child: /**/)); -} +```dart +import 'package:flutter_modular/flutter_modular.dart'; +final appModule = createModule(register: (c) { + c + ..addSingleton(Counter.new) // shared dependency (SSoT) + ..route('/', child: (ctx, state) => const HomePage()) + ..route('/details/:id', + child: (ctx, state) => DetailsPage(id: state.params['id']!)); +}); ``` -**ModularApp** forces us to add a main Module and main Widget. What are we going to do next? -This Widget does the initial setup so everything can work as expected. For more details go to **ModularApp** doc. - ->**💡 TIP:** It's important that **ModularApp** is the first widget in your app! - - -##
    Creating the Main Module
    - -A module represents a set of Routes and Binds. -- **ROUTE**: Page setup eligible for navigation. -- **BIND**: Represents an object that will be available for injection to other dependencies. - -We'll see more info about these topics further below. +### Bootstrap with ModularApp -We can have several modules, but for now, let's just create a main module called **AppModule**: +`ModularApp` is the first widget, above `MaterialApp`. It bootstraps the module, owns the injector, and exposes the router config. -```dart title="lib/main.dart" {8-16} -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; - -void main(){ - return runApp(ModularApp(module: AppModule(), child: )); -} +```dart +void main() => runApp( + ModularApp(module: appModule, child: const AppRoot()), + ); -class AppModule extends Module { - @override - List get binds => []; +class AppRoot extends StatelessWidget { + const AppRoot({super.key}); @override - List get routes => []; + Widget build(BuildContext context) => MaterialApp.router( + routerConfig: ModularApp.routerConfigOf(context), + ); } ``` -Note that the module is just a class that inherits from the **Module** class, overriding the **binds** and **routes** properties. -With this we have a route and injection mechanism separate from the application and can be both applied in a global context (as we are doing) or in a local context, for example, creating a module that contains only binds and routes only for a specific feature! - -We've added **AppModule** to ModularApp. Now we need an initial route, so let's create a StatelessWidget to serve as the home page. +### Navigate -```dart title="lib/main.dart" {14,18-27} -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; +```dart +context.pushNamed('/details/42'); // stacks a page (push stays out of the URL) +context.navigate('/'); // replaces the stack (owns the URL, resets history) +context.pop(result); // pops, delivering a result to the awaiting pushNamed +``` -void main(){ - return runApp(ModularApp(module: AppModule(), child: )); -} +### Page-scoped state -class AppModule extends Module { - @override - List get binds => []; +State lives 1:1 with a view via `provide` — built in a page-local scope and disposed when the route leaves, so there are no floating globals and no manual `dispose`. - @override - List get routes => [ - ChildRoute('/', child: (context, args) => HomePage()), - ]; -} +```dart +c.route('/counter', + provide: (s) => s.addChangeNotifier(CounterViewModel.new), + child: (ctx, state) => const CounterPage(), +); -class HomePage extends StatelessWidget { - Widget build(BuildContext context){ - return Scaffold( - appBar: AppBar(title: Text('Home Page')), - body: Center( - child: Text('This is initial page'), - ), - ); - } -} +// inside the page: +final vm = context.watch(); // rebuilds when the VM notifies ``` -We've created a Widget called **HomePage** and added its instances in a route called **ChildRoute**. - ->**💡 TIP:** There are two ModularRoute types: **ChildRoute** and **ModuleRoute**. - >- **ChildRoute**: Serves to build a Widget. - >- **ModuleRoute**: Concatenates another module. - ##
    🧑‍💻 Contributing
    @@ -308,7 +266,7 @@ Remember to include a tag, and to follow [Conventional Commits](https://www.conv

    (back to top)

    - + ##
    💬 Contact
    Flutterando Community diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 00000000..2e90f244 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,6 @@ +include: package:flutterando_analysis/flutter_package.yaml + +linter: + rules: + public_member_api_docs: false + require_trailing_commas: false diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 00000000..fa0b357c --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/flutter_modular/example/.gitignore b/example/.gitignore similarity index 87% rename from flutter_modular/example/.gitignore rename to example/.gitignore index 5942e1d2..3820a95c 100644 --- a/flutter_modular/example/.gitignore +++ b/example/.gitignore @@ -5,9 +5,12 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ +migrate_working_dir/ # IntelliJ related *.iml @@ -24,15 +27,11 @@ **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ -.flutter-plugins .flutter-plugins-dependencies -.packages .pub-cache/ .pub/ /build/ - -# Web related -lib/generated_plugin_registrant.dart +/coverage/ # Symbolication related app.*.symbols @@ -44,4 +43,3 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release -pubspec.lock diff --git a/example/.metadata b/example/.metadata new file mode 100644 index 00000000..7cd7e6ae --- /dev/null +++ b/example/.metadata @@ -0,0 +1,45 @@ +# 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 and should not be manually edited. + +version: + revision: "cc0734ac716fbb8b90f3f9db8020958b1553afa7" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: android + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: ios + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: linux + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: macos + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: web + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + - platform: windows + create_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + base_revision: cc0734ac716fbb8b90f3f9db8020958b1553afa7 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/example/README.md b/example/README.md new file mode 100644 index 00000000..44db196a --- /dev/null +++ b/example/README.md @@ -0,0 +1,96 @@ +# Flutter Modular example + +```dart +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +/// A page-scoped view model (built per page mount, disposed on exit). +class CounterViewModel extends ChangeNotifier { + int count = 0; + + void increment() { + count++; + notifyListeners(); + } +} + +/// An APP-scoped view model: it lives ABOVE the `MaterialApp`, so toggling it +/// rebuilds the whole app's theme — something a page-scoped VM (below the +/// `Navigator`) cannot do. +class ThemeViewModel extends ChangeNotifier { + ThemeMode mode = ThemeMode.light; + + void toggle() { + mode = mode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light; + notifyListeners(); + } +} + +/// The app module: declares DI + routes. The route provides its view model +/// page-scoped via `provide`. +final appModule = createModule( + register: (c) { + c.route( + '/', + provide: (s) => s.addChangeNotifier(CounterViewModel.new), + child: (context, state) => const CounterPage(), + ); + }, +); + +void main() { + runApp( + ModularApp( + module: appModule, + // App-scoped state, anchored above the MaterialApp. + provide: (Scoped s) => + s.addChangeNotifier(ThemeViewModel.new), + child: const AppRoot(), + ), + ); +} + +class AppRoot extends StatelessWidget { + const AppRoot({super.key}); + + @override + Widget build(BuildContext context) { + final theme = context + .watch(); // reactive, above MaterialApp + return MaterialApp.router( + title: 'flutter_modular v7 example', + themeMode: theme.mode, + theme: ThemeData.light(useMaterial3: true), + darkTheme: ThemeData.dark(useMaterial3: true), + routerConfig: ModularApp.routerConfigOf(context), + ); + } +} + +class CounterPage extends StatelessWidget { + const CounterPage({super.key}); + + @override + Widget build(BuildContext context) { + final vm = context.watch(); // reactive, page-scoped + return Scaffold( + appBar: AppBar( + title: const Text('flutter_modular v7 example'), + actions: [ + IconButton( + // The app-scoped VM is reachable from a page below the MaterialApp. + onPressed: context.read().toggle, + icon: const Icon(Icons.brightness_6), + ), + ], + ), + body: Center(child: Text('count: ${vm.count}')), + floatingActionButton: FloatingActionButton( + onPressed: context.read().increment, + child: const Icon(Icons.add), + ), + ); + } +} +``` + diff --git a/flutter_modular/example/analysis_options.yaml b/example/analysis_options.yaml similarity index 90% rename from flutter_modular/example/analysis_options.yaml rename to example/analysis_options.yaml index f93a130e..0d290213 100644 --- a/flutter_modular/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -13,8 +13,7 @@ linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. + # and their documentation is published at https://dart.dev/lints. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code @@ -22,7 +21,6 @@ linter: # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: - library_private_types_in_public_api: false # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule diff --git a/flutter_modular/example/android/.gitignore b/example/android/.gitignore similarity index 69% rename from flutter_modular/example/android/.gitignore rename to example/android/.gitignore index 6f568019..be3943c9 100644 --- a/flutter_modular/example/android/.gitignore +++ b/example/android/.gitignore @@ -5,9 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts new file mode 100644 index 00000000..c3d837e9 --- /dev/null +++ b/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/flutter_modular/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml similarity index 57% rename from flutter_modular/example/android/app/src/debug/AndroidManifest.xml rename to example/android/app/src/debug/AndroidManifest.xml index c208884f..399f6981 100644 --- a/flutter_modular/example/android/app/src/debug/AndroidManifest.xml +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/flutter_modular/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml similarity index 73% rename from flutter_modular/example/android/app/src/main/AndroidManifest.xml rename to example/android/app/src/main/AndroidManifest.xml index 3f41384d..74a78b93 100644 --- a/flutter_modular/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - - + @@ -8,6 +7,7 @@ android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" + android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" @@ -31,4 +31,15 @@ android:name="flutterEmbedding" android:value="2" /> + + + + + + + diff --git a/flutter_modular/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt similarity index 66% rename from flutter_modular/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt rename to example/android/app/src/main/kotlin/com/example/example/MainActivity.kt index e793a000..ac81bae6 100644 --- a/flutter_modular/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt +++ b/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -2,5 +2,4 @@ package com.example.example import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity : FlutterActivity() diff --git a/flutter_modular/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml similarity index 100% rename from flutter_modular/example/android/app/src/main/res/drawable-v21/launch_background.xml rename to example/android/app/src/main/res/drawable-v21/launch_background.xml diff --git a/flutter_modular/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from flutter_modular/example/android/app/src/main/res/drawable/launch_background.xml rename to example/android/app/src/main/res/drawable/launch_background.xml diff --git a/flutter_modular/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from flutter_modular/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/flutter_modular/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from flutter_modular/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/flutter_modular/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from flutter_modular/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/flutter_modular/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from flutter_modular/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/flutter_modular/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from flutter_modular/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/flutter_modular/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml similarity index 94% rename from flutter_modular/example/android/app/src/main/res/values-night/styles.xml rename to example/android/app/src/main/res/values-night/styles.xml index 3db14bb5..06952be7 100644 --- a/flutter_modular/example/android/app/src/main/res/values-night/styles.xml +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -3,7 +3,7 @@ diff --git a/example/android/build.gradle.kts b/example/android/build.gradle.kts new file mode 100644 index 00000000..dbee657b --- /dev/null +++ b/example/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties new file mode 100644 index 00000000..fbee1d8c --- /dev/null +++ b/example/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/flutter_modular/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties similarity index 80% rename from flutter_modular/example/android/gradle/wrapper/gradle-wrapper.properties rename to example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58af..e4ef43fb 100644 --- a/flutter_modular/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/example/android/settings.gradle.kts b/example/android/settings.gradle.kts new file mode 100644 index 00000000..ca7fe065 --- /dev/null +++ b/example/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/flutter_modular/example/ios/.gitignore b/example/ios/.gitignore similarity index 100% rename from flutter_modular/example/ios/.gitignore rename to example/ios/.gitignore diff --git a/flutter_modular/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist similarity index 93% rename from flutter_modular/example/ios/Flutter/AppFrameworkInfo.plist rename to example/ios/Flutter/AppFrameworkInfo.plist index 8d4492f9..391a902b 100644 --- a/flutter_modular/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 9.0 diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..592ceee8 --- /dev/null +++ b/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..592ceee8 --- /dev/null +++ b/example/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/flutter_modular/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj similarity index 77% rename from flutter_modular/example/ios/Runner.xcodeproj/project.pbxproj rename to example/ios/Runner.xcodeproj/project.pbxproj index 374c2d75..2100aedf 100644 --- a/flutter_modular/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,19 +3,30 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - F541686CAD10CC1E3936ED0B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD5FAE5C5F419A40B84E1D70 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -30,12 +41,14 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 00B0E52C62C9243F8EC07806 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; @@ -44,9 +57,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AD9915BA2C55C155C9A4AD20 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - C72AF0E6B7D598A7BD57A267 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - DD5FAE5C5F419A40B84E1D70 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -54,13 +64,20 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F541686CAD10CC1E3936ED0B /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -78,8 +95,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - AC907BB1798A45CFB58DF324 /* Pods */, - F82BFD0E6E90B300E369AFD6 /* Frameworks */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -87,6 +103,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -101,45 +118,42 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; - AC907BB1798A45CFB58DF324 /* Pods */ = { - isa = PBXGroup; - children = ( - AD9915BA2C55C155C9A4AD20 /* Pods-Runner.debug.xcconfig */, - 00B0E52C62C9243F8EC07806 /* Pods-Runner.release.xcconfig */, - C72AF0E6B7D598A7BD57A267 /* Pods-Runner.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; - F82BFD0E6E90B300E369AFD6 /* Frameworks */ = { - isa = PBXGroup; - children = ( - DD5FAE5C5F419A40B84E1D70 /* Pods_Runner.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - AC7C32AB9258C86437BBCB2C /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 98582118999F8EB90ABB3F6A /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -156,9 +170,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -179,11 +198,19 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -200,10 +227,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -214,6 +243,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -226,59 +256,37 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - 98582118999F8EB90ABB3F6A /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - AC7C32AB9258C86437BBCB2C /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -303,6 +311,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -332,6 +341,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -340,7 +350,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -371,10 +381,58 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -404,6 +462,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -418,7 +477,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -430,6 +489,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -459,6 +519,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -467,7 +528,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -526,6 +587,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/flutter_modular/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from flutter_modular/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/flutter_modular/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from flutter_modular/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/flutter_modular/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from flutter_modular/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/flutter_modular/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 82% rename from flutter_modular/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a3..e3773d42 100644 --- a/flutter_modular/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + + + + diff --git a/flutter_modular/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 67% rename from flutter_modular/example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to example/ios/Runner.xcworkspace/contents.xcworkspacedata index 21a3cc14..1d526a16 100644 --- a/flutter_modular/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,7 +4,4 @@ - - diff --git a/flutter_modular/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from flutter_modular/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/flutter_modular/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from flutter_modular/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/flutter_modular/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift similarity index 53% rename from flutter_modular/example/ios/Runner/AppDelegate.swift rename to example/ios/Runner/AppDelegate.swift index 70693e4a..c30b367e 100644 --- a/flutter_modular/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -1,13 +1,16 @@ -import UIKit import Flutter +import UIKit -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } } diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..7353c41e Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..6ed2d933 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..4cd7b009 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..fe730945 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..321773cd Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..502f463a Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..e9f5fea2 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..84ac32ae Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..8953cba0 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000..0467bf12 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from flutter_modular/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from flutter_modular/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from flutter_modular/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from flutter_modular/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from flutter_modular/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/flutter_modular/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from flutter_modular/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/flutter_modular/example/ios/Runner/Base.lproj/Main.storyboard b/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from flutter_modular/example/ios/Runner/Base.lproj/Main.storyboard rename to example/ios/Runner/Base.lproj/Main.storyboard diff --git a/flutter_modular/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist similarity index 68% rename from flutter_modular/example/ios/Runner/Info.plist rename to example/ios/Runner/Info.plist index 5baf7a1c..cd88d651 100644 --- a/flutter_modular/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -24,6 +26,29 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -41,7 +66,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - diff --git a/flutter_modular/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from flutter_modular/example/ios/Runner/Runner-Bridging-Header.h rename to example/ios/Runner/Runner-Bridging-Header.h diff --git a/example/ios/Runner/SceneDelegate.swift b/example/ios/Runner/SceneDelegate.swift new file mode 100644 index 00000000..b9ce8ea2 --- /dev/null +++ b/example/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/flutter_modular/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift similarity index 100% rename from flutter_modular/example/ios/RunnerTests/RunnerTests.swift rename to example/ios/RunnerTests/RunnerTests.swift diff --git a/example/lib/app/app_module.dart b/example/lib/app/app_module.dart new file mode 100644 index 00000000..0b660491 --- /dev/null +++ b/example/lib/app/app_module.dart @@ -0,0 +1,15 @@ +import 'package:example/app/home/home_module.dart'; +import 'package:example/core/core_module.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +/// THE ROOT MODULE — composition. This file is the app's whole coupling map: +/// which modules exist and how they connect. In Flutter the coupling factors +/// are DI + Routes, and a Module makes exactly those visible. Each module +/// declares its OWN `path` (or none, for shared DI), so there is no `at:` here. +final appModule = createModule( + register: (c) { + c + ..module(coreModule) + ..module(homeModule); + }, +); diff --git a/example/lib/app/args/args_module.dart b/example/lib/app/args/args_module.dart new file mode 100644 index 00000000..3fa70b7c --- /dev/null +++ b/example/lib/app/args/args_module.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +import 'models/editor_args.dart'; +import 'pages/args_home_page.dart'; +import 'pages/editor_page.dart'; + +/// --------------------------------------------------------------------------- +/// ARGS FEATURE — passing an OBJECT and getting a RESULT back. +/// +/// - `context.pushNamed('/args/editor', arguments: EditorArgs(...))` passes an +/// arbitrary object, recovered through `RouteState.arguments`; +/// - the editor returns a value via `context.pop(result)`, which completes the +/// `Future` returned by `pushNamed` at the call site. +/// +/// Unlike `:id` path params, `arguments` is NOT in the URL — so a deep link to +/// `/args/editor` arrives with `arguments == null`; read it defensively. +/// --------------------------------------------------------------------------- +final argsModule = createModule( + register: (c) { + c + ..route('/args', child: (ctx, state) => const ArgsHomePage()) + ..route( + '/args/editor', + child: (ctx, state) { + final args = state.arguments; + if (args is! EditorArgs) { + return const Scaffold( + body: Center(child: Text('Open this from the Arguments page.')), + ); + } + return EditorPage(args: args); + }, + ); + }, +); diff --git a/example/lib/app/args/models/editor_args.dart b/example/lib/app/args/models/editor_args.dart new file mode 100644 index 00000000..6c7cfa23 --- /dev/null +++ b/example/lib/app/args/models/editor_args.dart @@ -0,0 +1,9 @@ +/// An arbitrary object passed to a route via +/// `context.pushNamed('/args/editor', arguments: EditorArgs(...))` and read back +/// from `RouteState.arguments`. +class EditorArgs { + const EditorArgs({required this.title, required this.initialText}); + + final String title; + final String initialText; +} diff --git a/example/lib/app/args/pages/args_home_page.dart b/example/lib/app/args/pages/args_home_page.dart new file mode 100644 index 00000000..147aabe3 --- /dev/null +++ b/example/lib/app/args/pages/args_home_page.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +import '../models/editor_args.dart'; + +/// Launches the editor with an object [arguments] and awaits its popped result. +class ArgsHomePage extends StatefulWidget { + const ArgsHomePage({super.key}); + + @override + State createState() => _ArgsHomePageState(); +} + +class _ArgsHomePageState extends State { + String? _result; + + Future _openEditor() async { + // Pass an arbitrary object via `arguments`; await the value the editor pops. + // Relative: from `/home/args` → `/home/args/editor`. + final result = await context.pushNamed( + './editor', + arguments: const EditorArgs( + title: 'Edit greeting', + initialText: 'Hello, Modular!', + ), + ); + if (!mounted) return; + setState(() => _result = result); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Arguments & pop result')), + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + _result == null + ? 'No result yet — open the editor.' + : 'Editor returned: "$_result"', + ), + const SizedBox(height: 16), + FilledButton.icon( + icon: const Icon(Icons.edit), + label: const Text('Open editor'), + onPressed: _openEditor, + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/app/args/pages/editor_page.dart b/example/lib/app/args/pages/editor_page.dart new file mode 100644 index 00000000..18a0ae84 --- /dev/null +++ b/example/lib/app/args/pages/editor_page.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +import '../models/editor_args.dart'; + +/// Receives an [EditorArgs] (via `RouteState.arguments`) and returns the edited +/// text through `context.pop(result)`. +class EditorPage extends StatefulWidget { + const EditorPage({required this.args, super.key}); + + final EditorArgs args; + + @override + State createState() => _EditorPageState(); +} + +class _EditorPageState extends State { + late final TextEditingController _controller = TextEditingController( + text: widget.args.initialText, + ); + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(widget.args.title)), + body: Padding( + padding: const EdgeInsets.all(24), + child: Column( + children: [ + TextField( + controller: _controller, + decoration: const InputDecoration(labelText: 'Greeting'), + ), + const Spacer(), + Row( + children: [ + TextButton( + onPressed: () => context.pop(), // returns null + child: const Text('Cancel'), + ), + const Spacer(), + FilledButton( + // returns the edited value to the awaiting `pushNamed` + onPressed: () => context.pop(_controller.text), + child: const Text('Save'), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/app/checkout/checkout_module.dart b/example/lib/app/checkout/checkout_module.dart new file mode 100644 index 00000000..9f4b46f7 --- /dev/null +++ b/example/lib/app/checkout/checkout_module.dart @@ -0,0 +1,32 @@ +import 'package:flutter_modular/flutter_modular.dart'; + +import 'data/checkout_service.dart'; +import 'pages/checkout_page.dart'; +import 'pages/payment_page.dart'; +import 'viewmodels/checkout_view_model.dart'; + +/// --------------------------------------------------------------------------- +/// CHECKOUT FEATURE — demonstrates the per-module DI LIFECYCLE. +/// +/// [CheckoutService] is registered in THIS module's DI, so it is feature-scoped: +/// bound when the first checkout route enters the stack and disposed when the +/// LAST one leaves (recreated on re-entry). The summary and payment routes +/// share the single instance while either is on screen. +/// +/// Contrast with `core_module` (the ProductRepository), which is included +/// without `at` → root-owned → never disposed (the app-wide SSoT). +/// --------------------------------------------------------------------------- +final checkoutModule = createModule( + path: '/checkout', + register: (c) { + c + ..addSingleton(CheckoutService.new) + ..route( + '/', + provide: (s) => + s.addChangeNotifier(CheckoutViewModel.new), + child: (ctx, state) => const CheckoutPage(), + ) + ..route('/payment', child: (ctx, state) => const PaymentPage()); + }, +); diff --git a/example/lib/app/checkout/data/checkout_service.dart b/example/lib/app/checkout/data/checkout_service.dart new file mode 100644 index 00000000..1266ef17 --- /dev/null +++ b/example/lib/app/checkout/data/checkout_service.dart @@ -0,0 +1,29 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +/// A FEATURE-scoped service. Because it is registered in the Checkout module's +/// own DI (`module(checkoutModule, at: '/checkout')`), it is bound when the +/// module's FIRST route is entered and disposed when its LAST route leaves the +/// stack — then re-created on re-entry. Watch the debug console for +/// "#N opened" / "#N closed". +class CheckoutService implements Disposable { + CheckoutService() { + opens++; + debugPrint('CheckoutService #$id opened'); + } + + static int _seq = 0; + + /// Lifecycle counters (used by the example test). + static int opens = 0; + static int closes = 0; + + /// A per-instance id, so re-entry shows a NEW session number. + final int id = ++_seq; + + @override + void dispose() { + closes++; + debugPrint('CheckoutService #$id closed'); + } +} diff --git a/example/lib/app/checkout/pages/checkout_page.dart b/example/lib/app/checkout/pages/checkout_page.dart new file mode 100644 index 00000000..a06c2669 --- /dev/null +++ b/example/lib/app/checkout/pages/checkout_page.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +import '../viewmodels/checkout_view_model.dart'; + +class CheckoutPage extends StatelessWidget { + const CheckoutPage({super.key}); + + @override + Widget build(BuildContext context) { + final vm = context.watch(); + return Scaffold( + appBar: AppBar(title: const Text('Checkout')), + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Checkout session #${vm.sessionId}', + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 8), + const Text('Leaving checkout disposes this session.'), + const SizedBox(height: 24), + FilledButton( + // Relative: from `/home/checkout` → `/home/checkout/payment`. + onPressed: () => context.pushNamed('./payment'), + child: const Text('To payment'), + ), + const SizedBox(height: 12), + OutlinedButton( + onPressed: () => context.pop(), + child: const Text('Back home'), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/app/checkout/pages/payment_page.dart b/example/lib/app/checkout/pages/payment_page.dart new file mode 100644 index 00000000..6690bbe6 --- /dev/null +++ b/example/lib/app/checkout/pages/payment_page.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +import '../data/checkout_service.dart'; + +class PaymentPage extends StatelessWidget { + const PaymentPage({super.key}); + + @override + Widget build(BuildContext context) { + // `inject()` resolves the SAME feature instance the summary VM uses — + // the module is still active across both of its routes. + final session = inject().id; + return Scaffold( + appBar: AppBar(title: const Text('Payment')), + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Paying for session #$session', + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 24), + FilledButton( + onPressed: () => context.pop(), + child: const Text('Confirm'), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/app/checkout/viewmodels/checkout_view_model.dart b/example/lib/app/checkout/viewmodels/checkout_view_model.dart new file mode 100644 index 00000000..ad0f57ab --- /dev/null +++ b/example/lib/app/checkout/viewmodels/checkout_view_model.dart @@ -0,0 +1,13 @@ +import 'package:flutter/foundation.dart'; + +import '../data/checkout_service.dart'; + +/// Page-scoped VM that INJECTS the feature-scoped [CheckoutService] — two +/// lifecycles at once: the VM dies with its page, the service with the module. +class CheckoutViewModel extends ChangeNotifier { + CheckoutViewModel(this._service); + + final CheckoutService _service; + + int get sessionId => _service.id; +} diff --git a/example/lib/app/dashboard/dashboard_module.dart b/example/lib/app/dashboard/dashboard_module.dart new file mode 100644 index 00000000..82e1cce8 --- /dev/null +++ b/example/lib/app/dashboard/dashboard_module.dart @@ -0,0 +1,33 @@ +import 'package:flutter_modular/flutter_modular.dart'; + +import 'dashboard_shell.dart'; +import 'pages/feed_page.dart'; +import 'pages/item_page.dart'; +import 'pages/profile_page.dart'; +import 'pages/search_page.dart'; + +/// --------------------------------------------------------------------------- +/// DASHBOARD FEATURE — the canonical `RouterOutlet` case: a PERSISTENT SHELL +/// (a bottom bar) whose body swaps. +/// +/// The shell route renders a `DashboardShell` containing a `RouterOutlet`; its +/// `children` are the tabs, rendered INSIDE that outlet. This is the one place +/// `RouterOutlet` is for — `module(...)` flattens routes (no outlet); here we +/// declare the outlet EXPLICITLY because we want chrome that persists. +/// --------------------------------------------------------------------------- +final dashboardModule = createModule( + path: '/dashboard', + register: (c) { + c.route( + '/', + child: (ctx, state) => const DashboardShell(), + children: (sub) { + sub + ..route('/', child: (ctx, state) => const FeedPage()) + ..route('/search', child: (ctx, state) => const SearchPage()) + ..route('/profile', child: (ctx, state) => const ProfilePage()) + ..route('/item', child: (ctx, state) => const ItemPage()); + }, + ); + }, +); diff --git a/example/lib/app/dashboard/dashboard_shell.dart b/example/lib/app/dashboard/dashboard_shell.dart new file mode 100644 index 00000000..a853f3bc --- /dev/null +++ b/example/lib/app/dashboard/dashboard_shell.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +/// A PERSISTENT SHELL built on `RouterOutlet` — the bottom bar stays mounted +/// while the body swaps. Tab taps call `outlet.navigate(...)` (replace the +/// outlet's sub-stack — a tab switch, no history); pushing from inside a tab +/// stacks INSIDE the same outlet, so the shell persists there too. +class DashboardShell extends StatefulWidget { + const DashboardShell({super.key}); + + /// Counts shell builds from scratch — the example test asserts it stays 1 + /// across tab switches, proving the shell truly persists. + static int inits = 0; + + @override + State createState() => _DashboardShellState(); +} + +class _DashboardShellState extends State { + // Drives the body outlet directly — the bottom bar is a SIBLING of the + // outlet, so it reaches it via this key rather than `RouterOutlet.of`. + final _outlet = GlobalKey(); + + // Absolute tab paths: the bottom bar drives the outlet through a GlobalKey + // (not `context`), so there's no current-location to be relative to — the + // shell names its own mount. The dashboard module is mounted at + // `/home/dashboard`. + static const _tabs = [ + '/home/dashboard', + '/home/dashboard/search', + '/home/dashboard/profile', + ]; + + @override + void initState() { + super.initState(); + DashboardShell.inits++; + } + + @override + Widget build(BuildContext context) { + // The highlight is DERIVED from the route, never a stored copy: a deep link + // to a non-default tab (or any navigation) lights the right destination + // because `routeState()` rebuilds this when the URL changes. The route is + // the single source of truth — the bottom bar just reads it. `lastIndexWhere` + // so the most specific tab wins (every path starts with the Feed base). + final path = context.routeState().uri.path; + final selected = _tabs.lastIndexWhere( + (t) => path == t || path.startsWith('$t/'), + ); + + return Scaffold( + appBar: AppBar(title: const Text('Dashboard')), + body: RouterOutlet(key: _outlet), + bottomNavigationBar: NavigationBar( + selectedIndex: selected < 0 ? 0 : selected, + // A tab tap just navigates the outlet — no local state to flip. The + // resulting URL change rebuilds this and re-derives the highlight. + onDestinationSelected: (i) => _outlet.currentState?.navigate(_tabs[i]), + destinations: const [ + NavigationDestination(icon: Icon(Icons.dynamic_feed), label: 'Feed'), + NavigationDestination(icon: Icon(Icons.search), label: 'Search'), + NavigationDestination(icon: Icon(Icons.person), label: 'Profile'), + ], + ), + ); + } +} diff --git a/example/lib/app/dashboard/pages/feed_page.dart b/example/lib/app/dashboard/pages/feed_page.dart new file mode 100644 index 00000000..73250d3d --- /dev/null +++ b/example/lib/app/dashboard/pages/feed_page.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +class FeedPage extends StatelessWidget { + const FeedPage({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Feed view'), + const SizedBox(height: 12), + // Pushes INSIDE the outlet → item over feed, shell still mounted. + // RELATIVE route: from `/dashboard`, `./item` resolves to + // `/dashboard/item` — no need to repeat the shell prefix. + FilledButton( + onPressed: () => context.pushNamed('./item'), + child: const Text('Open an item'), + ), + ], + ), + ); + } +} diff --git a/example/lib/app/dashboard/pages/item_page.dart b/example/lib/app/dashboard/pages/item_page.dart new file mode 100644 index 00000000..dc99941c --- /dev/null +++ b/example/lib/app/dashboard/pages/item_page.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +class ItemPage extends StatelessWidget { + const ItemPage({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Item detail — still inside the shell'), + const SizedBox(height: 12), + OutlinedButton( + onPressed: () => context.pop(), // pops the outlet's sub-stack + child: const Text('Back to feed'), + ), + ], + ), + ); + } +} diff --git a/example/lib/app/dashboard/pages/profile_page.dart b/example/lib/app/dashboard/pages/profile_page.dart new file mode 100644 index 00000000..d0f21b58 --- /dev/null +++ b/example/lib/app/dashboard/pages/profile_page.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class ProfilePage extends StatelessWidget { + const ProfilePage({super.key}); + + @override + Widget build(BuildContext context) { + return const Center(child: Text('Profile view')); + } +} diff --git a/example/lib/app/dashboard/pages/search_page.dart b/example/lib/app/dashboard/pages/search_page.dart new file mode 100644 index 00000000..968acc7b --- /dev/null +++ b/example/lib/app/dashboard/pages/search_page.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class SearchPage extends StatelessWidget { + const SearchPage({super.key}); + + @override + Widget build(BuildContext context) { + return const Center(child: Text('Search view')); + } +} diff --git a/example/lib/app/home/home_module.dart b/example/lib/app/home/home_module.dart new file mode 100644 index 00000000..89025935 --- /dev/null +++ b/example/lib/app/home/home_module.dart @@ -0,0 +1,20 @@ +import 'package:example/app/args/args_module.dart'; +import 'package:example/app/checkout/checkout_module.dart'; +import 'package:example/app/dashboard/dashboard_module.dart'; +import 'package:example/app/home/home_page.dart'; +import 'package:example/app/products/products_module.dart'; +import 'package:example/app/settings/settings_module.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +final homeModule = createModule( + path: '/home', + register: (c) { + c + ..route('/', child: (ctx, state) => const HomePage()) + ..module(productsModule) + ..module(settingsModule) + ..module(argsModule) + ..module(checkoutModule) + ..module(dashboardModule); + }, +); diff --git a/example/lib/app/home/home_page.dart b/example/lib/app/home/home_page.dart new file mode 100644 index 00000000..ea1a27eb --- /dev/null +++ b/example/lib/app/home/home_page.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +/// The landing page, mounted at `/home`. Its buttons use RELATIVE routes +/// (`./products`, `./settings`, …): from `/home` they resolve to +/// `/home/products`, `/home/settings`, … so the page never repeats its own +/// mount prefix. A leading `/` would make them absolute instead. +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('flutter_modular v7')), + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('A tiny store, modular.'), + const SizedBox(height: 24), + FilledButton.icon( + icon: const Icon(Icons.storefront), + label: const Text('Browse products'), + onPressed: () => context.pushNamed('./products'), + ), + const SizedBox(height: 12), + OutlinedButton.icon( + icon: const Icon(Icons.settings), + label: const Text('Settings'), + onPressed: () => context.pushNamed('./settings'), + ), + const SizedBox(height: 12), + OutlinedButton.icon( + icon: const Icon(Icons.tune), + label: const Text('Arguments & pop result'), + onPressed: () => context.pushNamed('./args'), + ), + const SizedBox(height: 12), + OutlinedButton.icon( + icon: const Icon(Icons.shopping_cart_checkout), + label: const Text('Checkout'), + onPressed: () => context.pushNamed('./checkout'), + ), + const SizedBox(height: 12), + OutlinedButton.icon( + icon: const Icon(Icons.dashboard), + label: const Text('Dashboard (RouterOutlet)'), + onPressed: () => context.navigate('./dashboard'), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/app/products/data/realtime_connection.dart b/example/lib/app/products/data/realtime_connection.dart new file mode 100644 index 00000000..c554e143 --- /dev/null +++ b/example/lib/app/products/data/realtime_connection.dart @@ -0,0 +1,15 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +/// `addDisposable` demo: a NON-reactive resource opened while the detail page is +/// alive and closed on exit. It is injected into the detail VM (the same +/// page-scoped instance), showing reactivity and lifecycle are independent. +class RealtimeConnection implements Disposable { + bool isOpen = true; + + @override + void dispose() { + isOpen = false; + debugPrint('RealtimeConnection closed'); + } +} diff --git a/example/lib/app/products/pages/product_detail_page.dart b/example/lib/app/products/pages/product_detail_page.dart new file mode 100644 index 00000000..da656387 --- /dev/null +++ b/example/lib/app/products/pages/product_detail_page.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +import '../viewmodels/product_detail_view_model.dart'; + +class ProductDetailPage extends StatefulWidget { + const ProductDetailPage({required this.id, super.key}); + + final String id; + + @override + State createState() => _ProductDetailPageState(); +} + +class _ProductDetailPageState extends State { + @override + void initState() { + super.initState(); + context.read().load(widget.id); + } + + @override + Widget build(BuildContext context) { + final vm = context.watch(); + final viewers = context.watch>().value; // addStream value + final product = vm.product; + + return Scaffold( + appBar: AppBar(title: Text(product?.name ?? 'Loading…')), + body: vm.loading || product == null + ? const Center(child: CircularProgressIndicator()) + : Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.circle, + size: 10, + color: vm.connected ? Colors.green : Colors.grey, + ), + const SizedBox(width: 8), + Text(vm.connected ? 'connected' : 'offline'), + const Spacer(), + Text('👀 ${viewers ?? '—'} viewing'), + ], + ), + const SizedBox(height: 24), + Text( + '\$${product.price.toStringAsFixed(2)}', + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 12), + Text(product.description), + const Spacer(), + FilledButton( + onPressed: () => context.pop(), + child: const Text('Back'), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/app/products/pages/product_list_page.dart b/example/lib/app/products/pages/product_list_page.dart new file mode 100644 index 00000000..4ab47259 --- /dev/null +++ b/example/lib/app/products/pages/product_list_page.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +import '../viewmodels/product_list_view_model.dart'; + +class ProductListPage extends StatefulWidget { + const ProductListPage({super.key}); + + @override + State createState() => _ProductListPageState(); +} + +class _ProductListPageState extends State { + @override + void initState() { + super.initState(); + // The view drives the VM (params go to the view, not the VM constructor). + context.read().load(); + } + + @override + Widget build(BuildContext context) { + final vm = context.watch(); + return Scaffold( + appBar: AppBar( + // `module(at:)` is a namespace now, so this is a normal root page — the + // automatic back arrow works (no nested outlet swallowing it). + title: const Text('Products'), + actions: [ + // `Selector`: this badge rebuilds ONLY when the count changes. + Selector( + selector: (_, vm) => vm.products.length, + builder: (_, count, __) => Center( + child: Padding( + padding: const EdgeInsets.only(right: 16), + child: Text('$count items'), + ), + ), + ), + ], + ), + body: vm.loading + ? const Center(child: CircularProgressIndicator()) + : ListView( + children: [ + for (final p in vm.products) + ListTile( + title: Text(p.name), + subtitle: Text('\$${p.price.toStringAsFixed(2)}'), + trailing: const Icon(Icons.chevron_right), + // RELATIVE route: we're on `/products`, so a bare `id` + // resolves to `/products/$id` (no absolute prefix needed). + onTap: () => context.pushNamed(p.id), + ), + ], + ), + ); + } +} diff --git a/example/lib/app/products/products_module.dart b/example/lib/app/products/products_module.dart new file mode 100644 index 00000000..f8b00938 --- /dev/null +++ b/example/lib/app/products/products_module.dart @@ -0,0 +1,48 @@ +import 'package:flutter_modular/flutter_modular.dart'; + +import 'data/realtime_connection.dart'; +import 'pages/product_detail_page.dart'; +import 'pages/product_list_page.dart'; +import 'viewmodels/product_detail_view_model.dart'; +import 'viewmodels/product_list_view_model.dart'; + +/// --------------------------------------------------------------------------- +/// PRODUCTS FEATURE — the module wires the whole feature boundary: its routes +/// and the page-scoped state each route provides. The screens and view models +/// live in `pages/` and `viewmodels/`. +/// +/// Demonstrates: page-scoped view models (`addChangeNotifier`), params to the +/// view (`/:id`), `addDisposable` (non-reactive resource), and `addStream`. +/// --------------------------------------------------------------------------- + +/// `addStream` demo: a live "people viewing" ticker. +Stream _viewersStream() => + Stream.periodic(const Duration(seconds: 2), (i) => 40 + i); + +/// Declares its own `path` → mounted at `/products` by `module(productsModule)`, +/// so these relative routes become `/products` (list) and `/products/:id`. +final productsModule = createModule( + path: '/products', + register: (c) { + c + ..route( + '/', + provide: (s) { + s.addChangeNotifier(ProductListViewModel.new); + }, + child: (ctx, state) => const ProductListPage(), + ) + ..route( + '/:id', + provide: (s) { + s + ..addDisposable(RealtimeConnection.new) + ..addChangeNotifier( + ProductDetailViewModel.new, + ) + ..addStream(_viewersStream); + }, + child: (ctx, RouteState state) => ProductDetailPage(id: state['id']!), + ); + }, +); diff --git a/example/lib/app/products/viewmodels/product_detail_view_model.dart b/example/lib/app/products/viewmodels/product_detail_view_model.dart new file mode 100644 index 00000000..9c9fdc08 --- /dev/null +++ b/example/lib/app/products/viewmodels/product_detail_view_model.dart @@ -0,0 +1,25 @@ +import 'package:flutter/foundation.dart'; + +import 'package:example/core/data/product_repository.dart'; +import 'package:example/core/models/product.dart'; +import '../data/realtime_connection.dart'; + +/// Page-scoped: 1:1 with the detail view. Injects the page's +/// [RealtimeConnection] (the same instance, via DI) alongside the repository. +class ProductDetailViewModel extends ChangeNotifier { + ProductDetailViewModel(this._repo, this._connection); + + final ProductRepository _repo; + final RealtimeConnection _connection; + + bool loading = true; + Product? product; + + bool get connected => _connection.isOpen; + + Future load(String id) async { + product = await _repo.getById(id); + loading = false; + notifyListeners(); + } +} diff --git a/example/lib/app/products/viewmodels/product_list_view_model.dart b/example/lib/app/products/viewmodels/product_list_view_model.dart new file mode 100644 index 00000000..2ee095ca --- /dev/null +++ b/example/lib/app/products/viewmodels/product_list_view_model.dart @@ -0,0 +1,23 @@ +import 'package:flutter/foundation.dart'; + +import 'package:example/core/data/product_repository.dart'; +import 'package:example/core/models/product.dart'; + +/// Page-scoped: 1:1 with the list view. Reads the repository (SSoT) instead of +/// holding the truth itself. +class ProductListViewModel extends ChangeNotifier { + ProductListViewModel(this._repo); + + final ProductRepository _repo; + + // Starts loading; notify only AFTER the await (a synchronous notify inside + // the view's initState-triggered load() would fire during build). + bool loading = true; + List products = const []; + + Future load() async { + products = await _repo.getProducts(); + loading = false; + notifyListeners(); + } +} diff --git a/example/lib/app/settings/pages/secret_page.dart b/example/lib/app/settings/pages/secret_page.dart new file mode 100644 index 00000000..361554d6 --- /dev/null +++ b/example/lib/app/settings/pages/secret_page.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class SecretPage extends StatelessWidget { + const SecretPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Secret')), + body: const Center(child: Text('🔓 Secret area unlocked')), + ); + } +} diff --git a/example/lib/app/settings/pages/settings_page.dart b/example/lib/app/settings/pages/settings_page.dart new file mode 100644 index 00000000..e6df50aa --- /dev/null +++ b/example/lib/app/settings/pages/settings_page.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; + +import 'package:example/core/state/theme_controller.dart'; +import '../viewmodels/settings_view_model.dart'; + +class SettingsPage extends StatelessWidget { + const SettingsPage({super.key}); + + @override + Widget build(BuildContext context) { + final settings = context.watch(); + return Scaffold( + appBar: AppBar(title: const Text('Settings')), + body: ListView( + children: [ + SwitchListTile( + title: const Text('Dark theme'), + subtitle: const Text('App-scoped state, above the MaterialApp'), + // ThemeController is provided by ModularApp, above the MaterialApp; + // a page below it still reaches it through the element tree. + value: context.watch().mode == ThemeMode.dark, + onChanged: (_) => context.read().toggle(), + ), + const Divider(), + SwitchListTile( + title: const Text('Unlock secret area'), + subtitle: const Text('Flips the AppSession flag the guard reads'), + value: settings.unlocked, + onChanged: settings.setUnlocked, + ), + ListTile( + title: const Text('Open secret area'), + trailing: const Icon(Icons.lock_open), + // Relative push: from `/home/settings` → `/home/settings/secret`. + // Redirected back here by the guard while locked. + onTap: () => context.pushNamed('./secret'), + ), + ], + ), + ); + } +} diff --git a/example/lib/app/settings/settings_module.dart b/example/lib/app/settings/settings_module.dart new file mode 100644 index 00000000..7d749304 --- /dev/null +++ b/example/lib/app/settings/settings_module.dart @@ -0,0 +1,37 @@ +import 'package:flutter_modular/flutter_modular.dart'; + +import 'package:example/core/state/app_session.dart'; +import 'pages/secret_page.dart'; +import 'pages/settings_page.dart'; +import 'viewmodels/settings_view_model.dart'; + +/// --------------------------------------------------------------------------- +/// SETTINGS FEATURE — demonstrates route guards + reaching app-scoped state. +/// +/// - a GUARD redirects `/settings/secret` until the app is unlocked (it reads +/// the [AppSession] singleton from DI at navigation time); +/// - a page-scoped [SettingsViewModel] flips that same singleton; +/// - the Settings page toggles the APP-SCOPED ThemeController via `read`. +/// --------------------------------------------------------------------------- +final settingsModule = createModule( + register: (c) { + c + ..route( + '/settings', + provide: (s) => + s.addChangeNotifier(SettingsViewModel.new), + child: (ctx, state) => const SettingsPage(), + ) + ..route( + '/settings/secret', + // `inject()` reads DI at guard-eval time without exposing the + // injector object — Angular-style. + // A guard redirect is an absolute destination (no context to be + // relative to), so it names the mounted settings index directly. + guards: [ + (state) => inject().unlocked ? null : '/home/settings', + ], + child: (ctx, state) => const SecretPage(), + ); + }, +); diff --git a/example/lib/app/settings/viewmodels/settings_view_model.dart b/example/lib/app/settings/viewmodels/settings_view_model.dart new file mode 100644 index 00000000..b7b729e4 --- /dev/null +++ b/example/lib/app/settings/viewmodels/settings_view_model.dart @@ -0,0 +1,17 @@ +import 'package:flutter/foundation.dart'; + +import 'package:example/core/state/app_session.dart'; + +/// Page-scoped: flips the app-wide [AppSession] flag the route guard observes. +class SettingsViewModel extends ChangeNotifier { + SettingsViewModel(this._session); + + final AppSession _session; + + bool get unlocked => _session.unlocked; + + void setUnlocked(bool value) { + _session.unlocked = value; + notifyListeners(); + } +} diff --git a/example/lib/core/core_module.dart b/example/lib/core/core_module.dart new file mode 100644 index 00000000..7ab75475 --- /dev/null +++ b/example/lib/core/core_module.dart @@ -0,0 +1,26 @@ +import 'package:flutter_modular/flutter_modular.dart'; + +import 'data/product_repository.dart'; +import 'data/product_service.dart'; +import 'state/app_session.dart'; + +/// --------------------------------------------------------------------------- +/// CORE — the shared DATA LAYER (Flutter "App Architecture"). +/// +/// A [ProductService] (a remote-ish data source) sits behind a +/// [ProductRepository] that is the SINGLE SOURCE OF TRUTH. View models never +/// hold the truth — they read it from the repository. Everything here is an +/// app-wide singleton in this route-less module, so it is ROOT-OWNED and lives +/// for the whole app (a leaving route never disposes it). +/// +/// This is the architecture flutter_modular pushes: DI + page-scoped lifecycle +/// take the weight off "state management" — the truth has one clear home. +/// --------------------------------------------------------------------------- +final coreModule = createModule( + register: (c) { + c + ..addSingleton(ProductService.new) + ..addSingleton(ProductRepository.new) + ..addSingleton(AppSession.new); + }, +); diff --git a/example/lib/core/data/product_repository.dart b/example/lib/core/data/product_repository.dart new file mode 100644 index 00000000..76b1e736 --- /dev/null +++ b/example/lib/core/data/product_repository.dart @@ -0,0 +1,21 @@ +import '../models/product.dart'; +import 'product_service.dart'; + +/// SSoT: the single place product truth lives. Caches the catalog and serves it +/// to any view model that asks. Depends on [ProductService] via DI. +class ProductRepository { + ProductRepository(this._service); + + final ProductService _service; + List? _cache; + + Future> getProducts() async => + _cache ??= await _service.fetchAll(); + + Future getById(String id) async { + for (final p in await getProducts()) { + if (p.id == id) return p; + } + return null; + } +} diff --git a/example/lib/core/data/product_service.dart b/example/lib/core/data/product_service.dart new file mode 100644 index 00000000..da1ce33b --- /dev/null +++ b/example/lib/core/data/product_service.dart @@ -0,0 +1,44 @@ +import '../models/product.dart'; + +/// A fake remote data source (simulated latency). +class ProductService { + static const _catalog = [ + Product( + id: '1', + name: 'Mechanical Keyboard', + price: 119.90, + description: 'Hot-swappable, tactile switches, RGB.', + ), + Product( + id: '2', + name: 'Ergonomic Mouse', + price: 59.90, + description: 'Vertical grip, 6 buttons, silent click.', + ), + Product( + id: '3', + name: '4K Monitor', + price: 389.00, + description: '27", USB-C, 99% sRGB.', + ), + Product( + id: '4', + name: 'USB-C Hub', + price: 42.50, + description: '7-in-1, HDMI + Ethernet + PD.', + ), + ]; + + Future> fetchAll() async { + await Future.delayed(const Duration(milliseconds: 300)); + return _catalog; + } + + Future fetchById(String id) async { + await Future.delayed(const Duration(milliseconds: 300)); + for (final p in _catalog) { + if (p.id == id) return p; + } + return null; + } +} diff --git a/example/lib/core/models/product.dart b/example/lib/core/models/product.dart new file mode 100644 index 00000000..e6070865 --- /dev/null +++ b/example/lib/core/models/product.dart @@ -0,0 +1,14 @@ +/// Domain model. +class Product { + const Product({ + required this.id, + required this.name, + required this.price, + required this.description, + }); + + final String id; + final String name; + final double price; + final String description; +} diff --git a/example/lib/core/state/app_session.dart b/example/lib/core/state/app_session.dart new file mode 100644 index 00000000..d981f4b0 --- /dev/null +++ b/example/lib/core/state/app_session.dart @@ -0,0 +1,4 @@ +/// App-wide flag a route guard reads (flipped from the Settings view model). +class AppSession { + bool unlocked = false; +} diff --git a/example/lib/core/state/theme_controller.dart b/example/lib/core/state/theme_controller.dart new file mode 100644 index 00000000..1b89dd16 --- /dev/null +++ b/example/lib/core/state/theme_controller.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +/// App-scoped reactive state. NOT registered in DI — it is provided ABOVE the +/// `MaterialApp` by `ModularApp` in `main.dart` (`provide:`), which is what lets +/// it drive the app theme. A page-scoped VM (below the `Navigator`) could not. +class ThemeController extends ChangeNotifier { + ThemeMode mode = ThemeMode.light; + + void toggle() { + mode = mode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light; + notifyListeners(); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart new file mode 100644 index 00000000..5c0d5e6c --- /dev/null +++ b/example/lib/main.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_web_plugins/url_strategy.dart'; + +import 'app/app_module.dart'; +import 'core/state/theme_controller.dart'; + +/// Entry point. [ModularApp] bootstraps [appModule] once, owns the injector, +/// builds the router config, and hosts APP-SCOPED state via `provide` — +/// anchored ABOVE the `MaterialApp`, so the [ThemeController] can drive the app +/// theme (something a page-scoped VM, below the `Navigator`, cannot do). +void main() { + // Use real paths on the web (`/products/1`) instead of the default hash + // fragment (`/#/products/1`), so URLs are clean and deep-linkable. This sets + // the `PathUrlStrategy`; it's a no-op off the web. + usePathUrlStrategy(); + runApp( + ModularApp( + module: appModule, + // The landing feature is mounted at `/home`, so that's the initial route + // (the bare `/` has no page). A real entry URL still overrides it. + initialRoute: '/home', + provide: (s) => s.addChangeNotifier(ThemeController.new), + child: const AppRoot(), + ), + ); +} + +class AppRoot extends StatelessWidget { + const AppRoot({super.key}); + + @override + Widget build(BuildContext context) { + final theme = context.watch(); // above the MaterialApp + return MaterialApp.router( + title: 'flutter_modular v7 example', + themeMode: theme.mode, + theme: ThemeData.light(useMaterial3: true), + darkTheme: ThemeData.dark(useMaterial3: true), + routerConfig: ModularApp.routerConfigOf(context), + ); + } +} diff --git a/flutter_modular/example/linux/.gitignore b/example/linux/.gitignore similarity index 100% rename from flutter_modular/example/linux/.gitignore rename to example/linux/.gitignore diff --git a/flutter_modular/example/linux/CMakeLists.txt b/example/linux/CMakeLists.txt similarity index 64% rename from flutter_modular/example/linux/CMakeLists.txt rename to example/linux/CMakeLists.txt index 1c4f90b9..7a9a314f 100644 --- a/flutter_modular/example/linux/CMakeLists.txt +++ b/example/linux/CMakeLists.txt @@ -1,14 +1,32 @@ -cmake_minimum_required(VERSION 3.10) +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) project(runner LANGUAGES CXX) +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. set(BINARY_NAME "example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID set(APPLICATION_ID "com.example.example") +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. cmake_policy(SET CMP0063 NEW) +# Load bundled libraries from the lib/ directory relative to the binary. set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") -# Configure build options. +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) @@ -17,6 +35,10 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) endif() # Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_options(${TARGET} PRIVATE -Wall -Werror) @@ -24,27 +46,20 @@ function(APPLY_STANDARD_SETTINGS TARGET) target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") endfunction() -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") - # Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) -add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") -# Application build -add_executable(${BINARY_NAME} - "main.cc" - "my_application.cc" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" -) -apply_standard_settings(${BINARY_NAME}) -target_link_libraries(${BINARY_NAME} PRIVATE flutter) -target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) +# Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) + # Only the install-generated bundle's copy of the executable will launch # correctly, since the resources must in the right relative locations. To avoid # people trying to run the unbundled copy, put it in a subdirectory instead of @@ -54,6 +69,7 @@ set_target_properties(${BINARY_NAME} RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" ) + # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) @@ -69,7 +85,7 @@ endif() # Start with a clean build bundle directory every time. install(CODE " - file(REMOVE_RECURSE /"${BUILD_BUNDLE_DIR}//") + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") " COMPONENT Runtime) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") @@ -84,17 +100,23 @@ install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR} install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) -if(PLUGIN_BUNDLED_LIBRARIES) - install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) -endif() +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " - file(REMOVE_RECURSE /"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}/") + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) diff --git a/flutter_modular/example/linux/flutter/CMakeLists.txt b/example/linux/flutter/CMakeLists.txt similarity index 93% rename from flutter_modular/example/linux/flutter/CMakeLists.txt rename to example/linux/flutter/CMakeLists.txt index a1da1b9e..d5bd0164 100644 --- a/flutter_modular/example/linux/flutter/CMakeLists.txt +++ b/example/linux/flutter/CMakeLists.txt @@ -1,3 +1,4 @@ +# This file controls Flutter-level build steps. It should not be edited. cmake_minimum_required(VERSION 3.10) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") @@ -24,8 +25,6 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) -pkg_check_modules(BLKID REQUIRED IMPORTED_TARGET blkid) -pkg_check_modules(LZMA REQUIRED IMPORTED_TARGET liblzma) set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") @@ -67,8 +66,6 @@ target_link_libraries(flutter INTERFACE PkgConfig::GTK PkgConfig::GLIB PkgConfig::GIO - PkgConfig::BLKID - PkgConfig::LZMA ) add_dependencies(flutter flutter_assemble) @@ -82,7 +79,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" - linux-x64 ${CMAKE_BUILD_TYPE} + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..e71a16d2 --- /dev/null +++ b/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/flutter_modular/example/linux/flutter/generated_plugin_registrant.h b/example/linux/flutter/generated_plugin_registrant.h similarity index 100% rename from flutter_modular/example/linux/flutter/generated_plugin_registrant.h rename to example/linux/flutter/generated_plugin_registrant.h diff --git a/flutter_modular/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake similarity index 97% rename from flutter_modular/example/linux/flutter/generated_plugins.cmake rename to example/linux/flutter/generated_plugins.cmake index f16b4c34..2e1de87a 100644 --- a/flutter_modular/example/linux/flutter/generated_plugins.cmake +++ b/example/linux/flutter/generated_plugins.cmake @@ -3,7 +3,6 @@ # list(APPEND FLUTTER_PLUGIN_LIST - url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/example/linux/runner/CMakeLists.txt b/example/linux/runner/CMakeLists.txt new file mode 100644 index 00000000..e97dabc7 --- /dev/null +++ b/example/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/flutter_modular/example/linux/main.cc b/example/linux/runner/main.cc similarity index 100% rename from flutter_modular/example/linux/main.cc rename to example/linux/runner/main.cc diff --git a/flutter_modular/example/linux/my_application.cc b/example/linux/runner/my_application.cc similarity index 52% rename from flutter_modular/example/linux/my_application.cc rename to example/linux/runner/my_application.cc index 543eaca7..27b4f86d 100644 --- a/flutter_modular/example/linux/my_application.cc +++ b/example/linux/runner/my_application.cc @@ -14,6 +14,11 @@ struct _MyApplication { G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView* view) { + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + // Implements GApplication::activate. static void my_application_activate(GApplication* application) { MyApplication* self = MY_APPLICATION(application); @@ -29,51 +34,63 @@ static void my_application_activate(GApplication* application) { // if future cases occur). gboolean use_header_bar = TRUE; #ifdef GDK_WINDOWING_X11 - GdkScreen *screen = gtk_window_get_screen(window); + GdkScreen* screen = gtk_window_get_screen(window); if (GDK_IS_X11_SCREEN(screen)) { - const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); - if (g_strcmp0(wm_name, "GNOME Shell") != 0) { - use_header_bar = FALSE; - } + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } } #endif if (use_header_bar) { - GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, "example"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); - } - else { + } else { gtk_window_set_title(window, "example"); } gtk_window_set_default_size(window, 1280, 720); - gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); - fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 + // for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); + gtk_widget_realize(GTK_WIDGET(view)); + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } // Implements GApplication::local_command_line. -static gboolean my_application_local_command_line(GApplication* application, gchar ***arguments, int *exit_status) { +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { MyApplication* self = MY_APPLICATION(application); // Strip out the first argument as it is the binary name. self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); g_autoptr(GError) error = nullptr; if (!g_application_register(application, nullptr, &error)) { - g_warning("Failed to register: %s", error->message); - *exit_status = 1; - return TRUE; + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; } g_application_activate(application); @@ -82,8 +99,26 @@ static gboolean my_application_local_command_line(GApplication* application, gch return TRUE; } +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + // Implements GObject::dispose. -static void my_application_dispose(GObject *object) { +static void my_application_dispose(GObject* object) { MyApplication* self = MY_APPLICATION(object); g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); G_OBJECT_CLASS(my_application_parent_class)->dispose(object); @@ -91,14 +126,23 @@ static void my_application_dispose(GObject *object) { static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; - G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + return MY_APPLICATION(g_object_new(my_application_get_type(), - "application-id", APPLICATION_ID, - nullptr)); + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); } diff --git a/flutter_modular/example/linux/my_application.h b/example/linux/runner/my_application.h similarity index 70% rename from flutter_modular/example/linux/my_application.h rename to example/linux/runner/my_application.h index 72271d5e..db16367a 100644 --- a/flutter_modular/example/linux/my_application.h +++ b/example/linux/runner/my_application.h @@ -3,7 +3,10 @@ #include -G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, +G_DECLARE_FINAL_TYPE(MyApplication, + my_application, + MY, + APPLICATION, GtkApplication) /** diff --git a/flutter_modular/example/macos/.gitignore b/example/macos/.gitignore similarity index 100% rename from flutter_modular/example/macos/.gitignore rename to example/macos/.gitignore diff --git a/example/macos/Flutter/Flutter-Debug.xcconfig b/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..c2efd0b6 --- /dev/null +++ b/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/Flutter-Release.xcconfig b/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..c2efd0b6 --- /dev/null +++ b/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..cccf817a --- /dev/null +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/flutter_modular/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj similarity index 79% rename from flutter_modular/example/macos/Runner.xcodeproj/project.pbxproj rename to example/macos/Runner.xcodeproj/project.pbxproj index 7b59fd8e..a548a467 100644 --- a/flutter_modular/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -27,8 +27,6 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - CD64D63A96CC5CE8C6033103 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F64525CCA0B2EEC06C8D9D7 /* Pods_RunnerTests.framework */; }; - CEB98F717F8143ECBF4E15E6 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E6AE120CA645727B7912C494 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -62,13 +60,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 1F64525CCA0B2EEC06C8D9D7 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 2B77B95D89549D6261DC15CD /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -80,14 +76,8 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 4A5F65B7C3CB297A0D8AB705 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - 5B2890B659881BCF5B0164DC /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - 73C8206F57489E9E580CD168 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - BC5019ED43CD4DE2314FC755 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - C55343FE0848F526DA9DFA33 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - E6AE120CA645727B7912C494 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -95,7 +85,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - CD64D63A96CC5CE8C6033103 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -103,27 +92,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - CEB98F717F8143ECBF4E15E6 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 1E9B0F18B4E72A02E956FDF2 /* Pods */ = { - isa = PBXGroup; - children = ( - BC5019ED43CD4DE2314FC755 /* Pods-Runner.debug.xcconfig */, - C55343FE0848F526DA9DFA33 /* Pods-Runner.release.xcconfig */, - 2B77B95D89549D6261DC15CD /* Pods-Runner.profile.xcconfig */, - 73C8206F57489E9E580CD168 /* Pods-RunnerTests.debug.xcconfig */, - 5B2890B659881BCF5B0164DC /* Pods-RunnerTests.release.xcconfig */, - 4A5F65B7C3CB297A0D8AB705 /* Pods-RunnerTests.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; 331C80D6294CF71000263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( @@ -151,7 +125,6 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, - 1E9B0F18B4E72A02E956FDF2 /* Pods */, ); sourceTree = ""; }; @@ -202,8 +175,6 @@ D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( - E6AE120CA645727B7912C494 /* Pods_Runner.framework */, - 1F64525CCA0B2EEC06C8D9D7 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -215,7 +186,6 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 3671B5D1D93BAE1B34103F5D /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -234,13 +204,11 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 89FFDC90F81D9B38A980784F /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - 5B74B3619519178DE299E504 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -258,8 +226,9 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C80D4294CF70F00263BE5 = { @@ -360,67 +329,6 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; - 3671B5D1D93BAE1B34103F5D /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 5B74B3619519178DE299E504 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 89FFDC90F81D9B38A980784F /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -472,7 +380,6 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 73C8206F57489E9E580CD168 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -487,7 +394,6 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5B2890B659881BCF5B0164DC /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -502,7 +408,6 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4A5F65B7C3CB297A0D8AB705 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -520,6 +425,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -543,9 +449,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -553,7 +461,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -593,6 +501,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -616,9 +525,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -632,7 +543,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -646,6 +557,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -669,9 +581,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -679,7 +593,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/flutter_modular/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from flutter_modular/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/flutter_modular/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 98% rename from flutter_modular/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8fedab68..ac78810c 100644 --- a/flutter_modular/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ diff --git a/flutter_modular/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/example/macos/Runner.xcworkspace/contents.xcworkspacedata similarity index 67% rename from flutter_modular/example/macos/Runner.xcworkspace/contents.xcworkspacedata rename to example/macos/Runner.xcworkspace/contents.xcworkspacedata index 21a3cc14..1d526a16 100644 --- a/flutter_modular/example/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,7 +4,4 @@ - - diff --git a/flutter_modular/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from flutter_modular/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/flutter_modular/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift similarity index 62% rename from flutter_modular/example/macos/Runner/AppDelegate.swift rename to example/macos/Runner/AppDelegate.swift index d53ef643..b3c17614 100644 --- a/flutter_modular/example/macos/Runner/AppDelegate.swift +++ b/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/flutter_modular/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from flutter_modular/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/flutter_modular/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png similarity index 100% rename from flutter_modular/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png rename to example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png diff --git a/flutter_modular/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png similarity index 100% rename from flutter_modular/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png rename to example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png diff --git a/flutter_modular/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png similarity index 100% rename from flutter_modular/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png rename to example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png diff --git a/flutter_modular/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png similarity index 100% rename from flutter_modular/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png rename to example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png diff --git a/flutter_modular/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png similarity index 100% rename from flutter_modular/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png rename to example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png diff --git a/flutter_modular/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png similarity index 100% rename from flutter_modular/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png rename to example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png diff --git a/flutter_modular/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png similarity index 100% rename from flutter_modular/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png rename to example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png diff --git a/flutter_modular/example/macos/Runner/Base.lproj/MainMenu.xib b/example/macos/Runner/Base.lproj/MainMenu.xib similarity index 100% rename from flutter_modular/example/macos/Runner/Base.lproj/MainMenu.xib rename to example/macos/Runner/Base.lproj/MainMenu.xib diff --git a/flutter_modular/example/macos/Runner/Configs/AppInfo.xcconfig b/example/macos/Runner/Configs/AppInfo.xcconfig similarity index 89% rename from flutter_modular/example/macos/Runner/Configs/AppInfo.xcconfig rename to example/macos/Runner/Configs/AppInfo.xcconfig index dda192bc..f67a84b4 100644 --- a/flutter_modular/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/example/macos/Runner/Configs/AppInfo.xcconfig @@ -11,4 +11,4 @@ PRODUCT_NAME = example PRODUCT_BUNDLE_IDENTIFIER = com.example.example // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2023 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved. diff --git a/flutter_modular/example/macos/Runner/Configs/Debug.xcconfig b/example/macos/Runner/Configs/Debug.xcconfig similarity index 100% rename from flutter_modular/example/macos/Runner/Configs/Debug.xcconfig rename to example/macos/Runner/Configs/Debug.xcconfig diff --git a/flutter_modular/example/macos/Runner/Configs/Release.xcconfig b/example/macos/Runner/Configs/Release.xcconfig similarity index 100% rename from flutter_modular/example/macos/Runner/Configs/Release.xcconfig rename to example/macos/Runner/Configs/Release.xcconfig diff --git a/flutter_modular/example/macos/Runner/Configs/Warnings.xcconfig b/example/macos/Runner/Configs/Warnings.xcconfig similarity index 100% rename from flutter_modular/example/macos/Runner/Configs/Warnings.xcconfig rename to example/macos/Runner/Configs/Warnings.xcconfig diff --git a/flutter_modular/example/macos/Runner/DebugProfile.entitlements b/example/macos/Runner/DebugProfile.entitlements similarity index 100% rename from flutter_modular/example/macos/Runner/DebugProfile.entitlements rename to example/macos/Runner/DebugProfile.entitlements diff --git a/flutter_modular/example/macos/Runner/Info.plist b/example/macos/Runner/Info.plist similarity index 100% rename from flutter_modular/example/macos/Runner/Info.plist rename to example/macos/Runner/Info.plist diff --git a/flutter_modular/example/macos/Runner/MainFlutterWindow.swift b/example/macos/Runner/MainFlutterWindow.swift similarity index 100% rename from flutter_modular/example/macos/Runner/MainFlutterWindow.swift rename to example/macos/Runner/MainFlutterWindow.swift diff --git a/flutter_modular/example/macos/Runner/Release.entitlements b/example/macos/Runner/Release.entitlements similarity index 100% rename from flutter_modular/example/macos/Runner/Release.entitlements rename to example/macos/Runner/Release.entitlements diff --git a/flutter_modular/example/macos/RunnerTests/RunnerTests.swift b/example/macos/RunnerTests/RunnerTests.swift similarity index 100% rename from flutter_modular/example/macos/RunnerTests/RunnerTests.swift rename to example/macos/RunnerTests/RunnerTests.swift index 5418c9f5..61f3bd1f 100644 --- a/flutter_modular/example/macos/RunnerTests/RunnerTests.swift +++ b/example/macos/RunnerTests/RunnerTests.swift @@ -1,5 +1,5 @@ -import FlutterMacOS import Cocoa +import FlutterMacOS import XCTest class RunnerTests: XCTestCase { diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 00000000..614cc364 --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,96 @@ +name: example +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.11.5 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # Lets us swap the web URL strategy (drop the `#` fragment). Part of the + # Flutter SDK; a no-op on non-web platforms. + flutter_web_plugins: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + + flutter_modular: + path: ../ + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.2 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart new file mode 100644 index 00000000..f7be2cb6 --- /dev/null +++ b/example/test/widget_test.dart @@ -0,0 +1,229 @@ +import 'package:example/app/app_module.dart'; +import 'package:example/app/checkout/data/checkout_service.dart'; +import 'package:example/app/dashboard/dashboard_shell.dart'; +import 'package:example/core/state/theme_controller.dart'; +import 'package:example/main.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +Widget app() => ModularApp( + module: appModule, + initialRoute: '/home', // the landing feature is mounted at /home + provide: (s) => s.addChangeNotifier(ThemeController.new), + child: const AppRoot(), +); + +void main() { + testWidgets('home → products list loads from the repository (SSoT)', ( + tester, + ) async { + await tester.pumpWidget(app()); + await tester.pumpAndSettle(); + + expect(find.text('A tiny store, modular.'), findsOneWidget); + + await tester.tap(find.text('Browse products')); + await tester.pumpAndSettle(); + + // Served by ProductRepository (the single source of truth). + expect(find.text('Mechanical Keyboard'), findsOneWidget); + expect(find.text('4 items'), findsOneWidget); // Selector badge + + // The mounted-module index gets an explicit back button (canPop bubbles to + // the root); tapping it returns home. + expect(find.byType(BackButton), findsOneWidget); + await tester.tap(find.byType(BackButton)); + await tester.pumpAndSettle(); + expect(find.text('A tiny store, modular.'), findsOneWidget); + }); + + testWidgets('settings toggles the app-scoped theme (rebuilds MaterialApp)', ( + tester, + ) async { + await tester.pumpWidget(app()); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Settings')); + await tester.pumpAndSettle(); + + MaterialApp materialApp() => + tester.widget(find.byType(MaterialApp)); + expect(materialApp().themeMode, ThemeMode.light); + + await tester.tap(find.text('Dark theme')); + await tester.pumpAndSettle(); + + expect(materialApp().themeMode, ThemeMode.dark); + }); + + testWidgets('args flow: pass an object, edit, and pop a result back', ( + tester, + ) async { + await tester.pumpWidget(app()); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Arguments & pop result')); + await tester.pumpAndSettle(); + expect(find.text('No result yet — open the editor.'), findsOneWidget); + + await tester.tap(find.text('Open editor')); + await tester.pumpAndSettle(); + // The object argument arrived: title + prefilled text come from EditorArgs. + expect(find.text('Edit greeting'), findsOneWidget); + expect(find.text('Hello, Modular!'), findsOneWidget); + + // Edit and Save → the value is popped back to the awaiting pushNamed. + await tester.enterText(find.byType(TextField), 'Edited!'); + await tester.tap(find.text('Save')); + await tester.pumpAndSettle(); + expect(find.text('Editor returned: "Edited!"'), findsOneWidget); + }); + + testWidgets( + 'checkout feature module: service bound on enter, kept across its routes, ' + 'disposed only after the last leaves, recreated on re-entry', + (tester) async { + CheckoutService.opens = 0; + CheckoutService.closes = 0; + + await tester.pumpWidget(app()); + await tester.pumpAndSettle(); + expect(CheckoutService.opens, 0); // not entered yet → not bound + + // Enter checkout (route A) → the feature service is created once. + await tester.tap(find.text('Checkout')); + await tester.pumpAndSettle(); + expect(find.textContaining('Checkout session #'), findsOneWidget); + expect(CheckoutService.opens, 1); + expect(CheckoutService.closes, 0); + + // Push the payment route (B) → SAME instance (module still active). + await tester.tap(find.text('To payment')); + await tester.pumpAndSettle(); + expect(find.textContaining('Paying for session #'), findsOneWidget); + expect(CheckoutService.opens, 1); + + // Pop B → A still active → not disposed. + await tester.tap(find.text('Confirm')); + await tester.pumpAndSettle(); + expect(CheckoutService.closes, 0); + + // Leave checkout entirely (last route) → disposed. + await tester.tap(find.text('Back home')); + await tester.pumpAndSettle(); + expect(CheckoutService.closes, 1); + + // Re-enter → a fresh service instance. + await tester.tap(find.text('Checkout')); + await tester.pumpAndSettle(); + expect(CheckoutService.opens, 2); + }, + ); + + testWidgets('dashboard: the RouterOutlet shell persists while the body swaps', ( + tester, + ) async { + DashboardShell.inits = 0; + await tester.pumpWidget(app()); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Dashboard (RouterOutlet)')); + await tester.pumpAndSettle(); + expect(find.text('Feed view'), findsOneWidget); // index tab + expect(DashboardShell.inits, 1); + + // Switch tabs via the bottom bar → the body swaps, the shell stays mounted. + await tester.tap(find.text('Search')); + await tester.pumpAndSettle(); + expect(find.text('Search view'), findsOneWidget); + expect(find.text('Feed view'), findsNothing); + expect(DashboardShell.inits, 1); // shell NOT rebuilt → it persisted + + await tester.tap(find.text('Profile')); + await tester.pumpAndSettle(); + expect(find.text('Profile view'), findsOneWidget); + expect(DashboardShell.inits, 1); + + // Push from inside a tab → stacks INSIDE the outlet, shell still mounted. + await tester.tap(find.text('Feed')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Open an item')); + await tester.pumpAndSettle(); + expect(find.text('Item detail — still inside the shell'), findsOneWidget); + expect( + find.byType(NavigationBar), + findsOneWidget, + ); // bottom bar still there + expect(DashboardShell.inits, 1); + }); + + testWidgets('deep link to /home/dashboard boots into the shell (Feed tab)', ( + tester, + ) async { + // The platform hands the real entry URL via defaultRouteName — a refresh + // on the dashboard must land in the shell, not collapse to the landing. + tester.platformDispatcher.defaultRouteNameTestValue = '/home/dashboard'; + addTearDown(tester.platformDispatcher.clearDefaultRouteNameTestValue); + + await tester.pumpWidget(app()); + await tester.pumpAndSettle(); + + expect(find.text('Feed view'), findsOneWidget); // seeded inside the outlet + expect(find.byType(NavigationBar), findsOneWidget); // shell mounted + expect(find.text('A tiny store, modular.'), findsNothing); // not home + }); + + testWidgets('dashboard: the active-tab highlight is DERIVED from the route ' + '(deep link to a non-default tab, then bottom-bar switches)', ( + tester, + ) async { + // Boot straight to the Profile tab — the highlight must follow the route, + // not collapse to Feed (the dessynced-_index bug this fix closes). + tester.platformDispatcher.defaultRouteNameTestValue = + '/home/dashboard/profile'; + addTearDown(tester.platformDispatcher.clearDefaultRouteNameTestValue); + + await tester.pumpWidget(app()); + await tester.pumpAndSettle(); + + NavigationBar bar() => + tester.widget(find.byType(NavigationBar)); + expect(find.text('Profile view'), findsOneWidget); + expect(bar().selectedIndex, 2); // Profile lit on deep link + + // Switch via the bottom bar → the highlight follows, no local state. + await tester.tap(find.text('Search')); + await tester.pumpAndSettle(); + expect(find.text('Search view'), findsOneWidget); + expect(bar().selectedIndex, 1); + + await tester.tap(find.text('Feed')); + await tester.pumpAndSettle(); + expect(find.text('Feed view'), findsOneWidget); + expect(bar().selectedIndex, 0); + }); + + testWidgets('guard redirects the secret route until the app is unlocked', ( + tester, + ) async { + await tester.pumpWidget(app()); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Settings')); + await tester.pumpAndSettle(); + + // Locked: the guard redirects back to /settings. + await tester.tap(find.text('Open secret area')); + await tester.pumpAndSettle(); + expect(find.text('🔓 Secret area unlocked'), findsNothing); + expect(find.text('Settings'), findsOneWidget); + + // Unlock, then it is allowed. + await tester.tap(find.text('Unlock secret area')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Open secret area')); + await tester.pumpAndSettle(); + expect(find.text('🔓 Secret area unlocked'), findsOneWidget); + }); +} diff --git a/flutter_modular/example/web/favicon.png b/example/web/favicon.png similarity index 100% rename from flutter_modular/example/web/favicon.png rename to example/web/favicon.png diff --git a/flutter_modular/example/web/icons/Icon-192.png b/example/web/icons/Icon-192.png similarity index 100% rename from flutter_modular/example/web/icons/Icon-192.png rename to example/web/icons/Icon-192.png diff --git a/flutter_modular/example/web/icons/Icon-512.png b/example/web/icons/Icon-512.png similarity index 100% rename from flutter_modular/example/web/icons/Icon-512.png rename to example/web/icons/Icon-512.png diff --git a/flutter_modular/example/web/icons/Icon-maskable-192.png b/example/web/icons/Icon-maskable-192.png similarity index 100% rename from flutter_modular/example/web/icons/Icon-maskable-192.png rename to example/web/icons/Icon-maskable-192.png diff --git a/flutter_modular/example/web/icons/Icon-maskable-512.png b/example/web/icons/Icon-maskable-512.png similarity index 100% rename from flutter_modular/example/web/icons/Icon-maskable-512.png rename to example/web/icons/Icon-maskable-512.png diff --git a/example/web/index.html b/example/web/index.html new file mode 100644 index 00000000..badaed34 --- /dev/null +++ b/example/web/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + example + + + + + + + diff --git a/flutter_modular/example/web/manifest.json b/example/web/manifest.json similarity index 100% rename from flutter_modular/example/web/manifest.json rename to example/web/manifest.json diff --git a/flutter_modular/example/windows/.gitignore b/example/windows/.gitignore similarity index 100% rename from flutter_modular/example/windows/.gitignore rename to example/windows/.gitignore diff --git a/flutter_modular/example/windows/CMakeLists.txt b/example/windows/CMakeLists.txt similarity index 93% rename from flutter_modular/example/windows/CMakeLists.txt rename to example/windows/CMakeLists.txt index 13786727..d960948a 100644 --- a/flutter_modular/example/windows/CMakeLists.txt +++ b/example/windows/CMakeLists.txt @@ -8,7 +8,7 @@ set(BINARY_NAME "example") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. -cmake_policy(SET CMP0063 NEW) +cmake_policy(VERSION 3.14...3.25) # Define build configuration option. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) @@ -87,6 +87,12 @@ if(PLUGIN_BUNDLED_LIBRARIES) COMPONENT Runtime) endif() +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") diff --git a/flutter_modular/example/windows/flutter/CMakeLists.txt b/example/windows/flutter/CMakeLists.txt similarity index 100% rename from flutter_modular/example/windows/flutter/CMakeLists.txt rename to example/windows/flutter/CMakeLists.txt diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..8b6d4680 --- /dev/null +++ b/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/flutter_modular/example/windows/flutter/generated_plugin_registrant.h b/example/windows/flutter/generated_plugin_registrant.h similarity index 100% rename from flutter_modular/example/windows/flutter/generated_plugin_registrant.h rename to example/windows/flutter/generated_plugin_registrant.h diff --git a/flutter_modular/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake similarity index 91% rename from flutter_modular/example/windows/flutter/generated_plugins.cmake rename to example/windows/flutter/generated_plugins.cmake index a0d13886..b93c4c30 100644 --- a/flutter_modular/example/windows/flutter/generated_plugins.cmake +++ b/example/windows/flutter/generated_plugins.cmake @@ -3,9 +3,6 @@ # list(APPEND FLUTTER_PLUGIN_LIST - permission_handler_windows - share_plus - url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/flutter_modular/example/windows/runner/CMakeLists.txt b/example/windows/runner/CMakeLists.txt similarity index 100% rename from flutter_modular/example/windows/runner/CMakeLists.txt rename to example/windows/runner/CMakeLists.txt diff --git a/flutter_modular/example/windows/runner/Runner.rc b/example/windows/runner/Runner.rc similarity index 97% rename from flutter_modular/example/windows/runner/Runner.rc rename to example/windows/runner/Runner.rc index aecaa2b5..0e3e27b9 100644 --- a/flutter_modular/example/windows/runner/Runner.rc +++ b/example/windows/runner/Runner.rc @@ -93,7 +93,7 @@ BEGIN VALUE "FileDescription", "example" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "example" "\0" - VALUE "LegalCopyright", "Copyright (C) 2023 com.example. All rights reserved." "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" VALUE "OriginalFilename", "example.exe" "\0" VALUE "ProductName", "example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" diff --git a/flutter_modular/example/windows/runner/flutter_window.cpp b/example/windows/runner/flutter_window.cpp similarity index 100% rename from flutter_modular/example/windows/runner/flutter_window.cpp rename to example/windows/runner/flutter_window.cpp diff --git a/flutter_modular/example/windows/runner/flutter_window.h b/example/windows/runner/flutter_window.h similarity index 100% rename from flutter_modular/example/windows/runner/flutter_window.h rename to example/windows/runner/flutter_window.h diff --git a/flutter_modular/example/windows/runner/main.cpp b/example/windows/runner/main.cpp similarity index 100% rename from flutter_modular/example/windows/runner/main.cpp rename to example/windows/runner/main.cpp diff --git a/flutter_modular/example/windows/runner/resource.h b/example/windows/runner/resource.h similarity index 100% rename from flutter_modular/example/windows/runner/resource.h rename to example/windows/runner/resource.h diff --git a/flutter_modular/example/windows/runner/resources/app_icon.ico b/example/windows/runner/resources/app_icon.ico similarity index 100% rename from flutter_modular/example/windows/runner/resources/app_icon.ico rename to example/windows/runner/resources/app_icon.ico diff --git a/flutter_modular/example/windows/runner/runner.exe.manifest b/example/windows/runner/runner.exe.manifest similarity index 68% rename from flutter_modular/example/windows/runner/runner.exe.manifest rename to example/windows/runner/runner.exe.manifest index a42ea768..153653e8 100644 --- a/flutter_modular/example/windows/runner/runner.exe.manifest +++ b/example/windows/runner/runner.exe.manifest @@ -9,12 +9,6 @@ - - - - - - diff --git a/flutter_modular/example/windows/runner/utils.cpp b/example/windows/runner/utils.cpp similarity index 93% rename from flutter_modular/example/windows/runner/utils.cpp rename to example/windows/runner/utils.cpp index b2b08734..3a0b4651 100644 --- a/flutter_modular/example/windows/runner/utils.cpp +++ b/example/windows/runner/utils.cpp @@ -45,13 +45,13 @@ std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } - int target_length = ::WideCharToMultiByte( + unsigned int target_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, nullptr, 0, nullptr, nullptr) -1; // remove the trailing null character int input_length = (int)wcslen(utf16_string); std::string utf8_string; - if (target_length <= 0 || target_length > utf8_string.max_size()) { + if (target_length == 0 || target_length > utf8_string.max_size()) { return utf8_string; } utf8_string.resize(target_length); diff --git a/flutter_modular/example/windows/runner/utils.h b/example/windows/runner/utils.h similarity index 100% rename from flutter_modular/example/windows/runner/utils.h rename to example/windows/runner/utils.h diff --git a/flutter_modular/example/windows/runner/win32_window.cpp b/example/windows/runner/win32_window.cpp similarity index 100% rename from flutter_modular/example/windows/runner/win32_window.cpp rename to example/windows/runner/win32_window.cpp diff --git a/flutter_modular/example/windows/runner/win32_window.h b/example/windows/runner/win32_window.h similarity index 100% rename from flutter_modular/example/windows/runner/win32_window.h rename to example/windows/runner/win32_window.h diff --git a/flutter_modular/.gitignore b/flutter_modular/.gitignore deleted file mode 100644 index 45cd6e7d..00000000 --- a/flutter_modular/.gitignore +++ /dev/null @@ -1,51 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -/build/ - -# Web related -lib/generated_plugin_registrant.dart - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json - -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release - - -# coverage -coverage/ -pubspec.lock \ No newline at end of file diff --git a/flutter_modular/.vscode/launch.json b/flutter_modular/.vscode/launch.json deleted file mode 100644 index f3a2ed06..00000000 --- a/flutter_modular/.vscode/launch.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - // Use o IntelliSense para saber mais sobre os atributos possíveis. - // Focalizar para exibir as descrições dos atributos existentes. - // Para obter mais informações, acesse: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "shelf_modular", - "type": "dart", - "cwd": "../shelf_modular", - "request": "launch", - "program": "example/bin/example.dart", - "args": ["--enable-vm-service"] - }, - { - "name": "flutter_modular", - "request": "launch", - "type": "dart" - }, - { - "name": "flutter_modular (profile mode)", - "request": "launch", - "type": "dart", - "flutterMode": "profile" - }, - { - "name": "example", - "cwd": "example", - "request": "launch", - "type": "dart" - }, - { - "name": "example (profile mode)", - "cwd": "example", - "request": "launch", - "type": "dart", - "flutterMode": "profile" - } - ] -} \ No newline at end of file diff --git a/flutter_modular/CHANGELOG.md b/flutter_modular/CHANGELOG.md deleted file mode 100644 index 6461c58a..00000000 --- a/flutter_modular/CHANGELOG.md +++ /dev/null @@ -1,739 +0,0 @@ -## [6.4.1] - 2025/06/12 -- Fix AppModule Dispose. - -## [6.4.0] - 2025-06-10 -- Fix Deep Link query parameters. -- Update Result. - - -## [6.3.3] - 2024-04-08 -- Fix Deep Link - -## [6.3.0] - 2023-08-31 -- Fix phantom dependencies. - When an instance was registered in an ancestor Module, it was available to its children - automatically. This created comprehension issues when the module was separated from the base app. - Now we recommend creating a global Module, exporting the binds and importing them into the modules. - -## [6.1.1] - 2023-08-8 -- Fix Exported Dependencies - -## [6.1.0+1] - 2023-08-24 - -- Add: Register and get with keys: -```dart -i.add(MyController.new, key: 'Custom'); -... - -Modular.get(key: 'Custom'); -``` - -## [6.0.4] - 2023-08-16 - -- fix: support Flutter 3.13 - -## [6.0.3] - 2023-08-09 - -- fix: [#875](https://github.com/Flutterando/modular/issues/875) -- fix: [#615](https://github.com/Flutterando/modular/issues/615) - -## [6.0.2] - 2023-08-01 - -- fix: [#867](https://github.com/Flutterando/modular/issues/867) -- fix: [#869](https://github.com/Flutterando/modular/issues/869) - - -## [6.0.1] - 2023-08-01 - -- fix: InnerModules in routes. - - -## [6.0.0+2] - 2023-07-31 - -- Supports Dart 3.0 (Flutter >=3.10.0) -- Added `Modular.routerConfig` for new inicialization. -```dart -MaterialApp.router( - routerConfig: Modular.routerConfig, -); -``` -- **BREAKING CHANGES** Added: Using `auto_injector` instead of `Core`. -Since 2020, Modular has used the engine called `Core` for dependency injections and starting with v6 -will be replaced by `auto_injector`.
    -`auto_injector` uses the constructor as the instance signature for registration removing the need -of the `i()` argument in the bind register.
    - -**v5** -```dart -Bind((i) => MyClass(i(), i(), i())), -``` -**v6** -```dart -i.add(MyClass.new); -``` -- Added: `exportedBinds(i)` to register instances can be imported by other Modules. - -- **BREAKING CHANGES** refactor: The registration of routes was previously done by a getter of `List`, now the registration happens in method `routes(RouteManager r);`. - -**v5** -```dart -class AModule extends Module { - ... - @override - List get routes => [ - ChildRoute('/', child: (context, args) => APage()), - ModuleRoute('/b-module', module: BModule()), - ]; - ... -} -``` -**v6** -```dart -class AModule extends Module { - ... - @override - void routes(RouteManager r) { - r.child('/', child: (context) => APage()); - r.module('/b-module', module: BModule()); - } - ... -} -``` - -- **BREAKING CHANGES**: refactor: `ChildRoute`, `ModuleRoute` and `WildcardRoute` child. Removes `args`: - -**v5** -```dart - @override - List get routes => [ - ChildRoute('/', child: (context, args) => APage(data: args.data)), - ]; - -``` -**v6** -```dart - @override - void routes(RouteManager r) { - final args = r.args; - r.child('/', child: (context) => APage(data: args.data)); - } -``` - -- @DEPRECATED: `modular_test` package. Please check the `Test` section of documentation. - -- @DEPRECATED: `bloc_modular_bind` package. Please check the `Watch` section of documentation. - -- @DEPRECATED: `triple_modular_bind` package. Please check the `Watch` section of documentation. - -- Removed: `WidgetModule`; -- Removed: `ModularState`; -- Removed: `AsyncBind`; - - - -## [5.0.3] - 2022-06-03 -- Fix [#713](https://github.com/Flutterando/modular/issues/713) -- Fix [#676](https://github.com/Flutterando/modular/issues/676) -- Fix [#632](https://github.com/Flutterando/modular/issues/632) - -## [5.0.2] - 2022-04-22 -- Fix: Parse params in RouteOutlet -## [5.0.1] - 2022-04-22 -- Fix: Inject.get should return instance. - -## [5.0.0] - 2022-04-21 -- Support Flutter 3.0.0 -- [BREAK CHANGE]: Removed `MaterialApp.modular()` and `Cupertino().modular()`. - Use instead: - ```dart - return MaterialApp.router( - routeInformationParser: Modular.routeInformationParser, - routerDelegate: Modular.routerDelegate, - ); - - ``` -This modification aims to keep Modular support independent of `WidgetApp` updates, and can be used in other bootstraps such as `FluentApp [fluent_ui]`. - -- [BREAK CHANGE]: New auto-dispose configuration. -Previously Modular had automatic closing or destruction calls for objects of type `ChangeNotifier/ValueNotifier`, `Stream` and Triple\`s `Stores`. -Starting with version 5.0, Modular will provide the `Bind.onDispose` property for calls to destroy, close or dispose methods FOR EACH BIND. This will make the dispose settings more straightforward and less universal. Therefore, Modular will manage the destruction of Binds that implement `Disposable` only. This is the new configuration: -```dart -@override -final List binds = [ - Bind.singleton((i) => MyBloc(), onDispose: (bloc) => bloc.close()), -]; -``` -The `Bind.onDispose` CANNOT be used in Bind type factory. -You can choose to use `Bind.onDispose` or implement the `Disposable` class. - -- Added `Bind.selector`. Generates a reactivity (Listenable/Stream) to be listened to when `context.watch()` is called. -```dart -@override -final List binds = [ - //notifier return stream or listenable to use context.watch() - Bind.singleton((i) => MyBloc(), onDispose: (bloc) => bloc.close(), selector: (bloc) => bloc.stream), -]; -``` - -- [BREAK CHANGE]: As already described above, the reactivities worked externally to Modular, providing a -longer life to the project. For this reason, BLoC or Triple users should use special `Bind's` in order to use the `context.watch()` and auto dispose functionality. They are: `BlocBind()` and `TripleBind()`, which are available through external packages. -[modular_bloc_bind](https://pub.dev/packages/modular_bloc_bind) -> BlocBind
    -[modular_triple_bind](https://pub.dev/packages/modular_triple_bind) -> TripleBind - -Example: -```dart - -@override -final List binds = [ - BlocBind.singleton((i) => MyBloc()), -]; -``` - -- [BREAK CHANGE] `Bind.export` works only after imported. -- @deprecated `ModularState`. -A few months of research showed us that ModularState caused unnecessary coupling with the view and made it difficult for those who used it to understand. For this reason, we decided to deprecate it to ensure code congruence for all professionals who use Modular. - -- Removed `triple` dependency. -- Simplify docs. -- Added `Modular.setArguments`. -```dart -Modular.setArguments('cody1024d'); - -// get -Modular.args.data; // -> cody1024d -//or -Bind((i) => MyClass(i.args.data)); - -``` - -### Issues - -- Fix [#615](https://github.com/Flutterando/modular/issues/615) -- Fix [#643](https://github.com/Flutterando/modular/issues/643) -- Fix [#644](https://github.com/Flutterando/modular/issues/644) -- Fix [#666](https://github.com/Flutterando/modular/issues/666) -- Fix [#668](https://github.com/Flutterando/modular/issues/668) -- Fix [#681](https://github.com/Flutterando/modular/issues/681) -- Fix [#694](https://github.com/Flutterando/modular/issues/694) - -## [4.5.1+1] - 2022-04-05 -- Fixed `modular_core` and resolve issues [#699] [#671] [#678]. - -## [4.5.0] - 2022-02-22 - -- @Deprecated: `.modular()` extension. - Use instead: - ```dart - return MaterialApp.router( - routeInformationParser: Modular.routeInformationParser, - routerDelegate: Modular.routerDelegate, - ); - - ``` -- Added `Modular.setInitialRoute`. -- Added `Modular.setObservers`. -- Added `Modular.setNavigatorKey`. - -## [4.4.1] - 2022-02-18 - -- Fix bugs in lints. - -## [4.4.0+1] - 2022-01-22 - -- Added `CustomTransition Expansion`. - -## [4.4.0] - 2022-01-17 - -- Added `Modular.to.navigateHistory`. -- Added ChildRoute.maintainState property. -- Updated **Triple** version. - -## [4.3.1] - 2021-12-10 - -- Fixed "bind replaced" bug - -## [4.3.0] - 2021-12-10 - -- Added BuildContext extension [context.read()] and [context.watch()]; -- The [context.watch()] listen changes of [Listanable], [Stream] and [Store] by Triple; - -```dart -class Body extends StatelessWidget { - Widget build(BuildContext context){ - final notifier = context.watch(); - return Text('${notifier.value}') - } -} -``` - -- Use `select` in `.watch()` to select the reactive property: - -```dart -class Body extends StatelessWidget { - Widget build(BuildContext context){ - final bloc = context.watch((bloc) => bloc.stream); - return Text('${bloc.state}') - } -} -``` - -Also, use `Store Selectors` in conjunction with `.watch`: - -```dart -class OnlyErrorWidget extends StatelessWidget { - Widget build(BuildContext context){ - // changes with store.setError(); - final store = context.watch((store) => store.selectError); - return Text('${store.error}') - } -} -``` - -See more details [here](https://modular.flutterando.com.br/docs/flutter_modular/watch) - -## [4.2.0] - 2021-10-28 - -- Added cleanInjector() and cleanModular() for restart Modular. [#601](https://github.com/Flutterando/modular/pull/601) -- Updated modular_core. - -## [4.1.2] - 2021-10-08 - -- Added "maintainState" in routes. [#572](https://github.com/Flutterando/modular/issues/572) -- Fixed pushReplacementNamed - -## [4.1.0+1] - 2021-10-01 - -- Modular is now HOT-RELOAD friendly! -- Added ReassembleMixin. - -## [4.0.1+1] - 2021-09-22 - -- Fixed pushNamed. -- Fixed bug that allowed access to parameters and arguments in other modules. -- Fixed transitions bug. -- Fixed navigation blink. - -## [4.0.0+12] - 2021-09-16 - -- New documentation is here! [https://modular.flutterando.com.br](https://modular.flutterando.com.br). -- Modular design now uses Layered Architecture (Clean Architecture) with 100% code coverage. -- Up to 30% improvement in obtaining routes. -- BREAK CHANGE: **RouteGuard([redirectTo])** -> **RouteGuard({String? redirectTo})**. -- BREAK CHANGE: **flutter_modular_test** will be discontinued. Use **modular_test** instead. -- FIX [#516](https://github.com/Flutterando/modular/issues/516) - -## [3.4.1+1] - 2021-08-20 - -- Fix path(remove @) - -## [3.4.0] - 2021-08-18 - -- Added **RedirectRoute**. -- Navigation Transition now is more responsive! -- Triple integration -- Fix Bind when you only have Imports. - -## [3.3.1] - 2021-07-07 - -- Fix popUntil stack overflow -- Fix navigatorObservers - -## [3.3.0] - 2021-06-25 - -- Fix popUntil. -- Fix pushNamedAndRemoveUntil. -- Fix premature instanciation of singleton when flag experimentalNotAllowedParentBinds is true. -- Fix back and forward browser buttons navigation -- Remove RxDart dependency. -- Export ModularError interface. -- Multiples RouterOutlets. - -## [3.2.2+1] - 2021-05-19 - -- Fix popAndPushNamed - -## [3.2.1] - 2021-05-11 - -- Fix AsyncBind (Thanks Ygor and Gil); - -## [3.2.0] - 2021-05-03 - -- Added **AsyncBind** for Future injection binds. - -```dart -final List binds = [ - AsyncBind((i) => SharedPreferences.getInstance()), -]; - -... - -//get async -final share = await Modular.getAsync(); -//or initalize the module first -await Modular.isModuleReady(); -final share = Modular.get(); - -``` - -- Fix break navigation on **RouteOutlet** when frenetics tab changes -- Bwolf`s commits -- More bugs fixeds (Thanks @Mex978) -- Fix Navigate Flutter Web error. - -## [3.1.1] - 2021-04-25 - -- EXPERIMENTAL: ModularApp.notAllowedParentBinds. If true, all modules will only have access to their Binds, or Binds of imported modules (Module.imports); -- Page Transition use CupertinoPageRoute if application use CupertinoApp. -- A lot of bugs fixeds (Thanks @Mex978) - -## [3.1.0] - 2021-04-11 - -- Added redirect route when RouteGuard fails [#351](https://github.com/Flutterando/modular/issues/3510): - -```dart -@override -final List routes = [ - ChildRoute( - '/home', - child: (context, args) => HomePage(), - guards: [AuthGuard()], - guardedRoute: '/login', - ), - ChildRoute( - '/login', - child: (context, args) => LoginPage(), - ), -]; -``` - -- Fixed ChildRoute Generic type -- Dispose errors -- Fixed WidgetModule bugs - -## [3.0.2] - 2021-03-22 - -- Support modular_codegen 3.0.0 -- Added Support to CupertinoApp.modular() -- Fix bug: Get arguments in RouteGuard - -## [3.0.0+1] - 2021-03-12 - -- Fix pushNamed bug -- Fix parameter bugs -- navigate replaceAll aways true (@deprecated) - -## [3.0.0+1] - 2021-03-09 - -- BIG RELEASE! - -## [2.5.0] - - -- Navigator 2.0 -- Fixed Modular.link -- Refactor RouteGuard -- Added Modular.to.navigate -- Added 3 new bind factories - -1. Bind.factory -2. Bind.singleton -3. Bind.lazySingleton - -## [2.0.1] - 21 Sep 2020 - -- added onChangeRoute propety in RouterOutlet - -## [2.0.0+1] - 21 Ago 2020 - -### Welcome to Flutter Modular 2.0!!! - -## Break Changes - -Router object Renamed to ModularRouter. - -```dart -//before - @override - List get routers => [ - Router('/', (i, args) => LoginPage()), - Router('/home', (i, args) => HomePage()), - ]; -//now 2.0 - @override - List get routers => [ - ModularRouter('/', (i, args) => LoginPage()), - ModularRouter('/home', (i, args) => HomePage()), - ]; -``` - -- New Widget `RouterOutletList` (Check doc); -- `Inject.params` is deprecated - -## [1.3.2] - 30 Jul 2020 - -- Fix issue [#210](https://github.com/Flutterando/modular/issues/210) - -## [1.3.1] - 19 Jul 2020 - -- Prevent StackOverflow in Injections - -## [1.3.0] - 12 Jul 2020 - -- modular_codegen integration. -- Added defaul value in Modular.get - -```dart -//return AppBlocMock if no injectable AppBloc in module. -var appBloc = Modular.get(defaultValue: AppBlocMock()); -``` - -- Fix CI (Tests) and Lints -- Update docs - -## [1.2.7+1] - 26 Jun 2020 - -- Fix route error issue [#118](https://github.com/Flutterando/modular/issues/118) -- Added WillPopScope in RouterOutlet - -## [1.2.6+1] - 23 Jun 2020 - -- Direct call Inject - -```dart - @override - List get binds => [ - Bind((i) => HomeBloc(repository: i(), appBloc: i())), - Bind((i) => HomeRepository(dio: i())), - Bind((i) => Dio()), - ]; -``` - -Use **i()** instead **i.get()** - -## [1.2.5+1] - 26 May 2020 - -- Fix Modular.link bug -- Smooth Animation Navigator: 56% faster navigation animations - -## [1.2.4] - 23 May 2020 - -- Welcome Navigator API 2.0!!! -- Added push, pushReplacement in Modular.to and Modular.link; -- Added Modular.navigatorDelegate for tests mocks. Just implements IModularNavigator. - -```dart -//Modular.to and Modular.link will be called MyNavigatorMock implements! -Modular.navigatorDelegate = MyNavigatorMock(); -``` - -## [1.2.3] - 19 May 2020 - -- Health suggestions -- Added Contributors in README -- Fix RouterOutlet -- Fix Modular.link - -## [1.2.1] - 15 May 2020 - -- Fix bugs -- new Modular.link for Navigation in Current Module; - -```dart -//Modules home>product -Modular.to.pushNamed('/home/product/list'); -Modular.to.pushNamed('/home/product/detail/:id'); - -//into product module, use Modular.link and navigate between routes of current Module (Product) - -Modular.link.pushNamed('/list'); -Modular.link.pushNamed('/detail/:id'); - -``` - -Use Modular.to for literal paths or Modular.link for routes in current module. - -- Finally, use Modular.to.path (or Modular.link.path) if you want see the "Current Route Path". - -## [1.1.2] - 13 Apr 2020 - -- Fix bugs - -## [1.1.1] - 07 Apr 2020 - -- Added **showDialog** - -```dart -Modular.to.showDialog( - barrierDismissible: false, - builder: (_) => AlertDialog(), -); -``` - -## [1.0.0] - 24 Mar 2020 - -- Release!!! - -## [0.5.6] - 13 Mar 2020 - -- Added keepAlive flag in RouterOutlet. - -## [0.5.5] - 08 Mar 2020 - -- Fix StackOverflow error -- Fix RouteGuard -- Fix Transitions Animation -- PREVIEW: RouterOutlet Widget - Use Navigation in BottomBarTab or Drawer - -```Dart -PageView( - controller: controller - children: [ - RouterOutlet( - module: Tab1Module() - ), - RouterOutlet( - module: Tab2Module() - ), - RouterOutlet( - module: Tab3Module() - ), - ] -), -``` - -NOTE: Navigation is only Navigator.of (context) and only uses the module's literal route path. - -## [0.5.3] - 05 Mar 2020 - -- Prevent StackOverflow - -## [0.5.2] - 20 Feb 2020 - -- Prevent StackOverflow - -## [0.5.1] - 15 Feb 2020 - -- fix #52 - -## [0.5.0] - 13 Feb 2020 - -- Added router generic type - -```dart - @override - List get routers => [ - //type router with return type - Router('/event', child: (_, args) => EventPage()), - ] -``` - -Now you can type your pushNamed and pop - -```dart - String value = await Modular.to.pushNamed(); - //and - Modular.to.pop('My String'); -``` - -## [0.4.7] - 9 Feb 2020 - -- Added Custom Transition. -- Added **Modular.args** (get route params in Controller). -- (PREVIEW) RouterGuard in child routes. -- Fix error in WidgetTests -- Added Print routers in debugMode - -## [0.4.5] - 7 Feb 2020 - -- Added not lazy Objects - -```dart -@override - List get binds => [ - Bind((i) => OtherWidgetNotLazy(), lazy: false), - ]; -``` - -## [0.4.4] - 6 Feb 2020 - -- fix RouterGuards -- Added Modular.debugMode = false; -- Improve documentations -- Fix Error in initalRoute - -## [0.4.3] - 1 Feb 2020 - -- fix RouterGuards -- Added Modular.debugMode = false; - -## [0.4.2] - 1 Feb 2020 - -- fix routerGuards -- fix tests - -## [0.4.1] - 30 Jan 2020 - -- Internal Inject Interface reference - -## [0.4.0] - 28 Jan 2020 - -- added Modular.dispose(); -- ModularState -- Removed InjectMixin - -## [0.3.5+1] - 26 Jan 2020 - -- fix module widget -- fix inject error - -## [0.3.3+1] - 18 Jan 2020 - -- Modular is BETA!!! -- You can now control navigation without the context! -- Added **Modular.to** and replace Navigator.of(context) -- Added **Modular.get** and replace AppModule.to.get -- Added flag "singleton" in Bind injection -- Fix Router Guard - -## [0.1.8] - 08 Jan 2020 - -- fix test errors (initModule) -- Added modularException - -## [0.1.4] - 24 Dec 2019 - -- fix #7 and more - -## [0.1.3] - 17 Dec 2019 - -- Route Settings, RemoveUntil fix #11 - -## [0.1.1] - 17 Dec 2019 - -- Fix tests - -## [0.1.0+1] - 16 Dec 2019 - -- Added Route Transitions. -- Change ModularWidget to ModularApp. - -## [0.0.10] - 14 Dec 2019 - -- Added logo - -## [0.0.8] - 13 Dec 2019 - -- Route Guard - -## [0.0.7] - 10 Dec 2019 - -- Dynamic Router -- Added Doc Translation -- Change BrowserModule to MainModule -- Change CommonModule to Module -- Corrigido erro de blink na primeira rota -- fix routes param - -## [0.0.1] - 8 Dec 2019 - -- First Release diff --git a/flutter_modular/LICENSE b/flutter_modular/LICENSE deleted file mode 100644 index 22d8ef64..00000000 --- a/flutter_modular/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -The MIT License -License Copyright: Flutterando. -License License: Flutterando. -License Contact: Flutterando. -SPDX short identifier: MIT -Further resources... -  -Begin license text. -Copyright 2021 Flutterando -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -End license text. \ No newline at end of file diff --git a/flutter_modular/README.md b/flutter_modular/README.md deleted file mode 100644 index ab8b2971..00000000 --- a/flutter_modular/README.md +++ /dev/null @@ -1,128 +0,0 @@ -[![codecov](https://codecov.io/gh/Flutterando/modular/branch/master/graph/badge.svg?token=uO4x25wWuU)](https://codecov.io/gh/Flutterando/modular) -![CI](https://github.com/Flutterando/modular/workflows/CI/badge.svg) -![LICENSE](https://img.shields.io/hexpm/l/modular) -[![pub package](https://img.shields.io/pub/v/flutter_modular.svg)](https://pub.dev/packages/flutter_modular) -[![GitHub stars](https://badgen.net/github/stars/Flutterando/modular)](https://GitHub.com/Flutterando/modular/stargazers/) -[![All Contributors](https://img.shields.io/badge/all_contributors-46-orange.svg?style=flat-square)](#contributors-) - -## Flutter Modular - -![flutter_modular](https://raw.githubusercontent.com/Flutterando/modular/master/flutter_modular.png) - -Let's find out how to implement a Modular structure in your project. - -## What is Modular? - -Modular proposes to solve two problems: -- Modularized routes. -- Modularized Dependency Injection. - -In a monolithic architecture, where we have our entire application as a single module, we design our software in a quick and -elegant way, taking advantage of all the amazing features of Flutter💙. However, producing a larger app in a "monolithic" way -can generate technical debt in both maintanance and scalability. With this in mind, developers adopted architectural strategies to better divide the code, minimizing the negative impacts on the project's maintainability and scalability.. - -By better dividing the scope of features, we gain: - -- Improved understanding of features. -- Less breaking changes. -- Add new non-conflicting features. -- Less blind spots in the project's main business rule. -- Improved developer turnover. - -With a more readable code, we extend the life of the project. See example of a standard MVC with 3 features(Auth, Home, Product): - -### A typical MVC - - . - ├── models # All models - │ ├── auth_model.dart - │ ├── home_model.dart - │ └── product_model.dart - ├── controller # All controllers - │ ├── auth_controller.dart - │ ├── home_controller.dart - │ └── product_controller.dart - ├── views # All views - │ ├── auth_page.dart - │ ├── home_page.dart - │ └── product_page.dart - ├── core # Tools and utilities - ├── app_widget.dart # Main Widget containing MaterialApp - └── main.dart # runApp - - -Here we have a default structure using MVC. This is incredibly useful in almost every application. - -Let's see how the structure looks when we divide by scope: - - -### Structure divided by scope - - . - ├── features # All features or Modules - │ ├─ auth # Auth's MVC - │ │ ├── auth_model.dart - │ │ ├── auth_controller.dart - │ │ └── auth_page.dart - │ ├─ home # Home's MVC - │ │ ├── home_model.dart - │ │ ├── home_controller.dart - │ │ └── home_page.dart - │ └─ product # Product's MVC - │ ├── product_model.dart - │ ├── product_controller.dart - │ └── product_page.dart - ├── core # Tools and utilities - ├── app_widget.dart # Main Widget containing MaterialApp - └── main.dart # runApp - - - -What we did in this structure was to continue using MVC, but this time in scope. This means that -each feature has its own MVC, and this simple approach solves many scalability and maintainability issues. -We call this approach "Smart Structure". But two things were still Global and clashed with the structure itself, so we created Modular to solve this impasse. - -In short: Modular is a solution to modularize the route and dependency injection system, making each scope have -its own routes and injections independent of any other factor in the structure. -We create objects to group the Routes and Injections and call them **Modules**. - - - -## Ready to get started? - -Modular is not only ingenious for doing something amazing like componentizing Routes and Dependency Injections, it's amazing -for being able to do all this simply! - -Go to the next topic and start your journey towards an intelligent structure. - -## Common questions - -- Does Modular work with any state management approach? - - Yes, the dependency injection system is agnostic to any kind of class - including the reactivity that makes up state management. - -- Can I use dynamic routes or Wildcards? - - Yes! The entire route tree responds as on the Web. Therefore, you can use dynamic parameters, - query, fragments or simply include a wildcard to enable a redirect - to a 404 page for example. - -- Do I need to create a Module for all features? - - No. You can create a module only when you think it's necessary or when the feature is no longer a part of - the scope in which it is being worked on. - - - - -# Getting started with Modular - -- [flutter_modular Documentation](https://modular.flutterando.com.br/docs/flutter_modular/start) - -- [Modular Site](https://modular.flutterando.com.br) - -## Features and bugs - -Please send feature requests and bugs at the [issue tracker](https://github.com/Flutterando/modular/issues). - -This README was created based on templates made available by Stagehand under a BSD-style [license](https://github.com/dart-lang/stagehand/blob/master/LICENSE). - -This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind are welcome! \ No newline at end of file diff --git a/flutter_modular/analysis_options.yaml b/flutter_modular/analysis_options.yaml deleted file mode 100644 index 1a54db69..00000000 --- a/flutter_modular/analysis_options.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutterando_analysis/flutter_package.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - public_member_api_docs: false - no_logic_in_create_state: false - cascade_invocations: false - require_trailing_commas: false - parameter_assignments: false - avoid_positional_boolean_parameters: false - avoid_equals_and_hash_code_on_mutable_classes: false - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options - diff --git a/flutter_modular/example/.metadata b/flutter_modular/example/.metadata deleted file mode 100644 index 30d90a7e..00000000 --- a/flutter_modular/example/.metadata +++ /dev/null @@ -1,45 +0,0 @@ -# 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. - -version: - revision: 796c8ef79279f9c774545b3771238c3098dbefab - channel: stable - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: 796c8ef79279f9c774545b3771238c3098dbefab - base_revision: 796c8ef79279f9c774545b3771238c3098dbefab - - platform: android - create_revision: 796c8ef79279f9c774545b3771238c3098dbefab - base_revision: 796c8ef79279f9c774545b3771238c3098dbefab - - platform: ios - create_revision: 796c8ef79279f9c774545b3771238c3098dbefab - base_revision: 796c8ef79279f9c774545b3771238c3098dbefab - - platform: linux - create_revision: 796c8ef79279f9c774545b3771238c3098dbefab - base_revision: 796c8ef79279f9c774545b3771238c3098dbefab - - platform: macos - create_revision: 796c8ef79279f9c774545b3771238c3098dbefab - base_revision: 796c8ef79279f9c774545b3771238c3098dbefab - - platform: web - create_revision: 796c8ef79279f9c774545b3771238c3098dbefab - base_revision: 796c8ef79279f9c774545b3771238c3098dbefab - - platform: windows - create_revision: 796c8ef79279f9c774545b3771238c3098dbefab - base_revision: 796c8ef79279f9c774545b3771238c3098dbefab - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/flutter_modular/example/.vscode/launch.json b/flutter_modular/example/.vscode/launch.json deleted file mode 100644 index 48dbbe18..00000000 --- a/flutter_modular/example/.vscode/launch.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - // Use o IntelliSense para saber mais sobre os atributos possíveis. - // Focalizar para exibir as descrições dos atributos existentes. - // Para obter mais informações, acesse: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "example", - "request": "launch", - "type": "dart" - }, - { - "name": "example (profile mode)", - "request": "launch", - "type": "dart", - "flutterMode": "profile" - } - ] -} \ No newline at end of file diff --git a/flutter_modular/example/README.md b/flutter_modular/example/README.md deleted file mode 100644 index 1abb6587..00000000 --- a/flutter_modular/example/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Search - -![search](search.gif) diff --git a/flutter_modular/example/android/app/build.gradle b/flutter_modular/example/android/app/build.gradle deleted file mode 100644 index 5fe3c929..00000000 --- a/flutter_modular/example/android/app/build.gradle +++ /dev/null @@ -1,68 +0,0 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - -android { - compileSdkVersion flutter.compileSdkVersion - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.example" - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/flutter_modular/example/android/build.gradle b/flutter_modular/example/android/build.gradle deleted file mode 100644 index 4256f917..00000000 --- a/flutter_modular/example/android/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -buildscript { - ext.kotlin_version = '1.6.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/flutter_modular/example/android/gradle.properties b/flutter_modular/example/android/gradle.properties deleted file mode 100644 index 94adc3a3..00000000 --- a/flutter_modular/example/android/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M -android.useAndroidX=true -android.enableJetifier=true diff --git a/flutter_modular/example/android/settings.gradle b/flutter_modular/example/android/settings.gradle deleted file mode 100644 index 44e62bcf..00000000 --- a/flutter_modular/example/android/settings.gradle +++ /dev/null @@ -1,11 +0,0 @@ -include ':app' - -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() - -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } - -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/flutter_modular/example/ios/Flutter/Debug.xcconfig b/flutter_modular/example/ios/Flutter/Debug.xcconfig deleted file mode 100644 index ec97fc6f..00000000 --- a/flutter_modular/example/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" diff --git a/flutter_modular/example/ios/Flutter/Release.xcconfig b/flutter_modular/example/ios/Flutter/Release.xcconfig deleted file mode 100644 index c4855bfe..00000000 --- a/flutter_modular/example/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" diff --git a/flutter_modular/example/ios/Podfile b/flutter_modular/example/ios/Podfile deleted file mode 100644 index 1e8c3c90..00000000 --- a/flutter_modular/example/ios/Podfile +++ /dev/null @@ -1,41 +0,0 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '9.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/flutter_modular/example/ios/Podfile.lock b/flutter_modular/example/ios/Podfile.lock deleted file mode 100644 index e5dec136..00000000 --- a/flutter_modular/example/ios/Podfile.lock +++ /dev/null @@ -1,85 +0,0 @@ -PODS: - - better_player (0.0.1): - - Flutter - - KTVHTTPCache (~> 2.0.0) - - CocoaAsyncSocket (7.6.5) - - Flutter (1.0.0) - - flutter_local_notifications (0.0.1): - - Flutter - - KTVCocoaHTTPServer (1.0.0): - - CocoaAsyncSocket - - KTVHTTPCache (2.0.1): - - KTVCocoaHTTPServer - - open_file (0.0.1): - - Flutter - - package_info_plus (0.4.5): - - Flutter - - path_provider (0.0.1): - - Flutter - - "permission_handler (5.1.0+2)": - - Flutter - - sensors (0.0.1): - - Flutter - - share (0.0.1): - - Flutter - - wakelock (0.0.1): - - Flutter - -DEPENDENCIES: - - better_player (from `.symlinks/plugins/better_player/ios`) - - Flutter (from `Flutter`) - - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - - open_file (from `.symlinks/plugins/open_file/ios`) - - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - - path_provider (from `.symlinks/plugins/path_provider/ios`) - - permission_handler (from `.symlinks/plugins/permission_handler/ios`) - - sensors (from `.symlinks/plugins/sensors/ios`) - - share (from `.symlinks/plugins/share/ios`) - - wakelock (from `.symlinks/plugins/wakelock/ios`) - -SPEC REPOS: - trunk: - - CocoaAsyncSocket - - KTVCocoaHTTPServer - - KTVHTTPCache - -EXTERNAL SOURCES: - better_player: - :path: ".symlinks/plugins/better_player/ios" - Flutter: - :path: Flutter - flutter_local_notifications: - :path: ".symlinks/plugins/flutter_local_notifications/ios" - open_file: - :path: ".symlinks/plugins/open_file/ios" - package_info_plus: - :path: ".symlinks/plugins/package_info_plus/ios" - path_provider: - :path: ".symlinks/plugins/path_provider/ios" - permission_handler: - :path: ".symlinks/plugins/permission_handler/ios" - sensors: - :path: ".symlinks/plugins/sensors/ios" - share: - :path: ".symlinks/plugins/share/ios" - wakelock: - :path: ".symlinks/plugins/wakelock/ios" - -SPEC CHECKSUMS: - better_player: a4383402f457e53720525888c0fc5d337ef6ba11 - CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 - Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a - flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 - KTVCocoaHTTPServer: df8d7b861e603ff8037e9b2138aca2563a6b768d - KTVHTTPCache: 588c3eb16f6bd1e6fde1e230dabfb7bd4e490a4d - open_file: 02eb5cb6b21264bd3a696876f5afbfb7ca4f4b7d - package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e - path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c - permission_handler: ccb20a9fad0ee9b1314a52b70b76b473c5f8dab0 - sensors: 84eb7a30e47a649e4172b71d6e81be614c280336 - share: 0b2c3e82132f5888bccca3351c504d0003b3b410 - wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f - -PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c - -COCOAPODS: 1.11.2 diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 28c6bf03..00000000 Binary files a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 2ccbfd96..00000000 Binary files a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index f091b6b0..00000000 Binary files a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cde1211..00000000 Binary files a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index d0ef06e7..00000000 Binary files a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index dcdc2306..00000000 Binary files a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 2ccbfd96..00000000 Binary files a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index c8f9ed8f..00000000 Binary files a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index a6d6b860..00000000 Binary files a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index a6d6b860..00000000 Binary files a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 75b2d164..00000000 Binary files a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index c4df70d3..00000000 Binary files a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 6a84f41e..00000000 Binary files a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index d0e1f585..00000000 Binary files a/flutter_modular/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/flutter_modular/example/lib/app/app_module.dart b/flutter_modular/example/lib/app/app_module.dart deleted file mode 100644 index 21eadba1..00000000 --- a/flutter_modular/example/lib/app/app_module.dart +++ /dev/null @@ -1,35 +0,0 @@ -// ignore_for_file: invalid_use_of_visible_for_testing_member, invalid_use_of_protected_member - -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_modular_example/app/core/config/config.dart'; -import 'package:http/http.dart' as http; - -import 'search/domain/repositories/search_repository.dart'; -import 'search/domain/usecases/search_by_text.dart'; -import 'search/external/github/github_search_datasource.dart'; -import 'search/infra/datasources/search_datasource.dart'; -import 'search/infra/repositories/search_repository_impl.dart'; -import 'search/presenter/pages/details_page.dart'; -import 'search/presenter/pages/guardt.dart'; -import 'search/presenter/pages/search_page.dart'; -import 'search/presenter/stores/search_store.dart'; - -class AppModule extends Module { - AppModule(); - - @override - void binds(i) { - i.addInstance(http.Client()); - i.add(GithubSearchDatasource.new); - i.add(SearchRepositoryImpl.new); - i.add(SearchByTextImpl.new); - i.addSingleton(SearchStore.new, config: storeConfig()); - } - - @override - void routes(r) { - r.child('/', child: (_) => const SearchPage()); - r.child('/details', - child: (_) => DetailsPage(result: r.args.data), guards: [GuardT()]); - } -} diff --git a/flutter_modular/example/lib/app/app_widget.dart b/flutter_modular/example/lib/app/app_widget.dart deleted file mode 100644 index 6c4c360e..00000000 --- a/flutter_modular/example/lib/app/app_widget.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:alice/alice.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; - -class AppWidget extends StatelessWidget { - final alice = Alice(); - - AppWidget({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - Modular.routerDelegate.setNavigatorKey(alice.getNavigatorKey()); - return MaterialApp.router( - title: 'Flutter Slidy', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - routerConfig: Modular.routerConfig, - ); - } -} diff --git a/flutter_modular/example/lib/app/core/config/config.dart b/flutter_modular/example/lib/app/core/config/config.dart deleted file mode 100644 index 75c8fbe4..00000000 --- a/flutter_modular/example/lib/app/core/config/config.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_triple/flutter_triple.dart'; - -BindConfig storeConfig() { - return BindConfig( - onDispose: (value) => value.destroy(), - notifier: (value) => Listenable.merge( - [value.selectError, value.selectLoading, value.selectState]), - ); -} diff --git a/flutter_modular/example/lib/app/search/domain/entities/result.dart b/flutter_modular/example/lib/app/search/domain/entities/result.dart deleted file mode 100644 index 3e0b0bc8..00000000 --- a/flutter_modular/example/lib/app/search/domain/entities/result.dart +++ /dev/null @@ -1,12 +0,0 @@ -class Result { - final String image; - final String name; - final String nickname; - final String url; - - const Result( - {required this.image, - required this.name, - required this.nickname, - required this.url}); -} diff --git a/flutter_modular/example/lib/app/search/domain/errors/erros.dart b/flutter_modular/example/lib/app/search/domain/errors/erros.dart deleted file mode 100644 index b8ac89f3..00000000 --- a/flutter_modular/example/lib/app/search/domain/errors/erros.dart +++ /dev/null @@ -1,9 +0,0 @@ -class Failure implements Exception {} - -class InvalidSearchText extends Failure {} - -class EmptyList extends Failure {} - -class ErrorSearch extends Failure {} - -class DatasourceResultNull extends Failure {} diff --git a/flutter_modular/example/lib/app/search/domain/repositories/search_repository.dart b/flutter_modular/example/lib/app/search/domain/repositories/search_repository.dart deleted file mode 100644 index f5f205b9..00000000 --- a/flutter_modular/example/lib/app/search/domain/repositories/search_repository.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:dartz/dartz.dart'; -import 'package:flutter_modular_example/app/search/domain/entities/result.dart'; -import 'package:flutter_modular_example/app/search/domain/errors/erros.dart'; - -abstract class SearchRepository { - Future>> getUsers(String searchText); -} diff --git a/flutter_modular/example/lib/app/search/domain/usecases/search_by_text.dart b/flutter_modular/example/lib/app/search/domain/usecases/search_by_text.dart deleted file mode 100644 index 5d5482e4..00000000 --- a/flutter_modular/example/lib/app/search/domain/usecases/search_by_text.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:dartz/dartz.dart' hide Bind; -import 'package:flutter_modular_example/app/search/domain/entities/result.dart'; -import 'package:flutter_modular_example/app/search/domain/errors/erros.dart'; -import 'package:flutter_modular_example/app/search/domain/repositories/search_repository.dart'; - -mixin SearchByText { - Future>> call(String textSearch); -} - -class SearchByTextImpl implements SearchByText { - final SearchRepository repository; - - SearchByTextImpl(this.repository); - - @override - Future>> call(String? textSearch) async { - if (textSearch?.isEmpty ?? true) { - return Left(InvalidSearchText()); - } - return await repository.getUsers(textSearch!); - } -} diff --git a/flutter_modular/example/lib/app/search/external/github/github_search_datasource.dart b/flutter_modular/example/lib/app/search/external/github/github_search_datasource.dart deleted file mode 100644 index 2f6e2682..00000000 --- a/flutter_modular/example/lib/app/search/external/github/github_search_datasource.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter_modular_example/app/search/infra/datasources/search_datasource.dart'; -import 'package:flutter_modular_example/app/search/infra/models/result_model.dart'; -import 'package:http/http.dart'; - -class GithubSearchDatasource implements SearchDatasource { - final Client client; - - GithubSearchDatasource(this.client); - - @override - Future?> searchText(String textSearch) async { - var url = Uri.parse('https://api.github.com/search/users?q=$textSearch'); - var result = await client.get(url); - if (result.statusCode == 200) { - final json = jsonDecode(result.body); - debugPrint('execute datasource'); - var jsonList = json['items'] as List; - var list = jsonList - .map((item) => ResultModel( - name: '', - nickname: item['login'], - image: item['avatar_url'], - url: item['url'])) - .toList(); - return list; - } else { - throw Exception(); - } - } -} diff --git a/flutter_modular/example/lib/app/search/infra/datasources/search_datasource.dart b/flutter_modular/example/lib/app/search/infra/datasources/search_datasource.dart deleted file mode 100644 index 74398c21..00000000 --- a/flutter_modular/example/lib/app/search/infra/datasources/search_datasource.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:flutter_modular_example/app/search/infra/models/result_model.dart'; - -abstract class SearchDatasource { - Future?> searchText(String textSearch); -} diff --git a/flutter_modular/example/lib/app/search/infra/models/result_model.dart b/flutter_modular/example/lib/app/search/infra/models/result_model.dart deleted file mode 100644 index 6af40f1f..00000000 --- a/flutter_modular/example/lib/app/search/infra/models/result_model.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter_modular_example/app/search/domain/entities/result.dart'; - -class ResultModel implements Result { - @override - final String image; - @override - final String name; - @override - final String nickname; - @override - final String url; - - const ResultModel( - {required this.image, - required this.name, - required this.nickname, - required this.url}); - - Map toMap() { - return { - 'image': image, - 'name': name, - 'nickname': nickname, - 'url': url, - }; - } - - static ResultModel fromMap(Map map) { - return ResultModel( - image: map['image'], - name: map['name'], - nickname: map['nickname'], - url: map['url'], - ); - } - - String toJson() => json.encode(toMap()); - - static ResultModel fromJson(String source) => fromMap(json.decode(source)); -} diff --git a/flutter_modular/example/lib/app/search/infra/repositories/search_repository_impl.dart b/flutter_modular/example/lib/app/search/infra/repositories/search_repository_impl.dart deleted file mode 100644 index ef5325dc..00000000 --- a/flutter_modular/example/lib/app/search/infra/repositories/search_repository_impl.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:dartz/dartz.dart' hide Bind; -import 'package:flutter_modular_example/app/search/domain/entities/result.dart'; -import 'package:flutter_modular_example/app/search/domain/errors/erros.dart'; -import 'package:flutter_modular_example/app/search/domain/repositories/search_repository.dart'; -import 'package:flutter_modular_example/app/search/infra/datasources/search_datasource.dart'; - -class SearchRepositoryImpl implements SearchRepository { - final SearchDatasource datasource; - - SearchRepositoryImpl(this.datasource); - - @override - Future>> getUsers(String searchText) async { - try { - final list = await datasource.searchText(searchText); - if (list == null) { - return Left>(DatasourceResultNull()); - } - if (list.isEmpty) { - return Left(EmptyList()); - } - return Right>(list); - } catch (e) { - return Left>(ErrorSearch()); - } - } -} diff --git a/flutter_modular/example/lib/app/search/presenter/pages/details_page.dart b/flutter_modular/example/lib/app/search/presenter/pages/details_page.dart deleted file mode 100644 index b51371e7..00000000 --- a/flutter_modular/example/lib/app/search/presenter/pages/details_page.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter_modular_example/app/search/domain/entities/result.dart'; -import 'package:flutter_modular_example/app/search/presenter/stores/search_store.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; - -class DetailsPage extends StatefulWidget { - final Result? result; - const DetailsPage({ - Key? key, - this.result, - }) : super(key: key); - @override - _DetailsPageState createState() => _DetailsPageState(); -} - -class _DetailsPageState extends State { - @override - void dispose() { - Modular.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - debugPrint(Modular.args.queryParams['id'].toString()); - return Scaffold( - appBar: AppBar( - title: Text( - widget.result != null ? widget.result!.nickname : 'Make a search'), - ), - body: widget.result != null - ? Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Hero( - tag: widget.result!.image, - child: CircleAvatar( - backgroundImage: NetworkImage(widget.result!.image), - ), - ), - Text(widget.result!.nickname), - ], - ), - ) - : Center( - child: ElevatedButton( - onPressed: () => Modular.to.pushReplacementNamed('/'), - child: const Text('Make a search'), - ), - ), - ); - } -} diff --git a/flutter_modular/example/lib/app/search/presenter/pages/guardt.dart b/flutter_modular/example/lib/app/search/presenter/pages/guardt.dart deleted file mode 100644 index 59a8fc23..00000000 --- a/flutter_modular/example/lib/app/search/presenter/pages/guardt.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter_modular/flutter_modular.dart'; - -class GuardT extends RouteGuard { - @override - Future canActivate(String path, ModularRoute route) async { - debugPrint(Modular.args.toString()); - return true; - } -} diff --git a/flutter_modular/example/lib/app/search/presenter/pages/search_page.dart b/flutter_modular/example/lib/app/search/presenter/pages/search_page.dart deleted file mode 100644 index 581a10d9..00000000 --- a/flutter_modular/example/lib/app/search/presenter/pages/search_page.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_modular_example/app/search/domain/entities/result.dart'; -import 'package:flutter_modular_example/app/search/domain/errors/erros.dart'; - -import '../stores/search_store.dart'; - -class SearchPage extends StatefulWidget { - const SearchPage({Key? key}) : super(key: key); - - @override - _SearchPageState createState() => _SearchPageState(); -} - -class _SearchPageState extends State { - Widget _buildList(List list) { - if (list.isEmpty) { - return const Center( - child: Text('Please, type something...'), - ); - } - return ListView.builder( - itemCount: list.length, - itemBuilder: (_, index) { - var item = list[index]; - return ListTile( - leading: Hero( - tag: item.image, - child: CircleAvatar( - backgroundImage: NetworkImage(item.image), - ), - ), - title: Text(item.nickname), - onTap: () { - Modular.to.pushNamed('/details?id=1', arguments: item); - }, - ); - }, - ); - } - - Widget _buildError(error) { - if (error is EmptyList) { - return const Center( - child: Text('Nothing has been found'), - ); - } else if (error is ErrorSearch) { - return const Center( - child: Text('Github error'), - ); - } else { - return const Center( - child: Text('Internal error'), - ); - } - } - - @override - Widget build(BuildContext context) { - final store = context.watch(); - - return Scaffold( - appBar: AppBar( - title: const Text('Github Search'), - ), - body: Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 8, right: 8, left: 8), - child: TextField( - onChanged: store.setSearchText, - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Search...', - ), - ), - ), - Expanded( - child: store.when( - onState: _buildList, - onLoading: (loading) => - const Center(child: CircularProgressIndicator()), - onError: _buildError, - ), - ), - ], - ), - ); - } -} diff --git a/flutter_modular/example/lib/app/search/presenter/stores/search_store.dart b/flutter_modular/example/lib/app/search/presenter/stores/search_store.dart deleted file mode 100644 index 98542a96..00000000 --- a/flutter_modular/example/lib/app/search/presenter/stores/search_store.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'dart:async'; - -import 'package:flutter_modular_example/app/search/domain/entities/result.dart'; -import 'package:flutter_modular_example/app/search/domain/usecases/search_by_text.dart'; -import 'package:flutter_triple/flutter_triple.dart'; - -class SearchStore extends Store> { - final SearchByText searchByText; - - SearchStore(this.searchByText) : super([]); - - Timer? timer; - - void setSearchText(String value) { - setLoading(true); - timer?.cancel(); - - timer = Timer(const Duration(milliseconds: 500), () { - searchByText(value).then((value) => value.fold(setError, update)); - }); - } -} diff --git a/flutter_modular/example/lib/main.dart b/flutter_modular/example/lib/main.dart deleted file mode 100644 index 3cf091df..00000000 --- a/flutter_modular/example/lib/main.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; - -import 'app/app_module.dart'; -import 'app/app_widget.dart'; - -void main() { - runApp(ModularApp(module: AppModule(), child: AppWidget())); -} diff --git a/flutter_modular/example/linux/flutter/generated_plugin_registrant.cc b/flutter_modular/example/linux/flutter/generated_plugin_registrant.cc deleted file mode 100644 index f6f23bfe..00000000 --- a/flutter_modular/example/linux/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - -#include - -void fl_register_plugins(FlPluginRegistry* registry) { - g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); - url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); -} diff --git a/flutter_modular/example/macos/Flutter/Flutter-Debug.xcconfig b/flutter_modular/example/macos/Flutter/Flutter-Debug.xcconfig deleted file mode 100644 index 4b81f9b2..00000000 --- a/flutter_modular/example/macos/Flutter/Flutter-Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/flutter_modular/example/macos/Flutter/Flutter-Release.xcconfig b/flutter_modular/example/macos/Flutter/Flutter-Release.xcconfig deleted file mode 100644 index 5caa9d15..00000000 --- a/flutter_modular/example/macos/Flutter/Flutter-Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/flutter_modular/example/macos/Flutter/GeneratedPluginRegistrant.swift b/flutter_modular/example/macos/Flutter/GeneratedPluginRegistrant.swift deleted file mode 100644 index 8c4949ca..00000000 --- a/flutter_modular/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Generated file. Do not edit. -// - -import FlutterMacOS -import Foundation - -import flutter_local_notifications -import package_info_plus -import path_provider_foundation -import share_plus -import url_launcher_macos - -func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) - FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) - PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) - SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) - UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) -} diff --git a/flutter_modular/example/macos/Podfile b/flutter_modular/example/macos/Podfile deleted file mode 100644 index c795730d..00000000 --- a/flutter_modular/example/macos/Podfile +++ /dev/null @@ -1,43 +0,0 @@ -platform :osx, '10.14' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_macos_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do - inherit! :search_paths - end -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_macos_build_settings(target) - end -end diff --git a/flutter_modular/example/macos/Podfile.lock b/flutter_modular/example/macos/Podfile.lock deleted file mode 100644 index 652d58d3..00000000 --- a/flutter_modular/example/macos/Podfile.lock +++ /dev/null @@ -1,47 +0,0 @@ -PODS: - - flutter_local_notifications (0.0.1): - - FlutterMacOS - - FlutterMacOS (1.0.0) - - package_info_plus (0.0.1): - - FlutterMacOS - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - - share_plus (0.0.1): - - FlutterMacOS - - url_launcher_macos (0.0.1): - - FlutterMacOS - -DEPENDENCIES: - - flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`) - - FlutterMacOS (from `Flutter/ephemeral`) - - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - -EXTERNAL SOURCES: - flutter_local_notifications: - :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos - FlutterMacOS: - :path: Flutter/ephemeral - package_info_plus: - :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos - path_provider_foundation: - :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin - share_plus: - :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos - url_launcher_macos: - :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos - -SPEC CHECKSUMS: - flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4 - FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce - path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852 - share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 - url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 - -PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 - -COCOAPODS: 1.11.3 diff --git a/flutter_modular/example/pubspec.yaml b/flutter_modular/example/pubspec.yaml deleted file mode 100644 index d4e9dfe9..00000000 --- a/flutter_modular/example/pubspec.yaml +++ /dev/null @@ -1,90 +0,0 @@ -name: flutter_modular_example -description: A new Flutter project. - -# The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. -publish_to: "none" # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.0+1 - -resolution: workspace - -environment: - sdk: ">=3.6.0 <4.0.0" - -dependencies: - dartz: ^0.10.0-nullsafety.2 - flutter_mobx: ^2.0.1 - flutter_triple: ^2.1.4 - http: ^1.1.0 - flutter_modular: - alice: ^0.4.2 - bloc: ^8.0.3 - flutter: - sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - -dev_dependencies: - flutter_lints: ^2.0.2 - flutter_test: - sdk: flutter - mocktail: ^1.0.4 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true -vars: - clean: flutter clean - get: flutter pub get - runner: flutter pub run build_runner -scripts: - mobx_build: $clean & $get & $runner build --delete-conflicting-outputs - mobx_watch: $clean & $get & $runner watch --delete-conflicting-outputs -# dependency_overrides: -# flutter_modular: -# path: ../../../modular/flutter_modular -# To add assets to your application, add an assets section, like this: -# assets: -# - images/a_dot_burr.jpeg -# - images/a_dot_ham.jpeg -# An image asset can refer to one or more resolution-specific "variants", see -# https://flutter.dev/assets-and-images/#resolution-aware. -# For details regarding adding assets from package dependencies, see -# https://flutter.dev/assets-and-images/#from-packages -# To add custom fonts to your application, add a fonts section here, -# in this "flutter" section. Each entry in this list should have a -# "family" key with the font family name, and a "fonts" key with a -# list giving the asset and other descriptors for the font. For -# example: -# fonts: -# - family: Schyler -# fonts: -# - asset: fonts/Schyler-Regular.ttf -# - asset: fonts/Schyler-Italic.ttf -# style: italic -# - family: Trajan Pro -# fonts: -# - asset: fonts/TrajanPro.ttf -# - asset: fonts/TrajanPro_Bold.ttf -# weight: 700 -# -# For details regarding fonts from package dependencies, -# see https://flutter.dev/custom-fonts/#from-packages diff --git a/flutter_modular/example/search.gif b/flutter_modular/example/search.gif deleted file mode 100644 index db828235..00000000 Binary files a/flutter_modular/example/search.gif and /dev/null differ diff --git a/flutter_modular/example/web/index.html b/flutter_modular/example/web/index.html deleted file mode 100644 index b6b9dd23..00000000 --- a/flutter_modular/example/web/index.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - example - - - - - - - diff --git a/flutter_modular/example/windows/flutter/generated_plugin_registrant.cc b/flutter_modular/example/windows/flutter/generated_plugin_registrant.cc deleted file mode 100644 index d5013ba1..00000000 --- a/flutter_modular/example/windows/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,20 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - -#include -#include -#include - -void RegisterPlugins(flutter::PluginRegistry* registry) { - PermissionHandlerWindowsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); - SharePlusWindowsPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); - UrlLauncherWindowsRegisterWithRegistrar( - registry->GetRegistrarForPlugin("UrlLauncherWindows")); -} diff --git a/flutter_modular/lib/flutter_modular.dart b/flutter_modular/lib/flutter_modular.dart deleted file mode 100644 index 89947a29..00000000 --- a/flutter_modular/lib/flutter_modular.dart +++ /dev/null @@ -1,148 +0,0 @@ -library flutter_modular; - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_modular/src/flutter_modular_module.dart'; -import 'package:modular_core/modular_core.dart'; - -import 'src/presenter/modular_base.dart'; -import 'src/presenter/navigation/modular_page.dart'; -import 'src/presenter/navigation/modular_router_delegate.dart'; -import 'src/presenter/navigation/router_outlet_delegate.dart'; - -export 'package:modular_core/modular_core.dart' - show - ModularRoute, - RouteManager, - Disposable, - Module, - BindConfig, - Injector, - AutoInjectorException, - ModularArguments, - setPrintResolver; - -export 'src/presenter/extensions/route_manager_ext.dart'; -export 'src/presenter/guards/route_guard.dart'; -export 'src/presenter/models/child_route.dart'; -export 'src/presenter/models/modular_args.dart'; -export 'src/presenter/models/modular_navigator.dart'; -export 'src/presenter/models/module_route.dart'; -export 'src/presenter/models/redirect_to_route.dart'; -export 'src/presenter/models/route.dart'; -export 'src/presenter/models/wildcard_route.dart'; -export 'src/presenter/navigation/transitions/page_transition.dart'; -export 'src/presenter/navigation/transitions/transitions.dart'; -export 'src/presenter/widgets/modular_app.dart'; -export 'src/presenter/widgets/navigation_listener.dart'; - -IModularBase? _modular; - -/// Instance of Modular for search binds and route. -// ignore: non_constant_identifier_names -IModularBase get Modular { - _modular ??= injector.get(); - return _modular!; -} - -/// clean Modular -void cleanModular() { - _modular?.destroy(); - _modular = null; -} - -/// clean all -void cleanGlobals() { - cleanModular(); -} - -/// Extension to add args in AutoInjector class -extension InjectorExtends on Injector { - /// get arguments - ModularArguments get args => injector.get().arguments; -} - -/// It acts as a Nested Browser that will be populated -/// by the children of this route. -class RouterOutlet extends StatefulWidget { - /// An interface for observing the behavior of a [Navigator]. - final List? observers; - - /// It acts as a Nested Browser that will be populated - /// by the children of this route. - const RouterOutlet({Key? key, this.observers}) : super(key: key); - - @override - RouterOutletState createState() => RouterOutletState(); -} - -/// visible for test -@visibleForTesting -class RouterOutletState extends State { - late GlobalKey _navigatorKey; - RouterOutletDelegate? _delegate; - ChildBackButtonDispatcher? _backButtonDispatcher; - - /// Get all current observers - List get currentObservers => - widget.observers ?? []; - - @override - void initState() { - super.initState(); - _navigatorKey = GlobalKey(); - - Modular.to.addListener(listener); - } - - /// visible for test - @visibleForTesting - void listener() { - setState(() {}); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final modal = ModalRoute.of(context)?.settings as ModularPage?; - if (modal == null) { - return; - } - _delegate ??= RouterOutletDelegate( - modal.route.uri.toString(), - injector.get(), - _navigatorKey, - currentObservers, - ); - - /// Prevent RouterOutlent to take back button priority - /// when the new named route, is not a children - if (_newRouteIsNotChildren(modal.route)) { - _backButtonDispatcher = null; - return; - } - final router = Router.of(context); - _backButtonDispatcher = router.backButtonDispatcher // - ?.createChildBackButtonDispatcher(); - } - - bool _newRouteIsNotChildren(ParallelRoute route) { - return !route.children.any((e) => Modular.to.path.contains(e.name)); - } - - @override - void dispose() { - super.dispose(); - Modular.to.removeListener(listener); - } - - @override - Widget build(BuildContext context) { - _backButtonDispatcher?.takePriority(); - return Router( - routerDelegate: _delegate!, - backButtonDispatcher: _backButtonDispatcher, - ); - } -} diff --git a/flutter_modular/lib/src/domain/dtos/route_dto.dart b/flutter_modular/lib/src/domain/dtos/route_dto.dart deleted file mode 100644 index a87fca7f..00000000 --- a/flutter_modular/lib/src/domain/dtos/route_dto.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/cupertino.dart'; - -@immutable -class RouteParmsDTO { - final String url; - final dynamic arguments; - final String schema; - - const RouteParmsDTO({required this.url, this.arguments, this.schema = ''}); - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is RouteParmsDTO && - other.url == url && - other.arguments == arguments && - other.schema == schema; - } - - @override - int get hashCode => url.hashCode ^ arguments.hashCode ^ schema.hashCode; -} diff --git a/flutter_modular/lib/src/domain/errors/errors.dart b/flutter_modular/lib/src/domain/errors/errors.dart deleted file mode 100644 index 8f18a995..00000000 --- a/flutter_modular/lib/src/domain/errors/errors.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:modular_core/modular_core.dart'; - -class BindNotFoundException extends ModularError { - const BindNotFoundException(String message, [StackTrace? stackTrace]) - : super(message, stackTrace); -} - -class RouteNotFoundException extends ModularError { - const RouteNotFoundException(String message, [StackTrace? stackTrace]) - : super(message, stackTrace); -} diff --git a/flutter_modular/lib/src/domain/services/bind_service.dart b/flutter_modular/lib/src/domain/services/bind_service.dart deleted file mode 100644 index ecc8bf40..00000000 --- a/flutter_modular/lib/src/domain/services/bind_service.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -abstract class BindService { - ResultDart getBind([String? key]); - ResultDart disposeBind([String? key]); - ResultDart replaceInstance(T instance, [String? key]); -} diff --git a/flutter_modular/lib/src/domain/services/module_service.dart b/flutter_modular/lib/src/domain/services/module_service.dart deleted file mode 100644 index 44cbe69f..00000000 --- a/flutter_modular/lib/src/domain/services/module_service.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -abstract class ModuleService { - ResultDart start(Module module); - ResultDart bind(Module module, [String? tag]); - ResultDart unbind({String? type}); - ResultDart finish(); -} diff --git a/flutter_modular/lib/src/domain/services/route_service.dart b/flutter_modular/lib/src/domain/services/route_service.dart deleted file mode 100644 index 3c555f87..00000000 --- a/flutter_modular/lib/src/domain/services/route_service.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../dtos/route_dto.dart'; - -abstract class RouteService { - AsyncResultDart getRoute(RouteParmsDTO params); - ResultDart getArguments(); - ResultDart setArguments(ModularArguments args); - ResultDart reportPop(ModularRoute route); - ResultDart reportPush(ModularRoute route); -} diff --git a/flutter_modular/lib/src/domain/usecases/bind_module.dart b/flutter_modular/lib/src/domain/usecases/bind_module.dart deleted file mode 100644 index a90e7ef0..00000000 --- a/flutter_modular/lib/src/domain/usecases/bind_module.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../services/module_service.dart'; - -abstract class BindModule { - ResultDart call(Module module, [String? tag]); -} - -class BindModuleImpl implements BindModule { - final ModuleService moduleService; - - BindModuleImpl(this.moduleService); - - @override - ResultDart call(Module module, [String? tag]) { - return moduleService.bind(module, tag); - } -} diff --git a/flutter_modular/lib/src/domain/usecases/dispose_bind.dart b/flutter_modular/lib/src/domain/usecases/dispose_bind.dart deleted file mode 100644 index 4a3dcdc4..00000000 --- a/flutter_modular/lib/src/domain/usecases/dispose_bind.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../services/bind_service.dart'; - -abstract class DisposeBind { - ResultDart call([String? key]); -} - -class DisposeBindImpl implements DisposeBind { - final BindService bindService; - - DisposeBindImpl(this.bindService); - - @override - ResultDart call([String? key]) { - return bindService.disposeBind(key); - } -} diff --git a/flutter_modular/lib/src/domain/usecases/finish_module.dart b/flutter_modular/lib/src/domain/usecases/finish_module.dart deleted file mode 100644 index 21808632..00000000 --- a/flutter_modular/lib/src/domain/usecases/finish_module.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../services/module_service.dart'; - -abstract class FinishModule { - ResultDart call(); -} - -class FinishModuleImpl implements FinishModule { - final ModuleService moduleService; - - FinishModuleImpl(this.moduleService); - - @override - ResultDart call() { - return moduleService.finish(); - } -} diff --git a/flutter_modular/lib/src/domain/usecases/get_arguments.dart b/flutter_modular/lib/src/domain/usecases/get_arguments.dart deleted file mode 100644 index e104789d..00000000 --- a/flutter_modular/lib/src/domain/usecases/get_arguments.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../services/route_service.dart'; - -abstract class GetArguments { - ResultDart call(); -} - -class GetArgumentsImpl implements GetArguments { - final RouteService service; - - GetArgumentsImpl(this.service); - - @override - ResultDart call() { - return service.getArguments(); - } -} diff --git a/flutter_modular/lib/src/domain/usecases/get_bind.dart b/flutter_modular/lib/src/domain/usecases/get_bind.dart deleted file mode 100644 index f8bd0041..00000000 --- a/flutter_modular/lib/src/domain/usecases/get_bind.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../services/bind_service.dart'; - -abstract class GetBind { - ResultDart call([String? key]); -} - -class GetBindImpl implements GetBind { - final BindService bindService; - - GetBindImpl(this.bindService); - - @override - ResultDart call([String? key]) { - return bindService.getBind(key); - } -} diff --git a/flutter_modular/lib/src/domain/usecases/get_route.dart b/flutter_modular/lib/src/domain/usecases/get_route.dart deleted file mode 100644 index d9826e73..00000000 --- a/flutter_modular/lib/src/domain/usecases/get_route.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../dtos/route_dto.dart'; -import '../services/route_service.dart'; - -abstract class GetRoute { - AsyncResultDart call(RouteParmsDTO params); -} - -class GetRouteImpl implements GetRoute { - final RouteService service; - - GetRouteImpl(this.service); - - @override - AsyncResultDart call(RouteParmsDTO params) { - return service.getRoute(params); - } -} diff --git a/flutter_modular/lib/src/domain/usecases/replace_instance.dart b/flutter_modular/lib/src/domain/usecases/replace_instance.dart deleted file mode 100644 index 2de3b6b3..00000000 --- a/flutter_modular/lib/src/domain/usecases/replace_instance.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../services/bind_service.dart'; - -abstract class ReplaceInstance { - ResultDart call(T instance, [String? key]); -} - -class ReplaceInstanceImpl implements ReplaceInstance { - final BindService bindService; - - ReplaceInstanceImpl(this.bindService); - - @override - ResultDart call(T instance, [String? key]) { - return bindService.replaceInstance(instance, key); - } -} diff --git a/flutter_modular/lib/src/domain/usecases/report_pop.dart b/flutter_modular/lib/src/domain/usecases/report_pop.dart deleted file mode 100644 index 5745ade5..00000000 --- a/flutter_modular/lib/src/domain/usecases/report_pop.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../services/route_service.dart'; - -abstract class ReportPop { - ResultDart call(ModularRoute route); -} - -class ReportPopImpl implements ReportPop { - final RouteService service; - - ReportPopImpl(this.service); - - @override - ResultDart call(ModularRoute route) { - return service.reportPop(route); - } -} diff --git a/flutter_modular/lib/src/domain/usecases/report_push.dart b/flutter_modular/lib/src/domain/usecases/report_push.dart deleted file mode 100644 index 22ac2fe1..00000000 --- a/flutter_modular/lib/src/domain/usecases/report_push.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../services/route_service.dart'; - -abstract class ReportPush { - ResultDart call(ModularRoute route); -} - -class ReportPushImpl implements ReportPush { - final RouteService service; - - ReportPushImpl(this.service); - - @override - ResultDart call(ModularRoute route) { - return service.reportPush(route); - } -} diff --git a/flutter_modular/lib/src/domain/usecases/set_arguments.dart b/flutter_modular/lib/src/domain/usecases/set_arguments.dart deleted file mode 100644 index 26025863..00000000 --- a/flutter_modular/lib/src/domain/usecases/set_arguments.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../services/route_service.dart'; - -abstract class SetArguments { - ResultDart call(ModularArguments args); -} - -class SetArgumentsImpl implements SetArguments { - final RouteService service; - - SetArgumentsImpl(this.service); - - @override - ResultDart call(ModularArguments args) { - return service.setArguments(args); - } -} diff --git a/flutter_modular/lib/src/domain/usecases/start_module.dart b/flutter_modular/lib/src/domain/usecases/start_module.dart deleted file mode 100644 index 24545cc8..00000000 --- a/flutter_modular/lib/src/domain/usecases/start_module.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../services/module_service.dart'; - -abstract class StartModule { - ResultDart call(Module context); -} - -class StartModuleImpl implements StartModule { - final ModuleService moduleService; - - StartModuleImpl(this.moduleService); - - @override - ResultDart call(Module context) { - return moduleService.start(context); - } -} diff --git a/flutter_modular/lib/src/domain/usecases/unbind_module.dart b/flutter_modular/lib/src/domain/usecases/unbind_module.dart deleted file mode 100644 index 7c9f3ef8..00000000 --- a/flutter_modular/lib/src/domain/usecases/unbind_module.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../services/module_service.dart'; - -abstract class UnbindModule { - ResultDart call({String? type}); -} - -class UnbindModuleImpl implements UnbindModule { - final ModuleService moduleService; - - UnbindModuleImpl(this.moduleService); - - @override - ResultDart call({String? type}) { - return moduleService.unbind(type: type); - } -} diff --git a/flutter_modular/lib/src/flutter_modular_module.dart b/flutter_modular/lib/src/flutter_modular_module.dart deleted file mode 100644 index d6e65992..00000000 --- a/flutter_modular/lib/src/flutter_modular_module.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_modular/src/domain/usecases/replace_instance.dart'; -import 'package:modular_core/modular_core.dart'; - -import '../flutter_modular.dart'; -import 'domain/services/bind_service.dart'; -import 'domain/services/module_service.dart'; -import 'domain/services/route_service.dart'; -import 'domain/usecases/bind_module.dart'; -import 'domain/usecases/dispose_bind.dart'; -import 'domain/usecases/finish_module.dart'; -import 'domain/usecases/get_arguments.dart'; -import 'domain/usecases/get_bind.dart'; -import 'domain/usecases/get_route.dart'; -import 'domain/usecases/report_pop.dart'; -import 'domain/usecases/report_push.dart'; -import 'domain/usecases/set_arguments.dart'; -import 'domain/usecases/start_module.dart'; -import 'domain/usecases/unbind_module.dart'; -import 'infra/services/bind_service_impl.dart'; -import 'infra/services/module_service_impl.dart'; -import 'infra/services/route_service_impl.dart'; -import 'infra/services/url_service/url_service.dart'; -import 'presenter/modular_base.dart'; -import 'presenter/navigation/modular_route_information_parser.dart'; -import 'presenter/navigation/modular_router_delegate.dart'; - -final _innerInjector = AutoInjector( - tag: 'ModularApp', - on: (i) { - i.addInstance(i); - i.commit(); - }, -); - -final injector = AutoInjector( - tag: 'ModularCore', - on: (i) { - //datasource - i.addInstance(_innerInjector); - i.addSingleton(Tracker.new); - //infra - i.add(BindServiceImpl.new); - i.add(ModuleServiceImpl.new); - i.add(RouteServiceImpl.new); - i.add(UrlService.create); - //domain - i.add(DisposeBindImpl.new); - i.add(FinishModuleImpl.new); - i.add(GetBindImpl.new); - i.add(GetRouteImpl.new); - i.add(StartModuleImpl.new); - i.add(GetArgumentsImpl.new); - i.add(BindModuleImpl.new); - i.add(ReportPopImpl.new); - i.add(SetArgumentsImpl.new); - i.add(UnbindModuleImpl.new); - i.add(ReportPushImpl.new); - i.add(ReplaceInstanceImpl.new); - //presenter - i.addInstance(GlobalKey()); - i.addSingleton( - ModularRouteInformationParser.new); - i.addSingleton(ModularRouterDelegate.new); - i.add(() => i()); - i.addLazySingleton(ModularBase.new); - - i.commit(); - }, -); diff --git a/flutter_modular/lib/src/infra/services/bind_service_impl.dart b/flutter_modular/lib/src/infra/services/bind_service_impl.dart deleted file mode 100644 index 56a4ab18..00000000 --- a/flutter_modular/lib/src/infra/services/bind_service_impl.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../domain/errors/errors.dart'; -import '../../domain/services/bind_service.dart'; - -class BindServiceImpl extends BindService { - final AutoInjector injector; - - BindServiceImpl(this.injector); - - @override - ResultDart disposeBind([String? key]) { - final result = injector.disposeSingleton(key: key); - return Success(result != null); - } - - @override - ResultDart getBind([String? key]) { - try { - final result = injector.get(key: key); - return Success(result); - } on AutoInjectorException catch (e, s) { - return Failure(BindNotFoundException(e.toString(), s)); - } - } - - @override - ResultDart replaceInstance(T instance, [String? key]) { - injector.replaceInstance(instance, key: key); - return Success.unit(); - } -} diff --git a/flutter_modular/lib/src/infra/services/module_service_impl.dart b/flutter_modular/lib/src/infra/services/module_service_impl.dart deleted file mode 100644 index 06b45ca5..00000000 --- a/flutter_modular/lib/src/infra/services/module_service_impl.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../domain/services/module_service.dart'; - -class ModuleServiceImpl extends ModuleService { - final Tracker tracker; - - ModuleServiceImpl(this.tracker); - - @override - ResultDart finish() { - tracker.finishApp(); - return const Success(unit); - } - - @override - ResultDart start(Module module) { - tracker.runApp(module); - return const Success(unit); - } - - @override - ResultDart bind(Module module, [String? tag]) { - tracker.bindModule(module, tag); - return const Success(unit); - } - - @override - ResultDart unbind({String? type}) { - tracker.unbindModule(type ?? T.toString()); - return const Success(unit); - } -} diff --git a/flutter_modular/lib/src/infra/services/route_service_impl.dart b/flutter_modular/lib/src/infra/services/route_service_impl.dart deleted file mode 100644 index b679cd8d..00000000 --- a/flutter_modular/lib/src/infra/services/route_service_impl.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../domain/dtos/route_dto.dart'; -import '../../domain/errors/errors.dart'; -import '../../domain/services/route_service.dart'; - -class RouteServiceImpl implements RouteService { - final Tracker tracker; - - RouteServiceImpl(this.tracker); - - @override - AsyncResultDart getRoute( - RouteParmsDTO params) async { - final route = await tracker.findRoute( - params.url, - data: params.arguments, - schema: params.schema, - ); - if (route != null) { - return Success(route); - } else { - return Failure(RouteNotFoundException('Route (${params.url}) not found')); - } - } - - @override - ResultDart getArguments() { - return Success(tracker.arguments); - } - - @override - ResultDart reportPop(ModularRoute route) { - tracker.reportPopRoute(route); - return const Success(unit); - } - - @override - ResultDart setArguments(ModularArguments args) { - tracker.setArguments(args); - return const Success(unit); - } - - @override - ResultDart reportPush(ModularRoute route) { - tracker.reportPushRoute(route); - return const Success(unit); - } -} diff --git a/flutter_modular/lib/src/infra/services/url_service/html_url_service.dart b/flutter_modular/lib/src/infra/services/url_service/html_url_service.dart deleted file mode 100644 index 6a5b3d2c..00000000 --- a/flutter_modular/lib/src/infra/services/url_service/html_url_service.dart +++ /dev/null @@ -1,30 +0,0 @@ -// ignore_for_file: avoid_web_libraries_in_flutter - -import 'dart:html'; - -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_web_plugins/url_strategy.dart'; - -import 'url_service.dart'; - -class WebUrlService extends UrlService { - @override - String? getPath() { - final href = window.location.href; - - if (urlStrategy is HashUrlStrategy) { - if (href.contains('#')) { - return href.split('#').last; - } else if (href.endsWith(Modular.initialRoute)) { - return Modular.initialRoute; - - } - } - - return resolvePath(href); - } -} - -UrlService create() { - return WebUrlService(); -} diff --git a/flutter_modular/lib/src/infra/services/url_service/io_url_service.dart b/flutter_modular/lib/src/infra/services/url_service/io_url_service.dart deleted file mode 100644 index 3d876c69..00000000 --- a/flutter_modular/lib/src/infra/services/url_service/io_url_service.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'url_service.dart'; - -class IOUrlService extends UrlService { - @override - String? getPath() => null; -} - -UrlService create() { - return IOUrlService(); -} diff --git a/flutter_modular/lib/src/infra/services/url_service/url_service.dart b/flutter_modular/lib/src/infra/services/url_service/url_service.dart deleted file mode 100644 index 6c97a234..00000000 --- a/flutter_modular/lib/src/infra/services/url_service/url_service.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'io_url_service.dart' -// for web - if (dart.library.html) 'html_url_service.dart' as impl; - -abstract class UrlService { - String? getPath(); - - static UrlService create() { - return impl.create(); - } - - String resolvePath(String path) { - final uri = Uri.parse(path); - if (uri.hasFragment) { - return uri.fragment; - } - return uri.path; - } -} diff --git a/flutter_modular/lib/src/presenter/errors/errors.dart b/flutter_modular/lib/src/presenter/errors/errors.dart deleted file mode 100644 index 6f648926..00000000 --- a/flutter_modular/lib/src/presenter/errors/errors.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:modular_core/modular_core.dart'; - -class ModuleStartedException extends ModularError { - const ModuleStartedException(String message, [StackTrace? stackTrace]) - : super(message, stackTrace); -} - -class GuardedRouteException extends ModularError { - GuardedRouteException(String path) : super(path); -} - -class ModularPageException extends ModularError { - ModularPageException(String message, [StackTrace? stackTrace]) - : super(message, stackTrace); -} diff --git a/flutter_modular/lib/src/presenter/extensions/route_manager_ext.dart b/flutter_modular/lib/src/presenter/extensions/route_manager_ext.dart deleted file mode 100644 index ca71c552..00000000 --- a/flutter_modular/lib/src/presenter/extensions/route_manager_ext.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:flutter_modular/flutter_modular.dart'; - -extension RouteManageExt on RouteManager { - void child( - String name, { - required ModularChild child, - CustomTransition? customTransition, - List children = const [], - Duration? duration, - TransitionType? transition, - bool maintainState = true, - List guards = const [], - }) { - add(ChildRoute( - name, - child: child, - children: children, - customTransition: customTransition, - duration: duration, - transition: transition, - maintainState: maintainState, - guards: guards, - )); - } - - void redirect( - String name, { - required String to, - }) { - add(RedirectRoute(name, to: to)); - } - - void wildcard({ - required ModularChild child, - TransitionType transition = TransitionType.defaultTransition, - CustomTransition? customTransition, - Duration duration = const Duration(milliseconds: 300), - }) { - add(WildcardRoute( - child: child, - transition: transition, - customTransition: customTransition, - duration: duration, - )); - } - - void module( - String name, { - required Module module, - TransitionType? transition, - CustomTransition? customTransition, - Duration? duration, - List guards = const [], - }) { - add(ModuleRoute( - name, - module: module, - customTransition: customTransition, - duration: duration, - transition: transition, - guards: guards, - )); - } - - ModularArguments get args => Modular.args; -} diff --git a/flutter_modular/lib/src/presenter/guards/route_guard.dart b/flutter_modular/lib/src/presenter/guards/route_guard.dart deleted file mode 100644 index b9c179aa..00000000 --- a/flutter_modular/lib/src/presenter/guards/route_guard.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'dart:async'; - -import 'package:flutter_modular/src/presenter/models/route.dart'; -import 'package:modular_core/modular_core.dart'; - -import '../errors/errors.dart'; -import '../models/redirect_to_route.dart'; - -/// RouteGuard implements Middleware and adds guard behavior, -/// authorizing or not the route via the canActivate() method; -abstract class RouteGuard extends Middleware { - /// Returns a FutureOr. - /// If true, allow the route to continue processing. - /// If it is false, the Guard will try to redirect the route. - /// If there is no redirect then an error will - /// be thrown [GuardedRouteException]. - FutureOr canActivate(String path, ParallelRoute route); - - /// If the route is not allowed then the Guard will redirect to that route. - final String? redirectTo; - - RouteGuard({this.redirectTo}); - - @override - FutureOr pre(ModularRoute route) => route; - - @override - FutureOr pos( - ModularRoute route, - ModularArguments data, - ) async { - if (await canActivate(data.uri.toString(), route as ParallelRoute)) { - return route; - } else if (redirectTo != null) { - return RedirectRoute(route.name, to: redirectTo!); - } - - throw GuardedRouteException(route.uri.toString().trim()); - } -} diff --git a/flutter_modular/lib/src/presenter/models/child_route.dart b/flutter_modular/lib/src/presenter/models/child_route.dart deleted file mode 100644 index ac4a3556..00000000 --- a/flutter_modular/lib/src/presenter/models/child_route.dart +++ /dev/null @@ -1,34 +0,0 @@ -import '../guards/route_guard.dart'; -import 'route.dart'; - -/// Represents a route in the modular route tree. -/// You will be asked for a route name which should always start with '/' -/// and a widget which will be re-rendered when the route is requested. -class ChildRoute extends ParallelRoute { - ChildRoute( - String name, { - required ModularChild child, - CustomTransition? customTransition, - List children = const [], - Duration? duration, - TransitionType? transition, - bool isFullscreenDialog = false, - bool maintainState = true, - List guards = const [], - }) : assert(name.startsWith('/'), 'The name must always start with a /'), - assert( - children.where((e) => e.name == name).isEmpty, - 'Don\'t use name "/" in route\'s children when parent be "/" too', - ), - super( - name: name, - child: child, - maintainState: maintainState, - customTransition: customTransition, - children: children, - duration: duration, - transition: transition, - isFullscreenDialog: isFullscreenDialog, - middlewares: guards, - ); -} diff --git a/flutter_modular/lib/src/presenter/models/modular_args.dart b/flutter_modular/lib/src/presenter/models/modular_args.dart deleted file mode 100644 index 81601ce9..00000000 --- a/flutter_modular/lib/src/presenter/models/modular_args.dart +++ /dev/null @@ -1,27 +0,0 @@ -/// Flags can change Modular behavior. -/// [isDebug] = Enables text printing for debugging. -/// [isCupertino] = Works with Cupertino-style routes. -/// [experimentalNotAllowedParentBinds] = Prohibits taking -/// any bind of parent modules, -/// forcing the imports of the same in the current module to be accessed. -/// This is the same behavior as the system. -class ModularFlags { - /// Prohibits taking any bind of parent modules, - /// forcing the imports of the same in the current module to be accessed. - /// This is the same behavior as the system. - /// Default is false; - bool experimentalNotAllowedParentBinds; - - /// Works with Cupertino-style routes. - /// Default is false; - bool isCupertino; - - /// Enables text printing for debugging. - /// Default is true; - bool isDebug; - ModularFlags({ - this.experimentalNotAllowedParentBinds = false, - this.isCupertino = false, - this.isDebug = true, - }); -} diff --git a/flutter_modular/lib/src/presenter/models/modular_navigator.dart b/flutter_modular/lib/src/presenter/models/modular_navigator.dart deleted file mode 100644 index 6eed2093..00000000 --- a/flutter_modular/lib/src/presenter/models/modular_navigator.dart +++ /dev/null @@ -1,128 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'route.dart'; - -abstract class IModularNavigator implements Listenable { - /// Actual path - String get path; - List get navigateHistory; - - /// Navigate to a new screen. - /// - /// ``` - /// Modular.to.push(MaterialPageRoute(builder: (context) => HomePage()),); - /// ``` - Future push(Route route); - - /// Pop the current route off the navigator and navigate to a route. - /// - /// ``` - /// Modular.to.popAndPushNamed('/home'); - /// ``` - /// You could give parameters - /// ``` - /// Modular.to.popAndPushNamed('/home', arguments: 10); - /// ``` - Future popAndPushNamed( - String routeName, { - TO? result, - Object? arguments, - bool forRoot = false, - }); - - /// Navigate to a route. - /// - /// ``` - /// Modular.to.pushNamed('/home/10'); - /// ``` - /// You could give parameters - /// ``` - /// Modular.to.pushNamed('/home', arguments: 10); - /// ``` - Future pushNamed( - String routeName, { - Object? arguments, - bool forRoot = false, - }); - - /// Push the route with the given name onto the navigator that most tightly - /// encloses the given context, and then remove all the previous routes until - /// the predicate returns true. - /// - /// ``` - /// Modular.to.pushNamedAndRemoveUntil('/home/10', ModalRoute.withName('/')); - /// ``` - /// You could give parameters - /// ``` - /// Modular.to.pushNamedAndRemoveUntil('/home', ModalRoute.withName('/'), arguments: 10); - /// ``` - Future pushNamedAndRemoveUntil( - String newRouteName, - bool Function(Route) predicate, { - Object? arguments, - bool forRoot = false, - }); - - ///Replace the current route of the navigator that most tightly encloses the - ///given context by pushing the route named routeName and then disposing the - ///previous route once the new route has finished animating in. - /// - /// ``` - /// Modular.to.pushReplacementNamed('/home/10'); - /// ``` - /// You could give parameters - /// ``` - /// Modular.to.pushReplacementNamed('/home', arguments: 10); - /// ``` - Future pushReplacementNamed( - String routeName, { - TO? result, - Object? arguments, - bool forRoot = false, - }); - - /// Removes the current Route from the stack of routes. - /// - /// ``` - /// Modular.to.pop(); - /// ``` - void pop([T result]); - - /// The initial route cannot be popped off the navigator, which implies that - /// this function returns true only if popping the navigator would not remove - /// the initial route. - /// - /// ``` - /// Modular.to.canPop(); - /// ``` - bool canPop(); - - ///Consults the current route's Route.willPop method, and acts accordingly, - ///potentially popping the route as a result; returns whether the pop request - ///should be considered handled. - /// - /// ``` - /// Modular.to.maybePop(); - /// ``` - Future maybePop([T result]); - - ///Calls pop repeatedly on the navigator that most tightly encloses the given - ///context until the predicate returns true. - /// - /// ``` - /// Modular.to.popUntil(ModalRoute.withName('/login')); - /// ``` - void popUntil(bool Function(Route) predicate); - - /// Navigate to a new screen. - /// This action replaces all past routes. - /// - /// ``` - /// Modular.to.navigate('/home'); - /// ``` - void navigate(String path, {dynamic arguments}); - - void setObservers(List navigatorObservers); - - void setNavigatorKey(GlobalKey? navigatorkey); -} diff --git a/flutter_modular/lib/src/presenter/models/module_route.dart b/flutter_modular/lib/src/presenter/models/module_route.dart deleted file mode 100644 index 0bbb1069..00000000 --- a/flutter_modular/lib/src/presenter/models/module_route.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:flutter_modular/src/presenter/models/route.dart'; -import 'package:modular_core/modular_core.dart'; - -import '../guards/route_guard.dart'; - -/// This route represents a cluster of routes from another module -/// that will be concatenated to the context of the parent module. -class ModuleRoute extends ParallelRoute { - factory ModuleRoute( - String name, { - required Module module, - TransitionType? transition, - CustomTransition? customTransition, - Duration? duration, - List guards = const [], - }) { - final route = ModuleRoute._start( - name: name, - middlewares: guards, - transition: transition, - customTransition: customTransition, - duration: duration, - ); - return route.addModule(name, module: module) as ModuleRoute; - } - ModuleRoute._start({ - ModularChild? child, - required String name, - void Function(dynamic)? popCallback, - String parent = '', - String schema = '', - Module? module, - TransitionType? transition, - CustomTransition? customTransition, - Duration? duration, - bool isFullscreenDialog = false, - List children = const [], - List middlewares = const [], - Uri? uri, - Map innerModules = const {}, - }) : assert( - !name.contains('/:'), - 'ModuleRoute should not contain dynamic route', - ), - super( - name: name, - child: child, - popCallback: popCallback, - transition: transition, - customTransition: customTransition, - duration: duration, - isFullscreenDialog: isFullscreenDialog, - parent: parent, - schema: schema, - children: children, - module: module, - middlewares: middlewares, - uri: uri ?? Uri.parse('/'), - innerModules: innerModules, - ); - - @override - ModuleRoute copyWith({ - ModularChild? child, - TransitionType? transition, - CustomTransition? customTransition, - Duration? duration, - Module? module, - bool? isFullscreenDialog, - String? name, - String? schema, - void Function(dynamic)? popCallback, - List? middlewares, - List? children, - String? parent, - Uri? uri, - Map? routeMap, - Map? innerModules, - }) { - return ModuleRoute._start( - child: child ?? this.child, - transition: transition ?? this.transition, - customTransition: customTransition ?? this.customTransition, - duration: duration ?? this.duration, - isFullscreenDialog: isFullscreenDialog ?? this.isFullscreenDialog, - name: name ?? this.name, - schema: schema ?? this.schema, - popCallback: popCallback ?? this.popCallback, - middlewares: middlewares ?? this.middlewares, - children: children ?? this.children, - parent: parent ?? this.parent, - uri: uri ?? this.uri, - module: module ?? this.module, - innerModules: innerModules ?? this.innerModules, - ); - } -} diff --git a/flutter_modular/lib/src/presenter/models/redirect_to_route.dart b/flutter_modular/lib/src/presenter/models/redirect_to_route.dart deleted file mode 100644 index 8cc8727a..00000000 --- a/flutter_modular/lib/src/presenter/models/redirect_to_route.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:modular_core/modular_core.dart'; - -import 'child_route.dart'; -import 'route.dart'; - -/// A route to redirect. -class RedirectRoute extends ChildRoute { - final String to; - RedirectRoute( - String name, { - required this.to, - }) : super(name, child: (_) => const SizedBox()); - - @override - RedirectRoute copyWith({ - ModularChild? child, - Module? module, - TransitionType? transition, - CustomTransition? customTransition, - Duration? duration, - bool? isFullscreenDialog, - String? name, - String? schema, - void Function(dynamic)? popCallback, - List? middlewares, - List? children, - String? parent, - Uri? uri, - Map? routeMap, - Map? innerModules, - }) { - return this; - } -} diff --git a/flutter_modular/lib/src/presenter/models/route.dart b/flutter_modular/lib/src/presenter/models/route.dart deleted file mode 100644 index c75053d3..00000000 --- a/flutter_modular/lib/src/presenter/models/route.dart +++ /dev/null @@ -1,226 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:meta/meta.dart'; -import 'package:modular_core/modular_core.dart'; - -import '../navigation/transitions/transitions.dart'; - -typedef ModularChild = Widget Function(BuildContext context); -typedef RouteBuilder = Route Function(WidgetBuilder, RouteSettings); - -class ParallelRoute extends ModularRoute { - /// Whether the route should remain in memory when it is inactive. - /// If this is true, then the route is maintained, so that any - /// futures it is holding from the next route will properly resolve - /// when the next route pops. If this is not necessary, this can - /// be set to false to allow the framework to entirely discard - /// the route's widget hierarchy when it is not visible. - /// If this getter would ever start returning a different value, - /// the changedInternalState should be invoked so that the change - /// can take effect. - final bool maintainState; - - /// Widget Builder that will be called when prompted in navigation. - final ModularChild? child; - - /// Transition performed when one page overlaps another. - /// default is TransitionType.defaultTransition; - final TransitionType? transition; - - /// Defines a custom transition. - /// If the transition is TransitionType.custom, it becomes mandatory - /// to add a CustomTransition() object. - final CustomTransition? customTransition; - - /// define the Transition duration - /// Default is 300 milliseconds - final Duration? duration; - - /// Whether this page route is a full-screen dialog. - /// Default is false; - final bool isFullscreenDialog; - - @internal - final void Function(dynamic)? popCallback; - - ParallelRoute({ - this.child, - required String name, - this.popCallback, - this.maintainState = true, - String parent = '', - String schema = '', - this.transition, - this.customTransition, - this.duration, - this.isFullscreenDialog = false, - List children = const [], - List middlewares = const [], - Module? module, - Uri? uri, - Map innerModules = const {}, - }) : super( - name, - parent: parent, - schema: schema, - children: children, - middlewares: middlewares, - module: module, - uri: uri ?? Uri.parse('/'), - innerModules: innerModules, - ); - - factory ParallelRoute.child( - String name, { - required ModularChild child, - CustomTransition? customTransition, - List children = const [], - Duration? duration, - TransitionType? transition, - bool isFullscreenDialog = false, - List middlewares = const [], - }) { - return ParallelRoute( - child: child, - name: name, - children: children, - customTransition: customTransition, - transition: transition, - duration: duration, - isFullscreenDialog: isFullscreenDialog, - middlewares: middlewares, - ); - } - factory ParallelRoute.empty() { - return ParallelRoute(name: ''); - } - - factory ParallelRoute.module( - String name, { - required Module module, - List middlewares = const [], - }) { - final route = ParallelRoute(name: name, middlewares: middlewares); - return route.addModule(name, module: module); - } - - @override - ParallelRoute addModule(String name, {required Module module}) { - final innerModules = {module.runtimeType: module}; - - return copyWith( - name: name, - uri: Uri.parse(name), - innerModules: innerModules, - module: module, - ); - } - - @override - ModularRoute addParent(covariant ParallelRoute parent) { - // ignore: invalid_use_of_visible_for_overriding_member - final newRoute = super.addParent(parent) as ParallelRoute; - return newRoute.copyWith( - customTransition: customTransition ?? parent.customTransition, - transition: transition ?? parent.transition, - duration: duration ?? parent.duration, - ); - } - - @override - ParallelRoute copyWith({ - ModularChild? child, - Module? module, - TransitionType? transition, - CustomTransition? customTransition, - Duration? duration, - bool? isFullscreenDialog, - String? name, - String? schema, - void Function(dynamic)? popCallback, - List? middlewares, - List? children, - String? parent, - Uri? uri, - Map? routeMap, - Map? innerModules, - }) { - return ParallelRoute( - child: child ?? this.child, - transition: transition ?? this.transition, - module: module ?? this.module, - customTransition: customTransition ?? this.customTransition, - duration: duration ?? this.duration, - isFullscreenDialog: isFullscreenDialog ?? this.isFullscreenDialog, - name: name ?? this.name, - schema: schema ?? this.schema, - popCallback: popCallback ?? this.popCallback, - middlewares: middlewares ?? this.middlewares, - children: children ?? this.children, - parent: parent ?? this.parent, - uri: uri ?? this.uri, - innerModules: innerModules ?? this.innerModules, - ); - } - - final Map< - TransitionType, - PageRouteBuilder Function( - ModularChild builder, - Duration transitionDuration, - RouteSettings settings, - bool maintainState, - )> transitions = { - TransitionType.fadeIn: fadeInTransition, - TransitionType.rightToLeft: rightToLeft, - TransitionType.leftToRight: leftToRight, - TransitionType.upToDown: upToDown, - TransitionType.downToUp: downToUp, - TransitionType.scale: scale, - TransitionType.rotate: rotate, - TransitionType.size: size, - TransitionType.rightToLeftWithFade: rightToLeftWithFade, - TransitionType.leftToRightWithFade: leftToRightWithFade, - }; -} - -enum TransitionType { - defaultTransition, - fadeIn, - noTransition, - rightToLeft, - leftToRight, - upToDown, - downToUp, - scale, - rotate, - size, - rightToLeftWithFade, - leftToRightWithFade, - custom, -} - -class CustomTransition { - final Widget Function( - BuildContext, - Animation, - Animation, - Widget, - ) transitionBuilder; - Widget Function( - BuildContext, - Animation, - Animation, - )? pageBuilder; - final RouteBuilder? routeBuilder; - final Duration transitionDuration; - final Duration reverseTransitionDuration; - final bool opaque; - - CustomTransition( - {required this.transitionBuilder, - this.pageBuilder, - this.routeBuilder, - this.transitionDuration = const Duration(milliseconds: 300), - this.reverseTransitionDuration = const Duration(milliseconds: 300), - this.opaque = true}); -} diff --git a/flutter_modular/lib/src/presenter/models/wildcard_route.dart b/flutter_modular/lib/src/presenter/models/wildcard_route.dart deleted file mode 100644 index a1acc260..00000000 --- a/flutter_modular/lib/src/presenter/models/wildcard_route.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/widgets.dart'; - -import '../../../flutter_modular.dart'; - -/// Escape route if nothing is found in current context. -///Usually serves as a wildcard, and is called -///if no path matching the context is found. -/// -///ATTENTION: It is strongly recommended to use one WildcardRoute per module. -class WildcardRoute extends ChildRoute { - WildcardRoute({ - required Widget Function(BuildContext) child, - TransitionType transition = TransitionType.defaultTransition, - CustomTransition? customTransition, - Duration duration = const Duration(milliseconds: 300), - }) : super('/**', - duration: duration, - child: child, - customTransition: customTransition, - transition: transition); -} diff --git a/flutter_modular/lib/src/presenter/modular_base.dart b/flutter_modular/lib/src/presenter/modular_base.dart deleted file mode 100644 index 978d7680..00000000 --- a/flutter_modular/lib/src/presenter/modular_base.dart +++ /dev/null @@ -1,223 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_modular/src/domain/usecases/bind_module.dart'; -import 'package:flutter_modular/src/domain/usecases/replace_instance.dart'; -import 'package:flutter_modular/src/domain/usecases/unbind_module.dart'; -import 'package:modular_core/modular_core.dart'; - -import '../domain/usecases/dispose_bind.dart'; -import '../domain/usecases/finish_module.dart'; -import '../domain/usecases/get_arguments.dart'; -import '../domain/usecases/get_bind.dart'; -import '../domain/usecases/set_arguments.dart'; -import '../domain/usecases/start_module.dart'; -import 'errors/errors.dart'; -import 'navigation/modular_route_information_parser.dart'; -import 'navigation/modular_router_delegate.dart'; - -abstract class IModularBase { - /// Finishes all trees(Modules). - void destroy(); - - // Responsible for starting the app. - /// It should only be called once, but it should be the first - /// method to be called before a route or bind lookup. - void init(Module module); - - /// value is '/'; - String get initialRoute; - - /// Retrieves the ModularArguments instance. - /// A ModularArguments is renewed every search for a new route. - ModularArguments get args; - - /// Retrieves the IModularNavigator instance. - /// By default the instance that controls all routes globally is returned, - /// but this behavior can be replaced in ModularNavigator - /// by a custom instance: - /// - /// Modular.navigatorDelegate = MyNavigatorDelegate(); - IModularNavigator get to; - - /// replaces the default ModularNavigator with a custom instance: - /// Ideal for Unit Testing. - /// Modular.navigatorDelegate = MyNavigatorDelegate() - IModularNavigator? navigatorDelegate; - - /// Request an instance by [Type] - B get({String? key}); - - /// Request an instance by [Type] - ///
    - /// Return null if not found instance - B? tryGet({String? key}); - - /// Dispose a bind by [Type] - bool dispose({String? key}); - - /// Navigator 2.0 initializator: RouteInformationParser - ModularRouteInformationParser get routeInformationParser; - - /// Navigator 2.0 initializator: RouterDelegate - ModularRouterDelegate get routerDelegate; - - /// Navigator 2.0 initializator: RouterConfig - RouterConfig get routerConfig; - - /// Change the starting route path - void setInitialRoute(String initialRoute); - - /// Change a list of NavigatorObserver objects - void setObservers(List navigatorObservers); - - /// Change the navigatorKey - void setNavigatorKey(GlobalKey? key); - - /// Change the navigatorKey - void setArguments(dynamic arguments); - - /// Change the navigatorKey - void bindModule(Module module); - - /// remove all module binds by name - void unbindModule({String? type}); - - /// replace instance - void replaceInstance(T instance, {String? key}); - - @visibleForTesting - String get initialRoutePath; -} - -class ModularBase implements IModularBase { - final DisposeBind disposeBind; - final FinishModule finishModule; - final GetBind getBind; - final GetArguments getArguments; - final SetArguments setArgumentsUsecase; - final StartModule startModule; - final BindModule bindModuleUsecase; - final UnbindModule unbindModuleUsecase; - final ReplaceInstance replaceInstanceUsecase; - final IModularNavigator navigator; - @override - final ModularRouteInformationParser routeInformationParser; - @override - final ModularRouterDelegate routerDelegate; - - @override - IModularNavigator? navigatorDelegate; - - bool _moduleHasBeenStarted = false; - - String _initialRoutePath = '/'; - - @visibleForTesting - @override - String get initialRoutePath => _initialRoutePath; - - ModularBase( - this.routeInformationParser, - this.routerDelegate, - this.disposeBind, - this.getArguments, - this.finishModule, - this.getBind, - this.startModule, - this.navigator, - this.setArgumentsUsecase, - this.bindModuleUsecase, - this.unbindModuleUsecase, - this.replaceInstanceUsecase, - ); - - @override - bool dispose({String? key}) => - disposeBind(key).getOrElse((left) => false); - - @override - B get({String? key}) { - return getBind(key).getOrThrow(); - } - - @override - B? tryGet({String? key}) { - return getBind(key).getOrNull(); - } - - @override - void destroy() { - _moduleHasBeenStarted = false; - finishModule(); - } - - @override - void init(Module module) { - if (!_moduleHasBeenStarted) { - startModule(module).getOrThrow(); - printResolverFunc?.call('${module.runtimeType} started!'); - _moduleHasBeenStarted = true; - } else { - throw ModuleStartedException( - 'Module ${module.runtimeType} is already started', - ); - } - } - - @override - IModularNavigator get to => navigatorDelegate ?? navigator; - - @override - ModularArguments get args => - getArguments().getOrElse((l) => ModularArguments.empty()); - - final flags = ModularFlags(); - - @override - String get initialRoute => _initialRoutePath; - - @override - void setInitialRoute(String value) { - _initialRoutePath = value; - } - - @override - void setNavigatorKey(GlobalKey? key) { - routerDelegate.setNavigatorKey(key); - } - - @override - void setObservers(List navigatorObservers) { - routerDelegate.setObservers(navigatorObservers); - } - - @override - void setArguments(dynamic data) { - setArgumentsUsecase.call(args.copyWith(data: data)); - } - - @override - late final RouterConfig routerConfig = RouterConfig( - routerDelegate: routerDelegate, - routeInformationParser: routeInformationParser, - routeInformationProvider: PlatformRouteInformationProvider( - initialRouteInformation: const RouteInformation(uri: Uri.parse(initialRoute)), - ), - backButtonDispatcher: RootBackButtonDispatcher(), - ); - - @override - void bindModule(Module module) { - bindModuleUsecase(module).getOrThrow(); - } - - @override - void unbindModule({String? type}) { - unbindModuleUsecase.call(type: type).getOrThrow(); - } - - @override - void replaceInstance(T instance, {String? key}) { - replaceInstanceUsecase.call(instance, key).getOrThrow(); - } -} diff --git a/flutter_modular/lib/src/presenter/navigation/custom_navigator.dart b/flutter_modular/lib/src/presenter/navigation/custom_navigator.dart deleted file mode 100644 index 7a83a30f..00000000 --- a/flutter_modular/lib/src/presenter/navigation/custom_navigator.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:flutter/widgets.dart'; - -import '../modular_base.dart'; - -class CustomNavigator extends Navigator { - final IModularBase modularBase; - - const CustomNavigator({ - Key? key, - required this.modularBase, - List observers = const [], - List> pages = const >[], - bool Function(Route, dynamic)? onPopPage, - }) : super( - key: key, - pages: pages, - onPopPage: onPopPage, - observers: observers, - ); - - @override - NavigatorState createState() => _CustomNavigatorState(modularBase); -} - -class _CustomNavigatorState extends NavigatorState { - final IModularBase modularBase; - - _CustomNavigatorState(this.modularBase); - - @override - Future pushNamed(String routeName, - {Object? arguments}) { - return modularBase.to.pushNamed(routeName, arguments: arguments); - } - - @override - Future popAndPushNamed( - String routeName, - {TO? result, - Object? arguments}) { - return modularBase.to.popAndPushNamed(routeName, - result: result, arguments: arguments); - } - - @override - Future pushNamedAndRemoveUntil( - String newRouteName, dynamic predicate, - {Object? arguments}) { - return modularBase.to.pushNamedAndRemoveUntil(newRouteName, predicate, - arguments: arguments); - } - - @override - Future pushReplacementNamed( - String routeName, - {TO? result, - Object? arguments}) { - return modularBase.to.pushReplacementNamed(routeName, - result: result, arguments: arguments); - } -} diff --git a/flutter_modular/lib/src/presenter/navigation/modular_book.dart b/flutter_modular/lib/src/presenter/navigation/modular_book.dart deleted file mode 100644 index 3db07295..00000000 --- a/flutter_modular/lib/src/presenter/navigation/modular_book.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/cupertino.dart'; - -import '../../../flutter_modular.dart'; -import '../modular_base.dart'; -import 'modular_page.dart'; - -class ModularBook { - final List routes; - Uri get uri => routes.isEmpty ? Uri.parse('/') : routes.last.uri; - - const ModularBook({required this.routes}); - - Iterable chapters([String chapter = '']) { - final filteredRoutes = - routes.where((route) => route.schema == chapter).toList(); - final pages = []; - for (var i = 0; i < filteredRoutes.length; i++) { - final route = filteredRoutes[i]; - pages.add(ModularPage( - key: ValueKey('${route.uri}@${route.schema}@$i'), - route: route, - args: Modular.args, - flags: (Modular as ModularBase).flags, - )); - } - - return pages; - } - - ModularBook copyWith({ - List? routes, - Uri? uri, - }) { - return ModularBook( - routes: routes ?? this.routes, - ); - } -} diff --git a/flutter_modular/lib/src/presenter/navigation/modular_page.dart b/flutter_modular/lib/src/presenter/navigation/modular_page.dart deleted file mode 100644 index bcc605c9..00000000 --- a/flutter_modular/lib/src/presenter/navigation/modular_page.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:modular_core/modular_core.dart'; - -import '../errors/errors.dart'; -import '../models/modular_args.dart'; -import '../models/route.dart'; - -class ModularPage extends Page { - final ParallelRoute route; - final bool isEmpty; - final ModularFlags flags; - - ModularPage({ - LocalKey? key, - required this.route, - this.isEmpty = false, - required ModularArguments args, - required this.flags, - }) : super(key: key, name: route.uri.toString(), arguments: args.data); - - factory ModularPage.empty() { - return ModularPage( - isEmpty: true, - route: ParallelRoute.empty(), - args: ModularArguments.empty(), - flags: ModularFlags(), - ); - } - - @override - Route createRoute(BuildContext context) { - late final Widget page; - if (route.child != null) { - page = route.child!(context); - } else { - throw ModularPageException('Child not be null'); - } - - final transitionType = route.transition ?? TransitionType.defaultTransition; - - if (transitionType == TransitionType.custom && - route.customTransition != null) { - final transition = route.customTransition!; - if (transition.routeBuilder != null) { - return transition.routeBuilder!((context) => page, this) as Route; - } - - return PageRouteBuilder( - pageBuilder: transition.pageBuilder ?? (context, _, __) => page, - opaque: transition.opaque, - settings: this, - maintainState: route.maintainState, - transitionsBuilder: transition.transitionBuilder, - transitionDuration: transition.transitionDuration, - fullscreenDialog: route.isFullscreenDialog, - ); - } else if (transitionType == TransitionType.defaultTransition) { - // Helper function - Widget widgetBuilder(BuildContext context) => page; - - if (flags.isCupertino) { - return CupertinoPageRoute( - settings: this, - maintainState: route.maintainState, - builder: widgetBuilder, - fullscreenDialog: route.isFullscreenDialog, - ); - } - return MaterialPageRoute( - settings: this, - maintainState: route.maintainState, - builder: widgetBuilder, - fullscreenDialog: route.isFullscreenDialog, - ); - } else if (transitionType == TransitionType.noTransition) { - return NoTransitionMaterialPageRoute( - settings: this, - maintainState: route.maintainState, - builder: (_) => page, - fullscreenDialog: route.isFullscreenDialog, - ); - } else { - final selectTransition = route.transitions[transitionType]; - return selectTransition!( - (_) => page, - route.duration ?? const Duration(milliseconds: 300), - this, - route.maintainState) as Route; - } - } -} - -// class ModularRouteSettings extends Route { -// final ModularPage page; - -// ModularRouteSettings(this.page) : super(settings: page); -// } - -class NoTransitionMaterialPageRoute extends MaterialPageRoute { - NoTransitionMaterialPageRoute({ - required WidgetBuilder builder, - RouteSettings? settings, - bool maintainState = true, - bool fullscreenDialog = false, - }) : super( - builder: builder, - maintainState: maintainState, - settings: settings, - fullscreenDialog: fullscreenDialog); - - @override - Duration get transitionDuration => Duration.zero; - - @override - Widget buildTransitions(BuildContext context, Animation animation, - Animation secondaryAnimation, Widget child) { - return child; - } -} diff --git a/flutter_modular/lib/src/presenter/navigation/modular_route_information_parser.dart b/flutter_modular/lib/src/presenter/navigation/modular_route_information_parser.dart deleted file mode 100644 index 419e57ef..00000000 --- a/flutter_modular/lib/src/presenter/navigation/modular_route_information_parser.dart +++ /dev/null @@ -1,168 +0,0 @@ -// ignore_for_file: deprecated_member_use - -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_modular/src/domain/errors/errors.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../../flutter_modular.dart'; -import '../../domain/dtos/route_dto.dart'; -import '../../domain/usecases/get_arguments.dart'; -import '../../domain/usecases/get_route.dart'; -import '../../domain/usecases/report_push.dart'; -import '../../domain/usecases/set_arguments.dart'; -import '../../infra/services/url_service/url_service.dart'; -import 'modular_book.dart'; - -class ModularRouteInformationParser - extends RouteInformationParser { - final GetRoute getRoute; - final GetArguments getArguments; - final SetArguments setArguments; - final ReportPush reportPush; - final UrlService urlService; - - ModularRouteInformationParser( - this.getRoute, - this.getArguments, - this.setArguments, - this.reportPush, - this.urlService, - ); - - @override - Future parseRouteInformation( - RouteInformation routeInformation) async { - var path = ''; - - // 3.10 wrapper - final location = [null].contains(routeInformation.uri.path) - ? '/' - : routeInformation.uri.path; - if (location == '/') { - // ignore: invalid_use_of_visible_for_testing_member - path = urlService.getPath() ?? Modular.initialRoutePath; - - if ((const bool.fromEnvironment('dart.tool.dart2wasm') || - const bool.fromEnvironment('dart.library.js_util')) && - path == '/') { - // ignore: invalid_use_of_visible_for_testing_member - path = Modular.initialRoutePath; - } - } else { - // 3.10 wrapper - path = location; - } - - return selectBook( - path, - arguments: routeInformation.uri.queryParameters, - ); - } - - @override - RouteInformation restoreRouteInformation(ModularBook configuration) { - return RouteInformation(location: configuration.uri.toString()); - } - - Future selectBook(String path, - {dynamic arguments, void Function(dynamic)? popCallback}) async { - var route = await selectRoute(path, arguments: arguments); - - final modularArgs = - getArguments().getOrElse((l) => ModularArguments.empty()); - - if (popCallback != null) { - route = route.copyWith(popCallback: popCallback); - } - - late ModularBook book; - - if (route.parent.isEmpty) { - reportPush(route); - book = ModularBook(routes: [route]); - } else { - var parent = route.parent; - book = ModularBook(routes: [route.copyWith(schema: parent)]); - - while (parent != '') { - var child = await selectRoute(parent, arguments: arguments); - parent = child.parent; - if (parent == route.parent) { - parent = ''; - continue; - } - child = child.copyWith(schema: parent); - book.routes.insert(0, child); - } - - setArguments(modularArgs); - - for (final booksRoute in book.routes) { - reportPush(booksRoute); - } - } - - return book; - } - - String _resolverPath(String relativePath) { - return getArguments // - .call() - .map((r) => r.uri.resolve(relativePath)) - .map((s) => s.toString()) - .getOrDefault(relativePath); - } - - FutureOr selectRoute(String path, {dynamic arguments}) async { - if (path.isEmpty) { - throw Exception('Route can not be empty'); - } - - path = _resolverPath(path); - - final params = RouteParmsDTO(url: path, arguments: arguments); - - final fistTrying = getRoute.call(params).flatMap((success) { - if (success.name.endsWith('/**')) { - return const Failure(RouteNotFoundException( - 'Wildcard is not available for the first time')); - } - return Success(success); - }); - - return fistTrying.map(_routeSuccess).recover((modularError) { - final params = RouteParmsDTO(url: '$path/', arguments: arguments); - return getRoute - .call(params) // - .map(_routeSuccess) - .map((success) { - debugPrint('[MODULAR WARNING] - Please, use $path/ instead of $path.'); - - return success; - }); - }).getOrThrow(); - } - - FutureOr _routeSuccess(ModularRoute? route) async { - final modularArguments = - getArguments().getOrElse((l) => ModularArguments.empty()); - for (final middleware in route!.middlewares) { - route = await middleware.pos(route!, modularArguments); - if (route == null) { - break; - } - } - - if (route is RedirectRoute) { - route = await selectRoute(route.to, arguments: modularArguments.data); - } - - if (route != null) { - return route as ParallelRoute; - } - - throw Exception("route can't null"); - } -} diff --git a/flutter_modular/lib/src/presenter/navigation/modular_router_delegate.dart b/flutter_modular/lib/src/presenter/navigation/modular_router_delegate.dart deleted file mode 100644 index db70942f..00000000 --- a/flutter_modular/lib/src/presenter/navigation/modular_router_delegate.dart +++ /dev/null @@ -1,326 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import '../../../flutter_modular.dart'; -import '../../domain/usecases/report_pop.dart'; -import 'custom_navigator.dart'; -import 'modular_book.dart'; -import 'modular_page.dart'; -import 'modular_route_information_parser.dart'; - -class ModularRouterDelegate extends RouterDelegate - with - // ignore: prefer_mixin - ChangeNotifier, - PopNavigatorRouterDelegateMixin - implements - IModularNavigator { - @override - GlobalKey navigatorKey; - - final ModularRouteInformationParser parser; - final ReportPop reportPop; - List observers = []; - - ModularRouterDelegate(this.parser, this.navigatorKey, this.reportPop); - - @override - ModularBook? currentConfiguration; - @override - List get navigateHistory => currentConfiguration?.routes ?? []; - - @override - Widget build(BuildContext context) { - final pages = currentConfiguration?.chapters().toList() ?? []; - if (pages.isEmpty) { - return const Material(); - } - - return CustomNavigator( - key: navigatorKey, - modularBase: Modular, - pages: pages, - observers: observers, - onPopPage: onPopPage, - ); - } - - @override - void setObservers(List navigatorObservers) { - observers = navigatorObservers; - notifyListeners(); - } - - @override - void setNavigatorKey(GlobalKey? key) { - if (key != null) { - navigatorKey = key; - notifyListeners(); - } - } - - @override - Future setNewRoutePath(ModularBook configuration) async { - final disposableRoutes = []; - - for (final route - in currentConfiguration?.routes ?? >[]) { - if (configuration.routes - .indexWhere((element) => element.uri.path == route.uri.path) == - -1) { - disposableRoutes.add(route); - } - } - - currentConfiguration = configuration; - notifyListeners(); - - for (final disposableRoute in disposableRoutes) { - reportPop.call(disposableRoute); - } - } - - var _lastClick = DateTime.now(); - var _lastRouteName = ''; - - @override - Future navigate(String routeName, {dynamic arguments}) async { - _lastRouteName = routeName; - final currentTime = DateTime.now(); - if (routeName == path) { - return; - } - - final diffTimes = currentTime.isBefore(_lastClick) - ? 0 - : currentTime.difference(_lastClick).inMilliseconds; - if (diffTimes < 500) { - await Future.delayed(Duration(milliseconds: 500 - diffTimes)); - if (_lastRouteName != routeName) { - return; - } - } - _lastClick = currentTime; - - final book = await parser.selectBook(routeName, arguments: arguments); - return setNewRoutePath(book); - } - - bool onPopPage(Route route, dynamic result) { - if (!route.didPop(result) || route.isFirst) { - return false; - } - - final page = route.settings as ModularPage; - final parallel = page.route; - parallel.popCallback?.call(result); - currentConfiguration?.routes.remove(parallel); - if (currentConfiguration?.routes.indexWhere( - (element) => element.uri.toString() == parallel.uri.toString()) == - -1) { - reportPop.call(parallel); - } - final arguments = - parser.getArguments().getOrElse((l) => ModularArguments.empty()); - parser.setArguments(arguments.copyWith(uri: currentConfiguration!.uri)); - notifyListeners(); - - return true; - } - - @override - Future pushNamed(String routeName, - {Object? arguments, bool forRoot = false}) async { - final popComplete = Completer(); - var book = await parser.selectBook(routeName, - arguments: arguments, popCallback: popComplete.complete); - if (forRoot) { - book = currentConfiguration!.copyWith(routes: [ - ...currentConfiguration!.routes, - book.routes.last.copyWith(schema: '') - ]); - await setNewRoutePath(book); - } else { - final list = [...currentConfiguration!.routes]; - - for (final route in book.routes.reversed) { - if (list - .firstWhere( - (element) => element.uri.toString() == route.uri.toString(), - orElse: ParallelRoute.empty) - .name == - '') { - list.add(route); - } - } - - if (currentConfiguration!.routes.length == list.length) { - list.add(book.routes.last); - } - - await setNewRoutePath(book.copyWith(routes: list)); - } - - return await popComplete.future; - } - - @override - Future pushReplacementNamed( - String routeName, - {TO? result, - Object? arguments, - bool forRoot = false}) async { - final popComplete = Completer(); - var book = await parser.selectBook(routeName, - arguments: arguments, popCallback: popComplete.complete); - final currentRoutes = [...currentConfiguration!.routes]; - if (forRoot) { - //;currentRoutes.removeWhere((element) => element.schema != ''); - final indexLast = - currentRoutes.lastIndexWhere((element) => element.schema == ''); - currentRoutes[indexLast] = book.routes.first.copyWith(schema: ''); - book = currentConfiguration!.copyWith(routes: [...currentRoutes]); - await setNewRoutePath(book); - } else { - final list = currentRoutes..removeLast(); - - for (final route in book.routes.reversed) { - if (list - .firstWhere( - (element) => element.uri.toString() == route.uri.toString(), - orElse: ParallelRoute.empty) - .name == - '') { - list.add(route); - } - } - await setNewRoutePath(book.copyWith(routes: list)); - } - - return await popComplete.future; - } - - @override - Future popAndPushNamed( - String routeName, - {TO? result, - Object? arguments, - bool forRoot = false}) { - pop(result); - return pushNamed(routeName, arguments: arguments); - } - - @override - bool canPop() => navigatorKey.currentState?.canPop() ?? false; - - @override - Future maybePop([T? result]) => - navigatorKey.currentState?.maybePop(result) ?? Future.value(false); - - @override - void pop([T? result]) => - navigatorKey.currentState?.pop(result); - - @override - void popUntil(bool Function(Route) predicate) { - var isFoundedPages = currentConfiguration?.routes.where((route) { - return predicate( - CustomModalRoute( - ModularPage( - route: route, - args: ModularArguments.empty(), - flags: ModularFlags(), - ), - ), - ); - }); - - isFoundedPages ??= []; - if (isFoundedPages.isEmpty) { - navigatorKey.currentState?.popUntil((route) => route.isFirst); - } else { - navigatorKey.currentState?.popUntil(predicate); - } - } - - @override - Future pushNamedAndRemoveUntil( - String routeName, - bool Function(Route) predicate, { - Object? arguments, - bool forRoot = false, - }) async { - final popComplete = Completer(); - final book = await parser.selectBook( - routeName, - arguments: arguments, - popCallback: popComplete.complete, - ); - - final actualRoutes = currentConfiguration!.routes.toList(); - - final reversed = actualRoutes.reversed.toList(); - - for (final route in reversed) { - final result = predicate( - CustomModalRoute( - ModularPage( - route: route, - args: ModularArguments.empty(), - flags: ModularFlags(), - ), - ), - ); - - if (result) { - break; - } - - actualRoutes.remove(route); - } - - await setNewRoutePath(book.copyWith(routes: [ - ...actualRoutes, - ...book.routes, - ])); - - return await popComplete.future; - } - - @override - String get path => currentConfiguration?.uri.toString() ?? '/'; - - @override - Future push(Route route) async { - return await navigatorKey.currentState?.push(route); - } -} - -class CustomModalRoute extends ModalRoute { - CustomModalRoute(RouteSettings settings) : super(settings: settings); - - @override - Color? get barrierColor => throw UnimplementedError(); - - @override - bool get barrierDismissible => throw UnimplementedError(); - - @override - String? get barrierLabel => throw UnimplementedError(); - - @override - Widget buildPage(BuildContext context, Animation animation, - Animation secondaryAnimation) { - throw UnimplementedError(); - } - - @override - bool get maintainState => throw UnimplementedError(); - - @override - bool get opaque => throw UnimplementedError(); - - @override - Duration get transitionDuration => throw UnimplementedError(); -} diff --git a/flutter_modular/lib/src/presenter/navigation/router_outlet_delegate.dart b/flutter_modular/lib/src/presenter/navigation/router_outlet_delegate.dart deleted file mode 100644 index 243b4faa..00000000 --- a/flutter_modular/lib/src/presenter/navigation/router_outlet_delegate.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../flutter_modular.dart'; -import 'custom_navigator.dart'; -import 'modular_page.dart'; -import 'modular_router_delegate.dart'; - -class RouterOutletDelegate extends RouterDelegate - with - // ignore: prefer_mixin - ChangeNotifier, - PopNavigatorRouterDelegateMixin { - @override - final GlobalKey navigatorKey; - - final ModularRouterDelegate modularRouterDelegate; - final String path; - final List? observers; - - RouterOutletDelegate( - this.path, this.modularRouterDelegate, this.navigatorKey, this.observers); - - List _getPages() { - return modularRouterDelegate.currentConfiguration - ?.chapters(path) - .toList() ?? - []; - } - - @override - Widget build(BuildContext context) { - final _pages = _getPages(); - return _pages.isEmpty - ? const Material() - : CustomNavigator( - key: navigatorKey, - modularBase: Modular, - pages: _pages, - observers: observers ?? [], - onPopPage: modularRouterDelegate.onPopPage, - ); - } - - @override - Future setNewRoutePath(ParallelRoute configuration) async { - assert(false, 'Dont use this'); - } -} diff --git a/flutter_modular/lib/src/presenter/navigation/transitions/page_transition.dart b/flutter_modular/lib/src/presenter/navigation/transitions/page_transition.dart deleted file mode 100644 index 0bd6d3b8..00000000 --- a/flutter_modular/lib/src/presenter/navigation/transitions/page_transition.dart +++ /dev/null @@ -1,178 +0,0 @@ -import 'package:flutter/widgets.dart'; - -enum PageTransitionType { - rightToLeft, - leftToRight, - upToDown, - downToUp, - scale, - rotate, - size, - rightToLeftWithFade, - leftToRightWithFade, -} - -class PageTransition extends PageRouteBuilder { - final Widget Function(BuildContext context) builder; - final PageTransitionType type; - final Curve curve; - final Alignment alignment; - final Duration duration; - - PageTransition({ - required this.builder, - required this.type, - this.curve = Curves.easeInOut, - this.alignment = Alignment.center, - this.duration = const Duration(milliseconds: 600), - RouteSettings? settings, - bool maintainState = true, - }) : super( - pageBuilder: (context, animation, secondaryAnimation) { - return builder(context); - }, - transitionDuration: duration, - settings: settings, - maintainState: maintainState, - transitionsBuilder: (context, animation, secondaryAnimation, child) { - switch (type) { - case PageTransitionType.rightToLeft: - return SlideTransition( - transformHitTests: false, - position: Tween( - begin: const Offset(1, 0), - end: Offset.zero, - ).animate(CurvedAnimation(parent: animation, curve: curve)), - child: SlideTransition( - position: Tween( - begin: Offset.zero, - end: const Offset(-1, 0), - ).animate(CurvedAnimation( - parent: secondaryAnimation, curve: curve)), - child: child, - ), - ); - - case PageTransitionType.leftToRight: - return SlideTransition( - transformHitTests: false, - position: Tween( - begin: const Offset(-1, 0), - end: Offset.zero, - ).animate(CurvedAnimation(parent: animation, curve: curve)), - child: SlideTransition( - position: Tween( - begin: Offset.zero, - end: const Offset(1, 0), - ).animate(CurvedAnimation( - parent: secondaryAnimation, curve: curve)), - child: child, - ), - ); - case PageTransitionType.upToDown: - return SlideTransition( - transformHitTests: false, - position: Tween( - begin: const Offset(0, -1), - end: Offset.zero, - ).animate(CurvedAnimation(parent: animation, curve: curve)), - child: SlideTransition( - position: Tween( - begin: Offset.zero, - end: const Offset(0, 1), - ).animate(CurvedAnimation( - parent: secondaryAnimation, curve: curve)), - child: child, - ), - ); - case PageTransitionType.downToUp: - return SlideTransition( - transformHitTests: false, - position: Tween( - begin: const Offset(0, 1), - end: Offset.zero, - ).animate(CurvedAnimation(parent: animation, curve: curve)), - child: SlideTransition( - position: Tween( - begin: Offset.zero, - end: const Offset(0, -1), - ).animate(CurvedAnimation( - parent: secondaryAnimation, curve: curve)), - child: child, - ), - ); - case PageTransitionType.scale: - return ScaleTransition( - alignment: alignment, - scale: CurvedAnimation( - parent: animation, - curve: Interval( - 0, - 0.50, - curve: curve, - ), - ), - child: child, - ); - case PageTransitionType.rotate: - return RotationTransition( - alignment: alignment, - turns: CurvedAnimation(parent: animation, curve: curve), - child: ScaleTransition( - scale: CurvedAnimation(parent: animation, curve: curve), - child: FadeTransition( - opacity: animation, - child: child, - ), - ), - ); - case PageTransitionType.size: - return Align( - child: SizeTransition( - sizeFactor: CurvedAnimation( - parent: animation, - curve: curve, - ), - child: child, - ), - ); - case PageTransitionType.rightToLeftWithFade: - return SlideTransition( - position: Tween( - begin: const Offset(1, 0), - end: Offset.zero, - ).animate(CurvedAnimation(parent: animation, curve: curve)), - child: FadeTransition( - opacity: CurvedAnimation(parent: animation, curve: curve), - child: SlideTransition( - position: Tween( - begin: Offset.zero, - end: const Offset(-1, 0), - ).animate(CurvedAnimation( - parent: secondaryAnimation, curve: curve)), - child: child, - ), - ), - ); - case PageTransitionType.leftToRightWithFade: - return SlideTransition( - position: Tween( - begin: const Offset(-1, 0), - end: Offset.zero, - ).animate(CurvedAnimation(parent: animation, curve: curve)), - child: FadeTransition( - opacity: CurvedAnimation(parent: animation, curve: curve), - child: SlideTransition( - position: Tween( - begin: Offset.zero, - end: const Offset(1, 0), - ).animate(CurvedAnimation( - parent: secondaryAnimation, curve: curve)), - child: child, - ), - ), - ); - } - }, - ); -} diff --git a/flutter_modular/lib/src/presenter/navigation/transitions/transitions.dart b/flutter_modular/lib/src/presenter/navigation/transitions/transitions.dart deleted file mode 100644 index f08429ea..00000000 --- a/flutter_modular/lib/src/presenter/navigation/transitions/transitions.dart +++ /dev/null @@ -1,142 +0,0 @@ -import 'package:flutter/widgets.dart'; - -import '../../../../flutter_modular.dart'; - -PageRouteBuilder fadeInTransition( - ModularChild builder, - Duration transitionDuration, - RouteSettings settings, - bool maintainState, -) { - return PageRouteBuilder( - settings: settings, - transitionDuration: transitionDuration, - maintainState: maintainState, - pageBuilder: (context, __, ___) { - return builder(context); - }, - transitionsBuilder: (context, animation, secondaryAnimation, child) { - return FadeTransition( - opacity: animation, - child: child, - ); - }, - ); -} - -PageRouteBuilder rightToLeft(ModularChild builder, - Duration transitionDuration, RouteSettings settings, bool maintainState) { - return PageTransition( - settings: settings, - maintainState: maintainState, - duration: transitionDuration, - builder: (context) { - return builder(context); - }, - type: PageTransitionType.rightToLeft, - ); -} - -PageRouteBuilder leftToRight(ModularChild builder, - Duration transitionDuration, RouteSettings settings, bool maintainState) { - return PageTransition( - settings: settings, - maintainState: maintainState, - duration: transitionDuration, - builder: (context) { - return builder(context); - }, - type: PageTransitionType.leftToRight, - ); -} - -PageRouteBuilder upToDown(ModularChild builder, - Duration transitionDuration, RouteSettings settings, bool maintainState) { - return PageTransition( - settings: settings, - maintainState: maintainState, - duration: transitionDuration, - builder: (context) { - return builder(context); - }, - type: PageTransitionType.upToDown, - ); -} - -PageRouteBuilder downToUp(ModularChild builder, - Duration transitionDuration, RouteSettings settings, bool maintainState) { - return PageTransition( - settings: settings, - maintainState: maintainState, - duration: transitionDuration, - builder: (context) { - return builder(context); - }, - type: PageTransitionType.downToUp, - ); -} - -PageRouteBuilder scale(ModularChild builder, Duration transitionDuration, - RouteSettings settings, bool maintainState) { - return PageTransition( - settings: settings, - maintainState: maintainState, - duration: transitionDuration, - builder: (context) { - return builder(context); - }, - type: PageTransitionType.scale, - ); -} - -PageRouteBuilder rotate(ModularChild builder, Duration transitionDuration, - RouteSettings settings, bool maintainState) { - return PageTransition( - settings: settings, - maintainState: maintainState, - duration: transitionDuration, - builder: (context) { - return builder(context); - }, - type: PageTransitionType.rotate, - ); -} - -PageRouteBuilder size(ModularChild builder, Duration transitionDuration, - RouteSettings settings, bool maintainState) { - return PageTransition( - settings: settings, - maintainState: maintainState, - duration: transitionDuration, - builder: (context) { - return builder(context); - }, - type: PageTransitionType.size, - ); -} - -PageRouteBuilder rightToLeftWithFade(ModularChild builder, - Duration transitionDuration, RouteSettings settings, bool maintainState) { - return PageTransition( - settings: settings, - maintainState: maintainState, - duration: transitionDuration, - builder: (context) { - return builder(context); - }, - type: PageTransitionType.rightToLeftWithFade, - ); -} - -PageRouteBuilder leftToRightWithFade(ModularChild builder, - Duration transitionDuration, RouteSettings settings, bool maintainState) { - return PageTransition( - settings: settings, - maintainState: maintainState, - duration: transitionDuration, - builder: (context) { - return builder(context); - }, - type: PageTransitionType.leftToRightWithFade, - ); -} diff --git a/flutter_modular/lib/src/presenter/widgets/modular_app.dart b/flutter_modular/lib/src/presenter/widgets/modular_app.dart deleted file mode 100644 index ebc7d461..00000000 --- a/flutter_modular/lib/src/presenter/widgets/modular_app.dart +++ /dev/null @@ -1,186 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_modular/src/flutter_modular_module.dart'; -import 'package:modular_core/modular_core.dart'; - -import '../../../flutter_modular.dart'; -import '../modular_base.dart'; - -/// Widget responsible for starting the Modular engine. -/// This should be, if possible, the first widget in your application. -class ModularApp extends StatefulWidget { - /// Initial module. - /// This module will only be destroyed when the application is finished. - final Module module; - - /// Home application containing the MaterialApp or CupertinoApp. - final Widget child; - - ModularApp({ - Key? key, - required this.module, - required this.child, - - /// Home application containing the MaterialApp or CupertinoApp. - bool debugMode = true, - - /// Prohibits taking any bind of parent modules, forcing the imports - /// of the same in the current module to be accessed. - /// This is the same behavior as the system. Default is false; - bool notAllowedParentBinds = false, - }) : super(key: key) { - (Modular as ModularBase).flags.experimentalNotAllowedParentBinds = - notAllowedParentBinds; - (Modular as ModularBase).flags.isDebug = debugMode; - } - - @override - ModularAppState createState() => ModularAppState(); -} - -class ModularAppState extends State { - @override - void initState() { - super.initState(); - Modular.init(widget.module); - if ((Modular as ModularBase).flags.isDebug) { - setPrintResolver(debugPrint); - } - } - - @override - void dispose() { - Modular.destroy(); - printResolverFunc?.call('-- ${widget.module.runtimeType} DISPOSED'); - cleanGlobals(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return _ModularInherited(child: widget.child); - } -} - -typedef SelectCallback = Function(T bind); - -class _Register { - final T value; - Type get type => T; - final dynamic notifier; - - _Register(this.value, this.notifier); - - @override - bool operator ==(Object object) => - identical(this, object) || - object is _Register && - runtimeType == object.runtimeType && - type == object.type; - - @override - int get hashCode => value.hashCode ^ type.hashCode; -} - -class _ModularInherited extends InheritedWidget { - const _ModularInherited({Key? key, required Widget child}) - : super(key: key, child: child); - - static T of(BuildContext context, - {bool listen = true, SelectCallback? onSelect}) { - final instance = injector().get(); - final notifier = - onSelect?.call(instance) ?? injector().getNotifier(); - if (listen) { - final registre = _Register(instance, notifier ?? instance); - final inherited = - context.dependOnInheritedWidgetOfExactType<_ModularInherited>( - aspect: registre)!; - inherited.updateShouldNotify(inherited); - } - - return instance; - } - - @override - bool updateShouldNotify(covariant InheritedWidget oldWidget) { - return false; - } - - @override - InheritedElement createElement() => _InheritedModularElement(this); -} - -class _InheritedModularElement extends InheritedElement { - _InheritedModularElement(InheritedWidget widget) : super(widget); - - bool _dirty = false; - - Type? current; - - @override - void updateDependencies(Element dependent, covariant _Register aspect) { - var registers = getDependencies(dependent) as Set<_Register>?; - - registers ??= {}; - - if (registers.contains(aspect)) { - return; - } - - final value = aspect.notifier; - - if (value is Listenable) { - value.addListener(() => _handleUpdate(aspect.type)); - } else if (value is Stream) { - value.listen((event) => _handleUpdate(aspect.type)); - } - registers.add(aspect); - setDependencies(dependent, registers); - } - - @override - Widget build() { - if (_dirty) notifyClients(widget); - return super.build(); - } - - void _handleUpdate(Type type) { - current = type; - _dirty = true; - markNeedsBuild(); - } - - @override - void notifyClients(covariant Widget oldWidget) { - super.notifyClients(oldWidget as InheritedWidget); - _dirty = false; - current = null; - } - - @override - void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) { - var registers = getDependencies(dependent) as Set<_Register>?; - registers ??= {}; - - for (final register in registers) { - if (register.type == current) { - dependent.didChangeDependencies(); - } - } - } -} - -extension ModularWatchExtension on BuildContext { - /// Request an instance by [Type] and - /// watch your changes - /// - /// SUPPORTED CLASS ([Listenable], [Stream]). - T watch([SelectCallback? onSelect]) { - return _ModularInherited.of(this, onSelect: onSelect); - } - - /// Request an instance by [Type] - T read() { - return _ModularInherited.of(this, listen: false); - } -} diff --git a/flutter_modular/lib/src/presenter/widgets/navigation_listener.dart b/flutter_modular/lib/src/presenter/widgets/navigation_listener.dart deleted file mode 100644 index f69b1757..00000000 --- a/flutter_modular/lib/src/presenter/widgets/navigation_listener.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../../flutter_modular.dart'; - -class NavigationListener extends StatefulWidget { - final Widget Function(BuildContext context, Widget? child) builder; - final Widget? child; - - const NavigationListener({Key? key, required this.builder, this.child}) - : super(key: key); - - @override - NavigationListenerState createState() => NavigationListenerState(); -} - -class NavigationListenerState extends State { - void listener() => setState(() {}); - - @override - void initState() { - super.initState(); - Modular.to.addListener(listener); - } - - @override - void dispose() { - super.dispose(); - Modular.to.removeListener(listener); - } - - @override - Widget build(BuildContext context) { - return widget.builder(context, widget.child); - } -} diff --git a/flutter_modular/pubspec.yaml b/flutter_modular/pubspec.yaml deleted file mode 100644 index 168e2714..00000000 --- a/flutter_modular/pubspec.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: flutter_modular -description: Smart project structure with dependency injection and route management -version: 6.4.1 -homepage: https://github.com/Flutterando/modular - -resolution: workspace - -environment: - sdk: ">=3.6.0 <4.0.0" - -dependencies: - modular_core: ">=3.4.1 <4.0.0" - meta: ">=1.3.0 <2.0.0" - result_dart: ">=2.1.0 <3.0.0" - flutter: - sdk: flutter - flutter_web_plugins: - sdk: flutter - -dev_dependencies: - flutterando_analysis: ^0.0.2 - mocktail: ^1.0.4 - flutter_test: - sdk: flutter diff --git a/flutter_modular/test/flutter_modular_test.dart b/flutter_modular/test/flutter_modular_test.dart deleted file mode 100644 index 8bd67deb..00000000 --- a/flutter_modular/test/flutter_modular_test.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test('MaterialApp extension', () { - Modular.setInitialRoute('/'); - Modular.setObservers([]); - Modular.setNavigatorKey(GlobalKey()); - final app = MaterialApp.router( - routeInformationParser: Modular.routeInformationParser, - routerDelegate: Modular.routerDelegate, - ); - expect(app, isA()); - }); - - test('CupertinoApp extension', () { - final app = CupertinoApp.router( - routeInformationParser: Modular.routeInformationParser, - routerDelegate: Modular.routerDelegate, - ); - expect(app, isA()); - }); - - testWidgets('RouterOutlet', (tester) async { - Modular.init(AppModule()); - await tester.pumpWidget(MaterialApp.router( - routeInformationParser: Modular.routeInformationParser, - routerDelegate: Modular.routerDelegate, - )); - - await tester.pump(); - final finder = find.byKey(keyOutlet); - expect(finder, findsOneWidget); - - final state = tester.state(find.byKey(keyOutlet)); - state.listener(); - }); -} - -const keyOutlet = ValueKey('keyOutlet'); - -class AppModule extends Module { - @override - void routes(RouteManager r) { - r.child('/', child: (_) => const RouterOutlet(key: keyOutlet)); - } -} diff --git a/flutter_modular/test/src/domain/dtos/route_dto_test.dart b/flutter_modular/test/src/domain/dtos/route_dto_test.dart deleted file mode 100644 index 07aca9c0..00000000 --- a/flutter_modular/test/src/domain/dtos/route_dto_test.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:flutter_modular/src/domain/dtos/route_dto.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test('Equatable', () { - expect(const RouteParmsDTO(url: '/'), const RouteParmsDTO(url: '/')); - expect(const RouteParmsDTO(url: '/').hashCode, - const RouteParmsDTO(url: '/').hashCode); - }); -} diff --git a/flutter_modular/test/src/domain/usecases/bind_module_test.dart b/flutter_modular/test/src/domain/usecases/bind_module_test.dart deleted file mode 100644 index 1f13dd72..00000000 --- a/flutter_modular/test/src/domain/usecases/bind_module_test.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter_modular/src/domain/usecases/bind_module.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = ModuleServiceMock(); - final usecase = BindModuleImpl(service); - final module = ModuleMock(); - test('BindModuleImpl', () { - when(() => service.bind(module)).thenReturn(const Success(unit)); - - expect(usecase.call(module).getOrNull(), unit); - }); -} diff --git a/flutter_modular/test/src/domain/usecases/dispose_bind_test.dart b/flutter_modular/test/src/domain/usecases/dispose_bind_test.dart deleted file mode 100644 index a1cd2e13..00000000 --- a/flutter_modular/test/src/domain/usecases/dispose_bind_test.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter_modular/src/domain/usecases/dispose_bind.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = BindServiceMock(); - final usecase = DisposeBindImpl(service); - test('dispose bind', () { - when(() => service.disposeBind()).thenReturn(const Success(true)); - - expect(usecase.call().getOrNull(), true); - }); -} diff --git a/flutter_modular/test/src/domain/usecases/finish_module_test.dart b/flutter_modular/test/src/domain/usecases/finish_module_test.dart deleted file mode 100644 index f08cbb3d..00000000 --- a/flutter_modular/test/src/domain/usecases/finish_module_test.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter_modular/src/domain/usecases/finish_module.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = ModuleServiceMock(); - final usecase = FinishModuleImpl(service); - test('finish module', () { - when(service.finish).thenReturn(const Success(unit)); - - expect(usecase.call().isSuccess(), true); - }); -} diff --git a/flutter_modular/test/src/domain/usecases/get_arguments_test.dart b/flutter_modular/test/src/domain/usecases/get_arguments_test.dart deleted file mode 100644 index d300a2eb..00000000 --- a/flutter_modular/test/src/domain/usecases/get_arguments_test.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter_modular/src/domain/usecases/get_arguments.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = RouteServiceMock(); - final usecase = GetArgumentsImpl(service); - test('get ModularArguments', () { - final args = ModularArguments.empty(); - when(service.getArguments).thenReturn(Success(args)); - - expect(usecase.call().getOrElse((left) => ModularArguments.empty()), args); - }); -} diff --git a/flutter_modular/test/src/domain/usecases/get_bind_test.dart b/flutter_modular/test/src/domain/usecases/get_bind_test.dart deleted file mode 100644 index 10de0385..00000000 --- a/flutter_modular/test/src/domain/usecases/get_bind_test.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter_modular/src/domain/usecases/get_bind.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = BindServiceMock(); - final usecase = GetBindImpl(service); - test('get bind', () { - when(() => service.getBind()).thenReturn(const Success('test')); - - expect(usecase.call().getOrNull(), 'test'); - }); -} diff --git a/flutter_modular/test/src/domain/usecases/get_route_test.dart b/flutter_modular/test/src/domain/usecases/get_route_test.dart deleted file mode 100644 index 56c6ead2..00000000 --- a/flutter_modular/test/src/domain/usecases/get_route_test.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter_modular/src/domain/dtos/route_dto.dart'; -import 'package:flutter_modular/src/domain/usecases/get_route.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = RouteServiceMock(); - final usecase = GetRouteImpl(service); - final route = ModularRouteMock(); - const params = RouteParmsDTO(url: '/'); - test('get route', () async { - when(() => service.getRoute(params)) - .thenAnswer((_) async => Success(route)); - final result = await usecase.call(params); - expect(result.isSuccess(), true); - }); -} diff --git a/flutter_modular/test/src/domain/usecases/replace_instance_test.dart b/flutter_modular/test/src/domain/usecases/replace_instance_test.dart deleted file mode 100644 index faacb0d6..00000000 --- a/flutter_modular/test/src/domain/usecases/replace_instance_test.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter_modular/src/domain/usecases/replace_instance.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = BindServiceMock(); - final usecase = ReplaceInstanceImpl(service); - test('dispose bind', () { - const instance = 'String'; - when(() => service.replaceInstance(instance)) - .thenReturn(const Success(unit)); - - expect(usecase.call(instance).isSuccess(), true); - }); -} diff --git a/flutter_modular/test/src/domain/usecases/report_pop_test.dart b/flutter_modular/test/src/domain/usecases/report_pop_test.dart deleted file mode 100644 index a156d01e..00000000 --- a/flutter_modular/test/src/domain/usecases/report_pop_test.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter_modular/src/domain/usecases/report_pop.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../mocks/mocks.dart'; -import '../../presenter/modular_base_test.dart'; - -void main() { - final service = RouteServiceMock(); - final usecase = ReportPopImpl(service); - final route = ParallelRouteMock(); - test('ReportPopImpl', () { - when(() => service.reportPop(route)).thenReturn(const Success(unit)); - - expect(usecase.call(route).getOrNull(), unit); - }); -} diff --git a/flutter_modular/test/src/domain/usecases/report_push_test.dart b/flutter_modular/test/src/domain/usecases/report_push_test.dart deleted file mode 100644 index 62d55d20..00000000 --- a/flutter_modular/test/src/domain/usecases/report_push_test.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter_modular/src/domain/usecases/report_push.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../mocks/mocks.dart'; -import '../../presenter/modular_base_test.dart'; - -void main() { - final service = RouteServiceMock(); - final usecase = ReportPushImpl(service); - test('report push route', () { - final route = ParallelRouteMock(); - when(() => service.reportPush(route)).thenReturn(const Success(unit)); - final result = usecase.call(route); - expect(result.isSuccess(), true); - }); -} diff --git a/flutter_modular/test/src/domain/usecases/set_arguments_test.dart b/flutter_modular/test/src/domain/usecases/set_arguments_test.dart deleted file mode 100644 index d3e437f1..00000000 --- a/flutter_modular/test/src/domain/usecases/set_arguments_test.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter_modular/src/domain/usecases/set_arguments.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = RouteServiceMock(); - final usecase = SetArgumentsImpl(service); - test('set ModularArguments', () { - final args = ModularArguments.empty(); - when(() => service.setArguments(args)).thenReturn(const Success(unit)); - - expect(usecase.call(args).getOrElse((l) => throw l), unit); - }); -} diff --git a/flutter_modular/test/src/domain/usecases/start_module_test.dart b/flutter_modular/test/src/domain/usecases/start_module_test.dart deleted file mode 100644 index 04affed0..00000000 --- a/flutter_modular/test/src/domain/usecases/start_module_test.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter_modular/src/domain/usecases/start_module.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = ModuleServiceMock(); - final usecase = StartModuleImpl(service); - final module = ModuleMock(); - test('start module', () { - when(() => service.start(module)).thenReturn(const Success(unit)); - - expect(usecase.call(module).isSuccess(), true); - }); -} diff --git a/flutter_modular/test/src/domain/usecases/unbind_module_test.dart b/flutter_modular/test/src/domain/usecases/unbind_module_test.dart deleted file mode 100644 index 35e52aea..00000000 --- a/flutter_modular/test/src/domain/usecases/unbind_module_test.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter_modular/src/domain/usecases/unbind_module.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = ModuleServiceMock(); - final usecase = UnbindModuleImpl(service); - test('UnbindModuleImpl', () { - when(service.unbind).thenReturn(const Success(unit)); - - expect(usecase.call().getOrNull(), unit); - }); -} diff --git a/flutter_modular/test/src/flutter_modular_module_test.dart b/flutter_modular/test/src/flutter_modular_module_test.dart deleted file mode 100644 index 95f58f42..00000000 --- a/flutter_modular/test/src/flutter_modular_module_test.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:flutter_modular/src/flutter_modular_module.dart'; -import 'package:flutter_modular/src/presenter/modular_base.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test('resolver injection (ModularBase)', () { - expect(injector.get(), isA()); - }); -} diff --git a/flutter_modular/test/src/infra/services/bind_service_impl_test.dart b/flutter_modular/test/src/infra/services/bind_service_impl_test.dart deleted file mode 100644 index 169b13d0..00000000 --- a/flutter_modular/test/src/infra/services/bind_service_impl_test.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_modular/src/domain/errors/errors.dart'; -import 'package:flutter_modular/src/domain/services/bind_service.dart'; -import 'package:flutter_modular/src/infra/services/bind_service_impl.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - late InjectorMock injector; - late BindService service; - - setUp(() { - injector = InjectorMock(); - service = BindServiceImpl(injector); - }); - - tearDown(() => reset(injector)); - - group('getBind', () { - test('should get bind', () { - when(() => injector.get()).thenReturn('test'); - expect(service.getBind().getOrNull(), 'test'); - }); - test('should throw error not found bind', () { - when(() => injector.get()) - .thenThrow(AutoInjectorException('String')); - expect(service.getBind().exceptionOrNull(), - isA()); - }); - }); - - group('dispose', () { - test('should return true', () { - when(() => injector.disposeSingleton()).thenReturn(''); - expect(service.disposeBind().getOrNull(), true); - }); - }); - - group('replaceInstance', () { - test('should replace instance returning unit', () { - const instance = 'String'; - when(() => injector.isAdded()).thenReturn(true); - when(() => injector.replaceInstance(instance)).thenReturnVoid(); - - final result = service.replaceInstance(instance); - - expect(result.isSuccess(), true); - }); - }); -} diff --git a/flutter_modular/test/src/infra/services/module_service_impl_test.dart b/flutter_modular/test/src/infra/services/module_service_impl_test.dart deleted file mode 100644 index b35ec005..00000000 --- a/flutter_modular/test/src/infra/services/module_service_impl_test.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:flutter_modular/src/infra/services/module_service_impl.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../mocks/mocks.dart'; - -class TrackerMock extends Mock implements Tracker {} - -void main() { - late TrackerMock tracker; - late ModuleMock module; - late ModuleServiceImpl service; - - setUp(() { - tracker = TrackerMock(); - module = ModuleMock(); - service = ModuleServiceImpl(tracker); - }); - - group('start', () { - test('should return true', () { - tracker.runApp(module); - expect(service.start(module).isSuccess(), true); - }); - }); - - group('finish', () { - test('should return true', () { - expect(service.finish().isSuccess(), true); - }); - }); - - group('bind', () { - test('should execute', () { - when(() => tracker.bindModule(module)); - final result = service.bind(module); - expect(result.isSuccess(), true); - expect(result.getOrNull(), unit); - }); - }); - - group('unbind', () { - test('should execute', () { - // when(() => tracker.unbindModule('ModuleMock')); - final result = service.unbind(); - expect(result.isSuccess(), true); - }); - }); -} diff --git a/flutter_modular/test/src/infra/services/route_service_impl_test.dart b/flutter_modular/test/src/infra/services/route_service_impl_test.dart deleted file mode 100644 index 407a5451..00000000 --- a/flutter_modular/test/src/infra/services/route_service_impl_test.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:flutter_modular/src/domain/dtos/route_dto.dart'; -import 'package:flutter_modular/src/infra/services/route_service_impl.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:modular_core/modular_core.dart'; - -import '../../mocks/mocks.dart'; -import '../../presenter/modular_base_test.dart'; - -void main() { - final tracker = TrackerMock(); - final service = RouteServiceImpl(tracker); - const params = RouteParmsDTO(url: '/'); - - group('getRoute', () { - test('should get route', () async { - when(() => tracker.findRoute(params.url)) - .thenAnswer((_) async => ModularRouteMock()); - final result = await service.getRoute(params); - expect(result.isSuccess(), true); - }); - test('should throw error not found route', () async { - when(() => tracker.findRoute(params.url)).thenAnswer((_) async => null); - final result = await service.getRoute(params); - expect(result.isError(), true); - }); - }); - - group('getArguments', () { - test('should return args', () async { - when(() => tracker.arguments).thenReturn(ModularArguments.empty()); - final result = service.getArguments(); - expect(result.isSuccess(), true); - }); - }); - - group('setArguments', () { - test('should set args', () async { - final args = ModularArguments.empty(); - when(() => tracker.setArguments(args)).thenReturn(null); - final result = service.setArguments(args); - expect(result.isSuccess(), true); - }); - }); - - group('reportPop', () { - test('should send route', () async { - final route = ParallelRouteMock(); - when(() => tracker.reportPopRoute(route)).thenReturn(null); - final result = service.reportPop(route); - expect(result.isSuccess(), true); - }); - }); - - group('reportPush', () { - test('should send route', () async { - final route = ParallelRouteMock(); - when(() => tracker.reportPushRoute(route)).thenReturn(null); - final result = service.reportPush(route); - expect(result.isSuccess(), true); - }); - }); -} diff --git a/flutter_modular/test/src/infra/services/url_service/html_url_service_test.dart b/flutter_modular/test/src/infra/services/url_service/html_url_service_test.dart deleted file mode 100644 index 05c70a5f..00000000 --- a/flutter_modular/test/src/infra/services/url_service/html_url_service_test.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter_modular/src/infra/services/url_service/url_service.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test('html url service ...', () { - final service = _TestUrlService(); - - expect(service.resolvePath('http://flutterexample.dev/#/path/to/screen'), - '/path/to/screen'); - expect(service.resolvePath('http://flutterexample.dev/path/to/screen'), - '/path/to/screen'); - }); -} - -class _TestUrlService extends UrlService { - @override - String? getPath() { - return null; - } -} diff --git a/flutter_modular/test/src/mocks/mocks.dart b/flutter_modular/test/src/mocks/mocks.dart deleted file mode 100644 index 35b2a8cb..00000000 --- a/flutter_modular/test/src/mocks/mocks.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter_modular/src/domain/services/bind_service.dart'; -import 'package:flutter_modular/src/domain/services/module_service.dart'; -import 'package:flutter_modular/src/domain/services/route_service.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:modular_core/modular_core.dart'; - -class BindServiceMock extends Mock implements BindService {} - -class RouteServiceMock extends Mock implements RouteService {} - -class ModuleMock extends Mock implements Module {} - -class ModuleServiceMock extends Mock implements ModuleService {} - -class ModularRouteMock extends Mock implements ModularRoute {} - -class InjectorMock extends Mock implements AutoInjector {} - -class TrackerMock extends Mock implements Tracker {} - -extension WhenExtension on When { - void _stubFunc() {} - - void thenReturnVoid() { - thenReturn(_stubFunc()); - } - - void thenAnswerVoid() { - thenAnswer((_) => Future.value()); - } -} diff --git a/flutter_modular/test/src/presenter/extensions/route_manager_ext_test.dart b/flutter_modular/test/src/presenter/extensions/route_manager_ext_test.dart deleted file mode 100644 index 7083a89d..00000000 --- a/flutter_modular/test/src/presenter/extensions/route_manager_ext_test.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import '../models/route_test.dart'; - -void main() { - test('Extension method', () { - final manager = RouteManager(); - manager.child('/', child: (context) => Container()); - manager.redirect('/', to: '/other'); - manager.wildcard(child: (context) => Container()); - manager.module('/', module: MyModule()); - - expect(manager.args, isA()); - expect(manager.allRoutes, [ - isA(), - isA(), - isA(), - isA(), - ]); - }); -} diff --git a/flutter_modular/test/src/presenter/guards/route_guard_test.dart b/flutter_modular/test/src/presenter/guards/route_guard_test.dart deleted file mode 100644 index bdf18e94..00000000 --- a/flutter_modular/test/src/presenter/guards/route_guard_test.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test('instance', () async { - final route = ChildRoute('/', child: (_) => Container()); - final redirect = await MyGuard().pos(route, ModularArguments.empty()); - expect(redirect, isA()); - }); -} - -class MyGuard extends RouteGuard { - MyGuard() : super(redirectTo: '/'); - - @override - FutureOr canActivate(String path, ParallelRoute route) { - return false; - } -} diff --git a/flutter_modular/test/src/presenter/models/child_route_test.dart b/flutter_modular/test/src/presenter/models/child_route_test.dart deleted file mode 100644 index ec75e250..00000000 --- a/flutter_modular/test/src/presenter/models/child_route_test.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test('instance', () { - final route = ChildRoute('/', child: (context) => Container()); - expect(route.name, '/'); - }); - - test('Don\'t use name "/" in route\'s children when parent be "/" too', () { - final route = ChildRoute('/', child: (context) => Container()); - expect( - () => - ChildRoute('/', child: (context) => Container(), children: [route]), - throwsAssertionError); - }); - - test('The name must always start with a /', () { - expect(() => ChildRoute('test', child: (context) => Container()), - throwsAssertionError); - }); -} diff --git a/flutter_modular/test/src/presenter/models/module_route_test.dart b/flutter_modular/test/src/presenter/models/module_route_test.dart deleted file mode 100644 index ea28aef1..00000000 --- a/flutter_modular/test/src/presenter/models/module_route_test.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - test('instance', () { - final route = ModuleRoute('/', module: ModuleMock()); - expect(route.name, '/'); - }); - - test('copyWith', () { - final route = ModuleRoute('/', module: ModuleMock()); - expect(route.copyWith().copyWith(name: '/2').name, '/2'); - }); -} diff --git a/flutter_modular/test/src/presenter/models/redirect_to_route_test.dart b/flutter_modular/test/src/presenter/models/redirect_to_route_test.dart deleted file mode 100644 index 149305c8..00000000 --- a/flutter_modular/test/src/presenter/models/redirect_to_route_test.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test('instance', () { - final route = RedirectRoute('/@route', to: 'redirect_route').copyWith(); - expect(route.name, '/@route'); - }); -} diff --git a/flutter_modular/test/src/presenter/models/route_test.dart b/flutter_modular/test/src/presenter/models/route_test.dart deleted file mode 100644 index e86de0d2..00000000 --- a/flutter_modular/test/src/presenter/models/route_test.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test('route child', () { - final route = ParallelRoute.child( - '/', - child: (_) => Container(), - customTransition: CustomTransition( - transitionBuilder: (_, anim1, anim2, child) => child, - ), - ); - expect(route.name, '/'); - }); - - test('route empty', () { - final route = ParallelRoute.empty(); - expect(route.name, ''); - }); - - test('route copyWith', () { - final route = ParallelRoute.module('/', module: MyModule2()).copyWith(); - expect(route.name, '/'); - }); - - test('route addParent', () { - final routeParent = ParallelRoute.empty().copyWith(name: '/parent'); - final route = - ParallelRoute.empty().copyWith(name: '/').addParent(routeParent); - expect(route.name, '/parent/'); - }); -} - -class MyModule extends Module {} - -class MyModule2 extends Module {} diff --git a/flutter_modular/test/src/presenter/models/wildcard_route_test.dart b/flutter_modular/test/src/presenter/models/wildcard_route_test.dart deleted file mode 100644 index a53a51fe..00000000 --- a/flutter_modular/test/src/presenter/models/wildcard_route_test.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test('instance', () { - final route = WildcardRoute(child: (_) => Container()); - expect(route.name, '/**'); - }); -} diff --git a/flutter_modular/test/src/presenter/modular_base_test.dart b/flutter_modular/test/src/presenter/modular_base_test.dart deleted file mode 100644 index 75a514ac..00000000 --- a/flutter_modular/test/src/presenter/modular_base_test.dart +++ /dev/null @@ -1,194 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_modular/src/domain/dtos/route_dto.dart'; -import 'package:flutter_modular/src/domain/errors/errors.dart'; -import 'package:flutter_modular/src/domain/usecases/bind_module.dart'; -import 'package:flutter_modular/src/domain/usecases/dispose_bind.dart'; -import 'package:flutter_modular/src/domain/usecases/finish_module.dart'; -import 'package:flutter_modular/src/domain/usecases/get_arguments.dart'; -import 'package:flutter_modular/src/domain/usecases/get_bind.dart'; -import 'package:flutter_modular/src/domain/usecases/get_route.dart'; -import 'package:flutter_modular/src/domain/usecases/replace_instance.dart'; -import 'package:flutter_modular/src/domain/usecases/set_arguments.dart'; -import 'package:flutter_modular/src/domain/usecases/start_module.dart'; -import 'package:flutter_modular/src/domain/usecases/unbind_module.dart'; -import 'package:flutter_modular/src/presenter/errors/errors.dart'; -import 'package:flutter_modular/src/presenter/modular_base.dart'; -import 'package:flutter_modular/src/presenter/navigation/modular_route_information_parser.dart'; -import 'package:flutter_modular/src/presenter/navigation/modular_router_delegate.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../mocks/mocks.dart'; - -class DisposeBindMock extends Mock implements DisposeBind {} - -class ChangeNotifierMock extends Mock implements ChangeNotifier {} - -class SinkMock extends Mock implements Sink {} - -class GetArgumentsMock extends Mock implements GetArguments {} - -class SetArgumentsMock extends Mock implements SetArguments {} - -class FinishModuleMock extends Mock implements FinishModule {} - -class GetBindMock extends Mock implements GetBind {} - -class StartModuleMock extends Mock implements StartModule {} - -class GetRouteMock extends Mock implements GetRoute {} - -class ParallelRouteMock extends Mock implements ParallelRoute {} - -class DisposableMock extends Mock implements Disposable {} - -class IModularNavigatorMock extends Mock implements IModularNavigator {} - -class BindModuleMock extends Mock implements BindModule {} - -class UnbindModuleMock extends Mock implements UnbindModule {} - -class ReplaceInstanceMock extends Mock implements ReplaceInstance {} - -class ModularRouteInformationParserMock extends Mock - implements ModularRouteInformationParser {} - -class ModularRouterDelegateMock extends Mock implements ModularRouterDelegate {} - -class ModularErrorMock extends Mock implements ModularError {} - -void main() { - final disposeBind = DisposeBindMock(); - final getBind = GetBindMock(); - final getArguments = GetArgumentsMock(); - final setArguments = SetArgumentsMock(); - final finishModule = FinishModuleMock(); - final startModule = StartModuleMock(); - final modularNavigator = IModularNavigatorMock(); - final routeInformationParser = ModularRouteInformationParserMock(); - final routerDelegate = ModularRouterDelegateMock(); - final replaceInstance = ReplaceInstanceMock(); - final bindModule = BindModuleMock(); - final unbindModule = UnbindModuleMock(); - late IModularBase modularBase; - - setUpAll(() { - registerFallbackValue(const RouteParmsDTO(url: '/')); - registerFallbackValue(ModularArguments.empty()); - }); - - setUp(() { - modularBase = ModularBase( - routeInformationParser, - routerDelegate, - disposeBind, - getArguments, - finishModule, - getBind, - startModule, - modularNavigator, - setArguments, - bindModule, - unbindModule, - replaceInstance, - ); - - reset(disposeBind); - reset(finishModule); - reset(getArguments); - reset(getBind); - reset(modularNavigator); - reset(startModule); - reset(routeInformationParser); - reset(routerDelegate); - reset(setArguments); - reset(bindModule); - reset(replaceInstance); - reset(unbindModule); - }); - - test('to', () { - expect(modularBase.to, isA()); - }); - - test('init', () { - setPrintResolver((text) {}); - final module = ModuleMock(); - when(() => startModule.call(module)).thenReturn(const Success(unit)); - modularBase.init(module); - verify(() => startModule.call(module)); - expect( - () => modularBase.init(module), throwsA(isA())); - }); - - test('dispose', () { - when(disposeBind.call).thenReturn(const Success(true)); - expect(modularBase.dispose(), true); - }); - - test('get', () { - when(() => getBind.call()).thenReturn(const Success('modular')); - expect(modularBase.get(), 'modular'); - }); - - test('tryGet', () { - when(() => getBind.call()).thenReturn(const Success('modular')); - when(() => getBind.call()) - .thenReturn(const Failure(BindNotFoundException(''))); - expect(modularBase.tryGet(), 'modular'); - expect(modularBase.tryGet(), isNull); - }); - - test('bindModule', () { - final module = ModuleMock(); - when(() => bindModule.call(module)).thenReturn(const Success(unit)); - modularBase.bindModule(module); - verify(() => bindModule.call(module)).called(1); - }); - - test('unbindModule', () { - when(() => unbindModule.call()).thenReturn(const Success(unit)); - modularBase.unbindModule(); - verify(() => unbindModule.call()).called(1); - }); - - test('replaceInstance', () { - const instance = 'String'; - when(() => replaceInstance.call(instance)) - .thenReturn(const Success(unit)); - modularBase.replaceInstance(instance); - verify(() => replaceInstance.call(instance)).called(1); - }); - - test('destroy', () { - when(finishModule.call).thenReturn(const Success(unit)); - modularBase.destroy(); - verify(finishModule.call).called(1); - }); - - test('setArguments', () { - when(getArguments.call).thenReturn(Success(ModularArguments.empty())); - when(() => setArguments.call(any())).thenReturn(const Success(unit)); - modularBase.setArguments('args'); - verify(() => setArguments.call( - any(that: predicate((it) => it.data == 'args')), - )).called(1); - }); -} - -class MyGuard extends RouteGuard { - final bool activate; - - // ignore: avoid_positional_boolean_parameters - MyGuard(this.activate); - - @override - FutureOr canActivate(String request, ParallelRoute route) { - return activate; - } -} diff --git a/flutter_modular/test/src/presenter/navigation/custom_navigator_test.dart b/flutter_modular/test/src/presenter/navigation/custom_navigator_test.dart deleted file mode 100644 index f427f75d..00000000 --- a/flutter_modular/test/src/presenter/navigation/custom_navigator_test.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter_modular/src/presenter/models/modular_navigator.dart'; -import 'package:flutter_modular/src/presenter/modular_base.dart'; -import 'package:flutter_modular/src/presenter/navigation/custom_navigator.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; - -class ModularBaseMock extends Mock implements IModularBase {} - -class IModularNavigatorMock extends Mock implements IModularNavigator {} - -void main() { - final base = ModularBaseMock(); - final customNavigator = CustomNavigator( - modularBase: base, - ); - final state = customNavigator.createState(); - final navigator = IModularNavigatorMock(); - - when(() => base.to).thenReturn(navigator); - - test('pushNamed', () { - when(() => navigator.pushNamed(any())).thenAnswer((_) => Future.value()); - expect(state.pushNamed('/'), completes); - }); - - test('popAndPushNamed', () { - when(() => navigator.popAndPushNamed(any())) - .thenAnswer((_) => Future.value()); - expect(state.popAndPushNamed('/'), completes); - }); - - test('pushNamedAndRemoveUntil', () { - final predicate = ModalRoute.withName('/'); - when(() => navigator.pushNamedAndRemoveUntil(any(), predicate)) - .thenAnswer((_) => Future.value()); - expect(state.pushNamedAndRemoveUntil('/', predicate), completes); - }); - - test('pushReplacementNamed', () { - when(() => navigator.pushReplacementNamed(any())) - .thenAnswer((_) => Future.value()); - expect(state.pushReplacementNamed('/'), completes); - }); -} diff --git a/flutter_modular/test/src/presenter/navigation/modular_page_test.dart b/flutter_modular/test/src/presenter/navigation/modular_page_test.dart deleted file mode 100644 index e2df3875..00000000 --- a/flutter_modular/test/src/presenter/navigation/modular_page_test.dart +++ /dev/null @@ -1,197 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter_modular/src/presenter/errors/errors.dart'; -import 'package:flutter_modular/src/presenter/models/modular_args.dart'; -import 'package:flutter_modular/src/presenter/models/route.dart'; -import 'package:flutter_modular/src/presenter/navigation/modular_page.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:modular_core/modular_core.dart'; - -import '../modular_base_test.dart'; - -class BuildContextMock extends Mock implements BuildContext {} - -class AnimationMock extends Mock implements Animation {} - -void main() { - test('ModularPage.empty', () { - final page = ModularPage.empty(); - expect(page.name, '/'); - }); - - test('createRoute throw error child null', () { - final page = ModularPage.empty(); - expect(() => page.createRoute(BuildContextMock()), - throwsA(isA())); - }); - - test('createRoute default route', () { - final args = ModularArguments.empty(); - final context = BuildContextMock(); - final route = ParallelRouteMock(); - when(() => route.child).thenReturn((_) => Container()); - when(() => route.uri).thenReturn(Uri.parse('/')); - when(() => route.maintainState).thenReturn(true); - when(() => route.isFullscreenDialog).thenReturn(true); - when(() => route.transition).thenReturn(TransitionType.defaultTransition); - final page = ModularPage(args: args, flags: ModularFlags(), route: route); - expect(page.createRoute(context), isA()); - }); - - test('createRoute default route cupertino', () { - final args = ModularArguments.empty(); - final context = BuildContextMock(); - final route = ParallelRouteMock(); - final widget = Container(); - when(() => route.child).thenReturn((_) => widget); - when(() => route.uri).thenReturn(Uri.parse('/')); - when(() => route.isFullscreenDialog).thenReturn(true); - when(() => route.maintainState).thenReturn(true); - - when(() => route.transition).thenReturn(TransitionType.defaultTransition); - final page = ModularPage( - args: args, - flags: ModularFlags(isCupertino: true), - route: route, - ); - final routePage = page.createRoute(context); - expect(routePage, isA()); - expect((routePage as CupertinoPageRoute).builder(context), widget); - }); - - test('createRoute noTransition', () { - final args = ModularArguments.empty(); - final context = BuildContextMock(); - final route = ParallelRouteMock(); - final widget = Container(); - when(() => route.child).thenReturn((_) => widget); - when(() => route.maintainState).thenReturn(true); - when(() => route.isFullscreenDialog).thenReturn(true); - when(() => route.uri).thenReturn(Uri.parse('/')); - when(() => route.transition).thenReturn(TransitionType.noTransition); - final page = ModularPage(args: args, flags: ModularFlags(), route: route); - final pageRoute = page.createRoute(context); - expect(pageRoute, isA()); - expect( - (pageRoute as NoTransitionMaterialPageRoute).builder(context), - widget, - ); - expect(pageRoute.transitionDuration, Duration.zero); - expect( - pageRoute.buildTransitions( - context, - AnimationMock(), - AnimationMock(), - widget, - ), - widget, - ); - - final pageRouteGenerate = page.createRoute(context); - expect(pageRouteGenerate, isA()); - }); - - test('createRoute custom', () { - final args = ModularArguments.empty(); - final context = BuildContextMock(); - final route = ParallelRouteMock(); - final widget = Container(); - when(() => route.child).thenReturn((_) => widget); - when(() => route.isFullscreenDialog).thenReturn(true); - when(() => route.uri).thenReturn(Uri.parse('/')); - when(() => route.maintainState).thenReturn(true); - - when(() => route.transition).thenReturn(TransitionType.custom); - when(() => route.customTransition).thenReturn( - CustomTransition(transitionBuilder: (_, __, ___, child) => child)); - - final page = ModularPage(args: args, flags: ModularFlags(), route: route); - final pageRoute = page.createRoute(context); - expect(pageRoute, isA()); - expect( - (pageRoute as PageRouteBuilder).pageBuilder( - context, AnimationMock(), AnimationMock()), - widget); - }); - - test('createRoute other transitions', () { - final args = ModularArguments.empty(); - final context = BuildContextMock(); - final route = ParallelRouteMock(); - final widget = Container(); - - final transitionMap = ParallelRoute.empty().transitions; - final anim = AnimationMock(); - when(() => anim.status).thenReturn(AnimationStatus.completed); - final keys = transitionMap.keys - .where((k) => k != TransitionType.custom) - .where((k) => k != TransitionType.defaultTransition) - .where((k) => k != TransitionType.noTransition) - .toList(); - - for (final key in keys) { - when(() => route.transition).thenReturn(key); - when(() => route.transitions).thenReturn(transitionMap); - when(() => route.child).thenReturn((_) => widget); - when(() => route.maintainState).thenReturn(true); - - when(() => route.uri).thenReturn(Uri.parse('/')); - when(() => route.duration).thenReturn(Duration.zero); - - final page = ModularPage(args: args, flags: ModularFlags(), route: route); - final pageRoute = page.createRoute(context); - expect(pageRoute, isA()); - - if (key == TransitionType.fadeIn) { - expect((pageRoute as PageRouteBuilder).pageBuilder(context, anim, anim), - widget); - expect( - pageRoute.buildTransitions(context, AnimationMock(), - AnimationMock(), widget), - isA()); - } else { - expect((pageRoute as PageRouteBuilder).pageBuilder(context, anim, anim), - widget); - expect(pageRoute.buildTransitions(context, anim, anim, widget), - isA()); - } - - reset(route); - } - }); - - test('createRoute full screen dialog route', () { - final args = ModularArguments.empty(); - final context = BuildContextMock(); - final route = ParallelRouteMock(); - when(() => route.child).thenReturn((_) => Container()); - when(() => route.uri).thenReturn(Uri.parse('/')); - when(() => route.maintainState).thenReturn(true); - when(() => route.isFullscreenDialog).thenReturn(true); - when(() => route.transition).thenReturn(TransitionType.defaultTransition); - final page = ModularPage(args: args, flags: ModularFlags(), route: route); - expect(page.createRoute(context), isA()); - expect(page.route.isFullscreenDialog, equals(true)); - }); - - test('createRoute custom route builder', () { - final args = ModularArguments.empty(); - final context = BuildContextMock(); - final route = ParallelRouteMock(); - final widget = Container(); - when(() => route.child).thenReturn((_) => widget); - when(() => route.uri).thenReturn(Uri.parse('/')); - when(() => route.transition).thenReturn(TransitionType.custom); - when(() => route.customTransition).thenReturn(CustomTransition( - transitionBuilder: (_, __, ___, child) => child, - routeBuilder: (builder, settings) => CupertinoSheetRoute( - builder: builder, - settings: settings, - ), - )); - - final page = ModularPage(args: args, flags: ModularFlags(), route: route); - final pageRoute = page.createRoute(context); - expect(pageRoute, isA()); - }); -} diff --git a/flutter_modular/test/src/presenter/navigation/modular_route_information_parser_test.dart b/flutter_modular/test/src/presenter/navigation/modular_route_information_parser_test.dart deleted file mode 100644 index d2da93d3..00000000 --- a/flutter_modular/test/src/presenter/navigation/modular_route_information_parser_test.dart +++ /dev/null @@ -1,357 +0,0 @@ -// ignore_for_file: deprecated_member_use - -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_modular/src/domain/dtos/route_dto.dart'; -import 'package:flutter_modular/src/domain/usecases/get_arguments.dart'; -import 'package:flutter_modular/src/domain/usecases/get_route.dart'; -import 'package:flutter_modular/src/domain/usecases/report_push.dart'; -import 'package:flutter_modular/src/domain/usecases/set_arguments.dart'; -import 'package:flutter_modular/src/infra/services/url_service/url_service.dart'; -import 'package:flutter_modular/src/presenter/errors/errors.dart'; -import 'package:flutter_modular/src/presenter/navigation/modular_book.dart'; -import 'package:flutter_modular/src/presenter/navigation/modular_page.dart'; -import 'package:flutter_modular/src/presenter/navigation/modular_route_information_parser.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../modular_base_test.dart'; - -class GetRouteMock extends Mock implements GetRoute {} - -class GetArgumentsMock extends Mock implements GetArguments {} - -class SetArgumentsMock extends Mock implements SetArguments {} - -class ReportPushMock extends Mock implements ReportPush {} - -class UrlServiceMock extends Mock implements UrlService {} - -class ParallelRouteFake extends Fake implements ModularRoute {} - -void main() { - late ModularRouteInformationParser parser; - late GetRouteMock getRoute; - late GetArgumentsMock getArguments; - late SetArgumentsMock setArguments; - late ReportPush reportPush; - late UrlService urlService; - - setUp(() { - getRoute = GetRouteMock(); - getArguments = GetArgumentsMock(); - setArguments = SetArgumentsMock(); - reportPush = ReportPushMock(); - urlService = UrlServiceMock(); - parser = ModularRouteInformationParser( - getRoute, - getArguments, - setArguments, - reportPush, - urlService, - ); - }); - - setUpAll(() { - registerFallbackValue(const RouteParmsDTO(url: '/test')); - registerFallbackValue(ModularArguments.empty()); - registerFallbackValue(ParallelRouteFake()); - }); - - test('selectBook one', () async { - final routeMock = ParallelRouteMock(); - when(() => routeMock.uri).thenReturn(Uri.parse('/')); - when(() => routeMock.parent).thenReturn(''); - when(() => routeMock.schema).thenReturn(''); - when(() => routeMock.name).thenReturn('/'); - when(() => getRoute.call(any())) - .thenAnswer((_) async => Success(routeMock)); - when(() => getArguments.call()) - .thenReturn(Success(ModularArguments.empty())); - when(() => routeMock.middlewares).thenReturn([Guard()]); - - when(() => reportPush(routeMock)).thenReturn(const Success(unit)); - - final book = await parser.selectBook('/'); - expect(book.uri.toString(), '/'); - expect(book.chapters().first, isA()); - expect(book.chapters().first.name, '/'); - }); - - test('parseRouteInformation calls selectBook with correct arguments', - () async { - final routeMock = ParallelRouteMock(); - final params = {'param': 'value'}; - final uri = Uri.parse('/test'); - when(() => routeMock.uri).thenReturn(uri); - when(() => routeMock.parent).thenReturn(''); - when(() => routeMock.middlewares).thenReturn([]); - when(() => getRoute.call(any())) - .thenAnswer((_) async => Success(routeMock)); - when(() => getArguments.call()) - .thenReturn(Success(ModularArguments(uri: uri, data: params))); - when(() => reportPush(routeMock)).thenReturn(const Success(unit)); - - const routeInformation = RouteInformation(location: '/test?param=value'); - final book = await parser.parseRouteInformation(routeInformation); - - expect(book.uri.toString(), '/test'); - expect(parser.getArguments.call().getOrNull()?.data, params); - }); - - test('selectBook with parents', () async { - final routeMock = ParallelRouteMock(); - when(() => routeMock.uri).thenReturn(Uri.parse('/test')); - when(() => routeMock.parent).thenReturn('/'); - when(() => routeMock.schema).thenReturn('/'); - when(() => routeMock.name).thenReturn('/test'); - when(() => routeMock.middlewares).thenReturn([Guard()]); - when(() => routeMock.copyWith(schema: any(named: 'schema'))) - .thenReturn(routeMock); - - final routeParent = ParallelRouteMock(); - when(() => routeParent.uri).thenReturn(Uri.parse('/')); - when(() => routeParent.parent).thenReturn(''); - when(() => routeParent.schema).thenReturn(''); - when(() => routeParent.name).thenReturn('/'); - when(() => routeParent.middlewares).thenReturn([Guard()]); - when(() => routeParent.copyWith(schema: any(named: 'schema'))) - .thenReturn(routeParent); - - when(() => reportPush(routeMock)).thenReturn(const Success(unit)); - when(() => reportPush(routeParent)).thenReturn(const Success(unit)); - - when(() => getRoute.call(const RouteParmsDTO(url: '/test'))) - .thenAnswer((_) async => Success(routeMock)); - when(() => getRoute.call(const RouteParmsDTO(url: '/'))) - .thenAnswer((_) async => Success(routeParent)); - when(() => getArguments.call()) - .thenReturn(Success(ModularArguments.empty())); - - when(() => setArguments.call(any())).thenReturn(const Success(unit)); - - final book = await parser.selectBook('/test'); - expect(book.uri.toString(), '/test'); - expect(book.chapters().first.name, '/'); - expect(book.chapters('/').first.name, '/test'); - }); - - test('selectRoute with RedirectRoute', () async { - final redirect = RedirectRoute('/oo', to: '/test'); - final modularArgument = ModularArguments.empty(); - - final routeMock = ParallelRouteMock(); - when(() => routeMock.uri).thenReturn(Uri.parse('/test')); - when(() => routeMock.parent).thenReturn('/'); - when(() => routeMock.schema).thenReturn('/'); - when(() => routeMock.name).thenReturn('/test'); - when(() => routeMock.middlewares).thenReturn([Guard()]); - when(() => routeMock.copyWith(schema: any(named: 'schema'))) - .thenReturn(routeMock); - - final routeParent = ParallelRouteMock(); - when(() => routeParent.uri).thenReturn(Uri.parse('/')); - when(() => routeParent.parent).thenReturn(''); - when(() => routeParent.schema).thenReturn(''); - when(() => routeParent.name).thenReturn('/'); - when(() => routeParent.middlewares).thenReturn([Guard()]); - when(() => routeParent.copyWith(schema: any(named: 'schema'))) - .thenReturn(routeParent); - - when(() => reportPush(routeMock)).thenReturn(const Success(unit)); - when(() => reportPush(routeParent)).thenReturn(const Success(unit)); - - when(() => getRoute.call(const RouteParmsDTO(url: '/oo'))) - .thenAnswer((_) async => Success(redirect)); - when(() => getRoute - .call(RouteParmsDTO(url: '/test', arguments: modularArgument.data))) - .thenAnswer((_) async => Success(routeMock)); - when(() => getRoute.call(const RouteParmsDTO(url: '/'))) - .thenAnswer((_) async => Success(routeParent)); - when(() => getArguments.call()).thenReturn(Success(modularArgument)); - - when(() => setArguments.call(any())).thenReturn(const Success(unit)); - - final book = await parser.selectBook('/oo'); - expect(book.uri.toString(), '/test'); - expect(book.chapters().first.name, '/'); - expect(book.chapters('/').first.name, '/test'); - }); - test('selectRoute with resolver route withless /', () async { - final args = ModularArguments.empty(); - - final routeMock = ParallelRouteMock(); - when(() => routeMock.uri).thenReturn(Uri.parse('/test/')); - when(() => routeMock.parent).thenReturn(''); - when(() => routeMock.schema).thenReturn(''); - when(() => routeMock.middlewares).thenReturn([]); - - when(() => reportPush(routeMock)).thenReturn(const Success(unit)); - - when(() => getRoute.call(RouteParmsDTO(url: '/test', arguments: args))) - .thenAnswer((_) async => Failure(ModularPageException(''))); - when(() => getRoute.call(RouteParmsDTO(url: '/test/', arguments: args))) - .thenAnswer((_) async => Success(routeMock)); - when(() => getArguments.call()).thenReturn(Success(args)); - - when(() => setArguments.call(any())).thenReturn(const Success(unit)); - - final route = await parser.selectRoute('/test', arguments: args); - expect(route.uri.toString(), '/test/'); - }); - - test('restoreRouteInformation', () { - final route = ParallelRouteMock(); - when(() => route.uri).thenReturn(Uri.parse('/test')); - final info = parser.restoreRouteInformation(ModularBook(routes: [route])); - expect(info.location, '/test'); - }); - - test('parseRouteInformation with location null', () { - final routeMock = ParallelRouteMock(); - when(() => routeMock.uri).thenReturn(Uri.parse('/')); - when(() => routeMock.parent).thenReturn(''); - when(() => routeMock.name).thenReturn('/'); - - when(() => reportPush(routeMock)).thenReturn(const Success(unit)); - - when(() => getRoute.call(any())) - .thenAnswer((_) async => Success(routeMock)); - when(() => getArguments.call()) - .thenReturn(Success(ModularArguments.empty())); - when(() => routeMock.middlewares).thenReturn([Guard()]); - - expect( - parser.parseRouteInformation(const RouteInformation(location: '/test')), - completion(isA())); - expect( - parser.parseRouteInformation(const RouteInformation(location: '/test')), - completion(isA())); - expect(Guard().pre(routeMock), routeMock); - }); - - test('parseRouteInformation with location / and guard false', () { - final routeMock = ParallelRouteMock(); - - when(() => getRoute.call(any())) - .thenAnswer((_) async => Success(routeMock)); - when(() => getArguments.call()) - .thenReturn(Success(ModularArguments.empty())); - when(() => routeMock.middlewares).thenReturn([Guard(false)]); - when(() => routeMock.uri).thenReturn(Uri.parse('/')); - when(() => routeMock.name).thenReturn('/'); - - expect( - () => - parser.parseRouteInformation(const RouteInformation(location: '/')), - throwsA(isA())); - }); - - test('parseRouteInformation with location / and middleware null', () { - final routeMock = ParallelRouteMock(); - - when(() => getRoute.call(any())) - .thenAnswer((_) async => Success(routeMock)); - when(() => getArguments.call()) - .thenReturn(Success(ModularArguments.empty())); - when(() => routeMock.middlewares).thenReturn([MiddlewareNull()]); - when(() => routeMock.uri).thenReturn(Uri.parse('/')); - when(() => routeMock.name).thenReturn('/'); - - expect( - () => - parser.parseRouteInformation(const RouteInformation(location: '/')), - throwsA(isA())); - }); - - test('throw error if path be empty', () { - expect(() async => await parser.selectRoute(''), throwsA(isA())); - }); - - test('selectBook with popCallback', () { - final routeMock = ParallelRouteMock(); - when(() => reportPush(routeMock)).thenReturn(const Success(unit)); - when(() => getRoute.call(any())) - .thenAnswer((_) async => Success(routeMock)); - when(() => getArguments.call()) - .thenReturn(Success(ModularArguments.empty())); - when(() => routeMock.middlewares).thenReturn([]); - when(() => routeMock.uri).thenReturn(Uri.parse('/')); - when(() => routeMock.name).thenReturn('/'); - when(() => routeMock.parent).thenReturn(''); - when(() => routeMock.copyWith(popCallback: any(named: 'popCallback'))) - .thenReturn(routeMock); - expect(parser.selectBook('/', popCallback: (r) {}), completes); - }); - - test('selectRoute with wildcard', () async { - final routeMock = ParallelRouteMock(); - when(() => routeMock.uri).thenReturn(Uri.parse('/parent/test')); - when(() => routeMock.parent).thenReturn('/parent'); - when(() => routeMock.schema).thenReturn('/parent'); - when(() => routeMock.name).thenReturn('/test'); - when(() => routeMock.middlewares).thenReturn([Guard()]); - when(() => routeMock.copyWith(schema: any(named: 'schema'))) - .thenReturn(routeMock); - - final routeMock1 = ParallelRouteMock(); - when(() => routeMock1.uri).thenReturn(Uri.parse('/**')); - when(() => routeMock1.parent).thenReturn(''); - when(() => routeMock1.schema).thenReturn(''); - when(() => routeMock1.name).thenReturn('/**'); - when(() => routeMock1.middlewares).thenReturn([Guard()]); - when(() => routeMock1.copyWith(schema: any(named: 'schema'))) - .thenReturn(routeMock); - - final routeParent = ParallelRouteMock(); - when(() => routeParent.uri).thenReturn(Uri.parse('/parent')); - when(() => routeParent.parent).thenReturn(''); - when(() => routeParent.schema).thenReturn(''); - when(() => routeParent.name).thenReturn('/parent'); - when(() => routeParent.middlewares).thenReturn([Guard()]); - when(() => routeParent.copyWith(schema: any(named: 'schema'))) - .thenReturn(routeParent); - - when(() => reportPush(routeMock)).thenReturn(const Success(unit)); - when(() => reportPush(routeMock1)).thenReturn(const Success(unit)); - when(() => reportPush(routeParent)).thenReturn(const Success(unit)); - - when(() => getRoute.call(const RouteParmsDTO(url: '/parent/test'))) - .thenAnswer((_) async => Success(routeMock1)); - when(() => getRoute.call(const RouteParmsDTO(url: '/parent/test/'))) - .thenAnswer((_) async => Success(routeMock)); - when(() => getRoute.call(const RouteParmsDTO(url: '/parent'))) - .thenAnswer((_) async => Success(routeParent)); - when(() => getArguments.call()) - .thenReturn(Success(ModularArguments.empty())); - - when(() => setArguments.call(any())).thenReturn(const Success(unit)); - - final book = await parser.selectBook('/parent/test'); - expect(book.uri.toString(), '/parent/test'); - expect(book.chapters().first.name, '/parent'); - }); -} - -class Guard extends RouteGuard { - final bool isGuard; - - Guard([this.isGuard = true]); - - @override - FutureOr canActivate(String request, ParallelRoute route) { - return isGuard; - } -} - -class MiddlewareNull extends Middleware { - @override - FutureOr pos(ModularRoute route, dynamic data) async => null; - - @override - FutureOr pre(ModularRoute route) => route; -} diff --git a/flutter_modular/test/src/presenter/navigation/modular_router_delegate_test.dart b/flutter_modular/test/src/presenter/navigation/modular_router_delegate_test.dart deleted file mode 100644 index 7f657189..00000000 --- a/flutter_modular/test/src/presenter/navigation/modular_router_delegate_test.dart +++ /dev/null @@ -1,521 +0,0 @@ -// ignore_for_file: unawaited_futures - -import 'package:flutter/material.dart'; -import 'package:flutter_modular/src/domain/usecases/report_pop.dart'; -import 'package:flutter_modular/src/presenter/models/modular_args.dart'; -import 'package:flutter_modular/src/presenter/navigation/custom_navigator.dart'; -import 'package:flutter_modular/src/presenter/navigation/modular_book.dart'; -import 'package:flutter_modular/src/presenter/navigation/modular_page.dart'; -import 'package:flutter_modular/src/presenter/navigation/modular_route_information_parser.dart'; -import 'package:flutter_modular/src/presenter/navigation/modular_router_delegate.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -import '../../mocks/mocks.dart'; -import '../modular_base_test.dart' hide GetArgumentsMock, SetArgumentsMock; -import 'modular_page_test.dart'; -import 'modular_route_information_parser_test.dart'; - -class ModularRouteInformationParserMock extends Mock - implements ModularRouteInformationParser {} - -class BuildContextMock extends Mock implements BuildContext {} - -class RouteMock extends Mock implements Route {} - -class ReportPopMock extends Mock implements ReportPop {} - -class NavigatorKeyMock extends Mock implements GlobalKey {} - -class NavigatorStateMock extends Mock implements NavigatorState { - @override - String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return ''; - } -} - -void main() { - late ModularRouterDelegate delegate; - late ModularRouteInformationParserMock parser; - late NavigatorKeyMock key; - late NavigatorStateMock navigatorState; - late ReportPopMock reportPopMock; - - setUpAll(() { - registerFallbackValue(ModularArguments.empty()); - registerFallbackValue(ModularRouteMock()); - }); - - setUp(() { - key = NavigatorKeyMock(); - navigatorState = NavigatorStateMock(); - reportPopMock = ReportPopMock(); - when(() => key.currentState).thenReturn(navigatorState); - parser = ModularRouteInformationParserMock(); - delegate = ModularRouterDelegate(parser, key, reportPopMock); - }); - - test('setObserver', () { - delegate.setObservers([NavigatorObserver()]); - expect(delegate.observers.length, 1); - }); - - test('setNavigatorKey', () { - final key = GlobalKey(); - delegate.setNavigatorKey(key); - expect(delegate.navigatorKey, key); - }); - - test('build', () { - final context = BuildContextMock(); - var widget = delegate.build(context); - expect(widget, isA()); - - final route = ParallelRouteMock(); - when(() => route.schema).thenReturn(''); - when(() => route.uri).thenReturn(Uri.parse('/')); - - delegate.currentConfiguration = ModularBook(routes: [route]); - widget = delegate.build(context); - expect(widget, isA()); - expect(delegate.navigateHistory, delegate.currentConfiguration?.routes); - }); - - test('Book copywith', () { - expect(const ModularBook(routes: []).copyWith(), isA()); - }); - - test('navigate blink', () async { - final route1 = ParallelRouteMock(); - when(() => route1.uri).thenReturn(Uri.parse('/test2')); - when(() => parser.selectBook('/test2')) - .thenAnswer((_) async => ModularBook(routes: [route1])); - - delegate.navigate('/test'); - delegate.navigate('/test2'); - delegate.navigate('/test'); - delegate.navigate('/test2'); - - await Future.delayed(const Duration(seconds: 1)); - expect(delegate.currentConfiguration?.uri.toString(), '/test2'); - expect(delegate.navigateHistory, delegate.currentConfiguration?.routes); - }); - - test('navigate', () async { - final route1 = ParallelRouteMock(); - when(() => route1.uri).thenReturn(Uri.parse('/test')); - when(() => parser.selectBook('/test')) - .thenAnswer((_) async => ModularBook(routes: [route1])); - - final getArgsMock = GetArgumentsMock(); - final setArgsMock = SetArgumentsMock(); - - when(() => parser.getArguments).thenReturn(getArgsMock); - when(() => parser.setArguments).thenReturn(setArgsMock); - - final arguments = ModularArguments.empty(); - - when(getArgsMock.call).thenReturn(Success(arguments)); - when(() => setArgsMock.call(any())).thenReturn(const Success(unit)); - - await delegate.navigate('/'); - await delegate.navigate('/test'); - await Future.delayed(const Duration(milliseconds: 600)); - await delegate.navigate('/test'); - expect(delegate.currentConfiguration?.uri.toString(), '/test'); - expect(delegate.path, '/test'); - expect(delegate.navigateHistory, delegate.currentConfiguration!.routes); - }); - test('onPopPage', () { - final route = RouteMock(); - final parallel = ParallelRouteMock(); - when(() => parallel.uri).thenReturn(Uri.parse('/')); - final page = ModularPage( - route: parallel, args: ModularArguments.empty(), flags: ModularFlags()); - when(() => route.didPop(null)).thenReturn(true); - when(() => route.settings).thenReturn(page); - when(() => route.isFirst).thenReturn(false); - - when(() => reportPopMock.call(parallel)).thenReturn(const Success(unit)); - - final arguments = ModularArguments.empty(); - final getArgsMock = GetArgumentsMock(); - final setArgsMock = SetArgumentsMock(); - when(() => parser.getArguments).thenReturn(getArgsMock); - when(() => parser.setArguments).thenReturn(setArgsMock); - - when(getArgsMock.call).thenReturn(Success(arguments)); - when(() => setArgsMock.call(any())).thenReturn(const Success(unit)); - - delegate.currentConfiguration = ModularBook(routes: [parallel]); - expect(delegate.currentConfiguration?.routes.length, 1); - delegate.onPopPage(route, null); - expect(delegate.currentConfiguration?.routes.length, 0); - expect(delegate.navigateHistory, delegate.currentConfiguration?.routes); - }); - test('onPopPage with parent/child route', () { - final route = RouteMock(); - final parallel = ParallelRouteMock(); - when(() => parallel.uri).thenReturn(Uri.parse('/')); - final page = ModularPage( - route: parallel, args: ModularArguments.empty(), flags: ModularFlags()); - when(() => route.didPop(null)).thenReturn(true); - when(() => route.settings).thenReturn(page); - when(() => route.isFirst).thenReturn(false); - - when(() => reportPopMock.call(parallel)).thenReturn(const Success(unit)); - - final childRoute = RouteMock(); - final childParallel = ParallelRouteMock(); - when(() => childParallel.uri).thenReturn(Uri.parse('/child')); - when(() => childParallel.parent).thenReturn('/'); - final childPage = ModularPage( - route: childParallel, args: ModularArguments.empty(), flags: ModularFlags()); - when(() => childRoute.didPop(null)).thenReturn(true); - when(() => childRoute.settings).thenReturn(childPage); - when(() => childRoute.isFirst).thenReturn(false); - - when(() => reportPopMock.call(childParallel)).thenReturn(const Success(unit)); - - final arguments = ModularArguments.empty(); - final getArgsMock = GetArgumentsMock(); - final setArgsMock = SetArgumentsMock(); - when(() => parser.getArguments).thenReturn(getArgsMock); - when(() => parser.setArguments).thenReturn(setArgsMock); - - when(getArgsMock.call).thenReturn(Success(arguments)); - when(() => setArgsMock.call(any())).thenReturn(const Success(unit)); - - delegate.currentConfiguration = ModularBook(routes: [parallel, childParallel]); - expect(delegate.currentConfiguration?.routes.length, 2); - delegate.onPopPage(route, null); - expect(delegate.currentConfiguration?.routes.length, 0); - expect(delegate.navigateHistory, delegate.currentConfiguration?.routes); - }); - test('pushNamed with forRoot', () async { - final route1 = ParallelRouteMock(); - final route2 = ParallelRouteMock(); - when(() => route1.uri).thenReturn(Uri.parse('/')); - when(() => route1.copyWith(schema: '')).thenReturn(route1); - when(() => route1.schema).thenReturn(''); - - when(() => route2.uri).thenReturn(Uri.parse('/pushForce')); - when(() => route2.copyWith(schema: '')).thenReturn(route2); - when(() => route2.schema).thenReturn(''); - - final getArgsMock = GetArgumentsMock(); - final setArgsMock = SetArgumentsMock(); - - when(() => parser.getArguments).thenReturn(getArgsMock); - when(() => parser.setArguments).thenReturn(setArgsMock); - - final arguments = ModularArguments.empty(); - - when(getArgsMock.call).thenReturn(Success(arguments)); - when(() => setArgsMock.call(any())).thenReturn(const Success(unit)); - - delegate.currentConfiguration = ModularBook(routes: [route1]); - - when(() => parser.selectBook('/pushForce', - popCallback: any(named: 'popCallback'))) - .thenAnswer((_) async => ModularBook(routes: [route2])); - delegate.pushNamed('/pushForce', forRoot: true); - await Future.delayed(const Duration(milliseconds: 400)); - - expect(delegate.currentConfiguration?.uri.toString(), '/pushForce'); - expect(delegate.currentConfiguration?.routes.length, 2); - expect(delegate.navigateHistory, delegate.currentConfiguration?.routes); - }); - - test('pushNamed common', () async { - final route1 = ParallelRouteMock(); - final route2 = ParallelRouteMock(); - when(() => route1.uri).thenReturn(Uri.parse('/')); - when(() => route1.copyWith(schema: '')).thenReturn(route1); - when(() => route1.schema).thenReturn(''); - - when(() => route2.uri).thenReturn(Uri.parse('/pushForce')); - when(() => route2.copyWith(schema: '')).thenReturn(route2); - when(() => route2.schema).thenReturn(''); - when(() => route2.name).thenReturn('/pushForce'); - - final getArgsMock = GetArgumentsMock(); - final setArgsMock = SetArgumentsMock(); - - when(() => parser.getArguments).thenReturn(getArgsMock); - when(() => parser.setArguments).thenReturn(setArgsMock); - - final arguments = ModularArguments.empty(); - - when(getArgsMock.call).thenReturn(Success(arguments)); - when(() => setArgsMock.call(any())).thenReturn(const Success(unit)); - - delegate.currentConfiguration = ModularBook(routes: [route1]); - - when(() => parser.selectBook('/pushForce', - popCallback: any(named: 'popCallback'))) - .thenAnswer((_) async => ModularBook(routes: [route2])); - delegate.pushNamed('/pushForce'); - await Future.delayed(const Duration(milliseconds: 400)); - delegate.pushNamed('/pushForce'); - await Future.delayed(const Duration(milliseconds: 400)); - - expect(delegate.currentConfiguration?.uri.toString(), '/pushForce'); - expect(delegate.currentConfiguration?.routes.length, 3); - expect(delegate.navigateHistory, delegate.currentConfiguration?.routes); - }); - - test('pushReplacementNamed with forRoot', () async { - final route1 = ParallelRouteMock(); - final route2 = ParallelRouteMock(); - when(() => route1.uri).thenReturn(Uri.parse('/')); - when(() => route1.copyWith(schema: '')).thenReturn(route1); - when(() => route1.schema).thenReturn(''); - - when(() => route2.uri).thenReturn(Uri.parse('/pushForce')); - when(() => route2.copyWith(schema: '')).thenReturn(route2); - when(() => route2.schema).thenReturn(''); - final getArgsMock = GetArgumentsMock(); - final setArgsMock = SetArgumentsMock(); - - when(() => parser.getArguments).thenReturn(getArgsMock); - when(() => parser.setArguments).thenReturn(setArgsMock); - - final arguments = ModularArguments.empty(); - - when(getArgsMock.call).thenReturn(Success(arguments)); - when(() => setArgsMock.call(any())).thenReturn(const Success(unit)); - - when(() => reportPopMock.call(route1)).thenReturn(const Success(unit)); - - delegate.currentConfiguration = ModularBook(routes: [route1]); - - when(() => parser.selectBook('/pushForce', - popCallback: any(named: 'popCallback'))) - .thenAnswer((_) async => ModularBook(routes: [route2])); - delegate.pushReplacementNamed('/pushForce', forRoot: true); - await Future.delayed(const Duration(milliseconds: 400)); - - expect(delegate.currentConfiguration?.uri.toString(), '/pushForce'); - expect(delegate.currentConfiguration?.routes.length, 1); - expect(delegate.navigateHistory, delegate.currentConfiguration?.routes); - }); - - test('pushReplacementNamed common', () async { - final route1 = ParallelRouteMock(); - final route2 = ParallelRouteMock(); - final route3 = ParallelRouteMock(); - - when(() => route1.uri).thenReturn(Uri.parse('/')); - when(() => route1.copyWith(schema: '')).thenReturn(route1); - when(() => route1.schema).thenReturn(''); - - when(() => route2.uri).thenReturn(Uri.parse('/2')); - when(() => route2.copyWith(schema: '')).thenReturn(route2); - when(() => route2.schema).thenReturn(''); - - when(() => route3.uri).thenReturn(Uri.parse('/pushForce')); - when(() => route3.copyWith(schema: '')).thenReturn(route3); - when(() => route3.schema).thenReturn(''); - - when(() => reportPopMock.call(route1)).thenReturn(const Success(unit)); - when(() => reportPopMock.call(route2)).thenReturn(const Success(unit)); - - final getArgsMock = GetArgumentsMock(); - final setArgsMock = SetArgumentsMock(); - - when(() => parser.getArguments).thenReturn(getArgsMock); - when(() => parser.setArguments).thenReturn(setArgsMock); - - final arguments = ModularArguments.empty(); - - when(getArgsMock.call).thenReturn(Success(arguments)); - when(() => setArgsMock.call(any())).thenReturn(const Success(unit)); - - delegate.currentConfiguration = ModularBook(routes: [route1, route2]); - - when(() => parser.selectBook('/pushForce', - popCallback: any(named: 'popCallback'))) - .thenAnswer((_) async => ModularBook(routes: [route3])); - delegate.pushReplacementNamed('/pushForce'); - await Future.delayed(const Duration(milliseconds: 400)); - - expect(delegate.currentConfiguration?.uri.toString(), '/pushForce'); - expect(delegate.currentConfiguration?.routes.length, 2); - expect(delegate.navigateHistory, delegate.currentConfiguration?.routes); - }); - - test('popAndPushNamed ', () async { - final route1 = ParallelRouteMock(); - final route2 = ParallelRouteMock(); - when(() => route1.uri).thenReturn(Uri.parse('/')); - when(() => route1.copyWith(schema: '')).thenReturn(route1); - when(() => route1.schema).thenReturn(''); - - when(() => route2.uri).thenReturn(Uri.parse('/pushForce')); - when(() => route2.copyWith(schema: '')).thenReturn(route2); - when(() => route2.schema).thenReturn(''); - - final getArgsMock = GetArgumentsMock(); - final setArgsMock = SetArgumentsMock(); - - when(() => parser.getArguments).thenReturn(getArgsMock); - when(() => parser.setArguments).thenReturn(setArgsMock); - - final arguments = ModularArguments.empty(); - - when(getArgsMock.call).thenReturn(Success(arguments)); - when(() => setArgsMock.call(any())).thenReturn(const Success(unit)); - - delegate.currentConfiguration = ModularBook(routes: [route1]); - - when(() => parser.selectBook('/pushForce', - popCallback: any(named: 'popCallback'))) - .thenAnswer((_) async => ModularBook(routes: [route2])); - delegate.popAndPushNamed('/pushForce'); - await Future.delayed(const Duration(milliseconds: 400)); - - expect(delegate.currentConfiguration?.uri.toString(), '/pushForce'); - expect(delegate.navigateHistory, delegate.currentConfiguration?.routes); - }); - - test('pop ', () async { - when(() => navigatorState.pop()).thenReturn(null); - delegate.pop(); - verify(() => navigatorState.pop()); - }); - test('push ', () async { - final route = MaterialPageRoute(builder: (_) => Container()); - when(() => navigatorState.push(route)) - .thenAnswer((_) => Future.value(true)); - await delegate.push(route); - verify(() => navigatorState.push(route)); - }); - - test('canPop ', () async { - when(() => navigatorState.canPop()).thenReturn(true); - expect(delegate.canPop(), true); - }); - - test('maybePop ', () async { - when(() => navigatorState.maybePop()).thenAnswer((_) => Future.value(true)); - expect(await delegate.maybePop(), true); - }); - - test('popUntil ', () async { - final route1 = ParallelRouteMock(); - final route2 = ParallelRouteMock(); - when(() => route1.uri).thenReturn(Uri.parse('/')); - when(() => route1.copyWith(schema: '')).thenReturn(route1); - when(() => route1.schema).thenReturn(''); - - when(() => route2.uri).thenReturn(Uri.parse('/pushForce')); - when(() => route2.copyWith(schema: '')).thenReturn(route2); - when(() => route2.schema).thenReturn(''); - - when(() => navigatorState.popUntil(any())).thenReturn(null); - delegate.popUntil((_) => false); - delegate.currentConfiguration = ModularBook(routes: [route1, route2]); - delegate.popUntil((_) => true); - verify(() => navigatorState.popUntil(any())); - }); - - test('pushNamedAndRemoveUntil ', () async { - final route1 = ParallelRouteMock(); - final route2 = ParallelRouteMock(); - final route3 = ParallelRouteMock(); - - final arguments = ModularArguments.empty(); - - final getArgsMock = GetArgumentsMock(); - final setArgsMock = SetArgumentsMock(); - - when(getArgsMock.call).thenReturn(Success(arguments)); - when(() => setArgsMock.call(any())).thenReturn(const Success(unit)); - - when(() => parser.getArguments).thenReturn(getArgsMock); - when(() => parser.setArguments).thenReturn(setArgsMock); - - when(() => reportPopMock.call(any())).thenReturn(const Success(unit)); - - when(() => route1.uri).thenReturn(Uri.parse('/')); - when(() => route1.copyWith(schema: '')).thenReturn(route1); - when(() => route1.schema).thenReturn(''); - - when(() => route2.uri).thenReturn(Uri.parse('/pushForce')); - when(() => route2.copyWith(schema: '')).thenReturn(route2); - when(() => route2.schema).thenReturn(''); - - when(() => route3.uri).thenReturn(Uri.parse('/pushForce2')); - when(() => route3.copyWith(schema: '')).thenReturn(route3); - when(() => route3.schema).thenReturn(''); - - when(() => navigatorState.popUntil(any())).thenReturn(null); - - delegate.currentConfiguration = - ModularBook(routes: [route1, route2, route3]); - - when(() => parser.selectBook('/pushForce2', - popCallback: any(named: 'popCallback'))) - .thenAnswer((_) async => ModularBook(routes: [route3])); - delegate.pushNamedAndRemoveUntil('/pushForce2', ModalRoute.withName('/')); - await Future.delayed(const Duration(milliseconds: 400)); - - expect(delegate.currentConfiguration?.uri.toString(), '/pushForce2'); - expect(delegate.currentConfiguration?.routes.length, 2); - }); - - test('pushNamedAndRemoveUntil forRoot', () async { - final route1 = ParallelRouteMock(); - final route2 = ParallelRouteMock(); - - when(() => reportPopMock.call(any())).thenReturn(const Success(unit)); - - when(() => route1.uri).thenReturn(Uri.parse('/')); - when(() => route1.copyWith(schema: '')).thenReturn(route1); - when(() => route1.schema).thenReturn(''); - - when(() => route2.uri).thenReturn(Uri.parse('/pushForce')); - when(() => route2.copyWith(schema: '')).thenReturn(route2); - when(() => route2.schema).thenReturn(''); - - when(() => navigatorState.popUntil(any())).thenReturn(null); - - final getArgsMock = GetArgumentsMock(); - final setArgsMock = SetArgumentsMock(); - when(() => parser.getArguments).thenReturn(getArgsMock); - when(() => parser.setArguments).thenReturn(setArgsMock); - - final arguments = ModularArguments.empty(); - - when(getArgsMock.call).thenReturn(Success(arguments)); - when(() => setArgsMock.call(any())).thenReturn(const Success(unit)); - - delegate.currentConfiguration = ModularBook(routes: [route1]); - - when(() => parser.selectBook('/pushForce', - popCallback: any(named: 'popCallback'))) - .thenAnswer((_) async => ModularBook(routes: [route2])); - delegate.pushNamedAndRemoveUntil('/pushForce', (_) => false, forRoot: true); - await Future.delayed(const Duration(milliseconds: 400)); - - expect(delegate.currentConfiguration?.uri.toString(), '/pushForce'); - expect(delegate.currentConfiguration?.routes.length, 1); - }); - - test('CustomModalRoute ', () async { - final route = CustomModalRoute(ModularPage.empty()); - expect(() => route.barrierColor, throwsA(isA())); - expect(() => route.barrierDismissible, throwsA(isA())); - expect(() => route.maintainState, throwsA(isA())); - expect(() => route.opaque, throwsA(isA())); - expect(() => route.transitionDuration, throwsA(isA())); - expect(() => route.barrierLabel, throwsA(isA())); - expect( - () => route.buildPage( - BuildContextMock(), AnimationMock(), AnimationMock()), - throwsA(isA())); - }); -} diff --git a/flutter_modular/test/src/presenter/navigation/router_outlet_delegate_test.dart b/flutter_modular/test/src/presenter/navigation/router_outlet_delegate_test.dart deleted file mode 100644 index 72a99f20..00000000 --- a/flutter_modular/test/src/presenter/navigation/router_outlet_delegate_test.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_modular/src/presenter/models/route.dart'; -import 'package:flutter_modular/src/presenter/navigation/custom_navigator.dart'; -import 'package:flutter_modular/src/presenter/navigation/modular_book.dart'; -import 'package:flutter_modular/src/presenter/navigation/modular_router_delegate.dart'; -import 'package:flutter_modular/src/presenter/navigation/router_outlet_delegate.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../modular_base_test.dart'; -import 'modular_page_test.dart'; - -class ModularRouterDelegateMock extends Mock implements ModularRouterDelegate {} - -class NavigatorKeyMock extends Mock implements GlobalKey {} - -class NavigatorStateMock extends Mock implements NavigatorState { - @override - String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return ''; - } -} - -class NavigatorObserverMock extends Mock implements NavigatorObserver {} - -void main() { - late ModularRouterDelegateMock modularRouterDelegateMock; - late RouterOutletDelegate outlet; - late NavigatorKeyMock key; - late NavigatorStateMock navigatorState; - late NavigatorObserverMock navigatorObserver; - - setUp(() { - modularRouterDelegateMock = ModularRouterDelegateMock(); - key = NavigatorKeyMock(); - navigatorState = NavigatorStateMock(); - navigatorObserver = NavigatorObserverMock(); - when(() => key.currentState).thenReturn(navigatorState); - outlet = RouterOutletDelegate( - 'outlet', modularRouterDelegateMock, key, [navigatorObserver]); - }); - - test('setNewRoutePath...', () { - expect(() => outlet.setNewRoutePath(ParallelRoute.empty()), - throwsA(isAssertionError)); - }); - - test('build', () { - var widget = outlet.build(BuildContextMock()); - expect(widget, isA()); - - final route1 = ParallelRouteMock(); - final route2 = ParallelRouteMock(); - when(() => route1.uri).thenReturn(Uri.parse('/')); - when(() => route1.copyWith(schema: '')).thenReturn(route1); - when(() => route1.schema).thenReturn(''); - - when(() => route2.uri).thenReturn(Uri.parse('/pushForce')); - when(() => route2.copyWith(schema: 'outlet')).thenReturn(route2); - when(() => route2.schema).thenReturn('outlet'); - - when(() => modularRouterDelegateMock.currentConfiguration) - .thenReturn(ModularBook(routes: [route1, route2])); - widget = outlet.build(BuildContextMock()); - expect(widget, isA()); - }); -} diff --git a/flutter_modular/test/src/presenter/widgets/modular_app_test.dart b/flutter_modular/test/src/presenter/widgets/modular_app_test.dart deleted file mode 100644 index 95558a8a..00000000 --- a/flutter_modular/test/src/presenter/widgets/modular_app_test.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('ModularApp', (tester) async { - final modularKey = UniqueKey(); - final modularApp = ModularApp( - key: modularKey, - module: CustomModule(), - child: const AppWidget(), - ); - await tester.pumpWidget(modularApp); - - await tester.pump(); - expect(find.byKey(key), findsOneWidget); - - //final state = tester.state(find.byKey(modularKey)); - final result = Modular.get(); - expect(result, 'test'); - - await tester.pump(); - final notifier = Modular.get>(); - notifier.value++; - - await tester.pump(); - - expect(find.text('1'), findsOneWidget); - - final store = Modular.get(); - store.update(1); - - await tester.pump(); - - expect(find.text('1'), findsWidgets); - }); -} - -final key = UniqueKey(); - -class CustomModule extends Module { - @override - void binds(Injector i) { - i.addInstance('test'); - i.addSingleton>(() => ValueNotifier(0)); - i.addSingleton>( - () => Stream.value(0).asBroadcastStream(), - ); - i.addSingleton(MyStore.new); - - i.args; - } - - @override - void routes(RouteManager r) { - r.child('/', child: (_) => const Home()); - } -} - -class AppWidget extends StatelessWidget { - const AppWidget({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - context.read(); - - return MaterialApp.router( - routerConfig: Modular.routerConfig, - ); - } -} - -class Home extends StatelessWidget { - const Home({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final notifier = context.watch>(); - final stream = context.watch>(); - final store = context.watch(); - - return Container( - key: key, - child: Column( - children: [ - Text('${notifier.value}'), - StreamBuilder( - stream: stream, - builder: (context, snapshot) { - return Text('${snapshot.data}'); - }, - ), - Text('${store.state}') - ], - ), - ); - } -} - -class MyStore extends ValueNotifier { - MyStore() : super(0); - - int get state => value; - - late final void Function(int state)? fnState; - late final void Function(bool state)? fnLoading; - late final void Function(Exception state)? fnError; - - void update(int newState, {bool force = false}) { - value = newState; - } -} diff --git a/flutter_modular/test/src/presenter/widgets/navigation_listener_test.dart b/flutter_modular/test/src/presenter/widgets/navigation_listener_test.dart deleted file mode 100644 index f2269e3b..00000000 --- a/flutter_modular/test/src/presenter/widgets/navigation_listener_test.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('NavigationListener', (tester) async { - final modularApp = ModularApp( - module: CustomModule(), - child: const AppWidget(), - ); - await tester.pumpWidget(modularApp); - - await tester.pump(); - final finder = find.byKey(key); - expect(finder, findsOneWidget); - - final state = tester.state(find.byKey(key)); - state.listener(); - }); -} - -final key = UniqueKey(); - -class CustomModule extends Module { - @override - void binds(Injector i) => i.add((i) => 'test'); - - @override - void routes(RouteManager r) => r.add( - ChildRoute( - '/', - child: (_) => NavigationListener( - key: key, - builder: (context, snapshot) { - return Container(); - }), - ), - ); -} - -class AppWidget extends StatelessWidget { - const AppWidget({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return MaterialApp.router( - routeInformationParser: Modular.routeInformationParser, - routerDelegate: Modular.routerDelegate, - ); - } -} diff --git a/lib/flutter_modular.dart b/lib/flutter_modular.dart new file mode 100644 index 00000000..18dd855a --- /dev/null +++ b/lib/flutter_modular.dart @@ -0,0 +1,19 @@ +/// flutter_modular v7 — clean rewrite (work in progress). +/// +/// Navigator 2.0 (route matching + page stack + guards + transitions), the +/// module system (`createModule` / `ModularContext`), page-scoped state +/// (`provide` / `Scoped` + `context.watch`/`read`, `Consumer`/`Selector`, +/// `addStream`), and nested routes (`children` + `RouterOutlet`). +library; + +export 'src/app/modular_app.dart'; +export 'src/module/module.dart'; +export 'src/navigation/modular_navigation.dart'; +export 'src/navigation/modular_router_config.dart'; +export 'src/navigation/outlet.dart' show RouterOutlet, RouterOutletState; +export 'src/navigation/transition.dart' show TransitionType; +export 'src/route/modular_route.dart'; +export 'src/route/route_state.dart'; +export 'src/state/consumer.dart'; +export 'src/state/scoped.dart' + show Disposable, ModularStateX, Scoped, StreamValue; diff --git a/lib/src/app/modular_app.dart b/lib/src/app/modular_app.dart new file mode 100644 index 00000000..6beccb6c --- /dev/null +++ b/lib/src/app/modular_app.dart @@ -0,0 +1,130 @@ +import 'package:flutter/widgets.dart'; + +import '../module/module.dart'; +import '../navigation/modular_router_config.dart'; +import '../route/route_state.dart'; +import '../state/scoped.dart'; + +/// The root widget of a Modular app. It bootstraps [module] once (collecting +/// its routes + DI), owns the resulting injector, builds the [RouterConfig], +/// and — sitting ABOVE the `MaterialApp` — hosts optional APP-SCOPED state via +/// [provide]. That position is exactly what lets an app-global view model +/// (theme, locale, session) rebuild the `MaterialApp` itself, which page-scoped +/// state (living below the `Navigator`) cannot reach. +/// +/// The [child] reads the router config with [ModularApp.routerConfigOf] and any +/// app-scoped view model with `context.watch`/`read` — same `Scoped` mechanism +/// as a route's `provide`, only anchored at the app instead of the page: +/// +/// ```dart +/// void main() => runApp( +/// ModularApp( +/// module: appModule, +/// provide: (s) => s.addChangeNotifier(ThemeViewModel.new), +/// child: const AppRoot(), +/// ), +/// ); +/// +/// class AppRoot extends StatelessWidget { +/// const AppRoot({super.key}); +/// @override +/// Widget build(BuildContext context) { +/// final theme = context.watch(); // above MaterialApp +/// return MaterialApp.router( +/// themeMode: theme.mode, +/// routerConfig: ModularApp.routerConfigOf(context), +/// ); +/// } +/// } +/// ``` +class ModularApp extends StatefulWidget { + const ModularApp({ + required this.module, + required this.child, + this.provide, + this.initialRoute = '/', + this.navigatorKey, + this.navigatorObservers = const [], + super.key, + }); + + /// The root module: its routes + DI become the whole app graph. + final Module module; + + /// Typically a `MaterialApp.router` that reads [routerConfigOf] and watches + /// any app-scoped state declared in [provide]. + final Widget child; + + /// Optional APP-SCOPED state, declared like a route's `provide` but anchored + /// above the [child] (and thus above the `MaterialApp`), so it can drive the + /// app's theme/locale. + final void Function(Scoped scoped)? provide; + + /// The first route shown — used when the platform reports no deep link + /// (the bare `/`). A real entry URL (web refresh, app link) overrides it. + final String initialRoute; + + /// Optional key for the root [Navigator], for imperative access from outside + /// the widget tree (e.g. showing a global dialog). A fresh key is used if + /// omitted. + final GlobalKey? navigatorKey; + + /// Observers attached to the root [Navigator] — analytics, a `RouteObserver` + /// for route-aware widgets, etc. + final List navigatorObservers; + + /// The router config built by the nearest enclosing [ModularApp]. + static RouterConfig routerConfigOf(BuildContext context) { + final scope = context + .dependOnInheritedWidgetOfExactType<_ModularAppScope>(); + if (scope == null) { + throw FlutterError( + 'ModularApp.routerConfigOf: no ModularApp ancestor found.', + ); + } + return scope.routerConfig; + } + + @override + State createState() => _ModularAppState(); +} + +class _ModularAppState extends State { + late final ModularBootstrap _boot = bootstrapModule(widget.module); + late final RouterConfig _routerConfig = modularRouterConfig( + _boot.routes, + injector: _boot.injector, + manager: _boot.manager, + initialRoute: widget.initialRoute, + navigatorKey: widget.navigatorKey, + observers: widget.navigatorObservers, + ); + + @override + Widget build(BuildContext context) { + Widget child = _ModularAppScope( + routerConfig: _routerConfig, + child: widget.child, + ); + final provide = widget.provide; + if (provide != null) { + child = ScopedHost( + provide: provide, + parent: _boot.injector, + child: child, + ); + } + return child; + } +} + +/// Exposes the [RouterConfig] built by [ModularApp] to its descendants. +class _ModularAppScope extends InheritedWidget { + const _ModularAppScope({required this.routerConfig, required super.child}); + + final RouterConfig routerConfig; + + @override + bool updateShouldNotify(_ModularAppScope oldWidget) => + routerConfig != oldWidget.routerConfig; +} diff --git a/lib/src/module/module.dart b/lib/src/module/module.dart new file mode 100644 index 00000000..25dce2bf --- /dev/null +++ b/lib/src/module/module.dart @@ -0,0 +1,353 @@ +import 'package:auto_injector/auto_injector.dart'; +import 'package:flutter/foundation.dart'; + +import '../navigation/transition.dart'; +import '../route/modular_route.dart'; +import '../state/scoped.dart'; + +/// A module SPEC: declares DI + Routes via [register]. Build it functionally +/// with [createModule] (a `final` value, deduped by identity) or by extending. +abstract class Module { + /// Where this module mounts. A module WITH a path is a FEATURE: including it + /// flattens its routes under the path and feature-scopes its binds — disposed + /// when its last route leaves (the full mounted path is its lifecycle tag). A + /// module WITHOUT a path is a shared DI dependency: root-owned, never + /// disposed. Override at the include site with `module(m, at: ...)`. + String? get path => null; + + void register(ModularContext c); +} + +/// Creates a functional module. Store it in a `final` and reference the SAME +/// value everywhere — composition dedups by IDENTITY. +/// +/// Give a [path] to make it a FEATURE (mounted there, with bind lifecycle); +/// omit it for a shared DI module (root-owned). +Module createModule({ + String? path, + required void Function(ModularContext c) register, +}) { + assert(_isValidMountPath(path), _mountPathError(path)); + return _FunctionalModule(path, register); +} + +/// A module mount path is a STATIC prefix: it must start with `/` and carry no +/// dynamic segment (`:param`). Dynamic params belong to the routes INSIDE a +/// module, not to where the module mounts (which is also its lifecycle tag). +bool _isValidMountPath(String? path) => + path == null || (path.startsWith('/') && !path.contains(':')); + +String _mountPathError(String? path) => + 'Invalid module path "$path": it must start with "/" and contain no ' + 'dynamic segment (":"). Put `:params` on the routes inside the module.'; + +class _FunctionalModule extends Module { + _FunctionalModule(this.path, this._register); + + @override + final String? path; + final void Function(ModularContext) _register; + + @override + void register(ModularContext c) => _register(c); +} + +/// The single surface a module declares itself through: DI registration +/// (`add*`), routes (`route`, with `guards`/`transition`/nested `children`), +/// and the unified [module] include verb. +abstract class ModularContext { + void route( + String path, { + required ModularWidgetBuilder child, + void Function(Scoped scoped)? provide, + void Function(ModularContext c)? children, + List? guards, + TransitionType transition, + }); + + /// Include another module. The mount path is [at] ?? `module.path`: + /// - none → a shared DI dependency (binds root-owned, never disposed); + /// - a path → a NAMESPACE feature: routes flattened under it, binds + /// feature-scoped (disposed when the module's LAST route leaves). + /// [at] is the rare override of a module's own [Module.path]. Dedup by + /// identity. + void module(Module module, {String? at}); + + void add(Function constructor); + void addSingleton(Function constructor); + void addLazySingleton(Function constructor); + void addInstance(T instance); +} + +/// The active resolution injector, set by [bootstrapModule]. Backs [inject]. +AutoInjector? _activeInjector; + +/// Resolves [T] from the active module graph — Angular-style service access +/// that keeps the injector object PRIVATE. Works anywhere a constructor can't +/// inject for you (route guards, callbacks, widgets) after a Modular app has +/// bootstrapped. It reads the LIVE graph, so a feature module's binds are +/// reachable only while that module is active. +/// +/// ```dart +/// guards: [(state) => inject().unlocked ? null : '/login'], +/// ``` +T inject() { + final injector = _activeInjector; + if (injector == null) { + throw StateError('inject<$T>(): no Modular app has been bootstrapped yet.'); + } + return injector.get(); +} + +/// Result of bootstrapping a root module: the route tree, the resolution +/// [injector], and the [manager] that drives per-module bind lifecycle. +class ModularBootstrap { + ModularBootstrap(this.routes, this.manager); + + final RouteCollection routes; + final ModuleManager manager; + + /// The resolution entry point (sees every module's binds via the graph). + AutoInjector get injector => manager.root; +} + +/// Walks a root [Module], collecting its routes (tagged with their owning +/// feature modules) and binds. Root-owned binds (path-less modules) are +/// committed eagerly; feature binds (a module with a `path`) are bound lazily +/// on entry and disposed on exit. +ModularBootstrap bootstrapModule(Module root) { + final manager = ModuleManager(); + final topLevel = []; + final ctx = _ContextImpl( + routes: topLevel, + manager: manager, + seen: {root}, + ownerTags: const [], + collect: null, // root-owned: binds applied to root + ); + root.register(ctx); + manager.commitRoot(); + _activeInjector = manager.root; // backs `inject()` + + final collection = RouteCollection(); + for (final route in topLevel) { + collection.add(route); + } + return ModularBootstrap(collection, manager); +} + +/// Owns the resolution [root] injector and drives per-FEATURE-module bind +/// lifecycle: a feature module's binds are bound (its tagged injector created +/// and composed in) when its first route enters the stack, and disposed (via +/// `disposeInjectorByTag`) when its last route leaves — mirroring how the +/// "active path list" worked in flutter_modular 6.x. +class ModuleManager { + ModuleManager() : root = AutoInjector(tag: 'modular-root'); + + /// The resolution entry point. Every module (root-owned or feature) composes + /// into this graph, so `get()` from here sees them all. + final AutoInjector root; + + final Map> _featureBinds = {}; + final Map> _active = {}; + + void registerFeature(String tag, List binds) { + _featureBinds[tag] = binds; + _active.putIfAbsent(tag, () => {}); + } + + void commitRoot() => root.commit(); + + /// A route instance ([id]) owned by [tags] entered the stack. + void enter(String id, List tags) { + for (final tag in tags) { + final active = _active[tag]; + if (active == null) continue; // root-owned, not tracked + if (active.isEmpty) _bind(tag); + active.add(id); + } + } + + /// A route instance ([id]) owned by [tags] left the stack. + void leave(String id, List tags) { + for (final tag in tags) { + final active = _active[tag]; + if (active == null) continue; + active.remove(id); + if (active.isEmpty) _unbind(tag); + } + } + + void _bind(String tag) { + final binds = _featureBinds[tag]; + if (binds == null || binds.isEmpty) return; + final injector = AutoInjector(tag: tag); + for (final apply in binds) { + apply(injector); + } + root + ..uncommit() + ..addInjector(injector) + ..commit(); + } + + void _unbind(String tag) => root.disposeInjectorByTag(tag, _disposeInstance); + + void _disposeInstance(dynamic instance) { + if (instance is ChangeNotifier) { + instance.dispose(); + } else if (instance is Disposable) { + instance.dispose(); + } + } +} + +class _ContextImpl implements ModularContext { + _ContextImpl({ + required List routes, + required ModuleManager manager, + required Set seen, + required List ownerTags, + required List? collect, + String prefix = '', + }) : _routes = routes, + _manager = manager, + _seen = seen, + _ownerTags = ownerTags, + _collect = collect, + _prefix = prefix; + + final List _routes; + final ModuleManager _manager; + final Set _seen; + final List _ownerTags; + + /// The full path prefix at which this context's routes will live — the base + /// for a mounted feature's lifecycle tag. + final String _prefix; + + /// When non-null, `add*` calls are COLLECTED here (a feature module, bound + /// lazily) instead of applied to the root injector (root-owned). + final List? _collect; + + @override + void route( + String path, { + required ModularWidgetBuilder child, + void Function(Scoped scoped)? provide, + void Function(ModularContext c)? children, + List? guards, + TransitionType transition = TransitionType.material, + }) { + var nested = const []; + if (children != null) { + final sub = _sub([]); + children(sub); + nested = sub._routes; + } + _routes.add( + ModularRoute( + path, + child, + provide: provide, + children: nested, + guards: guards ?? const [], + transition: transition, + ownerTags: _ownerTags, + ), + ); + } + + @override + void module(Module module, {String? at}) { + if (!_seen.add(module)) return; // dedup by IDENTITY + final path = at ?? module.path; + assert(_isValidMountPath(path), _mountPathError(path)); + if (path == null) { + // Shared DI module: its binds are ROOT-OWNED (never disposed) even when + // included inside a feature; its routes (if any) sit at this level. + module.register(_rootOwned()); + return; + } + // FEATURE: flatten the submodule's routes under [path] (no RouterOutlet — + // use one explicitly for a shell) and feature-scope its binds under the + // FULL mounted path, so they dispose when its last route leaves. + final tag = _joinPath(_prefix, path); + final featureBinds = []; + final sub = _ContextImpl( + routes: [], + manager: _manager, + seen: _seen, + ownerTags: [..._ownerTags, tag], + collect: featureBinds, + prefix: tag, + ); + module.register(sub); + _manager.registerFeature(tag, featureBinds); + for (final route in sub._routes) { + _routes.add(_prefixed(path, route)); + } + } + + /// A view of this context whose `add*` go to the ROOT injector (shared, never + /// disposed) while routes still land here. + _ContextImpl _rootOwned() => _ContextImpl( + routes: _routes, + manager: _manager, + seen: _seen, + ownerTags: _ownerTags, + collect: null, + prefix: _prefix, + ); + + _ContextImpl _sub(List routes) => _ContextImpl( + routes: routes, + manager: _manager, + seen: _seen, + ownerTags: _ownerTags, + collect: _collect, + prefix: _prefix, + ); + + ModularRoute _prefixed(String at, ModularRoute route) => ModularRoute( + _joinPath(at, route.path), + route.builder, + provide: route.provide, + children: route.children, // stay relative — matched hierarchically + guards: route.guards, + transition: route.transition, + ownerTags: route.ownerTags, + ); + + String _joinPath(String at, String path) { + final segments = [ + ...at.split('/').where((s) => s.isNotEmpty), + ...path.split('/').where((s) => s.isNotEmpty), + ]; + return segments.isEmpty ? '/' : '/${segments.join('/')}'; + } + + @override + void add(Function constructor) => _register((i) => i.add(constructor)); + + @override + void addSingleton(Function constructor) => + _register((i) => i.addSingleton(constructor)); + + @override + void addLazySingleton(Function constructor) => + _register((i) => i.addLazySingleton(constructor)); + + @override + void addInstance(T instance) => + _register((i) => i.addInstance(instance)); + + void _register(void Function(AutoInjector) apply) { + final collect = _collect; + if (collect != null) { + collect.add(apply); // feature: applied lazily at bind time + } else { + apply(_manager.root); // root-owned: applied now + } + } +} diff --git a/lib/src/navigation/modular_navigation.dart b/lib/src/navigation/modular_navigation.dart new file mode 100644 index 00000000..77c59017 --- /dev/null +++ b/lib/src/navigation/modular_navigation.dart @@ -0,0 +1,212 @@ +import 'package:flutter/widgets.dart'; + +import '../route/route_state.dart'; +import 'modular_router_delegate.dart'; +import 'outlet.dart'; +import 'route_resolver.dart'; + +/// Navigation from any widget. +/// +/// `pushNamed` targets the nearest enclosing [RouterOutlet] (so the parent +/// shell persists); if there is none, it pushes the root delegate's stack. It +/// returns a `Future` that completes with the value passed to the matching +/// `pop(result)`. Pass an arbitrary object with `arguments:` and read it back +/// from `RouteState.arguments`. +/// +/// Paths may be RELATIVE to the route this context sits on: `dashboard` or +/// `./dashboard` from `/home` both target `/home/dashboard`, and `../x` climbs +/// a level. A leading `/` makes it absolute. See [resolveRoute]. +extension ModularNavigationX on BuildContext { + /// Resolves a relative [path] against the route this context is on; absolute + /// paths (leading `/`) pass through unchanged. + String _resolve(String path) => + resolveRoute(path, locationOf(this) ?? Uri.parse('/')).toString(); + + /// The app's current route as a full [RouteState] — `uri` (the stack base, + /// refined by the active [RouterOutlet]), resolved path `params`, and the + /// URL-base route's `arguments`. + /// + /// Reactive by default: a widget that reads this rebuilds when the app + /// navigates, so route-aware chrome (a shell's active-tab highlight, + /// breadcrumbs) DERIVES from the route instead of mirroring it in local + /// state. Pass `listen: false` to read without subscribing (e.g. in a + /// callback). Throws if there is no `ModularApp` / router above this context. + /// + /// Note: a widget INSIDE the active route subtree sees the same state as its + /// `builder`'s `(ctx, state)`; this also reaches chrome OUTSIDE that subtree + /// (beside an outlet), which the builder arg cannot. `arguments` here is the + /// URL-base route's — nested-outlet / pushed (modal-like) args stay out of + /// the URL by design and are read from their own builder's state. + RouteState routeState({bool listen = true}) { + final state = RouteStateScope.of(this, listen: listen); + if (state == null) { + throw FlutterError( + 'context.routeState: no ModularRouterDelegate found in the tree.', + ); + } + return state; + } + + Future pushNamed(String path, {Object? arguments}) { + final target = _resolve(path); + final outlet = RouterOutlet.of(this); + if (outlet != null && outlet.handles(target)) { + return outlet.push(target, arguments: arguments); + } + final delegate = Router.of(this).routerDelegate; + if (delegate is ModularRouterDelegate) { + return delegate.pushNamed(target, arguments: arguments); + } + throw FlutterError( + 'context.pushNamed: no outlet or ModularRouterDelegate found.', + ); + } + + /// Whether [pop] would do something — the nearest [RouterOutlet] or the root + /// delegate has a route to pop. Use it to show a back button on a mounted + /// module's INDEX page: that page is the sole entry of its outlet's nested + /// Navigator, so `AppBar(automaticallyImplyLeading:)` can't see the root + /// stack underneath and shows no arrow on its own. + bool canPop() { + final outlet = RouterOutlet.of(this); + if (outlet != null && outlet.canPop) return true; + final delegate = Router.of(this).routerDelegate; + if (delegate is ModularRouterDelegate && delegate.canPop) return true; + return Navigator.of(this).canPop(); + } + + /// Pops the current route with an optional [result] delivered to the + /// `pushNamed` future. Targets the nearest poppable [RouterOutlet], else the + /// root delegate, else the nearest [Navigator]. + void pop([T? result]) { + final outlet = RouterOutlet.of(this); + if (outlet != null && outlet.canPop) { + outlet.pop(result); + return; + } + final delegate = Router.of(this).routerDelegate; + if (delegate is ModularRouterDelegate && delegate.canPop) { + delegate.pop(result); + return; + } + Navigator.of(this).pop(result); + } + + /// Pops the current route only if possible; returns whether it did. + bool maybePop([T? result]) { + final outlet = RouterOutlet.of(this); + if (outlet != null && outlet.canPop) return outlet.maybePop(result); + final delegate = Router.of(this).routerDelegate; + if (delegate is ModularRouterDelegate) return delegate.maybePop(result); + return false; + } + + /// REPLACES the target's whole stack with [path] (resets history). Targets + /// the nearest [RouterOutlet] whose subtree owns [path] — so in a shell it + /// swaps the body (tab switch) — otherwise the root delegate (reset the app). + /// This is the v7 unification of 6.x's `navigate` + `pushNavigate`. + void navigate(String path, {Object? arguments}) { + final target = _resolve(path); + final outlet = RouterOutlet.of(this); + if (outlet != null && outlet.handles(target)) { + outlet.navigate(target, arguments: arguments); + return; + } + final delegate = Router.of(this).routerDelegate; + if (delegate is ModularRouterDelegate) { + delegate.navigate(target, arguments: arguments); + return; + } + throw FlutterError( + 'context.navigate: no outlet or ModularRouterDelegate found.', + ); + } + + /// Replaces the TOP route with [path] (no back to the replaced one); the + /// returned `Future` completes with the new route's pop result. + Future replace(String path, {Object? arguments}) { + final target = _resolve(path); + final outlet = RouterOutlet.of(this); + if (outlet != null && outlet.handles(target)) { + return outlet.replace(target, arguments: arguments); + } + final delegate = Router.of(this).routerDelegate; + if (delegate is ModularRouterDelegate) { + return delegate.replace(target, arguments: arguments); + } + throw FlutterError( + 'context.replace: no outlet or ModularRouterDelegate found.', + ); + } + + /// Pops repeatedly until [predicate] holds for the top route. Targets the + /// nearest poppable [RouterOutlet], else the root delegate. + void popUntil(bool Function(RouteState state) predicate) { + final outlet = RouterOutlet.of(this); + if (outlet != null && outlet.canPop) { + outlet.popUntil(predicate); + return; + } + final delegate = Router.of(this).routerDelegate; + if (delegate is ModularRouterDelegate) delegate.popUntil(predicate); + } + + /// Pops the current route delivering [result] to its `pushNamed` future, then + /// pushes [path]. Dispatches like [pushNamed]. + Future popAndPushNamed( + String path, { + Object? result, + Object? arguments, + }) { + final target = _resolve(path); + final outlet = RouterOutlet.of(this); + if (outlet != null && outlet.handles(target)) { + return outlet.popAndPushNamed( + target, + result: result, + arguments: arguments, + ); + } + final delegate = Router.of(this).routerDelegate; + if (delegate is ModularRouterDelegate) { + return delegate.popAndPushNamed( + target, + result: result, + arguments: arguments, + ); + } + throw FlutterError( + 'context.popAndPushNamed: no outlet or ModularRouterDelegate found.', + ); + } + + /// Pushes [path], then removes the routes beneath it until [predicate] holds. + /// Dispatches like [pushNamed]. + Future pushNamedAndRemoveUntil( + String path, + bool Function(RouteState state) predicate, { + Object? arguments, + }) { + final target = _resolve(path); + final outlet = RouterOutlet.of(this); + if (outlet != null && outlet.handles(target)) { + return outlet.pushNamedAndRemoveUntil( + target, + predicate, + arguments: arguments, + ); + } + final delegate = Router.of(this).routerDelegate; + if (delegate is ModularRouterDelegate) { + return delegate.pushNamedAndRemoveUntil( + target, + predicate, + arguments: arguments, + ); + } + throw FlutterError( + 'context.pushNamedAndRemoveUntil: no outlet or ModularRouterDelegate ' + 'found.', + ); + } +} diff --git a/lib/src/navigation/modular_route_information_parser.dart b/lib/src/navigation/modular_route_information_parser.dart new file mode 100644 index 00000000..6d1d1008 --- /dev/null +++ b/lib/src/navigation/modular_route_information_parser.dart @@ -0,0 +1,20 @@ +import 'package:flutter/widgets.dart'; + +import '../route/route_state.dart'; + +/// Translates between the platform's [RouteInformation] (the URL) and our +/// [RouteState]. Step 1 is a straight pass-through; route matching happens in +/// the delegate. Uses the modern `uri` API (no deprecated `location`). +class ModularRouteInformationParser extends RouteInformationParser { + @override + Future parseRouteInformation( + RouteInformation routeInformation, + ) async { + return RouteState(uri: routeInformation.uri); + } + + @override + RouteInformation restoreRouteInformation(RouteState configuration) { + return RouteInformation(uri: configuration.uri); + } +} diff --git a/lib/src/navigation/modular_router_config.dart b/lib/src/navigation/modular_router_config.dart new file mode 100644 index 00000000..dbdf8e89 --- /dev/null +++ b/lib/src/navigation/modular_router_config.dart @@ -0,0 +1,54 @@ +import 'package:auto_injector/auto_injector.dart'; +import 'package:flutter/widgets.dart'; + +import '../module/module.dart'; +import '../route/modular_route.dart'; +import '../route/route_state.dart'; +import 'modular_route_information_parser.dart'; +import 'modular_router_delegate.dart'; + +/// Wires the Navigator 2.0 pieces into a [RouterConfig] for +/// `MaterialApp.router(routerConfig: ...)`. +/// +/// Pass the [injector] (from `bootstrapModule`) when routes use `provide`, so +/// page-scoped view models can resolve their dependencies. Defaults to an empty +/// injector when omitted. +RouterConfig modularRouterConfig( + RouteCollection routes, { + AutoInjector? injector, + ModuleManager? manager, + String initialRoute = '/', + GlobalKey? navigatorKey, + List observers = const [], +}) { + final inj = injector ?? (AutoInjector()..commit()); + return RouterConfig( + routerDelegate: ModularRouterDelegate( + routes, + inj, + manager: manager, + navigatorKey: navigatorKey, + observers: observers, + ), + routeInformationParser: ModularRouteInformationParser(), + routeInformationProvider: PlatformRouteInformationProvider( + initialRouteInformation: RouteInformation(uri: _initialUri(initialRoute)), + ), + backButtonDispatcher: RootBackButtonDispatcher(), + ); +} + +/// The route to resolve on first build. +/// +/// On the web (and anywhere the app boots from a deep link), the platform +/// hands us the real entry URL via `PlatformDispatcher.defaultRouteName` — e.g. +/// `/dashboard` after a refresh. We MUST honor it: `PlatformRouteInformationProvider` +/// seeds the `Router` from the `initialRouteInformation` we pass and does NOT +/// consult `defaultRouteName` itself, so hardcoding `/` here is exactly what +/// sends every deep link back to the root. When the platform reports the bare +/// `/` (no deep link), the app's configured [initialRoute] wins. +Uri _initialUri(String initialRoute) { + final platformDefault = + WidgetsBinding.instance.platformDispatcher.defaultRouteName; + return Uri.parse(platformDefault == '/' ? initialRoute : platformDefault); +} diff --git a/lib/src/navigation/modular_router_delegate.dart b/lib/src/navigation/modular_router_delegate.dart new file mode 100644 index 00000000..a1da9012 --- /dev/null +++ b/lib/src/navigation/modular_router_delegate.dart @@ -0,0 +1,351 @@ +import 'dart:async'; + +import 'package:auto_injector/auto_injector.dart'; +import 'package:flutter/material.dart'; + +import '../module/module.dart'; +import '../route/modular_route.dart'; +import '../route/route_state.dart'; +import 'outlet.dart'; +import 'transition.dart'; + +/// Drives the Navigator 2.0 page STACK. Each page renders a matched route +/// CHAIN (parent shells + nested children via `RouterOutlet`), applies route +/// [ModularRoute.guards] (redirects), and uses each route's transition. +/// +/// `pushNamed` returns a `Future` that completes with the value passed to the +/// matching `pop(result)`. It also reports each entry to the [ModuleManager], +/// which binds a feature module on its first active route and disposes it when +/// its last route leaves. +class ModularRouterDelegate extends RouterDelegate + with ChangeNotifier, PopNavigatorRouterDelegateMixin { + ModularRouterDelegate( + this.routes, + this.injector, { + this.manager, + GlobalKey? navigatorKey, + this.observers = const [], + }) : navigatorKey = navigatorKey ?? GlobalKey(); + + final RouteCollection routes; + final AutoInjector injector; + final ModuleManager? manager; + + /// Observers attached to the root [Navigator] (analytics, a `RouteObserver` + /// for route-aware widgets, etc.). + final List observers; + + /// The root [Navigator]'s key — caller-supplied (for imperative access from + /// outside the tree) or a fresh one. `PopNavigatorRouterDelegateMixin` uses + /// it to route system back through this delegate. + @override + final GlobalKey navigatorKey; + + final List<_StackEntry> _stack = []; + int _seq = 0; + + /// The active nested [RouterOutlet]'s current sub-route (its tab base), + /// reported on a tab switch. When set, the URL shows THIS instead of just the + /// root base — so an outlet is reflected in the URL. Reset when the root base + /// is replaced. See [reportNestedLocation]. + Uri? _nestedLocation; + + /// Whether there is a route above the root to pop. + bool get canPop => _stack.length > 1; + + /// The browser URL mirrors the stack's BASE (`_stack.first`), NOT its top: + /// `navigate` (which resets the base) owns the URL, while `pushNamed` layers + /// pages that stay OUT of the URL — modal-like, lost on refresh by design. + /// A nested [RouterOutlet] refines it with its current sub-route, so a tab + /// switch shows in the URL too (see [reportNestedLocation]). + @override + RouteState? get currentConfiguration { + if (_stack.isEmpty) return null; + final base = _stack.first.state; + return _nestedLocation == null ? base : base.copyWith(uri: _nestedLocation); + } + + /// The effective current route as a FULL [RouteState] — what reactive + /// consumers read via `context.routeState()`. + /// + /// Same `uri` as [currentConfiguration] (the stack base, refined by the + /// active outlet), but with path `params` RESOLVED: the stored stack entry + /// only carries the raw uri + arguments — params are merged at render time — + /// so this re-matches the URI against the route tree and merges every level's + /// params. `arguments` is the URL-base route's: nested-outlet sub-routes and + /// pushed (modal-like) pages stay OUT of the URL by design, so their args are + /// reached through their own builder's `(ctx, state)`, not here. + RouteState? currentRouteState() { + final config = currentConfiguration; + if (config == null) return null; + final chain = routes.match(config.uri); + if (chain == null) return config; + final params = {}; + for (final level in chain) { + params.addAll(level.params); + } + return config.copyWith(params: params); + } + + /// Called by a [RouterOutlet] when its base sub-route changes (a tab switch), + /// so the URL reflects the outlet — not just the root stack's base. A push + /// INSIDE the outlet leaves the tab base unchanged, so it reports the same + /// value and stays out of the URL, consistent with root pushes. + void reportNestedLocation(Uri uri) { + if (_nestedLocation == uri) return; + _nestedLocation = uri; + notifyListeners(); + } + + @override + Future setNewRoutePath(RouteState configuration) async { + // Already here: compare the EFFECTIVE location (including a nested outlet), + // so the platform echoing back an outlet-driven URL doesn't rebuild and + // remount the shell. Empty stack → null → falls through to build the first. + if (currentConfiguration?.uri == configuration.uri) return; + _nestedLocation = null; // base is being replaced → drop stale outlet loc + for (final entry in _stack) { + _detach(entry, null); + } + _stack.clear(); + _push(_entry(_applyGuards(configuration))); + notifyListeners(); + } + + /// Pushes [path] onto the stack; completes when its `pop(result)` is called. + /// Always stacks (push is unbounded) — even a guard redirect just pushes the + /// resolved route. Deduping/replacing is `navigate`'s and `replace`'s job. + Future pushNamed(String path, {Object? arguments}) { + final entry = _entry( + _applyGuards(RouteState(uri: Uri.parse(path), arguments: arguments)), + ); + _push(entry); + notifyListeners(); + return entry.completer.future.then((value) => value as T?); + } + + /// Pops the top route, completing its `pushNamed` future with [result]. + /// DECLARATIVE: removes the entry from the page list — the Navigator animates + /// the exit. (The AppBar arrow / system back reach the same `_remove` through + /// `onDidRemovePage`.) + void pop([T? result]) { + if (!canPop) return; + _remove(_stack.last.key, result); + } + + /// Pops if possible; returns whether it did. + bool maybePop([T? result]) { + if (!canPop) return false; + _remove(_stack.last.key, result); + return true; + } + + /// Replaces the WHOLE stack with [path], resetting history — the URL-changing + /// "navigate" verb (`pushNamed` stacks instead). + void navigate(String path, {Object? arguments}) { + _nestedLocation = null; // base is being replaced → drop stale outlet loc + for (final entry in _stack) { + _detach(entry, null); + } + _stack.clear(); + _push( + _entry( + _applyGuards(RouteState(uri: Uri.parse(path), arguments: arguments)), + ), + ); + notifyListeners(); + } + + /// Replaces the TOP route with [path] (no back to the replaced one). + Future replace(String path, {Object? arguments}) { + if (_stack.isNotEmpty) _detach(_stack.removeLast(), null); + final entry = _entry( + _applyGuards(RouteState(uri: Uri.parse(path), arguments: arguments)), + ); + _push(entry); + notifyListeners(); + return entry.completer.future.then((value) => value as T?); + } + + /// Pops until [predicate] holds for the top route (or one route remains). + void popUntil(bool Function(RouteState state) predicate) { + var changed = false; + while (_stack.length > 1 && !predicate(_stack.last.state)) { + _detach(_stack.removeLast(), null); + changed = true; + } + if (changed) notifyListeners(); + } + + /// Pops the top route delivering [result] to its `pushNamed` future, then + /// pushes [path]. + Future popAndPushNamed( + String path, { + Object? result, + Object? arguments, + }) { + if (_stack.isNotEmpty) _detach(_stack.removeLast(), result); + final entry = _entry( + _applyGuards(RouteState(uri: Uri.parse(path), arguments: arguments)), + ); + _push(entry); + notifyListeners(); + return entry.completer.future.then((value) => value as T?); + } + + /// Pushes [path], then removes the routes BENEATH it until [predicate] holds + /// (or only the new route remains). + Future pushNamedAndRemoveUntil( + String path, + bool Function(RouteState state) predicate, { + Object? arguments, + }) { + final entry = _entry( + _applyGuards(RouteState(uri: Uri.parse(path), arguments: arguments)), + ); + _push(entry); + while (_stack.length > 1 && !predicate(_stack[_stack.length - 2].state)) { + _detach(_stack.removeAt(_stack.length - 2), null); + } + notifyListeners(); + return entry.completer.future.then((value) => value as T?); + } + + void _push(_StackEntry entry) { + _stack.add(entry); + manager?.enter(entry.id, entry.ownerTags); + } + + void _remove(Key? key, Object? result) { + final index = _stack.indexWhere((e) => e.key == key); + if (index == -1) return; + _detach(_stack.removeAt(index), result); + notifyListeners(); + } + + void _detach(_StackEntry entry, Object? result) { + manager?.leave(entry.id, entry.ownerTags); + if (!entry.completer.isCompleted) entry.completer.complete(result); + } + + /// Runs guards along the matched chain; follows redirects (bounded loop). + RouteState _applyGuards(RouteState state) { + var current = state; + for (var hop = 0; hop < 8; hop++) { + final chain = routes.match(current.uri); + if (chain == null) return current; + + String? redirect; + for (final level in chain) { + for (final guard in level.route.guards) { + redirect = guard(current); + if (redirect != null) break; + } + if (redirect != null) break; + } + if (redirect == null) return current; + current = RouteState(uri: Uri.parse(redirect)); + } + return current; + } + + _StackEntry _entry(RouteState state) { + final id = 'route-${_seq++}'; + final tags = routes.match(state.uri)?.last.route.ownerTags ?? const []; + return _StackEntry(state, ValueKey(id), id, Completer(), tags); + } + + @override + Widget build(BuildContext context) { + if (_stack.isEmpty) return const SizedBox.shrink(); + + // Wrap the root Navigator so any descendant — INCLUDING chrome that sits + // beside an outlet (a shell's bottom bar) — can read the current route via + // `context.routeState()` and rebuild when the app navigates. The delegate + // IS the notifier: it already `notifyListeners()`es on every nav change. + return RouteStateScope( + delegate: this, + child: Navigator( + key: navigatorKey, + observers: observers, + // `onPopPage` (v6-style, SYNCHRONOUS) instead of `onDidRemovePage`: it + // fires at the pop REQUEST, so the page list updates immediately rather + // than via the deferred did-remove callback — the suspect for the + // fast-multi-pop flicker. Imperative routes (dialogs/sheets) are left to + // the Navigator: only our Page-backed routes touch `_stack`. + // ignore: deprecated_member_use + onPopPage: (route, result) { + if (!route.didPop(result)) return false; + final settings = route.settings; + if (settings is Page) _remove(settings.key, result); + return true; + }, + pages: [for (final entry in _stack) _buildPage(entry)], + ), + ); + } + + Page _buildPage(_StackEntry entry) { + final chain = routes.match(entry.state.uri); + if (chain == null || chain.isEmpty) { + return MaterialPage(key: entry.key, child: const _RouteNotFound()); + } + + final child = buildRouteLevel( + chain: chain, + index: 0, + injector: injector, + uri: entry.state.uri, + params: const {}, + arguments: entry.state.arguments, + routes: routes, + ); + return buildTransitionPage(chain.first.route.transition, entry.key, child); + } +} + +class _StackEntry { + _StackEntry(this.state, this.key, this.id, this.completer, this.ownerTags); + final RouteState state; + final LocalKey key; + final String id; + final Completer completer; + final List ownerTags; +} + +class _RouteNotFound extends StatelessWidget { + const _RouteNotFound(); + + @override + Widget build(BuildContext context) { + return const Material(child: Center(child: Text('Route not found'))); + } +} + +/// Exposes the [ModularRouterDelegate] (a [ChangeNotifier]) to the subtree so a +/// widget can read the current [RouteState] reactively via +/// `context.routeState()` — rebuilding when the app navigates. Installed once +/// around the root Navigator in [ModularRouterDelegate.build]; not exported +/// (the public surface is the `context.routeState()` extension). +class RouteStateScope extends InheritedNotifier { + const RouteStateScope({ + required ModularRouterDelegate delegate, + required super.child, + super.key, + }) : super(notifier: delegate); + + /// The effective current [RouteState], or `null` above any + /// [ModularRouterDelegate]. Subscribes (rebuilds on navigation) unless + /// [listen] is false. + static RouteState? of(BuildContext context, {bool listen = true}) { + final RouteStateScope? scope; + if (listen) { + scope = context.dependOnInheritedWidgetOfExactType(); + } else { + final element = context + .getElementForInheritedWidgetOfExactType(); + scope = element?.widget as RouteStateScope?; + } + return scope?.notifier?.currentRouteState(); + } +} diff --git a/lib/src/navigation/outlet.dart b/lib/src/navigation/outlet.dart new file mode 100644 index 00000000..b37643a1 --- /dev/null +++ b/lib/src/navigation/outlet.dart @@ -0,0 +1,330 @@ +import 'dart:async'; + +import 'package:auto_injector/auto_injector.dart'; +import 'package:flutter/material.dart'; + +import '../route/modular_route.dart'; +import '../route/route_state.dart'; +import '../state/scoped.dart'; +import 'modular_router_delegate.dart'; +import 'transition.dart'; + +/// Builds one level of a matched route chain: the route's page (with its +/// page-scoped `provide`), wrapped in an [_OutletScope] that carries the +/// REMAINING chain — and any [arguments] — to any [RouterOutlet] inside it. +Widget buildRouteLevel({ + required List chain, + required int index, + required AutoInjector injector, + required Uri uri, + required Map params, + required RouteCollection routes, + Object? arguments, +}) { + final level = chain[index]; + final merged = {...params, ...level.params}; + final state = RouteState(uri: uri, params: merged, arguments: arguments); + + Widget page = Builder(builder: (ctx) => level.route.builder(ctx, state)); + final provide = level.route.provide; + if (provide != null) { + page = ScopedHost(provide: provide, parent: injector, child: page); + } + + return _OutletScope( + chain: chain, + index: index + 1, + injector: injector, + uri: uri, + params: merged, + arguments: arguments, + routes: routes, + child: page, + ); +} + +class _OutletScope extends InheritedWidget { + const _OutletScope({ + required this.chain, + required this.index, + required this.injector, + required this.uri, + required this.params, + required this.routes, + required super.child, + this.arguments, + }); + + final List chain; + final int index; + final AutoInjector injector; + final Uri uri; + final Map params; + final Object? arguments; + final RouteCollection routes; + + static _OutletScope? of(BuildContext context) => + context.dependOnInheritedWidgetOfExactType<_OutletScope>(); + + @override + bool updateShouldNotify(_OutletScope oldWidget) => + index != oldWidget.index || !identical(chain, oldWidget.chain); +} + +/// The URI of the route subtree [context] sits in — the base for resolving +/// relative navigation (`context.pushNamed('dashboard')`). Reads WITHOUT +/// subscribing (no rebuild on change); `null` above any route. Inside a +/// [RouterOutlet] this is the outlet's current sub-route, so relatives stay +/// scoped to the shell. +Uri? locationOf(BuildContext context) { + final element = context + .getElementForInheritedWidgetOfExactType<_OutletScope>(); + return (element?.widget as _OutletScope?)?.uri; +} + +/// Renders the child route(s) of the current level in a REAL nested +/// [Navigator] — with its own push/pop sub-stack. Calling `context.pushNamed` +/// from inside an outlet targets THIS outlet, so the parent shell persists, and +/// its returned `Future` completes with the value passed to `pop(result)`. +/// Scope nests through it: a child's `context.watch` reaches the parent's VMs. +class RouterOutlet extends StatefulWidget { + const RouterOutlet({super.key}); + + /// The nearest enclosing outlet, for navigation targeting. + static RouterOutletState? of(BuildContext context) { + final element = context + .getElementForInheritedWidgetOfExactType<_OutletController>(); + return (element?.widget as _OutletController?)?.state; + } + + @override + State createState() => RouterOutletState(); +} + +class RouterOutletState extends State { + late _OutletScope _scope; + Uri? _seedUri; + final List<_OutletEntry> _stack = []; + int _seq = 0; + + /// Whether this outlet has a sub-route above its seed to pop. + bool get canPop => _stack.length > 1; + + /// Whether [path] resolves WITHIN this outlet's subtree — i.e. its matched + /// chain shares this outlet's ancestor prefix and has a level at this + /// outlet's depth. If not (e.g. a sibling top-level route pushed from a + /// module mounted at `/`), the push must bubble up to the root delegate. + bool handles(String path) { + final chain = _scope.routes.match(Uri.parse(path)); + if (chain == null || chain.length <= _scope.index) return false; + for (var i = 0; i < _scope.index; i++) { + if (!identical(chain[i].route, _scope.chain[i].route)) return false; + } + return true; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _scope = _OutletScope.of(context)!; + // (Re)seed the sub-stack when the top route (the scope URL) changes. + if (_seedUri != _scope.uri) { + _seedUri = _scope.uri; + for (final e in _stack) { + if (!e.completer.isCompleted) e.completer.complete(null); + } + _stack + ..clear() + ..add(_entry(_scope.uri, _scope.arguments)); + } + } + + _OutletEntry _entry(Uri uri, Object? arguments) => _OutletEntry( + uri, + ValueKey('outlet-${identityHashCode(this)}-${_seq++}'), + Completer(), + arguments, + ); + + /// Pushes [path] onto THIS outlet's sub-stack (the parent shell persists); + /// the returned future completes with the value passed to `pop(result)`. + Future push(String path, {Object? arguments}) { + final entry = _entry(Uri.parse(path), arguments); + setState(() => _stack.add(entry)); + _reportLocation(); + return entry.completer.future.then((value) => value as T?); + } + + /// Pops this outlet's top sub-route, completing its `push` future. + void pop([T? result]) { + if (!canPop) return; + _remove(_stack.last.key, result); + } + + /// Pops this outlet's top sub-route if possible; returns whether it did. + bool maybePop([T? result]) { + if (!canPop) return false; + _remove(_stack.last.key, result); + return true; + } + + /// Replaces this outlet's WHOLE sub-stack with [path] — the shell "navigate" + /// (a bottom-bar tab switch swaps the body without stacking history). + void navigate(String path, {Object? arguments}) { + for (final entry in _stack) { + if (!entry.completer.isCompleted) entry.completer.complete(null); + } + setState(() { + _stack + ..clear() + ..add(_entry(Uri.parse(path), arguments)); + }); + _reportLocation(); + } + + /// Replaces this outlet's TOP sub-route with [path]. + Future replace(String path, {Object? arguments}) { + if (_stack.isNotEmpty) { + final top = _stack.removeLast(); + if (!top.completer.isCompleted) top.completer.complete(null); + } + final entry = _entry(Uri.parse(path), arguments); + setState(() => _stack.add(entry)); + _reportLocation(); + return entry.completer.future.then((value) => value as T?); + } + + /// Pops this outlet's sub-stack until [predicate] holds (or one remains). + void popUntil(bool Function(RouteState state) predicate) { + var changed = false; + while (_stack.length > 1 && + !predicate( + RouteState(uri: _stack.last.uri, arguments: _stack.last.arguments), + )) { + final top = _stack.removeLast(); + if (!top.completer.isCompleted) top.completer.complete(null); + changed = true; + } + if (changed && mounted) setState(() {}); + _reportLocation(); + } + + /// Pops this outlet's top sub-route delivering [result], then pushes [path]. + Future popAndPushNamed( + String path, { + Object? result, + Object? arguments, + }) { + if (_stack.isNotEmpty) { + final top = _stack.removeLast(); + if (!top.completer.isCompleted) top.completer.complete(result); + } + final entry = _entry(Uri.parse(path), arguments); + setState(() => _stack.add(entry)); + _reportLocation(); + return entry.completer.future.then((value) => value as T?); + } + + /// Pushes [path] onto this outlet, then removes the sub-routes BENEATH it + /// until [predicate] holds (or only the new one remains). + Future pushNamedAndRemoveUntil( + String path, + bool Function(RouteState state) predicate, { + Object? arguments, + }) { + final entry = _entry(Uri.parse(path), arguments); + _stack.add(entry); + while (_stack.length > 1 && + !predicate( + RouteState( + uri: _stack[_stack.length - 2].uri, + arguments: _stack[_stack.length - 2].arguments, + ), + )) { + final removed = _stack.removeAt(_stack.length - 2); + if (!removed.completer.isCompleted) removed.completer.complete(null); + } + setState(() {}); + _reportLocation(); + return entry.completer.future.then((value) => value as T?); + } + + /// Reports this outlet's current base sub-route to the root delegate so the + /// URL reflects the outlet (a tab switch shows up). A push/pop leaves the + /// base unchanged, so it reports the same value and stays out of the URL. + void _reportLocation() { + if (_stack.isEmpty) return; + final delegate = Router.maybeOf(context)?.routerDelegate; + if (delegate is ModularRouterDelegate) { + delegate.reportNestedLocation(_stack.first.uri); + } + } + + void _remove(Key? key, Object? result) { + final index = _stack.indexWhere((e) => e.key == key); + if (index == -1) return; + final entry = _stack.removeAt(index); + if (!entry.completer.isCompleted) entry.completer.complete(result); + if (mounted) setState(() {}); + _reportLocation(); + } + + @override + Widget build(BuildContext context) { + if (_scope.index >= _scope.chain.length) return const SizedBox.shrink(); + + return _OutletController( + state: this, + child: Navigator( + // ignore: deprecated_member_use + onPopPage: (route, result) { + if (!route.didPop(result)) return false; + final settings = route.settings; + if (settings is Page) _remove(settings.key, result); + return true; + }, + pages: [for (final entry in _stack) _page(entry)], + ), + ); + } + + Page _page(_OutletEntry entry) { + final chain = _scope.routes.match(entry.uri); + if (chain == null || _scope.index >= chain.length) { + return MaterialPage(key: entry.key, child: const SizedBox.shrink()); + } + final child = buildRouteLevel( + chain: chain, + index: _scope.index, + injector: _scope.injector, + uri: entry.uri, + params: _scope.params, + arguments: entry.arguments, + routes: _scope.routes, + ); + return buildTransitionPage( + chain[_scope.index].route.transition, + entry.key, + child, + ); + } +} + +class _OutletEntry { + _OutletEntry(this.uri, this.key, this.completer, this.arguments); + final Uri uri; + final LocalKey key; + final Completer completer; + final Object? arguments; +} + +/// Exposes the nearest [RouterOutletState] to the navigation extension. +class _OutletController extends InheritedWidget { + const _OutletController({required this.state, required super.child}); + + final RouterOutletState state; + + @override + bool updateShouldNotify(_OutletController oldWidget) => + state != oldWidget.state; +} diff --git a/lib/src/navigation/route_resolver.dart b/lib/src/navigation/route_resolver.dart new file mode 100644 index 00000000..b02d9d62 --- /dev/null +++ b/lib/src/navigation/route_resolver.dart @@ -0,0 +1,24 @@ +/// Resolves a possibly-relative route [reference] against the [current] +/// location, treating the current route as a DIRECTORY — Modular's relative +/// routes: +/// +/// - absolute (`/products`) → used as-is, [current] ignored; +/// - bare or dot (`dashboard`, → appended UNDER [current]: from +/// `./dashboard`) `/home` both give `/home/dashboard`; +/// - parent (`../settings`) → climbs one level: from `/home` +/// gives `/settings`. +/// +/// Query and fragment on the [reference] are preserved (`item?ref=x`). +/// +/// This improves on 6.x's raw `Uri.resolve`, which treats `/home` as a FILE and +/// so turns `dashboard` into `/dashboard` (dropping `home`) — surprising when +/// you only meant to go one level deeper. We append a trailing slash to +/// [current] first, so relative references resolve as "inside" it. +Uri resolveRoute(String reference, Uri current) { + final ref = Uri.parse(reference); + // Absolute path reference (`/x`): ignore where we are. + if (ref.hasAbsolutePath) return ref; + // Treat the current location as a directory, then RFC-resolve against it. + final dir = current.path.endsWith('/') ? current.path : '${current.path}/'; + return Uri(path: dir).resolveUri(ref); +} diff --git a/lib/src/navigation/transition.dart b/lib/src/navigation/transition.dart new file mode 100644 index 00000000..1de87bc2 --- /dev/null +++ b/lib/src/navigation/transition.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; + +/// Page transition for a route. +enum TransitionType { material, fade, none } + +/// Builds the [Page] for [child] with the requested [type]. +Page buildTransitionPage( + TransitionType type, + LocalKey key, + Widget child, +) { + switch (type) { + case TransitionType.material: + return MaterialPage(key: key, child: child); + case TransitionType.fade: + return _TransitionPage( + key: key, + child: child, + transitions: (animation, c) => + FadeTransition(opacity: animation, child: c), + ); + case TransitionType.none: + return _TransitionPage( + key: key, + child: child, + duration: Duration.zero, + transitions: (animation, c) => c, + ); + } +} + +class _TransitionPage extends Page { + const _TransitionPage({ + required super.key, + required this.child, + required this.transitions, + this.duration = const Duration(milliseconds: 300), + }); + + final Widget child; + final Widget Function(Animation animation, Widget child) transitions; + final Duration duration; + + @override + Route createRoute(BuildContext context) { + return PageRouteBuilder( + settings: this, + transitionDuration: duration, + reverseTransitionDuration: duration, + pageBuilder: (context, animation, secondary) => child, + transitionsBuilder: (context, animation, secondary, c) => + transitions(animation, c), + ); + } +} diff --git a/lib/src/route/modular_route.dart b/lib/src/route/modular_route.dart new file mode 100644 index 00000000..7fbd5d05 --- /dev/null +++ b/lib/src/route/modular_route.dart @@ -0,0 +1,104 @@ +import 'package:flutter/widgets.dart'; + +import '../navigation/transition.dart'; +import '../state/scoped.dart'; +import 'route_state.dart'; + +/// Builds the widget for a route, receiving the current [RouteState]. +typedef ModularWidgetBuilder = + Widget Function(BuildContext context, RouteState state); + +/// A route guard: returns a redirect path to send the user elsewhere, or +/// `null` to allow navigation. +typedef ModularGuard = String? Function(RouteState state); + +/// A single declared route: a RELATIVE path pattern, a builder, optional +/// page-scoped state ([provide]), nested [children], [guards] and a +/// [transition]. +class ModularRoute { + const ModularRoute( + this.path, + this.builder, { + this.provide, + this.children = const [], + this.guards = const [], + this.transition = TransitionType.material, + this.ownerTags = const [], + }); + + final String path; + final ModularWidgetBuilder builder; + final void Function(Scoped scoped)? provide; + final List children; + final List guards; + final TransitionType transition; + + /// Tags of the feature modules (mounted via `module(at:)`) that own this + /// route. When the LAST active route of a tag leaves the stack, that module's + /// binds are disposed. Empty for root-owned routes. + final List ownerTags; +} + +/// One matched level of a route chain: the route + its captured path params. +class RouteLevel { + const RouteLevel(this.route, this.params); + + final ModularRoute route; + final Map params; +} + +/// The declared route tree + the hierarchical matcher. +class RouteCollection { + final List _routes = []; + + List get routes => List.unmodifiable(_routes); + + void add(ModularRoute route) => _routes.add(route); + + /// Matches [uri] to a chain of routes (root → leaf) with params, or `null`. + List? match(Uri uri) => _matchChain(_routes, _segments(uri.path)); +} + +List? _matchChain( + List routes, + List segments, +) { + for (final route in routes) { + final consumed = _tryConsume(route.path, segments); + if (consumed == null) continue; + + final remaining = segments.sublist(consumed.$1); + final level = RouteLevel(route, consumed.$2); + + if (remaining.isEmpty) { + if (route.children.isEmpty) return [level]; + final index = _matchChain(route.children, const []); + return index == null ? [level] : [level, ...index]; + } + + final childChain = _matchChain(route.children, remaining); + if (childChain != null) return [level, ...childChain]; + } + return null; +} + +/// Matches a pattern (`/product/:id`) against the FRONT of [segments], +/// returning (segments consumed, captured params) or `null`. +(int, Map)? _tryConsume(String pattern, List segments) { + final patternSegments = _segments(pattern); + if (patternSegments.length > segments.length) return null; + + final params = {}; + for (var i = 0; i < patternSegments.length; i++) { + final seg = patternSegments[i]; + if (seg.startsWith(':')) { + params[seg.substring(1)] = segments[i]; + } else if (seg != segments[i]) { + return null; + } + } + return (patternSegments.length, params); +} + +List _segments(String path) => + path.split('/').where((s) => s.isNotEmpty).toList(); diff --git a/lib/src/route/route_state.dart b/lib/src/route/route_state.dart new file mode 100644 index 00000000..ed0ba902 --- /dev/null +++ b/lib/src/route/route_state.dart @@ -0,0 +1,42 @@ +/// Immutable snapshot of the route the app is currently resolving. +/// +/// Passed to every route `builder` as `(context, state)` — à la go_router. +/// Holds the full [uri], the matched path [params] (e.g. `:id`), exposes the +/// query string via [query], and carries any [arguments] passed to +/// `context.pushNamed(path, arguments: ...)`. +class RouteState { + const RouteState({required this.uri, this.params = const {}, this.arguments}); + + /// The full resolved URI (path + query). + final Uri uri; + + /// Path parameters extracted from the matched route (e.g. `{'id': '42'}`). + final Map params; + + /// An arbitrary object passed at navigation time via + /// `context.pushNamed(path, arguments: ...)`. NOT part of the URL, so it is + /// `null` after a deep link / refresh — read it defensively. + final Object? arguments; + + /// Query parameters (`?a=1&b=2`). + Map get query => uri.queryParameters; + + /// Convenience accessor for a path [params] entry. + String? operator [](String key) => params[key]; + + RouteState copyWith({ + Uri? uri, + Map? params, + Object? arguments, + }) { + return RouteState( + uri: uri ?? this.uri, + params: params ?? this.params, + arguments: arguments ?? this.arguments, + ); + } + + @override + String toString() => + 'RouteState($uri, params: $params, arguments: $arguments)'; +} diff --git a/lib/src/state/consumer.dart b/lib/src/state/consumer.dart new file mode 100644 index 00000000..9fc0cb9a --- /dev/null +++ b/lib/src/state/consumer.dart @@ -0,0 +1,96 @@ +import 'package:flutter/widgets.dart'; + +import 'scoped.dart'; + +/// Rebuilds ONLY its [builder] when [T] notifies — scopes the rebuild to a +/// sub-widget instead of the whole page (the granular alternative to `watch`). +class Consumer extends StatefulWidget { + const Consumer({required this.builder, this.child, super.key}); + + final Widget Function(BuildContext context, T value, Widget? child) builder; + final Widget? child; + + @override + State> createState() => _ConsumerState(); +} + +class _ConsumerState extends State> { + T? _value; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final value = context.read(); + if (!identical(value, _value)) { + _value?.removeListener(_onChange); + _value = value; + _value!.addListener(_onChange); + } + } + + void _onChange() { + if (mounted) setState(() {}); + } + + @override + void dispose() { + _value?.removeListener(_onChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) => + widget.builder(context, _value!, widget.child); +} + +/// Rebuilds its [builder] only when the SELECTED value [R] changes — surgical +/// reactivity over a [Listenable] view model. +class Selector extends StatefulWidget { + const Selector({ + required this.selector, + required this.builder, + this.child, + super.key, + }); + + final R Function(BuildContext context, T value) selector; + final Widget Function(BuildContext context, R value, Widget? child) builder; + final Widget? child; + + @override + State> createState() => _SelectorState(); +} + +class _SelectorState extends State> { + T? _source; + late R _selected; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final source = context.read(); + if (!identical(source, _source)) { + _source?.removeListener(_onChange); + _source = source; + _source!.addListener(_onChange); + _selected = widget.selector(context, _source!); + } + } + + void _onChange() { + final next = widget.selector(context, _source!); + if (next != _selected && mounted) { + setState(() => _selected = next); + } + } + + @override + void dispose() { + _source?.removeListener(_onChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) => + widget.builder(context, _selected, widget.child); +} diff --git a/lib/src/state/scoped.dart b/lib/src/state/scoped.dart new file mode 100644 index 00000000..965fefeb --- /dev/null +++ b/lib/src/state/scoped.dart @@ -0,0 +1,239 @@ +import 'dart:async'; + +import 'package:auto_injector/auto_injector.dart'; +import 'package:flutter/widgets.dart'; + +/// Registrar for PAGE-SCOPED state, used in `route(provide: (scoped) {...})`. +/// +/// Each registration becomes a factory built in a page-local injector at mount +/// (deps resolved from the module injector), provided reactively via +/// `InheritedNotifier`, and disposed at unmount. +class Scoped { + final List _specs = []; + + List get specs => List.unmodifiable(_specs); + + /// Registers a [ChangeNotifier] view model, scoped to the page (built in the + /// page-local injector, provided via `InheritedNotifier`, `dispose()`d on + /// unmount). The bound is [ChangeNotifier] — NOT [Listenable] — precisely so + /// the dispose is guaranteed: a bare `Listenable` has no `dispose`, which + /// would make resource cleanup silently best-effort. + void addChangeNotifier(Function constructor) { + _specs.add(_ChangeNotifierSpec(constructor)); + } + + /// Registers a NON-REACTIVE [Disposable], scoped to the page. It is built in + /// the page-local injector (one instance per mount, so view models can depend + /// on it) and `dispose()`d on unmount. Use this for resources that need + /// lifecycle but no reactivity — a socket, a subscription manager, a + /// use-case holding a connection. Reactivity ([ChangeNotifier]) and lifecycle + /// ([Disposable]) are thus independent: a thing can have either, both, or + /// neither. + void addDisposable(Function constructor) { + _specs.add(_DisposableSpec(constructor)); + } + + /// Registers stream-backed state. The latest value is exposed via + /// `context.watch>().value`. + void addStream(Stream Function() create) { + _specs.add(_StreamSpec(create)); + } +} + +/// A page-scoped resource that needs cleanup but is NOT reactive. Implement it +/// on any class and register it via [Scoped.addDisposable] to have it built in +/// the page-local injector and `dispose()`d when the page leaves the stack — +/// even though it is not a [Listenable]/[ChangeNotifier]. +abstract interface class Disposable { + void dispose(); +} + +/// Internal: one provided unit (register / resolve / wrap / dispose), typed. +abstract class ScopedSpec { + Type get type; + void register(AutoInjector injector); + Object resolve(AutoInjector injector); + Widget wrap(Object instance, Widget child); + void dispose(Object instance); +} + +class _ChangeNotifierSpec implements ScopedSpec { + _ChangeNotifierSpec(this.constructor); + + final Function constructor; + + @override + Type get type => T; + + @override + void register(AutoInjector injector) => injector.add(constructor); + + @override + Object resolve(AutoInjector injector) => injector.get(); + + @override + Widget wrap(Object instance, Widget child) => + _VMInherited(notifier: instance as T, child: child); + + @override + void dispose(Object instance) => (instance as ChangeNotifier).dispose(); +} + +/// A non-reactive, page-scoped [Disposable]: registered as a per-page singleton +/// (shared with any view model that injects it), NOT provided through an +/// `InheritedNotifier` (nothing to listen to), and `dispose()`d on unmount. +class _DisposableSpec implements ScopedSpec { + _DisposableSpec(this.constructor); + + final Function constructor; + + @override + Type get type => T; + + @override + void register(AutoInjector injector) => + injector.addLazySingleton(constructor); + + @override + Object resolve(AutoInjector injector) => injector.get(); + + @override + Widget wrap(Object instance, Widget child) => child; + + @override + void dispose(Object instance) => (instance as Disposable).dispose(); +} + +/// A [ChangeNotifier] holding the latest value emitted by a [Stream]. +class StreamValue extends ChangeNotifier { + StreamValue(Stream stream) { + _sub = stream.listen((event) { + _value = event; + notifyListeners(); + }); + } + + late final StreamSubscription _sub; + T? _value; + + T? get value => _value; + + @override + void dispose() { + _sub.cancel(); + super.dispose(); + } +} + +class _StreamSpec implements ScopedSpec { + _StreamSpec(this.create); + + final Stream Function() create; + + @override + Type get type => StreamValue; + + @override + void register(AutoInjector injector) {} + + @override + Object resolve(AutoInjector injector) => StreamValue(create()); + + @override + Widget wrap(Object instance, Widget child) => _VMInherited>( + notifier: instance as StreamValue, + child: child, + ); + + @override + void dispose(Object instance) => (instance as StreamValue).dispose(); +} + +class _VMInherited extends InheritedNotifier { + const _VMInherited({required super.notifier, required super.child}); +} + +/// Wraps a page subtree: builds the page-scoped instances in a page-local +/// injector (deps resolved from [parent], the module injector), provides them +/// via nested `InheritedNotifier`s, and disposes them on unmount. +/// +/// The page-local injector is NOT disposed (that would cascade to the shared +/// [parent]); the instances are disposed individually. +class ScopedHost extends StatefulWidget { + const ScopedHost({ + required this.provide, + required this.parent, + required this.child, + super.key, + }); + + final void Function(Scoped scoped) provide; + final AutoInjector parent; + final Widget child; + + @override + State createState() => _ScopedHostState(); +} + +class _ScopedHostState extends State { + late final List _specs; + final Map _instances = {}; + + @override + void initState() { + super.initState(); + final scoped = Scoped(); + widget.provide(scoped); + _specs = scoped.specs; + + final injector = AutoInjector()..addInjector(widget.parent); + for (final spec in _specs) { + spec.register(injector); + } + injector.commit(); + for (final spec in _specs) { + _instances[spec.type] = spec.resolve(injector); + } + } + + @override + void dispose() { + for (final spec in _specs) { + final instance = _instances[spec.type]; + if (instance != null) spec.dispose(instance); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + var result = widget.child; + for (final spec in _specs.reversed) { + result = spec.wrap(_instances[spec.type]!, result); + } + return result; + } +} + +/// Page-scoped state access from any descendant of the page. +extension ModularStateX on BuildContext { + /// Reactively reads a page-scoped [Listenable] (rebuilds on notify). + T watch() { + final inherited = dependOnInheritedWidgetOfExactType<_VMInherited>(); + final notifier = inherited?.notifier; + if (notifier == null) { + throw FlutterError('context.watch<$T>(): no scoped $T provided.'); + } + return notifier; + } + + /// Reads a page-scoped [Listenable] WITHOUT subscribing to rebuilds. + T read() { + final element = getElementForInheritedWidgetOfExactType<_VMInherited>(); + final notifier = (element?.widget as _VMInherited?)?.notifier; + if (notifier == null) { + throw FlutterError('context.read<$T>(): no scoped $T provided.'); + } + return notifier; + } +} diff --git a/melos_modular.iml b/melos_modular.iml deleted file mode 100644 index 5dd41607..00000000 --- a/melos_modular.iml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/modular.code-workspace b/modular.code-workspace deleted file mode 100644 index 40e60e47..00000000 --- a/modular.code-workspace +++ /dev/null @@ -1,20 +0,0 @@ -{ - "folders": [ - { - "name": "Core", - "path": "modular_core" - }, - { - "name": "Shelf", - "path": "shelf_modular" - }, - { - "name": "Flutter", - "path": "flutter_modular" - }, - { - "name": "Documentation", - "path": "doc" - } - ] -} \ No newline at end of file diff --git a/modular_core/.gitignore b/modular_core/.gitignore deleted file mode 100644 index 580e95ee..00000000 --- a/modular_core/.gitignore +++ /dev/null @@ -1,78 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -build/ - -# Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/gradlew -**/android/gradlew.bat -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java - -# iOS/XCode related -**/ios/**/*.mode1v3 -**/ios/**/*.mode2v3 -**/ios/**/*.moved-aside -**/ios/**/*.pbxuser -**/ios/**/*.perspectivev3 -**/ios/**/*sync/ -**/ios/**/.sconsign.dblite -**/ios/**/.tags* -**/ios/**/.vagrant/ -**/ios/**/DerivedData/ -**/ios/**/Icon? -**/ios/**/Pods/ -**/ios/**/.symlinks/ -**/ios/**/profile -**/ios/**/xcuserdata -**/ios/.generated/ -**/ios/Flutter/App.framework -**/ios/Flutter/Flutter.framework -**/ios/Flutter/Flutter.podspec -**/ios/Flutter/Generated.xcconfig -**/ios/Flutter/ephemeral -**/ios/Flutter/app.flx -**/ios/Flutter/app.zip -**/ios/Flutter/flutter_assets/ -**/ios/Flutter/flutter_export_environment.sh -**/ios/ServiceDefinitions.json -**/ios/Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!**/ios/**/default.mode1v3 -!**/ios/**/default.mode2v3 -!**/ios/**/default.pbxuser -!**/ios/**/default.perspectivev3 - -coverage/ -pubspec.lock diff --git a/modular_core/.metadata b/modular_core/.metadata deleted file mode 100644 index d536721a..00000000 --- a/modular_core/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# 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 and should not be manually edited. - -version: - revision: f4abaa0735eba4dfd8f33f73363911d63931fe03 - channel: stable - -project_type: package diff --git a/modular_core/CHANGELOG.md b/modular_core/CHANGELOG.md deleted file mode 100644 index 6e098998..00000000 --- a/modular_core/CHANGELOG.md +++ /dev/null @@ -1,62 +0,0 @@ -## 3.4.1 - 2025/06/12 -- Fix AppModule Dispose. - -## 3.4.0 - 2025/06/10 - -- Fix Deep Link query parameters. -- Update Result. - -## 3.1.1 - 2023/08/24 - -* fix: Fix Import Modules Dependencies - -## 2.0.3+1 - 2022/06/02 - -* feat: Added type propertie in removeBindContext - -## 2.0.1 - 2022/05/12 - -* Fix: Inject.get should return instance - -## 2.0.0 - 2022/05/11 - -* Remove `setDisposeResolver`. -* Apply `BindContract.onDispose` of `modular_interface`. -* Injetor.getBind now returns `BindEntry`. -* [BREAK CHANGE] Bind.export works only after imported. - -## 1.2.2 - 2022/04/05 - -* Fixed binds auto-dispose [#699] [#671] [#678] - -## 1.2.1 - 2021/12/31 - -* Fixed "bind replaced" bug - -## 1.2.0 - 2021/10/28 - -* Added cleanTracker() for restart Modular. [#601](https://github.com/Flutterando/modular/pull/601) - -## 1.2.0 - 2021/10/01 - -* Added reassemble method for routes and binds. - -## 1.0.5 - 2021/09/24 - -* Fixed destroy - -## 1.0.4 - 2021/09/22 - -* Rebuild modularArguments after get route - -## 1.0.0+1 - 2021/09/05 - -* Stable version - -## 0.0.2+1 - 2021/08/17 - -* Added ModularKey - -## 0.0.1 - 2021/08/17 - -* initial release. diff --git a/modular_core/LICENSE b/modular_core/LICENSE deleted file mode 100644 index 22d8ef64..00000000 --- a/modular_core/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -The MIT License -License Copyright: Flutterando. -License License: Flutterando. -License Contact: Flutterando. -SPDX short identifier: MIT -Further resources... -  -Begin license text. -Copyright 2021 Flutterando -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -End license text. \ No newline at end of file diff --git a/modular_core/README.md b/modular_core/README.md deleted file mode 100644 index 6c5ee5e7..00000000 --- a/modular_core/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# modular_core - -Injection system and routes for `flutter_modular` and `shelf_modular`; \ No newline at end of file diff --git a/modular_core/analysis_options.yaml b/modular_core/analysis_options.yaml deleted file mode 100644 index 0e9852c9..00000000 --- a/modular_core/analysis_options.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutterando_analysis/dart_package.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - public_member_api_docs: false - cascade_invocations: false - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options \ No newline at end of file diff --git a/modular_core/lib/modular_core.dart b/modular_core/lib/modular_core.dart deleted file mode 100644 index 39d3e224..00000000 --- a/modular_core/lib/modular_core.dart +++ /dev/null @@ -1,28 +0,0 @@ -library modular_core; - -import 'dart:async'; - -import 'package:auto_injector/auto_injector.dart'; -import 'package:characters/characters.dart'; -import 'package:meta/meta.dart'; - -export 'package:auto_injector/auto_injector.dart'; - -// errors -part 'src/errors/errors.dart'; -// di -part 'src/module/disposable.dart'; -// modules -part 'src/module/module.dart'; -// route -part 'src/route/arguments.dart'; -part 'src/route/middleware.dart'; -part 'src/route/route.dart'; -part 'src/route/route_manager.dart'; -part 'src/tracker.dart'; - -void Function(String text)? printResolverFunc; - -void setPrintResolver(void Function(String text) fn) { - printResolverFunc = fn; -} diff --git a/modular_core/lib/src/errors/errors.dart b/modular_core/lib/src/errors/errors.dart deleted file mode 100644 index b060a158..00000000 --- a/modular_core/lib/src/errors/errors.dart +++ /dev/null @@ -1,20 +0,0 @@ -part of '../../modular_core.dart'; - -abstract class ModularError implements Exception { - final String message; - final StackTrace? stackTrace; - - const ModularError(this.message, [this.stackTrace]); - - String _returnStackTrace() => stackTrace != null ? '\n$stackTrace' : ''; - - @override - String toString() => '$runtimeType: $message${_returnStackTrace()}'; -} - -class TrackerNotInitiated extends ModularError { - const TrackerNotInitiated( - String message, [ - StackTrace? stackTrace, - ]) : super(message, stackTrace); -} diff --git a/modular_core/lib/src/module/disposable.dart b/modular_core/lib/src/module/disposable.dart deleted file mode 100644 index 2b02d3d2..00000000 --- a/modular_core/lib/src/module/disposable.dart +++ /dev/null @@ -1,9 +0,0 @@ -part of '../../modular_core.dart'; - -/// A class which implements [Disposable] can be disposed automatically -/// once user leaves a Module -//ignore:one_member_abstracts -abstract class Disposable { - /// Disposes controllers, streams, etc. - void dispose(); -} diff --git a/modular_core/lib/src/module/module.dart b/modular_core/lib/src/module/module.dart deleted file mode 100644 index 774e2627..00000000 --- a/modular_core/lib/src/module/module.dart +++ /dev/null @@ -1,10 +0,0 @@ -part of '../../modular_core.dart'; - -abstract class Module { - List get imports => const []; - - void binds(Injector i) {} - void exportedBinds(Injector i) {} - - void routes(RouteManager r) {} -} diff --git a/modular_core/lib/src/route/arguments.dart b/modular_core/lib/src/route/arguments.dart deleted file mode 100644 index 7fbe9fab..00000000 --- a/modular_core/lib/src/route/arguments.dart +++ /dev/null @@ -1,66 +0,0 @@ -part of '../../modular_core.dart'; - -/// Object that clusters all arguments and parameters retrieved or -/// produced during a route search. -class ModularArguments { - /// It retrieves parameters after consulting a dynamic route. If it - /// is not a dynamic route the object will be an empty Map. - /// ex: /product/:id -> /product/1 - /// Modular.args.params['id']; -> '1' - final Map params; - - /// Uri of current route. - final Uri uri; - - /// Retrieved from a direct input of arguments from the - /// navigation system itself. - /// ex: Modular.to.navigate('/product', arguments: Products()); - /// Modular.args.data; -> Product(); - final dynamic data; - - const ModularArguments({ - this.params = const {}, - this.data, - required this.uri, - }); - - ModularArguments copyWith({ - Map? params, - dynamic data, - Uri? uri, - }) { - return ModularArguments( - params: params ?? this.params, - data: data ?? this.data, - uri: uri ?? this.uri, - ); - } - - /// The value is the empty string if there is no - /// fragment identifier component. - String get fragment => uri.fragment; - - /// The URI query split into a map according to the rules - /// specified for FORM post in the HTML 4.01 specification section 17.13.4. - /// Each key and value in the resulting map has been decoded. - /// If there is no query the empty map is returned. - /// Keys in the query string that have no value are mapped - /// to the empty string. If a key occurs more than once in - /// the query string, it is mapped to an arbitrary choice of - /// possible value. - Map get queryParams => uri.queryParameters; - - /// Returns the URI query split into a map according to the rules - /// specified for FORM post in the HTML 4.01 specification section 17.13.4. - /// Each key and value in the resulting map has been decoded. - /// If there is no query the map is empty. - /// Keys are mapped to lists of their values. If a key occurs - /// only once, its value is a singleton list. If a key occurs - /// with no value, the empty string is used as the value for that occurrence. - /// The map and the lists it contains are unmodifiable. - Map> get queryParamsAll => uri.queryParametersAll; - - factory ModularArguments.empty() { - return ModularArguments(uri: Uri.parse('/')); - } -} diff --git a/modular_core/lib/src/route/middleware.dart b/modular_core/lib/src/route/middleware.dart deleted file mode 100644 index 7fdea59b..00000000 --- a/modular_core/lib/src/route/middleware.dart +++ /dev/null @@ -1,10 +0,0 @@ -part of '../../modular_core.dart'; - -/// Object that intercepts a route request. -abstract class Middleware { - /// Method called as soon as route is found and before settings. - FutureOr pre(ModularRoute route); - - /// Method called as soon as route is found and after settings. - FutureOr pos(ModularRoute route, T data); -} diff --git a/modular_core/lib/src/route/route.dart b/modular_core/lib/src/route/route.dart deleted file mode 100644 index bdc87406..00000000 --- a/modular_core/lib/src/route/route.dart +++ /dev/null @@ -1,104 +0,0 @@ -part of '../../modular_core.dart'; - -abstract class ModularRoute { - /// name of route - final String name; - - /// schema of route - /// default is '' - final String schema; - - /// Add children to this route that can be retrieved through the parent route - final List children; - - /// Adds middleware that will be shared among your children. - final List middlewares; - - /// Key that references the route in the RouteContext tree. - late final ModularKey key; - - late final Uri uri; - - /// Module belonging to the route. - Module? module; - - /// guard your parent's path - final String parent; - - /// Contains a list of all BindContexts that will need - /// to be active when this route is active. - final Map innerModules; - - /// Create a new Route by adding a RouteContext to the context. - ModularRoute addModule(String name, {required Module module}); - - ModularRoute( - this.name, { - this.schema = '', - this.parent = '', - this.module, - required this.uri, - this.innerModules = const {}, - this.children = const [], - this.middlewares = const [], - }) { - key = ModularKey(name: name, schema: schema); - } - - ModularRoute copyWith({ - String? name, - String? schema, - List? children, - List? middlewares, - Map? innerModules, - Uri? uri, - String? parent, - Module? module, - }); - - ModularRoute addParent(ModularRoute parent) { - final newName = '${parent.name}$name'.replaceFirst('//', '/'); - return copyWith( - name: newName, - parent: parent.name, - middlewares: [ - ...parent.middlewares, - ...middlewares, - ], - innerModules: { - ...parent.innerModules, - ...innerModules, - }, - ); - } -} - -@immutable -class ModularKey { - final String schema; - final String name; - - const ModularKey({required this.name, this.schema = ''}); - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is ModularKey && other.schema == schema && other.name == name; - } - - @override - int get hashCode => schema.hashCode ^ name.hashCode; - - ModularKey copyWith({ - String? schema, - String? name, - }) { - return ModularKey(schema: schema ?? this.schema, name: name ?? this.name); - } - - @override - String toString() { - return 'ModularKey(schema: $schema, name: $name)'; - } -} diff --git a/modular_core/lib/src/route/route_manager.dart b/modular_core/lib/src/route/route_manager.dart deleted file mode 100644 index 8613d96c..00000000 --- a/modular_core/lib/src/route/route_manager.dart +++ /dev/null @@ -1,12 +0,0 @@ -part of '../../modular_core.dart'; - -class RouteManager { - final List _routes = []; - - @visibleForTesting - List get allRoutes => List.unmodifiable(_routes); - - void add(ModularRoute route) { - _routes.add(route); - } -} diff --git a/modular_core/lib/src/tracker.dart b/modular_core/lib/src/tracker.dart deleted file mode 100644 index 4721f97b..00000000 --- a/modular_core/lib/src/tracker.dart +++ /dev/null @@ -1,380 +0,0 @@ -// ignore_for_file: avoid_print - -part of '../../modular_core.dart'; - -abstract class Tracker { - /// Service Injector instancia - AutoInjector get injector; - - /// Initial Module - Module get module; - - ModularArguments get arguments; - - void setArguments(ModularArguments arguments); - - String get currentPath; - - factory Tracker(AutoInjector injector) => _Tracker(injector); - - /// Searches for a route by name or context throughout the tree. - FutureOr findRoute( - String path, { - dynamic data, - String schema = '', - }); - - /// Reports whether a route will leave the route context. - /// This is important to call automatic dispose of the entire context. - void reportPopRoute(ModularRoute route); - - /// It informs you that a new route has been found and that - /// it needs its dependent BindContexts started as well. - void reportPushRoute(ModularRoute route); - - /// Responsible for starting the app. - /// It should only be called once, but it should be the - /// first method to be called before a route or bind lookup. - void runApp(Module module, [String initialRoutePath = '/']); - - /// Add a Module to Injection System.
    - /// Use Tracker.unbindModule to remove registers; - void bindModule(Module module, [String? tag]); - - /// Remove registers manually; - void unbindModule(String moduleName); - - /// Finishes all trees. - void finishApp(); - - /// dispose instance - bool dispose(); -} - -class _Tracker implements Tracker { - @override - final AutoInjector injector; - - final _disposeTags = >{}; - final _importedInjector = {}; - - Module? _nullableModule; - @override - Module get module { - if (_nullableModule != null) { - return _nullableModule!; - } - - throw const TrackerNotInitiated('Execute Tracker.runApp()'); - } - - @visibleForTesting - final routeMap = {}; - - _Tracker(this.injector); - - var _arguments = ModularArguments.empty(); - - @override - ModularArguments get arguments => _arguments; - - @override - String get currentPath => arguments.uri.toString(); - - @override - void runApp(Module module, [String initialRoutePath = '/']) { - _nullableModule = module; - _disposeTags[module.runtimeType] = [initialRoutePath]; - bindModule(module); - addRoutes(module); - } - - @override - FutureOr findRoute( - String path, { - dynamic data, - String schema = '', - }) async { - final uri = _resolverPath(path); - final modularKey = ModularKey(schema: schema, name: uri.path); - - ModularRoute? route; - var params = {}; - - for (final key in routeMap.keys) { - final uriCandidate = Uri.parse(key.name); - if (uriCandidate.path == uri.path) { - final candidate = routeMap[key]; - if (key.copyWith(name: uri.path) == modularKey) { - route = candidate; - break; - } - } - if (uriCandidate.pathSegments.length != uri.pathSegments.length // - && - !uriCandidate.path.contains('**')) { - continue; - } - - if (!(uriCandidate.path.contains(':') // - || - uriCandidate.path.contains('**'))) { - continue; - } - - final result = _extractParams(uriCandidate, uri); - if (result != null) { - final candidate = routeMap[key]; - if (key.copyWith(name: uri.path) == modularKey) { - route = candidate; - params = result; - break; - } - } - } - - if (route == null) return null; - - route = route.copyWith(uri: uri); - - for (final middleware in route.middlewares) { - route = await middleware.pre(route!); - if (route == null) { - break; - } - } - - if (route == null) return null; - - _arguments = ModularArguments(uri: uri, data: data, params: params); - - return route; - } - - @override - void reportPopRoute(ModularRoute route) { - final tag = route.uri.toString(); - - for (final key in _disposeTags.keys) { - final moduleTags = _disposeTags[key]!; - - if (moduleTags.isEmpty) { - continue; - } - - moduleTags.remove(tag); - if (tag.characters.last == '/') { - moduleTags.remove('$tag/'.replaceAll('//', '')); - } - - if (moduleTags.isEmpty) { - unbindModule(key.toString()); - } - } - } - - void _removeRegisters(String tag) { - injector.disposeInjectorByTag(tag, _disposeInstance); - - printResolverFunc?.call('-- $tag DISPOSED'); - } - - void _disposeInstance(dynamic instance) { - if (instance is Disposable) { - instance.dispose(); - } - } - - @override - void reportPushRoute(ModularRoute route) { - for (final module in [...route.innerModules.values, module]) { - final key = module.runtimeType; - if (_disposeTags[key]!.isEmpty) { - bindModule(module); - printResolverFunc?.call('-- ${module.runtimeType} INITIALIZED'); - } - final routeUri = route.uri.toString(); - if (_disposeTags[key]!.isNotEmpty && routeUri != '/') { - if (_disposeTags[key]!.contains(routeUri)) { - continue; - } - } - _disposeTags[key]!.add(routeUri); - } - } - - @override - void bindModule(Module module, [String? tag]) { - final newInjector = _createInjector(module, tag); - - injector.uncommit(); - injector.addInjector(newInjector); - injector.commit(); - } - - @override - void unbindModule(String moduleName) { - _removeRegisters(moduleName); - } - - @override - bool dispose() { - final dead = injector.disposeSingleton(); - _disposeInstance(dead); - return dead != null; - } - - Uri _resolverPath(String path) { - return arguments.uri.resolve(path); - } - - Map? _extractParams(Uri candidate, Uri match) { - final settledUrl = _processUrl(candidate.path); - - final regExp = RegExp('^$settledUrl\$'); - final result = regExp.firstMatch(match.path); - - if (result != null) { - final params = {}; - for (final name in result.groupNames) { - params[name] = result.namedGroup(name)!; - } - return params; - } else { - return null; - } - } - - String _processUrl(String url) { - if (url.endsWith('**')) { - return url.replaceFirst('**', '(?.*)'); - } - - final newUrl = []; - for (var part in url.split('/')) { - part = part.contains(':') ? '(?<${part.substring(1)}>.*)' : part; - newUrl.add(part); - } - return newUrl.join('/'); - } - - AutoInjector _createExportedInjector(Module importedModule) { - final importTag = importedModule.runtimeType.toString(); - late AutoInjector exportedInject; - if (!_importedInjector.containsKey(importTag)) { - exportedInject = _createInjector(importedModule, '${importTag}_Imported'); - importedModule.exportedBinds(exportedInject); - _importedInjector[importTag] = exportedInject; - } else { - exportedInject = _importedInjector[importTag]!; - } - - return exportedInject; - } - - AutoInjector _createInjector(Module module, [String? tag]) { - final newInjector = AutoInjector(tag: tag ?? module.runtimeType.toString()); - for (final importedModule in module.imports) { - final exportedInject = _createExportedInjector(importedModule); - newInjector.addInjector(exportedInject); - } - - module.binds(newInjector); - return newInjector; - } - - void addRoutes(Module module) { - final manager = RouteManager(); - module.routes(manager); - final routes = manager._routes; - - final _routeMap = {}; - for (final route in routes) { - _routeMap.addAll(_assembleRoute(route)); - } - - final _odernatedMap = {}; - for (final key in _orderRouteKeys(_routeMap.keys)) { - _odernatedMap[key] = _routeMap[key]!; - } - - routeMap.addAll(_odernatedMap); - } - - Map _assembleRoute(ModularRoute route) { - final map = {}; - - if (route.module == null) { - map[route.key] = route; - map.addAll(_addChildren(route)); - } else { - map.addAll(_addModule(route)); - } - - return map; - } - - List _orderRouteKeys(Iterable keys) { - final ordenatekeys = [...keys]; - ordenatekeys.sort((preview, actual) { - if (preview.name.contains('/:') && !actual.name.contains('**')) { - return 1; - } - - if (preview.name.contains('**')) { - final c = - actual.name.split('/').length > preview.name.split('/').length; - if (!actual.name.contains('**') || c) { - return 1; - } - } - - return 0; - }); - return ordenatekeys; - } - - Map _addModule(ModularRoute route) { - final map = {}; - final module = route.module!; - final manager = RouteManager(); - module.routes(manager); - final routes = manager._routes; - _disposeTags[module.runtimeType] = []; - for (var child in routes) { - child = child.addParent(route); - child = child.copyWith( - innerModules: { - ...child.innerModules, - module.runtimeType: module, - }, - parent: route.parent, - ); - map.addAll(_assembleRoute(child)); - } - - return map; - } - - Map _addChildren(ModularRoute route) { - final map = {}; - - for (var child in route.children) { - child = child.addParent(route); - map.addAll(_assembleRoute(child)); - } - - return map; - } - - @override - void finishApp() { - injector.disposeRecursive(); - routeMap.clear(); - _disposeTags.clear(); - _nullableModule = null; - } - - @override - void setArguments(ModularArguments args) => _arguments = args; -} diff --git a/modular_core/pubspec.yaml b/modular_core/pubspec.yaml deleted file mode 100644 index f9388f37..00000000 --- a/modular_core/pubspec.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: modular_core -description: Smart project structure with dependency injection and route management -version: 3.4.1 -homepage: https://github.com/Flutterando/modular - -resolution: workspace - -environment: - sdk: ">=3.6.0 <4.0.0" - -dependencies: - auto_injector: ">=2.1.0 <3.0.0" - characters: ">=1.4.0 <2.0.0" - meta: ">=1.3.0 <2.0.0" - -dev_dependencies: - flutterando_analysis: ^0.0.2 - test: ">=1.16.0 <2.0.0" diff --git a/modular_core/test/src/errors/errors_test.dart b/modular_core/test/src/errors/errors_test.dart deleted file mode 100644 index d1943f0a..00000000 --- a/modular_core/test/src/errors/errors_test.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:test/test.dart'; - -void main() { - test('Return trace errors', () { - final error = TrackerNotInitiated('Test', StackTrace.current); - expect(error.toString(), isA()); - }); -} diff --git a/modular_core/test/src/route/arguments_test.dart b/modular_core/test/src/route/arguments_test.dart deleted file mode 100644 index 57a5e144..00000000 --- a/modular_core/test/src/route/arguments_test.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:test/test.dart'; - -void main() { - test('Create arguments', () { - final args = ModularArguments( - uri: Uri.parse('/'), - params: {'test': 'test'}, - data: 0, - ); - - final copy = args.copyWith(data: 1); - expect(copy.data, 1); - expect(copy.queryParamsAll, {}); - expect(copy.fragment, ''); - }); -} diff --git a/modular_core/test/src/tracker_test.dart b/modular_core/test/src/tracker_test.dart deleted file mode 100644 index 26f48077..00000000 --- a/modular_core/test/src/tracker_test.dart +++ /dev/null @@ -1,279 +0,0 @@ -// ignore_for_file: prefer_constructors_over_static_methods - -import 'dart:async'; - -import 'package:modular_core/modular_core.dart'; -import 'package:test/test.dart'; - -void main() { - late Tracker tracker; - - // Only coverage - MyModule().exportedBinds(AutoInjector()); - final manager = RouteManager(); - ImportedModule().routes(manager); - ModularArguments(uri: Uri.parse(''), data: '').copyWith(); - setPrintResolver((text) {}); - - setUp(() { - tracker = Tracker(AutoInjector(tag: 'Test')..commit()); - - final module = MyModule(); - manager.allRoutes; - - tracker.runApp(module); - }); - - test('throw error if runApp not initiate module', () { - tracker.finishApp(); - expect(() => tracker.module, throwsA(isA())); - }); - - test('setArguments', () { - final args = ModularArguments.empty(); - tracker.setArguments(args); - expect(tracker.arguments, args); - }); - - test('dispose instance', () async { - final route = await tracker.findRoute('/'); - tracker.reportPushRoute(route!); - - expect(tracker.dispose(), true); - }); - - test('find route', () async { - final route = await tracker.findRoute('/') as CustomRoute?; - expect(route?.uri.path, '/'); - expect(tracker.currentPath, '/'); - expect(route?.data, 'first'); - }); - - test('find route with params', () async { - var route = await tracker.findRoute('/product/1') as CustomRoute?; - expect(route?.uri.path, '/product/1'); - expect(tracker.currentPath, '/product/1'); - expect(tracker.arguments.params['id'], '1'); - - route = await tracker.findRoute('/product/test') as CustomRoute?; - expect(route?.uri.path, '/product/test'); - expect(tracker.currentPath, '/product/test'); - expect(tracker.arguments.params['id'], isNull); - }); - - test('find route with queries', () async { - final route = await tracker.findRoute('/?q=banana') as CustomRoute?; - expect(route?.uri.path, '/'); - expect(tracker.arguments.queryParams['q'], 'banana'); - }); - - test('find route in other module', () async { - final route = await tracker.findRoute('/other/') as CustomRoute?; - expect(route?.uri.path, '/other/'); - expect(route?.data, 'other'); - tracker.reportPopRoute(route!); - }); - - test('find child route in other module', () async { - final route = await tracker.findRoute('/other/details') as CustomRoute?; - expect(route?.uri.path, '/other/details'); - expect(route?.parent, '/other/'); - expect(route?.data, 'otherWithDetails'); - tracker.reportPopRoute(route!); - }); - - test('find child route in deep module', () async { - var route = await tracker.findRoute('/other/internal/') as CustomRoute?; - expect(route, isNotNull); - tracker.reportPushRoute(route!); - expect(route.uri.path, '/other/internal/'); - expect(route.data, 'internal'); - - tracker.reportPopRoute(route); - - route = await tracker.findRoute('/other/internal/deep') as CustomRoute?; - expect(route, isNotNull); - tracker.reportPushRoute(route!); - expect(route.uri.path, '/other/internal/deep'); - expect(route.parent, '/other/internal/'); - expect(route.data, 'deep'); - tracker.reportPopRoute(route); - }); - - test('find route with schema', () async { - expect(await tracker.findRoute('/schema'), isNull); - final route = - await tracker.findRoute('/schema', schema: 'tag') as CustomRoute?; - expect(route?.uri.path, '/schema'); - expect(route?.data, 'withSchema'); - }); - - test('find route with wildcard', () async { - final route = await tracker.findRoute('/wildcard/test/2') as CustomRoute?; - expect(route?.uri.path, '/wildcard/test/2'); - expect(route?.data, 'wildcard'); - }); - - test('finishApp', () { - tracker.finishApp(); - expect(() => tracker.module, throwsA(isA())); - }); -} - -class MyModule extends Module { - @override - final List imports = [ImportedModule()]; - - @override - void binds(Injector i) { - i.add(() => 'instance'); - i.addSingleton(TestController.new); - } - - @override - void routes(RouteManager r) { - r.add( - CustomRoute( - '/', - data: 'first', - middlewares: [CustomMiddleware()], - children: [ - CustomRoute('/second', data: 'second'), - ], - ), - ); - r.add(CustomRoute('/schema', data: 'withSchema', schema: 'tag')); - r.add(CustomRoute('/wildcard/**', data: 'wildcard')); - r.add(CustomRoute('/product/:id', data: 'withParams')); - r.add(CustomRoute('/product/test', data: 'test')); - r.add(CustomRoute.moduleMode('/other', module: OtherModule())); - } -} - -class OtherModule extends Module { - @override - void routes(RouteManager r) { - r.add( - CustomRoute( - '/', - data: 'other', - children: [ - CustomRoute('/details', data: 'otherWithDetails'), - ], - ), - ); - r.add(CustomRoute.moduleMode('/internal', module: DeepModule())); - } -} - -class DeepModule extends Module { - @override - void routes(RouteManager r) { - r.add( - CustomRoute( - '/', - data: 'internal', - children: [ - CustomRoute('/deep', data: 'deep'), - ], - ), - ); - } -} - -class BlockedModule extends Module { - @override - void routes(RouteManager r) { - r.add(CustomRoute('/')); - r.add(CustomRoute('/again')); - } -} - -class ImportedModule extends Module { - @override - final List imports = [ImportedModule2()]; - - @override - void exportedBinds(Injector i) { - i.add(() => 0); - } -} - -class ImportedModule2 extends Module { - @override - void exportedBinds(Injector i) { - i.add(() => 0.0); - } -} - -class EmptyModule extends Module {} - -class CustomMiddleware implements Middleware { - @override - FutureOr pre(ModularRoute route) { - pos(route, ''); - return route; - } - - @override - FutureOr pos(ModularRoute route, dynamic data) => route; -} - -class CustomRoute extends ModularRoute { - final dynamic data; - CustomRoute( - super.name, { - Uri? uri, - this.data, - super.children, - super.innerModules, - super.middlewares, - super.module, - super.parent, - super.schema, - }) : super(uri: uri ?? Uri.parse('/')); - - static CustomRoute moduleMode(String name, {required Module module}) { - return CustomRoute(name, module: module); - } - - @override - ModularRoute addModule(String name, {required Module module}) { - final innerModules = {module.runtimeType: module}; - return copyWith( - name: name, - uri: Uri.parse(name), - module: module, - innerModules: innerModules, - ); - } - - @override - ModularRoute copyWith({ - String? name, - String? schema, - List? children, - List? middlewares, - Map? innerModules, - Uri? uri, - String? parent, - Module? module, - }) { - return CustomRoute( - name ?? this.name, - uri: uri ?? this.uri, - children: children ?? this.children, - innerModules: innerModules ?? this.innerModules, - middlewares: middlewares ?? this.middlewares, - module: module ?? this.module, - parent: parent ?? this.parent, - schema: schema ?? this.schema, - data: data, - ); - } -} - -class TestController implements Disposable { - @override - void dispose() {} -} diff --git a/pubspec.lock b/pubspec.lock deleted file mode 100644 index 64fe43d3..00000000 --- a/pubspec.lock +++ /dev/null @@ -1,1178 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f - url: "https://pub.dev" - source: hosted - version: "82.0.0" - alice: - dependency: transitive - description: - name: alice - sha256: "6f7df43fdd1e5a296c89a5135eba758c525fb64608bad894c3a04791273d0bda" - url: "https://pub.dev" - source: hosted - version: "0.4.2" - analyzer: - dependency: transitive - description: - name: analyzer - sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0" - url: "https://pub.dev" - source: hosted - version: "7.4.5" - args: - dependency: transitive - description: - name: args - sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" - url: "https://pub.dev" - source: hosted - version: "2.4.0" - asn1lib: - dependency: transitive - description: - name: asn1lib - sha256: "0511d6be23b007e95105ae023db599aea731df604608978dada7f9faf2637623" - url: "https://pub.dev" - source: hosted - version: "1.6.4" - async: - dependency: transitive - description: - name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.dev" - source: hosted - version: "2.13.0" - auto_injector: - dependency: transitive - description: - name: auto_injector - sha256: ad7a95d7c381363d48b54e00cb680f024fd97009067244454e9b4850337608e8 - url: "https://pub.dev" - source: hosted - version: "2.1.0" - bloc: - dependency: transitive - description: - name: bloc - sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" - url: "https://pub.dev" - source: hosted - version: "8.1.4" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - buffer: - dependency: transitive - description: - name: buffer - sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1" - url: "https://pub.dev" - source: hosted - version: "1.2.3" - characters: - dependency: transitive - description: - name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - charcode: - dependency: transitive - description: - name: charcode - sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 - url: "https://pub.dev" - source: hosted - version: "1.3.1" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" - url: "https://pub.dev" - source: hosted - version: "2.0.4" - chopper: - dependency: transitive - description: - name: chopper - sha256: "1b6280ec22841b844448bec8ef2644d9cbe9ea8dfce13ec9cab9e8d3aac3830d" - url: "https://pub.dev" - source: hosted - version: "7.4.0" - cli_config: - dependency: transitive - description: - name: cli_config - sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec - url: "https://pub.dev" - source: hosted - version: "0.2.0" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" - source: hosted - version: "1.1.2" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - convert: - dependency: transitive - description: - name: convert - sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.dev" - source: hosted - version: "3.1.2" - coverage: - dependency: transitive - description: - name: coverage - sha256: "4b8701e48a58f7712492c9b1f7ba0bb9d525644dd66d023b62e1fc8cdb560c8a" - url: "https://pub.dev" - source: hosted - version: "1.14.0" - cross_file: - dependency: transitive - description: - name: cross_file - sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" - url: "https://pub.dev" - source: hosted - version: "0.3.4+2" - crypto: - dependency: transitive - description: - name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" - url: "https://pub.dev" - source: hosted - version: "3.0.6" - crypto_keys: - dependency: transitive - description: - name: crypto_keys - sha256: "2ed305a11a3e5d16dd7f489121c956fd19b9816938bb68bc7ed3a379827a304e" - url: "https://pub.dev" - source: hosted - version: "0.3.0+2" - dartz: - dependency: transitive - description: - name: dartz - sha256: e6acf34ad2e31b1eb00948692468c30ab48ac8250e0f0df661e29f12dd252168 - url: "https://pub.dev" - source: hosted - version: "0.10.1" - dbus: - dependency: transitive - description: - name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" - url: "https://pub.dev" - source: hosted - version: "0.7.11" - dio: - dependency: transitive - description: - name: dio - sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" - url: "https://pub.dev" - source: hosted - version: "5.8.0+1" - dio_web_adapter: - dependency: transitive - description: - name: dio_web_adapter - sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - equatable: - dependency: transitive - description: - name: equatable - sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" - url: "https://pub.dev" - source: hosted - version: "2.0.7" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" - source: hosted - version: "1.3.3" - ffi: - dependency: transitive - description: - name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - file: - dependency: transitive - description: - name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" - url: "https://pub.dev" - source: hosted - version: "6.1.4" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" - source: hosted - version: "1.1.1" - flutter: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: transitive - description: - name: flutter_lints - sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 - url: "https://pub.dev" - source: hosted - version: "2.0.3" - flutter_local_notifications: - dependency: transitive - description: - name: flutter_local_notifications - sha256: "674173fd3c9eda9d4c8528da2ce0ea69f161577495a9cc835a2a4ecd7eadeb35" - url: "https://pub.dev" - source: hosted - version: "17.2.4" - flutter_local_notifications_linux: - dependency: transitive - description: - name: flutter_local_notifications_linux - sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af - url: "https://pub.dev" - source: hosted - version: "4.0.1" - flutter_local_notifications_platform_interface: - dependency: transitive - description: - name: flutter_local_notifications_platform_interface - sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" - url: "https://pub.dev" - source: hosted - version: "7.2.0" - flutter_mobx: - dependency: transitive - description: - name: flutter_mobx - sha256: ba5e93467866a2991259dc51cffd41ef45f695c667c2b8e7b087bf24118b50fe - url: "https://pub.dev" - source: hosted - version: "2.3.0" - flutter_test: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - flutter_triple: - dependency: transitive - description: - name: flutter_triple - sha256: aef6471c9cee0007780234c27a969a057d6edb895d7aa923152e9eccbe1893a7 - url: "https://pub.dev" - source: hosted - version: "2.2.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - flutterando_analysis: - dependency: transitive - description: - name: flutterando_analysis - sha256: "9541b7d6c849f3f82c8ac4f65116c8c4371d37ad691e6d6da905325707aa6244" - url: "https://pub.dev" - source: hosted - version: "0.0.2" - fpdart: - dependency: transitive - description: - name: fpdart - sha256: cfeeedd030503c8317868411659cf95c4a204cc3e82d7e9fff2e272aaef12655 - url: "https://pub.dev" - source: hosted - version: "0.0.14" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" - glob: - dependency: transitive - description: - name: glob - sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" - source: hosted - version: "2.1.3" - hotreloader: - dependency: transitive - description: - name: hotreloader - sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b - url: "https://pub.dev" - source: hosted - version: "4.3.0" - http: - dependency: transitive - description: - name: http - sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" - url: "https://pub.dev" - source: hosted - version: "1.4.0" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 - url: "https://pub.dev" - source: hosted - version: "3.2.2" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" - url: "https://pub.dev" - source: hosted - version: "4.0.2" - io: - dependency: transitive - description: - name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" - url: "https://pub.dev" - source: hosted - version: "1.0.4" - jose: - dependency: transitive - description: - name: jose - sha256: "7955ec5d131960104e81fbf151abacb9d835c16c9e793ed394b2809f28b2198d" - url: "https://pub.dev" - source: hosted - version: "0.3.4" - js: - dependency: transitive - description: - name: js - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" - url: "https://pub.dev" - source: hosted - version: "0.7.2" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.dev" - source: hosted - version: "4.9.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" - url: "https://pub.dev" - source: hosted - version: "10.0.9" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 - url: "https://pub.dev" - source: hosted - version: "3.0.9" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.dev" - source: hosted - version: "3.0.1" - lints: - dependency: transitive - description: - name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - logging: - dependency: transitive - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.dev" - source: hosted - version: "0.12.17" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.dev" - source: hosted - version: "0.11.1" - meta: - dependency: transitive - description: - name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.dev" - source: hosted - version: "1.16.0" - mime: - dependency: transitive - description: - name: mime - sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" - url: "https://pub.dev" - source: hosted - version: "1.0.6" - mobx: - dependency: transitive - description: - name: mobx - sha256: bf1a90e5bcfd2851fc6984e20eef69557c65d9e4d0a88f5be4cf72c9819ce6b0 - url: "https://pub.dev" - source: hosted - version: "2.5.0" - mocktail: - dependency: transitive - description: - name: mocktail - sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" - url: "https://pub.dev" - source: hosted - version: "1.0.4" - nested: - dependency: transitive - description: - name: nested - sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - node_preamble: - dependency: transitive - description: - name: node_preamble - sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" - url: "https://pub.dev" - source: hosted - version: "2.0.2" - open_filex: - dependency: transitive - description: - name: open_filex - sha256: "9976da61b6a72302cf3b1efbce259200cd40232643a467aac7370addf94d6900" - url: "https://pub.dev" - source: hosted - version: "4.7.0" - package_config: - dependency: transitive - description: - name: package_config - sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc - url: "https://pub.dev" - source: hosted - version: "2.2.0" - package_info_plus: - dependency: transitive - description: - name: package_info_plus - sha256: cb44f49b6e690fa766f023d5b22cac6b9affe741dd792b6ac7ad4fabe0d7b097 - url: "https://pub.dev" - source: hosted - version: "6.0.0" - package_info_plus_platform_interface: - dependency: transitive - description: - name: package_info_plus_platform_interface - sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" - url: "https://pub.dev" - source: hosted - version: "2.0.1" - path: - dependency: transitive - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - path_provider: - dependency: transitive - description: - name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" - source: hosted - version: "2.1.5" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 - url: "https://pub.dev" - source: hosted - version: "2.2.17" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" - source: hosted - version: "2.3.0" - pedantic: - dependency: transitive - description: - name: pedantic - sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602" - url: "https://pub.dev" - source: hosted - version: "1.11.1" - permission_handler: - dependency: transitive - description: - name: permission_handler - sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" - url: "https://pub.dev" - source: hosted - version: "11.4.0" - permission_handler_android: - dependency: transitive - description: - name: permission_handler_android - sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc - url: "https://pub.dev" - source: hosted - version: "12.1.0" - permission_handler_apple: - dependency: transitive - description: - name: permission_handler_apple - sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 - url: "https://pub.dev" - source: hosted - version: "9.4.7" - permission_handler_html: - dependency: transitive - description: - name: permission_handler_html - sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" - url: "https://pub.dev" - source: hosted - version: "0.1.3+5" - permission_handler_platform_interface: - dependency: transitive - description: - name: permission_handler_platform_interface - sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 - url: "https://pub.dev" - source: hosted - version: "4.3.0" - permission_handler_windows: - dependency: transitive - description: - name: permission_handler_windows - sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" - url: "https://pub.dev" - source: hosted - version: "0.2.1" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" - url: "https://pub.dev" - source: hosted - version: "6.1.0" - platform: - dependency: transitive - description: - name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" - url: "https://pub.dev" - source: hosted - version: "3.1.0" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - pointycastle: - dependency: transitive - description: - name: pointycastle - sha256: "92aa3841d083cc4b0f4709b5c74fd6409a3e6ba833ffc7dc6a8fee096366acf5" - url: "https://pub.dev" - source: hosted - version: "4.0.0" - pool: - dependency: transitive - description: - name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" - url: "https://pub.dev" - source: hosted - version: "1.5.1" - postgres: - dependency: transitive - description: - name: postgres - sha256: f8e4f14734d096277f77ed5dddefcbc1ce18f8f7db5b7ff4b5dd6df2d9db2730 - url: "https://pub.dev" - source: hosted - version: "2.6.4" - provider: - dependency: transitive - description: - name: provider - sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" - url: "https://pub.dev" - source: hosted - version: "6.1.5" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" - url: "https://pub.dev" - source: hosted - version: "1.5.0" - qs_dart: - dependency: transitive - description: - name: qs_dart - sha256: f8d9f9f75fa6e6e72437995ccb549a27d52ec06236cfad1f4e5eceb755427649 - url: "https://pub.dev" - source: hosted - version: "1.3.7+1" - quiver: - dependency: transitive - description: - name: quiver - sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 - url: "https://pub.dev" - source: hosted - version: "3.2.1" - recursive_regex: - dependency: transitive - description: - name: recursive_regex - sha256: f7252e3d3dfd1665e594d9fe035eca6bc54139b1f2fee38256fa427ea41adc60 - url: "https://pub.dev" - source: hosted - version: "1.0.0" - redis_dart: - dependency: transitive - description: - name: redis_dart - sha256: "4de5252a1da7e602b619d71d7a43fe7df04fddfe3e1ae87e03b2f4ccb632f9cb" - url: "https://pub.dev" - source: hosted - version: "0.1.0+4" - result_dart: - dependency: transitive - description: - name: result_dart - sha256: eecf5b08b63572b348bfaa56a50f30191fe21d9edacef59d0e19a33905dd184d - url: "https://pub.dev" - source: hosted - version: "2.1.0" - rx_notifier: - dependency: transitive - description: - name: rx_notifier - sha256: "08201324c0a2c6987c13798277f9bab592fcc2dfde362d21789f60eed8bd9ea2" - url: "https://pub.dev" - source: hosted - version: "2.3.0" - rx_notifier_annotation: - dependency: transitive - description: - name: rx_notifier_annotation - sha256: "362496563d617aa6fa21ab9c131f66348f3266b6580d69ecc23d2c23a52070a2" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - rxdart: - dependency: transitive - description: - name: rxdart - sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" - url: "https://pub.dev" - source: hosted - version: "0.27.7" - sasl_scram: - dependency: transitive - description: - name: sasl_scram - sha256: a47207a436eb650f8fdcf54a2e2587b850dc3caef9973ce01f332b07a6fc9cb9 - url: "https://pub.dev" - source: hosted - version: "0.1.1" - saslprep: - dependency: transitive - description: - name: saslprep - sha256: "3d421d10be9513bf4459c17c5e70e7b8bc718c9fc5ad4ba5eb4f5fd27396f740" - url: "https://pub.dev" - source: hosted - version: "1.0.3" - sensors_plus: - dependency: transitive - description: - name: sensors_plus - sha256: "6898cd4490ffc27fea4de5976585e92fae55355175d46c6c3b3d719d42f9e230" - url: "https://pub.dev" - source: hosted - version: "5.0.1" - sensors_plus_platform_interface: - dependency: transitive - description: - name: sensors_plus_platform_interface - sha256: bc472d6cfd622acb4f020e726433ee31788b038056691ba433fec80e448a094f - url: "https://pub.dev" - source: hosted - version: "1.2.0" - share_plus: - dependency: transitive - description: - name: share_plus - sha256: ef3489a969683c4f3d0239010cc8b7a2a46543a8d139e111c06c558875083544 - url: "https://pub.dev" - source: hosted - version: "9.0.0" - share_plus_platform_interface: - dependency: transitive - description: - name: share_plus_platform_interface - sha256: "0f9e4418835d1b2c3ae78fdb918251959106cefdbc4dd43526e182f80e82f6d4" - url: "https://pub.dev" - source: hosted - version: "4.0.0" - shelf: - dependency: transitive - description: - name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 - url: "https://pub.dev" - source: hosted - version: "1.4.1" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - shelf_static: - dependency: transitive - description: - name: shelf_static - sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 - url: "https://pub.dev" - source: hosted - version: "1.1.3" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" - url: "https://pub.dev" - source: hosted - version: "1.0.4" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b - url: "https://pub.dev" - source: hosted - version: "2.1.2" - source_maps: - dependency: transitive - description: - name: source_maps - sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" - url: "https://pub.dev" - source: hosted - version: "0.10.13" - source_span: - dependency: transitive - description: - name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.dev" - source: hosted - version: "1.10.1" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" - source: hosted - version: "1.12.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - stream_transform: - dependency: transitive - description: - name: stream_transform - sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 - url: "https://pub.dev" - source: hosted - version: "2.1.1" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" - source: hosted - version: "1.4.1" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" - source: hosted - version: "1.2.2" - test: - dependency: transitive - description: - name: test - sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" - url: "https://pub.dev" - source: hosted - version: "1.25.15" - test_api: - dependency: transitive - description: - name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd - url: "https://pub.dev" - source: hosted - version: "0.7.4" - test_core: - dependency: transitive - description: - name: test_core - sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" - url: "https://pub.dev" - source: hosted - version: "0.6.8" - timezone: - dependency: transitive - description: - name: timezone - sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" - url: "https://pub.dev" - source: hosted - version: "0.9.4" - triple: - dependency: transitive - description: - name: triple - sha256: "0d049c7d44fbe093432c72ec059a958b1b22e0748b3b90e806383ef2376f34cb" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" - url: "https://pub.dev" - source: hosted - version: "1.3.1" - unorm_dart: - dependency: transitive - description: - name: unorm_dart - sha256: "23d8bf65605401a6a32cff99435fed66ef3dab3ddcad3454059165df46496a3b" - url: "https://pub.dev" - source: hosted - version: "0.3.0" - url_launcher: - dependency: transitive - description: - name: url_launcher - sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" - url: "https://pub.dev" - source: hosted - version: "6.3.1" - url_launcher_android: - dependency: transitive - description: - name: url_launcher_android - sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" - url: "https://pub.dev" - source: hosted - version: "6.3.16" - url_launcher_ios: - dependency: transitive - description: - name: url_launcher_ios - sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" - url: "https://pub.dev" - source: hosted - version: "6.3.3" - url_launcher_linux: - dependency: transitive - description: - name: url_launcher_linux - sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" - url: "https://pub.dev" - source: hosted - version: "3.2.1" - url_launcher_macos: - dependency: transitive - description: - name: url_launcher_macos - sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" - url: "https://pub.dev" - source: hosted - version: "3.2.2" - url_launcher_platform_interface: - dependency: transitive - description: - name: url_launcher_platform_interface - sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - url_launcher_web: - dependency: transitive - description: - name: url_launcher_web - sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - url_launcher_windows: - dependency: transitive - description: - name: url_launcher_windows - sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" - url: "https://pub.dev" - source: hosted - version: "3.1.4" - uuid: - dependency: transitive - description: - name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff - url: "https://pub.dev" - source: hosted - version: "4.5.1" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 - url: "https://pub.dev" - source: hosted - version: "15.0.0" - watcher: - dependency: transitive - description: - name: watcher - sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - weak_map: - dependency: transitive - description: - name: weak_map - sha256: "5f8e5d5ce57dc624db5fae814dd689ccae1f17f92b426e52f0a7cbe7f6f4ab97" - url: "https://pub.dev" - source: hosted - version: "4.0.1" - web: - dependency: transitive - description: - name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" - url: "https://pub.dev" - source: hosted - version: "0.5.1" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" - url: "https://pub.dev" - source: hosted - version: "2.4.5" - webkit_inspection_protocol: - dependency: transitive - description: - name: webkit_inspection_protocol - sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" - url: "https://pub.dev" - source: hosted - version: "1.2.1" - win32: - dependency: transitive - description: - name: win32 - sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" - url: "https://pub.dev" - source: hosted - version: "5.13.0" - x509: - dependency: transitive - description: - name: x509 - sha256: cbd1a63846884afd273cda247b0365284c8d85a365ca98e110413f93d105b935 - url: "https://pub.dev" - source: hosted - version: "0.2.4+3" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - xml: - dependency: transitive - description: - name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 - url: "https://pub.dev" - source: hosted - version: "6.5.0" - yaml: - dependency: transitive - description: - name: yaml - sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" - url: "https://pub.dev" - source: hosted - version: "3.1.1" -sdks: - dart: ">=3.8.0 <4.0.0" - flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index a778f6f0..90795767 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,12 +1,23 @@ -name: flutter_modular_workspace -publish_to: none +name: flutter_modular +description: Smart project structure with dependency injection and route management for Flutter. +version: 7.0.0 +homepage: https://github.com/Flutterando/modular +repository: https://github.com/Flutterando/modular +issue_tracker: https://github.com/Flutterando/modular/issues + environment: - sdk: ">=3.6.0 <4.0.0" + sdk: ">=3.11.0 <4.0.0" + +dependencies: + auto_injector: ">=2.1.0 <3.0.0" + meta: ">=1.3.0 <2.0.0" + web: ">=0.5.0 <2.0.0" + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter -workspace: - - modular_core - - flutter_modular - - shelf_modular - # examples - - flutter_modular/example - - shelf_modular/example +dev_dependencies: + flutterando_analysis: ^0.0.2 + flutter_test: + sdk: flutter diff --git a/shelf_modular/.gitignore b/shelf_modular/.gitignore deleted file mode 100644 index c9557bef..00000000 --- a/shelf_modular/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Files and directories created by pub. -.dart_tool/ -.packages - -# Conventional directory for build output. -build/ -coverage/ -pubspec.lock \ No newline at end of file diff --git a/shelf_modular/.vscode/settings.json b/shelf_modular/.vscode/settings.json deleted file mode 100644 index 8c0e5d1b..00000000 --- a/shelf_modular/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "conventionalCommits.scopes": [ - "shelf_modular" - ] -} \ No newline at end of file diff --git a/shelf_modular/CHANGELOG.md b/shelf_modular/CHANGELOG.md deleted file mode 100644 index f076496d..00000000 --- a/shelf_modular/CHANGELOG.md +++ /dev/null @@ -1,68 +0,0 @@ -## 2.2.0 - 2022/01/02 -- feat: added broadcast function - -## 2.0.1 - 2022/07/22 -- fix: Bind aways Scoped. - -## 2.0.0 - 2022/07/21 - -- [BREAK CHANGE]: Update to new Dart 2.17. -Preview version will no longer be supported. - -- [BREAK CHANGE]: New auto-dispose configuration. -Starting with version 2.0, Modular will provide the `Bind.onDispose` property for calls to destroy, close or dispose methods FOR EACH BIND. This will make the dispose settings more straightforward and less universal. Therefore, Modular will manage the destruction of Binds that implement `Disposable` only. This is the new configuration: -```dart -@override -final List binds = [ - Bind.singleton((i) => MyBloc(), onDispose: (bloc) => bloc.close()), -]; -``` -The `Bind.onDispose` CANNOT be used in Bind type factory. -You can choose to use `Bind.onDispose` or implement the `Disposable` class. - -- [BREAK CHANGE] `Bind.export` works only after imported. - -- feat: Added `middlewares` propertie in Modular() handler; - -```dart -final handler = Modular( - module: AppModule(), - middlewares: [ - logRequests(), //add any shelf middleware - CustomModularMiddleware(), // implementations of ModularMiddleware - ], - ); - - var server = await io.serve(handler, '0.0.0.0', 4000); - print('Serving at http://${server.address.host}:${server.port}'); -``` - -- feat: Added new Middleware system: -```dart -class AuthGuard extends ModularMiddleware { - @override - Handler execute(Handler handler, [ModularRoute? route]) { - return (request) { - final accessToken = request.headers['Authorization']?.split(' ').last; - if (accessToken == null || accessToken.isEmpty || accessToken != '1234') { - return Response.forbidden(jsonEncode({'error': 'Not authorized'})); - } - return handler(request); - }; - } -} -``` - - -## 1.0.2 - 2022/04/05 - -* Update modular_core - -## 1.0.1 - 2021/12/31 - -* Fixed "bind replaced" bug - -## 1.0.0 - 2021/10/20 - -* initial release. -* New doc! diff --git a/shelf_modular/LICENSE b/shelf_modular/LICENSE deleted file mode 100644 index 22d8ef64..00000000 --- a/shelf_modular/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -The MIT License -License Copyright: Flutterando. -License License: Flutterando. -License Contact: Flutterando. -SPDX short identifier: MIT -Further resources... -  -Begin license text. -Copyright 2021 Flutterando -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -End license text. \ No newline at end of file diff --git a/shelf_modular/README.md b/shelf_modular/README.md deleted file mode 100644 index 9fd33e4b..00000000 --- a/shelf_modular/README.md +++ /dev/null @@ -1,22 +0,0 @@ -[![codecov](https://codecov.io/gh/Flutterando/modular/branch/master/graph/badge.svg?token=uO4x25wWuU)](https://codecov.io/gh/Flutterando/modular) -![CI](https://github.com/Flutterando/modular/workflows/CI/badge.svg) -![LICENSE](https://img.shields.io/hexpm/l/modular) -[![pub package](https://img.shields.io/pub/v/flutter_modular.svg)](https://pub.dev/packages/flutter_modular) -[![GitHub stars](https://badgen.net/github/stars/Flutterando/modular)](https://GitHub.com/Flutterando/modular/stargazers/) -[![All Contributors](https://img.shields.io/badge/all_contributors-46-orange.svg?style=flat-square)](#contributors-) - -# shelf_modular - -Smart project structure with dependency injection and route management - -# Getting started with Modular - -- [shelf_modular Documentation](https://modular.flutterando.com.br/docs/shelf_modular/start) - -- [Modular Site](https://modular.flutterando.com.br) - -## Features and bugs - -Please send feature requests and bugs at the [issue tracker](https://github.com/Flutterando/modular/issues). - -This README was created based on templates made available by Stagehand under a BSD-style [license](https://github.com/dart-lang/stagehand/blob/master/LICENSE). \ No newline at end of file diff --git a/shelf_modular/analysis_options.yaml b/shelf_modular/analysis_options.yaml deleted file mode 100644 index 0e9852c9..00000000 --- a/shelf_modular/analysis_options.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutterando_analysis/dart_package.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - public_member_api_docs: false - cascade_invocations: false - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options \ No newline at end of file diff --git a/shelf_modular/example/.gitignore b/shelf_modular/example/.gitignore deleted file mode 100644 index 7c89c784..00000000 --- a/shelf_modular/example/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Files and directories created by pub. -.dart_tool/ -.packages - -# Conventional directory for build output. -build/ -pubspec.lock \ No newline at end of file diff --git a/shelf_modular/example/CHANGELOG.md b/shelf_modular/example/CHANGELOG.md deleted file mode 100644 index effe43c8..00000000 --- a/shelf_modular/example/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -## 1.0.0 - -- Initial version. diff --git a/shelf_modular/example/README.md b/shelf_modular/example/README.md deleted file mode 100644 index a2ce60c1..00000000 --- a/shelf_modular/example/README.md +++ /dev/null @@ -1 +0,0 @@ -A web server built using [Shelf](https://pub.dev/packages/shelf). diff --git a/shelf_modular/example/analysis_options.yaml b/shelf_modular/example/analysis_options.yaml deleted file mode 100644 index 18b40b8d..00000000 --- a/shelf_modular/example/analysis_options.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Defines a default set of lint rules enforced for projects at Google. For -# details and rationale, see -# https://github.com/dart-lang/pedantic#enabled-lints. - -include: package:pedantic/analysis_options.yaml - -# For lint rules and documentation, see http://dart-lang.github.io/linter/lints. - -# Uncomment to specify additional rules. -# linter: -# rules: -# - camel_case_types - -# analyzer: -# exclude: -# - path/to/excluded/files/** diff --git a/shelf_modular/example/bin/example.dart b/shelf_modular/example/bin/example.dart deleted file mode 100644 index e05a3695..00000000 --- a/shelf_modular/example/bin/example.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:shelf/shelf.dart'; -import 'package:shelf/shelf_io.dart' as io; -import 'package:shelf_modular/shelf_modular.dart'; -import 'package:shelf_modular_example/src/app_module.dart'; - -void main(List args) async { - final handler = Modular( - module: AppModule(), - middlewares: [ - logRequests(), - AuthMiddleware(), - ], - ); - - var server = await io.serve(handler, '0.0.0.0', 4000); - print('Serving at http://${server.address.host}:${server.port}'); -} - -class AuthMiddleware extends ModularMiddleware { - @override - Handler call(Handler handler, [ModularRoute? route]) { - return (request) { - return handler(request); - }; - } -} diff --git a/shelf_modular/example/docker-compose.yaml b/shelf_modular/example/docker-compose.yaml deleted file mode 100644 index 4631ff97..00000000 --- a/shelf_modular/example/docker-compose.yaml +++ /dev/null @@ -1,23 +0,0 @@ -version: '3.7' -services: - redis: - restart: always - image: redis:latest - ports: - - 6379:6379 - postgres: - image: postgres:10.5 - restart: always - environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres - ports: - - '5432:5432' - volumes: - - postgres-data:/var/lib/postgresql/data - # copy the sql script to create tables - - ./sql/create_tables.sql:/docker-entrypoint-initdb.d/create_tables.sql - # copy the sql script to fill tables - - ./sql/fill_tables.sql:/docker-entrypoint-initdb.d/fill_tables.sql -volumes: - postgres-data: diff --git a/shelf_modular/example/lib/src/app_module.dart b/shelf_modular/example/lib/src/app_module.dart deleted file mode 100644 index 2a5f9b36..00000000 --- a/shelf_modular/example/lib/src/app_module.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:shelf/shelf.dart'; -import 'package:shelf_modular/shelf_modular.dart'; - -import 'auth/auth_module.dart'; -import 'auth/presenter/guards/auth_guard.dart'; -import 'auth/presenter/websocket/chat_websocket.dart'; - -class AppModule extends Module { - @override - void routes(r) { - r.get('/', (Request request) => Response.ok('ok!!')); - r.get('/2', (Request request) => Response.ok('ok!!'), - middlewares: [AuthGuard3()]); - r.module('/auth', module: AuthModule()); - r.websocket('/websocket', websocket: ChatWebSocket()); - } -} diff --git a/shelf_modular/example/lib/src/auth/auth_module.dart b/shelf_modular/example/lib/src/auth/auth_module.dart deleted file mode 100644 index 9269676b..00000000 --- a/shelf_modular/example/lib/src/auth/auth_module.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:shelf_modular/shelf_modular.dart'; -import 'package:shelf_modular_example/src/auth/external/shared/token/token_manager.dart'; -import 'package:shelf_modular_example/src/auth/presenter/auth_resource.dart'; - -import 'domain/usecases/check_token.dart'; -import 'domain/usecases/login.dart'; -import 'domain/usecases/refresh_token.dart'; -import 'external/postgres/datasources/postgres_auth_datasource.dart'; -import 'external/postgres/datasources/postgres_connect.dart'; -import 'external/shared/redis/redis_service.dart'; -import 'infra/repositories/auth_repository.dart'; - -class AuthModule extends Module { - @override - void binds(i) { - //external - i.addSingleton(RedisService.new); - i.addSingleton(PostgresConnect.new); - i.add(TokenManager.new); - i.add(AuthDatasourceImpl.new); - //infra - i.add(AuthRepositoryImpl.new); - //domain - i.add(LoginImpl.new); - i.add(RefreshTokenImpl.new); - i.add(CheckTokenImpl.new); - } - - @override - void routes(r) { - r.resource(AuthResource()); - } -} diff --git a/shelf_modular/example/lib/src/auth/domain/entities/tokenization.dart b/shelf_modular/example/lib/src/auth/domain/entities/tokenization.dart deleted file mode 100644 index 044b7610..00000000 --- a/shelf_modular/example/lib/src/auth/domain/entities/tokenization.dart +++ /dev/null @@ -1,10 +0,0 @@ -class Tokenization { - final String accessToken; - final String refreshToken; - final int expiresIn; - - const Tokenization( - {required this.accessToken, - required this.refreshToken, - required this.expiresIn}); -} diff --git a/shelf_modular/example/lib/src/auth/domain/entities/user.dart b/shelf_modular/example/lib/src/auth/domain/entities/user.dart deleted file mode 100644 index 3c623019..00000000 --- a/shelf_modular/example/lib/src/auth/domain/entities/user.dart +++ /dev/null @@ -1,7 +0,0 @@ -class User { - final int id; - final String name; - final String email; - - const User({required this.id, required this.name, required this.email}); -} diff --git a/shelf_modular/example/lib/src/auth/domain/errors/errors.dart b/shelf_modular/example/lib/src/auth/domain/errors/errors.dart deleted file mode 100644 index 035616a0..00000000 --- a/shelf_modular/example/lib/src/auth/domain/errors/errors.dart +++ /dev/null @@ -1,11 +0,0 @@ -abstract class AuthException implements Exception { - final String message; - final StackTrace? stackTrace; - - const AuthException(this.message, [this.stackTrace]); - - @override - String toString() { - return '$runtimeType: $message\n${stackTrace ?? ''}'; - } -} diff --git a/shelf_modular/example/lib/src/auth/domain/repositories/auth_repository.dart b/shelf_modular/example/lib/src/auth/domain/repositories/auth_repository.dart deleted file mode 100644 index 2ea8bf0b..00000000 --- a/shelf_modular/example/lib/src/auth/domain/repositories/auth_repository.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:fpdart/fpdart.dart'; -import 'package:shelf_modular_example/src/auth/domain/entities/tokenization.dart'; -import 'package:shelf_modular_example/src/auth/domain/errors/errors.dart'; - -abstract class AuthRepository { - Future> fromCredentials( - {required String email, required String password}); - Future> refresh( - {required String refreshToken}); - Future> checkToken({required String accessToken}); -} diff --git a/shelf_modular/example/lib/src/auth/domain/usecases/check_token.dart b/shelf_modular/example/lib/src/auth/domain/usecases/check_token.dart deleted file mode 100644 index 8e00cb52..00000000 --- a/shelf_modular/example/lib/src/auth/domain/usecases/check_token.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:fpdart/fpdart.dart'; -import 'package:shelf_modular_example/src/auth/domain/errors/errors.dart'; -import 'package:shelf_modular_example/src/auth/domain/repositories/auth_repository.dart'; - -abstract class CheckToken { - Future> call({required String accessToken}); -} - -class CheckTokenImpl implements CheckToken { - final AuthRepository repository; - - CheckTokenImpl(this.repository); - - @override - Future> call( - {required String accessToken}) async { - return await repository.checkToken(accessToken: accessToken); - } -} diff --git a/shelf_modular/example/lib/src/auth/domain/usecases/login.dart b/shelf_modular/example/lib/src/auth/domain/usecases/login.dart deleted file mode 100644 index ee56dce3..00000000 --- a/shelf_modular/example/lib/src/auth/domain/usecases/login.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'dart:convert'; - -import 'package:fpdart/fpdart.dart'; -import 'package:shelf_modular_example/src/auth/domain/entities/tokenization.dart'; -import 'package:shelf_modular_example/src/auth/domain/errors/errors.dart'; -import 'package:shelf_modular_example/src/auth/domain/repositories/auth_repository.dart'; - -abstract class Login { - Future> call( - {required String credentials}); -} - -class LoginImpl implements Login { - final AuthRepository repository; - - LoginImpl(this.repository); - - @override - Future> call( - {required String credentials}) async { - final decoded = String.fromCharCodes(base64Decode(credentials)); - final splited = decoded.split(':'); - final email = splited[0]; - final password = splited[1]; - - return await repository.fromCredentials(email: email, password: password); - } -} diff --git a/shelf_modular/example/lib/src/auth/domain/usecases/refresh_token.dart b/shelf_modular/example/lib/src/auth/domain/usecases/refresh_token.dart deleted file mode 100644 index ea0e8d87..00000000 --- a/shelf_modular/example/lib/src/auth/domain/usecases/refresh_token.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:fpdart/fpdart.dart'; -import 'package:shelf_modular_example/src/auth/domain/entities/tokenization.dart'; -import 'package:shelf_modular_example/src/auth/domain/errors/errors.dart'; -import 'package:shelf_modular_example/src/auth/domain/repositories/auth_repository.dart'; - -abstract class RefreshToken { - Future> call( - {required String refreshToken}); -} - -class RefreshTokenImpl implements RefreshToken { - final AuthRepository repository; - - RefreshTokenImpl(this.repository); - - @override - Future> call( - {required String refreshToken}) async { - return await repository.refresh(refreshToken: refreshToken); - } -} diff --git a/shelf_modular/example/lib/src/auth/external/errors/errors.dart b/shelf_modular/example/lib/src/auth/external/errors/errors.dart deleted file mode 100644 index 28f30173..00000000 --- a/shelf_modular/example/lib/src/auth/external/errors/errors.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:shelf_modular_example/src/auth/domain/errors/errors.dart'; - -class NotAuthorized extends AuthException { - const NotAuthorized(String message, [StackTrace? stackTrace]) - : super(message, stackTrace); -} - -class JWTViolations extends AuthException { - final List violations; - const JWTViolations( - String message, - this.violations, - ) : super(message); - - @override - String toString() { - return 'JWTViolations: $message\n$violations'; - } -} diff --git a/shelf_modular/example/lib/src/auth/external/postgres/datasources/postgres_auth_datasource.dart b/shelf_modular/example/lib/src/auth/external/postgres/datasources/postgres_auth_datasource.dart deleted file mode 100644 index 6fd11514..00000000 --- a/shelf_modular/example/lib/src/auth/external/postgres/datasources/postgres_auth_datasource.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:shelf_modular_example/src/auth/domain/entities/tokenization.dart'; -import 'package:shelf_modular_example/src/auth/external/errors/errors.dart'; -import 'package:shelf_modular_example/src/auth/external/shared/redis/redis_service.dart'; -import 'package:shelf_modular_example/src/auth/external/shared/token/token_manager.dart'; -import 'package:shelf_modular_example/src/auth/infra/datasources/auth_datasource.dart'; -import 'package:uuid/uuid.dart'; - -import 'postgres_connect.dart'; - -class AuthDatasourceImpl implements AuthDatasource { - final TokenManager tokenManager; - final IRedisService redis; - final IPostgresConnect pg; - - AuthDatasourceImpl( - {required this.tokenManager, required this.redis, required this.pg}); - - @override - Future fromCredentials( - {required String email, required String password}) async { - final connection = await pg.connection; - final results = await connection.mappedResultsQuery( - 'SELECT id FROM users WHERE email=@email AND password=@password', - substitutionValues: { - 'email': email, - 'password': password, - }, - ); - final userList = results - .where((element) => element.containsKey('users')) - .map((e) => e['users']!); - - if (userList.isEmpty) { - throw NotAuthorized('acesso negado'); - } - - final userMap = userList.first; - final refreshToken = Uuid().v1(); - final tokenization = _generateTokenization(userMap, refreshToken); - await redis.setMap(refreshToken, userMap, Duration(seconds: 60)); - - return tokenization; - } - - @override - Future refresh({required String refreshToken}) async { - final userIdMap = await redis.getMap(refreshToken); - if (userIdMap.isEmpty) { - throw NotAuthorized('Revoked token'); - } - - await redis.delete(refreshToken); - refreshToken = Uuid().v1(); - await redis.setMap(refreshToken, userIdMap, Duration(seconds: 60)); - final tokenization = _generateTokenization(userIdMap, refreshToken); - return tokenization; - } - - @override - Future checkToken({required String accessToken}) async { - await tokenManager.validateToken(accessToken); - } - - Tokenization _generateTokenization(Map claims, String newRefreshToken) { - final expiresIn = const Duration(seconds: 30); - - final accessToken = tokenManager.generateToken({ - 'exp': tokenManager.expireTime(expiresIn), - ...claims, - }); - - final tokenization = Tokenization( - expiresIn: expiresIn.inSeconds, - accessToken: accessToken, - refreshToken: newRefreshToken); - return tokenization; - } -} diff --git a/shelf_modular/example/lib/src/auth/external/postgres/datasources/postgres_connect.dart b/shelf_modular/example/lib/src/auth/external/postgres/datasources/postgres_connect.dart deleted file mode 100644 index 33a6289c..00000000 --- a/shelf_modular/example/lib/src/auth/external/postgres/datasources/postgres_connect.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'dart:async'; - -import 'package:postgres/postgres.dart'; -import 'package:shelf_modular/shelf_modular.dart'; - -abstract class IPostgresConnect implements Disposable { - Future get connection; -} - -class PostgresConnect implements IPostgresConnect { - final _completer = Completer(); - - PostgresConnect() { - _completer.complete(_openConection()); - } - - Future _openConection() async { - final connection = PostgreSQLConnection('localhost', 5432, 'postgres', - username: 'postgres', password: 'postgres'); - await connection.open(); - return connection; - } - - @override - void dispose() async { - final pg = await _completer.future; - await pg.close(); - print('Postgres Closed!'); - } - - @override - Future get connection => _completer.future; -} diff --git a/shelf_modular/example/lib/src/auth/external/shared/redis/redis_service.dart b/shelf_modular/example/lib/src/auth/external/shared/redis/redis_service.dart deleted file mode 100644 index 75705fd3..00000000 --- a/shelf_modular/example/lib/src/auth/external/shared/redis/redis_service.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'dart:async'; - -import 'package:redis_dart/redis_dart.dart'; -import 'package:shelf_modular/shelf_modular.dart'; - -abstract class IRedisService implements Disposable { - Future setMap( - String key, Map map, Duration expiresIn); - - Future> getMap(String key); - Future delete(String key); -} - -class RedisService implements IRedisService { - final _completer = Completer(); - - RedisService() { - _completer.complete(RedisClient.connect('localhost')); - } - - @override - Future setMap( - String key, Map map, Duration expiresIn) async { - final redis = await _completer.future; - final result = await redis.setMap(key, map); - await redis.expireAt(key, DateTime.now().add(expiresIn)); - return result; - } - - @override - Future> getMap(String key) async { - final redis = await _completer.future; - final reply = await redis.getMap(key); - return (reply.value as Map).cast(); - } - - @override - Future delete(String key) async { - final redis = await _completer.future; - await redis.delete(key); - } - - @override - void dispose() async { - final redis = await _completer.future; - await redis.close(); - print('Redis Closed!'); - } -} diff --git a/shelf_modular/example/lib/src/auth/external/shared/token/token_manager.dart b/shelf_modular/example/lib/src/auth/external/shared/token/token_manager.dart deleted file mode 100644 index 8c8c6f00..00000000 --- a/shelf_modular/example/lib/src/auth/external/shared/token/token_manager.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:jose/jose.dart'; -import 'package:shelf_modular_example/src/auth/external/errors/errors.dart'; - -class TokenManager { - final key = JsonWebKey.fromJson({ - 'kty': 'oct', - 'k': - 'AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow', - }); - - int expireTime([Duration duration = const Duration(hours: 3)]) => Duration( - milliseconds: DateTime.now().add(duration).millisecondsSinceEpoch) - .inSeconds; - - String generateToken(Map claimsMap) { - var claims = JsonWebTokenClaims.fromJson(claimsMap); - - // create a builder, decoding the JWT in a JWS, so using a - // JsonWebSignatureBuilder - var builder = JsonWebSignatureBuilder(); - - // set the content - builder.jsonContent = claims.toJson(); - - // add a key to sign, can only add one for JWT - builder.addRecipient(key, algorithm: 'HS256'); - - // build the jws - var jws = builder.build(); - - return jws.toCompactSerialization(); - } - - Future validateToken(String encoded) async { - // create key store to verify the signature - var keyStore = JsonWebKeyStore()..addKey(key); - - // applicable for JWT inside JWE - var jwt = await JsonWebToken.decodeAndVerify(encoded, keyStore); - - var violations = jwt.claims.validate(); - - if (violations.isNotEmpty) { - final list = violations.map((e) => e.toString()).toList(); - throw JWTViolations( - 'One or more violations in current access token', list); - } - } -} diff --git a/shelf_modular/example/lib/src/auth/infra/datasources/auth_datasource.dart b/shelf_modular/example/lib/src/auth/infra/datasources/auth_datasource.dart deleted file mode 100644 index 2a5a86bb..00000000 --- a/shelf_modular/example/lib/src/auth/infra/datasources/auth_datasource.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:shelf_modular_example/src/auth/domain/entities/tokenization.dart'; - -abstract class AuthDatasource { - Future fromCredentials( - {required String email, required String password}); - Future refresh({required String refreshToken}); - Future checkToken({required String accessToken}); -} diff --git a/shelf_modular/example/lib/src/auth/infra/repositories/auth_repository.dart b/shelf_modular/example/lib/src/auth/infra/repositories/auth_repository.dart deleted file mode 100644 index 0960baa7..00000000 --- a/shelf_modular/example/lib/src/auth/infra/repositories/auth_repository.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:fpdart/fpdart.dart'; -import 'package:shelf_modular_example/src/auth/domain/entities/tokenization.dart'; -import 'package:shelf_modular_example/src/auth/domain/errors/errors.dart'; -import 'package:shelf_modular_example/src/auth/domain/repositories/auth_repository.dart'; -import 'package:shelf_modular_example/src/auth/infra/datasources/auth_datasource.dart'; - -class AuthRepositoryImpl implements AuthRepository { - final AuthDatasource datasource; - - AuthRepositoryImpl(this.datasource); - - @override - Future> fromCredentials( - {required String email, required String password}) async { - try { - final result = - await datasource.fromCredentials(email: email, password: password); - return Right(result); - } on AuthException catch (e) { - return Left(e); - } - } - - @override - Future> refresh( - {required String refreshToken}) async { - try { - final result = await datasource.refresh(refreshToken: refreshToken); - return Right(result); - } on AuthException catch (e) { - return Left(e); - } - } - - @override - Future> checkToken( - {required String accessToken}) async { - try { - await datasource.checkToken(accessToken: accessToken); - return Right(unit); - } on AuthException catch (e) { - return Left(e); - } - } -} diff --git a/shelf_modular/example/lib/src/auth/presenter/auth_resource.dart b/shelf_modular/example/lib/src/auth/presenter/auth_resource.dart deleted file mode 100644 index 3f3a0c04..00000000 --- a/shelf_modular/example/lib/src/auth/presenter/auth_resource.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:shelf/shelf.dart'; -import 'package:shelf_modular/shelf_modular.dart'; -import 'package:shelf_modular_example/src/auth/domain/usecases/login.dart'; -import 'package:shelf_modular_example/src/auth/domain/usecases/refresh_token.dart'; -import 'package:shelf_modular_example/src/auth/presenter/extensions/tokenization_extension.dart'; - -import 'guards/auth_guard.dart'; - -class AuthResource implements Resource { - @override - void routes(RouteManager r) { - r - ..get('/login', login) - ..get('/refresh_token/:token', refreshToken) - ..get('/check_token', checkToken, middlewares: [AuthGuard()]) - ..get('/get_user', getUser, middlewares: [AuthGuard()]); - } - - FutureOr login(Request request, Injector injector) async { - final credentials = request.headers['Authorization']?.split(' ').last; - - if (credentials == null || credentials.isEmpty) { - return Response.forbidden( - jsonEncode({'error': 'Authorization not found'})); - } - - final result = await injector.get().call(credentials: credentials); - return result.fold( - (l) => Response.forbidden(jsonEncode({'error': l.message})), - (r) => Response.ok(r.toJson())); - } - - FutureOr refreshToken( - Request request, ModularArguments args, Injector injector) async { - final result = await injector - .get() - .call(refreshToken: args.params['token']); - return result.fold( - (l) => Response.forbidden(jsonEncode({'error': l.message})), - (r) => Response.ok(r.toJson())); - } - - FutureOr checkToken() { - return Response.ok(jsonEncode({'status': 'ok!'})); - } - - FutureOr getUser() { - return Response.ok(jsonEncode({'status': 'user'})); - } -} diff --git a/shelf_modular/example/lib/src/auth/presenter/extensions/tokenization_extension.dart b/shelf_modular/example/lib/src/auth/presenter/extensions/tokenization_extension.dart deleted file mode 100644 index e0ef8865..00000000 --- a/shelf_modular/example/lib/src/auth/presenter/extensions/tokenization_extension.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'dart:convert'; - -import 'package:shelf_modular_example/src/auth/domain/entities/tokenization.dart'; - -extension UserExtension on Tokenization { - Map toMap() { - return { - 'expires_id': expiresIn, - 'access_token': accessToken, - 'refresh_token': refreshToken, - }; - } - - String toJson() => jsonEncode(toMap()); -} diff --git a/shelf_modular/example/lib/src/auth/presenter/extensions/user_extension.dart b/shelf_modular/example/lib/src/auth/presenter/extensions/user_extension.dart deleted file mode 100644 index 48f3cf73..00000000 --- a/shelf_modular/example/lib/src/auth/presenter/extensions/user_extension.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'dart:convert'; - -import 'package:shelf_modular_example/src/auth/domain/entities/user.dart'; - -extension UserExtension on User { - Map toMap() { - return { - 'id': id, - 'name': name, - 'email': email, - }; - } - - String toJson() => jsonEncode(toMap()); -} diff --git a/shelf_modular/example/lib/src/auth/presenter/guards/auth_guard.dart b/shelf_modular/example/lib/src/auth/presenter/guards/auth_guard.dart deleted file mode 100644 index e762eb28..00000000 --- a/shelf_modular/example/lib/src/auth/presenter/guards/auth_guard.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:shelf/shelf.dart'; -import 'package:shelf_modular/shelf_modular.dart'; -import 'package:shelf_modular_example/src/auth/domain/usecases/check_token.dart'; - -class AuthGuard extends RouteGuard { - @override - FutureOr canActivate(Request request, Route route) async { - final accessToken = request.headers['Authorization']?.split(' ').last; - if (accessToken == null || accessToken.isEmpty) { - return false; - } - final result = - await Modular.get().call(accessToken: accessToken); - return result.fold((l) => false, (r) => true); - } -} - -class AuthGuard2 extends RouteGuard { - @override - FutureOr canActivate(Request request, Route route) async { - final accessToken = request.headers['Authorization']?.split(' ').last; - if (accessToken == null || accessToken.isEmpty) { - return false; - } - - return accessToken == '1234'; - } -} - -class AuthGuard3 extends ModularMiddleware { - @override - Handler call(Handler handler, [ModularRoute? route]) { - return (request) { - final accessToken = request.headers['Authorization']?.split(' ').last; - if (accessToken == null || accessToken.isEmpty || accessToken != '1234') { - return Response.forbidden(jsonEncode({'error': 'Not authorized'})); - } - return handler(request); - }; - } -} diff --git a/shelf_modular/example/lib/src/auth/presenter/websocket/chat_websocket.dart b/shelf_modular/example/lib/src/auth/presenter/websocket/chat_websocket.dart deleted file mode 100644 index 7140349d..00000000 --- a/shelf_modular/example/lib/src/auth/presenter/websocket/chat_websocket.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:shelf_modular/shelf_modular.dart'; - -class ChatWebSocket extends WebSocketResource { - @override - void connect(socket) { - socket.emit('SERVIDOR: Novo cliente conectado!'); - } - - @override - void onMessage(covariant String data, socket) { - if (data.startsWith('@enterroom ')) { - final room = data.replaceFirst('@enterroom ', ''); - socket.joinRoom(room); - socket.sink.add('Você entrou na sala $room'); - socket.emit('ROOM: Novo cliente conectado', [room]); - } else if (data.startsWith('@leaveroom ')) { - final room = data.replaceFirst('@leaveroom ', ''); - socket.sink.add('Você saiu na sala $room'); - socket.leaveRoom(room); - socket.emit('ROOM: Cliente saiu da sala', [room]); - } else if (data.startsWith('@changename ')) { - final name = data.replaceFirst('@changename ', ''); - socket.emitToRooms('${socket.tag} trocou o nome para $name'); - socket.sink.add('Agora seu nome é $name'); - socket.tag = name; - } else if (socket.enteredRooms.isNotEmpty) { - socket.emitToRooms('${socket.tag}: $data'); - } else { - socket.sink.add('Entre em uma sala pra tc'); - } - } - - @override - void disconnect(socket) { - socket.emit('SERVIDOR: Cliente desconectado'); - } -} diff --git a/shelf_modular/example/melos_shelf_modular_example.iml b/shelf_modular/example/melos_shelf_modular_example.iml deleted file mode 100644 index 389d07a1..00000000 --- a/shelf_modular/example/melos_shelf_modular_example.iml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/shelf_modular/example/pubspec.yaml b/shelf_modular/example/pubspec.yaml deleted file mode 100644 index d950fbbb..00000000 --- a/shelf_modular/example/pubspec.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: shelf_modular_example -description: A web server built using the shelf package. -version: 1.0.0 -publish_to: none -# homepage: https://www.example.com - -resolution: workspace - -environment: - sdk: ">=3.6.0 <4.0.0" - -dependencies: - hotreloader: ^4.3.0 - shelf: ^1.3.0 - fpdart: ^0.0.10 - postgres: ^2.4.1+2 - jose: ^0.3.2 - redis_dart: ^0.1.0+4 - uuid: ^4.5.1 - shelf_modular: - -dev_dependencies: - pedantic: ^1.10.0 - test: ">=1.16.0 <2.0.0" diff --git a/shelf_modular/example/sql/create_tables.sql b/shelf_modular/example/sql/create_tables.sql deleted file mode 100644 index 1769de58..00000000 --- a/shelf_modular/example/sql/create_tables.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - name VARCHAR(250) NOT NULL, - email VARCHAR(250) NOT NULL, - password VARCHAR(250) NOT NULL -); \ No newline at end of file diff --git a/shelf_modular/example/sql/fill_tables.sql b/shelf_modular/example/sql/fill_tables.sql deleted file mode 100644 index 88f09cc5..00000000 --- a/shelf_modular/example/sql/fill_tables.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO users(name,email,password) VALUES ('Flutterando', 'contato@flutterando.com.br', '40bd001563085fc35165329ea1ff5c5ecbdbbeef') ; \ No newline at end of file diff --git a/shelf_modular/index.html b/shelf_modular/index.html deleted file mode 100644 index b5754e20..00000000 --- a/shelf_modular/index.html +++ /dev/null @@ -1 +0,0 @@ -ok \ No newline at end of file diff --git a/shelf_modular/lib/shelf_modular.dart b/shelf_modular/lib/shelf_modular.dart deleted file mode 100644 index 32824831..00000000 --- a/shelf_modular/lib/shelf_modular.dart +++ /dev/null @@ -1,32 +0,0 @@ -library dart_modular; - -import 'package:shelf_modular/src/shelf_modular_module.dart'; - -import 'src/presenter/modular_base.dart'; - -export 'package:modular_core/modular_core.dart' - show - ModularRoute, - Disposable, - Module, - BindConfig, - Injector, - AutoInjectorException, - ModularArguments, - RouteManager, - setPrintResolver; - -export 'src/presenter/extensions/route_manage_extension.dart'; -export 'src/presenter/middlewares/middlewares.dart'; -export 'src/presenter/models/route.dart'; -export 'src/presenter/resources/resource.dart'; -export 'src/presenter/resources/websocket_resource.dart'; - -IModularBase? _modular; - -/// Instance of Modular for search binds and route. -// ignore: non_constant_identifier_names -IModularBase get Modular { - _modular ??= injector.get(); - return _modular!; -} diff --git a/shelf_modular/lib/src/domain/dtos/route_dto.dart b/shelf_modular/lib/src/domain/dtos/route_dto.dart deleted file mode 100644 index fea8e29a..00000000 --- a/shelf_modular/lib/src/domain/dtos/route_dto.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:meta/meta.dart'; - -@immutable -class RouteParmsDTO { - final String url; - final dynamic arguments; - final String schema; - - const RouteParmsDTO({ - required this.url, - this.arguments, - this.schema = '', - }); - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is RouteParmsDTO // - && - other.url == url && - other.arguments == arguments && - other.schema == schema; - } - - @override - int get hashCode => url.hashCode ^ arguments.hashCode ^ schema.hashCode; -} diff --git a/shelf_modular/lib/src/domain/errors/errors.dart b/shelf_modular/lib/src/domain/errors/errors.dart deleted file mode 100644 index 8f18a995..00000000 --- a/shelf_modular/lib/src/domain/errors/errors.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:modular_core/modular_core.dart'; - -class BindNotFoundException extends ModularError { - const BindNotFoundException(String message, [StackTrace? stackTrace]) - : super(message, stackTrace); -} - -class RouteNotFoundException extends ModularError { - const RouteNotFoundException(String message, [StackTrace? stackTrace]) - : super(message, stackTrace); -} diff --git a/shelf_modular/lib/src/domain/services/bind_service.dart b/shelf_modular/lib/src/domain/services/bind_service.dart deleted file mode 100644 index 13dd8851..00000000 --- a/shelf_modular/lib/src/domain/services/bind_service.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -abstract class BindService { - ResultDart getBind(); - ResultDart disposeBind(); -} diff --git a/shelf_modular/lib/src/domain/services/module_service.dart b/shelf_modular/lib/src/domain/services/module_service.dart deleted file mode 100644 index 06123df8..00000000 --- a/shelf_modular/lib/src/domain/services/module_service.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; - -abstract class ModuleService { - ResultDart start(Module module); - ResultDart finish(); -} diff --git a/shelf_modular/lib/src/domain/services/route_service.dart b/shelf_modular/lib/src/domain/services/route_service.dart deleted file mode 100644 index 24e539a1..00000000 --- a/shelf_modular/lib/src/domain/services/route_service.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/dtos/route_dto.dart'; - -abstract class RouteService { - AsyncResultDart getRoute(RouteParmsDTO params); - ResultDart getArguments(); - ResultDart reportPush(ModularRoute route); -} diff --git a/shelf_modular/lib/src/domain/usecases/dispose_bind.dart b/shelf_modular/lib/src/domain/usecases/dispose_bind.dart deleted file mode 100644 index 838c8af4..00000000 --- a/shelf_modular/lib/src/domain/usecases/dispose_bind.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/services/bind_service.dart'; - -abstract class DisposeBind { - ResultDart call(); -} - -class DisposeBindImpl implements DisposeBind { - final BindService bindService; - - DisposeBindImpl(this.bindService); - - @override - ResultDart call() { - return bindService.disposeBind(); - } -} diff --git a/shelf_modular/lib/src/domain/usecases/finish_module.dart b/shelf_modular/lib/src/domain/usecases/finish_module.dart deleted file mode 100644 index d62190fc..00000000 --- a/shelf_modular/lib/src/domain/usecases/finish_module.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/services/module_service.dart'; - -abstract class FinishModule { - ResultDart call(); -} - -class FinishModuleImpl implements FinishModule { - final ModuleService moduleService; - - FinishModuleImpl(this.moduleService); - - @override - ResultDart call() { - return moduleService.finish(); - } -} diff --git a/shelf_modular/lib/src/domain/usecases/get_arguments.dart b/shelf_modular/lib/src/domain/usecases/get_arguments.dart deleted file mode 100644 index f1607520..00000000 --- a/shelf_modular/lib/src/domain/usecases/get_arguments.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/services/route_service.dart'; - -abstract class GetArguments { - ResultDart call(); -} - -class GetArgumentsImpl implements GetArguments { - final RouteService service; - - GetArgumentsImpl(this.service); - - @override - ResultDart call() { - return service.getArguments(); - } -} diff --git a/shelf_modular/lib/src/domain/usecases/get_bind.dart b/shelf_modular/lib/src/domain/usecases/get_bind.dart deleted file mode 100644 index cf781e66..00000000 --- a/shelf_modular/lib/src/domain/usecases/get_bind.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/services/bind_service.dart'; - -abstract class GetBind { - ResultDart call(); -} - -class GetBindImpl implements GetBind { - final BindService bindService; - - GetBindImpl(this.bindService); - - @override - ResultDart call() { - return bindService.getBind(); - } -} diff --git a/shelf_modular/lib/src/domain/usecases/get_route.dart b/shelf_modular/lib/src/domain/usecases/get_route.dart deleted file mode 100644 index ce4719d7..00000000 --- a/shelf_modular/lib/src/domain/usecases/get_route.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/dtos/route_dto.dart'; -import 'package:shelf_modular/src/domain/services/route_service.dart'; - -abstract class GetRoute { - AsyncResultDart call(RouteParmsDTO params); -} - -class GetRouteImpl implements GetRoute { - final RouteService service; - - GetRouteImpl(this.service); - - @override - AsyncResultDart call(RouteParmsDTO params) { - return service.getRoute(params); - } -} diff --git a/shelf_modular/lib/src/domain/usecases/report_push.dart b/shelf_modular/lib/src/domain/usecases/report_push.dart deleted file mode 100644 index 5c4df891..00000000 --- a/shelf_modular/lib/src/domain/usecases/report_push.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/services/route_service.dart'; - -abstract class ReportPush { - ResultDart call(ModularRoute route); -} - -class ReportPushImpl implements ReportPush { - final RouteService service; - - ReportPushImpl(this.service); - - @override - ResultDart call(ModularRoute route) { - return service.reportPush(route); - } -} diff --git a/shelf_modular/lib/src/domain/usecases/start_module.dart b/shelf_modular/lib/src/domain/usecases/start_module.dart deleted file mode 100644 index aa04f930..00000000 --- a/shelf_modular/lib/src/domain/usecases/start_module.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/services/module_service.dart'; - -abstract class StartModule { - ResultDart call(Module module); -} - -class StartModuleImpl implements StartModule { - final ModuleService moduleService; - - StartModuleImpl(this.moduleService); - - @override - ResultDart call(Module module) { - return moduleService.start(module); - } -} diff --git a/shelf_modular/lib/src/infra/services/bind_service_impl.dart b/shelf_modular/lib/src/infra/services/bind_service_impl.dart deleted file mode 100644 index 8dc6f370..00000000 --- a/shelf_modular/lib/src/infra/services/bind_service_impl.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/errors/errors.dart'; -import 'package:shelf_modular/src/domain/services/bind_service.dart'; - -class BindServiceImpl extends BindService { - final AutoInjector injector; - - BindServiceImpl(this.injector); - - @override - ResultDart disposeBind() { - final result = injector.disposeSingleton(); - return Success(result != null); - } - - @override - ResultDart getBind() { - try { - final result = injector.get(); - return Success(result); - } on AutoInjectorException catch (e, s) { - return Failure(BindNotFoundException(e.toString(), s)); - } - } -} diff --git a/shelf_modular/lib/src/infra/services/module_service_impl.dart b/shelf_modular/lib/src/infra/services/module_service_impl.dart deleted file mode 100644 index 25967dd1..00000000 --- a/shelf_modular/lib/src/infra/services/module_service_impl.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/services/module_service.dart'; - -class ModuleServiceImpl extends ModuleService { - final Tracker tracker; - - ModuleServiceImpl(this.tracker); - - @override - ResultDart finish() { - tracker.finishApp(); - return const Success(unit); - } - - @override - ResultDart start(Module module) { - tracker.runApp(module); - return const Success(unit); - } -} diff --git a/shelf_modular/lib/src/infra/services/route_service_impl.dart b/shelf_modular/lib/src/infra/services/route_service_impl.dart deleted file mode 100644 index 743aa19b..00000000 --- a/shelf_modular/lib/src/infra/services/route_service_impl.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/dtos/route_dto.dart'; -import 'package:shelf_modular/src/domain/errors/errors.dart'; -import 'package:shelf_modular/src/domain/services/route_service.dart'; - -class RouteServiceImpl implements RouteService { - final Tracker tracker; - - RouteServiceImpl(this.tracker); - - @override - AsyncResultDart getRoute( - RouteParmsDTO params) async { - final route = await tracker.findRoute( - params.url, - data: params.arguments, - schema: params.schema, - ); - if (route != null) { - return Success(route); - } else { - return Failure(RouteNotFoundException('Route (${params.url}) not found')); - } - } - - @override - ResultDart getArguments() { - return Success(tracker.arguments); - } - - @override - ResultDart reportPush(ModularRoute route) { - tracker.reportPushRoute(route); - return const Success(unit); - } -} diff --git a/shelf_modular/lib/src/presenter/errors/errors.dart b/shelf_modular/lib/src/presenter/errors/errors.dart deleted file mode 100644 index d2c962fa..00000000 --- a/shelf_modular/lib/src/presenter/errors/errors.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:modular_core/modular_core.dart'; - -class ModuleStartedException extends ModularError { - const ModuleStartedException(String message, [StackTrace? stackTrace]) - : super(message, stackTrace); -} diff --git a/shelf_modular/lib/src/presenter/extensions/route_manage_extension.dart b/shelf_modular/lib/src/presenter/extensions/route_manage_extension.dart deleted file mode 100644 index 2c584e93..00000000 --- a/shelf_modular/lib/src/presenter/extensions/route_manage_extension.dart +++ /dev/null @@ -1,67 +0,0 @@ -import '../../../shelf_modular.dart'; - -extension RouteManagerExt on RouteManager { - void get( - String name, - Function handler, { - List middlewares = const [], - }) { - add(Route.get(name, handler, middlewares: middlewares)); - } - - void post( - String name, - Function handler, { - List middlewares = const [], - }) { - add(Route.post(name, handler, middlewares: middlewares)); - } - - void put( - String name, - Function handler, { - List middlewares = const [], - }) { - add(Route.put(name, handler, middlewares: middlewares)); - } - - void patch( - String name, - Function handler, { - List middlewares = const [], - }) { - add(Route.path(name, handler, middlewares: middlewares)); - } - - void delete( - String name, - Function handler, { - List middlewares = const [], - }) { - add(Route.delete(name, handler, middlewares: middlewares)); - } - - void resource( - Resource resource, { - String name = '/', - List middlewares = const [], - }) { - add(Route.resource(name, resource: resource, middlewares: middlewares)); - } - - void module( - String name, { - required Module module, - List middlewares = const [], - }) { - add(Route.module(name, module: module, middlewares: middlewares)); - } - - void websocket( - String name, { - required WebSocketResource websocket, - List middlewares = const [], - }) { - add(Route.websocket(name, websocket: websocket, middlewares: middlewares)); - } -} diff --git a/shelf_modular/lib/src/presenter/handlers/handlers.dart b/shelf_modular/lib/src/presenter/handlers/handlers.dart deleted file mode 100644 index ce38ad9b..00000000 --- a/shelf_modular/lib/src/presenter/handlers/handlers.dart +++ /dev/null @@ -1,111 +0,0 @@ -import 'dart:async'; - -import 'package:modular_core/modular_core.dart'; -import 'package:shelf/shelf.dart'; - -//less -typedef HandlerWithlessParams = FutureOr Function(); -//one param -typedef Handler1 = FutureOr Function(ModularArguments args); -typedef Handler2 = FutureOr Function(AutoInjector i); - -//two params -typedef HandlerTwoParams = FutureOr Function( - Request request, - ModularArguments args, -); -typedef HandlerTwoParams1 = FutureOr Function( - Request request, - AutoInjector i, -); -typedef HandlerTwoParams2 = FutureOr Function( - ModularArguments args, - Request request, -); -typedef HandlerTwoParams3 = FutureOr Function( - ModularArguments args, - AutoInjector i, -); -typedef HandlerTwoParams4 = FutureOr Function( - AutoInjector i, - ModularArguments args, -); -typedef HandlerTwoParams5 = FutureOr Function( - AutoInjector i, - Request request, -); -//three params -typedef HandlerThreeParams = FutureOr Function( - Request request, - ModularArguments args, - AutoInjector i, -); -typedef HandlerThreeParams1 = FutureOr Function( - Request request, - AutoInjector i, - ModularArguments args, -); -typedef HandlerThreeParams2 = FutureOr Function( - ModularArguments args, - Request request, - AutoInjector i, -); -typedef HandlerThreeParams3 = FutureOr Function( - ModularArguments args, - AutoInjector i, - Request request, -); -typedef HandlerThreeParams4 = FutureOr Function( - AutoInjector i, - ModularArguments args, - Request request, -); -typedef HandlerThreeParams5 = FutureOr Function( - AutoInjector i, - Request request, - ModularArguments args, -); - -FutureOr? applyHandler( - Function fn, { - required Request request, - required ModularArguments arguments, - required AutoInjector injector, -}) { - //less - if (fn is HandlerWithlessParams) { - return fn(); - } else if (fn is Handler) { - return fn(request); - } else if (fn is Handler1) { - return fn(arguments); - } else if (fn is Handler2) { - return fn(injector); - } else if (fn is HandlerTwoParams) { - return fn(request, arguments); - } else if (fn is HandlerTwoParams1) { - return fn(request, injector); - } else if (fn is HandlerTwoParams2) { - return fn(arguments, request); - } else if (fn is HandlerTwoParams3) { - return fn(arguments, injector); - } else if (fn is HandlerTwoParams4) { - return fn(injector, arguments); - } else if (fn is HandlerTwoParams5) { - return fn(injector, request); - } else if (fn is HandlerThreeParams) { - return fn(request, arguments, injector); - } else if (fn is HandlerThreeParams1) { - return fn(request, injector, arguments); - } else if (fn is HandlerThreeParams2) { - return fn(arguments, request, injector); - } else if (fn is HandlerThreeParams3) { - return fn(arguments, injector, request); - } else if (fn is HandlerThreeParams4) { - return fn(injector, arguments, request); - } else if (fn is HandlerThreeParams5) { - return fn(injector, request, arguments); - } else { - return null; - } -} diff --git a/shelf_modular/lib/src/presenter/middlewares/middlewares.dart b/shelf_modular/lib/src/presenter/middlewares/middlewares.dart deleted file mode 100644 index ea781273..00000000 --- a/shelf_modular/lib/src/presenter/middlewares/middlewares.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:modular_core/modular_core.dart'; -import 'package:shelf/shelf.dart' as shelf; -import 'package:shelf_modular/src/presenter/models/route.dart'; - -abstract class RouteGuard extends ModularMiddleware { - FutureOr canActivate(shelf.Request request, Route route); - - @override - FutureOr Function(shelf.Request) call( - shelf.Handler handler, [ - ModularRoute? route, - ]) { - return (request) async { - if (!await canActivate(request, route! as Route)) { - return shelf.Response.forbidden( - jsonEncode({'error': route.uri.toString().trim()}), - ); - } - return handler(request); - }; - } -} - -abstract class ModularMiddleware implements Middleware { - const ModularMiddleware(); - - @override - FutureOr pre(ModularRoute route) => route; - - @override - FutureOr pos( - ModularRoute route, - shelf.Request data, - ) => - route as Route; - - shelf.Handler call(shelf.Handler handler, [ModularRoute? route]); -} diff --git a/shelf_modular/lib/src/presenter/models/route.dart b/shelf_modular/lib/src/presenter/models/route.dart deleted file mode 100644 index 76b5f760..00000000 --- a/shelf_modular/lib/src/presenter/models/route.dart +++ /dev/null @@ -1,160 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:shelf/shelf.dart' hide Middleware; -import 'package:shelf_modular/shelf_modular.dart'; - -class Route extends ModularRoute { - final Function? handler; - - Route._( - super.name, { - this.handler, - super.parent = '', - super.schema = '', - super.children = const [], - Uri? uri, - super.module, - super.middlewares = const [], - super.innerModules = const {}, - }) : super(uri: uri ?? Uri.parse('/')); - - factory Route.get( - String name, - Function handler, { - List middlewares = const [], - }) { - return Route._( - name, - handler: handler, - schema: 'GET', - middlewares: middlewares, - ); - } - - factory Route.post( - String name, - Function handler, { - List middlewares = const [], - }) { - return Route._( - name, - handler: handler, - schema: 'POST', - middlewares: middlewares, - ); - } - - factory Route.delete( - String name, - Function handler, { - List middlewares = const [], - }) { - return Route._( - name, - handler: handler, - schema: 'DELETE', - middlewares: middlewares, - ); - } - factory Route.path( - String name, - Function handler, { - List middlewares = const [], - }) { - return Route._( - name, - handler: handler, - schema: 'PATCH', - middlewares: middlewares, - ); - } - - factory Route.put( - String name, - Function handler, { - List middlewares = const [], - }) { - return Route._( - name, - handler: handler, - schema: 'PUT', - middlewares: middlewares, - ); - } - - factory Route.resource( - String name, { - required Resource resource, - List middlewares = const [], - }) { - final manager = RouteManager(); - - resource.routes(manager); - - return Route._( - name, - // ignore: invalid_use_of_visible_for_testing_member - children: manager.allRoutes, - middlewares: middlewares, - ); - } - - factory Route.module( - String name, { - required Module module, - List middlewares = const [], - }) { - final route = Route._(name, middlewares: middlewares); - return route.addModule(name, module: module); - } - - factory Route.websocket( - String name, { - required WebSocketResource websocket, - List middlewares = const [], - }) { - return Route._( - name, - handler: websocket.handler, - schema: 'GET', - middlewares: middlewares, - ); - } - - @override - Route copyWith({ - Handler? handler, - String? name, - String? schema, - Module? module, - List? middlewares, - List? children, - String? parent, - Uri? uri, - Map? routeMap, - Map? innerModules, - }) { - return Route._( - name ?? this.name, - handler: handler ?? this.handler, - schema: schema ?? this.schema, - middlewares: middlewares ?? this.middlewares, - children: children ?? this.children, - parent: parent ?? this.parent, - module: module ?? this.module, - uri: uri ?? uri, - innerModules: innerModules ?? this.innerModules, - ); - } - - @override - Route addModule(String name, {required Module module}) { - final innerModules = {module.runtimeType: module}; - - return copyWith( - name: name, - uri: Uri.parse(name), - innerModules: innerModules, - module: module, - ); - } -} diff --git a/shelf_modular/lib/src/presenter/modular_base.dart b/shelf_modular/lib/src/presenter/modular_base.dart deleted file mode 100644 index b9bc68f7..00000000 --- a/shelf_modular/lib/src/presenter/modular_base.dart +++ /dev/null @@ -1,227 +0,0 @@ -// ignore_for_file: noop_primitive_operations, avoid_print - -import 'dart:async'; -import 'dart:convert'; - -import 'package:http_parser/http_parser.dart'; -import 'package:meta/meta.dart'; -import 'package:modular_core/modular_core.dart' hide Middleware; -import 'package:result_dart/functions.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf/shelf.dart'; -import 'package:shelf_modular/shelf_modular.dart'; -import 'package:shelf_modular/src/domain/dtos/route_dto.dart'; -import 'package:shelf_modular/src/domain/errors/errors.dart'; -import 'package:shelf_modular/src/domain/usecases/dispose_bind.dart'; -import 'package:shelf_modular/src/domain/usecases/finish_module.dart'; -import 'package:shelf_modular/src/domain/usecases/get_arguments.dart'; -import 'package:shelf_modular/src/domain/usecases/get_bind.dart'; -import 'package:shelf_modular/src/domain/usecases/get_route.dart'; -import 'package:shelf_modular/src/domain/usecases/report_push.dart'; -import 'package:shelf_modular/src/domain/usecases/start_module.dart'; -import 'package:shelf_modular/src/shelf_modular_module.dart'; - -import 'errors/errors.dart'; -import 'handlers/handlers.dart'; - -abstract class IModularBase { - /// Finishes all trees(BindContext and RouteContext). - void destroy(); - - /// Responsible for starting the app. - /// It should only be called once, but it should be the first method to - /// be called before a route or bind lookup. - /// [module]: Start initial module. - /// [middlewares]: List of Shelf middlewares. - Handler call({ - required Module module, - List middlewares = const [], - }); - - /// Responsible for starting the app. - /// It should only be called once, but it should be the first method to - /// be called before a route or bind lookup. - Handler start({required Module module}); - - /// Request an instance by [Type] - B get(); - - /// Request an instance by [Type] - /// Returning null if not found instance - B? tryGet(); - - /// Dispose a bind by [Type] - bool dispose(); -} - -class ModularBase implements IModularBase { - final DisposeBind disposeBind; - final GetArguments getArguments; - final FinishModule finishModule; - final GetBind getBind; - final StartModule startModule; - final GetRoute getRoute; - final ReportPush reportPush; - - bool _moduleHasBeenStarted = false; - - ModularBase( - this.disposeBind, - this.finishModule, - this.getBind, - this.startModule, - this.getRoute, - this.getArguments, - this.reportPush, - ); - - @override - bool dispose() { - return disposeBind().getOrElse((_) => false); - } - - @override - B get() { - return getBind().getOrThrow(); - } - - @override - B? tryGet() { - return getBind().getOrNull(); - } - - @override - void destroy() => finishModule(); - - @override - Handler call({ - required Module module, - List middlewares = const [], - }) { - if (!_moduleHasBeenStarted) { - startModule(module).getOrThrow(); - print('${module.runtimeType} started!'); - _moduleHasBeenStarted = true; - - setPrintResolver(print); - var pipeline = const Pipeline(); - for (final middleware in middlewares) { - pipeline = pipeline.addMiddleware(middleware); - } - - return pipeline.addHandler(handler); - } else { - throw ModuleStartedException( - 'Module ${module.runtimeType} is already started', - ); - } - } - - @override - Handler start({required Module module}) => call(module: module); - - @visibleForTesting - FutureOr handler(Request request) async { - try { - final body = await request.readAsString(); - - final data = await tryJsonDecode(request.change(body: body)); - final params = RouteParmsDTO( - url: '/${request.url.toString()}', - schema: request.method, - arguments: data, - ); - return getRoute // - .call(params) - .map((route) => _routeSuccess(route, request.change(body: body))) - .mapError(_routeError) - .fold(identity, identity); - } on Exception catch (e, s) { - if (e.toString().contains( - 'Exception: Got a response for hijacked request', - )) { - return Response.ok(''); - } else { - print(e.toString()); - print('STACK TRACE \n $s'); - return Response.internalServerError(); - } - } - } - - FutureOr _routeSuccess(ModularRoute? route, Request request) async { - final middlewares = route?.middlewares ?? []; - var pipeline = const Pipeline(); - - for (final middleware in middlewares) { - if (middleware is ModularMiddleware) { - pipeline = pipeline.addMiddleware( - (innerHandler) => middleware(innerHandler, route), - ); - } - } - - if (route is! Route) { - return Response.notFound(''); - } - reportPush(route); - - final routeHandler = route.handler!; - - return pipeline.addHandler((request) async { - final response = await applyHandler( - routeHandler, - request: request, - arguments: getArguments().getOrElse((left) => ModularArguments.empty()), - injector: injector(), - ); - - if (response != null) { - return response; - } else { - return Response.internalServerError(body: 'Handler not correct'); - } - })(request); - } - - FutureOr _routeError(ModularError error) { - if (error is RouteNotFoundException) { - return Response.notFound(error.message); - } - - return Response.internalServerError(body: error.toString()); - } - - @visibleForTesting - Future tryJsonDecode(Request request) async { - if (request.method == 'GET') return {}; - - if (!_isMultipart(request)) { - try { - final data = await request.readAsString(); - - if (data.isEmpty) return {}; - - return jsonDecode(data); - } on FormatException catch (e) { - print(e); - return {}; - } - } - - return {}; - } - - bool _isMultipart(Request request) { - return _extractMultipartBoundary(request) != null; - } - - String? _extractMultipartBoundary(Request request) { - if (!request.headers.containsKey('Content-Type')) return null; - - final contentType = MediaType.parse(request.headers['Content-Type']!); - if (contentType.type != 'multipart') return null; - - return contentType.parameters['boundary']; - } -} diff --git a/shelf_modular/lib/src/presenter/resources/resource.dart b/shelf_modular/lib/src/presenter/resources/resource.dart deleted file mode 100644 index 723477f5..00000000 --- a/shelf_modular/lib/src/presenter/resources/resource.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:shelf_modular/shelf_modular.dart'; - -abstract class Resource { - void routes(RouteManager r); -} diff --git a/shelf_modular/lib/src/presenter/resources/websocket_resource.dart b/shelf_modular/lib/src/presenter/resources/websocket_resource.dart deleted file mode 100644 index 5c4c331b..00000000 --- a/shelf_modular/lib/src/presenter/resources/websocket_resource.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'dart:async'; - -import 'package:meta/meta.dart'; -import 'package:shelf/shelf.dart'; -import 'package:shelf_web_socket/shelf_web_socket.dart'; -import 'package:web_socket_channel/web_socket_channel.dart'; - -abstract class WebSocketResource { - FutureOr handler(Request request) { - return webSocketHandler(connectSocket)(request); - } - - final List _websockets = []; - - void onMessage(dynamic data, WebSocket socket); - - void connect(WebSocket socket); - void disconnect(WebSocket socket); - - @visibleForTesting - void connectSocket(WebSocketChannel socketChannel) { - final socket = WebSocket._(socketChannel, broadcast); - - _websockets.add(socket); - connect(socket); - socket.stream.listen( - (message) { - onMessage(message, socket); - }, - onDone: () { - _websockets.remove(socket); - disconnect(socket); - }, - ); - } - - void broadcast( - dynamic message, { - WebSocket? currentSocket, - Iterable rooms = const [], - }) { - for (final room in rooms.isEmpty ? [''] : rooms) { - var list = _websockets.where((socket) => currentSocket != socket); - if (room.isNotEmpty) { - list = list.where((socket) => socket._enteredRooms.contains(room)); - } - - for (final websocket in list) { - websocket.sink.add(message); - } - } - } -} - -class WebSocket { - final WebSocketChannel _channel; - final Set _enteredRooms = {}; - late final Stream _stream = _channel.stream.asBroadcastStream(); - Set get enteredRooms => Set.unmodifiable(_enteredRooms); - final void Function( - dynamic message, { - WebSocket? currentSocket, - Iterable rooms, - }) _broadcast; - dynamic tag; - - Stream get stream => _stream; - Sink get sink => _channel.sink; - - void joinRoom(String room) => _enteredRooms.add(room); - bool leaveRoom(String room) => _enteredRooms.remove(room); - - void emit(dynamic data, [Iterable rooms = const []]) => - _broadcast(data, currentSocket: this, rooms: rooms); - void emitToRooms(dynamic data) => - _broadcast(data, currentSocket: this, rooms: _enteredRooms); - - WebSocket._(this._channel, this._broadcast); -} diff --git a/shelf_modular/lib/src/shelf_modular_module.dart b/shelf_modular/lib/src/shelf_modular_module.dart deleted file mode 100644 index 60e2fa85..00000000 --- a/shelf_modular/lib/src/shelf_modular_module.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:shelf_modular/src/domain/usecases/report_push.dart'; - -import 'domain/services/bind_service.dart'; -import 'domain/services/module_service.dart'; -import 'domain/services/route_service.dart'; -import 'domain/usecases/dispose_bind.dart'; -import 'domain/usecases/finish_module.dart'; -import 'domain/usecases/get_arguments.dart'; -import 'domain/usecases/get_bind.dart'; -import 'domain/usecases/get_route.dart'; -import 'domain/usecases/start_module.dart'; -import 'infra/services/bind_service_impl.dart'; -import 'infra/services/module_service_impl.dart'; -import 'infra/services/route_service_impl.dart'; -import 'presenter/modular_base.dart'; - -final _innerInjector = AutoInjector( - tag: 'ModularApp', - on: (i) { - i.addInstance(i); - i.commit(); - }, -); - -final injector = AutoInjector( - tag: 'ModularCore', - on: (i) { - i.addLazySingleton(Tracker.new); - i.addInstance(_innerInjector); - //infra - i.add(BindServiceImpl.new); - i.add(ModuleServiceImpl.new); - i.add(RouteServiceImpl.new); - //domain - i.add(DisposeBindImpl.new); - i.add(FinishModuleImpl.new); - i.add(GetBindImpl.new); - i.add(GetRouteImpl.new); - i.add(StartModuleImpl.new); - i.add(GetArgumentsImpl.new); - i.add(ReportPushImpl.new); - //presenter - i.addSingleton(ModularBase.new); - i.commit(); - }, -); diff --git a/shelf_modular/melos_shelf_modular.iml b/shelf_modular/melos_shelf_modular.iml deleted file mode 100644 index 389d07a1..00000000 --- a/shelf_modular/melos_shelf_modular.iml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/shelf_modular/pubspec.yaml b/shelf_modular/pubspec.yaml deleted file mode 100644 index 94c8722d..00000000 --- a/shelf_modular/pubspec.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: shelf_modular -description: Smart project structure with dependency injection and route management -version: 3.0.1 -homepage: https://github.com/Flutterando/modular - -resolution: workspace - -environment: - sdk: ">=3.6.0 <4.0.0" - -dependencies: - shelf: ">=1.3.0 <2.0.0" - meta: ">=1.3.0 <2.0.0" - modular_core: ^3.4.0 - mime: ">=1.0.2 <2.0.0" - http_parser: ">=4.0.1 <5.0.0" - web_socket_channel: ">=2.2.0 <3.0.0" - shelf_web_socket: ">=1.0.1 <2.0.0" - result_dart: ">=2.1.0 <3.0.0" - -dev_dependencies: - mocktail: ^1.0.4 - flutterando_analysis: ^0.0.2 - test: ">=1.16.0 <2.0.0" diff --git a/shelf_modular/test/shelf_modular_test.dart b/shelf_modular/test/shelf_modular_test.dart deleted file mode 100644 index f133581d..00000000 --- a/shelf_modular/test/shelf_modular_test.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:shelf_modular/shelf_modular.dart'; -import 'package:shelf_modular/src/presenter/modular_base.dart'; -import 'package:test/test.dart'; - -void main() { - test('Modular instance', () { - expect(Modular, isA()); - }); -} diff --git a/shelf_modular/test/src/domain/dtos/route_dto_test.dart b/shelf_modular/test/src/domain/dtos/route_dto_test.dart deleted file mode 100644 index 6e2d8ff1..00000000 --- a/shelf_modular/test/src/domain/dtos/route_dto_test.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:shelf_modular/src/domain/dtos/route_dto.dart'; -import 'package:test/test.dart'; - -void main() { - test('Equatable', () { - expect(const RouteParmsDTO(url: '/'), const RouteParmsDTO(url: '/')); - // ignore: prefer_const_constructors - expect(RouteParmsDTO(url: '/'), RouteParmsDTO(url: '/')); - expect( - const RouteParmsDTO(url: '/').hashCode, - const RouteParmsDTO(url: '/').hashCode, - ); - }); -} diff --git a/shelf_modular/test/src/domain/usecases/dispose_bind_test.dart b/shelf_modular/test/src/domain/usecases/dispose_bind_test.dart deleted file mode 100644 index d2c69453..00000000 --- a/shelf_modular/test/src/domain/usecases/dispose_bind_test.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/usecases/dispose_bind.dart'; -import 'package:test/test.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = BindServiceMock(); - final usecase = DisposeBindImpl(service); - test('dispose bind', () { - when(() => service.disposeBind()).thenReturn(const Success(true)); - - expect(usecase.call().getOrElse((left) => false), true); - }); -} diff --git a/shelf_modular/test/src/domain/usecases/finish_module_test.dart b/shelf_modular/test/src/domain/usecases/finish_module_test.dart deleted file mode 100644 index 0f73ee9f..00000000 --- a/shelf_modular/test/src/domain/usecases/finish_module_test.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/usecases/finish_module.dart'; -import 'package:test/test.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = ModuleServiceMock(); - final usecase = FinishModuleImpl(service); - test('finish module', () { - when(service.finish).thenReturn(const Success(unit)); - - expect(usecase.call().isSuccess(), true); - }); -} diff --git a/shelf_modular/test/src/domain/usecases/get_arguments_test.dart b/shelf_modular/test/src/domain/usecases/get_arguments_test.dart deleted file mode 100644 index 7e595f30..00000000 --- a/shelf_modular/test/src/domain/usecases/get_arguments_test.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:mocktail/mocktail.dart'; -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/usecases/get_arguments.dart'; -import 'package:test/test.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = RouteServiceMock(); - final usecase = GetArgumentsImpl(service); - test('get ModularArguments', () { - final args = ModularArguments.empty(); - when(service.getArguments).thenReturn(Success(args)); - - expect(usecase.call().getOrElse((left) => ModularArguments.empty()), args); - }); -} diff --git a/shelf_modular/test/src/domain/usecases/get_bind_test.dart b/shelf_modular/test/src/domain/usecases/get_bind_test.dart deleted file mode 100644 index 80d47119..00000000 --- a/shelf_modular/test/src/domain/usecases/get_bind_test.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/usecases/get_bind.dart'; -import 'package:test/test.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = BindServiceMock(); - final usecase = GetBindImpl(service); - test('get bind', () { - when(() => service.getBind()).thenReturn(const Success('test')); - - expect(usecase.call().getOrElse((left) => ''), 'test'); - }); -} diff --git a/shelf_modular/test/src/domain/usecases/get_route_test.dart b/shelf_modular/test/src/domain/usecases/get_route_test.dart deleted file mode 100644 index cb99c70d..00000000 --- a/shelf_modular/test/src/domain/usecases/get_route_test.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/dtos/route_dto.dart'; -import 'package:shelf_modular/src/domain/usecases/get_route.dart'; -import 'package:test/test.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = RouteServiceMock(); - final usecase = GetRouteImpl(service); - final route = ModularRouteMock(); - const params = RouteParmsDTO(url: '/'); - test('get route', () async { - when(() => service.getRoute(params)) - .thenAnswer((_) async => Success(route)); - final result = await usecase.call(params); - expect(result.isSuccess(), true); - }); -} diff --git a/shelf_modular/test/src/domain/usecases/report_push_test.dart b/shelf_modular/test/src/domain/usecases/report_push_test.dart deleted file mode 100644 index 5c15100d..00000000 --- a/shelf_modular/test/src/domain/usecases/report_push_test.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/usecases/report_push.dart'; -import 'package:test/test.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = RouteServiceMock(); - final usecase = ReportPushImpl(service); - test('report push route', () { - final route = RouteMock(); - when(() => service.reportPush(route)).thenReturn(const Success(unit)); - final result = usecase.call(route); - expect(result.isSuccess(), true); - }); -} diff --git a/shelf_modular/test/src/domain/usecases/start_module_test.dart b/shelf_modular/test/src/domain/usecases/start_module_test.dart deleted file mode 100644 index cba41d3c..00000000 --- a/shelf_modular/test/src/domain/usecases/start_module_test.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf_modular/src/domain/usecases/start_module.dart'; -import 'package:test/test.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final service = ModuleServiceMock(); - final usecase = StartModuleImpl(service); - final module = ModuleMock(); - test('start module', () { - when(() => service.start(module)).thenReturn(const Success(unit)); - - expect(usecase.call(module).isSuccess(), true); - }); -} diff --git a/shelf_modular/test/src/infra/services/bind_service_impl_test.dart b/shelf_modular/test/src/infra/services/bind_service_impl_test.dart deleted file mode 100644 index 07a7c800..00000000 --- a/shelf_modular/test/src/infra/services/bind_service_impl_test.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:mocktail/mocktail.dart'; -import 'package:modular_core/modular_core.dart'; -import 'package:result_dart/functions.dart'; -import 'package:shelf_modular/src/domain/errors/errors.dart'; -import 'package:shelf_modular/src/infra/services/bind_service_impl.dart'; -import 'package:test/test.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final injector = InjectorMock(); - final service = BindServiceImpl(injector); - - group('getBind', () { - test('should get bind', () { - when(() => injector.get()).thenReturn('test'); - expect(service.getBind().getOrElse((left) => ''), 'test'); - }); - test('should throw error not found bind', () { - when(() => injector.get()).thenThrow( - AutoInjectorException('String'), - ); - expect( - service.getBind().fold(id, id), - isA(), - ); - }); - }); - - group('dispose', () { - test('should return true', () { - when(() => injector.disposeSingleton()).thenReturn('true'); - expect(service.disposeBind().getOrElse((left) => false), true); - }); - }); -} diff --git a/shelf_modular/test/src/infra/services/module_service_impl_test.dart b/shelf_modular/test/src/infra/services/module_service_impl_test.dart deleted file mode 100644 index 70fa0416..00000000 --- a/shelf_modular/test/src/infra/services/module_service_impl_test.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:shelf_modular/src/infra/services/module_service_impl.dart'; -import 'package:test/test.dart'; - -import '../../mocks/mocks.dart'; - -void main() { - final tracker = TrackerMock(); - final service = ModuleServiceImpl(tracker); - final module = ModuleMock(); - - group('start', () { - test('should return true', () { - tracker.runApp(module); - expect(service.start(module).isSuccess(), true); - }); - }); - - group('finish', () { - test('should return true', () { - tracker.finishApp(); - expect(service.finish().isSuccess(), true); - }); - }); -} diff --git a/shelf_modular/test/src/infra/services/route_service_impl_test.dart b/shelf_modular/test/src/infra/services/route_service_impl_test.dart deleted file mode 100644 index 36116e30..00000000 --- a/shelf_modular/test/src/infra/services/route_service_impl_test.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:mocktail/mocktail.dart'; -import 'package:shelf_modular/shelf_modular.dart'; -import 'package:shelf_modular/src/domain/dtos/route_dto.dart'; -import 'package:shelf_modular/src/infra/services/route_service_impl.dart'; -import 'package:test/test.dart'; - -import '../../mocks/mocks.dart'; - -class ModularRouteFake extends Fake implements ModularRoute {} - -void main() { - final tracker = TrackerMock(); - final service = RouteServiceImpl(tracker); - const params = RouteParmsDTO(url: '/'); - - setUp(() { - reset(tracker); - }); - - group('getRoute', () { - test('should get route', () async { - when(() => tracker.findRoute(params.url)).thenAnswer( - (_) async => ModularRouteFake(), - ); - final result = await service.getRoute(params); - expect(result.isSuccess(), true); - }); - test('should throw error not found route', () async { - when(() => tracker.findRoute(params.url)).thenReturn(null); - final result = await service.getRoute(params); - expect(result.isError(), true); - }); - }); - - group('getArguments', () { - test('should return args', () async { - when(() => tracker.arguments).thenReturn(ModularArguments.empty()); - final result = service.getArguments(); - expect(result.isSuccess(), true); - }); - }); - - group('reportPush', () { - test('report pushroute', () async { - final route = RouteMock(); - when(() => tracker.reportPopRoute(route)); - final result = service.reportPush(route); - expect(result.isSuccess(), true); - }); - }); -} diff --git a/shelf_modular/test/src/mocks/mocks.dart b/shelf_modular/test/src/mocks/mocks.dart deleted file mode 100644 index 72a6ac31..00000000 --- a/shelf_modular/test/src/mocks/mocks.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:mocktail/mocktail.dart'; -import 'package:modular_core/modular_core.dart'; -import 'package:shelf/shelf.dart'; -import 'package:shelf_modular/shelf_modular.dart'; -import 'package:shelf_modular/src/domain/services/bind_service.dart'; -import 'package:shelf_modular/src/domain/services/module_service.dart'; -import 'package:shelf_modular/src/domain/services/route_service.dart'; -import 'package:shelf_modular/src/domain/usecases/dispose_bind.dart'; -import 'package:shelf_modular/src/domain/usecases/finish_module.dart'; -import 'package:shelf_modular/src/domain/usecases/get_arguments.dart'; -import 'package:shelf_modular/src/domain/usecases/get_bind.dart'; -import 'package:shelf_modular/src/domain/usecases/get_route.dart'; -import 'package:shelf_modular/src/domain/usecases/report_push.dart'; -import 'package:shelf_modular/src/domain/usecases/start_module.dart'; - -class BindServiceMock extends Mock implements BindService {} - -class RouteServiceMock extends Mock implements RouteService {} - -class ModuleServiceMock extends Mock implements ModuleService {} - -class ModuleMock extends Mock implements Module {} - -class ModularRouteMock extends Mock implements ModularRoute {} - -class InjectorMock extends Mock implements AutoInjector {} - -class TrackerMock extends Mock implements Tracker {} - -class DisposeBindMock extends Mock implements DisposeBind {} - -class GetArgumentsMock extends Mock implements GetArguments {} - -class FinishModuleMock extends Mock implements FinishModule {} - -class GetBindMock extends Mock implements GetBind {} - -class StartModuleMock extends Mock implements StartModule {} - -class GetRouteMock extends Mock implements GetRoute {} - -class RequestMock extends Mock implements Request {} - -class RouteMock extends Mock implements Route {} - -class DisposableMock extends Mock implements Disposable {} - -class ReportPushMock extends Mock implements ReportPush {} diff --git a/shelf_modular/test/src/presenter/errors/errors_test.dart b/shelf_modular/test/src/presenter/errors/errors_test.dart deleted file mode 100644 index ab73b3a2..00000000 --- a/shelf_modular/test/src/presenter/errors/errors_test.dart +++ /dev/null @@ -1 +0,0 @@ -void main() {} diff --git a/shelf_modular/test/src/presenter/extensions/route_manage_extension_test.dart b/shelf_modular/test/src/presenter/extensions/route_manage_extension_test.dart deleted file mode 100644 index a3a314f3..00000000 --- a/shelf_modular/test/src/presenter/extensions/route_manage_extension_test.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:modular_core/modular_core.dart'; -import 'package:shelf/shelf.dart'; -import 'package:shelf_modular/shelf_modular.dart'; -import 'package:test/test.dart'; - -void main() { - test('route manage extension', () { - final manager = RouteManager(); - manager.get('/', () => Response.ok('')); - manager.post('/', () => Response.ok('')); - manager.put('/', () => Response.ok('')); - manager.patch('/', () => Response.ok('')); - manager.delete('/', () => Response.ok('')); - manager.websocket('/', websocket: CustomWebsocket()); - manager.module('/', module: CustomModule()); - manager.resource(CustomResource()); - - expect(manager.allRoutes.length, 8); - }); -} - -class CustomResource extends Resource { - @override - void routes(RouteManager r) {} -} - -class CustomModule extends Module {} - -class CustomWebsocket extends WebSocketResource { - @override - void connect(WebSocket socket) {} - - @override - void disconnect(WebSocket socket) {} - - @override - void onMessage(dynamic data, WebSocket socket) {} -} diff --git a/shelf_modular/test/src/presenter/handlers/handlers_test.dart b/shelf_modular/test/src/presenter/handlers/handlers_test.dart deleted file mode 100644 index 45ee25dd..00000000 --- a/shelf_modular/test/src/presenter/handlers/handlers_test.dart +++ /dev/null @@ -1,175 +0,0 @@ -import 'dart:async'; - -import 'package:mocktail/mocktail.dart'; -import 'package:modular_core/modular_core.dart'; -import 'package:shelf/shelf.dart'; -import 'package:shelf_modular/src/presenter/handlers/handlers.dart'; -import 'package:test/test.dart'; - -import '../../mocks/mocks.dart'; - -class RequestMock extends Mock implements Request {} - -void main() { - FutureOr? _applyHandler(Function fn) { - return applyHandler( - fn, - request: RequestMock(), - arguments: ModularArguments.empty(), - injector: InjectorMock(), - ); - } - - test('HandlerWithlessParams', () { - expect(_applyHandler(() => Response.ok('')), isNotNull); - }); - - test('Handle', () { - expect(_applyHandler((request) => Response.ok('')), isNotNull); - }); - - test('Handler1', () { - expect( - _applyHandler((ModularArguments args) => Response.ok('')), - isNotNull, - ); - }); - - test('Handler2', () { - expect( - _applyHandler((AutoInjector injector) => Response.ok('')), - isNotNull, - ); - }); - - test('HandlerTwoParams', () { - expect( - _applyHandler( - (Request request, ModularArguments args) => Response.ok(''), - ), - isNotNull, - ); - }); - - test('HandlerTwoParams1', () { - expect( - _applyHandler((Request request, AutoInjector i) => Response.ok('')), - isNotNull, - ); - }); - - test('HandlerTwoParams2', () { - expect( - _applyHandler( - (ModularArguments args, Request request) => Response.ok(''), - ), - isNotNull, - ); - }); - test('HandlerTwoParams3', () { - expect( - _applyHandler( - (ModularArguments args, AutoInjector i) => Response.ok(''), - ), - isNotNull, - ); - }); - - test('HandlerTwoParams4', () { - expect( - _applyHandler( - (AutoInjector i, ModularArguments args) => Response.ok(''), - ), - isNotNull, - ); - }); - - test('HandlerTwoParams5', () { - expect( - _applyHandler((AutoInjector i, Request request) => Response.ok('')), - isNotNull, - ); - }); - - test('HandlerThreeParams', () { - expect( - _applyHandler( - ( - Request request, - ModularArguments args, - AutoInjector i, - ) => - Response.ok(''), - ), - isNotNull, - ); - }); - - test('HandlerThreeParams1', () { - expect( - _applyHandler( - ( - Request request, - AutoInjector i, - ModularArguments args, - ) => - Response.ok(''), - ), - isNotNull, - ); - }); - test('HandlerThreeParams2', () { - expect( - _applyHandler( - ( - ModularArguments args, - Request request, - AutoInjector i, - ) => - Response.ok(''), - ), - isNotNull, - ); - }); - - test('HandlerThreeParams3', () { - expect( - _applyHandler( - ( - ModularArguments args, - AutoInjector i, - Request request, - ) => - Response.ok(''), - ), - isNotNull, - ); - }); - - test('HandlerThreeParams4', () { - expect( - _applyHandler( - ( - AutoInjector i, - ModularArguments args, - Request request, - ) => - Response.ok(''), - ), - isNotNull, - ); - }); - test('HandlerThreeParams5', () { - expect( - _applyHandler( - ( - AutoInjector i, - Request request, - ModularArguments args, - ) => - Response.ok(''), - ), - isNotNull, - ); - }); -} diff --git a/shelf_modular/test/src/presenter/models/route_test.dart b/shelf_modular/test/src/presenter/models/route_test.dart deleted file mode 100644 index cf7db117..00000000 --- a/shelf_modular/test/src/presenter/models/route_test.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:shelf_modular/shelf_modular.dart'; -import 'package:test/test.dart'; - -void main() { - test('route get', () { - final route = Route.get('/', () {}); - expect(route.name, '/'); - expect(route.schema, 'GET'); - }); - - test('route post', () { - final route = Route.post('/', () {}); - expect(route.name, '/'); - expect(route.schema, 'POST'); - }); - test('route delete', () { - final route = Route.delete('/', () {}); - expect(route.name, '/'); - expect(route.schema, 'DELETE'); - }); - test('route PATCH', () { - final route = Route.path('/', () {}); - expect(route.name, '/'); - expect(route.schema, 'PATCH'); - }); - test('route PUT', () { - final route = Route.put('/', () {}); - expect(route.name, '/'); - expect(route.schema, 'PUT'); - }); - test('route module', () { - final route = Route.module('/', module: MyModule()); - expect(route.name, '/'); - }); - test('route resource', () { - final route = Route.resource('/', resource: MyResource()); - expect(route.name, '/'); - }); - - test('route resource', () { - final route = Route.websocket('/', websocket: MyWebsocketResource()); - expect(route.name, '/'); - }); - test('route copyWith', () { - final route = Route.resource('/', resource: MyResource()).copyWith(); - expect(route.name, '/'); - }); -} - -class MyModule extends Module {} - -class MyResource extends Resource { - @override - void routes(RouteManager r) {} -} - -class MyWebsocketResource extends WebSocketResource { - @override - void connect(WebSocket socket) {} - - @override - void disconnect(WebSocket socket) {} - - @override - void onMessage(dynamic data, WebSocket socket) {} -} diff --git a/shelf_modular/test/src/presenter/modular_base_test.dart b/shelf_modular/test/src/presenter/modular_base_test.dart deleted file mode 100644 index 2356086b..00000000 --- a/shelf_modular/test/src/presenter/modular_base_test.dart +++ /dev/null @@ -1,292 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:http_parser/http_parser.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:result_dart/result_dart.dart'; -import 'package:shelf/shelf.dart'; -import 'package:shelf_modular/shelf_modular.dart'; -import 'package:shelf_modular/src/domain/dtos/route_dto.dart'; -import 'package:shelf_modular/src/domain/errors/errors.dart'; -import 'package:shelf_modular/src/presenter/errors/errors.dart'; -import 'package:shelf_modular/src/presenter/modular_base.dart'; -import 'package:test/test.dart'; - -import '../mocks/mocks.dart'; - -void main() { - final disposeBind = DisposeBindMock(); - final getBind = GetBindMock(); - final getArguments = GetArgumentsMock(); - final finishModule = FinishModuleMock(); - final startModule = StartModuleMock(); - final getRoute = GetRouteMock(); - - final reportPush = ReportPushMock(); - - late IModularBase modularBase; - - setUpAll(() { - registerFallbackValue(const RouteParmsDTO(url: '/')); - }); - - setUp(() { - modularBase = ModularBase( - disposeBind, - finishModule, - getBind, - startModule, - getRoute, - getArguments, - reportPush, - ); - }); - - test('dispose', () { - when(disposeBind.call).thenReturn(const Success(true)); - expect(modularBase.dispose(), true); - }); - - test('get', () { - when(() => getBind.call()).thenReturn(const Success('modular')); - expect(modularBase.get(), 'modular'); - expect(modularBase.tryGet(), 'modular'); - }); - - test('destroy', () { - when(finishModule.call).thenReturn(const Success(unit)); - modularBase.destroy(); - verify(finishModule.call).called(1); - }); - - test('start (call)', () { - final module = ModuleMock(); - when(() => startModule.call(module)).thenReturn(const Success(unit)); - final handler = modularBase.call( - module: module, - middlewares: [MyGuard(true)], - ); - - verify(() => startModule.call(module)).called(1); - expect(handler, isA Function(Request)>()); - expect( - () => modularBase.start(module: module), - throwsA(isA()), - ); - }); - - test('handler', () async { - final request = RequestMock(); - final response = Response.ok('test'); - final route = RouteMock(); - - when(() => request.method).thenReturn('GET'); - when(() => request.url).thenReturn(Uri.parse('')); - - when(() => route.middlewares).thenReturn([]); - when(() => route.handler).thenReturn(() => response); - when(getArguments.call).thenReturn(Success(ModularArguments.empty())); - when(() => getRoute.call(any())).thenAnswer((_) async => Success(route)); - when(() => reportPush.call(route)).thenReturn(const Success(unit)); - - final result = await (modularBase as ModularBase).handler(request); - expect(result.statusCode, 200); - }); - - test('handler with error', () async { - final request = RequestMock(); - final route = RouteMock(); - - when(() => request.method).thenReturn('GET'); - when(() => request.url).thenReturn(Uri.parse('')); - when(() => route.handler).thenReturn((String v) {}); - when(() => route.middlewares).thenReturn([]); - - when(getArguments.call).thenReturn(Success(ModularArguments.empty())); - - when(() => request.method).thenReturn('GET'); - when(() => request.url).thenReturn(Uri.parse('')); - when(() => getRoute.call(any())).thenThrow(Exception()); - - when(() => reportPush.call(route)).thenReturn(const Success(unit)); - - final result = await (modularBase as ModularBase).handler(request); - expect(result.statusCode, 500); - }); - test('handler with hijacked request', () async { - final request = RequestMock(); - when(() => request.method).thenThrow( - Exception('Got a response for hijacked request'), - ); - - final result = await (modularBase as ModularBase).handler(request); - expect(result.statusCode, 200); - }); - - test('handler not found because is not Route', () async { - final request = RequestMock(); - final route = ModularRouteMock(); - when(() => route.middlewares).thenReturn([]); - - when(() => request.method).thenReturn('GET'); - when(() => request.url).thenReturn(Uri.parse('')); - when(() => getRoute.call(any())).thenAnswer((_) async => Success(route)); - - when(() => reportPush.call(route)).thenReturn(const Success(unit)); - - final result = await (modularBase as ModularBase).handler(request); - expect(result.statusCode, 404); - }); - - test('handler error handlerFunction return', () async { - final request = RequestMock(); - final route = RouteMock(); - - when(() => request.method).thenReturn('GET'); - when(() => request.url).thenReturn(Uri.parse('')); - when(() => route.handler).thenReturn((String v) {}); - when(() => route.middlewares).thenReturn([]); - - when(getArguments.call).thenReturn(Success(ModularArguments.empty())); - when(() => getRoute.call(any())).thenAnswer((_) async => Success(route)); - - when(() => reportPush.call(route)).thenReturn(const Success(unit)); - - final result = await (modularBase as ModularBase).handler(request); - expect(result.statusCode, 500); - }); - - test('handler error in route RouteNotFoundException', () async { - final request = RequestMock(); - final route = RouteMock(); - - when(() => request.method).thenReturn('GET'); - when(() => request.url).thenReturn(Uri.parse('')); - when(() => route.handler).thenReturn(() {}); - when(() => route.middlewares).thenReturn([]); - - when(getArguments.call).thenReturn(Success(ModularArguments.empty())); - when(() => getRoute.call(any())).thenAnswer( - (_) async => const Failure(RouteNotFoundException('')), - ); - - when(() => reportPush.call(route)).thenReturn(const Success(unit)); - - final result = await (modularBase as ModularBase).handler(request); - expect(result.statusCode, 404); - }); - - test('handler error in route (other errors)', () async { - final request = RequestMock(); - final route = RouteMock(); - - when(() => request.method).thenReturn('GET'); - when(() => request.url).thenReturn(Uri.parse('')); - when(() => route.handler).thenReturn(() {}); - when(() => route.middlewares).thenReturn([]); - - when(getArguments.call).thenReturn(Success(ModularArguments.empty())); - when(() => getRoute.call(any())).thenAnswer( - (_) async => const Failure(ModuleStartedException('')), - ); - - when(() => reportPush.call(route)).thenReturn(const Success(unit)); - - final result = await (modularBase as ModularBase).handler(request); - expect(result.statusCode, 500); - }); - - test("handler error 'Handler not correct'", () async { - final request = RequestMock(); - final route = RouteMock(); - - when(() => request.method).thenReturn('GET'); - when(() => request.url).thenReturn(Uri.parse('')); - when(() => route.handler).thenReturn(() {}); - when(() => route.middlewares).thenReturn([]); - - when(getArguments.call).thenReturn(Success(ModularArguments.empty())); - when(() => getRoute.call(any())).thenAnswer( - (_) async => const Failure(ModuleStartedException('')), - ); - - when(() => reportPush.call(route)).thenReturn(const Success(unit)); - - final result = await (modularBase as ModularBase).handler(request); - expect(result.statusCode, 500); - }); - - test('handler with guard', () async { - final request = RequestMock(); - final response = Response.ok('test'); - final route = RouteMock(); - - when(() => request.method).thenReturn('GET'); - when(() => request.url).thenReturn(Uri.parse('')); - when(() => route.uri).thenReturn(Uri.parse('/')); - - when(() => route.middlewares).thenReturn([MyGuard(true), MyGuard(false)]); - when(() => route.handler).thenReturn(() => response); - when(getArguments.call).thenReturn(Success(ModularArguments.empty())); - when(() => getRoute.call(any())).thenAnswer((_) async => Success(route)); - - when(() => reportPush.call(route)).thenReturn(const Success(unit)); - - final result = await (modularBase as ModularBase).handler(request); - expect(result.statusCode, 403); - - expect(MyGuard(true).pre(route), route); - expect(MyGuard(true).pos(route, request), route); - }); - - test('tryJsonDecode isMultipart false', () async { - final request = RequestMock(); - when(() => request.method).thenReturn('POST'); - when(() => request.headers).thenReturn({}); - when(request.readAsString).thenAnswer( - (_) async => jsonEncode({'name': 'Jacob'}), - ); - final result = await (modularBase as ModularBase).tryJsonDecode(request); - expect(result['name'], 'Jacob'); - }); - - test('tryJsonDecode isMultipart false with FormatException', () async { - final request = RequestMock(); - when(() => request.method).thenReturn('POST'); - when(() => request.headers).thenReturn( - {'Content-Type': MediaType('image', 'png').toString()}, - ); - when(request.readAsString).thenThrow(const FormatException()); - - final result = await (modularBase as ModularBase).tryJsonDecode(request); - expect(result, {}); - }); - - test('tryJsonDecode isMultipart true return {}', () async { - final request = RequestMock(); - when(() => request.method).thenReturn('POST'); - when(() => request.headers).thenReturn( - { - 'Content-Type': MediaType( - 'multipart', - 'form-data', - {'boundary': 'boundary'}, - ).toString(), - }, - ); - final result = await (modularBase as ModularBase).tryJsonDecode(request); - expect(result.isEmpty, true); - }); -} - -class MyGuard extends RouteGuard { - final bool activate; - - // ignore: avoid_positional_boolean_parameters - MyGuard(this.activate); - - @override - FutureOr canActivate(Request request, Route route) { - return activate; - } -} diff --git a/shelf_modular/test/src/presenter/resources/websocket__resource_test.dart b/shelf_modular/test/src/presenter/resources/websocket__resource_test.dart deleted file mode 100644 index 605e5a52..00000000 --- a/shelf_modular/test/src/presenter/resources/websocket__resource_test.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'dart:async'; - -import 'package:mocktail/mocktail.dart'; -import 'package:shelf_modular/src/presenter/resources/websocket_resource.dart'; -import 'package:test/test.dart'; -import 'package:web_socket_channel/web_socket_channel.dart'; - -import '../../mocks/mocks.dart'; - -class WebSocketChannelMock extends Mock implements WebSocketChannel {} - -class WebSocketSinkMock extends Fake implements WebSocketSink { - final Sink sink; - - WebSocketSinkMock(this.sink); - - @override - void add(dynamic data) => sink.add(data); -} - -void main() { - test('handle', () { - final request = RequestMock(); - final resource = MyWebsocketResource(); - expect( - () async => await resource.handler(request), - throwsA(isA()), - ); - }); - - test('connectSocket', () async { - final controllerGeneral = StreamController.broadcast(); - final controllerResourceSocket = StreamController.broadcast(); - final resource = MyWebsocketResource(controllerResourceSocket); - final channel = WebSocketChannelMock(); - - final sinkMock = WebSocketSinkMock(controllerGeneral.sink); - - when(() => channel.stream).thenAnswer((_) => controllerGeneral.stream); - when(() => channel.sink).thenReturn(sinkMock); - expect( - controllerResourceSocket.stream, - emitsInOrder([isA(), isA(), isA()]), - ); - - //start - resource.connectSocket(channel); - resource.connectSocket(channel); - controllerGeneral.sink.add('message'); - await controllerGeneral.sink.close(); - - expect(resource.message, equals('message')); - }); -} - -class MyWebsocketResource extends WebSocketResource { - final StreamController? controllerResourceSocket; - - String message = ''; - - MyWebsocketResource([this.controllerResourceSocket]); - - @override - void connect(WebSocket socket) { - controllerResourceSocket?.add(socket); - socket.emit('teste'); - socket.sink.add('teste'); - if (socket.enteredRooms.isNotEmpty) { - throw Exception('should be empty'); - } - - socket.joinRoom('room'); - socket.emitToRooms('teste'); - if (socket.enteredRooms.isEmpty) { - throw Exception('should be not empty'); - } - socket.leaveRoom('room'); - } - - @override - void disconnect(WebSocket socket) { - controllerResourceSocket?.add(socket); - } - - @override - void onMessage(dynamic data, WebSocket socket) { - message = data; - controllerResourceSocket?.add(socket); - } -} diff --git a/shelf_modular/test/src/shelf_modular_module_test.dart b/shelf_modular/test/src/shelf_modular_module_test.dart deleted file mode 100644 index c4db53f0..00000000 --- a/shelf_modular/test/src/shelf_modular_module_test.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:shelf_modular/src/presenter/modular_base.dart'; -import 'package:shelf_modular/src/shelf_modular_module.dart'; -import 'package:test/test.dart'; - -void main() { - test('resolver injection (ModularBase)', () { - expect(injector.get(), isA()); - }); -} diff --git a/test/arguments_test.dart b/test/arguments_test.dart new file mode 100644 index 00000000..7f8b639e --- /dev/null +++ b/test/arguments_test.dart @@ -0,0 +1,149 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +/// Launches `/editor` with an object argument and shows whatever it pops back. +class _Home extends StatefulWidget { + const _Home(); + @override + State<_Home> createState() => _HomeState(); +} + +class _HomeState extends State<_Home> { + String? result; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + Text(result == null ? 'no-result' : 'result:$result'), + TextButton( + onPressed: () async { + final r = await context.pushNamed( + '/editor', + arguments: 'payload-42', + ); + if (!mounted) return; + setState(() => result = r); + }, + child: const Text('open'), + ), + ], + ), + ); + } +} + +Widget _editor(BuildContext ctx, RouteState s) => Scaffold( + body: Column( + children: [ + Text('arg:${s.arguments}'), + TextButton( + onPressed: () => ctx.pop('edited!'), + child: const Text('save'), + ), + ], + ), +); + +final rootArgsModule = createModule( + register: (c) { + c + ..route('/', child: (ctx, s) => const _Home()) + ..route('/editor', child: _editor); + }, +); + +/// Same flow, but the launcher/editor live INSIDE a nested outlet. +class _Launcher extends StatefulWidget { + const _Launcher(); + @override + State<_Launcher> createState() => _LauncherState(); +} + +class _LauncherState extends State<_Launcher> { + String? result; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text(result == null ? 'none' : 'got:$result'), + TextButton( + onPressed: () async { + final r = await context.pushNamed( + '/shell/editor', + arguments: 'X', + ); + if (!mounted) return; + setState(() => result = r); + }, + child: const Text('go'), + ), + ], + ); + } +} + +final shellArgsModule = createModule( + register: (c) { + c.route( + '/shell', + child: (ctx, s) => const Scaffold(body: RouterOutlet()), + children: (sub) { + sub + ..route('/', child: (ctx, s) => const _Launcher()) + ..route('/editor', child: _editor); + }, + ); + }, +); + +void main() { + testWidgets('arguments reach RouteState; pop result completes pushNamed', ( + tester, + ) async { + final boot = bootstrapModule(rootArgsModule); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig(boot.routes, injector: boot.injector), + ), + ); + await tester.pumpAndSettle(); + expect(find.text('no-result'), findsOneWidget); + + await tester.tap(find.text('open')); + await tester.pumpAndSettle(); + expect(find.text('arg:payload-42'), findsOneWidget); // arg → RouteState + + await tester.tap(find.text('save')); + await tester.pumpAndSettle(); + expect(find.text('result:edited!'), findsOneWidget); // pop result awaited + }); + + testWidgets('arguments + pop result also work through a nested outlet', ( + tester, + ) async { + final boot = bootstrapModule(shellArgsModule); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig( + boot.routes, + injector: boot.injector, + initialRoute: '/shell', + ), + ), + ); + await tester.pumpAndSettle(); + expect(find.text('none'), findsOneWidget); + + await tester.tap(find.text('go')); + await tester.pumpAndSettle(); + expect(find.text('arg:X'), findsOneWidget); + + await tester.tap(find.text('save')); + await tester.pumpAndSettle(); + expect(find.text('got:edited!'), findsOneWidget); + }); +} diff --git a/test/can_pop_test.dart b/test/can_pop_test.dart new file mode 100644 index 00000000..135c9301 --- /dev/null +++ b/test/can_pop_test.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +/// A MANUAL shell (the case `RouterOutlet` is for): a page whose body is a +/// `RouterOutlet` rendering its children. Its index child is the sole/seed +/// entry of the nested Navigator, so the automatic AppBar arrow can't see the +/// root stack below — that is where `context.canPop()` is needed. +final shellModule = createModule( + register: (c) { + c + ..route( + '/', + child: (ctx, s) => Scaffold( + body: TextButton( + onPressed: () => ctx.pushNamed('/shell'), + child: const Text('go'), + ), + ), + ) + ..route( + '/shell', + child: (ctx, s) => const Scaffold(body: RouterOutlet()), + children: (sub) { + sub.route( + '/', + child: (ctx, s) => Scaffold( + appBar: AppBar( + leading: ctx.canPop() + ? BackButton(onPressed: () => ctx.pop()) + : null, + title: const Text('inner'), + ), + ), + ); + }, + ); + }, +); + +void main() { + testWidgets( + 'canPop() bubbles to the root from a shell index (outlet seed), so an ' + 'explicit back button shows and returns home', + (tester) async { + final boot = bootstrapModule(shellModule); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig( + boot.routes, + injector: boot.injector, + ), + ), + ); + await tester.pumpAndSettle(); + expect(find.text('go'), findsOneWidget); + + await tester.tap(find.text('go')); + await tester.pumpAndSettle(); + + // Inner index: its outlet Navigator has one page, but canPop() sees the + // root stack → the explicit BackButton is shown. + expect(find.text('inner'), findsOneWidget); + expect(find.byType(BackButton), findsOneWidget); + + await tester.tap(find.byType(BackButton)); + await tester.pumpAndSettle(); + expect(find.text('go'), findsOneWidget); // bubbled pop returned home + }, + ); +} diff --git a/test/disposable_test.dart b/test/disposable_test.dart new file mode 100644 index 00000000..4c7a9218 --- /dev/null +++ b/test/disposable_test.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +/// A NON-reactive resource: lifecycle only, no `ChangeNotifier`. +class Connection implements Disposable { + bool closed = false; + + @override + void dispose() => closed = true; +} + +/// A reactive VM that DEPENDS on the non-reactive resource — injected from the +/// same page-local scope, so it gets the page's single [Connection] instance. +class FeedVM extends ChangeNotifier { + FeedVM(this.connection); + final Connection connection; +} + +Connection? captured; + +final feedModule = createModule( + register: (c) { + c.route( + '/feed', + provide: (s) => s + ..addDisposable(Connection.new) + ..addChangeNotifier(FeedVM.new), + child: (ctx, state) { + captured = ctx.watch().connection; + return const Scaffold(body: Text('feed')); + }, + ); + }, +); + +void main() { + setUp(() => captured = null); + + testWidgets( + 'page-scoped Disposable is the same instance the VM injects (per-page ' + 'singleton) and is disposed on unmount — without being reactive', + (tester) async { + final boot = bootstrapModule(feedModule); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig( + boot.routes, + injector: boot.injector, + initialRoute: '/feed', + ), + ), + ); + await tester.pumpAndSettle(); + + final connection = captured!; + expect(connection.closed, isFalse); + + // Unmount the page. The Connection — never provided through an + // InheritedNotifier — is still cleaned up. Because the VM-injected + // instance is the one that closes, this also proves the per-page + // singleton sharing. + await tester.pumpWidget(const SizedBox()); + await tester.pumpAndSettle(); + + expect(connection.closed, isTrue); + }, + ); +} diff --git a/test/granularity_test.dart b/test/granularity_test.dart new file mode 100644 index 00000000..d9a6ddff --- /dev/null +++ b/test/granularity_test.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class MultiVM extends ChangeNotifier { + int a = 0; + int b = 0; + void incA() { + a++; + notifyListeners(); + } + + void incB() { + b++; + notifyListeners(); + } +} + +int selectorBuilds = 0; + +final gModule = createModule( + register: (c) { + c.route( + '/', + provide: (s) => s.addChangeNotifier(MultiVM.new), + child: (ctx, state) => Scaffold( + body: Column( + children: [ + Selector( + selector: (c, vm) => vm.a, + builder: (c, a, _) { + selectorBuilds++; + return Text('a:$a'); + }, + ), + TextButton( + onPressed: () => ctx.read().incA(), + child: const Text('incA'), + ), + TextButton( + onPressed: () => ctx.read().incB(), + child: const Text('incB'), + ), + ], + ), + ), + ); + }, +); + +void main() { + testWidgets('Selector rebuilds only when the selected value changes', ( + tester, + ) async { + selectorBuilds = 0; + final boot = bootstrapModule(gModule); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig(boot.routes, injector: boot.injector), + ), + ); + await tester.pumpAndSettle(); + expect(find.text('a:0'), findsOneWidget); + final builds = selectorBuilds; + + // Changing `b` must NOT rebuild the Selector (it selects `a`). + await tester.tap(find.text('incB')); + await tester.pumpAndSettle(); + expect(selectorBuilds, builds); + expect(find.text('a:0'), findsOneWidget); + + // Changing `a` rebuilds it. + await tester.tap(find.text('incA')); + await tester.pumpAndSettle(); + expect(selectorBuilds, greaterThan(builds)); + expect(find.text('a:1'), findsOneWidget); + }); +} diff --git a/test/guard_test.dart b/test/guard_test.dart new file mode 100644 index 00000000..0dcaa025 --- /dev/null +++ b/test/guard_test.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +String? authGuard(RouteState state) => + '/login'; // always redirect, for the test + +final guardedModule = createModule( + register: (c) { + c + ..route('/', child: (ctx, s) => const Text('home')) + ..route( + '/admin', + guards: [authGuard], + child: (ctx, s) => const Text('admin'), + ) + ..route('/login', child: (ctx, s) => const Text('login')); + }, +); + +void main() { + testWidgets('a guard redirects before the page is shown', (tester) async { + final boot = bootstrapModule(guardedModule); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig(boot.routes, initialRoute: '/admin'), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('login'), findsOneWidget); + expect(find.text('admin'), findsNothing); + }); + + testWidgets('unguarded route is shown normally', (tester) async { + final boot = bootstrapModule(guardedModule); + await tester.pumpWidget( + MaterialApp.router(routerConfig: modularRouterConfig(boot.routes)), + ); + await tester.pumpAndSettle(); + + expect(find.text('home'), findsOneWidget); + }); +} diff --git a/test/inject_test.dart b/test/inject_test.dart new file mode 100644 index 00000000..ffcf07bd --- /dev/null +++ b/test/inject_test.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class Service { + String hello() => 'hi'; +} + +class AuthState { + bool open = false; +} + +void main() { + test('inject() resolves from the active graph after bootstrap', () { + final module = createModule( + register: (c) { + c.addSingleton(Service.new); + }, + ); + bootstrapModule(module); + + expect(inject(), isA()); + expect(inject().hello(), 'hi'); + }); + + testWidgets('a guard reads DI through inject() (no exposed injector)', ( + tester, + ) async { + final module = createModule( + register: (c) { + c + ..addSingleton(AuthState.new) + ..route('/', child: (ctx, s) => const Text('home')) + ..route( + '/secret', + guards: [(s) => inject().open ? null : '/'], + child: (ctx, s) => const Text('secret'), + ); + }, + ); + final boot = bootstrapModule(module); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig( + boot.routes, + injector: boot.injector, + manager: boot.manager, + initialRoute: '/secret', + ), + ), + ); + await tester.pumpAndSettle(); + + // Closed → the guard (reading DI via inject) redirected to home. + expect(find.text('secret'), findsNothing); + expect(find.text('home'), findsOneWidget); + }); +} diff --git a/test/modular_app_test.dart b/test/modular_app_test.dart new file mode 100644 index 00000000..913a986d --- /dev/null +++ b/test/modular_app_test.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class ThemeViewModel extends ChangeNotifier { + ThemeMode mode = ThemeMode.light; + void toggle() { + mode = mode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light; + notifyListeners(); + } +} + +final module = createModule( + register: (c) { + c.route( + '/', + child: (ctx, s) => Scaffold( + body: Center( + child: TextButton( + onPressed: () => ctx.read().toggle(), + child: const Text('toggle'), + ), + ), + ), + ); + }, +); + +class _Root extends StatelessWidget { + const _Root(); + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return MaterialApp.router( + themeMode: theme.mode, + theme: ThemeData.light(), + darkTheme: ThemeData.dark(), + routerConfig: ModularApp.routerConfigOf(context), + ); + } +} + +void main() { + testWidgets( + 'app-scoped VM above the MaterialApp drives the theme (rebuilds it); ' + 'a page below reaches it via context.read', + (tester) async { + await tester.pumpWidget( + ModularApp( + module: module, + provide: (s) => + s.addChangeNotifier(ThemeViewModel.new), + child: const _Root(), + ), + ); + await tester.pumpAndSettle(); + + MaterialApp app() => tester.widget(find.byType(MaterialApp)); + expect(app().themeMode, ThemeMode.light); + + await tester.tap(find.text('toggle')); + await tester.pumpAndSettle(); + + expect(app().themeMode, ThemeMode.dark); + }, + ); +} diff --git a/test/module_dispose_test.dart b/test/module_dispose_test.dart new file mode 100644 index 00000000..85cb0629 --- /dev/null +++ b/test/module_dispose_test.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +int created = 0; +int disposed = 0; + +/// A feature module's OWN bind. Created when the module's first route enters, +/// disposed only when its LAST route leaves. +class CatalogService implements Disposable { + CatalogService() { + created++; + } + + @override + void dispose() => disposed++; +} + +/// The feature module: a singleton + two routes that both belong to it. +final catalogModule = createModule( + path: '/catalog', + register: (c) { + c + ..addSingleton(CatalogService.new) + ..route( + '/', + child: (ctx, s) => Scaffold( + body: Column( + children: [ + const Text('catalog'), + TextButton( + onPressed: () => ctx.pushNamed('/catalog/item'), + child: const Text('toItem'), + ), + TextButton( + onPressed: () => ctx.pop(), + child: const Text('toHome'), + ), + ], + ), + ), + ) + ..route( + '/item', + child: (ctx, s) => Scaffold( + body: Column( + children: [ + const Text('item'), + TextButton(onPressed: () => ctx.pop(), child: const Text('back')), + ], + ), + ), + ); + }, +); + +final rootModule = createModule( + register: (c) { + c + ..route( + '/', + child: (ctx, s) => Scaffold( + body: TextButton( + onPressed: () => ctx.pushNamed('/catalog'), + child: const Text('open'), + ), + ), + ) + ..module(catalogModule); + }, +); + +void main() { + setUp(() { + created = 0; + disposed = 0; + }); + + testWidgets( + 'feature binds: bound on first route, disposed only after the LAST ' + 'route leaves, recreated on re-entry', + (tester) async { + final boot = bootstrapModule(rootModule); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig( + boot.routes, + injector: boot.injector, + manager: boot.manager, + ), + ), + ); + await tester.pumpAndSettle(); + + // Home: the catalog module has not been entered → not bound. + expect(created, 0); + expect(disposed, 0); + + // Enter route A (/catalog) → module bound, service created once. + await tester.tap(find.text('open')); + await tester.pumpAndSettle(); + expect(find.text('catalog'), findsOneWidget); + expect(created, 1); + expect(disposed, 0); + + // Push route B (/catalog/item) → SAME module still active, no rebuild. + await tester.tap(find.text('toItem')); + await tester.pumpAndSettle(); + expect(find.text('item'), findsOneWidget); + expect(created, 1); + expect(disposed, 0); + + // Pop B → A is still active → NOT disposed. + await tester.tap(find.text('back')); + await tester.pumpAndSettle(); + expect(find.text('catalog'), findsOneWidget); + expect(disposed, 0); + + // Pop A (last route of the module) → NOW disposed. + await tester.tap(find.text('toHome')); + await tester.pumpAndSettle(); + expect(find.text('open'), findsOneWidget); + expect(disposed, 1); + + // Re-enter → a fresh instance is created. + await tester.tap(find.text('open')); + await tester.pumpAndSettle(); + expect(created, 2); + expect(disposed, 1); + }, + ); +} diff --git a/test/module_test.dart b/test/module_test.dart new file mode 100644 index 00000000..779b2eea --- /dev/null +++ b/test/module_test.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class Counter { + int value = 0; +} + +final adminModule = createModule( + path: '/admin', + register: (c) { + c.route('/', child: (context, state) => const Text('admin home')); + }, +); + +final appModule = createModule( + register: (c) { + c + ..addSingleton(Counter.new) + ..route('/', child: (context, state) => const Text('home')) + ..route( + '/product/:id', + child: (context, state) => Text('product ${state['id']}'), + ) + ..module(adminModule); // admin declares path '/admin' + }, +); + +void main() { + test('module declares routes (params + mounted submodule) and binds', () { + final boot = bootstrapModule(appModule); + + expect(boot.routes.match(Uri.parse('/'))?.last.route.path, '/'); + expect( + boot.routes.match(Uri.parse('/product/42'))?.last.params['id'], + '42', + ); + + // A mounted module is a NAMESPACE: its '/' index is flattened to '/admin' + // (no outlet shell), so the chain is a single level. + final admin = boot.routes.match(Uri.parse('/admin')); + expect(admin?.length, 1); + expect(admin?.last.route.path, '/admin'); + + expect(boot.injector.get(), isA()); + }); + + test('a module path must start with "/" and carry no dynamic segment', () { + expect( + () => createModule(path: '/checkout', register: (_) {}), + returnsNormally, + ); + expect(() => createModule(register: (_) {}), returnsNormally); // no path ok + expect( + () => createModule(path: 'checkout', register: (_) {}), + throwsA(isA()), + ); + expect( + () => createModule(path: '/users/:id', register: (_) {}), + throwsA(isA()), + ); + }); + + testWidgets('renders a route declared by a mounted module', (tester) async { + final boot = bootstrapModule(appModule); + + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig(boot.routes, initialRoute: '/admin'), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('admin home'), findsOneWidget); + }); +} diff --git a/test/navigation_verbs_test.dart b/test/navigation_verbs_test.dart new file mode 100644 index 00000000..c8e55e57 --- /dev/null +++ b/test/navigation_verbs_test.dart @@ -0,0 +1,178 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +Widget _btn(String label, VoidCallback onPressed) => + TextButton(onPressed: onPressed, child: Text(label)); + +String? aResult; // result delivered back to the awaiter of `pushNamed('/a')` + +final module = createModule( + register: (c) { + c + ..route( + '/', + child: (ctx, s) => Scaffold( + body: Column( + children: [ + const Text('home'), + _btn( + 'toA', + () async => aResult = await ctx.pushNamed('/a'), + ), + _btn('tryPop', () => ctx.maybePop()), + ], + ), + ), + ) + ..route( + '/a', + child: (ctx, s) => Scaffold( + body: Column( + children: [ + const Text('A'), + _btn('toB', () => ctx.pushNamed('/b')), + _btn('replaceX', () => ctx.replace('/x')), + _btn('navX', () => ctx.navigate('/x')), + _btn( + 'popPushX', + () => ctx.popAndPushNamed('/x', result: 'fromA'), + ), + ], + ), + ), + ) + ..route( + '/b', + child: (ctx, s) => Scaffold( + body: Column( + children: [ + const Text('B'), + _btn('popHome', () => ctx.popUntil((st) => st.uri.path == '/')), + _btn( + 'pushXClear', + () => ctx.pushNamedAndRemoveUntil( + '/x', + (st) => st.uri.path == '/', + ), + ), + ], + ), + ), + ) + ..route( + '/x', + child: (ctx, s) => Scaffold( + body: Column( + children: [const Text('X'), _btn('back', () => ctx.maybePop())], + ), + ), + ); + }, +); + +Future _pump(WidgetTester tester) async { + final boot = bootstrapModule(module); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig( + boot.routes, + injector: boot.injector, + manager: boot.manager, + ), + ), + ); + await tester.pumpAndSettle(); +} + +void main() { + testWidgets('replace swaps the top route (back skips the replaced one)', ( + tester, + ) async { + await _pump(tester); + await tester.tap(find.text('toA')); + await tester.pumpAndSettle(); + await tester.tap(find.text('replaceX')); // [/, /a] → [/, /x] + await tester.pumpAndSettle(); + expect(find.text('X'), findsOneWidget); + + await tester.tap(find.text('back')); // pop X → home, NOT A + await tester.pumpAndSettle(); + expect(find.text('home'), findsOneWidget); + expect(find.text('A'), findsNothing); + }); + + testWidgets('popUntil pops back to the matching route', (tester) async { + await _pump(tester); + await tester.tap(find.text('toA')); + await tester.pumpAndSettle(); + await tester.tap(find.text('toB')); // [/, /a, /b] + await tester.pumpAndSettle(); + + await tester.tap(find.text('popHome')); + await tester.pumpAndSettle(); + expect(find.text('home'), findsOneWidget); + expect(find.text('A'), findsNothing); + expect(find.text('B'), findsNothing); + }); + + testWidgets('navigate resets the WHOLE stack (even home is gone)', ( + tester, + ) async { + await _pump(tester); + await tester.tap(find.text('toA')); // [/, /a] + await tester.pumpAndSettle(); + + await tester.tap(find.text('navX')); // → [/x] + await tester.pumpAndSettle(); + expect(find.text('X'), findsOneWidget); + expect(find.text('home'), findsNothing); // stack was reset, not pushed + expect(find.text('A'), findsNothing); + }); + + testWidgets('maybePop is a no-op at the stack base', (tester) async { + await _pump(tester); + await tester.tap(find.text('tryPop')); // nothing to pop + await tester.pumpAndSettle(); + expect(find.text('home'), findsOneWidget); // still here, no crash + }); + + testWidgets('popAndPushNamed pops with a result, then pushes', ( + tester, + ) async { + aResult = null; + await _pump(tester); + await tester.tap(find.text('toA')); // [/, /a], home awaits the result + await tester.pumpAndSettle(); + + await tester.tap(find.text('popPushX')); // pop /a (→ 'fromA'), push /x + await tester.pumpAndSettle(); + expect(find.text('X'), findsOneWidget); + expect(aResult, 'fromA'); // the popped /a delivered its result + + await tester.tap(find.text('back')); // pop X → home (A was replaced) + await tester.pumpAndSettle(); + expect(find.text('home'), findsOneWidget); + expect(find.text('A'), findsNothing); + }); + + testWidgets('pushNamedAndRemoveUntil clears down to the predicate', ( + tester, + ) async { + await _pump(tester); + await tester.tap(find.text('toA')); + await tester.pumpAndSettle(); + await tester.tap(find.text('toB')); // [/, /a, /b] + await tester.pumpAndSettle(); + + await tester.tap(find.text('pushXClear')); // push /x, remove until '/' + await tester.pumpAndSettle(); + expect(find.text('X'), findsOneWidget); // → [/, /x] + + await tester.tap(find.text('back')); // pop X → home + await tester.pumpAndSettle(); + expect(find.text('home'), findsOneWidget); + expect(find.text('A'), findsNothing); + expect(find.text('B'), findsNothing); + }); +} diff --git a/test/navigator_skeleton_test.dart b/test/navigator_skeleton_test.dart new file mode 100644 index 00000000..3c867067 --- /dev/null +++ b/test/navigator_skeleton_test.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('renders the matched route through Navigator 2.0', ( + tester, + ) async { + final routes = RouteCollection() + ..add(ModularRoute('/', (context, state) => const Text('home'))); + + await tester.pumpWidget( + MaterialApp.router(routerConfig: modularRouterConfig(routes)), + ); + await tester.pumpAndSettle(); + + expect(find.text('home'), findsOneWidget); + }); + + testWidgets('shows not-found for an unmatched path', (tester) async { + final routes = RouteCollection() + ..add(ModularRoute('/', (context, state) => const Text('home'))); + + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig(routes, initialRoute: '/missing'), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('Route not found'), findsOneWidget); + }); +} diff --git a/test/nested_navigator_test.dart b/test/nested_navigator_test.dart new file mode 100644 index 00000000..4cf66805 --- /dev/null +++ b/test/nested_navigator_test.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +int shellInits = 0; + +class _Shell extends StatefulWidget { + const _Shell(); + @override + State<_Shell> createState() => _ShellState(); +} + +class _ShellState extends State<_Shell> { + @override + void initState() { + super.initState(); + shellInits++; + } + + @override + Widget build(BuildContext context) => const Scaffold( + body: Column( + children: [ + Text('shell'), + Expanded(child: RouterOutlet()), + ], + ), + ); +} + +final nestedModule = createModule( + register: (c) { + c.route( + '/shell', + child: (ctx, s) => const _Shell(), + children: (sub) { + sub + ..route( + '/a', + child: (ctx, s) => Column( + children: [ + const Text('A'), + TextButton( + onPressed: () => ctx.pushNamed('/shell/b'), + child: const Text('toB'), + ), + ], + ), + ) + ..route( + '/b', + child: (ctx, s) => Column( + children: [ + const Text('B'), + TextButton( + onPressed: () => ctx.pop(), + child: const Text('popB'), + ), + ], + ), + ); + }, + ); + }, +); + +void main() { + testWidgets('outlet has its own push/pop sub-stack; the shell persists', ( + tester, + ) async { + shellInits = 0; + final boot = bootstrapModule(nestedModule); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig( + boot.routes, + injector: boot.injector, + initialRoute: '/shell/a', + ), + ), + ); + await tester.pumpAndSettle(); + expect(find.text('shell'), findsOneWidget); + expect(find.text('A'), findsOneWidget); + expect(shellInits, 1); + + // Push within the outlet → B on top of A, shell NOT recreated. + await tester.tap(find.text('toB')); + await tester.pumpAndSettle(); + expect(find.text('B'), findsOneWidget); + expect(find.text('shell'), findsOneWidget); + expect(shellInits, 1); // shell persisted → it was an outlet sub-stack push + + // Pop the outlet → back to A. + await tester.tap(find.text('popB')); + await tester.pumpAndSettle(); + expect(find.text('A'), findsOneWidget); + expect(find.text('B'), findsNothing); + expect(shellInits, 1); + }); +} diff --git a/test/outlet_test.dart b/test/outlet_test.dart new file mode 100644 index 00000000..59eda703 --- /dev/null +++ b/test/outlet_test.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class DashVM extends ChangeNotifier { + final String title = 'Dashboard'; +} + +class OverviewVM extends ChangeNotifier { + final int count = 7; +} + +final dashModule = createModule( + register: (c) { + c.route( + '/dashboard', + provide: (s) => s.addChangeNotifier(DashVM.new), + child: (ctx, state) => Scaffold( + body: Column( + children: [ + Text('shell:${ctx.watch().title}'), + const Expanded(child: RouterOutlet()), + ], + ), + ), + children: (sub) { + sub.route( + '/overview', + provide: (s) => s.addChangeNotifier(OverviewVM.new), + child: (ctx, state) { + final overview = ctx.watch(); + final dash = ctx.watch(); // parent VM, through the outlet + return Text('overview:${overview.count}:${dash.title}'); + }, + ); + }, + ); + }, +); + +void main() { + testWidgets( + 'renders nested child in the parent outlet; child sees parent VM', + (tester) async { + final boot = bootstrapModule(dashModule); + + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig( + boot.routes, + injector: boot.injector, + initialRoute: '/dashboard/overview', + ), + ), + ); + await tester.pumpAndSettle(); + + // Parent shell rendered... + expect(find.text('shell:Dashboard'), findsOneWidget); + // ...and the child rendered in its outlet, reading BOTH its own VM + // and the parent's DashVM through the nested scope. + expect(find.text('overview:7:Dashboard'), findsOneWidget); + }, + ); +} diff --git a/test/outlet_url_test.dart b/test/outlet_url_test.dart new file mode 100644 index 00000000..bc9a2213 --- /dev/null +++ b/test/outlet_url_test.dart @@ -0,0 +1,81 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +Widget _btn(String label, VoidCallback onPressed) => + TextButton(onPressed: onPressed, child: Text(label)); + +final outletKey = GlobalKey(); + +final module = createModule( + register: (c) { + c + ..route( + '/', + child: (ctx, s) => + Scaffold(body: _btn('toShell', () => ctx.navigate('/shell'))), + ) + ..route( + '/shell', + child: (ctx, s) => Scaffold(body: RouterOutlet(key: outletKey)), + children: (sub) { + sub + ..route('/', child: (ctx, s) => const Text('tabA')) + ..route('/b', child: (ctx, s) => const Text('tabB')) + ..route('/b/deep', child: (ctx, s) => const Text('deep')); + }, + ); + }, +); + +Future _pump(WidgetTester tester) async { + final boot = bootstrapModule(module); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig( + boot.routes, + injector: boot.injector, + manager: boot.manager, + ), + ), + ); + await tester.pumpAndSettle(); +} + +String? _url(WidgetTester tester) { + final router = + tester.widget(find.byWidgetPredicate((w) => w is Router)) as Router; + return (router.routerDelegate.currentConfiguration as RouteState?)?.uri.path; +} + +void main() { + testWidgets('a RouterOutlet tab switch shows in the URL; a push does not', ( + tester, + ) async { + await _pump(tester); + await tester.tap(find.text('toShell')); // navigate('/shell') + await tester.pumpAndSettle(); + expect(find.text('tabA'), findsOneWidget); + expect(_url(tester), '/shell'); // the shell base + + // Tab switch via the outlet → the URL now carries the sub-route. + outletKey.currentState!.navigate('/shell/b'); + await tester.pumpAndSettle(); + expect(find.text('tabB'), findsOneWidget); + expect(_url(tester), '/shell/b'); + + // A push INSIDE the outlet stays OUT of the URL (tab base unchanged). + unawaited(outletKey.currentState!.push('/shell/b/deep')); + await tester.pumpAndSettle(); + expect(find.text('deep'), findsOneWidget); + expect(_url(tester), '/shell/b'); + + // Pop back within the outlet → still the tab base. + outletKey.currentState!.pop(); + await tester.pumpAndSettle(); + expect(find.text('tabB'), findsOneWidget); + expect(_url(tester), '/shell/b'); + }); +} diff --git a/test/provide_test.dart b/test/provide_test.dart new file mode 100644 index 00000000..0691ca67 --- /dev/null +++ b/test/provide_test.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +int disposeCalls = 0; + +class FakeRepo { + String widgetName() => 'Widget X'; +} + +class ProductVM extends ChangeNotifier { + ProductVM(this._repo); + + final FakeRepo _repo; + String? name; + + void load() { + name = _repo.widgetName(); + notifyListeners(); + } + + @override + void dispose() { + disposeCalls++; + super.dispose(); + } +} + +final pModule = createModule( + register: (c) { + c + ..addSingleton(FakeRepo.new) + ..route( + '/product', + provide: (s) => s.addChangeNotifier(ProductVM.new), + child: (ctx, state) { + final vm = ctx.watch(); + return Scaffold( + body: Column( + children: [ + Text(vm.name ?? 'idle'), + TextButton( + onPressed: () => ctx.read().load(), + child: const Text('load'), + ), + ], + ), + ); + }, + ); + }, +); + +void main() { + setUp(() => disposeCalls = 0); + + testWidgets('builds the VM with module deps; watch rebuilds on notify', ( + tester, + ) async { + final boot = bootstrapModule(pModule); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig( + boot.routes, + injector: boot.injector, + initialRoute: '/product', + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('idle'), findsOneWidget); + + await tester.tap(find.text('load')); + await tester.pumpAndSettle(); + + // 'Widget X' proves the dep (FakeRepo) was resolved from the module + // injector AND that watch rebuilt on notifyListeners. + expect(find.text('Widget X'), findsOneWidget); + }); + + testWidgets('disposes the page-scoped VM on unmount', (tester) async { + final boot = bootstrapModule(pModule); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig( + boot.routes, + injector: boot.injector, + initialRoute: '/product', + ), + ), + ); + await tester.pumpAndSettle(); + expect(disposeCalls, 0); + + await tester.pumpWidget(const SizedBox()); + await tester.pumpAndSettle(); + expect(disposeCalls, 1); + }); +} diff --git a/test/relative_routes_test.dart b/test/relative_routes_test.dart new file mode 100644 index 00000000..1643c4c8 --- /dev/null +++ b/test/relative_routes_test.dart @@ -0,0 +1,138 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +// White-box: exercise the pure resolver directly for edge cases. +import 'package:flutter_modular/src/navigation/route_resolver.dart'; +import 'package:flutter_test/flutter_test.dart'; + +Widget _btn(String label, VoidCallback onPressed) => + TextButton(onPressed: onPressed, child: Text(label)); + +final module = createModule( + register: (c) { + c + ..route( + '/home', + child: (ctx, s) => Scaffold( + body: Column( + children: [ + const Text('home'), + _btn( + 'bare', + () => ctx.pushNamed('dashboard'), + ), // → /home/dashboard + _btn('dot', () => ctx.pushNamed('./dashboard')), // same + ], + ), + ), + ) + ..route( + '/home/dashboard', + child: (ctx, s) => Scaffold( + body: Column( + children: [ + const Text('dashboard'), + _btn( + 'up', + () => ctx.pushNamed('../settings'), + ), // → /home/settings + ], + ), + ), + ) + ..route( + '/home/settings', + child: (ctx, s) => const Scaffold(body: Text('settings')), + ); + }, +); + +Future _pump(WidgetTester tester) async { + final boot = bootstrapModule(module); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig( + boot.routes, + injector: boot.injector, + manager: boot.manager, + initialRoute: '/home', + ), + ), + ); + await tester.pumpAndSettle(); +} + +void main() { + group('resolveRoute (current route treated as a directory)', () { + test('bare and ./ append UNDER the current route', () { + expect( + resolveRoute('dashboard', Uri.parse('/home')).toString(), + '/home/dashboard', + ); + expect( + resolveRoute('./dashboard', Uri.parse('/home')).toString(), + '/home/dashboard', + ); + }); + + test('.. climbs one level', () { + expect( + resolveRoute('..', Uri.parse('/home/dashboard')).toString(), + '/home/', + ); + expect( + resolveRoute('../settings', Uri.parse('/home/dashboard')).toString(), + '/home/settings', + ); + }); + + test('a leading slash is absolute — current location is ignored', () { + expect( + resolveRoute('/products', Uri.parse('/home/dashboard')).toString(), + '/products', + ); + }); + + test('relative from the root', () { + expect( + resolveRoute('dashboard', Uri.parse('/')).toString(), + '/dashboard', + ); + }); + + test('a query on the reference is preserved', () { + expect( + resolveRoute('item?ref=x', Uri.parse('/products')).toString(), + '/products/item?ref=x', + ); + }); + + test('a trailing slash on the current route is honored as-is', () { + expect(resolveRoute('a', Uri.parse('/home/')).toString(), '/home/a'); + }); + }); + + group('context.pushNamed resolves relative paths', () { + testWidgets('bare name pushes under the current route', (tester) async { + await _pump(tester); + await tester.tap(find.text('bare')); // /home + dashboard + await tester.pumpAndSettle(); + expect(find.text('dashboard'), findsOneWidget); + }); + + testWidgets('./ behaves like a bare name', (tester) async { + await _pump(tester); + await tester.tap(find.text('dot')); + await tester.pumpAndSettle(); + expect(find.text('dashboard'), findsOneWidget); + }); + + testWidgets('../ climbs to a sibling', (tester) async { + await _pump(tester); + await tester.tap(find.text('bare')); // now on /home/dashboard + await tester.pumpAndSettle(); + await tester.tap(find.text('up')); // ../settings → /home/settings + await tester.pumpAndSettle(); + expect(find.text('settings'), findsOneWidget); + }); + }); +} diff --git a/test/route_state_test.dart b/test/route_state_test.dart new file mode 100644 index 00000000..4320ca80 --- /dev/null +++ b/test/route_state_test.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +final outletKey = GlobalKey(); + +/// A flat route with a `:id` param — proves `routeState()` RESOLVES path params +/// (the stored stack entry doesn't carry them; they're merged at render). +final paramsModule = createModule( + register: (c) { + c + ..route( + '/', + child: (ctx, s) => Scaffold( + body: TextButton( + onPressed: () => ctx.navigate('/users/42'), + child: const Text('go'), + ), + ), + ) + ..route( + '/users/:id', + // Reads the param via routeState(), not the builder's `s`. + child: (ctx, s) { + final id = ctx.routeState().params['id']; + return Text('user $id'); + }, + ); + }, +); + +/// A shell whose body is a [RouterOutlet] and whose SIBLING reads the current +/// location via `routeState()` — the exact shape of a bottom bar that must +/// reflect the active tab without being able to reach the outlet via `context`. +final shellModule = createModule( + register: (c) { + c.route( + '/shell', + child: (ctx, s) => Column( + children: [ + Expanded(child: RouterOutlet(key: outletKey)), + Builder(builder: (ctx) => Text('loc:${ctx.routeState().uri.path}')), + ], + ), + children: (sub) { + sub + ..route('/', child: (ctx, s) => const Text('A')) + ..route('/b', child: (ctx, s) => const Text('B')); + }, + ); + }, +); + +Future _pump(WidgetTester tester, Module module) async { + final boot = bootstrapModule(module); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig( + boot.routes, + injector: boot.injector, + manager: boot.manager, + ), + ), + ); + await tester.pumpAndSettle(); +} + +void main() { + testWidgets('routeState().params resolves the matched path params', ( + tester, + ) async { + await _pump(tester, paramsModule); + + await tester.tap(find.text('go')); // navigate('/users/42') + await tester.pumpAndSettle(); + + // The param is reached through routeState(), not the builder arg. + expect(find.text('user 42'), findsOneWidget); + }); + + testWidgets( + 'routeState() reactively reflects the active outlet sub-route to a ' + 'SIBLING of the outlet (the active-tab-highlight mechanism)', + (tester) async { + tester.platformDispatcher.defaultRouteNameTestValue = '/shell'; + addTearDown(tester.platformDispatcher.clearDefaultRouteNameTestValue); + await _pump(tester, shellModule); + + expect(find.text('A'), findsOneWidget); + expect(find.text('loc:/shell'), findsOneWidget); // sibling sees the base + + // Tab switch via the outlet → the SIBLING (which can't reach the + // outlet via context) re-derives the location reactively, no local state. + outletKey.currentState!.navigate('/shell/b'); + await tester.pumpAndSettle(); + expect(find.text('B'), findsOneWidget); + expect(find.text('loc:/shell/b'), findsOneWidget); + }, + ); + + testWidgets('routeState() exposes the deep-linked outlet sub-route on boot', ( + tester, + ) async { + // Boot straight into a non-default tab — the sibling must read THAT, not + // the shell base (the bug the highlight fix closes). + tester.platformDispatcher.defaultRouteNameTestValue = '/shell/b'; + addTearDown(tester.platformDispatcher.clearDefaultRouteNameTestValue); + await _pump(tester, shellModule); + + expect(find.text('B'), findsOneWidget); + expect(find.text('loc:/shell/b'), findsOneWidget); + }); +} diff --git a/test/router_config_test.dart b/test/router_config_test.dart new file mode 100644 index 00000000..e0692677 --- /dev/null +++ b/test/router_config_test.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class _RecordingObserver extends NavigatorObserver { + int pushes = 0; + @override + void didPush(Route route, Route? previousRoute) => pushes++; +} + +void main() { + testWidgets('routerConfig wires a custom navigatorKey and observers', ( + tester, + ) async { + final key = GlobalKey(); + final observer = _RecordingObserver(); + final routes = RouteCollection() + ..add(ModularRoute('/', (context, state) => const Text('home'))); + + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig( + routes, + navigatorKey: key, + observers: [observer], + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('home'), findsOneWidget); + expect(key.currentState, isNotNull); // our key drives the root Navigator + expect(observer.pushes, greaterThan(0)); // observer saw the initial route + }); + + testWidgets('initialRoute selects the first page when no deep link', ( + tester, + ) async { + final routes = RouteCollection() + ..add(ModularRoute('/', (context, state) => const Text('home'))) + ..add(ModularRoute('/start', (context, state) => const Text('start'))); + + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig(routes, initialRoute: '/start'), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('start'), findsOneWidget); + expect(find.text('home'), findsNothing); + }); +} diff --git a/test/routing_test.dart b/test/routing_test.dart new file mode 100644 index 00000000..67020a52 --- /dev/null +++ b/test/routing_test.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('extracts path params (/product/:id)', (tester) async { + final routes = RouteCollection() + ..add( + ModularRoute( + '/product/:id', + (context, state) => Text('product ${state['id']}'), + ), + ); + + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig(routes, initialRoute: '/product/42'), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('product 42'), findsOneWidget); + }); + + testWidgets('push grows the stack and pop returns', (tester) async { + final routes = RouteCollection() + ..add( + ModularRoute( + '/', + (context, state) => Scaffold( + body: Builder( + builder: (ctx) => TextButton( + onPressed: () => ctx.pushNamed('/details'), + child: const Text('go'), + ), + ), + ), + ), + ) + ..add( + ModularRoute( + '/details', + (context, state) => Scaffold( + body: Builder( + builder: (ctx) => TextButton( + onPressed: () => ctx.pop(), + child: const Text('back'), + ), + ), + ), + ), + ); + + await tester.pumpWidget( + MaterialApp.router(routerConfig: modularRouterConfig(routes)), + ); + await tester.pumpAndSettle(); + expect(find.text('go'), findsOneWidget); + + await tester.tap(find.text('go')); + await tester.pumpAndSettle(); + expect(find.text('back'), findsOneWidget); + + await tester.tap(find.text('back')); + await tester.pumpAndSettle(); + expect(find.text('go'), findsOneWidget); + expect(find.text('back'), findsNothing); + }); +} diff --git a/test/stream_test.dart b/test/stream_test.dart new file mode 100644 index 00000000..97a48bc1 --- /dev/null +++ b/test/stream_test.dart @@ -0,0 +1,37 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +final controller = StreamController.broadcast(); + +final sModule = createModule( + register: (c) { + c.route( + '/', + provide: (s) => s.addStream(() => controller.stream), + child: (ctx, state) { + final value = ctx.watch>().value; + return Text('v:${value ?? 'none'}', textDirection: TextDirection.ltr); + }, + ); + }, +); + +void main() { + testWidgets('addStream exposes the latest value reactively', (tester) async { + final boot = bootstrapModule(sModule); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig(boot.routes, injector: boot.injector), + ), + ); + await tester.pumpAndSettle(); + expect(find.text('v:none'), findsOneWidget); + + controller.add(42); + await tester.pumpAndSettle(); + expect(find.text('v:42'), findsOneWidget); + }); +} diff --git a/test/transition_test.dart b/test/transition_test.dart new file mode 100644 index 00000000..93e2e0ab --- /dev/null +++ b/test/transition_test.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +final tModule = createModule( + register: (c) { + c.route( + '/', + transition: TransitionType.fade, + child: (ctx, s) => const Text('faded'), + ); + }, +); + +void main() { + testWidgets('a route renders with a custom (fade) transition', ( + tester, + ) async { + final boot = bootstrapModule(tModule); + await tester.pumpWidget( + MaterialApp.router(routerConfig: modularRouterConfig(boot.routes)), + ); + await tester.pumpAndSettle(); + + // The fade page route builds and settles to the content. + expect(find.text('faded'), findsOneWidget); + expect(find.byType(FadeTransition), findsWidgets); + }); +} diff --git a/test/web_url_test.dart b/test/web_url_test.dart new file mode 100644 index 00000000..a55247ff --- /dev/null +++ b/test/web_url_test.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:flutter_test/flutter_test.dart'; + +// Web routing rules: +// 1. The URL mirrors the stack BASE (`navigate`/deep link own it); `pushNamed` +// layers pages that stay OUT of the URL (modal-like, lost on refresh). +// 2. A deep link / refresh boots straight to its route — the platform's real +// entry URL is honored, not collapsed to the root. + +Widget _btn(String label, VoidCallback onPressed) => + TextButton(onPressed: onPressed, child: Text(label)); + +final module = createModule( + register: (c) { + c + ..route( + '/', + child: (ctx, s) => Scaffold( + body: Column( + children: [ + const Text('home'), + _btn('push', () => ctx.pushNamed('/detail')), + _btn('navSettings', () => ctx.navigate('/settings')), + ], + ), + ), + ) + ..route( + '/detail', + child: (ctx, s) => Scaffold( + body: Column( + children: [ + const Text('detail'), + _btn('back', () => ctx.maybePop()), + ], + ), + ), + ) + ..route( + '/settings', + child: (ctx, s) => Scaffold( + body: Column( + children: [ + const Text('settings'), + _btn('pushFromSettings', () => ctx.pushNamed('/detail')), + ], + ), + ), + ); + }, +); + +Future _pump(WidgetTester tester) async { + final boot = bootstrapModule(module); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: modularRouterConfig( + boot.routes, + injector: boot.injector, + manager: boot.manager, + ), + ), + ); + await tester.pumpAndSettle(); +} + +/// What the browser address bar would show — read straight off the delegate's +/// reported configuration (the source of truth for the URL). +String? _url(WidgetTester tester) { + final router = + tester.widget(find.byWidgetPredicate((w) => w is Router)) as Router; + return (router.routerDelegate.currentConfiguration as RouteState?)?.uri.path; +} + +void main() { + testWidgets( + 'URL mirrors the base: push stays out, navigate sets it, pop keeps it', + (tester) async { + await _pump(tester); + expect(_url(tester), '/'); + + // push: the detail page shows, but the URL does NOT move off the base. + await tester.tap(find.text('push')); + await tester.pumpAndSettle(); + expect(find.text('detail'), findsOneWidget); + expect(_url(tester), '/'); + + // navigate: resets the stack AND owns the URL. + await tester.tap(find.text('back')); // back to home first + await tester.pumpAndSettle(); + await tester.tap(find.text('navSettings')); + await tester.pumpAndSettle(); + expect(find.text('settings'), findsOneWidget); + expect(find.text('home'), findsNothing); // stack was reset + expect(_url(tester), '/settings'); + + // push on top of the NEW base: still hidden from the URL. + await tester.tap(find.text('pushFromSettings')); + await tester.pumpAndSettle(); + expect(find.text('detail'), findsOneWidget); + expect(_url(tester), '/settings'); + + // pop back down to the base: the URL never moved. + await tester.tap(find.text('back')); + await tester.pumpAndSettle(); + expect(find.text('settings'), findsOneWidget); + expect(_url(tester), '/settings'); + }, + ); + + testWidgets('a deep link boots straight to that route (not the root)', ( + tester, + ) async { + // The platform hands us the real entry URL via defaultRouteName. + tester.platformDispatcher.defaultRouteNameTestValue = '/settings'; + addTearDown(tester.platformDispatcher.clearDefaultRouteNameTestValue); + + await _pump(tester); + + expect(find.text('settings'), findsOneWidget); + expect(find.text('home'), findsNothing); // did NOT fall back to '/' + expect(_url(tester), '/settings'); + }); +}