├── .github ├── FUNDING.yml └── workflows │ └── publish.yml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── main.dart ├── example ├── README.md ├── main.dart ├── pubspec.lock └── pubspec.yaml ├── lib ├── cli_dialog │ ├── cli_dialog.dart │ └── src │ │ ├── dialog.dart │ │ ├── keys.dart │ │ ├── list_chooser.dart │ │ ├── services.dart │ │ ├── stdin_service.dart │ │ ├── stdout_service.dart │ │ └── xterm.dart ├── json_dart_generator │ ├── class_type.dart │ ├── dart_code_generator.dart │ ├── extension.dart │ └── json_def.dart ├── metro │ ├── menu.dart │ ├── metro.dart │ ├── ny_cli.dart │ └── stubs │ │ ├── api_service_stub.dart │ │ ├── config_stub.dart │ │ ├── controller_stub.dart │ │ ├── custom_command_stub.dart │ │ ├── event_stub.dart │ │ ├── form_stub.dart │ │ ├── interceptor_stub.dart │ │ ├── model_stub.dart │ │ ├── navigation_hub_stub.dart │ │ ├── navigation_tab_state_journey.dart │ │ ├── network_method_stub.dart │ │ ├── page_stub.dart │ │ ├── page_w_controller_stub.dart │ │ ├── postman_api_service_stub.dart │ │ ├── provider_stub.dart │ │ ├── route_guard_stub.dart │ │ ├── theme_colors_stub.dart │ │ ├── theme_stub.dart │ │ ├── widget_state_managed_stub.dart │ │ ├── widget_stateful_stub.dart │ │ └── widget_stateless_stub.dart ├── nylo_framework.dart └── theme │ └── helper │ └── ny_theme.dart ├── pubspec.lock ├── pubspec.yaml ├── screenshots └── logo.png └── test └── nylo_framework_test.dart /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [agordn52] 2 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/publish.yml 2 | name: Publish to pub.dev 3 | 4 | on: 5 | push: 6 | tags: 7 | - 'v[0-9]+.[0-9]+.[0-9]+*' # tag-pattern on pub.dev: 'v' 8 | 9 | # Publish using the reusable workflow from dart-lang. 10 | jobs: 11 | publish-package: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v3 17 | 18 | - name: Set up Flutter 19 | uses: subosito/flutter-action@v2 20 | 21 | - name: Get dependencies 22 | run: flutter pub get 23 | 24 | - name: Analyze code 25 | run: flutter analyze 26 | 27 | - name: Format code 28 | run: dart format . 29 | 30 | - name: Check publish warnings 31 | run: flutter pub publish --dry-run 32 | 33 | - name: Publish package 34 | uses: k-paxian/dart-package-publisher@master 35 | with: 36 | credentialJson: ${{ secrets.CREDENTIAL_SECRET }} 37 | flutter: true 38 | skipTests: true 39 | 40 | - name: Update nylo.dev 41 | uses: fjogeleit/http-request-action@v1 42 | with: 43 | url: 'https://nylo.dev/api/github/actions/framework/version' 44 | method: 'POST' 45 | customHeaders: '{"Content-Type": "application/json", "X-GH-API-KEY": "${{ secrets.NYLO_API_GH_SECRET }}"}' 46 | data: '{"version": "${{ github.ref_name }}"}' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 76 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 1ad9baa8b99a2897c20f9e6e54d3b9b359ade314 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [6.8.7] - 2025-05-23 2 | 3 | * pubspec.yaml updates 4 | 5 | ## [6.8.6] - 2025-05-08 6 | 7 | * pubspec.yaml updates 8 | 9 | ## [6.8.5] - 2025-05-02 10 | 11 | * Fix: postman imports 12 | * pubspec.yaml updates 13 | 14 | ## [6.8.4] - 2025-04-30 15 | 16 | * pubspec.yaml updates 17 | 18 | ## [6.8.3] - 2025-04-24 19 | 20 | * pubspec.yaml updates 21 | 22 | ## [6.8.2] - 2025-04-19 23 | 24 | * pubspec.yaml updates 25 | 26 | ## [6.8.1] - 2025-04-16 27 | 28 | * Fix `runProcess` in ny_cli.dart 29 | 30 | ## [6.8.0] - 2025-04-16 31 | 32 | * New stub for creating `JourneyState`s in your project 33 | * Update `navigation_hub` stub 34 | * Ability to create multiple `stateful_widget`s at once using Metro. E.g. `metro make:stateful_widget home,settings` 35 | * Ability to create multiple `stateless_widget`s at once using Metro. E.g. `metro make:stateless_widget home,settings` 36 | * pubspec.yaml updates 37 | 38 | ## [6.7.6] - 2025-04-11 39 | 40 | * Fix methods: `addPackage` and `addPackages` 41 | 42 | ## [6.7.5] - 2025-04-11 43 | 44 | * Small refactor to metro.dart 45 | 46 | ## [6.7.4] - 2025-04-10 47 | 48 | * Fix commands not being run using `runProcess` 49 | 50 | ## [6.7.3] - 2025-04-09 51 | 52 | * Update pubspec.yaml 53 | 54 | ## [6.7.2] - 2025-04-09 55 | 56 | * Update command stub 57 | 58 | ## [6.7.1] - 2025-04-09 59 | 60 | * Update `builder` in NyCustomCommand class 61 | 62 | ## [6.7.0] - 2025-04-08 63 | 64 | * Added: `make:command` to metro cli so you can create custom commands in Nylo 🚀 65 | * Added: New stub for creating commands 66 | * Update metro cli to support custom commands 67 | * New ny_cli.dart file to handle custom commands 68 | * Update pubspec.yaml 69 | 70 | ## [6.6.5] - 2025-04-01 71 | 72 | * Update pubspec.yaml 73 | 74 | ## [6.6.4] - 2025-03-31 75 | 76 | * Update pubspec.yaml 77 | 78 | ## [6.6.3] - 2025-03-27 79 | 80 | * Update pubspec.yaml 81 | 82 | ## [6.6.2] - 2025-03-21 83 | 84 | * Update pubspec.yaml 85 | 86 | ## [6.6.1] - 2025-02-28 87 | 88 | * Update pubspec.yaml 89 | 90 | ## [6.6.0] - 2025-02-27 91 | 92 | * Update Form stub to include `submitButton` 93 | * Update pubspec.yaml 94 | 95 | ## [6.5.13] - 2025-02-23 96 | 97 | * Update GitHub workflows 98 | * Merge PR from [rytisder](https://github.com/rytisder) to fix page_w_controller_stub.dart 99 | * Update pubspec.yaml 100 | 101 | ## [6.5.12] - 2025-02-10 102 | 103 | * Update pubspec.yaml 104 | 105 | ## [6.5.11] - 2025-02-05 106 | 107 | * Update pubspec.yaml 108 | 109 | ## [6.5.10] - 2025-02-04 110 | 111 | * Update pubspec.yaml 112 | 113 | ## [6.5.9] - 2025-02-02 114 | 115 | * Update pubspec.yaml 116 | 117 | ## [6.5.8] - 2025-01-29 118 | 119 | * Update pubspec.yaml 120 | 121 | ## [6.5.7] - 2025-01-26 122 | 123 | * Update pubspec.yaml 124 | 125 | ## [6.5.6] - 2025-01-16 126 | 127 | * Update pubspec.yaml 128 | 129 | ## [6.5.5] - 2025-01-12 130 | 131 | * Fix model stub 132 | * Update pubspec.yaml 133 | 134 | ## [6.5.4] - 2025-01-06 135 | 136 | * Update pubspec.yaml 137 | 138 | ## [6.5.3] - 2025-01-04 139 | 140 | * Update route guard stub 141 | * Update pubspec.yaml 142 | 143 | ## [6.5.2] - 2024-12-31 144 | 145 | * Update copyright year 146 | * Update pubspec.yaml 147 | 148 | ## [6.5.1] - 2024-12-29 149 | 150 | * Update pubspec.yaml 151 | 152 | ## [6.5.0] - 2024-12-29 153 | 154 | * Update Form stub to include `init` 155 | * Update pubspec.yaml 156 | 157 | ## [6.4.4] - 2024-12-22 158 | 159 | * Update pubspec.yaml 160 | 161 | ## [6.4.3] - 2024-12-19 162 | 163 | * Update pubspec.yaml 164 | 165 | ## [6.4.2] - 2024-12-18 166 | 167 | * Update pubspec.yaml 168 | 169 | ## [6.4.1] - 2024-12-18 170 | 171 | * Update pubspec.yaml 172 | 173 | ## [6.4.0] - 2024-12-16 174 | 175 | * Added: 176 | * export 'package:nylo_support/widgets/styles/bottom_modal_sheet_style.dart'; 177 | * export 'package:nylo_support/helpers/ny_color.dart'; 178 | * export 'package:nylo_support/helpers/ny_text_style.dart'; 179 | 180 | ## [6.3.2] - 2024-12-13 181 | 182 | * Update pubspec.yaml 183 | 184 | ## [6.3.1] - 2024-12-12 185 | 186 | * Update pubspec.lock 187 | 188 | ## [6.3.0] - 2024-12-12 189 | 190 | * Update pubspec.yaml 191 | 192 | ## [6.2.10] - 2024-12-08 193 | 194 | * Update pubspec.yaml 195 | 196 | ## [6.2.9] - 2024-12-06 197 | 198 | * Update pubspec.yaml 199 | 200 | ## [6.2.8] - 2024-12-01 201 | 202 | * Update NavigationHub stub 203 | 204 | ## [6.2.7] - 2024-11-29 205 | 206 | * Update pubspec.yaml 207 | 208 | ## [6.2.6] - 2024-11-29 209 | 210 | * Update widget stubs to use `view` 211 | 212 | ## [6.2.5] - 2024-11-27 213 | 214 | * Update pubspec.yaml 215 | 216 | ## [6.2.4] - 2024-11-25 217 | 218 | * Update pubspec.yaml 219 | 220 | ## [6.2.3] - 2024-11-23 221 | 222 | * Update stub constructors to include `{super.key}` 223 | * Update pubspec.yaml 224 | 225 | ## [6.2.2] - 2024-11-15 226 | 227 | * Update pubspec.yaml 228 | 229 | ## [6.2.1] - 2024-11-13 230 | 231 | * Update pubspec.yaml 232 | 233 | ## [6.2.0] - 2024-11-10 234 | 235 | * Remove page_bottom_nav_stub 236 | * Update pubspec.yaml 237 | 238 | ## [6.1.2] - 2024-11-08 239 | 240 | * Update pubspec.yaml 241 | 242 | ## [6.1.1] - 2024-11-08 243 | 244 | * Update page_w_controller_stub 245 | * Contribution from [jitendravn](https://github.com/jitendravn) PR [#40](https://github.com/nylo-core/framework/pull/40) 246 | 247 | ## [6.1.0] - 2024-11-04 248 | 249 | * Update pubspec.yaml 250 | 251 | ## [6.0.0] - 2024-11-02 252 | 253 | * New version of Nylo - read more about the changes [here](https://nylo.dev/) 254 | 255 | ## [5.32.6] - 2024-07-18 256 | 257 | * Small refactor to cli_dialog to pass static analysis 258 | * Update pubspec.yaml 259 | 260 | ## [5.32.5] - 2024-07-09 261 | 262 | * Update import paths in Metro 263 | 264 | ## [5.32.4] - 2024-07-09 265 | 266 | * Update pubspec.yaml 267 | 268 | ## [5.32.3] - 2024-07-09 269 | 270 | * Update pubspec.yaml 271 | 272 | ## [5.32.2] - 2024-07-08 273 | 274 | * Refactor Form stub 275 | * Update pubspec.yaml 276 | 277 | ## [5.32.1] - 2024-07-06 278 | 279 | * Update pubspec.yaml 280 | 281 | ## [5.32.0] - 2024-07-06 282 | 283 | * Update the Form stub 284 | * Add new dependency `date_field` 285 | * update README 286 | * Update pubspec.yaml 287 | 288 | ## [5.31.2] - 2024-07-05 289 | 290 | * Update pubspec.yaml 291 | 292 | ## [5.31.1] - 2024-07-03 293 | 294 | * Update pubspec.yaml 295 | 296 | ## [5.31.0] - 2024-07-02 297 | 298 | * Ability to create Forms using Metro. E.g. `metro make:form register` 299 | * New stub for creating Forms 300 | * Small refactor to model stub 301 | * Refactor `slate` command to `metro slate:publish` 302 | * New `metro slate:install` command to install the slate package and publish all the files 303 | 304 | ## [5.30.0] - 2024-06-16 305 | 306 | * Update stubs 307 | * Update pubspec.yaml 308 | 309 | ## [5.29.6] - 2024-06-14 310 | 311 | * Update pubspec.yaml 312 | 313 | ## [5.29.5] - 2024-06-14 314 | 315 | * Update pubspec.yaml 316 | 317 | ## [5.29.4] - 2024-06-14 318 | 319 | * Update pubspec.yaml 320 | 321 | ## [5.29.3] - 2024-06-13 322 | 323 | * Update pubspec.yaml 324 | 325 | ## [5.29.2] - 2024-06-13 326 | 327 | * Fix `apiServiceStub` to use `destroy` instead of `delete` 328 | 329 | ## [5.29.1] - 2024-06-12 330 | 331 | * Update pubspec.yaml 332 | 333 | ## [5.29.0] - 2024-06-11 334 | 335 | * Fix `Metro` to not add duplicate suffixes to some files 336 | * Update pubspec.yaml 337 | 338 | ## [5.28.2] - 2024-06-06 339 | 340 | * Update pubspec.yaml 341 | 342 | ## [5.28.1] - 2024-06-05 343 | 344 | * Dart format 345 | 346 | ## [5.28.0] - 2024-06-05 347 | 348 | * Remove the ability to use a "postman.json" file in the `_makeApiService` command 349 | * Update pubspec.yaml 350 | 351 | ## [5.27.6] - 2024-05-22 352 | 353 | * Update pubspec.yaml 354 | 355 | ## [5.27.5] - 2024-05-17 356 | 357 | * Update pubspec.yaml 358 | 359 | ## [5.27.4] - 2024-05-14 360 | 361 | * Update pubspec.yaml 362 | * Add `dart_console` 363 | 364 | ## [5.27.3] - 2024-05-12 365 | 366 | * Downgrade `flutter_secure_storage` to ^9.0.0 367 | 368 | ## [5.27.2] - 2024-05-11 369 | 370 | * Update pubspec.yaml 371 | 372 | ## [5.27.1] - 2024-05-04 373 | 374 | * Update pubspec.yaml 375 | 376 | ## [5.27.0] - 2024-05-01 377 | 378 | * Add `error_stack` to framework 379 | 380 | ## [5.26.2] - 2024-04-29 381 | 382 | * Fix `interceptor` stub 383 | 384 | ## [5.26.1] - 2024-04-28 385 | 386 | * Update pubspec.yaml 387 | 388 | ## [5.26.0] - 2024-04-25 389 | 390 | * Ability create multiple pages at once using Metro. E.g. `metro make:page home,dashboard,settings` // this will create 3 pages home_page, dashboard_page and settings_page 391 | * Update pubspec.yaml 392 | 393 | ## [5.25.6] - 2024-04-25 394 | 395 | * Update pubspec.yaml 396 | 397 | ## [5.25.5] - 2024-04-20 398 | 399 | * Update pubspec.yaml 400 | 401 | ## [5.25.4] - 2024-04-20 402 | 403 | * Update pubspec.yaml 404 | 405 | ## [5.25.3] - 2024-04-18 406 | 407 | * Update metro make:model --json command to check if cli is running on Windows or MacOS 408 | 409 | ## [5.25.2] - 2024-04-17 410 | 411 | * Update pubspec.yaml 412 | 413 | ## [5.25.1] - 2024-04-08 414 | 415 | * Update pubspec.yaml 416 | 417 | ## [5.24.6] - 2024-04-01 418 | 419 | * Update pubspec.yaml 420 | 421 | ## [5.24.5] - 2024-03-31 422 | 423 | * Update pubspec.yaml 424 | 425 | ## [5.24.4] - 2024-03-30 426 | 427 | * Update pubspec.yaml 428 | 429 | ## [5.24.3] - 2024-03-28 430 | 431 | * Update pubspec.yaml 432 | 433 | ## [5.24.2] - 2024-03-26 434 | 435 | * Update pubspec.yaml 436 | 437 | ## [5.24.1] - 2024-03-25 438 | 439 | * Update pubspec.yaml 440 | 441 | ## [5.24.0] - 2024-03-22 442 | 443 | * Update stubs 444 | 445 | ## [5.23.0] - 2024-03-21 446 | 447 | * Update stubs 448 | * Update pubspec.yaml 449 | 450 | ## [5.22.2] - 2024-03-18 451 | 452 | * Update pubspec.yaml 453 | 454 | ## [5.22.1] - 2024-03-18 455 | 456 | * Update pubspec.yaml 457 | 458 | ## [5.22.0] - 2024-03-17 459 | 460 | * Update config stub 461 | * Update pubspec.yaml 462 | 463 | ## [5.21.10] - 2024-03-10 464 | 465 | * Update pubspec.yaml 466 | 467 | ## [5.21.9] - 2024-03-07 468 | 469 | * Update pubspec.yaml 470 | 471 | ## [5.21.8] - 2024-03-05 472 | 473 | * Update pubspec.yaml 474 | 475 | ## [5.21.7] - 2024-03-04 476 | 477 | * Add `skeletonizer` package 478 | * Update pubspec.yaml 479 | 480 | ## [5.21.6] - 2024-02-28 481 | 482 | * Update pubspec.yaml 483 | 484 | ## [5.21.5] - 2024-02-28 485 | 486 | * Update pubspec.yaml 487 | 488 | ## [5.21.4] - 2024-02-26 489 | 490 | * Update pubspec.yaml 491 | 492 | ## [5.21.3] - 2024-02-24 493 | 494 | * Update pubspec.yaml 495 | 496 | ## [5.21.2] - 2024-02-16 497 | 498 | * Update pubspec.yaml 499 | 500 | ## [5.21.1] - 2024-02-14 501 | 502 | * Update pubspec.yaml 503 | 504 | ## [5.21.0] - 2024-02-12 505 | 506 | * Tweak stubs 507 | * Update pubspec.yaml 508 | 509 | ## [5.20.18] - 2024-02-08 510 | 511 | * Update pubspec.yaml 512 | 513 | ## [5.20.17] - 2024-02-07 514 | 515 | * Update pubspec.yaml 516 | 517 | ## [5.20.16] - 2024-02-07 518 | 519 | * Update pubspec.yaml 520 | 521 | ## [5.20.15] - 2024-02-04 522 | 523 | * Update pubspec.yaml 524 | 525 | ## [5.20.14] - 2024-02-04 526 | 527 | * export `ny_language_switcher` to `nylo_framework.dart` 528 | * Update pubspec.yaml 529 | 530 | ## [5.20.13] - 2024-02-02 531 | 532 | * Update pubspec.yaml 533 | 534 | ## [5.20.12] - 2024-02-02 535 | 536 | * Update pubspec.yaml 537 | 538 | ## [5.20.11] - 2024-02-01 539 | 540 | * Update page stub docs 541 | 542 | ## [5.20.10] - 2024-02-01 543 | 544 | * Update pubspec.yaml 545 | 546 | ## [5.20.9] - 2024-01-29 547 | 548 | * Update pubspec.yaml 549 | 550 | ## [5.20.8] - 2024-01-28 551 | 552 | * Remove `await Future.delayed(Duration(seconds: 2));` from stub 553 | * Update config stub 554 | * Update pubspec.yaml 555 | 556 | ## [5.20.7] - 2024-01-26 557 | 558 | * Fix postman not automatically adding the `ApiService` into the `decoders.dart` file 559 | * Update pubspec.yaml 560 | 561 | ## [5.20.6] - 2024-01-24 562 | 563 | * Add `override` to model stub 564 | 565 | ## [5.20.5] - 2024-01-23 566 | 567 | * Update pubspec.yaml 568 | 569 | ## [5.20.4] - 2024-01-23 570 | 571 | * Update pubspec.yaml 572 | 573 | ## [5.20.3] - 2024-01-22 574 | 575 | * Update pubspec.yaml 576 | 577 | ## [5.20.2] - 2024-01-21 578 | 579 | * Update pubspec.yaml 580 | 581 | ## [5.20.1] - 2024-01-18 582 | 583 | * Update README.md 584 | 585 | ## [5.20.0] - 2024-01-18 586 | 587 | * New `--bottom-nav` flag to create bottom nav pages. E.g. `metro make:page dashboard_nav --bottom-nav` 588 | * Add new stub for creating pages that use bottom navigation tabs 589 | * Update pubspec.yaml 590 | 591 | ## [5.19.0] - 2024-01-15 592 | 593 | * Update page and controller stub to use `view` instead of `build` 594 | * Update the page and controller stub to include controller getter 595 | * Update pubspec.yaml 596 | 597 | ## [5.18.9] - 2024-01-13 598 | 599 | * Update pubspec.yaml 600 | 601 | ## [5.18.8] - 2024-01-13 602 | 603 | * Update pubspec.yaml 604 | 605 | ## [5.18.7] - 2024-01-11 606 | 607 | * Improve `metro make:api_service` when using Postman flag 608 | 609 | ## [5.18.6] - 2024-01-11 610 | 611 | * Improve `metro make:api_service` when using Postman flag 612 | 613 | ## [5.18.5] - 2024-01-10 614 | 615 | * Fix `metro make:api_service` error when using Postman flag 616 | 617 | ## [5.18.4] - 2024-01-06 618 | 619 | * Update pubspec.yaml 620 | 621 | ## [5.18.3] - 2024-01-03 622 | 623 | * Update pubspec.yaml 624 | 625 | ## [5.18.2] - 2024-01-03 626 | 627 | * Update stateful stub 628 | * Update pubspec.yaml 629 | 630 | ## [5.18.1] - 2024-01-02 631 | 632 | * Update pubspec.yaml 633 | 634 | ## [5.18.0] - 2024-01-01 635 | 636 | * Update pubspec.yaml 637 | 638 | ## [5.17.0] - 2024-01-01 639 | 640 | * Metro cli improvements 641 | * Ability to create pages in a subfolder. E.g. `metro make:page auth/login` 642 | * Ability to create models in a subfolder. E.g. `metro make:model auth/user` 643 | * Ability to create controllers in a subfolder. E.g. `metro make:controller auth/login` 644 | * Update `--postman` flag for API Services. Now you can run `metro make:api_service example_api_service --postman` and it will help you create an API Service from a Postman collection. 645 | * Update stubs 646 | * Add `cli_dialog` as a dependency 647 | * Update pubspec.yaml 648 | 649 | ## [5.16.0] - 2023-12-25 650 | 651 | * Update pubspec.yaml 652 | 653 | ## [5.15.0] - 2023-12-09 654 | 655 | * Export new `ny_route_history_observer.dart` file 656 | * Update pubspec.yaml 657 | 658 | ## [5.14.0] - 2023-12-03 659 | 660 | * Update pubspec.yaml 661 | 662 | ## [5.13.0] - 2023-12-02 663 | 664 | * Add new `--json` flag to the "make:model" command to create a model from a json string. E.g. `metro make:model user --json '{"name": "John Doe", "age": 30}'` 665 | 666 | ## [5.12.0] - 2023-12-02 667 | 668 | * Update pubspec.yaml 669 | 670 | ## [5.11.1] - 2023-12-01 671 | 672 | * Update pubspec.yaml 673 | 674 | ## [5.11.0] - 2023-11-25 675 | 676 | * Update pubspec.yaml 677 | 678 | ## [5.10.0] - 2023-11-23 679 | 680 | * Ability to set routes as the initialPage using **--initial** E.g. `metro make:page dashboard --initial` 681 | * Ability to set routes as the authPage using **--auth** E.g. `metro make:page dashboard --auth` 682 | * Update pubspec.yaml 683 | 684 | ## [5.9.0] - 2023-11-22 685 | 686 | * New ability to create dio Interceptors using Metro. E.g. `metro make:interceptor auth_token` 687 | * Update stubs for creating pages and api services 688 | * Small refactor to Metro class 689 | * Remove ny_base_networking.dart 690 | * Update pubspec.yaml 691 | 692 | ## [5.8.0] - 2023-11-04 693 | 694 | * Fix `make:api_service` when using Postman flag 695 | * New `publish:slate` command added to Metro 696 | * Added more docs to Metro methods 697 | 698 | ## [5.7.1] - 2023-10-27 699 | 700 | * Update page stub to include an example on how to set the state. 701 | 702 | ## [5.7.0] - 2023-10-22 703 | 704 | * Ability to auto add themes to the Nylo config using e.g. `metro make:theme bright_theme` 705 | * Update default theme stub to use `useMaterial3` 706 | * Update pubspec.yaml 707 | 708 | ## [5.6.0] - 2023-10-19 709 | 710 | * Add docblock to more APIs 711 | * Update pubspec.yaml 712 | 713 | ## [5.5.0] - 2023-10-17 714 | 715 | * Update page stub to use `NyPage` 716 | * Fix metro make:api_service Postman error when URL is null 717 | * Update pubspec.yaml 718 | 719 | ## [5.4.1] - 2023-10-08 720 | 721 | * Update pubspec.yaml 722 | 723 | ## [5.4.0] - 2023-10-01 724 | 725 | * Ability to create config files via Metro 726 | * Update pubspec.yaml 727 | 728 | ## [5.3.7] - 2023-09-22 729 | 730 | * Update pubspec.yaml 731 | 732 | ## [5.3.6] - 2023-09-14 733 | 734 | * Update pubspec.yaml 735 | 736 | ## [5.3.5] - 2023-08-31 737 | 738 | * Update pubspec.yaml 739 | 740 | ## [5.3.4] - 2023-08-31 741 | 742 | * Update pubspec.yaml 743 | 744 | ## [5.3.3] - 2023-08-31 745 | 746 | * Update pubspec.yaml 747 | 748 | ## [5.3.2] - 2023-08-28 749 | 750 | * Add docblock to setPagination method and fix the queryParameters not being reset when using the `api` helper 751 | 752 | ## [5.3.1] - 2023-08-26 753 | 754 | * Update pubspec.yaml 755 | 756 | ## [5.3.0] - 2023-08-25 757 | 758 | * Update stateful_stub to remove `stateInit` 759 | * Add `setPagination` to NyBaseNetworking 760 | * Update pubspec.yaml 761 | 762 | ## [5.2.1] - 2023-08-21 763 | 764 | * Update pubspec.yaml 765 | 766 | ## [5.2.0] - 2023-08-21 767 | 768 | * Add `useUndefinedResponse` to `network` helper. This will allow you to catch the response when your decoder.dart file fails to return the correct type. To listen to undefinedResponse's, call `onUndefinedResponse(dynamic data, Response response, BuildContext? context)` in your ApiService. 769 | * Update stateful_stub to include `stateInit` and `stateUpdated` 770 | * Small refactor to page_stub 771 | * Fix Metro error - The class 'FileSystemEvent' can't be extended, implemented 772 | * Add json_dart_generator into the library 773 | 774 | ## [5.1.2] - 2023-07-13 775 | 776 | * Pubspec.yaml dependency updates. 777 | 778 | ## [5.1.1] - 2023-07-03 779 | 780 | * Pubspec.yaml dependency updates. 781 | 782 | ## [5.1.0] - 2023-06-17 783 | 784 | * Breaking change to validation. 785 | * If you are using Nylo 5.x, do the following: 786 | * Go to **config/validation_rules.dart**. 787 | * Update 'final Map validationRules = {' 788 | * to this 'final Map validationRules = {' 789 | * Custom validation rules must now the follow the following format: 790 | * "simple_password": (attribute) => SimplePassword(attribute), // example 791 | * Pubspec.yaml dependency updates. 792 | 793 | ## [5.0.6] - 2023-06-14 794 | 795 | * Update Nylo framework version. 796 | 797 | ## [5.0.5] - 2023-06-14 798 | 799 | * Update GitHub actions. 800 | * Refactor metro class. 801 | * Pubspec.yaml dependency updates. 802 | 803 | ## [5.0.4] - 2023-06-08 804 | 805 | * Change nylo_framework version. 806 | 807 | ## [5.0.3] - 2023-06-08 808 | 809 | * Change `DioError` to `DioException` as per dio upgrade notes. 810 | * Refactor metro to use `runCommand` from MetroService. 811 | * Pubspec.yaml dependency updates. 812 | 813 | ## [5.0.2] - 2023-05-28 814 | 815 | * Pubspec.yaml dependency updates. 816 | 817 | ## [5.0.1] - 2023-05-24 818 | 819 | * Pubspec.yaml dependency updates. 820 | 821 | ## [5.0.0] - 2023-05-16 822 | 823 | * `NyBaseApiService` has a new method `getContext` to get the BuildContext. 824 | * Remove 'Storable' from the Model stub. 825 | * Page stubs now include a `path` variable to make routing easier. 826 | * New command to create Route Guards. 827 | * Pubspec.yaml dependency updates. 828 | * Readme update. 829 | 830 | ## [4.2.0] - 2023-05-16 831 | 832 | * Flutter v3.10.0 fixes: 833 | * Update: theme_provider package 834 | 835 | ## [4.1.5] - 2023-03-07 836 | 837 | * Fix metro make:model if modelDecoders doesn't exist. 838 | 839 | ## [4.1.4] - 2023-03-03 840 | 841 | * Pubspec.yaml dependency updates. 842 | 843 | * ## [4.1.3] - 2023-02-22 844 | 845 | * Pubspec.yaml dependency updates. 846 | 847 | ## [4.1.2] - 2023-02-14 848 | 849 | * Add logo to package 850 | * Pubspec.yaml dependency updates. 851 | 852 | ## [4.1.0] - 2023-01-30 853 | 854 | * Update stubs. 855 | * Now you can pass a baseUrl to the `network` or `nyApi` method. 856 | * Small refactor to the Metro class. 857 | * Fix importPaths when using `addToRouter`. 858 | * Pubspec.yaml dependency updates. 859 | 860 | ## [4.0.0] - 2023-01-01 861 | 862 | * Metro will automatically add Routes to the router.dart file 863 | * Metro will automatically add Events to the config/events.dart file 864 | * Metro will automatically add ApiServices and Models to the config/decoders.dart file 865 | * Metro will automatically add Providers to the config/providers.dart file 866 | * Create Api Services from Postman collections using Metro, read more [here](https://nylo.dev/docs/5.x/metro#make-api-service-using-postman) 867 | * `NyBaseApiService` class has new parameters to add a `bearerToken` or `headers` on each request 868 | * Update stubs 869 | * Copyright year updated 870 | * Pubspec.yaml dependency updates 871 | 872 | ## [3.4.0] - 2022-09-19 873 | 874 | * Add page_transition to dependencies 875 | * Pubspec.yaml dependency updates 876 | 877 | ## [3.3.0] - 2022-08-27 878 | 879 | * Update theme stubs 880 | * Pubspec.yaml dependency updates 881 | 882 | ## [3.2.0] - 2022-07-27 883 | 884 | * Update page stub to include default base Controller on creation 885 | * Refactor stubs using 'package:flutter_app' to use project path 886 | * Pubspec.yaml dependency updates 887 | 888 | ## [3.1.4] - 2022-06-28 889 | 890 | * Pubspec.yaml dependency updates 891 | 892 | ## [3.1.3] - 2022-05-19 893 | 894 | * Add @override to event stubs 895 | * Pubspec.yaml dependency updates 896 | 897 | ## [3.1.2] - 2022-05-04 898 | 899 | * Add version to nylo_framework 900 | 901 | ## [3.1.1] - 2022-05-04 902 | 903 | * Remove import from ny_base_networking.dart 904 | * Flutter format 905 | 906 | ## [3.1.0] - 2022-05-04 907 | 908 | * Add new `NyBaseApiService` class to the networking folder 909 | * Add dio as a dependency 910 | * Pubspec.yaml dependency updates 911 | 912 | ## [3.0.0] - 2022-04-29 913 | 914 | * New command to create: Events, Providers and API Services 915 | * Remove command: project:init 916 | * Docs link updated in Model stub 917 | * Update README info 918 | * Remove nylo_support ref from stubs 919 | * Pubspec.yaml dependency updates 920 | 921 | ## [2.2.0] - 2022-03-29 922 | 923 | * Remove appended 'Widget' from Metro:make command for stateless and stateful widgets 924 | * Docs link updated in Model stub 925 | * Pubspec.yaml dependency updates 926 | 927 | ## [2.1.4] - 2022-01-02 928 | 929 | * Merge PR #27 - Example moved to null safety 930 | * Remove build appicons command from Metro cli 931 | 932 | ## [2.1.3] - 2021-12-17 933 | 934 | * Small refactor to Metro Cli 935 | * Pubspec.yaml dependency updates 936 | 937 | ## [2.1.2] - 2021-12-12 938 | 939 | * Update Controller stub 940 | * Pubspec.yaml dependency updates 941 | 942 | ## [2.1.1] - 2021-12-10 943 | 944 | * Upgrade to Dart 2.15 945 | * Add toast notifications 946 | * Pubspec.yaml dependency updates 947 | 948 | ## [2.1.0] - 2021-12-08 949 | 950 | * Update Nylo version 951 | * Ability to create themes and theme colors from Metro Cli 952 | * Pubspec.yaml dependency updates 953 | 954 | ## [2.0.4] - 2021-09-21 955 | 956 | * Update Nylo version 957 | 958 | ## [2.0.3] - 2021-09-21 959 | 960 | * Pubspec.yaml dependency updates 961 | 962 | ## [2.0.2] - 2021-09-18 963 | 964 | * Upgrade Dart (v2.14.0) 965 | * Pubspec.yaml dependency updates 966 | 967 | ## [2.0.1] - 2021-09-11 968 | 969 | * Remove NyBaseColors class 970 | 971 | ## [2.0.0] - 2021-09-10 972 | 973 | * Refactor nylo_framework.dart 974 | * Metro cli stability improvements 975 | * New NyTheme class for setting the Flutter theme 976 | * Null safety stubs 977 | * Pubspec.yaml dependency updates 978 | 979 | ## [1.1.0] - 2021-07-13 980 | 981 | * Null safety stubs 982 | 983 | ## [1.0.1] - 2021-07-07 984 | 985 | * README changes 986 | 987 | ## [1.0.0] - 2021-07-07 988 | 989 | * New method to create pages in router 'router.page(NyStatefulWidget widget)' 990 | * Pubspec.yaml dependency updates 991 | 992 | ## [0.8.5] - 2021-04-30 993 | 994 | * Use pascal format for metro cli 995 | * Fix metro make:page command 996 | 997 | ## [0.8.4] - 2021-04-29 998 | 999 | * Fix stub using NyStatefulWidget 1000 | 1001 | ## [0.8.3] - 2021-04-28 1002 | 1003 | * Fix stub using NyStatefulWidget 1004 | 1005 | ## [0.8.2] - 2021-04-10 1006 | 1007 | * Remove operand check 1008 | 1009 | ## [0.8.1] - 2021-04-10 1010 | 1011 | * Update adaptive_theme dependency 1012 | 1013 | ## [0.8.0] - 2021-04-10 1014 | 1015 | * Null safety support 1016 | * Rename StatefulPageWidget to NyStatefulWidget 1017 | * Pubspec.yaml dependency updates 1018 | 1019 | ## [0.7.0+1] - 2021-03-16 1020 | 1021 | * Changes as per dart analysis log 1022 | 1023 | ## [0.7.0] - 2021-03-16 1024 | 1025 | * Router bug fix 1026 | * Small tweaks 1027 | 1028 | ## [0.6.2] - 2021-03-07 1029 | 1030 | * Flutter 2.0.0+ support 1031 | * Bug fixes 1032 | 1033 | ## [0.6.1] - 2021-02-18 1034 | 1035 | * Bug fixes 1036 | 1037 | ## [0.6.0] - 2021-02-18 1038 | 1039 | * Ability to create plugins for Nylo 1040 | * Refactored NyRouter class 1041 | * Add page transitions 1042 | * `StatefulPageWidget` class now has `data()` method 1043 | * Controller and model stub update 1044 | * new `construct(context)` method for controllers 1045 | * Version bump 1046 | 1047 | ## [0.5.2] - 2021-01-31 1048 | 1049 | * Fix `onPop` called on null 1050 | * Pass new "result" into `pop` method 1051 | 1052 | ## [0.5.1] - 2021-01-30 1053 | 1054 | * `flutter format` 1055 | 1056 | ## [0.5.0] - 2021-01-30 1057 | 1058 | * Metro new commands: create stateless widgets, stateful widgets and storable models 1059 | * "common" folder renamed to "resources" 1060 | * Router new `route` method to create routes 1061 | * Controller refactored to remove .of(BuildContext context) 1062 | * New Storable method `save('key')` to store to secure storage 1063 | * onPop parameter added to `routeTo` method 1064 | * Small improvements and tweaks 1065 | 1066 | ## [0.4.0] - 2020-12-21 1067 | 1068 | * New default state widget NyState 1069 | * Updates to Metro stubs 1070 | * APIRender Widget parameter refactor 1071 | * Helper class small refactor 1072 | * Dart code formatting 1073 | 1074 | ## [0.3.2] - 2020-09-23 1075 | 1076 | * ApiRender widget updated with new required parameters and change in functionality. 1077 | 1078 | ## [0.3.1] - 2020-09-19 1079 | 1080 | * Small fix to Metro cli tool 1081 | * Update to readme 1082 | 1083 | ## [0.3.0] - 2020-09-18 1084 | 1085 | * Bug fix for controller when fetching request data 1086 | * Tweak to routes and transitions 1087 | * Improvements to Metro Cli 1088 | 1089 | ## [0.2.0] - 2020-09-15 1090 | 1091 | * Controllers can now return arguments from routes 1092 | * Dispatch jobs with the new BusQueue 1093 | * Updated router class 1094 | * ApiRender widget for rending Widgets from api futures 1095 | * Removal of unused methods, code clean up, example added 1096 | * New method for storing objects and lists to shared preferences 1097 | * New methods to check app connectivity in networking 1098 | * Metro improvements and bug fixes 1099 | * Bug fixes to project 1100 | 1101 | ## [0.1.0] - 2020-08-23 1102 | 1103 | * Initial release. 1104 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Anthony Gordon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Nylo Banner](https://nylo.dev/images/nylo_logo_header.png) 2 | 3 |

4 | Latest Release Version 5 | Latest Stable Version 6 | GitHub stars 7 |

8 | 9 | ## Nylo Framework 10 | 11 | Nylo is a micro-framework for Flutter which is designed to help simplify app development. Every project provides a simple boilerplate and MVC pattern to help you build apps easier. 12 | 13 | This project is open source and MIT-licenced, we welcome any contributions. You can join as a backer/sponsor to fund future development for this project [here](https://nylo.dev/contributions) 14 | 15 | > **Note:** If you want to build an application using Nylo, please visit the main [Nylo repository](https://github.com/nylo-core/nylo). This repository contains the code for the framework Nylo uses. 16 | 17 | --- 18 | 19 | ## Features 20 | Some core features available 21 | * [Routing](https://nylo.dev/docs/6.x/router). 22 | * [Light and dark themes](https://nylo.dev/docs/6.x/themes-and-styling). 23 | * [Localization](https://nylo.dev/docs/6.x/localization). 24 | * [CLI for generating project files](https://nylo.dev/docs/6.x/metro). 25 | * [Elegant API Services for Networking](https://nylo.dev/docs/6.x/networking). 26 | * [State Management](https://nylo.dev/docs/6.x/state-management). 27 | * [Validation](https://nylo.dev/docs/6.x/validation). 28 | * [Form Handling](https://nylo.dev/docs/6.x/forms). 29 | * [Creating App Icons](https://nylo.dev/docs/6.x/app-icons). 30 | * [Project Configuration](https://nylo.dev/docs/6.x/configuration). 31 | * [Streamlined Project Structure](https://nylo.dev/docs/6.x/directory-structure). 32 | 33 | ## Documentation 34 | 35 | Visit the docs on [Nylo](https://nylo.dev/docs) to view our extensive and thorough documentation. 36 | 37 | ## Changelog 38 | Please see [CHANGELOG](https://github.com/nylo-core/framework/blob/5.20.0/CHANGELOG.md) for more information what has changed recently. 39 | 40 | ## Social 41 | * [Twitter](https://twitter.com/nylo_dev) 42 | 43 | ## Security 44 | If you discover any security related issues, please email support@nylo.dev instead of using the issue tracker. 45 | 46 | ## Licence 47 | 48 | The MIT License (MIT). Please view the [License](https://github.com/nylo-core/nylo/blob/5.20.0/LICENSE) File for more information. 49 | -------------------------------------------------------------------------------- /bin/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:nylo_framework/metro/menu.dart'; 4 | import 'package:nylo_framework/metro/metro.dart' as metro_cli; 5 | import 'package:nylo_support/metro/metro_service.dart'; 6 | 7 | void main(List arguments) async { 8 | // Discover custom commands 9 | final customCommands = await MetroService.discoverCustomCommands(); 10 | 11 | // Merge with built-in commands 12 | final allCommands = [...metro_cli.allCommands, ...customCommands]; 13 | 14 | String commandMenu = metroMenu; 15 | if (customCommands.isNotEmpty) { 16 | commandMenu += '\n[Custom Commands]\n'; 17 | for (var customCommand in customCommands) { 18 | commandMenu += ' ${customCommand.category}:${customCommand.name}\n'; 19 | } 20 | } 21 | 22 | // Run with the combined command set 23 | await MetroService.runCommand(arguments, 24 | allCommands: allCommands, menu: commandMenu); 25 | exit(0); 26 | } 27 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | A new Flutter project. 4 | 5 | For help getting started with Flutter, view our online 6 | [documentation](https://flutter.io/) -------------------------------------------------------------------------------- /example/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(const MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | const MyApp({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return MaterialApp( 11 | title: 'Nylo Framework', 12 | theme: ThemeData( 13 | primarySwatch: Colors.blue, 14 | ), 15 | home: const MyHomePage(title: 'Nylo'), 16 | ); 17 | } 18 | } 19 | 20 | class MyHomePage extends StatefulWidget { 21 | const MyHomePage({super.key, required this.title}); 22 | 23 | final String title; 24 | 25 | @override 26 | createState() => _MyHomePageState(); 27 | } 28 | 29 | class _MyHomePageState extends State { 30 | @override 31 | Widget build(BuildContext context) { 32 | return Scaffold( 33 | appBar: AppBar( 34 | title: Text(widget.title), 35 | ), 36 | body: const Center( 37 | child: Text("Framework"), 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | animate_do: 5 | dependency: transitive 6 | description: 7 | name: animate_do 8 | sha256: e5c8b92e8495cba5adfff17c0b017d50f46b2766226e9faaf68bc08c91aef034 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "4.2.0" 12 | app_badge_plus: 13 | dependency: transitive 14 | description: 15 | name: app_badge_plus 16 | sha256: cbbb03cdac77c89c1494534fc2397e7f54deb84d2f968af9f8caaecbc54e86e7 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "1.2.3" 20 | args: 21 | dependency: transitive 22 | description: 23 | name: args 24 | sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "2.7.0" 28 | async: 29 | dependency: transitive 30 | description: 31 | name: async 32 | sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "2.13.0" 36 | base58check: 37 | dependency: transitive 38 | description: 39 | name: base58check 40 | sha256: "6c300dfc33e598d2fe26319e13f6243fea81eaf8204cb4c6b69ef20a625319a5" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "2.0.0" 44 | bech32: 45 | dependency: transitive 46 | description: 47 | name: bech32 48 | sha256: "156cbace936f7720c79a79d16a03efad343b1ef17106716e04b8b8e39f99f7f7" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "0.2.2" 52 | boolean_selector: 53 | dependency: transitive 54 | description: 55 | name: boolean_selector 56 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "2.1.2" 60 | characters: 61 | dependency: transitive 62 | description: 63 | name: characters 64 | sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "1.4.0" 68 | clock: 69 | dependency: transitive 70 | description: 71 | name: clock 72 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "1.1.2" 76 | collection: 77 | dependency: transitive 78 | description: 79 | name: collection 80 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "1.19.1" 84 | convert: 85 | dependency: transitive 86 | description: 87 | name: convert 88 | sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "3.1.2" 92 | crypto: 93 | dependency: transitive 94 | description: 95 | name: crypto 96 | sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "3.0.6" 100 | date_field: 101 | dependency: transitive 102 | description: 103 | name: date_field 104 | sha256: "5714b1ac0ba1c860877b3040601a6fd20cb846042c9ae6d8216ffede422d43d1" 105 | url: "https://pub.dev" 106 | source: hosted 107 | version: "6.0.3+1" 108 | dbus: 109 | dependency: transitive 110 | description: 111 | name: dbus 112 | sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "0.7.11" 116 | dio: 117 | dependency: transitive 118 | description: 119 | name: dio 120 | sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" 121 | url: "https://pub.dev" 122 | source: hosted 123 | version: "5.8.0+1" 124 | dio_web_adapter: 125 | dependency: transitive 126 | description: 127 | name: dio_web_adapter 128 | sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" 129 | url: "https://pub.dev" 130 | source: hosted 131 | version: "2.1.1" 132 | equatable: 133 | dependency: transitive 134 | description: 135 | name: equatable 136 | sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" 137 | url: "https://pub.dev" 138 | source: hosted 139 | version: "2.0.7" 140 | error_stack: 141 | dependency: transitive 142 | description: 143 | name: error_stack 144 | sha256: ff50365afc48969e9ba3c5aab032c67d6d282be7e4a8eea9bf7fbd81d2414cc8 145 | url: "https://pub.dev" 146 | source: hosted 147 | version: "1.10.3" 148 | fake_async: 149 | dependency: transitive 150 | description: 151 | name: fake_async 152 | sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" 153 | url: "https://pub.dev" 154 | source: hosted 155 | version: "1.3.3" 156 | ffi: 157 | dependency: transitive 158 | description: 159 | name: ffi 160 | sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" 161 | url: "https://pub.dev" 162 | source: hosted 163 | version: "2.1.4" 164 | file: 165 | dependency: transitive 166 | description: 167 | name: file 168 | sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 169 | url: "https://pub.dev" 170 | source: hosted 171 | version: "7.0.1" 172 | fixnum: 173 | dependency: transitive 174 | description: 175 | name: fixnum 176 | sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be 177 | url: "https://pub.dev" 178 | source: hosted 179 | version: "1.1.1" 180 | flutter: 181 | dependency: "direct main" 182 | description: flutter 183 | source: sdk 184 | version: "0.0.0" 185 | flutter_dotenv: 186 | dependency: transitive 187 | description: 188 | name: flutter_dotenv 189 | sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b 190 | url: "https://pub.dev" 191 | source: hosted 192 | version: "5.2.1" 193 | flutter_local_notifications: 194 | dependency: transitive 195 | description: 196 | name: flutter_local_notifications 197 | sha256: b94a50aabbe56ef254f95f3be75640f99120429f0a153b2dc30143cffc9bfdf3 198 | url: "https://pub.dev" 199 | source: hosted 200 | version: "19.2.1" 201 | flutter_local_notifications_linux: 202 | dependency: transitive 203 | description: 204 | name: flutter_local_notifications_linux 205 | sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5 206 | url: "https://pub.dev" 207 | source: hosted 208 | version: "6.0.0" 209 | flutter_local_notifications_platform_interface: 210 | dependency: transitive 211 | description: 212 | name: flutter_local_notifications_platform_interface 213 | sha256: "2569b973fc9d1f63a37410a9f7c1c552081226c597190cb359ef5d5762d1631c" 214 | url: "https://pub.dev" 215 | source: hosted 216 | version: "9.0.0" 217 | flutter_local_notifications_windows: 218 | dependency: transitive 219 | description: 220 | name: flutter_local_notifications_windows 221 | sha256: f8fc0652a601f83419d623c85723a3e82ad81f92b33eaa9bcc21ea1b94773e6e 222 | url: "https://pub.dev" 223 | source: hosted 224 | version: "1.0.0" 225 | flutter_localizations: 226 | dependency: transitive 227 | description: flutter 228 | source: sdk 229 | version: "0.0.0" 230 | flutter_multi_formatter: 231 | dependency: transitive 232 | description: 233 | name: flutter_multi_formatter 234 | sha256: "01e8ba4cfae4b52377d1c83e57293da24eb64696d7c2df064acc4ae97bb4a157" 235 | url: "https://pub.dev" 236 | source: hosted 237 | version: "2.13.7" 238 | flutter_secure_storage: 239 | dependency: transitive 240 | description: 241 | name: flutter_secure_storage 242 | sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" 243 | url: "https://pub.dev" 244 | source: hosted 245 | version: "9.2.4" 246 | flutter_secure_storage_linux: 247 | dependency: transitive 248 | description: 249 | name: flutter_secure_storage_linux 250 | sha256: bf7404619d7ab5c0a1151d7c4e802edad8f33535abfbeff2f9e1fe1274e2d705 251 | url: "https://pub.dev" 252 | source: hosted 253 | version: "1.2.2" 254 | flutter_secure_storage_macos: 255 | dependency: transitive 256 | description: 257 | name: flutter_secure_storage_macos 258 | sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" 259 | url: "https://pub.dev" 260 | source: hosted 261 | version: "3.1.3" 262 | flutter_secure_storage_platform_interface: 263 | dependency: transitive 264 | description: 265 | name: flutter_secure_storage_platform_interface 266 | sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 267 | url: "https://pub.dev" 268 | source: hosted 269 | version: "1.1.2" 270 | flutter_secure_storage_web: 271 | dependency: transitive 272 | description: 273 | name: flutter_secure_storage_web 274 | sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 275 | url: "https://pub.dev" 276 | source: hosted 277 | version: "1.2.1" 278 | flutter_secure_storage_windows: 279 | dependency: transitive 280 | description: 281 | name: flutter_secure_storage_windows 282 | sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 283 | url: "https://pub.dev" 284 | source: hosted 285 | version: "3.1.2" 286 | flutter_staggered_grid_view: 287 | dependency: transitive 288 | description: 289 | name: flutter_staggered_grid_view 290 | sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" 291 | url: "https://pub.dev" 292 | source: hosted 293 | version: "0.7.0" 294 | flutter_styled_toast: 295 | dependency: transitive 296 | description: 297 | name: flutter_styled_toast 298 | sha256: e667f13a665820eb0fa8506547e47eacbcddf1948d6d3036cfd3b089bd4b0516 299 | url: "https://pub.dev" 300 | source: hosted 301 | version: "2.2.1" 302 | flutter_test: 303 | dependency: "direct dev" 304 | description: flutter 305 | source: sdk 306 | version: "0.0.0" 307 | flutter_timezone: 308 | dependency: transitive 309 | description: 310 | name: flutter_timezone 311 | sha256: "13b2109ad75651faced4831bf262e32559e44aa549426eab8a597610d385d934" 312 | url: "https://pub.dev" 313 | source: hosted 314 | version: "4.1.1" 315 | flutter_web_plugins: 316 | dependency: transitive 317 | description: flutter 318 | source: sdk 319 | version: "0.0.0" 320 | get_time_ago: 321 | dependency: transitive 322 | description: 323 | name: get_time_ago 324 | sha256: "1c6ccc877e480eee559987411ec5242cecc088d45fc821f0d8aec98bbf4f210e" 325 | url: "https://pub.dev" 326 | source: hosted 327 | version: "2.3.1" 328 | http: 329 | dependency: transitive 330 | description: 331 | name: http 332 | sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f 333 | url: "https://pub.dev" 334 | source: hosted 335 | version: "1.3.0" 336 | http_parser: 337 | dependency: transitive 338 | description: 339 | name: http_parser 340 | sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" 341 | url: "https://pub.dev" 342 | source: hosted 343 | version: "4.1.2" 344 | intl: 345 | dependency: transitive 346 | description: 347 | name: intl 348 | sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" 349 | url: "https://pub.dev" 350 | source: hosted 351 | version: "0.20.2" 352 | js: 353 | dependency: transitive 354 | description: 355 | name: js 356 | sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 357 | url: "https://pub.dev" 358 | source: hosted 359 | version: "0.6.7" 360 | leak_tracker: 361 | dependency: transitive 362 | description: 363 | name: leak_tracker 364 | sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" 365 | url: "https://pub.dev" 366 | source: hosted 367 | version: "10.0.9" 368 | leak_tracker_flutter_testing: 369 | dependency: transitive 370 | description: 371 | name: leak_tracker_flutter_testing 372 | sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 373 | url: "https://pub.dev" 374 | source: hosted 375 | version: "3.0.9" 376 | leak_tracker_testing: 377 | dependency: transitive 378 | description: 379 | name: leak_tracker_testing 380 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 381 | url: "https://pub.dev" 382 | source: hosted 383 | version: "3.0.1" 384 | mask_text_input_formatter: 385 | dependency: transitive 386 | description: 387 | name: mask_text_input_formatter 388 | sha256: "978c58ec721c25621ceb468e633f4eef64b64d45424ac4540e0565d4f7c800cd" 389 | url: "https://pub.dev" 390 | source: hosted 391 | version: "2.9.0" 392 | matcher: 393 | dependency: transitive 394 | description: 395 | name: matcher 396 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 397 | url: "https://pub.dev" 398 | source: hosted 399 | version: "0.12.17" 400 | material_color_utilities: 401 | dependency: transitive 402 | description: 403 | name: material_color_utilities 404 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 405 | url: "https://pub.dev" 406 | source: hosted 407 | version: "0.11.1" 408 | meta: 409 | dependency: transitive 410 | description: 411 | name: meta 412 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c 413 | url: "https://pub.dev" 414 | source: hosted 415 | version: "1.16.0" 416 | nylo_framework: 417 | dependency: "direct dev" 418 | description: 419 | path: ".." 420 | relative: true 421 | source: path 422 | version: "6.8.7" 423 | nylo_support: 424 | dependency: transitive 425 | description: 426 | name: nylo_support 427 | sha256: "435b77fe64865579e8f63afb5eed4780e83c547cd8f2f9bb9ebdfb2fd29f3b0a" 428 | url: "https://pub.dev" 429 | source: hosted 430 | version: "6.28.5" 431 | path: 432 | dependency: transitive 433 | description: 434 | name: path 435 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" 436 | url: "https://pub.dev" 437 | source: hosted 438 | version: "1.9.1" 439 | path_provider: 440 | dependency: transitive 441 | description: 442 | name: path_provider 443 | sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" 444 | url: "https://pub.dev" 445 | source: hosted 446 | version: "2.1.5" 447 | path_provider_android: 448 | dependency: transitive 449 | description: 450 | name: path_provider_android 451 | sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" 452 | url: "https://pub.dev" 453 | source: hosted 454 | version: "2.2.16" 455 | path_provider_foundation: 456 | dependency: transitive 457 | description: 458 | name: path_provider_foundation 459 | sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" 460 | url: "https://pub.dev" 461 | source: hosted 462 | version: "2.4.1" 463 | path_provider_linux: 464 | dependency: transitive 465 | description: 466 | name: path_provider_linux 467 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 468 | url: "https://pub.dev" 469 | source: hosted 470 | version: "2.2.1" 471 | path_provider_platform_interface: 472 | dependency: transitive 473 | description: 474 | name: path_provider_platform_interface 475 | sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" 476 | url: "https://pub.dev" 477 | source: hosted 478 | version: "2.1.2" 479 | path_provider_windows: 480 | dependency: transitive 481 | description: 482 | name: path_provider_windows 483 | sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 484 | url: "https://pub.dev" 485 | source: hosted 486 | version: "2.3.0" 487 | petitparser: 488 | dependency: transitive 489 | description: 490 | name: petitparser 491 | sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" 492 | url: "https://pub.dev" 493 | source: hosted 494 | version: "6.1.0" 495 | platform: 496 | dependency: transitive 497 | description: 498 | name: platform 499 | sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" 500 | url: "https://pub.dev" 501 | source: hosted 502 | version: "3.1.6" 503 | plugin_platform_interface: 504 | dependency: transitive 505 | description: 506 | name: plugin_platform_interface 507 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" 508 | url: "https://pub.dev" 509 | source: hosted 510 | version: "2.1.8" 511 | pretty_dio_logger: 512 | dependency: transitive 513 | description: 514 | name: pretty_dio_logger 515 | sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407" 516 | url: "https://pub.dev" 517 | source: hosted 518 | version: "1.4.0" 519 | pull_to_refresh_flutter3: 520 | dependency: transitive 521 | description: 522 | name: pull_to_refresh_flutter3 523 | sha256: "37a88d901cca9a46dbdd46523de8e7b35a3e58634a0e775b1a5904981f69b353" 524 | url: "https://pub.dev" 525 | source: hosted 526 | version: "2.0.2" 527 | recase: 528 | dependency: transitive 529 | description: 530 | name: recase 531 | sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 532 | url: "https://pub.dev" 533 | source: hosted 534 | version: "4.1.0" 535 | rxdart: 536 | dependency: transitive 537 | description: 538 | name: rxdart 539 | sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" 540 | url: "https://pub.dev" 541 | source: hosted 542 | version: "0.28.0" 543 | shared_preferences: 544 | dependency: transitive 545 | description: 546 | name: shared_preferences 547 | sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" 548 | url: "https://pub.dev" 549 | source: hosted 550 | version: "2.5.3" 551 | shared_preferences_android: 552 | dependency: transitive 553 | description: 554 | name: shared_preferences_android 555 | sha256: c2c8c46297b5d6a80bed7741ec1f2759742c77d272f1a1698176ae828f8e1a18 556 | url: "https://pub.dev" 557 | source: hosted 558 | version: "2.4.9" 559 | shared_preferences_foundation: 560 | dependency: transitive 561 | description: 562 | name: shared_preferences_foundation 563 | sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" 564 | url: "https://pub.dev" 565 | source: hosted 566 | version: "2.5.4" 567 | shared_preferences_linux: 568 | dependency: transitive 569 | description: 570 | name: shared_preferences_linux 571 | sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" 572 | url: "https://pub.dev" 573 | source: hosted 574 | version: "2.4.1" 575 | shared_preferences_platform_interface: 576 | dependency: transitive 577 | description: 578 | name: shared_preferences_platform_interface 579 | sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" 580 | url: "https://pub.dev" 581 | source: hosted 582 | version: "2.4.1" 583 | shared_preferences_web: 584 | dependency: transitive 585 | description: 586 | name: shared_preferences_web 587 | sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 588 | url: "https://pub.dev" 589 | source: hosted 590 | version: "2.4.3" 591 | shared_preferences_windows: 592 | dependency: transitive 593 | description: 594 | name: shared_preferences_windows 595 | sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" 596 | url: "https://pub.dev" 597 | source: hosted 598 | version: "2.4.1" 599 | skeletonizer: 600 | dependency: transitive 601 | description: 602 | name: skeletonizer 603 | sha256: a9ddf63900947f4c0648372b6e9987bc2b028db9db843376db6767224d166c31 604 | url: "https://pub.dev" 605 | source: hosted 606 | version: "2.0.1" 607 | sky_engine: 608 | dependency: transitive 609 | description: flutter 610 | source: sdk 611 | version: "0.0.0" 612 | source_span: 613 | dependency: transitive 614 | description: 615 | name: source_span 616 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" 617 | url: "https://pub.dev" 618 | source: hosted 619 | version: "1.10.1" 620 | sprintf: 621 | dependency: transitive 622 | description: 623 | name: sprintf 624 | sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" 625 | url: "https://pub.dev" 626 | source: hosted 627 | version: "7.0.0" 628 | stack_trace: 629 | dependency: transitive 630 | description: 631 | name: stack_trace 632 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" 633 | url: "https://pub.dev" 634 | source: hosted 635 | version: "1.12.1" 636 | stream_channel: 637 | dependency: transitive 638 | description: 639 | name: stream_channel 640 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" 641 | url: "https://pub.dev" 642 | source: hosted 643 | version: "2.1.4" 644 | string_scanner: 645 | dependency: transitive 646 | description: 647 | name: string_scanner 648 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" 649 | url: "https://pub.dev" 650 | source: hosted 651 | version: "1.4.1" 652 | term_glyph: 653 | dependency: transitive 654 | description: 655 | name: term_glyph 656 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" 657 | url: "https://pub.dev" 658 | source: hosted 659 | version: "1.2.2" 660 | test_api: 661 | dependency: transitive 662 | description: 663 | name: test_api 664 | sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd 665 | url: "https://pub.dev" 666 | source: hosted 667 | version: "0.7.4" 668 | theme_provider: 669 | dependency: transitive 670 | description: 671 | name: theme_provider 672 | sha256: "6a2839ee1bd539ceb789f25ea9696fe90f9dfad28e3228f209b8ff9255c58099" 673 | url: "https://pub.dev" 674 | source: hosted 675 | version: "0.6.0" 676 | timezone: 677 | dependency: transitive 678 | description: 679 | name: timezone 680 | sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 681 | url: "https://pub.dev" 682 | source: hosted 683 | version: "0.10.1" 684 | typed_data: 685 | dependency: transitive 686 | description: 687 | name: typed_data 688 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 689 | url: "https://pub.dev" 690 | source: hosted 691 | version: "1.4.0" 692 | url_launcher: 693 | dependency: transitive 694 | description: 695 | name: url_launcher 696 | sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" 697 | url: "https://pub.dev" 698 | source: hosted 699 | version: "6.3.1" 700 | url_launcher_android: 701 | dependency: transitive 702 | description: 703 | name: url_launcher_android 704 | sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4" 705 | url: "https://pub.dev" 706 | source: hosted 707 | version: "6.3.15" 708 | url_launcher_ios: 709 | dependency: transitive 710 | description: 711 | name: url_launcher_ios 712 | sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" 713 | url: "https://pub.dev" 714 | source: hosted 715 | version: "6.3.3" 716 | url_launcher_linux: 717 | dependency: transitive 718 | description: 719 | name: url_launcher_linux 720 | sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" 721 | url: "https://pub.dev" 722 | source: hosted 723 | version: "3.2.1" 724 | url_launcher_macos: 725 | dependency: transitive 726 | description: 727 | name: url_launcher_macos 728 | sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" 729 | url: "https://pub.dev" 730 | source: hosted 731 | version: "3.2.2" 732 | url_launcher_platform_interface: 733 | dependency: transitive 734 | description: 735 | name: url_launcher_platform_interface 736 | sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" 737 | url: "https://pub.dev" 738 | source: hosted 739 | version: "2.3.2" 740 | url_launcher_web: 741 | dependency: transitive 742 | description: 743 | name: url_launcher_web 744 | sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" 745 | url: "https://pub.dev" 746 | source: hosted 747 | version: "2.4.0" 748 | url_launcher_windows: 749 | dependency: transitive 750 | description: 751 | name: url_launcher_windows 752 | sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" 753 | url: "https://pub.dev" 754 | source: hosted 755 | version: "3.1.4" 756 | uuid: 757 | dependency: transitive 758 | description: 759 | name: uuid 760 | sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff 761 | url: "https://pub.dev" 762 | source: hosted 763 | version: "4.5.1" 764 | validated: 765 | dependency: transitive 766 | description: 767 | name: validated 768 | sha256: f4da18b50fa2aeda8d2f6e55bdf73759593abe3f9dd4aeece4e98bf3438e6a9f 769 | url: "https://pub.dev" 770 | source: hosted 771 | version: "2.0.0" 772 | vector_math: 773 | dependency: transitive 774 | description: 775 | name: vector_math 776 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 777 | url: "https://pub.dev" 778 | source: hosted 779 | version: "2.1.4" 780 | vm_service: 781 | dependency: transitive 782 | description: 783 | name: vm_service 784 | sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 785 | url: "https://pub.dev" 786 | source: hosted 787 | version: "15.0.0" 788 | web: 789 | dependency: transitive 790 | description: 791 | name: web 792 | sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" 793 | url: "https://pub.dev" 794 | source: hosted 795 | version: "1.1.1" 796 | win32: 797 | dependency: transitive 798 | description: 799 | name: win32 800 | sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" 801 | url: "https://pub.dev" 802 | source: hosted 803 | version: "5.13.0" 804 | xdg_directories: 805 | dependency: transitive 806 | description: 807 | name: xdg_directories 808 | sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" 809 | url: "https://pub.dev" 810 | source: hosted 811 | version: "1.1.0" 812 | xml: 813 | dependency: transitive 814 | description: 815 | name: xml 816 | sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 817 | url: "https://pub.dev" 818 | source: hosted 819 | version: "6.5.0" 820 | sdks: 821 | dart: ">=3.7.0 <4.0.0" 822 | flutter: ">=3.31.0-0.0.pre" 823 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: nylo_framework_example 2 | description: Demonstrates how to use the Nylo plugin. 3 | 4 | environment: 5 | sdk: '>=2.19.0 <3.0.0' 6 | 7 | dependencies: 8 | flutter: 9 | sdk: flutter 10 | 11 | dev_dependencies: 12 | flutter_test: 13 | sdk: flutter 14 | 15 | nylo_framework: 16 | path: ../ 17 | 18 | flutter: 19 | 20 | uses-material-design: true -------------------------------------------------------------------------------- /lib/cli_dialog/cli_dialog.dart: -------------------------------------------------------------------------------- 1 | export 'src/dialog.dart'; 2 | export 'src/services.dart'; 3 | export 'src/xterm.dart' show XTerm; 4 | -------------------------------------------------------------------------------- /lib/cli_dialog/src/dialog.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'list_chooser.dart'; 3 | export 'list_chooser.dart'; 4 | import 'services.dart'; 5 | import 'xterm.dart'; 6 | 7 | /// This enum is used to indicate the type of question in the CLI dialog. 8 | enum CliDialogQuestionType { message, question, booleanQuestion, listQuestion } 9 | 10 | /// This is the most important class which should usually be instantiated when building CLI dialogs. 11 | class CliDialog { 12 | /// This is where the results = answers from the CLI dialog go. This map is updated and returned when calling [ask]. 13 | Map answers = {}; 14 | 15 | /// This is where the boolean questions are stored during runtime. Feel free to access it like any other list. 16 | List? booleanQuestions; 17 | 18 | /// This is where the list questions are stored during runtime (where the user selects a value). Feel free to access it like any other list. 19 | List? listQuestions; 20 | 21 | /// Messages inform the user without asking a question. A key must be provided for each message if you want to use a custom [order]. 22 | List? messages; 23 | 24 | /// Navigation mode means that every question is displayed with a number which you can use to navigate through questions 25 | bool navigationMode; 26 | 27 | /// Resume moed means that progress in your CLI dialog is automatically saved with each question so you can savely 28 | /// Quit and resume later. Specify a file path where you progress will be saved and loaded from 29 | String? resume; 30 | 31 | /// This list contains the order in which the questions are asked in the dialog. Feel free to access it like any other list. 32 | List? order; 33 | 34 | /// This is where the regular questions are stored during runtime. 35 | List? questions; 36 | 37 | /// Indicates the default behaviour of boolean questions when no input (except '\n') is given. 38 | bool trueByDefault; 39 | 40 | /// This is the default constructor to create a CLI Dialog 41 | /// You can pass lists of normal [questions], [booleanQuestions], [listQuestions]. 42 | /// All these named parameters are optional as long as at least one of them is given. 43 | /// Furthermore you can pass a particular order. If no order is given then the default order is used (see README.md) 44 | /// There is also [trueByDefault] (false by default) which indicates how booleanQuestions behave if no input is given 45 | /// There are basic format checks which try to prevent you from passing invalid argmuents. 46 | CliDialog( 47 | {this.messages, 48 | this.questions, 49 | this.booleanQuestions, 50 | this.listQuestions, 51 | this.order, 52 | this.trueByDefault = false, 53 | this.navigationMode = false, 54 | this.resume = ''}) { 55 | _checkQuestions(); 56 | _initializeLists(); 57 | } 58 | 59 | /// This named constructor should mostly be used when unit testing. 60 | /// 61 | /// ``` 62 | /// var std_output = StdoutService(mock: true); 63 | /// var std_input = StdinService(mock: true, informStdout: std_output); 64 | /// std_input.addToBuffer(...Keys.arrowDown, Keys.enter); 65 | /// final dialog = CLI_Dialog.std(std_input, std_output, listQuestions: listQuestions); 66 | /// ``` 67 | CliDialog.std(this._stdInput, this._stdOutput, 68 | {this.messages, 69 | this.questions, 70 | this.booleanQuestions, 71 | this.listQuestions, 72 | this.order, 73 | this.trueByDefault = false, 74 | this.navigationMode = false}) { 75 | _checkQuestions(); 76 | _initializeLists(); 77 | } 78 | 79 | /// This method is another way of adding questions after instantiating [CLI_Dialog] 80 | /// Pass [is_bool] or [isList] as a named argument to indicate the type of question you are adding (boolean qualifier). 81 | /// 82 | /// ``` 83 | /// final dialog = CLI_Dialog(); 84 | /// dialog.addQuestion([{'question': 'How are you?', options: ['Good', 'Not so good']}, 'mood'], isList: true); 85 | /// ``` 86 | void addQuestion(pQuestion, key, 87 | {isBoolean = false, isList = false, isMessage = false}) { 88 | if ((isBoolean ? 1 : 0) + (isList ? 1 : 0) + (isMessage ? 1 : 0) > 1) { 89 | throw ArgumentError( 90 | 'A question can not have more than one boolean qualifier.'); 91 | } 92 | final newItem = [pQuestion, key]; 93 | if (isBoolean) { 94 | booleanQuestions!.add(newItem); 95 | } else if (isList) { 96 | listQuestions!.add(newItem); 97 | } else if (isMessage) { 98 | messages!.add(newItem); 99 | } else { 100 | questions!.add(newItem); 101 | } 102 | } 103 | 104 | /// Same as [addQuestion] but you can add multiple questions (of the same type) 105 | void addQuestions(pQuestions, 106 | {isBoolean = false, isList = false, isMessage = false}) { 107 | if (isBoolean) { 108 | booleanQuestions!.addAll(pQuestions); 109 | } else if (isList) { 110 | listQuestions!.addAll(pQuestions); 111 | } else if (isMessage) { 112 | messages!.addAll(pQuestions); 113 | } else { 114 | questions!.addAll(pQuestions); 115 | } 116 | } 117 | 118 | /// Use this method to retrieve the results from your CLI dialog. 119 | /// A map is being returned which you can query using your keys. 120 | /// 121 | /// ``` 122 | /// final dialog = CLI_Dialog(); 123 | /// dialog.addQuestion('What is your name?', 'name'); 124 | /// var answers = dialog.ask(); 125 | /// print('Your name is ${answers["name"]}'); 126 | /// ``` 127 | Map ask() { 128 | _navigationIndex = 0; // reset navigation 129 | if (order == null) { 130 | _standardOrder(); 131 | } else { 132 | _customOrder(); 133 | } 134 | return answers; 135 | } 136 | 137 | // END OF PUBLIC API 138 | 139 | var _stdInput = StdinService(); 140 | var _stdOutput = StdoutService(); 141 | 142 | void _askBooleanQuestion(question, key) { 143 | _stdOutput.write(_booleanQuestion(question)); 144 | _getBooleanAnswer(question, key); 145 | } 146 | 147 | void _askListQuestion(optionsMap, key) { 148 | _stdOutput.writeln(_listQuestion(optionsMap['question'])); 149 | _getListAnswer(optionsMap['options'], key); 150 | } 151 | 152 | void _askQuestion(question, key) { 153 | _stdOutput.write(_question(question)); 154 | _getAnswer(question, key); 155 | } 156 | 157 | String _booleanQuestion(str) => 158 | '${_question(str)}${_comment(trueByDefault ? '(Y/n)' : '(y/N)')} '; 159 | 160 | void _checkDuplicateKeys() { 161 | var keyList = []; 162 | 163 | for (var entry in [questions, booleanQuestions, listQuestions]) { 164 | if (entry != null) { 165 | for (var element in entry) { 166 | keyList.add(element[1]); 167 | } 168 | } 169 | } 170 | 171 | //check for duplicates 172 | if (keyList.length != keyList.toSet().length) { 173 | throw ArgumentError('You have two or more keys with the same name.'); 174 | } 175 | } 176 | 177 | bool _checkNavigation(String input) { 178 | if (navigationMode) { 179 | if (input[0] == ':') { 180 | // -1 because of zero index 181 | _navigationIndex = int.parse(input.substring(1)) - 1; 182 | // -1 because _navigationIndex will be incremented in _iterateCustomOrder() 183 | _navigationIndex += _messagesBefore - 1; 184 | _stdOutput.writeln(''); 185 | return true; 186 | } 187 | return false; 188 | } 189 | return false; 190 | } 191 | 192 | void _checkQuestions() { 193 | for (var element in [messages]) { 194 | if (element is List && element.length > 2) { 195 | throw ArgumentError( 196 | 'Each message is either just a string or a list with a string and a key.'); 197 | } 198 | } 199 | 200 | for (var entry in [questions, booleanQuestions, listQuestions]) { 201 | if (entry != null) { 202 | for (var element in entry) { 203 | if (element != null) { 204 | if (element.length != 2) { 205 | throw ArgumentError( 206 | 'Each question entry must be a list consisting of a question and a key.'); 207 | } 208 | } else { 209 | throw ArgumentError('All questions and keys must be Strings.'); 210 | } 211 | } 212 | } 213 | } 214 | 215 | for (var entry in [questions, booleanQuestions]) { 216 | if (entry != null) { 217 | for (var element in entry) { 218 | if (element[0] is! String || element[1] is! String) { 219 | throw ArgumentError('All questions and keys must be Strings.'); 220 | } 221 | } 222 | } 223 | } 224 | 225 | if (listQuestions != null) { 226 | for (var element in listQuestions!) { 227 | if (element[0]['question'] is! String) { 228 | throw ArgumentError('Your question must be a String.'); 229 | } 230 | if (element[0]['options'] is! List) { 231 | throw ArgumentError('Your list options must be a list of Strings.'); 232 | } 233 | 234 | if (element[0].length != 2) { 235 | throw ArgumentError( 236 | 'Your list dialog map must have exactly two entries.'); 237 | } 238 | } 239 | } 240 | _checkDuplicateKeys(); 241 | } 242 | 243 | String _comment(str) => XTerm.gray(str); 244 | 245 | void _customOrder() { 246 | if (navigationMode) { 247 | _iterateCustomOrder(getCustomNavList()); 248 | } else { 249 | _customOrderWithoutNavigation(); 250 | } 251 | } 252 | 253 | void _customOrderWithoutNavigation() { 254 | for (var i = 0; i < order!.length; i++) { 255 | final questionAndFunction = _findQuestion(order![i]); 256 | if (questionAndFunction != null) { 257 | questionAndFunction[1]( 258 | questionAndFunction[0][0], questionAndFunction[0][1]); 259 | } 260 | } 261 | } 262 | 263 | void _displayMessage(msg, key) { 264 | if (msg is String) { 265 | _stdOutput.writeln(_comment(msg)); 266 | } else { 267 | _stdOutput.writeln(_comment(msg[0])); 268 | } 269 | } 270 | 271 | dynamic _findQuestion(key) { 272 | dynamic ret; 273 | for (var element in [ 274 | [messages, _displayMessage], 275 | [questions, _askQuestion], 276 | [booleanQuestions, _askBooleanQuestion], 277 | [listQuestions, _askListQuestion] 278 | ]) { 279 | if (element[0] != null) { 280 | var retVal = _search(element[0], element[1], key); 281 | if (retVal != null) { 282 | ret = retVal; 283 | continue; 284 | } 285 | } 286 | } 287 | return ret; 288 | } 289 | 290 | void _getAnswer(question, key) { 291 | final input = _getInput(_question(question)); 292 | if (!_checkNavigation(input)) { 293 | answers[key] = input; 294 | _stdOutput.writeln( 295 | '\r${_question(question)}${XTerm.teal(answers[key])}${XTerm.blankRemaining()}'); 296 | } 297 | } 298 | 299 | void _getBooleanAnswer(question, key) { 300 | var input = _getInput(_booleanQuestion(question), acceptEmptyAnswer: true); 301 | if (!_checkNavigation(input)) { 302 | if (input.isEmpty) { 303 | answers[key] = trueByDefault; 304 | } else { 305 | answers[key] = (input[0] == 'y' || input[0] == 'Y'); 306 | } 307 | var replaceStr = 308 | '\r${_booleanQuestion(question)}${XTerm.blankRemaining()}'; 309 | replaceStr += (answers[key] ? XTerm.teal('Yes') : XTerm.teal('No')); 310 | _stdOutput.writeln(replaceStr); 311 | } 312 | } 313 | 314 | Function? _getFunctionForQuestionType(type) { 315 | if (type == CliDialogQuestionType.message) { 316 | return _displayMessage; 317 | } 318 | if (type == CliDialogQuestionType.question) { 319 | return _askQuestion; 320 | } 321 | if (type == CliDialogQuestionType.booleanQuestion) { 322 | return _askBooleanQuestion; 323 | } 324 | if (type == CliDialogQuestionType.listQuestion) { 325 | return _askListQuestion; 326 | } 327 | return null; 328 | } 329 | 330 | String _getInput(formattedQuestion, {acceptEmptyAnswer = false}) { 331 | var input = ''; 332 | if (!acceptEmptyAnswer) { 333 | while (input.isEmpty) { 334 | input = _stdInput 335 | .readLineSync(encoding: Encoding.getByName('utf-8'))! 336 | .trim(); 337 | _stdOutput.write(XTerm.moveUp(1) + formattedQuestion); 338 | } 339 | } else { 340 | input = 341 | _stdInput.readLineSync(encoding: Encoding.getByName('utf-8'))!.trim(); 342 | _stdOutput.write(XTerm.moveUp(1) + formattedQuestion); 343 | } 344 | return input; 345 | } 346 | 347 | void _getListAnswer(options, key) { 348 | var chooser = ListChooser.std(_stdInput, _stdOutput, options, 349 | navigationMode: navigationMode); 350 | final input = chooser.choose(); 351 | if (!_checkNavigation(input)) { 352 | answers[key] = input; 353 | } 354 | } 355 | 356 | List _getStdNavList() => 357 | [...messages!, ...questions!, ...booleanQuestions!, ...listQuestions!]; 358 | 359 | List getCustomNavList() { 360 | var navList = []; 361 | for (var key in order!) { 362 | navList.add(_simpleSearch(key)); 363 | } 364 | return navList; 365 | } 366 | 367 | CliDialogQuestionType? _getQuestionType(item) { 368 | if (messages!.contains(item)) { 369 | return CliDialogQuestionType.message; 370 | } 371 | if (questions!.contains(item)) { 372 | return CliDialogQuestionType.question; 373 | } 374 | if (booleanQuestions!.contains(item)) { 375 | return CliDialogQuestionType.booleanQuestion; 376 | } 377 | if (listQuestions!.contains(item)) { 378 | return CliDialogQuestionType.listQuestion; 379 | } 380 | return null; 381 | } 382 | 383 | // this is needed because only unmodifiable lists can be used as default values 384 | void _initializeLists() { 385 | messages ??= []; 386 | booleanQuestions ??= []; 387 | listQuestions ??= []; 388 | questions ??= []; 389 | } 390 | 391 | void _iterateCustomOrder(List navlist) { 392 | for (_navigationIndex; 393 | _navigationIndex < navlist.length; 394 | _navigationIndex++) { 395 | final element = navlist[_navigationIndex]; 396 | _getFunctionForQuestionType(_getQuestionType(element))!( 397 | element[0], element[1]); 398 | } 399 | } 400 | 401 | String _listQuestion(str) => _question(str) + _comment('(Use arrow keys)'); 402 | 403 | int get _messagesBefore { 404 | var messagesBefore = 0; 405 | if (navigationMode && order != null) { 406 | for (var i = _navigationIndex - 1; i >= 0; i--) { 407 | if (_getQuestionType(_simpleSearch(order![i])) == 408 | CliDialogQuestionType.message) { 409 | messagesBefore++; 410 | } 411 | } 412 | } 413 | return messagesBefore; 414 | } 415 | 416 | int _navigationIndex = 0; 417 | 418 | String _question(str) => 419 | '${navigationMode ? '(${_navigationIndex + 1 - _messagesBefore}) ' : ''}${XTerm.green('?')} ${XTerm.bold(str)} '; 420 | dynamic _search(list, fn, key) { 421 | dynamic ret; 422 | list.forEach((element) { 423 | if (element[1] == key) { 424 | ret = [element, fn]; 425 | return; // corresponds to a break in a normal for-loop 426 | } 427 | }); 428 | return ret; 429 | } 430 | 431 | dynamic _simpleSearch(String key) { 432 | dynamic ret; 433 | for (var list in [messages, questions, booleanQuestions, listQuestions]) { 434 | for (var element in list!) { 435 | if (element[1] == key) { 436 | ret = element; 437 | continue; // break 438 | } 439 | } 440 | if (ret != null) { 441 | continue; 442 | } 443 | } 444 | return ret; 445 | } 446 | 447 | // Standard behaviour if no order is given 448 | void _standardOrder() { 449 | if (navigationMode) { 450 | _iterateCustomOrder(_getStdNavList()); 451 | } else { 452 | _standardOrderNoNavigation(); 453 | } 454 | } 455 | 456 | void _standardOrderNoNavigation() { 457 | for (var i = 0; i < messages!.length; i++) { 458 | _displayMessage(messages![i], messages![i][1]); 459 | } 460 | for (var i = 0; i < questions!.length; i++) { 461 | _askQuestion(questions![i][0], questions![i][1]); 462 | } 463 | for (var i = 0; i < booleanQuestions!.length; i++) { 464 | _askBooleanQuestion(booleanQuestions![i][0], booleanQuestions![i][1]); 465 | } 466 | for (var i = 0; i < listQuestions!.length; i++) { 467 | _askListQuestion(listQuestions![i][0], listQuestions![i][1]); 468 | } 469 | } 470 | } 471 | -------------------------------------------------------------------------------- /lib/cli_dialog/src/keys.dart: -------------------------------------------------------------------------------- 1 | const arrowUp = 65; 2 | const arrowDown = 66; 3 | const enter = 10; 4 | 5 | const winUp = 119; 6 | const winDown = 115; 7 | const winEnter = 13; 8 | -------------------------------------------------------------------------------- /lib/cli_dialog/src/list_chooser.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'services.dart'; 4 | import 'xterm.dart'; 5 | import 'keys.dart'; 6 | 7 | /// Implementation of list questions. Can be used without [CLI_Dialog]. 8 | class ListChooser { 9 | /// The options which are presented to the user 10 | List? items; 11 | 12 | /// Select the navigation mode. See dialog.dart for details. 13 | bool navigationMode; 14 | 15 | /// Default constructor for the list chooser. 16 | /// It is as simple as passing your [items] as a List of strings 17 | ListChooser(this.items, {this.navigationMode = false}) { 18 | _checkItems(); 19 | //relevant when running from IntelliJ console pane for example 20 | if (stdin.hasTerminal) { 21 | // lineMode must be true to set echoMode in Windows 22 | // see https://github.com/dart-lang/sdk/issues/28599 23 | stdin.echoMode = false; 24 | stdin.lineMode = false; 25 | } 26 | } 27 | 28 | /// Named constructor mostly for unit testing. 29 | /// For context and an example see [CLI_Dialog], `README.md` and the `test/` folder. 30 | ListChooser.std(this._stdInput, this._stdOutput, this.items, 31 | {this.navigationMode = false}) { 32 | _checkItems(); 33 | if (stdin.hasTerminal) { 34 | stdin.echoMode = false; 35 | stdin.lineMode = false; 36 | } 37 | } 38 | 39 | /// Similar to [ask] this actually triggers the dialog and returns the chosen item = option. 40 | String choose() { 41 | int? input; 42 | var index = 0; 43 | 44 | _renderList(0, initial: true); 45 | 46 | while ((input = _userInput()) != enter) { 47 | if (input! < 0) { 48 | _resetStdin(); 49 | return ':${-input}'; 50 | } 51 | if (input == arrowUp) { 52 | if (index > 0) { 53 | index--; 54 | } 55 | } else if (input == arrowDown) { 56 | if (index < items!.length - 1) { 57 | index++; 58 | } 59 | } 60 | _renderList(index); 61 | } 62 | _resetStdin(); 63 | return items![index]; 64 | } 65 | 66 | // END OF PUBLIC API 67 | 68 | var _stdInput = StdinService(); 69 | var _stdOutput = StdoutService(); 70 | 71 | void _checkItems() { 72 | if (items == null) { 73 | throw ArgumentError('No options for list dialog given'); 74 | } 75 | } 76 | 77 | int? _checkNavigation() { 78 | final input = _stdInput.readByteSync(); 79 | if (navigationMode) { 80 | if (input == 58) { 81 | // 58 = : 82 | _stdOutput.write(':'); 83 | final inputLine = 84 | _stdInput.readLineSync(encoding: Encoding.getByName('utf-8'))!; 85 | final lineNumber = int.parse(inputLine.trim()); 86 | _stdOutput.writeln('$lineNumber'); 87 | return -lineNumber; // make the result negative so it can be told apart from normal key codes 88 | } else { 89 | return input; 90 | } 91 | } else { 92 | return input; 93 | } 94 | } 95 | 96 | void _deletePreviousList() { 97 | for (var i = 0; i < items!.length; i++) { 98 | _stdOutput.write(XTerm.moveUp(1) + XTerm.blankRemaining()); 99 | } 100 | } 101 | 102 | void _renderList(index, {initial = false}) { 103 | if (!initial) { 104 | _deletePreviousList(); 105 | } 106 | for (var i = 0; i < items!.length; i++) { 107 | if (i == index) { 108 | _stdOutput 109 | .writeln('${XTerm.rightIndicator()} ${XTerm.teal(items![i])}'); 110 | continue; 111 | } 112 | _stdOutput.writeln(' ${items![i]}'); 113 | } 114 | } 115 | 116 | void _resetStdin() { 117 | if (stdin.hasTerminal) { 118 | //see default ctor. Order is important here 119 | stdin.lineMode = true; 120 | stdin.echoMode = true; 121 | } 122 | } 123 | 124 | int? _userInput() { 125 | final navigationResult = 126 | _checkNavigation()!; // just receives the read byte, if not successful, 127 | if (navigationResult < 0) { 128 | // < 0 = user has navigated 129 | return navigationResult; 130 | } 131 | 132 | if (Platform.isWindows) { 133 | if (navigationResult == Keys.enter) { 134 | return enter; 135 | } 136 | if (navigationResult == Keys.arrowUp[0]) { 137 | return arrowUp; 138 | } 139 | if (navigationResult == Keys.arrowDown[0]) { 140 | return arrowDown; 141 | } else { 142 | return navigationResult; 143 | } 144 | } else { 145 | if (navigationResult == enter) { 146 | return enter; 147 | } 148 | final anotherByte = _stdInput.readByteSync(); 149 | if (anotherByte == enter) { 150 | return enter; 151 | } 152 | final input = _stdInput.readByteSync(); 153 | return input; 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /lib/cli_dialog/src/services.dart: -------------------------------------------------------------------------------- 1 | export 'stdin_service.dart'; 2 | export 'stdout_service.dart'; 3 | -------------------------------------------------------------------------------- /lib/cli_dialog/src/stdin_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:nylo_support/dart_console/dart_console.dart'; 3 | import '/cli_dialog/src/stdout_service.dart'; 4 | import 'keys.dart'; 5 | 6 | final console = Console(); 7 | 8 | /// Service to simulate stdin. Use it in unit tests. 9 | class StdinService { 10 | /// Indicates whether this is a mock service. 11 | /// Being a mock service means here that it does not really read stdin 12 | /// but gets it´s input from an internal buffer which is filled with [addToBuffer] 13 | bool mock; 14 | 15 | /// The stdout which should be informed if stdin is received in echoMode. 16 | /// echoMode means that you see that the user sees what he/her is typing. See [here](https://api.dart.dev/stable/2.7.2/dart-io/Stdin/echoMode.html). 17 | StdoutService? informStdout; 18 | 19 | /// The default and only constructor where you can optionally indicate 20 | /// whether you want [mock] stdin and [informStdout] 21 | StdinService({this.mock = false, this.informStdout, isTest}) { 22 | if (isTest != null && isTest) { 23 | _isTest = true; 24 | } 25 | } 26 | 27 | /// Use this to simulate stdin. 28 | /// 29 | /// ``` 30 | /// std_output = StdoutService(mock: true); 31 | /// std_input = StdinService(mock: true, informStdout: std_output); 32 | /// std_input.addToBuffer('Some input\n', ...Keys.arrowDown, Keys.enter); 33 | /// ``` 34 | void addToBuffer(elements) { 35 | if (elements is Iterable) { 36 | _mockBuffer.addAll(elements); 37 | } else { 38 | _mockBuffer.add(elements); // that is, if it is only one element 39 | } 40 | } 41 | 42 | /// Use this to read a byte, whether in [mock] mode or with real stdin. 43 | int? readByteSync() { 44 | if (mock) { 45 | var ret = _mockBuffer[0]; 46 | _mockBuffer.removeAt(0); 47 | return (ret is int ? ret : int.parse(ret)); 48 | } 49 | int? key; 50 | if (Platform.isWindows) { 51 | var keyInput = console.readKey().toString(); 52 | if (keyInput.startsWith('ControlCharacter')) { 53 | keyInput = keyInput.split('.')[1]; 54 | if (keyInput == 'arrowUp') { 55 | return winUp; 56 | } else if (keyInput == 'arrowDown') { 57 | return winDown; 58 | } else if (keyInput == 'enter') { 59 | return winEnter; 60 | } 61 | } else { 62 | return keyInput.codeUnitAt(0); 63 | } 64 | } else { 65 | key = stdin.readByteSync(); 66 | } 67 | return key; 68 | } 69 | 70 | /// Use this to read a whole line, whether in [mock] mode or with real stdin. 71 | String? readLineSync({encoding}) { 72 | if (mock) { 73 | var ret = _mockBuffer[0]; 74 | _mockBuffer.removeAt(0); 75 | if (informStdout != null && (_isTest || _getEchomode())) { 76 | informStdout!.write(ret); 77 | } 78 | return ret; 79 | } 80 | return stdin.readLineSync(encoding: encoding); 81 | } 82 | 83 | // END OF PUBLIC API 84 | 85 | bool _isTest = false; 86 | 87 | final _mockBuffer = []; 88 | bool _getEchomode() { 89 | if (!stdin.hasTerminal) { 90 | return false; 91 | } else { 92 | return stdin.echoMode; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/cli_dialog/src/stdout_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | /// Service to simulate stdout. Use it in unit tests. 4 | class StdoutService { 5 | /// Indicates if this StdoutService is in mock mode, see also [StdinService]. 6 | bool mock; 7 | 8 | /// The default and only constructor where you can indicate 9 | // if your stdout should be in [mock] mode. 10 | StdoutService({this.mock = false}); 11 | 12 | /// Use this to write a string, whether in [mock] mode or with real stdout. 13 | void write(str) { 14 | if (mock) { 15 | _buffer += str; 16 | _flush(); 17 | } else { 18 | stdout.write(str); 19 | } 20 | } 21 | 22 | /// Use this to write a string with a trailing newline ('\n'). 23 | /// Whether in [mock] mode or with real stdout. 24 | void writeln(str) { 25 | if (mock) { 26 | _buffer += str + '\n'; 27 | _flush(); 28 | } else { 29 | stdout.writeln(str); 30 | } 31 | } 32 | 33 | /// This returns the stdout as a list. 34 | /// Empty strings at the end are removed. 35 | /// For a string version see [getStringOutput] 36 | List getOutput() { 37 | final ret = []; 38 | for (var element in _output) { 39 | if (element.isNotEmpty) { 40 | ret.add(element); 41 | } 42 | } 43 | return ret; 44 | } 45 | 46 | /// Calls [getOutput] and joins the returned list using the 47 | /// newline character ('\n'). 48 | String getStringOutput() => getOutput().join('\n'); 49 | 50 | // END OF PUBLIC API 51 | 52 | var _buffer = ''; 53 | final _cursor = {'x': 0, 'y': 0}; 54 | final _output = ['']; 55 | 56 | void _addChar() { 57 | var currLine = _output[_cursor['y']!].split(''); 58 | if (_cursor['x']! < currLine.length) { 59 | currLine.removeAt(_cursor['x']!); 60 | currLine.insert(_cursor['x']!, _buffer[0]); 61 | } else { 62 | currLine.add(_buffer[0]); 63 | } 64 | _output[_cursor['y']!] = currLine.join(''); 65 | _cursor['x'] = _cursor['x']! + 1; 66 | } 67 | 68 | void _flush() { 69 | var bufferCpy = _buffer; // copy of buffer 70 | 71 | for (var i = 0; i < bufferCpy.length; i++) { 72 | var utf16char = bufferCpy[i]; 73 | 74 | switch (utf16char) { 75 | case '\n': 76 | _handleNewline(); 77 | break; 78 | case '\r': 79 | _handleCarriageReturn(); 80 | break; 81 | case '\u001b': 82 | var found = _handleEscapeSequence(); 83 | if (found) { 84 | var toSkip = _removeSequenceFromBuffer(); 85 | i += toSkip; 86 | continue; 87 | } else { 88 | _addChar(); 89 | } 90 | break; 91 | default: 92 | _addChar(); 93 | } 94 | _removeCharFromBuffer(); 95 | } 96 | } 97 | 98 | // not all escape sequences have the m delimiter 99 | int _getDelimiterIndex() { 100 | var delims = ['A', 'm', 'K']; 101 | for (var i = 0; i < _buffer.length; i++) { 102 | if (delims.contains(_buffer[i])) return i; 103 | } 104 | return 0; 105 | } 106 | 107 | void _handleCarriageReturn() { 108 | _cursor['x'] = 0; 109 | } 110 | 111 | bool _handleEscapeSequence() { 112 | final sequence = _buffer.substring(1, _getDelimiterIndex() + 1); 113 | 114 | if (sequence == '[0K') { 115 | //blank remaning 116 | _output[_cursor['y']!] = 117 | _output[_cursor['y']!].substring(0, _cursor['x']); 118 | return true; 119 | } 120 | if (RegExp(r'\[\dA').hasMatch(sequence)) { 121 | _cursor['x'] = 0; 122 | var stepsUp = 123 | int.parse(RegExp(r'\[\dA').firstMatch(sequence)!.group(0)![1]); 124 | if (_cursor['y']! - stepsUp >= 0) { 125 | _cursor['y'] = _cursor['y']! - stepsUp; 126 | } 127 | return true; 128 | } 129 | return false; 130 | } 131 | 132 | void _handleNewline() { 133 | _output.add(''); 134 | _cursor['x'] = 0; 135 | _cursor['y'] = _cursor['y']! + 1; 136 | } 137 | 138 | void _removeCharFromBuffer() { 139 | if (_buffer.length > 1) { 140 | _buffer = _buffer.substring(1); 141 | } else { 142 | _buffer = ''; 143 | } 144 | } 145 | 146 | int _removeSequenceFromBuffer() { 147 | var delimIndex = _getDelimiterIndex(); 148 | _buffer = _buffer.substring(delimIndex + 1); 149 | return delimIndex; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /lib/cli_dialog/src/xterm.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'keys.dart'; 3 | 4 | /// Abstract class providing some key codes which can be statically accessed. 5 | abstract class Keys { 6 | /// Arrow down. Consists of three bytes. 7 | /// For historical reasons, you can also use 's' instead of arrow down on Windows. 8 | /// It is packed into a single element list in order to not break the spread syntax. 9 | static final arrowDown = Platform.isWindows ? [winDown] : [27, 91, 66]; 10 | 11 | /// Arrows up. Consists of three bytes in Unix like systems (including MacOS). 12 | /// For historical reasons, you can also use 'w' insted of arrow up on Windows. 13 | /// It is packed into a single element list in order to not break the spread syntax. 14 | static final arrowUp = Platform.isWindows ? [winUp] : [27, 91, 65]; 15 | 16 | /// Enter key. Single byte. 17 | static final enter = Platform.isWindows ? winEnter : 10; 18 | } 19 | 20 | /// Abstract class providing XTerm escape sequences 21 | abstract class XTerm { 22 | /// This is actually just a string but it does something 23 | /// so I decided to wrap it into a method 24 | static String blankRemaining() => '\u001b[0K'; 25 | 26 | /// Bold output. 27 | static String bold(str) => '\u001b[1m$str$reset'; 28 | 29 | /// Gray color. 30 | static String gray(str) => '\u001b[38;5;246m$str$reset'; 31 | 32 | /// Green color. 33 | static String green(str) => '\u001b[32m$str$reset'; 34 | 35 | /// The reverse of '\n'. Goes to the beginning of the previous line. 36 | static String moveUp(n) => '\u001b[${n}A'; 37 | 38 | /// Use [moveUp], concatenate your string ([str]) and then blank the remaining line. 39 | static String replacePreviousLine(str) => moveUp(1) + str + blankRemaining(); 40 | 41 | /// Reset XTerm sequence. This is always applied after using some other sequence. 42 | static var reset = '\u001b[0m'; 43 | 44 | /// UTF-16 char for selecting an item in [ListChooser]. 45 | /// Formally called HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT. 46 | /// On windows a simple > character is used 47 | /// See: https://codepoints.net/U+276F 48 | static String rightIndicator() => 49 | Platform.isWindows ? teal('>') : teal('\u276F'); 50 | 51 | /// Teal (=blue/green) color. 52 | static String teal(str) => '\u001b[38;5;6m$str$reset'; 53 | } 54 | -------------------------------------------------------------------------------- /lib/json_dart_generator/class_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | 3 | class ClassType { 4 | final String _value; 5 | 6 | const ClassType._internal(this._value); 7 | 8 | factory ClassType.name(String typeName) { 9 | var find = values.firstWhereOrNull((element) => element.value == typeName); 10 | 11 | if (find == null) { 12 | print(typeName); 13 | throw typeName; 14 | } 15 | return find; 16 | } 17 | 18 | String get value => _value; 19 | 20 | static bool isPrimitiveType(String typeName) { 21 | return primitiveTypes.map((e) => e.value).contains(typeName); 22 | } 23 | 24 | bool get isPrimitive { 25 | return primitiveTypes.contains(this); 26 | } 27 | 28 | bool get isNull { 29 | return this == ClassType.tNull; 30 | } 31 | 32 | bool get isList { 33 | return this == ClassType.tListDynamic; 34 | } 35 | 36 | bool get isObject { 37 | return this == ClassType.tObject; 38 | } 39 | 40 | bool get isDynamic { 41 | return this == ClassType.tDynamic; 42 | } 43 | 44 | static ClassType getType(dynamic value) { 45 | if (value == null) { 46 | return tNull; 47 | } 48 | 49 | if (value is String) { 50 | return tString; 51 | } else if (value is int) { 52 | return tInt; 53 | } else if (value is double) { 54 | return tDouble; 55 | } else if (value is bool) { 56 | return tBool; 57 | } else if (value is List) { 58 | return tListDynamic; 59 | } else { 60 | return tObject; 61 | } 62 | } 63 | 64 | /// (int -> double) / bool -> string -> dynamic 65 | /// Class / List -> dynamic 66 | static ClassType? mergeType(ClassType? oriType, ClassType? newType) { 67 | if (oriType == null || oriType.isNull) { 68 | return newType; 69 | } else if (newType == null || newType.isNull) { 70 | return oriType; 71 | } else if (oriType == newType) { 72 | return oriType; 73 | } 74 | 75 | if (oriType == ClassType.tInt || oriType == ClassType.tDouble) { 76 | if (newType == ClassType.tInt || newType == ClassType.tDouble) { 77 | return ClassType.tDouble; 78 | } else if (newType == ClassType.tBool || newType == ClassType.tString) { 79 | return ClassType.tString; 80 | } else { 81 | return ClassType.tDynamic; 82 | } 83 | } else if (oriType == ClassType.tBool) { 84 | if (newType == ClassType.tInt || 85 | newType == ClassType.tDouble || 86 | newType == ClassType.tString) { 87 | return ClassType.tString; 88 | } else { 89 | return ClassType.tDynamic; 90 | } 91 | } else if (oriType == ClassType.tString) { 92 | if (newType == ClassType.tInt || 93 | newType == ClassType.tDouble || 94 | newType == ClassType.tBool) { 95 | return ClassType.tString; 96 | } else { 97 | return ClassType.tDynamic; 98 | } 99 | } else { 100 | return ClassType.tDynamic; 101 | } 102 | } 103 | 104 | static const primitiveTypes = [ 105 | tInt, 106 | tDouble, 107 | tString, 108 | tBool, 109 | ]; 110 | 111 | static const values = [ 112 | tInt, 113 | tDouble, 114 | tString, 115 | tBool, 116 | tListDynamic, 117 | tDynamic, 118 | tObject, 119 | tNull, 120 | ]; 121 | 122 | static const tInt = ClassType._internal(ClassType._int); 123 | static const tDouble = ClassType._internal(ClassType._double); 124 | static const tString = ClassType._internal(ClassType._string); 125 | static const tBool = ClassType._internal(ClassType._bool); 126 | static const tListDynamic = ClassType._internal(ClassType._listDynamic); 127 | static const tDynamic = ClassType._internal(ClassType._dynamic); 128 | static const tNull = ClassType._internal(ClassType._null); 129 | static const tObject = ClassType._internal(ClassType._object); 130 | 131 | static const String _int = 'int'; 132 | static const String _double = 'double'; 133 | static const String _string = 'String'; 134 | static const String _bool = 'bool'; 135 | static const String _listDynamic = 'List'; 136 | static const String _dynamic = 'dynamic'; 137 | static const String _object = 'object'; 138 | static const String _null = 'Null'; 139 | 140 | @override 141 | String toString() { 142 | return value; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/json_dart_generator/dart_code_generator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'json_def.dart'; 5 | 6 | /// [DartCodeGenerator] code 7 | class DartCodeGenerator { 8 | final String? rootClassName; 9 | 10 | final bool rootClassNameWithPrefixSuffix; 11 | 12 | final String? classPrefix; 13 | 14 | final String? classSuffix; 15 | 16 | DartCodeGenerator({ 17 | this.rootClassName, 18 | this.rootClassNameWithPrefixSuffix = true, 19 | this.classPrefix, 20 | this.classSuffix, 21 | }); 22 | 23 | String generate(String rawJson) { 24 | dynamic jsonData; 25 | try { 26 | jsonData = json.decode(rawJson); 27 | } catch (e) { 28 | stderr.write('json invalid\n$e\n'); 29 | return ''; 30 | } 31 | 32 | var def = JsonDef( 33 | rootClassName: rootClassName, 34 | jsonData: jsonData, 35 | rootClassNameWithPrefixSuffix: rootClassNameWithPrefixSuffix, 36 | classNamePrefixSuffixBuilder: (String name, bool isPrefix) { 37 | if (isPrefix) { 38 | return classPrefix; 39 | } else { 40 | return classSuffix; 41 | } 42 | }, 43 | ); 44 | 45 | return def.classCode; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/json_dart_generator/extension.dart: -------------------------------------------------------------------------------- 1 | import 'class_type.dart'; 2 | import 'json_def.dart'; 3 | 4 | extension DartCodeGenerator on ValueDef { 5 | String get classCode { 6 | if (childrenDef is! List && childrenDef is! Map) { 7 | return ''; 8 | } 9 | 10 | String fieldText() { 11 | String detectListInner(ValueDef def) { 12 | if (def.type.isList) { 13 | var defListType = def.listType!; 14 | if (defListType.isDynamic) { 15 | return 'List<${defListType.value}>?'; 16 | } else if (defListType.isPrimitive) { 17 | return 'List<${defListType.value}>?'; 18 | } else if (defListType.isList) { 19 | var childType = 20 | detectListInner((def.childrenDef as List).first); 21 | return 'List<$childType>?'; 22 | } else { 23 | return 'List<${detectListInner((def.childrenDef as List).first)}>?'; 24 | } 25 | } 26 | 27 | var obj = findCustomObject(def)!; 28 | return obj.classNameFull; 29 | } 30 | 31 | var text = ''; 32 | if (childrenDef is List) { 33 | var prefix = detectListInner(this); 34 | text += '$prefix value;'; 35 | } else if (childrenDef is Map) { 36 | var keyMap = childrenDef as Map; 37 | keyMap.forEach((key, value) { 38 | if (value.type == ClassType.tListDynamic) { 39 | var prefix = detectListInner(value); 40 | text += '$prefix ${key.lowerCamel()};'; 41 | } else if (value.type == ClassType.tObject) { 42 | late ValueDef findCustomDef; 43 | try { 44 | findCustomDef = findCustomObject(value)!; 45 | } catch (e) { 46 | print(value); 47 | } 48 | text += '${findCustomDef.classNameFull}? ${key.lowerCamel()};'; 49 | } else { 50 | var typeShow = '${value.type}'; 51 | if (!value.type.isDynamic) { 52 | typeShow += '?'; 53 | } 54 | text += '$typeShow ${key.lowerCamel()};'; 55 | } 56 | }); 57 | } 58 | return text; 59 | } 60 | 61 | // 建構子區域 62 | String constructorText() { 63 | var body = ''; 64 | 65 | if (childrenDef is List) { 66 | body += 'this.value'; 67 | } else if (childrenDef is Map) { 68 | var keyMap = childrenDef as Map; 69 | keyMap.forEach((key, value) { 70 | body += 'this.${key.lowerCamel()},'; 71 | }); 72 | } 73 | 74 | if (body.isNotEmpty) { 75 | body = '{$body}'; 76 | } 77 | 78 | return '$classNameFull($body);'; 79 | } 80 | 81 | // fromJson 82 | String fromJsonText() { 83 | var body = ''; 84 | var param = ''; 85 | 86 | String listInnerContent(ListInner innerType) { 87 | if (innerType.type.isObject) { 88 | return '${innerType.className}.fromJson(e)'; 89 | } else if (innerType.type.isPrimitive) { 90 | if (innerType.type == ClassType.tDouble) { 91 | return 'e.toDouble()'; 92 | } else if (innerType.type == ClassType.tString) { 93 | return 'e.toString()'; 94 | } else { 95 | return 'e as ${innerType.type.value}'; 96 | } 97 | } else if (innerType.type.isDynamic) { 98 | return 'e'; 99 | } 100 | return ''; 101 | } 102 | 103 | String? listInnerTypeShow(ListInner innerType) { 104 | if (innerType.type.isPrimitive) { 105 | if (innerType.type == ClassType.tDouble) { 106 | return 'double'; 107 | } else if (innerType.type == ClassType.tString) { 108 | return 'String'; 109 | } else if (innerType.type == ClassType.tInt) { 110 | return 'int'; 111 | } else if (innerType.type == ClassType.tBool) { 112 | return 'bool'; 113 | } 114 | } 115 | return null; 116 | } 117 | 118 | String detectListInner( 119 | ValueDef def, 120 | String innerContent, 121 | String? innerTypeText, [ 122 | int depth = 0, 123 | ]) { 124 | String nextInner() { 125 | return detectListInner( 126 | ((def.childrenDef as List).first), 127 | innerContent, 128 | innerTypeText, 129 | depth + 1, 130 | ); 131 | } 132 | 133 | if (depth == 0) { 134 | if (def.listType!.isDynamic) { 135 | return isListRoot && isRoot 136 | ? 'json' 137 | : 'json[\'${def.key}\'] as List'; 138 | } else { 139 | return isListRoot && isRoot 140 | ? 'json${nextInner()}' 141 | : '(json[\'${def.key}\'] as List)${nextInner()}'; 142 | } 143 | } else { 144 | if (def.type.isList) { 145 | if (def.listType!.isDynamic) { 146 | return '.map((e) => (e as List).toList()).toList()'; 147 | } else { 148 | return '.map((e) => (e as List)${nextInner()}).toList()'; 149 | } 150 | } else { 151 | if (innerTypeText != null) { 152 | return '.map<$innerTypeText>((e) => $innerContent).toList()'; 153 | } else { 154 | return '.map((e) => $innerContent).toList()'; 155 | } 156 | } 157 | } 158 | } 159 | 160 | if (childrenDef is List) { 161 | param = 'List json'; 162 | var innerType = listInnerType!; 163 | 164 | body += 'value = ${detectListInner( 165 | this, 166 | listInnerContent(innerType), 167 | listInnerTypeShow(innerType), 168 | )};'; 169 | } else if (childrenDef is Map) { 170 | param = 'Map json'; 171 | var keyMap = childrenDef as Map; 172 | keyMap.forEach((key, value) { 173 | if (body.isNotEmpty) { 174 | body += '\n'; 175 | } 176 | 177 | if (value.type == ClassType.tListDynamic) { 178 | var innerType = value.listInnerType!; 179 | body += 180 | 'if (json[\'${value.key}\'] != null) {\n ${(value.key ?? value.parentKey)!.lowerCamel()} = ${detectListInner( 181 | value, 182 | listInnerContent(innerType), 183 | listInnerTypeShow(innerType), 184 | )}; \n}'; 185 | } else if (value.type == ClassType.tObject) { 186 | var findCustomDef = findCustomObject(value)!; 187 | body += 188 | '${key.lowerCamel()} = json[\'$key\'] != null ? ${findCustomDef.classNameFull}.fromJson(json[\'${value.key}\']) : null;'; 189 | } else { 190 | body += '${key.lowerCamel()} = json[\'$key\']'; 191 | 192 | if (value.type == ClassType.tDouble) { 193 | body += '?.toDouble();'; 194 | } else if (value.type == ClassType.tString) { 195 | body += '?.toString();'; 196 | } else { 197 | body += ';'; 198 | } 199 | } 200 | }); 201 | } 202 | 203 | return '$classNameFull.fromJson($param) {\n$body\n}'; 204 | } 205 | 206 | String toJsonText() { 207 | var body = ''; 208 | var returnText = ''; 209 | 210 | String detectListInner(ValueDef def, String innerContent, 211 | [int depth = 0]) { 212 | String nextInner() { 213 | return detectListInner( 214 | (def.childrenDef as List).first, 215 | innerContent, 216 | depth + 1, 217 | ); 218 | } 219 | 220 | if (depth == 0) { 221 | return '${isListRoot ? 'value' : (def.key ?? def.parentKey)!.lowerCamel()}?${nextInner()}'; 222 | } else { 223 | if (def.type.isList) { 224 | return '.map((e) => e${nextInner()}).toList()'; 225 | } else { 226 | return '.map((e) => $innerContent).toList()'; 227 | } 228 | } 229 | } 230 | 231 | if (childrenDef is List) { 232 | returnText = 'List?'; 233 | 234 | var innerType = listInnerType!; 235 | 236 | if (innerType.type.isObject) { 237 | var innerContent = 'e.toJson()'; 238 | body += 'return ${detectListInner(this, innerContent)};'; 239 | } else { 240 | body += 'return value;'; 241 | } 242 | } else if (childrenDef is Map) { 243 | returnText = 'Map'; 244 | var keyMap = childrenDef as Map; 245 | keyMap.forEach((key, value) { 246 | if (body.isNotEmpty) { 247 | body += '\n'; 248 | } 249 | 250 | if (value.type == ClassType.tListDynamic) { 251 | var innerType = value.listInnerType!; 252 | 253 | if (innerType.type.isObject) { 254 | var innerContent = 'e.toJson()'; 255 | body += '\'$key\' : ${detectListInner(value, innerContent)},'; 256 | } else { 257 | body += '\'$key\' : ${key.lowerCamel()},'; 258 | } 259 | } else if (value.type == ClassType.tObject) { 260 | body += '\'$key\' : ${key.lowerCamel()}?.toJson(),'; 261 | } else { 262 | body += '\'$key\' : ${key.lowerCamel()},'; 263 | } 264 | }); 265 | body = 'return {\n$body\n};'; 266 | } 267 | 268 | return '$returnText toJson() {\n$body\n}'; 269 | } 270 | 271 | return ''' 272 | class $classNameFull { 273 | ${fieldText()} 274 | 275 | ${constructorText()} 276 | 277 | ${fromJsonText()} 278 | 279 | ${toJsonText()} 280 | } 281 | '''; 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /lib/json_dart_generator/json_def.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'class_type.dart'; 3 | import 'extension.dart'; 4 | 5 | typedef ClassNamePrefixSuffixBuilder = String? Function( 6 | String name, 7 | bool isPrefix, 8 | ); 9 | 10 | class JsonDef { 11 | final dynamic jsonData; 12 | 13 | late ValueDef _jsonStruct; 14 | 15 | late ValueDef _summarizeStruct; 16 | 17 | List get allCustomObject => _summarizeStruct.customObjects; 18 | 19 | JsonDef({ 20 | String? rootClassName, 21 | this.jsonData, 22 | required bool rootClassNameWithPrefixSuffix, 23 | ClassNamePrefixSuffixBuilder? classNamePrefixSuffixBuilder, 24 | }) { 25 | _jsonStruct = ValueDef( 26 | rootClassName: rootClassName, 27 | rootClassNameWithPrefixSuffix: rootClassNameWithPrefixSuffix, 28 | value: jsonData, 29 | classNamePrefixSuffixBuilder: classNamePrefixSuffixBuilder, 30 | ); 31 | _summarizeStruct = _jsonStruct.summarize(); 32 | } 33 | 34 | String get structString { 35 | return _jsonStruct.structString; 36 | } 37 | 38 | String get summarizeString { 39 | return _summarizeStruct.structString; 40 | } 41 | 42 | String get customObjectString { 43 | return _summarizeStruct.customObjectString; 44 | } 45 | 46 | String get classCode { 47 | var code = ''; 48 | code += _summarizeStruct.classCode; 49 | code += allCustomObject.map((e) => e.classCode).join('\n\n'); 50 | return code; 51 | } 52 | } 53 | 54 | class ListInner { 55 | ClassType type; 56 | 57 | ClassType oriType; 58 | 59 | String className; 60 | 61 | ListInner({ 62 | required this.type, 63 | required this.oriType, 64 | required this.className, 65 | }); 66 | } 67 | 68 | class ValueDef { 69 | ClassType type; 70 | 71 | ClassType? listType; 72 | 73 | String? rootClassName; 74 | 75 | bool rootClassNameWithPrefixSuffix; 76 | 77 | ListInner? get listInnerType { 78 | if (listType == null) { 79 | return null; 80 | } 81 | 82 | ListInner findInner(ValueDef def) { 83 | if (def.type == ClassType.tListDynamic) { 84 | if (def.listType == ClassType.tDynamic) { 85 | return ListInner( 86 | type: ClassType.tDynamic, 87 | oriType: ClassType.tDynamic, 88 | className: ClassType.tDynamic.value, 89 | ); 90 | } else { 91 | return findInner((def.childrenDef as List).first); 92 | } 93 | } else if (def.type == ClassType.tObject) { 94 | var customObject = findCustomObject(def)!; 95 | return ListInner( 96 | type: def.type, 97 | oriType: def.type, 98 | className: customObject.classNameFull, 99 | ); 100 | } else { 101 | return ListInner( 102 | type: def.type, 103 | oriType: ClassType.getType(def.value), 104 | className: def.type.value, 105 | ); 106 | } 107 | } 108 | 109 | return findInner(this); 110 | } 111 | 112 | bool get isRoot => parent == null; 113 | 114 | bool get isListRoot { 115 | var parentDef = this; 116 | 117 | while (true) { 118 | if (parentDef.parent == null) { 119 | break; 120 | } 121 | parentDef = parentDef.parent!; 122 | } 123 | return parentDef.type.isList; 124 | } 125 | 126 | ValueDef? parent; 127 | 128 | int get depth { 129 | return (parent?.depth ?? -1) + 1; 130 | } 131 | 132 | String get _intentSpace { 133 | return ' '; 134 | } 135 | 136 | String get _depthIntentSpace { 137 | var text = ''; 138 | for (var i = 0; i < depth; i++) { 139 | text += _intentSpace; 140 | } 141 | return text; 142 | } 143 | 144 | /// get parent key 145 | String? get parentKey { 146 | ValueDef? parentDef = this; 147 | var key = parentDef.key; 148 | 149 | while (key == null && parentDef != null) { 150 | key = parentDef.key; 151 | parentDef = parentDef.parent; 152 | } 153 | return key; 154 | } 155 | 156 | final String? key; 157 | 158 | final dynamic value; 159 | 160 | dynamic childrenDef; 161 | 162 | ClassNamePrefixSuffixBuilder? classNamePrefixSuffixBuilder; 163 | 164 | String get classNamePrefix => 165 | classNamePrefixSuffixBuilder?.call( 166 | classNameNoPrefixSuffix, 167 | true, 168 | ) ?? 169 | ''; 170 | 171 | String get classNameSuffix => 172 | classNamePrefixSuffixBuilder?.call( 173 | classNameNoPrefixSuffix, 174 | false, 175 | ) ?? 176 | ''; 177 | 178 | List get customObjects { 179 | var objects = []; 180 | if (depth != 0 && type == ClassType.tObject) { 181 | objects.add(this); 182 | } 183 | 184 | if (childrenDef is List) { 185 | var childrenObject = (childrenDef as List) 186 | .map((e) => e.customObjects) 187 | .expand((element) => element) 188 | .toList(); 189 | 190 | for (var outerE in childrenObject) { 191 | var isExist = objects.any((innerE) { 192 | var result = innerE.isStructSame(outerE); 193 | if (innerE.key == 'ios') { 194 | print(result); 195 | } 196 | return result; 197 | }); 198 | 199 | if (!isExist) { 200 | objects.add(outerE); 201 | } 202 | } 203 | } else if (childrenDef is Map) { 204 | var childrenObject = (childrenDef as Map) 205 | .entries 206 | .map((e) => e.value.customObjects) 207 | .expand((element) => element) 208 | .toList(); 209 | 210 | for (var outerE in childrenObject) { 211 | var isExist = objects.any((innerE) { 212 | var result = innerE.isStructSame(outerE); 213 | return result; 214 | }); 215 | 216 | if (!isExist) { 217 | objects.add(outerE); 218 | } 219 | } 220 | } 221 | return objects; 222 | } 223 | 224 | /// all custom object 225 | List get _allCustomObject { 226 | ValueDef? parentDef = this; 227 | do { 228 | parentDef = parentDef?.parent; 229 | } while (parentDef?.parent != null); 230 | return parentDef?.customObjects ?? customObjects; 231 | } 232 | 233 | ValueDef? findCustomObject(ValueDef def) { 234 | var find = _allCustomObject.firstWhereOrNull( 235 | (element) => def.isStructSame(element), 236 | ); 237 | return find; 238 | } 239 | 240 | bool isStructSame(ValueDef other, {bool debug = false}) { 241 | if (childrenDef is List && other.childrenDef is List) { 242 | var thisFirst = (childrenDef as List).first; 243 | var otherFirst = (other.childrenDef as List).first; 244 | return thisFirst.isStructSame(otherFirst); 245 | } else if (childrenDef is Map && 246 | other.childrenDef is Map) { 247 | var thisKeyList = (childrenDef as Map).entries; 248 | var otherKeyList = (other.childrenDef as Map).entries; 249 | var isLengthSame = thisKeyList.length == otherKeyList.length; 250 | 251 | if (isLengthSame) { 252 | var isSame = !thisKeyList.any((element) { 253 | var thisKey = element.key; 254 | var thisValue = element.value; 255 | 256 | var findOther = otherKeyList 257 | .firstWhereOrNull((element) => element.key == thisKey); 258 | 259 | if (findOther != null) { 260 | var isSame = thisValue.isStructSame(findOther.value); 261 | return !isSame; 262 | } else { 263 | return true; 264 | } 265 | }); 266 | 267 | return isSame; 268 | } 269 | } else if (type == other.type && key == other.key) { 270 | return true; 271 | } 272 | 273 | return false; 274 | } 275 | 276 | ValueDef copyWith( 277 | {ClassType? type, ClassType? listType, dynamic childrenDef}) { 278 | return ValueDef._( 279 | rootClassName: rootClassName, 280 | type: type ?? this.type, 281 | listType: listType, 282 | key: key, 283 | rootClassNameWithPrefixSuffix: rootClassNameWithPrefixSuffix, 284 | childrenDef: childrenDef ?? this.childrenDef, 285 | )..classNamePrefixSuffixBuilder = classNamePrefixSuffixBuilder; 286 | } 287 | 288 | ValueDef._({ 289 | this.rootClassName, 290 | required this.rootClassNameWithPrefixSuffix, 291 | required this.type, 292 | this.listType, 293 | this.key, 294 | this.childrenDef, 295 | }) : value = null { 296 | _detectAllParentNode(); 297 | } 298 | 299 | ValueDef({ 300 | this.rootClassName, 301 | required this.rootClassNameWithPrefixSuffix, 302 | this.key, 303 | this.value, 304 | this.classNamePrefixSuffixBuilder, 305 | }) : type = ClassType.getType(value) { 306 | _childDef(); 307 | _detectAllParentNode(); 308 | } 309 | 310 | void _detectAllParentNode() { 311 | if (childrenDef is List) { 312 | for (var element in (childrenDef as List)) { 313 | element.parent = this; 314 | element._detectAllParentNode(); 315 | } 316 | } else if (childrenDef is Map) { 317 | (childrenDef as Map).forEach((key, value) { 318 | value.parent = this; 319 | value._detectAllParentNode(); 320 | }); 321 | } 322 | } 323 | 324 | void _childDef() { 325 | switch (type) { 326 | case ClassType.tListDynamic: 327 | childrenDef = (value as List) 328 | .map((e) => ValueDef( 329 | value: e, 330 | rootClassNameWithPrefixSuffix: false, 331 | classNamePrefixSuffixBuilder: classNamePrefixSuffixBuilder, 332 | )) 333 | .toList(); 334 | break; 335 | case ClassType.tObject: 336 | childrenDef = 337 | (value as Map).map((key, value) => MapEntry( 338 | key, 339 | ValueDef( 340 | key: key, 341 | rootClassNameWithPrefixSuffix: false, 342 | value: value, 343 | classNamePrefixSuffixBuilder: classNamePrefixSuffixBuilder, 344 | ))); 345 | break; 346 | default: 347 | childrenDef = value; 348 | break; 349 | } 350 | } 351 | 352 | ValueDef _summarizeData(ValueDef other) { 353 | if (type == ClassType.tListDynamic && 354 | other.type == ClassType.tListDynamic) { 355 | ValueDef? elementDef; 356 | 357 | var keyList = List.from(childrenDef) 358 | ..addAll(other.childrenDef) 359 | ..nonNulls; 360 | 361 | for (var i = 0; i < keyList.length; i++) { 362 | var element = keyList[i]; 363 | 364 | elementDef ??= element; 365 | elementDef = elementDef!._summarizeData(element!); 366 | 367 | listType = elementDef.type; 368 | if (listType == ClassType.tDynamic) { 369 | break; 370 | } 371 | } 372 | 373 | return copyWith( 374 | type: type, 375 | listType: listType, 376 | childrenDef: listType == ClassType.tDynamic ? [] : [elementDef], 377 | ); 378 | } else if (type == ClassType.tObject && other.type == ClassType.tObject) { 379 | var keyMap = Map.from(childrenDef); 380 | 381 | (other.childrenDef as Map).forEach((key, value) { 382 | if (keyMap.containsKey(key)) { 383 | keyMap[key] = keyMap[key]!._summarizeData(value); 384 | } else { 385 | keyMap[key] = value; 386 | } 387 | }); 388 | return copyWith(type: type, childrenDef: keyMap); 389 | } else { 390 | var resultType = ClassType.mergeType(type, other.type); 391 | 392 | var mergeListType = ClassType.mergeType(listType, other.listType); 393 | var children = type.isNull ? other.childrenDef : childrenDef; 394 | 395 | return copyWith( 396 | type: resultType, 397 | listType: mergeListType, 398 | childrenDef: children, 399 | ); 400 | } 401 | } 402 | 403 | ValueDef _summarizeEntry() { 404 | switch (type) { 405 | case ClassType.tListDynamic: 406 | if ((childrenDef as List).isEmpty) { 407 | listType = ClassType.tDynamic; 408 | 409 | return copyWith( 410 | type: type, 411 | listType: listType, 412 | ); 413 | } else { 414 | ValueDef? elementDef; 415 | 416 | var keyList = List.from(childrenDef); 417 | for (var i = 0; i < keyList.length; i++) { 418 | var element = keyList[i]; 419 | 420 | elementDef ??= element; 421 | elementDef = elementDef._summarizeData(element); 422 | 423 | listType = elementDef.type; 424 | if (listType == ClassType.tDynamic) { 425 | break; 426 | } 427 | } 428 | 429 | return copyWith( 430 | type: type, 431 | listType: listType, 432 | childrenDef: listType == ClassType.tDynamic ? [] : [elementDef], 433 | ); 434 | } 435 | case ClassType.tObject: 436 | var keyMap = Map.from(childrenDef); 437 | (childrenDef as Map).forEach((key, value) { 438 | keyMap[key] = value._summarizeEntry(); 439 | }); 440 | return copyWith(type: type, childrenDef: keyMap); 441 | default: 442 | return this; 443 | } 444 | } 445 | 446 | ValueDef _convertNullToDynamic(ValueDef def) { 447 | switch (def.type) { 448 | case ClassType.tListDynamic: 449 | var listType = def.listType!; 450 | if (listType.isNull || listType.isDynamic) { 451 | listType = ClassType.tDynamic; 452 | return def.copyWith( 453 | type: def.type, 454 | listType: listType, 455 | ); 456 | } else { 457 | var keyList = List.from(def.childrenDef); 458 | for (var i = 0; i < keyList.length; i++) { 459 | keyList[i] = _convertNullToDynamic(keyList[i]); 460 | } 461 | return def.copyWith( 462 | type: def.type, 463 | listType: listType, 464 | childrenDef: keyList, 465 | ); 466 | } 467 | case ClassType.tObject: 468 | var keyMap = Map.from(def.childrenDef); 469 | (def.childrenDef as Map).forEach((key, value) { 470 | keyMap[key] = _convertNullToDynamic(value); 471 | }); 472 | return def.copyWith(type: def.type, childrenDef: keyMap); 473 | default: 474 | if (def.type.isNull) { 475 | def.type = ClassType.tDynamic; 476 | } 477 | return def; 478 | } 479 | } 480 | 481 | ValueDef summarize() { 482 | var def = _summarizeEntry(); 483 | 484 | return _convertNullToDynamic(def); 485 | } 486 | 487 | @override 488 | String toString() => structString; 489 | 490 | String get structString { 491 | var keyShow = ''; 492 | if (key != null) { 493 | keyShow = '($key)'; 494 | } 495 | if (childrenDef is Map) { 496 | return '${_depthIntentSpace}Map$keyShow {\n${(childrenDef as Map).values.join(',\n')}\n$_depthIntentSpace}'; 497 | } else if (childrenDef is List) { 498 | if ((childrenDef as List).isEmpty) { 499 | return '${_depthIntentSpace}List<$listType>$keyShow []'; 500 | } else { 501 | return '${_depthIntentSpace}List<$listType>$keyShow [\n${(childrenDef as List).join(',\n')}\n$_depthIntentSpace]'; 502 | } 503 | } else { 504 | return '$_depthIntentSpace$type$keyShow $childrenDef'; 505 | } 506 | } 507 | 508 | String get customObjectString { 509 | var text = ''; 510 | 511 | for (var element in customObjects) { 512 | if (text.isNotEmpty) { 513 | text += '\n\n'; 514 | } 515 | var tempParent = parent; 516 | element.parent = null; 517 | text += element.structString; 518 | element.parent = tempParent; 519 | } 520 | return text; 521 | } 522 | 523 | String get classNameFull { 524 | if (isRoot && !rootClassNameWithPrefixSuffix) { 525 | return classNameNoPrefixSuffix; 526 | } else { 527 | return '$classNamePrefix$classNameNoPrefixSuffix$classNameSuffix'; 528 | } 529 | } 530 | 531 | String get classNameNoPrefixSuffix { 532 | if (isRoot) { 533 | return rootClassName ?? 'Root'; 534 | } else { 535 | var parentName = parent?.classNameNoPrefixSuffix; 536 | 537 | if (parentKey == null && type.isObject) { 538 | return '${parentName}Value'.upperCamel(); 539 | } else { 540 | if (key == null) { 541 | return '$parentName'.upperCamel(); 542 | } else { 543 | return '$parentName${key!.upperCamel()}'.upperCamel(); 544 | } 545 | } 546 | } 547 | } 548 | } 549 | 550 | extension StringExtension on String { 551 | String upperCamel() { 552 | String capitalize(Match match) { 553 | var text = match[0]; 554 | if (text == null) return ''; 555 | if (text.length >= 2) { 556 | return '${text[0].toUpperCase()}${text.substring(1)}'; 557 | } else if (text.length == 1) { 558 | return text[0].toUpperCase(); 559 | } else { 560 | return text; 561 | } 562 | } 563 | 564 | String skip(String s) => ''; 565 | 566 | return splitMapJoin( 567 | RegExp(r'[a-zA-Z0-9]+'), 568 | onMatch: capitalize, 569 | onNonMatch: skip, 570 | ); 571 | } 572 | 573 | String lowerCamel() { 574 | var upper = upperCamel(); 575 | return '${upper[0].toLowerCase()}${upper.substring(1)}'; 576 | } 577 | } 578 | -------------------------------------------------------------------------------- /lib/metro/menu.dart: -------------------------------------------------------------------------------- 1 | /// The [metroMenu] is displayed when running `dart run nylo_framework:main` 2 | /// To run a command you can call `dart run nylo_framework:main make:model my_model` 3 | const String metroMenu = """ 4 | Metro - Nylo's Companion to Build Flutter apps by Anthony Gordon 5 | 6 | Usage: 7 | command [options] [arguments] 8 | 9 | Options 10 | -h 11 | 12 | All commands: 13 | 14 | [Widget Commands] 15 | make:page 16 | make:stateful_widget 17 | make:stateless_widget 18 | make:state_managed_widget 19 | make:navigation_hub 20 | make:journey_widget 21 | make:form 22 | 23 | [App Commands] 24 | make:model 25 | make:provider 26 | make:api_service 27 | make:controller 28 | make:event 29 | make:theme 30 | make:route_guard 31 | make:config 32 | make:interceptor 33 | make:command 34 | """; 35 | -------------------------------------------------------------------------------- /lib/metro/ny_cli.dart: -------------------------------------------------------------------------------- 1 | library nylo_framework; 2 | 3 | import 'dart:async'; 4 | import 'dart:io'; 5 | import 'package:args/args.dart'; 6 | import 'package:nylo_support/metro/metro_service.dart'; 7 | import 'package:dio/dio.dart'; 8 | 9 | export 'package:nylo_support/metro/metro_service.dart'; 10 | export 'package:nylo_support/metro/metro_console.dart'; 11 | export 'package:nylo_support/metro/constants/strings.dart'; 12 | export 'package:nylo_support/metro/models/metro_project_file.dart'; 13 | export 'package:nylo_support/metro/models/ny_command.dart'; 14 | export 'package:nylo_support/metro/models/ny_template.dart'; 15 | export 'package:dio/dio.dart'; 16 | 17 | /// Base class for custom commands 18 | abstract class NyCustomCommand { 19 | /// Define the command configuration 20 | CommandBuilder builder(CommandBuilder commandBuilder) => commandBuilder; 21 | 22 | CommandBuilder? _builder; 23 | 24 | List arguments; 25 | 26 | /// Execute the command with parsed results 27 | Future handle(CommandResult result); 28 | 29 | NyCustomCommand(this.arguments) { 30 | CommandBuilder commandBuilder = CommandBuilder(); 31 | _builder = builder(commandBuilder); 32 | } 33 | 34 | /// Run the command 35 | run() { 36 | assert( 37 | _builder != null, 38 | 'CommandBuilder must be initialized before running the command.', 39 | ); 40 | 41 | // Handle help flag 42 | if (arguments.contains('--help') || arguments.contains('-h')) { 43 | print('\nUsage:'); 44 | print(_builder!.usage); 45 | return; 46 | } 47 | 48 | final CommandResult result = _builder!.parse(arguments); 49 | handle(result); 50 | } 51 | 52 | /// Run a process with the given command 53 | runProcess(String command, 54 | {String? workingDirectory, bool? runInShell, bool silent = false}) async { 55 | // Parse command properly handling quotes 56 | final List parts = _parseCommand(command); 57 | final String executable = parts[0]; 58 | final List args = parts.sublist(1); 59 | 60 | // Run the process 61 | final Process process = await Process.start( 62 | executable, 63 | args, 64 | mode: ProcessStartMode.normal, 65 | workingDirectory: workingDirectory, 66 | runInShell: runInShell ?? false, 67 | ); 68 | 69 | if (silent == false) { 70 | // Listen to the process output 71 | process.stdout.transform(SystemEncoding().decoder).listen((String data) { 72 | // Print the output of the command 73 | print(data); 74 | }); 75 | 76 | process.stderr.transform(SystemEncoding().decoder).listen((String data) { 77 | // Print the error output of the command 78 | print(data); 79 | }); 80 | } 81 | 82 | // Wait for the process to complete 83 | final int exitCode = await process.exitCode; 84 | if (silent == false) { 85 | if (exitCode != 0) { 86 | // Print an error message if the process failed 87 | error('Command failed with exit code: $exitCode'); 88 | } 89 | } 90 | 91 | // Return the exit code 92 | return exitCode; 93 | } 94 | 95 | // Helper method to parse command strings correctly handling quotes 96 | List _parseCommand(String command) { 97 | final List parts = []; 98 | bool inQuotes = false; 99 | String currentPart = ''; 100 | String quoteChar = ''; 101 | 102 | for (int i = 0; i < command.length; i++) { 103 | final char = command[i]; 104 | 105 | if ((char == '"' || char == "'") && (i == 0 || command[i - 1] != '\\')) { 106 | if (!inQuotes) { 107 | inQuotes = true; 108 | quoteChar = char; 109 | } else if (char == quoteChar) { 110 | inQuotes = false; 111 | quoteChar = ''; 112 | } else { 113 | currentPart += char; 114 | } 115 | } else if (char == ' ' && !inQuotes) { 116 | if (currentPart.isNotEmpty) { 117 | parts.add(currentPart); 118 | currentPart = ''; 119 | } 120 | } else { 121 | currentPart += char; 122 | } 123 | } 124 | 125 | if (currentPart.isNotEmpty) { 126 | parts.add(currentPart); 127 | } 128 | 129 | return parts; 130 | } 131 | 132 | /// Add a package to the pubspec.yaml file 133 | addPackage(String package, {String? version, bool dev = false}) async { 134 | await MetroService.addPackage(package, dev: dev, version: version); 135 | } 136 | 137 | /// Add multiple packages to the pubspec.yaml file 138 | addPackages(List packages, {bool dev = false}) async { 139 | await MetroService.addPackages(packages, dev: dev); 140 | } 141 | 142 | /// Prints a message in blue color 143 | info(String message) { 144 | // Print info message in blue 145 | print('\x1B[34m$message\x1B[0m'); 146 | } 147 | 148 | /// Prints a message in red color 149 | error(String message) { 150 | // Print error message in red 151 | print('\x1B[31m$message\x1B[0m'); 152 | } 153 | 154 | /// Prints a message in green color 155 | success(String message) { 156 | // Print success message in green 157 | print('\x1B[32m$message\x1B[0m'); 158 | } 159 | 160 | /// Prints a message in yellow color 161 | warning(String message) { 162 | // Print warning message in yellow 163 | print('\x1B[33m$message\x1B[0m'); 164 | } 165 | 166 | /// Asks the user a question and returns their response 167 | String prompt(String question, {String defaultValue = ''}) { 168 | // Print the question and default value if provided 169 | if (defaultValue.isNotEmpty) { 170 | print('$question (default: $defaultValue)'); 171 | } else { 172 | print('$question'); 173 | } 174 | 175 | // Explicitly flush stdout to ensure the prompt is displayed 176 | stdout.flush(); 177 | 178 | // Try to read input in a more robust way 179 | String? input; 180 | try { 181 | input = stdin.readLineSync(); 182 | } catch (e) { 183 | error('Error reading input: $e'); 184 | // Fall back to default value in case of error 185 | return defaultValue; 186 | } 187 | 188 | // Trim the input and handle null/empty cases 189 | final trimmedInput = input?.trim() ?? ''; 190 | 191 | // Return default value if user just presses Enter 192 | return trimmedInput.isEmpty ? defaultValue : trimmedInput; 193 | } 194 | 195 | /// Asks the user a yes/no question and returns a boolean 196 | bool confirm(String question, {bool defaultValue = false}) { 197 | final defaultText = defaultValue ? 'Y/n' : 'y/N'; 198 | stdout.write('$question [$defaultText] '); 199 | 200 | final input = stdin.readLineSync()?.trim().toLowerCase() ?? ''; 201 | 202 | if (input.isEmpty) { 203 | return defaultValue; 204 | } 205 | 206 | return input == 'y' || input == 'yes'; 207 | } 208 | 209 | /// Asks the user to select an option from a list 210 | String select(String question, List options, 211 | {String? defaultOption}) { 212 | info(question); 213 | 214 | for (int i = 0; i < options.length; i++) { 215 | final option = options[i]; 216 | final isDefault = option == defaultOption; 217 | final marker = isDefault ? '*' : ' '; 218 | 219 | print(' $marker ${i + 1}. $option'); 220 | } 221 | 222 | stdout.write('Enter your choice (1-${options.length}): '); 223 | final input = stdin.readLineSync()?.trim() ?? ''; 224 | 225 | // Try to parse as integer first 226 | try { 227 | final index = int.parse(input) - 1; 228 | if (index >= 0 && index < options.length) { 229 | return options[index]; 230 | } 231 | } catch (_) { 232 | // Not a number, check if it matches an option directly 233 | if (options.contains(input)) { 234 | return input; 235 | } 236 | } 237 | 238 | // Return default or first option if input is invalid 239 | return defaultOption ?? options.first; 240 | } 241 | 242 | /// Multi-select - allows user to select multiple options 243 | List multiSelect(String question, List options) { 244 | final selected = []; 245 | 246 | print('$question'); 247 | print( 248 | 'Enter the numbers of your choices (comma-separated) or "all" for all options:'); 249 | 250 | // Display options with numbers 251 | for (int i = 0; i < options.length; i++) { 252 | print(' ${i + 1}. ${options[i]}'); 253 | } 254 | 255 | print('\nYour selection (e.g. "1,3,4" or "all"): '); 256 | final input = stdin.readLineSync()?.trim().toLowerCase() ?? ''; 257 | 258 | if (input == 'all') { 259 | return List.from(options); 260 | } 261 | 262 | // Parse number inputs 263 | final selections = input.split(','); 264 | for (final selection in selections) { 265 | try { 266 | final index = int.parse(selection.trim()) - 1; 267 | if (index >= 0 && index < options.length) { 268 | selected.add(options[index]); 269 | } 270 | } catch (_) { 271 | // Skip invalid inputs 272 | } 273 | } 274 | 275 | return selected; 276 | } 277 | 278 | /// A simplified API wrapper function that doesn't require type parameter 279 | Future api(Future Function(ApiService) request) async { 280 | final service = ApiService(); 281 | try { 282 | return await request(service); 283 | } catch (e) { 284 | error('API Error: $e'); 285 | rethrow; 286 | } 287 | } 288 | 289 | /// sleep for a specified number of seconds 290 | /// [optional] microseconds 291 | Future sleep(int seconds, [int microseconds = 0]) async { 292 | await Future.delayed(Duration( 293 | seconds: seconds, 294 | microseconds: microseconds, 295 | )); 296 | } 297 | } 298 | 299 | /// A class that handles showing a spinner animation in the console 300 | class ConsoleSpinner { 301 | static const List _spinnerFrames = [ 302 | '⠋', 303 | '⠙', 304 | '⠹', 305 | '⠸', 306 | '⠼', 307 | '⠴', 308 | '⠦', 309 | '⠧', 310 | '⠇', 311 | '⠏' 312 | ]; 313 | 314 | Timer? _timer; 315 | String _message; 316 | bool _running = false; 317 | int _currentFrame = 0; 318 | String? _previousLine; 319 | 320 | ConsoleSpinner(this._message); 321 | 322 | /// Start the spinner with an optional message 323 | void start([String? message]) { 324 | if (_running) return; 325 | 326 | if (message != null) { 327 | _message = message; 328 | } 329 | 330 | _running = true; 331 | _currentFrame = 0; 332 | 333 | // Hide cursor 334 | stdout.write('\x1B[?25l'); 335 | 336 | _timer = Timer.periodic(Duration(milliseconds: 80), (_) { 337 | _clearPreviousLine(); 338 | final frame = _spinnerFrames[_currentFrame]; 339 | stdout.write('\r\x1B[36m$frame\x1B[0m $_message'); 340 | _previousLine = '\r\x1B[36m$frame\x1B[0m $_message'; 341 | _currentFrame = (_currentFrame + 1) % _spinnerFrames.length; 342 | }); 343 | } 344 | 345 | /// Update the spinner message 346 | void update(String message) { 347 | _message = message; 348 | } 349 | 350 | /// Stop the spinner with an optional completion message 351 | void stop({String? completionMessage, bool success = true}) { 352 | if (!_running) return; 353 | 354 | _timer?.cancel(); 355 | _timer = null; 356 | _running = false; 357 | 358 | _clearPreviousLine(); 359 | 360 | if (completionMessage != null) { 361 | final color = success ? '\x1B[32m' : '\x1B[31m'; 362 | final symbol = success ? '✓' : '✗'; 363 | stdout.write('\r$color$symbol\x1B[0m $completionMessage\n'); 364 | } 365 | 366 | // Show cursor again 367 | stdout.write('\x1B[?25h'); 368 | } 369 | 370 | void _clearPreviousLine() { 371 | if (_previousLine != null) { 372 | final length = _previousLine!.length; 373 | stdout.write('\r${' ' * length}\r'); 374 | } 375 | } 376 | } 377 | 378 | /// Extension method to add spinner functionality to NyCustomCommand 379 | extension SpinnerExtension on NyCustomCommand { 380 | /// Run a task with a spinner animation 381 | Future withSpinner({ 382 | required Future Function() task, 383 | required String message, 384 | String? successMessage, 385 | String? errorMessage, 386 | }) async { 387 | final spinner = ConsoleSpinner(message); 388 | spinner.start(); 389 | 390 | try { 391 | final result = await task(); 392 | spinner.stop( 393 | completionMessage: successMessage ?? '$message completed', 394 | success: true, 395 | ); 396 | return result; 397 | } catch (e) { 398 | spinner.stop( 399 | completionMessage: errorMessage ?? 'Failed: $e', 400 | success: false, 401 | ); 402 | rethrow; 403 | } 404 | } 405 | 406 | /// Create and return a spinner instance for manual control 407 | ConsoleSpinner createSpinner(String message) { 408 | return ConsoleSpinner(message); 409 | } 410 | } 411 | 412 | /// API Service that wraps Dio 413 | class ApiService { 414 | final Dio _dio; 415 | 416 | ApiService({Dio? dio}) : _dio = dio ?? Dio() { 417 | // Configure dio instance with defaults 418 | _dio.options.connectTimeout = Duration(seconds: 30); 419 | _dio.options.receiveTimeout = Duration(seconds: 30); 420 | _dio.options.responseType = ResponseType.json; 421 | 422 | // Add interceptors if needed 423 | _dio.interceptors.add(LogInterceptor(responseBody: true)); 424 | } 425 | 426 | /// GET request 427 | Future get( 428 | String path, { 429 | Map? queryParameters, 430 | Options? options, 431 | CancelToken? cancelToken, 432 | ProgressCallback? onReceiveProgress, 433 | }) async { 434 | try { 435 | final response = await _dio.get( 436 | path, 437 | queryParameters: queryParameters, 438 | options: options, 439 | cancelToken: cancelToken, 440 | onReceiveProgress: onReceiveProgress, 441 | ); 442 | return _handleResponse(response); 443 | } catch (e) { 444 | _handleError(e); 445 | return null; 446 | } 447 | } 448 | 449 | /// POST request 450 | Future post( 451 | String path, { 452 | dynamic data, 453 | Map? queryParameters, 454 | Options? options, 455 | CancelToken? cancelToken, 456 | ProgressCallback? onSendProgress, 457 | ProgressCallback? onReceiveProgress, 458 | }) async { 459 | try { 460 | final response = await _dio.post( 461 | path, 462 | data: data, 463 | queryParameters: queryParameters, 464 | options: options, 465 | cancelToken: cancelToken, 466 | onSendProgress: onSendProgress, 467 | onReceiveProgress: onReceiveProgress, 468 | ); 469 | return _handleResponse(response); 470 | } catch (e) { 471 | _handleError(e); 472 | return null; 473 | } 474 | } 475 | 476 | /// PUT request 477 | Future put( 478 | String path, { 479 | dynamic data, 480 | Map? queryParameters, 481 | Options? options, 482 | CancelToken? cancelToken, 483 | ProgressCallback? onSendProgress, 484 | ProgressCallback? onReceiveProgress, 485 | }) async { 486 | try { 487 | final response = await _dio.put( 488 | path, 489 | data: data, 490 | queryParameters: queryParameters, 491 | options: options, 492 | cancelToken: cancelToken, 493 | onSendProgress: onSendProgress, 494 | onReceiveProgress: onReceiveProgress, 495 | ); 496 | return _handleResponse(response); 497 | } catch (e) { 498 | _handleError(e); 499 | return null; 500 | } 501 | } 502 | 503 | /// DELETE request 504 | Future delete( 505 | String path, { 506 | dynamic data, 507 | Map? queryParameters, 508 | Options? options, 509 | CancelToken? cancelToken, 510 | }) async { 511 | try { 512 | final response = await _dio.delete( 513 | path, 514 | data: data, 515 | queryParameters: queryParameters, 516 | options: options, 517 | cancelToken: cancelToken, 518 | ); 519 | return _handleResponse(response); 520 | } catch (e) { 521 | _handleError(e); 522 | return null; 523 | } 524 | } 525 | 526 | /// PATCH request 527 | Future patch( 528 | String path, { 529 | dynamic data, 530 | Map? queryParameters, 531 | Options? options, 532 | CancelToken? cancelToken, 533 | ProgressCallback? onSendProgress, 534 | ProgressCallback? onReceiveProgress, 535 | }) async { 536 | try { 537 | final response = await _dio.patch( 538 | path, 539 | data: data, 540 | queryParameters: queryParameters, 541 | options: options, 542 | cancelToken: cancelToken, 543 | onSendProgress: onSendProgress, 544 | onReceiveProgress: onReceiveProgress, 545 | ); 546 | return _handleResponse(response); 547 | } catch (e) { 548 | _handleError(e); 549 | return null; 550 | } 551 | } 552 | 553 | /// Handle successful response 554 | T? _handleResponse(Response response) { 555 | if (response.statusCode! >= 200 && response.statusCode! < 300) { 556 | return response.data; 557 | } else { 558 | throw DioException( 559 | requestOptions: response.requestOptions, 560 | response: response, 561 | error: 'Server responded with status code: ${response.statusCode}', 562 | ); 563 | } 564 | } 565 | 566 | /// Handle errors 567 | void _handleError(dynamic error) { 568 | if (error is DioException) { 569 | // Handle Dio specific errors 570 | switch (error.type) { 571 | case DioExceptionType.connectionTimeout: 572 | case DioExceptionType.sendTimeout: 573 | case DioExceptionType.receiveTimeout: 574 | throw TimeoutException('Request timeout'); 575 | case DioExceptionType.badResponse: 576 | final statusCode = error.response?.statusCode; 577 | final data = error.response?.data; 578 | throw ApiException( 579 | code: statusCode ?? 0, 580 | message: 'Server error: $statusCode', 581 | data: data, 582 | ); 583 | case DioExceptionType.cancel: 584 | throw RequestCancelledException('Request was cancelled'); 585 | default: 586 | throw NetworkException('Network error occurred'); 587 | } 588 | } else { 589 | throw UnknownException('Unknown error: ${error.toString()}'); 590 | } 591 | } 592 | } 593 | 594 | /// Custom exception classes 595 | class ApiException implements Exception { 596 | final int code; 597 | final String message; 598 | final dynamic data; 599 | 600 | ApiException({required this.code, required this.message, this.data}); 601 | 602 | @override 603 | String toString() => 'ApiException: $code - $message'; 604 | } 605 | 606 | class TimeoutException implements Exception { 607 | final String message; 608 | TimeoutException(this.message); 609 | 610 | @override 611 | String toString() => 'TimeoutException: $message'; 612 | } 613 | 614 | class NetworkException implements Exception { 615 | final String message; 616 | NetworkException(this.message); 617 | 618 | @override 619 | String toString() => 'NetworkException: $message'; 620 | } 621 | 622 | class RequestCancelledException implements Exception { 623 | final String message; 624 | RequestCancelledException(this.message); 625 | 626 | @override 627 | String toString() => 'RequestCancelledException: $message'; 628 | } 629 | 630 | class UnknownException implements Exception { 631 | final String message; 632 | UnknownException(this.message); 633 | 634 | @override 635 | String toString() => 'UnknownException: $message'; 636 | } 637 | 638 | /// A fluent wrapper around ArgParser to make command definitions more readable 639 | class CommandBuilder { 640 | final ArgParser _parser = ArgParser(); 641 | final Map _defaults = {}; 642 | 643 | CommandBuilder() { 644 | // Add help flag by default 645 | addFlag('help', abbr: 'h', help: 'Show help information'); 646 | } 647 | 648 | /// Add an option (--option or -o) 649 | CommandBuilder addOption( 650 | String name, { 651 | String? abbr, 652 | String? help, 653 | List? allowed, 654 | String? defaultValue, 655 | }) { 656 | _parser.addOption( 657 | name, 658 | abbr: abbr, 659 | help: help, 660 | allowed: allowed, 661 | ); 662 | 663 | if (defaultValue != null) { 664 | _defaults[name] = defaultValue; 665 | } 666 | 667 | return this; 668 | } 669 | 670 | /// Add a flag (boolean option, --flag or -f) 671 | CommandBuilder addFlag( 672 | String name, { 673 | String? abbr, 674 | String? help, 675 | bool defaultValue = false, 676 | }) { 677 | _parser.addFlag( 678 | name, 679 | abbr: abbr, 680 | help: help, 681 | defaultsTo: defaultValue, 682 | ); 683 | 684 | _defaults[name] = defaultValue; 685 | 686 | return this; 687 | } 688 | 689 | /// Parse arguments and return a result object with convenient accessors 690 | CommandResult parse(List arguments) { 691 | final ArgResults results = _parser.parse(arguments); 692 | return CommandResult(results, _defaults); 693 | } 694 | 695 | /// Get the usage string for help text 696 | String get usage => _parser.usage; 697 | } 698 | 699 | /// Wrapper around ArgResults with convenient accessors 700 | class CommandResult { 701 | final ArgResults _results; 702 | final Map _defaults; 703 | 704 | CommandResult(this._results, this._defaults); 705 | 706 | /// Get a value with typed access, falling back to default if provided 707 | T? get(String name) { 708 | if (_results.wasParsed(name)) { 709 | return _results[name] as T?; 710 | } 711 | return _defaults.containsKey(name) ? _defaults[name] as T? : null; 712 | } 713 | 714 | /// Get a string value with a fallback 715 | String getString(String name, {String defaultValue = ''}) { 716 | return get(name) ?? defaultValue; 717 | } 718 | 719 | /// Get a boolean value with a fallback 720 | bool getBool(String name, {bool defaultValue = false}) { 721 | return get(name) ?? defaultValue; 722 | } 723 | 724 | /// Get an integer value with a fallback 725 | int getInt(String name, {int defaultValue = 0}) { 726 | return get(name) ?? defaultValue; 727 | } 728 | 729 | /// Get all the command line arguments 730 | List get arguments => _results.arguments; 731 | 732 | /// Get the rest arguments (unparsed) 733 | List get rest => _results.rest; 734 | } 735 | -------------------------------------------------------------------------------- /lib/metro/stubs/api_service_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create a new API Service. 4 | String apiServiceStub(ReCase rc, 5 | {required ReCase model, required String baseUrl}) => 6 | '''import 'package:flutter/material.dart'; 7 | import '/config/decoders.dart'; 8 | ${baseUrl == "getEnv('API_BASE_URL')" ? "import 'package:nylo_framework/nylo_framework.dart';" : ""}${model.originalText != 'Model' ? "\nimport '/app/models/${model.snakeCase}.dart';" : ""} 9 | 10 | class ${rc.pascalCase}ApiService extends NyApiService { 11 | ${rc.pascalCase}ApiService({BuildContext? buildContext}) : super(buildContext, decoders: modelDecoders); 12 | 13 | @override 14 | String get baseUrl => $baseUrl; 15 | 16 | ${model.originalText != "Model" ? ''' 17 | /// Return a list of ${model.pascalCase} 18 | Future?> fetchAll({dynamic query}) async { 19 | return await network>( 20 | request: (request) => request.get("/endpoint-path", queryParameters: query), 21 | ); 22 | } 23 | 24 | /// Find a ${model.pascalCase} 25 | Future<${model.pascalCase}?> find({required int id}) async { 26 | return await network<${model.pascalCase}>( 27 | request: (request) => request.get("/endpoint-path/\$id"), 28 | ); 29 | } 30 | 31 | /// Create a ${model.pascalCase} 32 | Future<${model.pascalCase}?> create({required dynamic data}) async { 33 | return await network<${model.pascalCase}>( 34 | request: (request) => request.post("/endpoint-path", data: data), 35 | ); 36 | } 37 | 38 | /// Update a ${model.pascalCase} 39 | Future<${model.pascalCase}?> update({dynamic query}) async { 40 | return await network<${model.pascalCase}>( 41 | request: (request) => request.put("/endpoint-path", queryParameters: query), 42 | ); 43 | } 44 | 45 | /// Delete a ${model.pascalCase} 46 | Future destroy({required int id}) async { 47 | return await network( 48 | request: (request) => request.delete("/endpoint-path/\$id"), 49 | ); 50 | }''' : ''' 51 | /// Example API Request 52 | Future fetchData() async { 53 | return await network( 54 | request: (request) => request.get("/endpoint-path"), 55 | ); 56 | }'''} 57 | } 58 | '''; 59 | -------------------------------------------------------------------------------- /lib/metro/stubs/config_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create a new Config. 4 | String configStub(ReCase configName) => ''' 5 | /* ${configName.titleCase} 6 | |-------------------------------------------------------------------------- 7 | | Learn more: https://nylo.dev/docs/6.x/configuration 8 | |-------------------------------------------------------------------------- */ 9 | 10 | // ... 11 | 12 | '''; 13 | -------------------------------------------------------------------------------- /lib/metro/stubs/controller_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create a new Controller. 4 | String controllerStub({required String controllerName}) => ''' 5 | import '/app/controllers/controller.dart'; 6 | import 'package:flutter/widgets.dart'; 7 | 8 | class ${controllerName.pascalCase}Controller extends Controller { 9 | 10 | @override 11 | construct(BuildContext context) { 12 | super.construct(context); 13 | 14 | } 15 | 16 | } 17 | '''; 18 | -------------------------------------------------------------------------------- /lib/metro/stubs/custom_command_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create a new Custom Command. 4 | String customCommandStub( 5 | {required ReCase customCommand, String category = 'app'}) => 6 | ''' 7 | import 'package:nylo_framework/metro/ny_cli.dart'; 8 | 9 | void main(arguments) => _${customCommand.pascalCase}Command(arguments).run(); 10 | 11 | /// ${customCommand.titleCase} Command 12 | /// 13 | /// Usage: 14 | /// [From Terminal] dart run nylo_framework:main ${category}:${customCommand.snakeCase} 15 | /// [With Metro] metro ${category}:${customCommand.snakeCase} 16 | class _${customCommand.pascalCase}Command extends NyCustomCommand { 17 | _${customCommand.pascalCase}Command(super.arguments); 18 | 19 | @override 20 | CommandBuilder builder(CommandBuilder command) { 21 | /// Example adding flags and options 22 | // command.addFlag('verbose', abbr: 'v', defaultValue: false); 23 | // command.addOption('version', defaultValue: '1.0.0'); 24 | 25 | return command; 26 | } 27 | 28 | @override 29 | Future handle(CommandResult result) async { 30 | // final version = result.getString('version'); 31 | // final verbose = result.getBool('verbose'); 32 | // 33 | // if (verbose) { 34 | // info('Verbose mode enabled'); 35 | // print('Command arguments: \${result.arguments}'); 36 | // } 37 | // 38 | // implement your command logic... 39 | } 40 | } 41 | '''; 42 | -------------------------------------------------------------------------------- /lib/metro/stubs/event_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create a new Event. 4 | String eventStub({required ReCase eventName}) => ''' 5 | import 'package:nylo_framework/nylo_framework.dart'; 6 | 7 | class ${eventName.pascalCase}Event implements NyEvent { 8 | 9 | @override 10 | final listeners = { 11 | DefaultListener: DefaultListener(), 12 | }; 13 | } 14 | 15 | class DefaultListener extends NyListener { 16 | 17 | @override 18 | handle(dynamic event) async { 19 | // Handle the event 20 | 21 | } 22 | } 23 | '''; 24 | -------------------------------------------------------------------------------- /lib/metro/stubs/form_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create NyFormData. 4 | String formStub(ReCase className) => ''' 5 | import 'package:nylo_framework/nylo_framework.dart'; 6 | import 'package:flutter/material.dart'; 7 | import '/resources/widgets/buttons/buttons.dart'; 8 | 9 | /* ${className.pascalCase} Form 10 | |-------------------------------------------------------------------------- 11 | | Usage: https://nylo.dev/docs/6.x/forms#how-it-works 12 | | Casts: https://nylo.dev/docs/6.x/forms#form-casts 13 | | Validation Rules: https://nylo.dev/docs/6.x/validation#validation-rules 14 | |-------------------------------------------------------------------------- */ 15 | 16 | class ${className.pascalCase}Form extends NyFormData { 17 | 18 | ${className.pascalCase}Form({String? name}) : super(name ?? "${className.snakeCase}"); 19 | 20 | // @override 21 | // get init => () { 22 | // /// Initial data for the form 23 | // return { 24 | // "name": "Anthony", 25 | // "price": "100", 26 | // "favourite_color": "Blue", 27 | // "bio": "I am a Flutter Developer" 28 | // }; 29 | // }; 30 | 31 | @override 32 | fields() => [ 33 | Field.text("Name", 34 | style: "compact" 35 | ), 36 | [ 37 | Field.currency("Price", 38 | currency: "usd", 39 | dummyData: "19.99", 40 | style: "compact", 41 | ), 42 | Field.picker("Favourite Color", 43 | options: [ 44 | "Red", 45 | "Blue", 46 | "Green" 47 | ], 48 | validate: FormValidator.contains(["Red","Blue","Green"]), 49 | style: "compact", 50 | ), 51 | ], 52 | Field.textArea("Bio", 53 | style: "compact" 54 | ), 55 | ]; 56 | 57 | // @override 58 | // Widget? get submitButton => Button.primary(text: "Submit", submitForm: (this, (data) { 59 | // print(['data', data]); 60 | // })); 61 | } 62 | '''; 63 | -------------------------------------------------------------------------------- /lib/metro/stubs/interceptor_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create an Interceptor. 4 | String interceptorStub({required ReCase interceptorName}) => ''' 5 | import 'package:nylo_framework/nylo_framework.dart'; 6 | 7 | class ${interceptorName.pascalCase}Interceptor extends Interceptor { 8 | @override 9 | void onRequest(RequestOptions options, RequestInterceptorHandler handler) { 10 | return super.onRequest(options, handler); 11 | } 12 | 13 | @override 14 | void onResponse(Response response, ResponseInterceptorHandler handler) { 15 | handler.next(response); 16 | } 17 | 18 | @override 19 | void onError(DioException err, ErrorInterceptorHandler handler) { 20 | handler.next(err); 21 | } 22 | } 23 | '''; 24 | -------------------------------------------------------------------------------- /lib/metro/stubs/model_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create a new Model. 4 | String modelStub({required ReCase modelName}) => ''' 5 | import 'package:nylo_framework/nylo_framework.dart'; 6 | 7 | class ${modelName.pascalCase} extends Model { 8 | 9 | static StorageKey key = "${modelName.snakeCase}"; 10 | 11 | ${modelName.pascalCase}() : super(key: key); 12 | 13 | ${modelName.pascalCase}.fromJson(data) : super(key: key) { 14 | 15 | } 16 | 17 | @override 18 | toJson() { 19 | return {}; 20 | } 21 | } 22 | '''; 23 | -------------------------------------------------------------------------------- /lib/metro/stubs/navigation_hub_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create a navigation hub widget 4 | String navigationHubStub(ReCase rc) => ''' 5 | import 'package:flutter/material.dart'; 6 | import 'package:nylo_framework/nylo_framework.dart'; 7 | 8 | class ${rc.pascalCase}NavigationHub extends NyStatefulWidget with BottomNavPageControls { 9 | static RouteView path = ("/${rc.paramCase}", (_) => ${rc.pascalCase}NavigationHub()); 10 | 11 | ${rc.pascalCase}NavigationHub() 12 | : super( 13 | child: () => _${rc.pascalCase}NavigationHubState(), 14 | stateName: path.stateName()); 15 | 16 | /// State actions 17 | static NavigationHubStateActions stateActions = NavigationHubStateActions(path.stateName()); 18 | } 19 | 20 | class _${rc.pascalCase}NavigationHubState extends NavigationHub<${rc.pascalCase}NavigationHub> { 21 | 22 | /// Layouts: 23 | /// - [NavigationHubLayout.bottomNav] Bottom navigation 24 | /// - [NavigationHubLayout.topNav] Top navigation 25 | /// - [NavigationHubLayout.journey] Journey navigation 26 | NavigationHubLayout? layout = NavigationHubLayout.bottomNav( 27 | // backgroundColor: Colors.white, 28 | ); 29 | 30 | /// Should the state be maintained 31 | @override 32 | bool get maintainState => true; 33 | 34 | /// Navigation pages 35 | _${rc.pascalCase}NavigationHubState() : super(() async { 36 | /// * Creating Navigation Tabs 37 | /// [Navigation Tabs] 'dart run nylo_framework:main make:stateful_widget home_tab,settings_tab' 38 | /// [Journey States] 'dart run nylo_framework:main make:journey_widget welcome_tab,users_dob,users_info --parent=${rc.pascalCase}' 39 | return { 40 | // 0: NavigationTab( 41 | // title: "Home", 42 | // // page: HomeTab(), 43 | // icon: Icon(Icons.home), 44 | // activeIcon: Icon(Icons.home), 45 | // ), 46 | // 1: NavigationTab( 47 | // title: "Settings", 48 | // // page: SettingsTab(), 49 | // icon: Icon(Icons.settings), 50 | // activeIcon: Icon(Icons.settings), 51 | // ), 52 | }; 53 | }); 54 | 55 | /// Handle the tap event 56 | @override 57 | onTap(int index) { 58 | super.onTap(index); 59 | } 60 | } 61 | '''; 62 | -------------------------------------------------------------------------------- /lib/metro/stubs/navigation_tab_state_journey.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create a navigation tab Journey State widget 4 | String navigationTabJourneyStateStub(ReCase rc, 5 | {required ReCase parentNavigationHub}) => 6 | ''' 7 | import 'package:flutter/material.dart'; 8 | import '/resources/pages/${parentNavigationHub.snakeCase}_navigation_hub.dart'; 9 | import '/resources/widgets/buttons/buttons.dart'; 10 | import 'package:nylo_framework/nylo_framework.dart'; 11 | 12 | class ${rc.pascalCase} extends StatefulWidget { 13 | const ${rc.pascalCase}({super.key}); 14 | 15 | @override 16 | createState() => _${rc.pascalCase}State(); 17 | } 18 | 19 | class _${rc.pascalCase}State extends JourneyState<${rc.pascalCase}> { 20 | _${rc.pascalCase}State() : super( 21 | navigationHubState: ${parentNavigationHub.pascalCase}NavigationHub.path.stateName()); 22 | 23 | @override 24 | get init => () { 25 | // Your initialization logic here 26 | }; 27 | 28 | @override 29 | Widget view(BuildContext context) { 30 | return buildJourneyContent( 31 | content: Column( 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | children: [ 34 | Text('${rc.pascalCase}', style: Theme.of(context).textTheme.headlineMedium), 35 | const SizedBox(height: 20), 36 | Text('This onboarding journey will help you get started.'), 37 | ], 38 | ), 39 | nextButton: Button.primary( 40 | text: isLastStep ? "Get Started" : "Continue", 41 | onPressed: onNextPressed, 42 | ), 43 | backButton: isFirstStep ? null : Button.textOnly( 44 | text: "Back", 45 | textColor: Colors.black87, 46 | onPressed: onBackPressed, 47 | ), 48 | ); 49 | } 50 | 51 | /// Check if the journey can continue to the next step 52 | /// Override this method to add validation logic 53 | @override 54 | Future canContinue() async { 55 | // Perform your validation logic here 56 | // Return true if the journey can continue, false otherwise 57 | return true; 58 | } 59 | 60 | /// Called when unable to continue (canContinue returns false) 61 | /// Override this method to handle validation failures 62 | @override 63 | Future onCannotContinue() async { 64 | showToastSorry(description: "You cannot continue"); 65 | } 66 | 67 | /// Called before navigating to the next step 68 | /// Override this method to perform actions before continuing 69 | @override 70 | Future onBeforeNext() async { 71 | // E.g. save data to session 72 | // session('onboarding', { 73 | // 'name': 'Anthony Gordon', 74 | // 'occupation': 'Software Engineer', 75 | // }); 76 | // 77 | // final sessionData = session('onboarding').data(); // {'name': 'Anthony Gordon', 'occupation': 'Software Engineer'} 78 | // printInfo(sessionData); 79 | 80 | // access the session data from other NavigationTabs 81 | } 82 | 83 | /// Called after navigating to the next step 84 | /// Override this method to perform actions after continuing 85 | @override 86 | Future onAfterNext() async { 87 | print('Navigated to the next step'); 88 | } 89 | 90 | /// Called when the journey is complete (at the last step) 91 | /// Override this method to perform completion actions 92 | @override 93 | Future onComplete() async {} 94 | } 95 | '''; 96 | -------------------------------------------------------------------------------- /lib/metro/stubs/network_method_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// Creates a network method 4 | String networkMethodStub({ 5 | required String methodName, 6 | Map queryParams = const {}, 7 | Map headerParams = const {}, 8 | Map dataParams = const {}, 9 | Map pathParams = const {}, 10 | String? model, 11 | bool isList = false, 12 | String path = "", 13 | required String method, 14 | String? urlFullPath, 15 | }) => 16 | ''' 17 | //$method\n${urlFullPath != null ? ' /// $urlFullPath' : ''} 18 | Future<${_getType(model, isList: isList, isOptional: true)}> $methodName(${_mapParams(queryParams, dataParams, pathParams)}) async => await network${_getType(model, isList: isList, returnDynamic: false, addBrackets: true)}( 19 | ${_callBackType(headers: headerParams, method: method, path: path, queryParams: queryParams, dataParams: dataParams, pathParams: pathParams)} 20 | ${urlFullPath != null ? 'baseUrl: "${Uri.tryParse(urlFullPath)?.origin}"' : ''} 21 | ); 22 | '''; 23 | 24 | String _getType(String? model, 25 | {bool returnDynamic = true, 26 | bool isOptional = false, 27 | bool isList = false, 28 | bool addBrackets = false}) { 29 | if (model != null) { 30 | String type = model; 31 | String optional = isOptional ? '?' : ''; 32 | if (addBrackets) { 33 | if (isList) { 34 | return "$optional>"; 35 | } 36 | return '<$type$optional>'; 37 | } 38 | if (isList) { 39 | return "List<$type>$optional"; 40 | } 41 | return type + optional; 42 | } 43 | if (returnDynamic) { 44 | return 'dynamic'; 45 | } 46 | return ''; 47 | } 48 | 49 | String _mapDataParams(Map dataParams, 50 | {bool isOptional = false}) => 51 | dataParams.entries 52 | .map((e) => '"${e.key}${isOptional ? '?' : ''}": ${e.key.camelCase}') 53 | .toList() 54 | .join(", "); 55 | 56 | String _mapParams(Map queryParams, 57 | Map dataParams, Map pathParams) { 58 | Map params = {}; 59 | if (queryParams.isNotEmpty) { 60 | params.addAll(queryParams); 61 | } 62 | if (dataParams.isNotEmpty) { 63 | params.addAll(dataParams); 64 | } 65 | if (pathParams.isNotEmpty) { 66 | params.addAll(pathParams); 67 | } 68 | if (params.entries.isEmpty) { 69 | return ''; 70 | } 71 | // ignore: prefer_interpolation_to_compose_strings 72 | return "{" + 73 | params.entries 74 | .map((e) { 75 | String type = e.value.runtimeType.toString(); 76 | if (type == '_InternalLinkedHashMap') { 77 | type = 'Map'; 78 | } 79 | if (type == "_Map") { 80 | type = 'Map'; 81 | } 82 | return "$type? ${ReCase(e.key).camelCase}"; 83 | }) 84 | .toList() 85 | .join(", ") + 86 | "}"; 87 | } 88 | 89 | String _callBackType({ 90 | required Map headers, 91 | required String method, 92 | required String path, 93 | required Map queryParams, 94 | required Map dataParams, 95 | required Map pathParams, 96 | }) { 97 | if (headers.isEmpty) { 98 | return "request: (request) => ${_requestType(method, path, queryParams, dataParams, pathParams)}"; 99 | } 100 | return '''request: (request) { 101 | request.options.headers.addAll({ 102 | ${headers.entries.map((e) => "\"${e.key}\": '${e.value}'").toList().join(", ")} 103 | }); 104 | return ${_requestType(method, path, queryParams, dataParams, pathParams).substring(0, _requestType(method, path, queryParams, dataParams, pathParams).length - 1)}; 105 | },'''; 106 | } 107 | 108 | String _requestType( 109 | String method, 110 | String path, 111 | Map queryParams, 112 | Map dataParams, 113 | Map pathParams) { 114 | RegExp regExp = RegExp(r':([\w_$&+,:;=?@#!]+)'); 115 | path = path.replaceAllMapped(regExp, (match) { 116 | String key = match.group(1) ?? ""; 117 | if (key == "") return ""; 118 | 119 | if (!pathParams.containsKey(key)) { 120 | return match.group(0) ?? ""; 121 | } 122 | return "\$${key.camelCase}"; 123 | }); 124 | 125 | if (method.toLowerCase() == 'get') { 126 | queryParams.addAll(dataParams); 127 | } 128 | return ''' 129 | request.${method.toLowerCase()}("$path"${queryParams.isNotEmpty ? ', queryParameters: {${_mapDataParams(queryParams)}}' : ''}${method.toLowerCase() != 'get' && dataParams.isNotEmpty ? ', data: {${_mapDataParams(dataParams)}}' : ''}),'''; 130 | } 131 | -------------------------------------------------------------------------------- /lib/metro/stubs/page_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create a new Page. 4 | String pageStub({required String className}) => ''' 5 | import 'package:flutter/material.dart'; 6 | import 'package:nylo_framework/nylo_framework.dart'; 7 | 8 | class ${className.pascalCase}Page extends NyStatefulWidget { 9 | 10 | static RouteView path = ("/${className.paramCase}", (_) => ${className.pascalCase}Page()); 11 | 12 | ${className.pascalCase}Page({super.key}) : super(child: () => _${className.pascalCase}PageState()); 13 | } 14 | 15 | class _${className.pascalCase}PageState extends NyPage<${className.pascalCase}Page> { 16 | 17 | @override 18 | get init => () { 19 | 20 | }; 21 | 22 | @override 23 | Widget view(BuildContext context) { 24 | return Scaffold( 25 | appBar: AppBar( 26 | title: Text("${className.titleCase}") 27 | ), 28 | body: SafeArea( 29 | child: Container(), 30 | ), 31 | ); 32 | } 33 | } 34 | '''; 35 | -------------------------------------------------------------------------------- /lib/metro/stubs/page_w_controller_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create a new Page + Controller. 4 | String pageWithControllerStub( 5 | {required String className, String? creationPath}) => 6 | ''' 7 | import 'package:flutter/material.dart'; 8 | import 'package:nylo_framework/nylo_framework.dart'; 9 | import '/app/controllers/${creationPath != null ? "$creationPath/${className.snakeCase}" : className.snakeCase}_controller.dart'; 10 | 11 | class ${className.pascalCase}Page extends NyStatefulWidget<${className.pascalCase}Controller> { 12 | static RouteView path = ("/${className.paramCase}", (_) => ${className.pascalCase}Page()); 13 | 14 | ${className.pascalCase}Page({super.key}) : super(child: () => _${className.pascalCase}PageState()); 15 | } 16 | 17 | class _${className.pascalCase}PageState extends NyPage<${className.pascalCase}Page> { 18 | 19 | /// [${className.pascalCase}Controller] controller 20 | ${className.pascalCase}Controller get controller => widget.controller; 21 | 22 | @override 23 | get init => () { 24 | 25 | }; 26 | 27 | @override 28 | Widget view(BuildContext context) { 29 | return Scaffold( 30 | appBar: AppBar( 31 | title: const Text("${className.titleCase}") 32 | ), 33 | body: SafeArea( 34 | child: Container(), 35 | ), 36 | ); 37 | } 38 | } 39 | '''; 40 | -------------------------------------------------------------------------------- /lib/metro/stubs/postman_api_service_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create an API Service class in the /app/networking/ directory. 4 | String postmanApiServiceStub(ReCase rc, 5 | {required String networkMethods, 6 | required, 7 | required String imports, 8 | required String baseUrl}) => 9 | '''import 'package:flutter/material.dart'; 10 | import 'package:nylo_framework/nylo_framework.dart'; 11 | import '/config/decoders.dart';${imports != "" ? '\n$imports' : ''} 12 | 13 | class ${rc.pascalCase}ApiService extends NyApiService { 14 | ${rc.pascalCase}ApiService({BuildContext? buildContext}) : super(buildContext, decoders: modelDecoders); 15 | 16 | @override 17 | String get baseUrl => ""; 18 | 19 | $networkMethods 20 | } 21 | '''; 22 | -------------------------------------------------------------------------------- /lib/metro/stubs/provider_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create a Provider class in the /app/providers/ directory. 4 | String providerStub(ReCase rc) => ''' 5 | import 'package:nylo_framework/nylo_framework.dart'; 6 | 7 | class ${rc.pascalCase}Provider implements NyProvider { 8 | 9 | @override 10 | boot(Nylo nylo) async { 11 | 12 | // boot your provider 13 | // ... 14 | 15 | return nylo; 16 | } 17 | 18 | @override 19 | afterBoot(Nylo nylo) async { 20 | 21 | // Called after Nylo has finished booting 22 | // ... 23 | } 24 | } 25 | '''; 26 | -------------------------------------------------------------------------------- /lib/metro/stubs/route_guard_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create a Route Guard class in the /routes/guards/ directory. 4 | String routeGuardStub(ReCase rc) => ''' 5 | import 'package:nylo_framework/nylo_framework.dart'; 6 | 7 | /* ${rc.pascalCase} Route Guard 8 | |-------------------------------------------------------------------------- */ 9 | 10 | class ${rc.pascalCase}RouteGuard extends NyRouteGuard { 11 | ${rc.pascalCase}RouteGuard(); 12 | 13 | @override 14 | onRequest(PageRequest pageRequest) async { 15 | // example 16 | // if ((await Auth.isAuthenticated()) == false) { 17 | // return redirect(HomePage.path); 18 | // } 19 | // 20 | // helpers 21 | // data = will give you access to the data passed to the route 22 | // context = will give you access to the BuildContext 23 | return pageRequest; 24 | } 25 | } 26 | '''; 27 | -------------------------------------------------------------------------------- /lib/metro/stubs/theme_colors_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create ColorStyles in the /resources/themes/styles/ directory. 4 | String themeColorsStub(ReCase rc) => ''' 5 | import 'package:flutter/material.dart'; 6 | import '/resources/themes/styles/color_styles.dart'; 7 | 8 | /* ${rc.titleCase} Theme Colors 9 | |-------------------------------------------------------------------------- */ 10 | 11 | class ${rc.pascalCase}ThemeColors implements ColorStyles { 12 | // general 13 | @override 14 | Color get background => const Color(0xFFFFFFFF); 15 | 16 | @override 17 | Color get content => const Color(0xFF000000); 18 | @override 19 | Color get primaryAccent => const Color(0xFF0045a0); 20 | 21 | @override 22 | Color get surfaceBackground => Colors.white; 23 | @override 24 | Color get surfaceContent => Colors.black; 25 | 26 | // app bar 27 | @override 28 | Color get appBarBackground => Colors.blue; 29 | @override 30 | Color get appBarPrimaryContent => Colors.white; 31 | 32 | // buttons 33 | @override 34 | Color get buttonBackground => Colors.blue; 35 | @override 36 | Color get buttonContent => Colors.white; 37 | 38 | @override 39 | Color get buttonSecondaryBackground => const Color(0xff151925); 40 | @override 41 | Color get buttonSecondaryContent => Colors.white.withOpacity(0.9); 42 | 43 | // bottom tab bar 44 | @override 45 | Color get bottomTabBarBackground => Colors.white; 46 | 47 | // bottom tab bar - icons 48 | @override 49 | Color get bottomTabBarIconSelected => Colors.blue; 50 | @override 51 | Color get bottomTabBarIconUnselected => Colors.black54; 52 | 53 | // bottom tab bar - label 54 | @override 55 | Color get bottomTabBarLabelUnselected => Colors.black45; 56 | @override 57 | Color get bottomTabBarLabelSelected => Colors.black; 58 | 59 | // toast notification 60 | Color get toastNotificationBackground => Colors.white; 61 | } 62 | '''; 63 | -------------------------------------------------------------------------------- /lib/metro/stubs/theme_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create a new Theme in the /resources/themes/ directory. 4 | String themeStub(ReCase rc, {bool isDark = false}) => ''' 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/services.dart'; 7 | import '/config/design.dart'; 8 | import '/resources/themes/styles/color_styles.dart'; 9 | import '/resources/themes/text_theme/default_text_theme.dart'; 10 | import 'package:nylo_framework/nylo_framework.dart'; 11 | 12 | /* ${rc.titleCase} Theme 13 | |-------------------------------------------------------------------------- 14 | | Theme Config - config/app_theme.dart 15 | |-------------------------------------------------------------------------- */ 16 | 17 | ThemeData ${rc.camelCase}Theme(ColorStyles color) { 18 | TextTheme ${rc.camelCase}Theme = getAppTextTheme( 19 | appFont, defaultTextTheme.merge(_textTheme(color))); 20 | 21 | return ThemeData( 22 | useMaterial3: true, 23 | primaryColor: color.content, 24 | primaryColorLight: color.primaryAccent, 25 | focusColor: color.content, 26 | scaffoldBackgroundColor: color.background, 27 | hintColor: color.primaryAccent, 28 | dividerTheme: DividerThemeData(color: Colors.grey[100]), 29 | appBarTheme: AppBarTheme( 30 | surfaceTintColor: Colors.transparent, 31 | backgroundColor: color.appBarBackground, 32 | titleTextStyle: 33 | ${rc.camelCase}Theme.titleLarge!.copyWith(color: color.appBarPrimaryContent), 34 | iconTheme: IconThemeData(color: color.appBarPrimaryContent), 35 | elevation: 1.0, 36 | systemOverlayStyle: SystemUiOverlayStyle.dark, 37 | ), 38 | buttonTheme: ButtonThemeData( 39 | buttonColor: color.buttonContent, 40 | colorScheme: ColorScheme.light(primary: color.buttonBackground), 41 | ), 42 | textButtonTheme: TextButtonThemeData( 43 | style: TextButton.styleFrom(foregroundColor: color.content), 44 | ), 45 | elevatedButtonTheme: ElevatedButtonThemeData( 46 | style: TextButton.styleFrom( 47 | foregroundColor: color.buttonContent, 48 | backgroundColor: color.buttonBackground), 49 | ), 50 | bottomNavigationBarTheme: BottomNavigationBarThemeData( 51 | backgroundColor: color.bottomTabBarBackground, 52 | unselectedIconTheme: 53 | IconThemeData(color: color.bottomTabBarIconUnselected), 54 | selectedIconTheme: IconThemeData(color: color.bottomTabBarIconSelected), 55 | unselectedLabelStyle: TextStyle(color: color.bottomTabBarLabelUnselected), 56 | selectedLabelStyle: TextStyle(color: color.bottomTabBarLabelSelected), 57 | selectedItemColor: color.bottomTabBarLabelSelected, 58 | ), 59 | textTheme: ${rc.camelCase}Theme, 60 | colorScheme: ColorScheme.light( 61 | surface: color.background, 62 | onSecondary: Colors.white, 63 | primary: color.primaryAccent, 64 | ), 65 | ); 66 | } 67 | 68 | /* ${rc.titleCase} Text Theme 69 | |-------------------------------------------------------------------------- */ 70 | 71 | TextTheme _textTheme(ColorStyles colors) { 72 | TextTheme textTheme = const TextTheme().apply(displayColor: colors.content); 73 | return textTheme.copyWith( 74 | labelLarge: TextStyle(color: colors.content.withOpacity(0.8))); 75 | } 76 | '''; 77 | -------------------------------------------------------------------------------- /lib/metro/stubs/widget_state_managed_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create a State Managed Widget in the /resources/widgets/ directory. 4 | String widgetStateManagedStub(ReCase rc) => ''' 5 | import 'package:flutter/material.dart'; 6 | import 'package:nylo_framework/nylo_framework.dart'; 7 | 8 | class ${rc.pascalCase} extends StatefulWidget { 9 | 10 | const ${rc.pascalCase}({super.key}); 11 | 12 | static String state = "${rc.snakeCase}"; 13 | 14 | @override 15 | createState() => _${rc.pascalCase}State(); 16 | } 17 | 18 | class _${rc.pascalCase}State extends NyState<${rc.pascalCase}> { 19 | 20 | _${rc.pascalCase}State() { 21 | stateName = ${rc.pascalCase}.state; 22 | } 23 | 24 | @override 25 | get init => () async { 26 | // 'stateData' will contain the current state data 27 | }; 28 | 29 | @override 30 | stateUpdated(dynamic data) async { 31 | // e.g. to update this state from another class 32 | // updateState(${rc.pascalCase}.state, data: "example payload"); 33 | } 34 | 35 | // @override 36 | // Map get stateActions => { 37 | // "clear_data": () { 38 | // ... 39 | // 40 | // // Example how to invoke this action from another widget or class 41 | // // stateAction("clear_data", state: ${rc.pascalCase}.state); 42 | // }, 43 | // }; 44 | 45 | @override 46 | Widget view(BuildContext context) { 47 | return Container( 48 | 49 | ); 50 | } 51 | } 52 | '''; 53 | -------------------------------------------------------------------------------- /lib/metro/stubs/widget_stateful_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create a Stateful Widget in the /resources/widgets/ directory. 4 | String widgetStatefulStub(ReCase rc) => ''' 5 | import 'package:flutter/material.dart'; 6 | import 'package:nylo_framework/nylo_framework.dart'; 7 | 8 | class ${rc.pascalCase} extends StatefulWidget { 9 | 10 | const ${rc.pascalCase}({super.key}); 11 | 12 | @override 13 | createState() => _${rc.pascalCase}State(); 14 | } 15 | 16 | class _${rc.pascalCase}State extends NyState<${rc.pascalCase}> { 17 | 18 | @override 19 | get init => () { 20 | 21 | }; 22 | 23 | @override 24 | Widget view(BuildContext context) { 25 | return Container( 26 | 27 | ); 28 | } 29 | } 30 | '''; 31 | -------------------------------------------------------------------------------- /lib/metro/stubs/widget_stateless_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:recase/recase.dart'; 2 | 3 | /// This stub is used to create a Stateless Widget in the /resources/widgets/ directory. 4 | String widgetStatelessStub(ReCase rc) => ''' 5 | import 'package:flutter/material.dart'; 6 | 7 | class ${rc.pascalCase} extends StatelessWidget { 8 | const ${rc.pascalCase}({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | 14 | ); 15 | } 16 | } 17 | '''; 18 | -------------------------------------------------------------------------------- /lib/nylo_framework.dart: -------------------------------------------------------------------------------- 1 | library nylo_framework; 2 | 3 | export 'package:nylo_support/networking/ny_networking.dart'; 4 | export 'package:nylo_support/router/ny_router.dart'; 5 | export 'package:nylo_support/helpers/ny_helpers.dart'; 6 | export 'package:nylo_support/alerts/ny_alerts.dart'; 7 | export 'package:nylo_support/widgets/ny_widgets.dart'; 8 | 9 | export 'package:nylo_support/controllers/controller.dart'; 10 | export 'package:nylo_support/controllers/ny_controller.dart'; 11 | export 'package:nylo_support/localization/app_localization.dart'; 12 | export 'package:nylo_support/local_storage/local_storage.dart'; 13 | export 'package:nylo_support/themes/base_color_styles.dart'; 14 | export 'package:nylo_support/themes/base_theme_config.dart'; 15 | export 'package:nylo_support/providers/providers.dart'; 16 | export 'package:nylo_support/local_notifications/local_notifications.dart'; 17 | export 'package:nylo_support/events/events.dart'; 18 | export 'package:nylo_support/validation/ny_validator.dart'; 19 | export 'package:nylo_support/validation/rules.dart'; 20 | export 'package:nylo_support/forms/ny_login_form.dart'; 21 | export 'package:nylo_support/nylo.dart'; 22 | 23 | // Packages 24 | export 'package:theme_provider/theme_provider.dart'; 25 | export 'package:error_stack/error_stack.dart'; 26 | export 'package:skeletonizer/skeletonizer.dart'; 27 | export 'package:flutter_secure_storage/flutter_secure_storage.dart'; 28 | export 'package:date_field/date_field.dart'; 29 | export 'package:dio/dio.dart'; 30 | 31 | /// Nylo version 32 | const String nyloVersion = 'v6.8.7'; 33 | -------------------------------------------------------------------------------- /lib/theme/helper/ny_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:theme_provider/theme_provider.dart'; 3 | 4 | /// Class to help manage current theme in the app. 5 | class NyTheme { 6 | /// Changes the current theme to the new [theme] 7 | /// standard light [themeName] (id is "light_theme") 8 | /// standard dark [themeName] (id is "dark_theme") 9 | static set(BuildContext context, {required String id}) { 10 | ThemeProvider.controllerOf(context).setTheme(id); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | animate_do: 5 | dependency: transitive 6 | description: 7 | name: animate_do 8 | sha256: e5c8b92e8495cba5adfff17c0b017d50f46b2766226e9faaf68bc08c91aef034 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "4.2.0" 12 | app_badge_plus: 13 | dependency: transitive 14 | description: 15 | name: app_badge_plus 16 | sha256: cbbb03cdac77c89c1494534fc2397e7f54deb84d2f968af9f8caaecbc54e86e7 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "1.2.3" 20 | args: 21 | dependency: "direct main" 22 | description: 23 | name: args 24 | sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "2.7.0" 28 | async: 29 | dependency: transitive 30 | description: 31 | name: async 32 | sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "2.13.0" 36 | base58check: 37 | dependency: transitive 38 | description: 39 | name: base58check 40 | sha256: "6c300dfc33e598d2fe26319e13f6243fea81eaf8204cb4c6b69ef20a625319a5" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "2.0.0" 44 | bech32: 45 | dependency: transitive 46 | description: 47 | name: bech32 48 | sha256: "156cbace936f7720c79a79d16a03efad343b1ef17106716e04b8b8e39f99f7f7" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "0.2.2" 52 | boolean_selector: 53 | dependency: transitive 54 | description: 55 | name: boolean_selector 56 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "2.1.2" 60 | characters: 61 | dependency: transitive 62 | description: 63 | name: characters 64 | sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "1.4.0" 68 | clock: 69 | dependency: transitive 70 | description: 71 | name: clock 72 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "1.1.2" 76 | collection: 77 | dependency: "direct main" 78 | description: 79 | name: collection 80 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "1.19.1" 84 | convert: 85 | dependency: transitive 86 | description: 87 | name: convert 88 | sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "3.1.2" 92 | crypto: 93 | dependency: transitive 94 | description: 95 | name: crypto 96 | sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "3.0.6" 100 | date_field: 101 | dependency: "direct main" 102 | description: 103 | name: date_field 104 | sha256: "5714b1ac0ba1c860877b3040601a6fd20cb846042c9ae6d8216ffede422d43d1" 105 | url: "https://pub.dev" 106 | source: hosted 107 | version: "6.0.3+1" 108 | dbus: 109 | dependency: transitive 110 | description: 111 | name: dbus 112 | sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "0.7.11" 116 | dio: 117 | dependency: "direct main" 118 | description: 119 | name: dio 120 | sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" 121 | url: "https://pub.dev" 122 | source: hosted 123 | version: "5.8.0+1" 124 | dio_web_adapter: 125 | dependency: transitive 126 | description: 127 | name: dio_web_adapter 128 | sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" 129 | url: "https://pub.dev" 130 | source: hosted 131 | version: "2.1.1" 132 | equatable: 133 | dependency: transitive 134 | description: 135 | name: equatable 136 | sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" 137 | url: "https://pub.dev" 138 | source: hosted 139 | version: "2.0.7" 140 | error_stack: 141 | dependency: "direct main" 142 | description: 143 | name: error_stack 144 | sha256: ff50365afc48969e9ba3c5aab032c67d6d282be7e4a8eea9bf7fbd81d2414cc8 145 | url: "https://pub.dev" 146 | source: hosted 147 | version: "1.10.3" 148 | fake_async: 149 | dependency: transitive 150 | description: 151 | name: fake_async 152 | sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" 153 | url: "https://pub.dev" 154 | source: hosted 155 | version: "1.3.3" 156 | ffi: 157 | dependency: transitive 158 | description: 159 | name: ffi 160 | sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" 161 | url: "https://pub.dev" 162 | source: hosted 163 | version: "2.1.4" 164 | file: 165 | dependency: transitive 166 | description: 167 | name: file 168 | sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 169 | url: "https://pub.dev" 170 | source: hosted 171 | version: "7.0.1" 172 | fixnum: 173 | dependency: transitive 174 | description: 175 | name: fixnum 176 | sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be 177 | url: "https://pub.dev" 178 | source: hosted 179 | version: "1.1.1" 180 | flutter: 181 | dependency: "direct main" 182 | description: flutter 183 | source: sdk 184 | version: "0.0.0" 185 | flutter_dotenv: 186 | dependency: "direct main" 187 | description: 188 | name: flutter_dotenv 189 | sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b 190 | url: "https://pub.dev" 191 | source: hosted 192 | version: "5.2.1" 193 | flutter_lints: 194 | dependency: "direct dev" 195 | description: 196 | name: flutter_lints 197 | sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" 198 | url: "https://pub.dev" 199 | source: hosted 200 | version: "5.0.0" 201 | flutter_local_notifications: 202 | dependency: transitive 203 | description: 204 | name: flutter_local_notifications 205 | sha256: b94a50aabbe56ef254f95f3be75640f99120429f0a153b2dc30143cffc9bfdf3 206 | url: "https://pub.dev" 207 | source: hosted 208 | version: "19.2.1" 209 | flutter_local_notifications_linux: 210 | dependency: transitive 211 | description: 212 | name: flutter_local_notifications_linux 213 | sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5 214 | url: "https://pub.dev" 215 | source: hosted 216 | version: "6.0.0" 217 | flutter_local_notifications_platform_interface: 218 | dependency: transitive 219 | description: 220 | name: flutter_local_notifications_platform_interface 221 | sha256: "2569b973fc9d1f63a37410a9f7c1c552081226c597190cb359ef5d5762d1631c" 222 | url: "https://pub.dev" 223 | source: hosted 224 | version: "9.0.0" 225 | flutter_local_notifications_windows: 226 | dependency: transitive 227 | description: 228 | name: flutter_local_notifications_windows 229 | sha256: f8fc0652a601f83419d623c85723a3e82ad81f92b33eaa9bcc21ea1b94773e6e 230 | url: "https://pub.dev" 231 | source: hosted 232 | version: "1.0.0" 233 | flutter_localizations: 234 | dependency: transitive 235 | description: flutter 236 | source: sdk 237 | version: "0.0.0" 238 | flutter_multi_formatter: 239 | dependency: transitive 240 | description: 241 | name: flutter_multi_formatter 242 | sha256: "01e8ba4cfae4b52377d1c83e57293da24eb64696d7c2df064acc4ae97bb4a157" 243 | url: "https://pub.dev" 244 | source: hosted 245 | version: "2.13.7" 246 | flutter_secure_storage: 247 | dependency: "direct main" 248 | description: 249 | name: flutter_secure_storage 250 | sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" 251 | url: "https://pub.dev" 252 | source: hosted 253 | version: "9.2.4" 254 | flutter_secure_storage_linux: 255 | dependency: transitive 256 | description: 257 | name: flutter_secure_storage_linux 258 | sha256: bf7404619d7ab5c0a1151d7c4e802edad8f33535abfbeff2f9e1fe1274e2d705 259 | url: "https://pub.dev" 260 | source: hosted 261 | version: "1.2.2" 262 | flutter_secure_storage_macos: 263 | dependency: transitive 264 | description: 265 | name: flutter_secure_storage_macos 266 | sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" 267 | url: "https://pub.dev" 268 | source: hosted 269 | version: "3.1.3" 270 | flutter_secure_storage_platform_interface: 271 | dependency: transitive 272 | description: 273 | name: flutter_secure_storage_platform_interface 274 | sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 275 | url: "https://pub.dev" 276 | source: hosted 277 | version: "1.1.2" 278 | flutter_secure_storage_web: 279 | dependency: transitive 280 | description: 281 | name: flutter_secure_storage_web 282 | sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 283 | url: "https://pub.dev" 284 | source: hosted 285 | version: "1.2.1" 286 | flutter_secure_storage_windows: 287 | dependency: transitive 288 | description: 289 | name: flutter_secure_storage_windows 290 | sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 291 | url: "https://pub.dev" 292 | source: hosted 293 | version: "3.1.2" 294 | flutter_staggered_grid_view: 295 | dependency: transitive 296 | description: 297 | name: flutter_staggered_grid_view 298 | sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" 299 | url: "https://pub.dev" 300 | source: hosted 301 | version: "0.7.0" 302 | flutter_styled_toast: 303 | dependency: transitive 304 | description: 305 | name: flutter_styled_toast 306 | sha256: e667f13a665820eb0fa8506547e47eacbcddf1948d6d3036cfd3b089bd4b0516 307 | url: "https://pub.dev" 308 | source: hosted 309 | version: "2.2.1" 310 | flutter_test: 311 | dependency: "direct dev" 312 | description: flutter 313 | source: sdk 314 | version: "0.0.0" 315 | flutter_timezone: 316 | dependency: transitive 317 | description: 318 | name: flutter_timezone 319 | sha256: "13b2109ad75651faced4831bf262e32559e44aa549426eab8a597610d385d934" 320 | url: "https://pub.dev" 321 | source: hosted 322 | version: "4.1.1" 323 | flutter_web_plugins: 324 | dependency: transitive 325 | description: flutter 326 | source: sdk 327 | version: "0.0.0" 328 | get_time_ago: 329 | dependency: transitive 330 | description: 331 | name: get_time_ago 332 | sha256: "1c6ccc877e480eee559987411ec5242cecc088d45fc821f0d8aec98bbf4f210e" 333 | url: "https://pub.dev" 334 | source: hosted 335 | version: "2.3.1" 336 | http: 337 | dependency: transitive 338 | description: 339 | name: http 340 | sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f 341 | url: "https://pub.dev" 342 | source: hosted 343 | version: "1.3.0" 344 | http_parser: 345 | dependency: transitive 346 | description: 347 | name: http_parser 348 | sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" 349 | url: "https://pub.dev" 350 | source: hosted 351 | version: "4.1.2" 352 | intl: 353 | dependency: transitive 354 | description: 355 | name: intl 356 | sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" 357 | url: "https://pub.dev" 358 | source: hosted 359 | version: "0.20.2" 360 | js: 361 | dependency: transitive 362 | description: 363 | name: js 364 | sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 365 | url: "https://pub.dev" 366 | source: hosted 367 | version: "0.6.7" 368 | leak_tracker: 369 | dependency: transitive 370 | description: 371 | name: leak_tracker 372 | sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" 373 | url: "https://pub.dev" 374 | source: hosted 375 | version: "10.0.9" 376 | leak_tracker_flutter_testing: 377 | dependency: transitive 378 | description: 379 | name: leak_tracker_flutter_testing 380 | sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 381 | url: "https://pub.dev" 382 | source: hosted 383 | version: "3.0.9" 384 | leak_tracker_testing: 385 | dependency: transitive 386 | description: 387 | name: leak_tracker_testing 388 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 389 | url: "https://pub.dev" 390 | source: hosted 391 | version: "3.0.1" 392 | lints: 393 | dependency: transitive 394 | description: 395 | name: lints 396 | sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 397 | url: "https://pub.dev" 398 | source: hosted 399 | version: "5.1.1" 400 | mask_text_input_formatter: 401 | dependency: transitive 402 | description: 403 | name: mask_text_input_formatter 404 | sha256: "978c58ec721c25621ceb468e633f4eef64b64d45424ac4540e0565d4f7c800cd" 405 | url: "https://pub.dev" 406 | source: hosted 407 | version: "2.9.0" 408 | matcher: 409 | dependency: transitive 410 | description: 411 | name: matcher 412 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 413 | url: "https://pub.dev" 414 | source: hosted 415 | version: "0.12.17" 416 | material_color_utilities: 417 | dependency: transitive 418 | description: 419 | name: material_color_utilities 420 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 421 | url: "https://pub.dev" 422 | source: hosted 423 | version: "0.11.1" 424 | meta: 425 | dependency: transitive 426 | description: 427 | name: meta 428 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c 429 | url: "https://pub.dev" 430 | source: hosted 431 | version: "1.16.0" 432 | nylo_support: 433 | dependency: "direct main" 434 | description: 435 | name: nylo_support 436 | sha256: "435b77fe64865579e8f63afb5eed4780e83c547cd8f2f9bb9ebdfb2fd29f3b0a" 437 | url: "https://pub.dev" 438 | source: hosted 439 | version: "6.28.5" 440 | path: 441 | dependency: transitive 442 | description: 443 | name: path 444 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" 445 | url: "https://pub.dev" 446 | source: hosted 447 | version: "1.9.1" 448 | path_provider: 449 | dependency: transitive 450 | description: 451 | name: path_provider 452 | sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" 453 | url: "https://pub.dev" 454 | source: hosted 455 | version: "2.1.5" 456 | path_provider_android: 457 | dependency: transitive 458 | description: 459 | name: path_provider_android 460 | sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" 461 | url: "https://pub.dev" 462 | source: hosted 463 | version: "2.2.16" 464 | path_provider_foundation: 465 | dependency: transitive 466 | description: 467 | name: path_provider_foundation 468 | sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" 469 | url: "https://pub.dev" 470 | source: hosted 471 | version: "2.4.1" 472 | path_provider_linux: 473 | dependency: transitive 474 | description: 475 | name: path_provider_linux 476 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 477 | url: "https://pub.dev" 478 | source: hosted 479 | version: "2.2.1" 480 | path_provider_platform_interface: 481 | dependency: transitive 482 | description: 483 | name: path_provider_platform_interface 484 | sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" 485 | url: "https://pub.dev" 486 | source: hosted 487 | version: "2.1.2" 488 | path_provider_windows: 489 | dependency: transitive 490 | description: 491 | name: path_provider_windows 492 | sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 493 | url: "https://pub.dev" 494 | source: hosted 495 | version: "2.3.0" 496 | petitparser: 497 | dependency: transitive 498 | description: 499 | name: petitparser 500 | sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" 501 | url: "https://pub.dev" 502 | source: hosted 503 | version: "6.1.0" 504 | platform: 505 | dependency: transitive 506 | description: 507 | name: platform 508 | sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" 509 | url: "https://pub.dev" 510 | source: hosted 511 | version: "3.1.6" 512 | plugin_platform_interface: 513 | dependency: transitive 514 | description: 515 | name: plugin_platform_interface 516 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" 517 | url: "https://pub.dev" 518 | source: hosted 519 | version: "2.1.8" 520 | pretty_dio_logger: 521 | dependency: transitive 522 | description: 523 | name: pretty_dio_logger 524 | sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407" 525 | url: "https://pub.dev" 526 | source: hosted 527 | version: "1.4.0" 528 | pull_to_refresh_flutter3: 529 | dependency: transitive 530 | description: 531 | name: pull_to_refresh_flutter3 532 | sha256: "37a88d901cca9a46dbdd46523de8e7b35a3e58634a0e775b1a5904981f69b353" 533 | url: "https://pub.dev" 534 | source: hosted 535 | version: "2.0.2" 536 | recase: 537 | dependency: "direct main" 538 | description: 539 | name: recase 540 | sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 541 | url: "https://pub.dev" 542 | source: hosted 543 | version: "4.1.0" 544 | rxdart: 545 | dependency: transitive 546 | description: 547 | name: rxdart 548 | sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" 549 | url: "https://pub.dev" 550 | source: hosted 551 | version: "0.28.0" 552 | shared_preferences: 553 | dependency: transitive 554 | description: 555 | name: shared_preferences 556 | sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" 557 | url: "https://pub.dev" 558 | source: hosted 559 | version: "2.5.3" 560 | shared_preferences_android: 561 | dependency: transitive 562 | description: 563 | name: shared_preferences_android 564 | sha256: c2c8c46297b5d6a80bed7741ec1f2759742c77d272f1a1698176ae828f8e1a18 565 | url: "https://pub.dev" 566 | source: hosted 567 | version: "2.4.9" 568 | shared_preferences_foundation: 569 | dependency: transitive 570 | description: 571 | name: shared_preferences_foundation 572 | sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" 573 | url: "https://pub.dev" 574 | source: hosted 575 | version: "2.5.4" 576 | shared_preferences_linux: 577 | dependency: transitive 578 | description: 579 | name: shared_preferences_linux 580 | sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" 581 | url: "https://pub.dev" 582 | source: hosted 583 | version: "2.4.1" 584 | shared_preferences_platform_interface: 585 | dependency: transitive 586 | description: 587 | name: shared_preferences_platform_interface 588 | sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" 589 | url: "https://pub.dev" 590 | source: hosted 591 | version: "2.4.1" 592 | shared_preferences_web: 593 | dependency: transitive 594 | description: 595 | name: shared_preferences_web 596 | sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 597 | url: "https://pub.dev" 598 | source: hosted 599 | version: "2.4.3" 600 | shared_preferences_windows: 601 | dependency: transitive 602 | description: 603 | name: shared_preferences_windows 604 | sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" 605 | url: "https://pub.dev" 606 | source: hosted 607 | version: "2.4.1" 608 | skeletonizer: 609 | dependency: "direct main" 610 | description: 611 | name: skeletonizer 612 | sha256: a9ddf63900947f4c0648372b6e9987bc2b028db9db843376db6767224d166c31 613 | url: "https://pub.dev" 614 | source: hosted 615 | version: "2.0.1" 616 | sky_engine: 617 | dependency: transitive 618 | description: flutter 619 | source: sdk 620 | version: "0.0.0" 621 | source_span: 622 | dependency: transitive 623 | description: 624 | name: source_span 625 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" 626 | url: "https://pub.dev" 627 | source: hosted 628 | version: "1.10.1" 629 | sprintf: 630 | dependency: transitive 631 | description: 632 | name: sprintf 633 | sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" 634 | url: "https://pub.dev" 635 | source: hosted 636 | version: "7.0.0" 637 | stack_trace: 638 | dependency: transitive 639 | description: 640 | name: stack_trace 641 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" 642 | url: "https://pub.dev" 643 | source: hosted 644 | version: "1.12.1" 645 | stream_channel: 646 | dependency: transitive 647 | description: 648 | name: stream_channel 649 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" 650 | url: "https://pub.dev" 651 | source: hosted 652 | version: "2.1.4" 653 | string_scanner: 654 | dependency: transitive 655 | description: 656 | name: string_scanner 657 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" 658 | url: "https://pub.dev" 659 | source: hosted 660 | version: "1.4.1" 661 | term_glyph: 662 | dependency: transitive 663 | description: 664 | name: term_glyph 665 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" 666 | url: "https://pub.dev" 667 | source: hosted 668 | version: "1.2.2" 669 | test_api: 670 | dependency: transitive 671 | description: 672 | name: test_api 673 | sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd 674 | url: "https://pub.dev" 675 | source: hosted 676 | version: "0.7.4" 677 | theme_provider: 678 | dependency: "direct main" 679 | description: 680 | name: theme_provider 681 | sha256: "6a2839ee1bd539ceb789f25ea9696fe90f9dfad28e3228f209b8ff9255c58099" 682 | url: "https://pub.dev" 683 | source: hosted 684 | version: "0.6.0" 685 | timezone: 686 | dependency: transitive 687 | description: 688 | name: timezone 689 | sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 690 | url: "https://pub.dev" 691 | source: hosted 692 | version: "0.10.1" 693 | typed_data: 694 | dependency: transitive 695 | description: 696 | name: typed_data 697 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 698 | url: "https://pub.dev" 699 | source: hosted 700 | version: "1.4.0" 701 | url_launcher: 702 | dependency: transitive 703 | description: 704 | name: url_launcher 705 | sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" 706 | url: "https://pub.dev" 707 | source: hosted 708 | version: "6.3.1" 709 | url_launcher_android: 710 | dependency: transitive 711 | description: 712 | name: url_launcher_android 713 | sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4" 714 | url: "https://pub.dev" 715 | source: hosted 716 | version: "6.3.15" 717 | url_launcher_ios: 718 | dependency: transitive 719 | description: 720 | name: url_launcher_ios 721 | sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" 722 | url: "https://pub.dev" 723 | source: hosted 724 | version: "6.3.3" 725 | url_launcher_linux: 726 | dependency: transitive 727 | description: 728 | name: url_launcher_linux 729 | sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" 730 | url: "https://pub.dev" 731 | source: hosted 732 | version: "3.2.1" 733 | url_launcher_macos: 734 | dependency: transitive 735 | description: 736 | name: url_launcher_macos 737 | sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" 738 | url: "https://pub.dev" 739 | source: hosted 740 | version: "3.2.2" 741 | url_launcher_platform_interface: 742 | dependency: transitive 743 | description: 744 | name: url_launcher_platform_interface 745 | sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" 746 | url: "https://pub.dev" 747 | source: hosted 748 | version: "2.3.2" 749 | url_launcher_web: 750 | dependency: transitive 751 | description: 752 | name: url_launcher_web 753 | sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" 754 | url: "https://pub.dev" 755 | source: hosted 756 | version: "2.4.0" 757 | url_launcher_windows: 758 | dependency: transitive 759 | description: 760 | name: url_launcher_windows 761 | sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" 762 | url: "https://pub.dev" 763 | source: hosted 764 | version: "3.1.4" 765 | uuid: 766 | dependency: transitive 767 | description: 768 | name: uuid 769 | sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff 770 | url: "https://pub.dev" 771 | source: hosted 772 | version: "4.5.1" 773 | validated: 774 | dependency: transitive 775 | description: 776 | name: validated 777 | sha256: f4da18b50fa2aeda8d2f6e55bdf73759593abe3f9dd4aeece4e98bf3438e6a9f 778 | url: "https://pub.dev" 779 | source: hosted 780 | version: "2.0.0" 781 | vector_math: 782 | dependency: transitive 783 | description: 784 | name: vector_math 785 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 786 | url: "https://pub.dev" 787 | source: hosted 788 | version: "2.1.4" 789 | vm_service: 790 | dependency: transitive 791 | description: 792 | name: vm_service 793 | sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 794 | url: "https://pub.dev" 795 | source: hosted 796 | version: "15.0.0" 797 | web: 798 | dependency: transitive 799 | description: 800 | name: web 801 | sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" 802 | url: "https://pub.dev" 803 | source: hosted 804 | version: "1.1.1" 805 | win32: 806 | dependency: transitive 807 | description: 808 | name: win32 809 | sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" 810 | url: "https://pub.dev" 811 | source: hosted 812 | version: "5.13.0" 813 | xdg_directories: 814 | dependency: transitive 815 | description: 816 | name: xdg_directories 817 | sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" 818 | url: "https://pub.dev" 819 | source: hosted 820 | version: "1.1.0" 821 | xml: 822 | dependency: transitive 823 | description: 824 | name: xml 825 | sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 826 | url: "https://pub.dev" 827 | source: hosted 828 | version: "6.5.0" 829 | sdks: 830 | dart: ">=3.7.0 <4.0.0" 831 | flutter: ">=3.31.0-0.0.pre" 832 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: nylo_framework 2 | description: Micro-framework for Flutter that's built to simplify app development for Flutter projects. 3 | version: 6.8.7 4 | homepage: https://nylo.dev 5 | repository: https://github.com/nylo-core/framework/tree/6.x 6 | issue_tracker: https://github.com/nylo-core/framework/issues 7 | documentation: https://github.com/nylo-core/framework 8 | funding: 9 | - https://github.com/sponsors/agordn52 10 | topics: 11 | - micro-framework 12 | - framework 13 | 14 | environment: 15 | sdk: '>=3.4.0 <4.0.0' 16 | flutter: ">=3.24.0" 17 | 18 | dependencies: 19 | flutter_dotenv: ^5.2.1 20 | nylo_support: ^6.28.5 21 | theme_provider: ^0.6.0 22 | flutter_secure_storage: ^9.2.4 23 | skeletonizer: ^2.0.1 24 | collection: ^1.18.0 25 | args: ^2.7.0 26 | dio: ^5.8.0+1 27 | recase: ^4.1.0 28 | error_stack: ^1.10.3 29 | date_field: ^6.0.3+1 30 | flutter: 31 | sdk: flutter 32 | 33 | dev_dependencies: 34 | flutter_lints: ^5.0.0 35 | flutter_test: 36 | sdk: flutter 37 | 38 | screenshots: 39 | - description: The Nylo package logo. 40 | path: screenshots/logo.png 41 | 42 | flutter: 43 | 44 | -------------------------------------------------------------------------------- /screenshots/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylo-core/framework/8ab5099c1f570670d3da21e952af02d7df9fa5c6/screenshots/logo.png -------------------------------------------------------------------------------- /test/nylo_framework_test.dart: -------------------------------------------------------------------------------- 1 | void main() {} 2 | --------------------------------------------------------------------------------