From fb75fc7be6f5d3d55a023808444c0588bbb41284 Mon Sep 17 00:00:00 2001 From: Anthony Date: Thu, 18 Jan 2024 14:54:26 +0700 Subject: [PATCH] Add Algolia search, update docs to 5.20.0, new ecosystem page and css styling for docs --- .../Commands/GenerateSitemapCommand.php | 1 + app/Http/Controllers/LandingController.php | 12 + composer.json | 4 +- composer.lock | 157 +++- config/project/doc-index.php | 46 + config/project/doc-tutorials.php | 18 + config/project/meta.php | 12 +- public/css/app.css | 184 ++-- public/css/docs.css | 33 + public/css/docs.min.css | 1 + resources/docs/5.20.0/app-icons.md | 92 ++ resources/docs/5.20.0/assets.md | 67 ++ resources/docs/5.20.0/authentication.md | 126 +++ resources/docs/5.20.0/configuration.md | 151 ++++ resources/docs/5.20.0/controllers.md | 212 +++++ resources/docs/5.20.0/decoders.md | 128 +++ resources/docs/5.20.0/directory-structure.md | 121 +++ resources/docs/5.20.0/events.md | 251 ++++++ resources/docs/5.20.0/installation.md | 125 +++ resources/docs/5.20.0/localization.md | 223 +++++ resources/docs/5.20.0/logging.md | 93 ++ resources/docs/5.20.0/metro.md | 617 +++++++++++++ resources/docs/5.20.0/networking.md | 713 +++++++++++++++ resources/docs/5.20.0/ny-future-builder.md | 76 ++ resources/docs/5.20.0/ny-list-view.md | 105 +++ resources/docs/5.20.0/ny-pull-to-refresh.md | 105 +++ resources/docs/5.20.0/ny-state.md | 811 ++++++++++++++++++ resources/docs/5.20.0/ny-switch.md | 65 ++ resources/docs/5.20.0/ny-text-field.md | 116 +++ resources/docs/5.20.0/providers.md | 99 +++ resources/docs/5.20.0/requirements.md | 44 + resources/docs/5.20.0/router.md | 530 ++++++++++++ resources/docs/5.20.0/slates.md | 52 ++ resources/docs/5.20.0/state-management.md | 175 ++++ resources/docs/5.20.0/storage.md | 298 +++++++ resources/docs/5.20.0/themes-and-styling.md | 495 +++++++++++ resources/docs/5.20.0/upgrade-guide.md | 22 + resources/docs/5.20.0/validation.md | 809 +++++++++++++++++ resources/docs/5.20.0/what-is-nylo.md | 77 ++ resources/docs/5.x/metro.md | 15 +- resources/views/docs/nav.blade.php | 1 - .../views/docs/sidebar-tutorials.blade.php | 2 +- resources/views/docs/sidebar.blade.php | 26 +- resources/views/docs/template.blade.php | 6 +- resources/views/includes/header.blade.php | 3 + resources/views/layouts/app-docs.blade.php | 56 +- resources/views/pages/ecosystem.blade.php | 56 ++ routes/web.php | 8 +- 48 files changed, 7292 insertions(+), 147 deletions(-) create mode 100644 public/css/docs.css create mode 100644 public/css/docs.min.css create mode 100644 resources/docs/5.20.0/app-icons.md create mode 100644 resources/docs/5.20.0/assets.md create mode 100644 resources/docs/5.20.0/authentication.md create mode 100644 resources/docs/5.20.0/configuration.md create mode 100644 resources/docs/5.20.0/controllers.md create mode 100644 resources/docs/5.20.0/decoders.md create mode 100644 resources/docs/5.20.0/directory-structure.md create mode 100644 resources/docs/5.20.0/events.md create mode 100644 resources/docs/5.20.0/installation.md create mode 100644 resources/docs/5.20.0/localization.md create mode 100644 resources/docs/5.20.0/logging.md create mode 100644 resources/docs/5.20.0/metro.md create mode 100644 resources/docs/5.20.0/networking.md create mode 100644 resources/docs/5.20.0/ny-future-builder.md create mode 100644 resources/docs/5.20.0/ny-list-view.md create mode 100644 resources/docs/5.20.0/ny-pull-to-refresh.md create mode 100644 resources/docs/5.20.0/ny-state.md create mode 100644 resources/docs/5.20.0/ny-switch.md create mode 100644 resources/docs/5.20.0/ny-text-field.md create mode 100644 resources/docs/5.20.0/providers.md create mode 100644 resources/docs/5.20.0/requirements.md create mode 100644 resources/docs/5.20.0/router.md create mode 100644 resources/docs/5.20.0/slates.md create mode 100644 resources/docs/5.20.0/state-management.md create mode 100644 resources/docs/5.20.0/storage.md create mode 100644 resources/docs/5.20.0/themes-and-styling.md create mode 100644 resources/docs/5.20.0/upgrade-guide.md create mode 100644 resources/docs/5.20.0/validation.md create mode 100644 resources/docs/5.20.0/what-is-nylo.md create mode 100644 resources/views/pages/ecosystem.blade.php diff --git a/app/Console/Commands/GenerateSitemapCommand.php b/app/Console/Commands/GenerateSitemapCommand.php index 48b8040..445edad 100644 --- a/app/Console/Commands/GenerateSitemapCommand.php +++ b/app/Console/Commands/GenerateSitemapCommand.php @@ -34,6 +34,7 @@ public function handle(DocService $docService) $sitemap->add(Url::create('/')->setPriority(0.95)); $sitemap->add(Url::create('resources')->setPriority(0.90)); + $sitemap->add(Url::create('ecosystem')->setPriority(0.60)); $sitemap->add(Url::create('privacy-policy')->setPriority(0.90)); $sitemap->add(Url::create('terms-and-conditions')->setPriority(0.90)); diff --git a/app/Http/Controllers/LandingController.php b/app/Http/Controllers/LandingController.php index eb79301..7046b16 100644 --- a/app/Http/Controllers/LandingController.php +++ b/app/Http/Controllers/LandingController.php @@ -89,6 +89,18 @@ public function resources() return view('pages.resources', compact('resourceData')); } + /** + * Ecosystem page for Nylo. + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View + */ + public function ecosystem() + { + $this->seoService->setTitle('Ecosystem'); + + return view('pages.ecosystem'); + } + /** * Tutorials page for Nylo. * diff --git a/composer.json b/composer.json index 1a54a01..94e34ab 100644 --- a/composer.json +++ b/composer.json @@ -9,11 +9,13 @@ "license": "MIT", "require": { "php": "^8.0.2", + "algolia/algoliasearch-client-php": "^3.4", "artesaos/seotools": "^1.2.0", "guzzlehttp/guzzle": "^7.2", "laravel/framework": "^10.0", "laravel/tinker": "^2.7", - "spatie/laravel-sitemap": "^6.1.0" + "spatie/laravel-sitemap": "^6.1.0", + "voku/simple_html_dom": "^4.8" }, "require-dev": { "fakerphp/faker": "^1.9.1", diff --git a/composer.lock b/composer.lock index 0ccc433..ef7121c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,82 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "07ea6cd2e9a9dbee144f6d5c9fbeb87a", + "content-hash": "94e01c114ffa1f6f312305c5e1d324c0", "packages": [ + { + "name": "algolia/algoliasearch-client-php", + "version": "3.4.1", + "source": { + "type": "git", + "url": "https://github.com/algolia/algoliasearch-client-php.git", + "reference": "cf87b649f745479c0800299481d91dc303e23cea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/algolia/algoliasearch-client-php/zipball/cf87b649f745479c0800299481d91dc303e23cea", + "reference": "cf87b649f745479c0800299481d91dc303e23cea", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": "^7.3 || ^8.0", + "psr/http-message": "^1.1 || ^2.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "fzaninotto/faker": "^1.8", + "phpunit/phpunit": "^8.0 || ^9.0", + "symfony/yaml": "^2.0 || ^4.0" + }, + "suggest": { + "guzzlehttp/guzzle": "If you prefer to use Guzzle HTTP client instead of the Http Client implementation provided by the package" + }, + "bin": [ + "bin/algolia-doctor" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-2.0": "2.0.x-dev" + } + }, + "autoload": { + "files": [ + "src/Http/Psr7/functions.php", + "src/functions.php" + ], + "psr-4": { + "Algolia\\AlgoliaSearch\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Algolia Team", + "email": "contact@algolia.com" + } + ], + "description": "Algolia Search API Client for PHP", + "keywords": [ + "algolia", + "api", + "client", + "php", + "search" + ], + "support": { + "issues": "https://github.com/algolia/algoliasearch-client-php/issues", + "source": "https://github.com/algolia/algoliasearch-client-php/tree/3.4.1" + }, + "time": "2023-08-28T14:34:04+00:00" + }, { "name": "artesaos/seotools", "version": "v1.2.0", @@ -6252,6 +6326,87 @@ ], "time": "2022-03-08T17:03:00+00:00" }, + { + "name": "voku/simple_html_dom", + "version": "4.8.8", + "source": { + "type": "git", + "url": "https://github.com/voku/simple_html_dom.git", + "reference": "9ef90f0280fe16054c117e04ea86617ce0fcdd35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/simple_html_dom/zipball/9ef90f0280fe16054c117e04ea86617ce0fcdd35", + "reference": "9ef90f0280fe16054c117e04ea86617ce0fcdd35", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-simplexml": "*", + "php": ">=7.0.0", + "symfony/css-selector": "~3.0 || ~4.0 || ~5.0 || ~6.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "voku/portable-utf8": "If you need e.g. UTF-8 fixed output." + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\helper\\": "src/voku/helper/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "dimabdc", + "email": "support@titor.ru", + "homepage": "https://github.com/dimabdc", + "role": "Developer" + }, + { + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/", + "role": "Fork-Maintainer" + } + ], + "description": "Simple HTML DOM package.", + "homepage": "https://github.com/voku/simple_html_dom", + "keywords": [ + "HTML Parser", + "dom", + "php dom" + ], + "support": { + "issues": "https://github.com/voku/simple_html_dom/issues", + "source": "https://github.com/voku/simple_html_dom/tree/4.8.8" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/simple_html_dom", + "type": "tidelift" + } + ], + "time": "2023-02-12T16:15:15+00:00" + }, { "name": "webmozart/assert", "version": "1.11.0", diff --git a/config/project/doc-index.php b/config/project/doc-index.php index 4e7b2d8..e92a0e6 100644 --- a/config/project/doc-index.php +++ b/config/project/doc-index.php @@ -169,6 +169,52 @@ 'ny-page' ], + 'advanced' => [ + 'state-management', + 'providers', + 'decoders', + 'events', + 'slates' + ], + ], + + '5.20.0' => [ + 'introduction' => [ + 'what-is-nylo', + 'requirements' + ], + + 'getting-started' => [ + 'installation', + 'configuration', + 'directory-structure', + 'upgrade-guide' + ], + + 'basics' => [ + 'router', + 'networking', + 'metro', + 'localization', + 'storage', + 'controllers', + 'app-icons', + 'validation', + 'authentication', + 'logging' + ], + + 'widgets' => [ + 'themes-and-styling', + 'assets', + 'ny-state', + 'ny-future-builder', + 'ny-text-field', + 'ny-pull-to-refresh', + 'ny-list-view', + 'ny-switch', + ], + 'advanced' => [ 'state-management', 'providers', diff --git a/config/project/doc-tutorials.php b/config/project/doc-tutorials.php index 4f3eb9c..6d593ea 100644 --- a/config/project/doc-tutorials.php +++ b/config/project/doc-tutorials.php @@ -41,8 +41,26 @@ [ 'label' => 'providers', 'link' => 'https://www.youtube.com/embed/0Y13JyV6Cc4?si=ibgLXBiw9x6GqXKn' + ], + [ + 'label' => 'state management', + 'link' => 'https://www.youtube.com/embed/X5EVh1KooFk?si=gengIXC02gGbgRki' ], ], + 'Widgets' => [ + [ + 'label' => 'NyPullToRefresh', + 'link' => 'https://www.youtube.com/embed/nz6oJiwdggw?si=sRx8lkr6EuoBDIuP' + ], + [ + 'label' => 'NyListView', + 'link' => 'https://www.youtube.com/embed/mEvoy6SKb5w?si=j7n-G9FXLQfXfdk0' + ], + [ + 'label' => 'NyFutureBuilder', + 'link' => 'https://www.youtube.com/embed/UGg_XsQCHeA?si=D7cVYW3c9tNxrfHP' + ], + ] ] ] ]; diff --git a/config/project/meta.php b/config/project/meta.php index de18d4d..7757150 100644 --- a/config/project/meta.php +++ b/config/project/meta.php @@ -2,12 +2,18 @@ return [ 'ga_id' => env('GOOGLE_ANALYTICS_ID'), - + 'fa_integrity' => env('FONT_AWESOME_INTEGRITY'), 'gh_auth_token' => env('GH_AUTH_MIDDLEWARE'), - + 'process_token' => env('APP_PROCESS_TOKEN'), - 'github_webhook_secret' => env('GITHUB_WEBHOOK_SECRET') + 'github_webhook_secret' => env('GITHUB_WEBHOOK_SECRET'), + + 'algolia_app_id' => env('ALGOLIA_APP_ID'), + + 'algolia_app_key' => env('ALGOLIA_APP_KEY'), + + 'algolia_index_name' => env('ALGOLIA_INDEX_NAME'), ]; diff --git a/public/css/app.css b/public/css/app.css index 05cf907..b127676 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -878,14 +878,14 @@ Ensure the default browser behavior of the `hidden` attribute. .col-span-1 { grid-column: span 1 / span 1; } -.col-span-4 { - grid-column: span 4 / span 4; +.col-span-5 { + grid-column: span 5 / span 5; } .col-span-3 { grid-column: span 3 / span 3; } -.col-span-5 { - grid-column: span 5 / span 5; +.col-span-4 { + grid-column: span 4 / span 4; } .clear-both { clear: both; @@ -909,14 +909,14 @@ Ensure the default browser behavior of the `hidden` attribute. margin-left: 0.75rem; margin-right: 0.75rem; } -.my-16 { - margin-top: 4rem; - margin-bottom: 4rem; -} .mx-2 { margin-left: 0.5rem; margin-right: 0.5rem; } +.my-16 { + margin-top: 4rem; + margin-bottom: 4rem; +} .mt-16 { margin-top: 4rem; } @@ -971,6 +971,12 @@ Ensure the default browser behavior of the `hidden` attribute. .mt-\[-82px\] { margin-top: -82px; } +.mb-2 { + margin-bottom: 0.5rem; +} +.mb-10 { + margin-bottom: 2.5rem; +} .mb-7 { margin-bottom: 1.75rem; } @@ -983,9 +989,6 @@ Ensure the default browser behavior of the `hidden` attribute. .mb-44 { margin-bottom: 11rem; } -.mb-2 { - margin-bottom: 0.5rem; -} .mb-5 { margin-bottom: 1.25rem; } @@ -1004,9 +1007,6 @@ Ensure the default browser behavior of the `hidden` attribute. .mb-\[40px\] { margin-bottom: 40px; } -.mb-10 { - margin-bottom: 2.5rem; -} .block { display: block; } @@ -1061,11 +1061,6 @@ Ensure the default browser behavior of the `hidden` attribute. .w-\[19\.5rem\] { width: 19.5rem; } -.w-max { - width: -webkit-max-content; - width: -moz-max-content; - width: max-content; -} .w-\[20px\] { width: 20px; } @@ -1075,6 +1070,11 @@ Ensure the default browser behavior of the `hidden` attribute. .w-0 { width: 0px; } +.w-max { + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; +} .min-w-0 { min-width: 0px; } @@ -1090,6 +1090,9 @@ Ensure the default browser behavior of the `hidden` attribute. .flex-1 { flex: 1 1 0%; } +.cursor-pointer { + cursor: pointer; +} .grid-flow-row { grid-auto-flow: row; } @@ -1184,12 +1187,12 @@ Ensure the default browser behavior of the `hidden` attribute. .rounded-2xl { border-radius: 1rem; } -.rounded-lg { - border-radius: 0.5rem; -} .rounded-3xl { border-radius: 1.5rem; } +.rounded-lg { + border-radius: 0.5rem; +} .border { border-width: 1px; } @@ -1236,14 +1239,14 @@ Ensure the default browser behavior of the `hidden` attribute. .border-slate-900\/10 { border-color: rgb(15 23 42 / 0.1); } -.border-gray-200 { - --tw-border-opacity: 1; - border-color: rgb(229 231 235 / var(--tw-border-opacity)); -} .border-gray-100 { --tw-border-opacity: 1; border-color: rgb(243 244 246 / var(--tw-border-opacity)); } +.border-gray-200 { + --tw-border-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-border-opacity)); +} .bg-gray-50 { --tw-bg-opacity: 1; background-color: rgb(249 250 251 / var(--tw-bg-opacity)); @@ -1264,13 +1267,13 @@ Ensure the default browser behavior of the `hidden` attribute. .bg-slate-400\/10 { background-color: rgb(148 163 184 / 0.1); } -.bg-white\/50 { - background-color: rgb(255 255 255 / 0.5); -} .bg-\[\#ECF5FC\] { --tw-bg-opacity: 1; background-color: rgb(236 245 252 / var(--tw-bg-opacity)); } +.bg-white\/50 { + background-color: rgb(255 255 255 / 0.5); +} .bg-gradient-to-b { background-image: linear-gradient(to bottom, var(--tw-gradient-stops)); } @@ -1293,10 +1296,6 @@ Ensure the default browser behavior of the `hidden` attribute. .p-\[24px\] { padding: 24px; } -.py-3 { - padding-top: 0.75rem; - padding-bottom: 0.75rem; -} .py-5 { padding-top: 1.25rem; padding-bottom: 1.25rem; @@ -1325,17 +1324,9 @@ Ensure the default browser behavior of the `hidden` attribute. padding-top: 0.25rem; padding-bottom: 0.25rem; } -.px-8 { - padding-left: 2rem; - padding-right: 2rem; -} -.px-14 { - padding-left: 3.5rem; - padding-right: 3.5rem; -} -.py-48 { - padding-top: 12rem; - padding-bottom: 12rem; +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; } .px-6 { padding-left: 1.5rem; @@ -1345,17 +1336,29 @@ Ensure the default browser behavior of the `hidden` attribute. padding-left: 1.25rem; padding-right: 1.25rem; } -.px-2 { - padding-left: 0.5rem; - padding-right: 0.5rem; +.px-8 { + padding-left: 2rem; + padding-right: 2rem; +} +.px-0 { + padding-left: 0px; + padding-right: 0px; } .px-7 { padding-left: 1.75rem; padding-right: 1.75rem; } -.px-0 { - padding-left: 0px; - padding-right: 0px; +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} +.px-14 { + padding-left: 3.5rem; + padding-right: 3.5rem; +} +.py-48 { + padding-top: 12rem; + padding-bottom: 12rem; } .pt-10 { padding-top: 2.5rem; @@ -1366,6 +1369,9 @@ Ensure the default browser behavior of the `hidden` attribute. .pl-4 { padding-left: 1rem; } +.pb-\[20px\] { + padding-bottom: 20px; +} .pt-\[150px\] { padding-top: 150px; } @@ -1378,6 +1384,9 @@ Ensure the default browser behavior of the `hidden` attribute. .pt-\[82px\] { padding-top: 82px; } +.pt-\[80px\] { + padding-top: 80px; +} .pt-20 { padding-top: 5rem; } @@ -1387,18 +1396,6 @@ Ensure the default browser behavior of the `hidden` attribute. .pb-\[113px\] { padding-bottom: 113px; } -.pt-\[80px\] { - padding-top: 80px; -} -.pb-\[4px\] { - padding-bottom: 4px; -} -.pb-\[10px\] { - padding-bottom: 10px; -} -.pb-\[20px\] { - padding-bottom: 20px; -} .text-center { text-align: center; } @@ -1422,19 +1419,22 @@ Ensure the default browser behavior of the `hidden` attribute. font-size: 3rem; line-height: 1; } -.text-lg { - font-size: 1.125rem; - line-height: 1.75rem; +.text-\[20px\] { + font-size: 20px; } .text-\[18px\] { font-size: 18px; } -.text-\[20px\] { - font-size: 20px; +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; } .font-semibold { font-weight: 600; } +.font-light { + font-weight: 300; +} .font-medium { font-weight: 500; } @@ -1444,9 +1444,6 @@ Ensure the default browser behavior of the `hidden` attribute. .font-bold { font-weight: 700; } -.font-light { - font-weight: 300; -} .leading-6 { line-height: 1.5rem; } @@ -1485,6 +1482,14 @@ Ensure the default browser behavior of the `hidden` attribute. --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity)); } +.text-\[\#81888E\] { + --tw-text-opacity: 1; + color: rgb(129 136 142 / var(--tw-text-opacity)); +} +.text-\[\#6C7379\] { + --tw-text-opacity: 1; + color: rgb(108 115 121 / var(--tw-text-opacity)); +} .text-\[\#484D50\] { --tw-text-opacity: 1; color: rgb(72 77 80 / var(--tw-text-opacity)); @@ -1497,10 +1502,6 @@ Ensure the default browser behavior of the `hidden` attribute. --tw-text-opacity: 1; color: rgb(151 157 162 / var(--tw-text-opacity)); } -.text-\[\#6C7379\] { - --tw-text-opacity: 1; - color: rgb(108 115 121 / var(--tw-text-opacity)); -} .text-\[\#2F3234\] { --tw-text-opacity: 1; color: rgb(47 50 52 / var(--tw-text-opacity)); @@ -1509,10 +1510,6 @@ Ensure the default browser behavior of the `hidden` attribute. --tw-text-opacity: 1; color: rgb(0 0 0 / var(--tw-text-opacity)); } -.text-\[\#81888E\] { - --tw-text-opacity: 1; - color: rgb(129 136 142 / var(--tw-text-opacity)); -} .antialiased { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -1624,14 +1621,14 @@ Ensure the default browser behavior of the `hidden` attribute. background-color: rgb(229 231 235 / var(--tw-bg-opacity)); } -.hover\:bg-\[\#1E74C1\]:hover { +.hover\:bg-gray-50:hover { --tw-bg-opacity: 1; - background-color: rgb(30 116 193 / var(--tw-bg-opacity)); + background-color: rgb(249 250 251 / var(--tw-bg-opacity)); } -.hover\:bg-gray-50:hover { +.hover\:bg-\[\#1E74C1\]:hover { --tw-bg-opacity: 1; - background-color: rgb(249 250 251 / var(--tw-bg-opacity)); + background-color: rgb(30 116 193 / var(--tw-bg-opacity)); } .hover\:text-slate-500:hover { @@ -1780,14 +1777,6 @@ Ensure the default browser behavior of the `hidden` attribute. grid-column: span 1 / span 1; } - .sm\:col-span-5 { - grid-column: span 5 / span 5; - } - - .sm\:col-span-4 { - grid-column: span 4 / span 4; - } - .sm\:mb-0 { margin-bottom: 0px; } @@ -1808,16 +1797,6 @@ Ensure the default browser behavior of the `hidden` attribute. padding-left: 1.5rem; padding-right: 1.5rem; } - - .sm\:px-8 { - padding-left: 2rem; - padding-right: 2rem; - } - - .sm\:px-0 { - padding-left: 0px; - padding-right: 0px; - } } @media (min-width: 768px) { @@ -2194,11 +2173,6 @@ Ensure the default browser behavior of the `hidden` attribute. padding-right: 2rem; } - .lg\:px-0 { - padding-left: 0px; - padding-right: 0px; - } - .lg\:pl-\[19\.5rem\] { padding-left: 19.5rem; } diff --git a/public/css/docs.css b/public/css/docs.css new file mode 100644 index 0000000..83c5eb6 --- /dev/null +++ b/public/css/docs.css @@ -0,0 +1,33 @@ +.DocSearch { + width: 100%; +} + +.DocSearch-Button { + background: #f5f6f8 !important; + padding: 20px 20px !important; + border: 1px solid #eaeaec !important; +} + +.DocSearch-Button:hover { + box-shadow: inset 0 0 0 2px white !important; +} + +.DocSearch-Button .DocSearch-Search-Icon { + color: #969faf !important; +} + +.DocSearch-Search-Icon { + margin-right: 14px; + width: 15px; + height: 15px; +} + +.version-switcher { + border: 1px solid #eaeaec !important; +} + +.version-switcher:hover { + border: 1px solid #eaeaec !important; + box-shadow: inset 0 0 0 2px white !important; + transition: all 0.1s ease-in-out; +} diff --git a/public/css/docs.min.css b/public/css/docs.min.css new file mode 100644 index 0000000..cc4ec7a --- /dev/null +++ b/public/css/docs.min.css @@ -0,0 +1 @@ +.DocSearch-Button,.version-switcher,.version-switcher:hover{border:1px solid #eaeaec!important}.DocSearch-Button:hover,.version-switcher:hover{box-shadow:inset 0 0 0 2px #fff!important}.DocSearch{width:100%}.DocSearch-Button{background:#f5f6f8!important;padding:20px!important}.DocSearch-Button .DocSearch-Search-Icon{color:#969faf!important}.DocSearch-Search-Icon{margin-right:14px;width:15px;height:15px}.version-switcher:hover{transition:.1s ease-in-out} diff --git a/resources/docs/5.20.0/app-icons.md b/resources/docs/5.20.0/app-icons.md new file mode 100644 index 0000000..185454e --- /dev/null +++ b/resources/docs/5.20.0/app-icons.md @@ -0,0 +1,92 @@ +# App Icons + +--- + + +- [Introduction](#introduction "Introduction") +- [Generating app icons](#generating-app-icons "Generating app icons") +- [Adding your app icon](#adding-your-app-icon) +- [App icon filename](#app-icon-filenames "App icon filenames") +- [App icon filetype](#app-icon-filetype "App icon filetype") +- [Configuration](#configuration "Configuration for app icons") + + +
+ +## Introduction + +You can build all your app icons using `dart run flutter_launcher_icons:main` from the command line. +This will take your current app icon in /public/assets/app_icon/ and auto-generate all your iOS and Android icons. + +> Your app icon should be a `.png` with the size dimensions of 1024x1024px + +If you have custom icons for different operating systems you can also just add them manually. + +{{ config('app.name') }} uses the flutter_launcher_icons library to build icons, to understand the library more you can check out their documentation too. + + +
+ +## Generating app icons + + +You can run the below command from the terminal to auto-generate your app icons. +``` bash +dart run flutter_launcher_icons:main +``` + +This command will use the app icon located in your `/public/assets/app_icon` directory to make the IOS and Android app icons to the correct dimensions. + + + +
+ +## Adding your app icon + +You can place your 'app icon' inside the `/public/assets/app_icon` directory. + +> Make your icon filesize is **1024x1024** for the best results. + +Once you’ve added your app icon, you’ll then need to update the **image\_path** if your filename is different to the default {{ config('app.name') }} app icon name. + +Open your pubspec.yaml file and look for **image\_path** section, this is where you can update the image path for the file. Make sure that the “image\_path” matches the location for your new app icon. + + + +
+ +## App icon filenames + +Your filenames shouldn’t include special characters. It’s best to keep it simple, like “app\_icon.jpg” or “icon.png”. + + + +
+ +## App icon file types + +The App Icon needs to be a .png type. + +App icon attributes. + +| Attribute | Value | +|---|---| +| Format | png | +| Size | 1024x1024px | +| Layers | Flattened with no transparency | + +If you are interested in learning more, you can view the official guidelines from both Google and Apple. + +- Apple’s human interface guideline is here +- Google’s icon design specifications are here + + + +
+ +## Configuration + +You can also modify the settings when generating your app icons. +Inside the `pubspec.yaml` file, look for the `flutter_icons` section, and here you can make changes to the configuration. + +Check out the official flutter_launcher_icons library to see what's possible. diff --git a/resources/docs/5.20.0/assets.md b/resources/docs/5.20.0/assets.md new file mode 100644 index 0000000..b6367e2 --- /dev/null +++ b/resources/docs/5.20.0/assets.md @@ -0,0 +1,67 @@ +# Assets + +--- + + +- [Introduction](#introduction "Introduction to assets") +- Files + - [Displaying images](#displaying-images "Displaying images") + - [Returning files](#returning-files "Returning files") +- Managing assets + - [Adding new files](#adding-new-files "Adding new files") + + + +
+## Introduction + +In this section, we'll look into how you can manage assets throughout your widgets. +{{ config('app.name') }} provides a few helper methods which make it easy to fetch images, files and more from your `public/assets` directory. + + +
+ +## Displaying images +You can return images by calling the below helper method. + +``` dart +getImageAsset('nylo_logo.png'); +``` + +In your widget, it would look something like the below. + +``` dart +Image.asset( + getImageAsset("nylo_logo.png"), +) + +// or + +Image.asset( + "nylo_logo.png", +).localAsset() +``` + + +
+ +## Returning files + +You can call the below helper method to get the full file path for an asset. + +``` dart +getPublicAsset('/images/nylo_logo.png'); +``` + +This could also be any file within the `public/assets` directory too + +``` dart +getPublicAsset('/video/welcome.mp4'); +``` + + +
+ +## Adding new files + +To add new files, open the `public/assets` directory and include your files in a new folder or an existing one. diff --git a/resources/docs/5.20.0/authentication.md b/resources/docs/5.20.0/authentication.md new file mode 100644 index 0000000..cd29a77 --- /dev/null +++ b/resources/docs/5.20.0/authentication.md @@ -0,0 +1,126 @@ +# Authentication + +--- + + +- [Introduction](#introduction "Introduction to authentication in {{ config('app.name') }}") +- Authentication + - [Adding an auth user](#adding-an-auth-user "Adding an auth user") + - [Retrieve an auth user](#retrieve-an-auth-user "Retrieve an auth user") + - [Removing an auth user](#removing-an-auth-user "Removing an auth user") +- [Authentication page](#authentication-page "Authentication page") + + +
+ +## Introduction + +In {{ config('app.name') }}, you can use the built-in helpers to make Authentication a breeze. + +To authenticate a user, run the below command. + +```dart +User user = User(); + +await Auth.set(user); +``` + +To retrieve a user, run the below command. + +```dart +User? user = await Auth.user(); + +print(user); // User +``` + +Let's imagine the below scenario. + +1. A user registers using an email and password. +2. After registering, you create the user a session token. +3. We now want to store the session token on the user's device for future use. + +``` dart +TextEditingController _tfEmail = TextEditingController(); +TextEditingController _tfPassword = TextEditingController(); + +_login() async { + // 1 - Example register via an API Service + User? user = await api((request) => request.register(email: _tfEmail.text, password: _tfPassword.text)); + + // 2 - Returns the users session token + print(user?.token); + + // 3 - Save the user to Nylo + await Auth.set(user); +} +``` + +Now the User model will be saved on the device. + +To retrieve the authenticated user back, use `Backpack.instance.auth()`. This will return the model that was saved previously. + + +
+ +## Adding an auth user + +When a user logs in to your application, you can add them using the `Auth.set(user)` helper. + +``` dart +_login() async { + User user = User(); + + await Auth.set(user); +} +``` + + +
+ +## Retrieve an auth user + +If a user is logged into your app, you can retrieve the user by calling `getAuthUser()`. This helper method will return the model stored. + +``` dart +_getUser() async { + User? user = await Auth.user(); + // or + User? user = Backpack.instance.auth(); +} +``` + + +
+ +## Removing an auth user + +When a user logs out of your application, you can remove them using the `Auth.remove()` helper. + +``` dart +_logout() async { + + await Auth.remove(); +} +``` + +Now, the user is logged out of the app and the [authentication page](#authentication-page) won't show when they next visit the app. + + +
+ +## Authentication Page + +Once your user is stored using the `Auth.set(user)` helper. You'll be able to set an 'authentication page', this will be used as the initial page the user sees when they open the app. + +Go to your **routes/router.dart** file and set parameter `authPage`. + +``` dart +appRouter() => nyRoutes((router) { + + router.route(HomePage.path, (context) => HomePage(title: "Hello World")); + + router.route(ProfilePage.path, (context) => ProfilePage(), authPage: true); // auth page +}); +``` + +Now, when the app boots, it will use the auth page instead of the default route link. diff --git a/resources/docs/5.20.0/configuration.md b/resources/docs/5.20.0/configuration.md new file mode 100644 index 0000000..21270fb --- /dev/null +++ b/resources/docs/5.20.0/configuration.md @@ -0,0 +1,151 @@ +# Configuration + +--- + + +- [Introduction](#introduction "Introduction to configuration in {{ config('app.name') }}") +- Environment + - [Configuration](#environment-configuration "Environment configuration") + - [Variable Types](#environment-variable-types "Environment variable types") + - [Retrieving Values](#retrieving-environment-values "Retrieving environment values") +- [Environment flavours](#environment-flavours "Environment flavours") + +
+## Introduction + +{{ config('app.name') }} provides a `.env` file which contains global configuration variables like the app name, default locale and your App's environment. + +This file is located at the root of your project, named ".env". + +``` +APP_NAME={{ config('app.name') }} +APP_ENV=local +APP_DEBUG=true +APP_URL=https://nylo.dev + +ASSET_PATH_PUBLIC=public/assets/ +ASSET_PATH_IMAGES=public/assets/images +TIMEZONE=UTC +DEFAULT_LOCALE=en +``` + +You can add new variables here and then fetch them using the `getEnv()` helper. + +
+ +#### Accessing values from the .env file + +You can access your `.env` values anywhere in the app using the below helper. + +``` dart +import 'package:nylo_framework/nylo_framework.dart'; + +String appName = getEnv('APP_NAME'); +``` + + +
+ +## Environment Configuration + +Configuring your applications environment is simple. + +First open your `.env` file and then update the keys in the environment file. + +You can also add additional keys here, e.g. `SHOW_ADS="false"`. + +Your .env file +``` dart +APP_NAME="My Super App" +``` + +Your Text widget + +``` dart +Text( + "Hello, my app's name is " + getEnv('APP_NAME'), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontWeight: FontWeight.bold), +) +``` + +--- + +Best practises: + +- Don't store anything sensitive or large. + +- Don't commit your `.env` file to a (public/private) repository. + + +
+ +## Environment Variable Types + +The values in your .env file are defined as `String`'s but {{ config('app.name') }} will return them differently if they match a type e.g. `Boolean` or `null`. + +| `.env` file | Return type | +|---|---| +| APP\_NAME="MySuper App" | `String` | +| DEBUG\_MODE=true | `Boolean` | +| URL_TERMS=null | `null` | + + + +
+ +## Retrieving Environment Values + +Fetching values from your `.env` file is simple in {{ config('app.name') }}, you can call the `getEnv(String key)` helper. + +``` dart +String appName = getEnv('APP_NAME'); +``` + +
+You can also provide a defaultValue if the key doesn't exist in the .env file. + +``` dart +String locale = getEnv('DEFAULT_LOCALE', defaultValue: "en"); + +int httpConnectionTimeout = getEnv('HTTP_CONNECTION_TIMEOUT', defaultValue: (60 * 1000)); +``` + + +
+ +## Environment flavours + +In {{ config('app.name') }}, you can build your application from different environment 'flavours'. This allows you to **create** separate `.env` files e.g. 'production', 'staging' or 'developing' and then build your app from the configuration. + +### Creating an environment flavour + +First, make a new file at the root level of your project, e.g. `.env.production`. Then, you can define all your env variables in this file. + +Next, add the new env file to your **'assets'** in the `pubspec.yaml` file. + +**pubspec.yaml** + +``` dart + assets: + ... + - .env + - .env.production // -- new +``` + +Then, from the terminal, build your Flutter app by specifying the **env** file like in the below example. + +``` bash +flutter run --dart-define="ENV_FILE=.env.production" + +flutter run --dart-define="ENV_FILE=.env.developing" + +// or build + +flutter build ios --dart-define="ENV_FILE=.env.developing" + +flutter build appbundle --dart-define="ENV_FILE=.env.developing" +``` + +> The `ENV_FILE` will default to `.env` diff --git a/resources/docs/5.20.0/controllers.md b/resources/docs/5.20.0/controllers.md new file mode 100644 index 0000000..0c36360 --- /dev/null +++ b/resources/docs/5.20.0/controllers.md @@ -0,0 +1,212 @@ +# Controllers + +--- + + +- [Introduction](#introduction "Introduction to controllers") +- [Creating pages and controllers](#creating-pages-and-controllers "Creating pages and controllers") +- [Using controllers](#using-controllers "Using controllers") +- [Singleton Controller](#singleton-controller "Singleton Controller") + + +
+ +## Introduction + +Before starting, let's go over what a controller is for those new. + +Here's a quick summary from tutorialspoint.com. + +> The Controller is responsible for controlling the application logic and acts as the coordinator between the View and the Model. The Controller receives an input from the users via the View, then processes the user's data with the help of Model and passes the results back to the View. + +
+ +Controller with services +``` dart +... +class HomeController extends Controller { + + AnalyticsService analyticsService; + NotificationService notificationService; + + @override + construct(BuildContext context) { + // example services + analyticsService = AnalyticsService(); + notificationService = NotificationService(); + } + + bool sendMessage(String message) async { + bool success = await this.notificationService.sendMessage(message); + if (success == false) { + this.analyticsService.sendError("failed to send message"); + } + return success; + } + + onTapDocumentation() { + launch("https://nylo.dev/docs"); + } + + ... +``` + +
+ +``` dart +class _MyHomePageState extends NyState { + ... + MaterialButton( + child: Text("Documentation"), + onPressed: widget.controller.onTapDocumentation, // call the action + ), +``` + +If your widget has a controller, you can use `widget.controller` to access its properties. + +You can use `dart run nylo_framework:main make:page account --controller` command to create a new page and controller automatically for you. + + +
+ +## Creating pages and controllers + +You can use the Metro CLI tool to create your pages & controllers automatically. + +``` dart +dart run nylo_framework:main make:page dashboard_page --controller +// or +dart run nylo_framework:main make:page dashboard_page -c +``` + +This will create a new controller in your **app/controllers** directory and a page in your **resources/pages** directory. + +Or you can create a single controller using the below command. + +``` dart +dart run nylo_framework:main make:controller profile_controller +``` + + +#### Retrieving arguments from routes + +If you need to pass data from one widget to another, you can send the data using `Navigator` class or use the `routeTo` helper. + +``` dart +// Send an object to another page +User user = new User(); +user.firstName = 'Anthony'; + +routeTo(ProfilePage.path, data: user); +``` + +When we navigate to the 'Profile' page, we can call `data()` to get the data from the previous page. + +``` dart +... +class _ProfilePageState extends NyState { + + @override + init() async { + super.init(); + dynamic data = widget.data(); // data passed from previous page + + User user = data; + print(user.firstName); // Anthony + } +``` + +The `routeTo(String routeName, data: dynamic)` **data** parameter accepts dynamic types so you can cast the object after it’s returned. + + +
+ +## Using controllers + +In your page, you can access the controller using `widget.controller`. + +Your controller must added to the NyStatefulWidget class like in the below example: + +`NyStatefulWidget` + +``` dart +import 'package:nylo_framework/nylo_framework.dart'; +import '/app/controllers/my_controller.dart'; + +class HomePage extends NyStatefulWidget { + + static const path = '/home'; + HomePage() : super(path, child: _HomePageState()); +} + +class _HomePageState extends NyState { + + // init - called when the page is created + init() async { + // access the controller + widget.controller.data(); // data passed from a previous page + widget.controller.queryParameters(); // query parameters passed from a previous page + } + + @override + Widget view(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("My Page"), + ), + body: Column( + children: [ + Text("My Page").onTap(() { + // call an action from that controller + widget.controller.doSomething(); + }), + TextField( + controller: widget.controller.myController, + // access the controller's properties + ), + ], + ) + ); + } +} +``` + +### Controller Decoders + +In {{ config('app.name') }} your project will contain a `config/decoders.dart` file. + +Inside this file there is a variable named `controllers` which is a map of all your controllers. + +``` dart +import 'package:nylo_framework/nylo_framework.dart'; +... + +final Map controllers = { + HomeController: () => HomeController(), + + MyNewController: () => MyNewController(), // new controller + // ... +}; +``` + + +
+ +## Singleton controller + +You can use the `singleton` property in your controller to make it a singleton. + +``` dart +import 'package:nylo_framework/nylo_framework.dart'; + +class HomeController extends Controller { + + @override + bool get singleton => true; + + ... +``` + +This will make sure that the controller is only created once and will be reused throughout the app. + + diff --git a/resources/docs/5.20.0/decoders.md b/resources/docs/5.20.0/decoders.md new file mode 100644 index 0000000..ebbe4d4 --- /dev/null +++ b/resources/docs/5.20.0/decoders.md @@ -0,0 +1,128 @@ +# Decoders + +--- + + +- [Introduction](#introduction "Introduction") +- Usage + - [Model Decoders](#model-decoders "Model Decoders") + - [API Decoders](#api-decoders "API Decoders") + + + +
+## Introduction + +Decoders are a new concept introduced in {{ config('app.name') }} which allows you to decode data into objects or classes. +You'll likely use decoders when dealing with the [networking](/docs/{{$version}}/networking) class or if you want to use the `api` helper in {{ config('app.name') }}. + +> By default, the location for decoders is `lib/config/decoders.dart` + +The decoders.dart file will contain two variables: +- [modelDecoders](#model-decoders) - Handles all your model decoders +- [apiDecoders](#api-decoders) - Handles all your API decoders + + +
+ +## Model decoders + +Model decoders are new in {{ config('app.name') }}, they provide a way for you to morph data payloads into model representations. + +The `network()` helper method will use the `modelDecoders` variable inside your config/decoders.dart file to determine which decoder to use. + +Here's an example. + +Here's how the `network` helper uses modelDecoders. + +```dart +class ApiService extends BaseApiService { + ApiService({BuildContext? buildContext}) : super(buildContext); + + @override + String get baseUrl => "https://jsonplaceholder.typicode.com"; + + Future fetchUsers() async { + return await network( + request: (request) => request.get("/users"), + ); + } +... +``` + +The `fetchUsers` method will automatically decode the payload from the request into a `User`. + +How does this work? + +You have a `User` class like the below. + +```dart +class User { + String? name; + String? email; + + User.fromJson(dynamic data) { + this.name = data['name']; + this.email = data['email']; + } + + toJson() => { + "name": this.name, + "email": this.email + }; +} +``` + +You can see from the above that this class has a `fromJson` method which provides us with a way to initialize the class. + +We can initialize this class by calling the below method. + +```dart +User user = User.fromJson({'name': 'Anthony', 'email': 'agordon@mail.com'}); +``` + +Now, to set up our decoders, we have to do the following. + +File: config/decoders.dart +```dart +final modelDecoders = { + List: (data) => List.from(data).map((json) => User.fromJson(json)).toList(), + + User: (data) => User.fromJson(data), + + // ... +}; +``` + +In the modelDecoders file, we need to provide the `Type` as the key and handle the morph in the value like the above example. + +The `data` argument will contain the payload from the API request. + + +
+ +## API decoders + +API decoders are used when calling the `api` helper method. + +```dart +loadUser() async { + User user = await api((request) => request.fetchUser()); +} +``` + +The `api` helper will match the correct API Service using generics, so you can call the below helper to access your service. + +```dart +await api((request) => request.callMyMethod()); +``` + +Before using the `api` helper, you will need to first add your API Service into lib/config/decoders.dart > apiDecoders. + +```dart +final Map apiDecoders = { + ApiService: ApiService(), + + // ... +}; +``` diff --git a/resources/docs/5.20.0/directory-structure.md b/resources/docs/5.20.0/directory-structure.md new file mode 100644 index 0000000..b6a3252 --- /dev/null +++ b/resources/docs/5.20.0/directory-structure.md @@ -0,0 +1,121 @@ +# Directory Structure + +--- + + +- [Introduction](#introduction "Introduction to Directory structures in {{ config('app.name') }}") +- [App Directories](#app-directories "App directories") +- [Public assets](#public-assets "Public assets") + - [Retrieving image assets](#retrieving-image-assets "Retrieving image assets") + - [Retrieving public assets](#retrieving-public-assets "Retrieving public assets") + + +
+ +## Introduction + +Every {{ config('app.name') }} project comes with a simple boilerplate for managing your files. It has this structure to streamline the development of your projects. + +The directory structure was inspired by Laravel. + + +
+ +## App Directories + +The below app directories are listed inside the lib folder. + +- `app` This folder includes any files relating to models, controllers and networking. + - `controllers` Include your controllers here for your Widget pages. + - `models` Create your model classes here. + - `networking` Add any API services here for managing APIs or fetching data from the internet. + - `events` Add all your event classes here. + - `providers` Add any provider classes here that need booting when your app runs. + +- `config` This folder contains configuration files such as your font, theme and localization settings. + - `design` Manage the font, logo and loader with this file. + - `theme` Set and configure the themes you want your flutter app to use. + - `localization` Manage the localization, language and other things relating to locale in this file. + - `decoders` Register modelDecoders and apiDecoders. + - `storage keys` Contains your local storage keys. + - `events` Register your events in the Map object. + - `providers` Register your providers in the Map object. + - `validation_rules` Register your custom validation rules. + - `toast_notification_styles` Register your toast notification styles. + +- `resources` This folder includes any files that are key components for your user's UI experience like pages, widgets and themes. + - `pages` Include your Widgets here that you will use as Page's in your project. E.g. home\_page.dart. + - `themes` By default, you'll find two themes here for light and dark mode, but you can add more. + - `widgets` Any widgets you need to create can be inserted here, like a date\_picker.dart file. + +- `routes` This folder includes any files relating to routing. + - `router.dart` You can add your page routes in this file. + + +
+ +## Public assets + +Public assets can be found in the `public/assets`. This directory is used for images, fonts and more files that you may want to include in your project. + +- `app_icon` This is used for generating app\_icons for the project. +- `images` Include any images here in this directory. +- `fonts` Add any custom fonts here. +- `postman` + - `collections` Add your postman collections here. + - `environments` Add your postman environments here. + + +
+ +## Retrieving an image asset + +You can use the normal, standard way in Flutter by running the following: +``` dart +Image.asset( + Image.asset('public/assets/images/my_logo.png'), + height: 50, + width: 50, +), +``` + +Or you can use `getImageAsset(String key)` helper + +``` dart +Image.asset( + getImageAsset("my_logo.png"), + height: 50, + width: 50, +), +``` + +Lastly, with the `localAsset` extension helper + +``` dart +Image.asset( + "my_logo.png", + height: 50, + width: 50, +).localAsset(), +``` + +In this example, our public/assets/images/ directory has one file `nylo_logo.png`. + +- public/assets/images/nylo_logo.png + + +
+ +## Retrieving a public asset + +You can get any public asset using `getPublicAsset(String key)` + +``` dart +VideoPlayerController.asset( + getPublicAsset('videos/intro.mp4') +); +``` + +In this example, our `public/assets/videos/` directory has one file `intro.mp4`. + +- public/assets/images/intro.mp4 diff --git a/resources/docs/5.20.0/events.md b/resources/docs/5.20.0/events.md new file mode 100644 index 0000000..2d8e5a0 --- /dev/null +++ b/resources/docs/5.20.0/events.md @@ -0,0 +1,251 @@ +# Events + +--- + + +- [Introduction](#introduction "Introduction") + - [Understanding Events](#understanding-events "Understanding Events") +- [Practical example](#practical-example "Practical example") +- Usage + - [Dispatching Events](#dispatching-events "Dispatching Events") + - [Setup Listeners](#setup-listeners "Setup Listeners") + - [Listeners Handle Method](#listeners-handle-method "Listeners Handle Method") + + +
+ +## Introduction + +Events are powerful when you need to handle logic after something happens in your application. +{{ config('app.name') }} provides a simple implementation of events that allows you to call listeners registered to the event. Listeners can perform logic from the event's payload. + +> {{ config('app.name') }} events are managed in the `app/events/` directory. +> +> To register your events, add them into your `config/events.dart` map. + + +
+ +## Understanding Events + +Events, also known as 'Event-driven programming' is a programming paradigm in which the flow of the program is determined by events such as user actions, sensor outputs, or messages passing from other programs or threads. + +### Examples of events + +Here are some examples of events your application might have: +- User Registers +- User login +- Product added to cart +- Successful payment + +In {{ config('app.name') }}, after creating an Event, you can add your listeners to that event that should handle your desired needs. + +If we use the last example "Successful payment", we might have to do the following things: + +1. Clear the user's cart & any checkout sessions stored locally +2. Use a service like Google Tag Manager to log the purchase for analytics + +In the next section, we'll show a real world example. + + +
+ +## Practical Example + +In this practical example, we'll imagine we own an e-commerce app selling t-shirts. + +When user's make a successful payment, we want to dispatch an event to handle the following things: + +- Clear the user's cart +- Use Google Tag Manager to log the event for analytics + +#### First, create an event + +```dart +dart run nylo_framework:main make:event payment_successful_event +``` + +After the event is created, you'll be able to view it in your `lib/app/events/` directory. + +If we open the file we just created lib/app/events/payment_successful_event.dart you should see the below class. + +``` dart +import 'package:nylo_framework/nylo_framework.dart'; + +class PaymentSuccessfulEvent implements NyEvent { + + final listeners = { + DefaultListener: DefaultListener(), + }; +} + +class DefaultListener extends NyListener { + handle(dynamic event) async { + // handle the payload from event + } +} +``` + +In this file, we'll add two new listeners, `SanitizeCheckoutListener` and `GTMPurchaseListener`. + +Here's the implementation for that in our event. + +``` dart +import 'package:nylo_framework/nylo_framework.dart'; +import 'package:google_tag_manager/google_tag_manager.dart' as gtm; + +class PaymentSuccessfulEvent implements NyEvent { + + final listeners = { + SanitizeCheckoutListener: SanitizeCheckoutListener(), + GTMPurchaseListener: GTMPurchaseListener(), + }; +} + +class SanitizeCheckoutListener extends NyListener { + handle(dynamic event) async { + // clear the cart in storage + await NyStorage.store('cart', null); // clear the cart in storage + } +} + +class GTMPurchaseListener extends NyListener { + handle(dynamic event) async { + // Get payload from event + Order order = event['order']; + + // Push event to gtm (Google tag manager). + gtm.push({ + 'ecommerce': { + 'purchase': { + 'actionField': { + 'id': order.id, // Transaction ID. Required for purchases and refunds. + 'revenue': order.revenue, // Total transaction value (incl. tax and shipping) + 'tax': order.tax, + 'shipping': order.shipping + }, + 'products': order.line_items + } + } + }); + } +} +``` + +Next, open your config/events.dart file and register the `PaymentSuccessfulEvent` class. + +```dart +import 'package:flutter_app/app/events/payment_successful_event.dart'; + +final Map events = { + LoginEvent: LoginEvent(), + LogoutEvent: LogoutEvent(), + PaymentSuccessfulEvent: PaymentSuccessfulEvent() +}; +``` + +Now we can dispatch the event from our application when a user makes a purchase. + +```dart +stripePay(List products) async { + + // create the order + Order? order = await api((api) => api.createOrder(products)); + if (order == null) { + return; + } + + // send event + await event(data: {'order': order}); +... +``` + +This is all we need to use our new event. + + +
+ +## Dispatching Events + +You can dispatch events by calling the below method: + +```dart +loginUser() async { + User user = await _apiService.loginUser('email': '...', 'password': '...'); + + event(data: {'user': user}); +} +``` + +This will dispatch the `LoginEvent` event. + +> It's important that your `LoginEvent` class is registered in your config/events.dart file. + + +
+ +## Setup listeners + +Listeners are added to events. Your listener must extend the NyListener class and have a `handle` method to work. + + + +Here's an example. + +```dart +import 'package:nylo_framework/nylo_framework.dart'; + +class LoginEvent implements NyEvent { + + final listeners = { + DefaultListener: DefaultListener(), + AuthUserListener: AuthUserListener(), + }; +} + +class DefaultListener extends NyListener { + handle(dynamic event) async { + // handle the payload from event + } +} + +class AuthUserListener extends NyListener { + handle(dynamic event) async { + // handle the payload from event + } +} + +``` + + +
+ +## Listener's handle method + +The `handle(dynamic event)` method is where you can add all your logic for the listener. + +The `event` argument in the `handle` will contain any data from the event, below is an example. + +```dart +// From your widget +loginUser() async { + User user = await _apiService.loginUser('email': '...', 'password': '...'); + + event({'user': user}); +} + +// Login event file +... +class LoginEvent implements NyEvent { + + final listeners = { + DefaultListener: DefaultListener(), + }; +} + +class DefaultListener extends NyListener { + handle(dynamic event) async { + print(event['user']); // instance of User + } +} +``` diff --git a/resources/docs/5.20.0/installation.md b/resources/docs/5.20.0/installation.md new file mode 100644 index 0000000..647868d --- /dev/null +++ b/resources/docs/5.20.0/installation.md @@ -0,0 +1,125 @@ +# Installation + +--- + + +- [Install](#install "Install") +- [Running the project](#running-the-project "Running the project") +- [Metro CLI](#metro-cli "Metro CLI") + - [Installing Metro Alias (Mac)](#installing-metro-alias "Installing Metro Alias (Mac)") + + +
+ +## Install + +You can either download {{ config('app.name') }} here or clone the git repository using the below command. + +```bash +git clone https://github.com/nylo-core/nylo.git nylo_app +``` + +> Note: run `flutter pub get` when opening the project for the first time to fetch all dependencies. + + +
+ +## Running the project + +{{ config('app.name') }} projects run in the exact 'normal' way you'd build a Flutter app. Depending on which IDE you have chosen, this part will be slightly different. + +Check this guide here for Android Studio or Visual Studio Code. + +Once you have done the above steps, try running the project. +If the build is successful, the app will display {{ config('app.name') }}'s **default** landing screen. + + + +
+ +## Metro CLI tool + +{{ config('app.name') }} provides a CLI tool called Metro. +It's been built, so you can run commands in the terminal to create things. With Metro, you can create the following in your project: + +- Models +- Controllers +- Pages +- Stateful widgets and stateless widgets +- Events +- Providers +- API Services +- Themes +- Route Guards +- Configs +- Interceptors + +E.g. Running `dart run nylo_framework:main make:model Property` will create a new '**Property**' model in your project. + +To access the menu, you can run the below in the terminal. + +`dart run nylo_framework:main` + + +
+ +## Installing Metro alias (Mac) + +Typing `dart run nylo_framework:main` each time you want to run a command is long. +So, to make things easier, create an alias. + +If you're new to aliases, they allow you to create alternative names for your commands. + +E.g. `dart run nylo_framework:main` can become `metro`. + +### Adding the alias + +In the terminal run the following: + +``` bash +sudo echo "alias metro='dart run nylo_framework:main'" >>~/.bash_profile && source ~/.bash_profile +``` + +> This will add the `metro` alias to your bash_profile and reload it. + +--- + +Try using `metro` in your terminal. You should see the below output. + +``` bash +Metro - Nylo's Companion to Build Flutter apps by Anthony Gordon + +Usage: + command [options] [arguments] + +Options + -h + +All commands: + + make + make:controller + make:model + make:page + make:stateful_widget + make:stateless_widget + make:provider + make:event + make:api_service + make:interceptor + make:theme + make:route_guard + make:config + +publish + publish:slate +``` + +Now you can type `metro` from your terminal to run commands in your {{ config('app.name') }} project. + +E.g. `metro make:controller profile_controller` + +### Can't find your bash\_profile? + +If you are unsure where to add the above, check out some guides online for where to find your bash\_profile file. +The above example assumes that your bash_profile is in your `~/` location. diff --git a/resources/docs/5.20.0/localization.md b/resources/docs/5.20.0/localization.md new file mode 100644 index 0000000..6dcb33c --- /dev/null +++ b/resources/docs/5.20.0/localization.md @@ -0,0 +1,223 @@ +# Localization + +--- + + +- [Introduction](#introduction "Introduction to localization") +- [Adding localized files](#adding-localized-files "Adding localized files") +- Basics + - [Localizing text](#localizing-text "Localizing text") + - [Arguments](#arguments "Arguments") + - [Updating the locale](#updating-the-locale "Updating the locale") + - [Setting a default locale](#setting-a-default-locale "Settings a default locale") + + + +
+ +## Introduction + +Localizing our projects provides us with an easy way to change the language for users in different countries. + +If your app's primary Locale was **en** (English) but you also wanted to provide users in Spain with a Spanish version, localising the app would be your best option. + +Here's an example to localize text in your app using {{ config('app.name') }}. + +#### Example of an localized file: `lang/en.json` +``` json +{ + "documentation": "documentation", + "changelog": "changelog" +} +``` +#### Example widget +``` dart +... +ListView( + children: [ + Text( + "documentation".tr() + ), + // ... or + Text( + trans("documentation") + ) + ] +) +``` + +The above will display the text from the lang/en.json file. If you support more than one locale, add another file like lang/es.json and copy the keys but change the values to match the locale. +Here's an example. +#### Example of a English localized file: `lang/en.json` +``` json +{ + "documentation": "documentation", + "changelog": "changelog" +} +``` +#### Example of a Spanish localized file: `lang/es.json` +``` json +{ + "documentation": "documentación", + "changelog": "registro de cambios" +} +``` + + +
+ +## Adding Localized files + +Add all your localization files to the `lang/` directory. Inside here, you'll be able to include your different locales. E.g. es.json for Spanish or pt.json for Portuguese. + +#### Example: `lang/en.json` +``` dart +{ + "documentation": "documentation", + "changelog": "changelog", + "intros": { + "hello": "hello @{{first_name}}", + } +} +``` + +#### Example: `lang/es.json` +``` dart +{ + "documentation": "documentación", + "changelog": "registro de cambios", + "intros": { + "hello": "hola @{{first_name}}", + } +} +``` + + +Once you’ve added the `.json` files, you’ll need to include them within your pubspec.yaml file. + +Go to your **pubspec.yaml** file and then at the `assets` section, add the new files. + +> You can include as many locale files here but make sure you also include them within your pubspec.yaml assets. + + + +
+ +## Localizing text + +You can localize any text with a key from your lang `.json` file. + +``` dart +"documentation".tr() +// or +trans("documentation"); +``` + +You can also use nested keys in the json file. Here's an example below. + +#### Example: `lang/en.json` +``` json +{ + "changelog": "changelog", + "intros": { + "hello": "hello" + } +} +``` + +#### Example: `lang/es.json` +``` json +{ + "changelog": "registro de cambios", + "intros": { + "hello": "hola" + } +} +``` +#### Example using nested JSON keys +``` dart +"intros.hello".tr() +// or +trans("intros.hello"); +``` + + +
+ +### Arguments + +You can supply arguments to fill in values for your keys. In the below example, we have a key named **"intros.hello_name"**. It has one fillable value named **"first_name"** to fill this value, pass a value to the method below. + +#### Example: `lang/en.json` +``` json +{ + "changelog": "changelog", + "intros": { + "hello_name": "hello @{{first_name}}", + } +} +``` + +#### Example: `lang/es.json` +``` json +{ + "changelog": "registro de cambios", + "intros": { + "hello_name": "hola @{{first_name}}" + } +} +``` + +Example to fill arguments in your JSON file. +``` dart +"intros.hello_name".tr(arguments: {"first_name": "Anthony"}) // Hello Anthony +// or +trans("intros.hello_name", arguments: {"first_name": "Anthony"}); // Hello Anthony +``` + + +
+ +## Updating the locale + +Updating the locale in the app is simple in {{ config('app.name') }}, you can use the below method in your widget. + +``` dart +String language = 'es'; // country code must match your json file e.g. pt.json would be 'pt + +await NyLocalization.instance.setLanguage(context, language: language); // Switches language +``` + +This will update the locale to Spanish. + +If your widget extends the `NyState` class, you can set the locale by calling the `changeLanguage` helper. + +Example below. + +```dart +class _MyHomePageState extends NyState { +... + InkWell( + child: Text("Switch to Spanish"), + onTap: () async { + await changeLanguage('es'); + }, + ) +``` + +This is useful if you need to provide users with a menu to select a language to use in the app. +E.g. if they navigated to a settings screen with language flags and selected Spanish. + + + +
+ +## Setting a default locale + +You may want to update the default locale when users open your app for the first time, follow the steps below to see how. +1. First, open the `.env` file. +2. Next, update the `DEFAULT_LOCALE` property to your Locale, like the below example. + +``` dart +DEFAULT_LOCALE="es" // e.g. for Spanish and you'll then need to add your new .json file in /lang/es.json +``` diff --git a/resources/docs/5.20.0/logging.md b/resources/docs/5.20.0/logging.md new file mode 100644 index 0000000..95c4280 --- /dev/null +++ b/resources/docs/5.20.0/logging.md @@ -0,0 +1,93 @@ +# Logging + +--- + + +- [Introduction](#introduction "Introduction") +- [Log Levels](#log-levels "Log Levels") +- [Helpers](#helpers "Helpers") + + + +
+ +## Introduction + +When you need to know what's happening in your application, use the `NyLogger` class. **{{ config('app.name') }}** provides a reliable logging tool you can use to **print** information to the console. + +Example using `NyLogger` + + +``` dart +import 'package:nylo_framework/nylo_framework.dart'; +... + +String name = 'Anthony'; +String city = 'London'; +int age = 18; + +NyLogger.info(name); // [2024-01-01 11:30:30] Anthony + +age.dump(); // [2024-01-01 11:30:30] 18 + +dunp(city); // [2024-01-01 11:30:30] London +``` + +#### Why use NyLogger? + +NyLogger may appear similar to `print` in Flutter, however, there's more to it. + +If your application's **.env** variable `APP_DEBUG` is set to false, NyLogger will **not** print to the console. + +In some scenarios you may want to print while your application's APP_DEBUG is false, the `showNextLog` helper can be used for that. + +``` dart +// .env +APP_DEBUG=false + +// usage for showNextLog +String name = 'Anthony'; +String country = 'UK'; +List favouriteCountries = ['Spain', 'USA', 'Canada']; + +NyLogger.info(name); + +showNextLog(); +NyLogger.debug(country); // UK + +NyLogger.debug(favouriteCountries); +``` + + +
+ +## Log Levels + +You can use the following log levels: + +- NyLogger.info(dynamic message) +- NyLogger.debug(dynamic message) +- NyLogger.dump(dynamic message) +- NyLogger.error(dynamic message) +- NyLogger.json(dynamic message) + + + +
+ +## Helpers + +You can print data easily using the `dump` or `dd` extension helpers. +They can be called from your objects, like in the below example. + +``` dart +String project = 'Nylo'; +List seasons = ['Spring', 'Summer', 'Fall', 'Winter']; + +project.dump(); // 'Nylo' +seasons.dump(); // ['Spring', 'Summer', 'Fall', 'Winter'] + +String code = 'Dart'; + +code.dd(); // Prints: 'Dart' and exits the code +``` diff --git a/resources/docs/5.20.0/metro.md b/resources/docs/5.20.0/metro.md new file mode 100644 index 0000000..79373a2 --- /dev/null +++ b/resources/docs/5.20.0/metro.md @@ -0,0 +1,617 @@ +# Metro CLI tool + +--- + + +- [Introduction](#introduction "Introduction") +- [Install](#install "Installing Metro Alias for {{ config('app.name') }}") +- Make Commands + - [Make controller](#make-controller "Make a new controller with Metro") + - [Make model](#make-model "Make a new model with Metro") + - [Make page](#make-page "Make a new page with Metro") + - [Make stateless widget](#make-stateless-widget "Make a new stateless widget with Metro") + - [Make stateful widget](#make-stateful-widget "Make a new stateful widget with Metro") + - [Make API Service](#make-api-service "Make a new API Service with Metro") + - [Using Postman](#make-api-service-using-postman "Create API services with Postman") + - [Make Event](#make-event "Make a new Event with Metro") + - [Make Provider](#make-provider "Make a new provider with Metro") + - [Make Theme](#make-theme "Make a new theme with Metro") + - [Make Route Guard](#make-route-guard "Make a new route guard with Metro") +- App Icons + - [Building App Icons](#build-app-icons "Building App Icons with Metro") + + + +
+## Introduction + +Metro is a CLI tool that works under the hood of the {{ config('app.name') }} framework. +It provides a lot of helpful tools to speed up development. + + +
+## Install + +Mac guide + +**Run the below command from the terminal** + +``` bash +echo "alias metro='dart run nylo_framework:main'" >>~/.bash_profile && source ~/.bash_profile +``` + +If you open a project that uses {{ config('app.name') }}, try to run the following in the terminal. + +``` bash +metro +``` + +You should see an output similar to the below. + +``` bash +Usage: + command [options] [arguments] + +Options + -h + +All commands: + + make + make:controller + make:model + make:page + make:stateless_widget + make:stateful_widget + make:theme + make:event + make:provider + make:api_service + make:theme + make:route_guard +``` + + +
+ +## Make controller + +- [Making a new controller](#making-a-new-controller "Make a new controller with Metro") +- [Forcefully make a controller](#forcefully-make-a-controller "Forcefully make a new controller with Metro") + +
+ +### Making a new controller + +You can make a new controller by running the below in the terminal. + +``` bash +dart run nylo_framework:main make:controller profile_controller +# or with the alias metro +metro make:controller profile_controller +``` + +This will create a new controller if it doesn't exist within the `lib/app/controllers/` directory. + + +
+ +### Forcefully make a controller + +**Arguments:** + +Using the `--force` or `-f` flag will overwrite an existing controller if it already exists. + +``` bash +metro make:controller profile_controller --force +``` + + +
+ +## Make model + +- [Making a new model](#making-a-new-model "Make a new model with Metro") +- [Make model from JSON](#make-model-from-json "Make a new model from JSON with Metro") +- [Forcefully make a model](#forcefully-make-a-model "Forcefully make a new model with Metro") + +
+ +### Making a new model + +You can make a new model by running the below in the terminal. + +``` bash +dart run nylo_framework:main make:model product +# or with the alias metro +metro make:model product +``` + +It will place the newly created model in `lib/app/models/`. + + +
+ +### Make a model from JSON + +**Arguments:** + +Using the `--json` or `-j` flag will create a new model from a JSON payload. + +``` bash +dart run nylo_framework:main make:model product --json +# or with the alias metro +metro make:model product --json +``` + +Then, you can paste your JSON into the terminal and it will generate a model for you. + + +
+ +### Forcefully make a model + +**Arguments:** + +Using the `--force` or `-f` flag will overwrite an existing model if it already exists. + +``` bash +dart run nylo_framework:main make:model product --force +# or with the alias metro +metro make:model product --force +``` + + +
+ +## Make page + +- [Making a new page](#making-a-new-page "Make a new page with Metro") +- [Create a page with a controller](#create-a-page-with-a-controller "Make a new page with a controller with Metro") +- [Create an auth page](#create-an-auth-page "Make a new auth page with Metro") +- [Create an initial page](#create-an-initial-page "Make a new initial page with Metro") +- [Create a bottom navigation page](#create-a-bottom-navigation-page "Make a new bottom navigation page with Metro") +- [Forcefully make a page](#forcefully-make-a-page "Forcefully make a new page with Metro") + + +
+ +### Making a new page + +You can make a new page by running the below in the terminal. + +``` bash +dart run nylo_framework:main make:page product_page +# or with the alias metro +metro make:page product_page +``` + +This will create a new page if it doesn't exist within the `lib/resources/pages/` directory. + + +
+ +### Create a page with a controller + +You can make a new page with a controller by running the below in the terminal. + +**Arguments:** + +Using the `--controller` or `-c` flag will create a new page with a controller. + +``` bash +dart run nylo_framework:main make:page product_page --controller +# or with the alias metro +metro make:page product_page -c +``` + + +
+ +### Create an auth page + +You can make a new auth page by running the below in the terminal. + +**Arguments:** + +Using the `--auth` or `-a` flag will create a new auth page. + +``` bash +dart run nylo_framework:main make:page login_page --auth +# or with the alias metro +metro make:page login_page -a +``` + + +
+ +### Create an initial page + +You can make a new initial page by running the below in the terminal. + +**Arguments:** + +Using the `--initial` or `-i` flag will create a new initial page. + +``` bash +dart run nylo_framework:main make:page home_page --initial +# or with the alias metro +metro make:page home_page -i +``` + + +
+ +### Create a bottom navigation page + +You can make a new bottom navigation page by running the below in the terminal. + +**Arguments:** + +Using the `--bottom-nav` or `-b` flag will create a new bottom navigation page. + +``` bash +dart run nylo_framework:main make:page dashboard --bottom-nav +# or with the alias metro +metro make:page dashboard -b +``` + + +
+ +### Forcefully make a page + +**Arguments:** + +Using the `--force` or `-f` flag will overwrite an existing page if it already exists. + +``` bash +dart run nylo_framework:main make:page product_page --force +# or with the alias metro +metro make:page product_page --force +``` + + +
+ +## Make stateless widget + +- [Making a new stateless widget](#making-a-new-stateless-widget "Make a new stateless widget with Metro") +- [Forcefully make a stateless widget](#forcefully-make-a-stateless-widget "Forcefully make a new stateless widget with Metro") + +
+ +### Making a new stateless widget + +You can make a new stateless widget by running the below in the terminal. + +``` bash +dart run nylo_framework:main make:stateless_widget product_rating_widget +# or with the alias metro +metro make:stateless_widget product_rating_widget +``` + +The above will create a new widget if it doesn't exist within the `lib/resources/widgets/` directory. + + +
+ +### Forcefully make a stateless widget + +**Arguments:** + +Using the `--force` or `-f` flag will overwrite an existing widget if it already exists. + +``` bash +dart run nylo_framework:main make:stateless_widget product_rating_widget --force +# or with the alias metro +metro make:stateless_widget product_rating_widget --force +``` + + +
+ +## Make stateful widget + +- [Making a new stateful widget](#making-a-new-stateful-widget "Make a new stateful widget with Metro") +- [Forcefully make a stateful widget](#forcefully-make-a-stateful-widget "Forcefully make a new stateful widget with Metro") + + +
+ +### Making a new stateful widget + +You can make a new stateful widget by running the below in the terminal. + +``` bash +dart run nylo_framework:main make:stateful_widget product_rating_widget +# or with the alias metro +metro make:stateful_widget product_rating_widget +``` + +The above will create a new widget if it doesn't exist within the `lib/resources/widgets/` directory. + + +
+ +### Forcefully make a stateful widget + +**Arguments:** + +Using the `--force` or `-f` flag will overwrite an existing widget if it already exists. + +``` bash +dart run nylo_framework:main make:stateful_widget product_rating_widget --force +# or with the alias metro +metro make:stateful_widget product_rating_widget --force +``` + + +
+ +## Make API Service + +- [Making a new API Service](#making-a-new-api-service "Make a new API Service with Metro") +- [Making a new API Service with a model](#making-a-new-api-service-with-a-model "Make a new API Service with a model with Metro") +- [Make API Service using Postman](#make-api-service-using-postman "Create API services with Postman") +- [Forcefully make an API Service](#forcefully-make-an-api-service "Forcefully make a new API Service with Metro") + + +
+ +### Making a new API Service + +You can make a new API service by running the below in the terminal. + +``` bash +dart run nylo_framework:main make:api_service user +# or with the alias metro +metro make:api_service user_api_service +``` + +It will place the newly created API service in `lib/app/networking/`. + + +
+ +### Making a new API Service with a model + +You can make a new API service with a model by running the below in the terminal. + +**Arguments:** + +Using the `--model` or `-m` option will create a new API service with a model. + +``` bash +dart run nylo_framework:main make:api_service user --model="User" +# or with the alias metro +metro make:api_service user --model="User" +``` + +It will place the newly created API service in `lib/app/networking/`. + + +
+ +### Use Postman to Make API Services + +You can make new API services using Postman v2.1 collection files. + +First, you must export your postman collection into a JSON file (using v2.1). + +Copy the exported file into the `public/assets/postman/collections` directory. + +After copying the file into the above directory, go to the terminal and run the following command. + +**Arguments:** + +Using the `--postman` or `-p` option will create a new API service using a Postman collection. + +``` bash +dart run nylo_framework:main make:api_service my_collection_name --postman +# or with the alias metro +metro make:api_service my_collection_name --postman +``` + +This will prompt you to select a collection from the `public/assets/postman/collections` directory. + +#### Postman Environments + +If your postman collection has an environment, export the file and add it to the `public/assets/postman/environments` directory. + +Then, run the below command. + +``` bash +dart run nylo_framework:main make:api_service collection --postman +# or with the alias metro +metro make:api_service collection --postman +``` + +It will prompt you to select an environment from the `public/assets/postman/environments` directory. + +#### What does it do? + +It will also add **Models** to your project if your Postman collections have a saved response. +You can check if your collection has saved responses by going to your Postman collection, then selecting a request. You should see a dropdown which will contain any saved responses. + +The saved response **name** will be used for the **Model** name that {{ config('app.name') }} will create. + + +
+ +### Forcefully make an API Service + +**Arguments:** + +Using the `--force` or `-f` flag will overwrite an existing API Service if it already exists. + +``` bash +dart run nylo_framework:main make:api_service user --force +# or with the alias metro +metro make:api_service user --force +``` + + +
+ +## Make event + +- [Making a new event](#making-a-new-event "Make a new event with Metro") +- [Forcefully make an event](#forcefully-make-an-event "Forcefully make a new event with Metro") + + +
+ +### Making a new event + +You can make a new event by running the below in the terminal. + +``` bash +dart run nylo_framework:main make:event login_event +# or with the alias metro +metro make:event login_event +``` + +This will create a new event in `lib/app/events`. + + +
+ +### Forcefully make an event + +**Arguments:** + +Using the `--force` or `-f` flag will overwrite an existing event if it already exists. + +``` bash +dart run nylo_framework:main make:event login_event --force +# or with the alias metro +metro make:event login_event --force +``` + + +
+ +## Make provider + +- [Making a new provider](#making-a-new-provider "Make a new provider with Metro") +- [Forcefully make a provider](#forcefully-make-a-provider "Forcefully make a new provider with Metro") + + +
+ +### Making a new provider + +Create new providers in your application using the below command. + +``` bash +dart run nylo_framework:main make:provider firebase_provider +# or with the alias metro +metro make:provider firebase_provider +``` + +It will place the newly created provider in `lib/app/providers/`. + + +
+ +### Forcefully make a provider + +**Arguments:** + +Using the `--force` or `-f` flag will overwrite an existing provider if it already exists. + +``` bash +dart run nylo_framework:main make:provider firebase_provider --force +# or with the alias metro +metro make:provider firebase_provider --force +``` + + +
+ +## Make theme + +- [Making a new theme](#making-a-new-theme "Make a new theme with Metro") +- [Forcefully make a theme](#forcefully-make-a-theme "Forcefully make a new theme with Metro") + + +
+ +### Making a new theme + +You can make themes by running the below in the terminal. + +``` bash +dart run nylo_framework:main make:theme bright_theme +# or with the alias metro +metro make:theme bright_theme +``` + +This will create a new theme in `lib/app/themes`. + + +
+ +### Forcefully make a theme + +**Arguments:** + +Using the `--force` or `-f` flag will overwrite an existing theme if it already exists. + +``` bash +dart run nylo_framework:main make:theme bright_theme --force +# or with the alias metro +metro make:theme bright_theme --force +``` + + +
+ +## Make Route Guard + +- [Making a new route guard](#making-a-new-route-guard "Make a new route guard with Metro") +- [Forcefully make a route guard](#forcefully-make-a-route-guard "Forcefully make a new route guard with Metro") + + +
+ +### Making a new route guard + +You can make a route guard by running the below in the terminal. + +``` bash +dart run nylo_framework:main make:route_guard premium_content +# or with the alias metro +metro make:route_guard premium_content +``` + +This will create a new route guard in `lib/app/route_guards`. + + +
+ +### Forcefully make a route guard + +**Arguments:** + +Using the `--force` or `-f` flag will overwrite an existing route guard if it already exists. + +``` bash +dart run nylo_framework:main make:route_guard premium_content --force +# or with the alias metro +metro make:route_guard premium_content --force +``` + + +
+ +## Build App Icons + +You can generate all the app icons for IOS and Android by running the below command. + +``` bash +dart run flutter_launcher_icons:main +``` + +This uses the flutter_icons configuration in your `pubspec.yaml` file. diff --git a/resources/docs/5.20.0/networking.md b/resources/docs/5.20.0/networking.md new file mode 100644 index 0000000..e3155b4 --- /dev/null +++ b/resources/docs/5.20.0/networking.md @@ -0,0 +1,713 @@ +# Networking + +--- + + +- [Introduction](#introduction "Introduction") +- [Making HTTP requests](#making-http-requests "Making HTTP requests") + - [Base Options](#base-options "Base Options") + - [Adding Headers](#adding-headers "Adding Headers") + - [Interceptors](#interceptors "Interceptors") + - [Understanding the network helper](#understanding-the-network-helper "Understanding the network helper") +- [Using an API Service](#using-an-api-service "Using an API Service") +- [Create an API Service](#create-an-api-service "Create an API Service") +- [Morphing JSON payloads to models](#morphing-json-payloads-to-models "Morphing JSON payloads to models") +- [Retrying failed requests](#retrying-failed-requests "retrying failed requests") +- [Refreshing tokens](#refreshing-tokens "Refreshing tokens") +- [Singleton API Service](#singleton-api-service "Singleton API Service") + + +
+ +## Introduction + +{{ config('app.name') }} makes networking in modern mobile applications simple. You can do GET, PUT, POST and DELETE requests via the base networking class. + +Your API Services directory is located here `app/networking/*` + +Fresh copies of {{ config('app.name') }} will include a default API Service `app/networking/api_service.dart`. + +```dart +class ApiService extends BaseApiService { + ApiService({BuildContext? buildContext}) : super(buildContext); + + @override + String get baseUrl => "https://jsonplaceholder.typicode.com"; + + @override + final interceptors = { + LoggingInterceptor: LoggingInterceptor() + }; + + Future fetchUsers() async { + return await network( + request: (request) => request.get("/users"), + ); +} +``` + +Variables you can override using the BaseApiService class. + +- `baseUrl` - This is the base URL for the API, e.g. "https://jsonplaceholder.typicode.com". +- `interceptors` - Here, you can add Dio interceptors. Learn more about interceptors here. +- `useInterceptors` - You can set this to true or false. It will let the API Service know whether to use your interceptors. + +Under the hood, the base networking class uses Dio, a powerful HTTP client. + + +
+ +## Making HTTP requests + +In your API Service, use the `network` method to build your API request. + +```dart +class ApiService extends BaseApiService { + ApiService({BuildContext? buildContext}) : super(buildContext); + + @override + String get baseUrl => "https://jsonplaceholder.typicode.com"; + + Future fetchUsers() async { + return await network( + request: (request) { + // return request.get("/users"); // GET request + // return request.put("/users", data: {"user": "data"}); // PUT request + // return request.post("/users", data: {"user": "data"}); // POST request + // return request.delete("/users/1"); // DELETE request + + return request.get("/users"); + }, + ); + } +``` + +The `request` argument is a Dio instance, so you can call all the methods from that object. + + +
+ +### Base Options + +The `BaseOptions` variable is highly configurable to allow you to modify how your Api Service should send your requests. + +Inside your API Service, you can override your constructor. + +```dart +class ApiService extends BaseApiService { + ApiService({BuildContext? buildContext}) : super(buildContext) { + baseOptions = BaseOptions( + receiveTimeout: 10000, // Timeout in milliseconds (10 seconds) + connectTimeout: 5000 // Timeout in milliseconds (5 seconds) + ); + } +... +``` + +Click here to view all the base options you can set. + + +
+ +### Adding Headers + +You can add headers to your requests either via your baseOptions variable, on each request or an interceptor. + +Here's the simplest way to add headers to a request. + +```dart +Future fetchWithHeaders() async => await network( + request: (request) => request.get("/test"), + headers: { + "Authorization": "Bearer aToken123", + "Device": "iPhone" + } +); +``` + +You can also add **Bearer Token's** like in the below example. + +```dart +Future fetchUserWithBearer() async => await network( + request: (request) => request.get("/user"), + bearerToken: "hello-world-123", +); +``` + +Or lastly, like the below. + +```dart +... +Future fetchUsers() async { + return await network( + request: (request) { + request.options.headers = { + "Authorization": "Bearer $token" + }; + + return request.get("/users"); + }, + ); +} + +``` + + +
+ +### Interceptors + +If you're new to interceptors, don't worry. They're a new concept for managing how your HTTP requests are sent. + +Put in simple terms. An 'interceptor' will intercept the request, allowing you to modify the request before it's sent, handle the response after it completes and also what happens if there's an error. + +{{ config('app.name') }} allows you to add new interceptors to your API Services like in the below example. + +```dart +class ApiService extends BaseApiService { + ApiService({BuildContext? buildContext}) : super(buildContext); + ... + + @override + final interceptors = { + LoggingInterceptor: LoggingInterceptor(), + + // Add more interceptors for the API Service + // BearerAuthInterceptor: BearerAuthInterceptor(), + }; + +... +``` + +Let's take a look at an interceptor. + +Example of a custom interceptor. + +```dart +import 'package:nylo_framework/nylo_framework.dart'; + +class CustomInterceptor extends Interceptor { + @override + void onRequest(RequestOptions options, RequestInterceptorHandler handler) { + // options - modify the RequestOptions before the request + + return super.onRequest(options, handler); + } + + @override + void onResponse(Response response, ResponseInterceptorHandler handler) { + // response - handle/manipulate the Response object + + handler.next(response); + } + + @override + void onError(DioError error, ErrorInterceptorHandler handler) { + // error - handle the DioError object + handler.next(err); + } +} +``` + +Fresh copies on {{ config('app.name') }} will include a `app/networking/dio/intecetors/*` directory. + +### Creating a new interceptor + +You can create a new interceptor using the command below. + +```bash +# Run this command in your terminal +dart run nylo_framework:main make:interceptor logging_interceptor +``` + + +File: `app/networking/dio/intecetors/logging_interceptor.dart` +```dart +import 'dart:developer'; +import 'package:nylo_framework/nylo_framework.dart'; + +class LoggingInterceptor extends Interceptor { + @override + void onRequest(RequestOptions options, RequestInterceptorHandler handler) { + + print('REQUEST[${options.method}] => PATH: ${options.path}'); + + return super.onRequest(options, handler); + } + + @override + void onResponse(Response response, ResponseInterceptorHandler handler) { + + print( + 'RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions + .path}'); + + print('DATA: ${response.requestOptions.path}'); + + log(response.data.toString()); + + handler.next(response); + } + + @override + void onError(DioError err, ErrorInterceptorHandler handler) { + + print('ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions + .path}'); + + handler.next(err); + } +} +``` + + +
+ +## Understanding the network helper + +The `network` helper provides us with a way to make HTTP requests from our application. +The helper method can be accessed when using an API Service in {{ config('app.name') }}. + +```dart +class ApiService extends BaseApiService { + ... + + Future fetchUsers() async { + return await network( + request: (request) { + // return request.get("/users"); // GET request + // return request.put("/users", data: {"user": "data"}); // PUT request + // return request.post("/users", data: {"user": "data"}); // POST request + // return request.delete("/users/1"); // DELETE request + + return request.get("/users"); + }, + ); + } +``` + +### Return Types + +There are two ways to handle the response from an HTTP request. +Let's take a look at both in action, there's no right or wrong way to do this. + +#### Using Model Decoders + +Model Decoders are a new concept introduced in {{ config('app.name') }} v3.x. + +They make it easy to return your objects, like in the below example. + +```dart +class ApiService extends BaseApiService { + ... + + Future fetchUser() async { + return await network( + request: (request) => request.get("/users/1"), + ); + } +``` + +File: config/decoders.dart +```dart +final modelDecoders = { + User: (data) => User.fromJson(data), // add your model and handle the return of the object + + // ... +}; +``` + +The `data` parameter will contain the HTTP response body. + +Learn more about decoders here + + +#### Using handleSuccess + +The `handleSuccess: (Response response) {}` argument can be used to return a value from the HTTP body. + +This method is only called if the HTTP response has a status code equal to 200. + +Here's an example below. + +```dart +class ApiService extends BaseApiService { + ... + // Example: returning an Object + Future findUser() async { + return await network( + request: (request) => request.get("/users/1"), + handleSuccess: (Response response) { // response - Dio Response object + dynamic data = response.data; + return User.fromJson(data); + } + ); + } + // Example: returning a String + Future findMessage() async { + return await network( + request: (request) => request.get("/message/1"), + handleSuccess: (Response response) { // response - Dio Response object + dynamic data = response.data; + if (data['name'] == 'Anthony') { + return "It's Anthony"; + } + return "Hello world"; + } + ); + } + // Example: returning a bool + Future updateUser() async { + return await network( + request: (request) => request.put("/user/1", data: {"name": "Anthony"}), + handleSuccess: (Response response) { // response - Dio Response object + dynamic data = response.data; + if (data['status'] == 'OK') { + return true; + } + return false; + } + ); + } +``` + +#### Using handleFailure + +The `handleFailure` method will be called if the HTTP response returns a status code not equal to 200. + +You can provide the network helper with the `handleFailure: (DioError dioError) {}` argument and then handle the response in the function. + +Here's an example of how it works. + +```dart +class ApiService extends BaseApiService { + ... + // Example: returning an Object + Future findUser() async { + return await network( + request: (request) => request.get("/users/1"), + handleFailure: (DioError dioError) { // response - DioError object + dynamic data = response.data; + // Handle the response + + return null; + } + ); + } +} +``` + + +
+ +## Using an API Service + +When you need to call an API from a widget, there are two different approaches in {{ config('app.name') }}. + +1. You can create a new instance of the API Service and then call the method you want to use, like in the below example. + +```dart +class _MyHomePageState extends NyState { + + ApiService _apiService = ApiService(); + + @override + init() async { + List? users = await _apiService.fetchUsers(); + print(users); // List? instance +... +``` + +2. Use the `api` helper, this method is shorter and works by using your `apiDecoders` variable inside config/decoders.dart. +Learn more about decoders here. + +```dart +class _MyHomePageState extends NyState { + + @override + init() async { + User? user = await api((request) => request.fetchUser()); + print(user); // User? instance +... +``` + +Using the api helper also allows you to handle UI feedback to your users if the request isn't successful. +To do this, add the `context` parameter to the `api` helper, example below. + +```dart +// Your Widget +... + User _user = User(); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: MaterialButton( + onPressed: () { + _sendFriendRequest(_user); + }, + child: Text("Send Friend Request"), + ); + ); + } + + _sendFriendRequest(User user) async { + bool? successful = await api( + (request) => request.sendFriendRequest(user), + context: context + ); + } +... + +// Your API Service +class ApiService extends BaseApiService { + ... + + // Add this + displayError(DioException dioException, BuildContext context) { + showToastNotification(context, title: 'Oops!', description: dioError.message); + // or display the error however you want + } +} +``` + +`displayError` - If an error occurs with the request (e.g. 500 status code), you can instantly give your users feedback via a toast notification. + + +
+ +## Create an API Service + +To create more api_services, e.g. a `user_api_service`, use the command below. + +``` bash +dart run nylo_framework:main make:api_service user +``` + +You can also create an API Service for a model with the `--model="User"` option. + +``` bash +dart run nylo_framework:main make:api_service user --model="User" +``` + +This will create an API Service with the following methods. + +```dart +class UserApiService extends BaseApiService { + ... + + /// Return a list of users + Future?> fetchAll({dynamic query}) async { + return await network>( + request: (request) => request.get("/endpoint-path", queryParameters: query), + ); + } + + /// Find a User + Future find({required int id}) async { + return await network( + request: (request) => request.get("/endpoint-path/$id"), + ); + } + + /// Create a User + Future create({required dynamic data}) async { + return await network( + request: (request) => request.post("/endpoint-path", data: data), + ); + } + + /// Update a User + Future update({dynamic query}) async { + return await network( + request: (request) => request.put("/endpoint-path", queryParameters: query), + ); + } + + /// Delete a User + Future delete({required int id}) async { + return await network( + request: (request) => request.delete("/endpoint-path/$id"), + ); + } +} + +``` + + +
+ +## Morphing JSON payloads to models + +You can automatically decode JSON payloads from your API requests to models using decoders. + +Here is an API request that uses {{ config('app.name') }}'s implementation of decoders. + +```dart +class ApiService extends BaseApiService { + ApiService({BuildContext? buildContext}) : super(buildContext); + + Future fetchUsers() async { + return await network( + request: (request) => request.get("/users"), + ); + } +} +``` + +The `fetchUsers` method will handle the JSON conversion to the model representation using generics. + +You will first need to add your model to your `config/decoders.dart` file like the below. + +```dart +/// file 'config/decoders.dart' + +final modelDecoders = { + List: (data) => List.from(data).map((json) => User.fromJson(json)).toList(), + + User: (data) => User.fromJson(data), + + // ... +}; +``` + +Here you can provide the different representations of the Model, e.g. to object or list<Model> like the above. + +The data argument in the decoder will contain the body payload from the API request. + +To get started with decoders, check out this section of the [documentation](/docs/{{$version}}/decoders#model-decoders). + + +
+ +## Retrying failed requests + +In {{ config('app.name') }} v{{ $version }}, you can retry failed requests. + +If the response from the API request is an error status code (e.g. status code 500), the request will be retried. + +### Retrying an API request +You can retry failed requests using the `retry` method. + +```dart +Future fetchUsers() async { + return await network( + request: (request) => request.get("/users"), + retry: 3, // retry 3 times + ); +} +``` + +### Retry delay + +You can also set a **delay** between each retry attempt. + +```dart +Future fetchUsers() async { + return await network( + request: (request) => request.get("/users"), + retry: 3, // retry 3 times + retryDelay: Duration(seconds: 2), // retry after 2 second + ); +} +``` + +### Retry if + +You can also set a **condition** for when to retry the request. + +```dart +Future fetchUsers() async { + return await network( + request: (request) => request.get("/users"), + retry: 3, // retry 3 times + retryIf: (DioException dioException) { + // retry if the status code is 500 + return dioException.response?.statusCode == 500; + }, + ); +} +``` + + +
+ +## Refreshing tokens + +If your application needs to refresh tokens, you can handle this in your API Service. + +In {{ config('app.name') }}, you can override 3 methods in your API Service to handle token refreshing. + +- `refreshToken` - This method will be called when the API Service needs to refresh the token. +- `setAuthHeaders` - This method will be called before every request. You can add your auth headers here. +- `shouldRefreshToken` - This method will be called before every request. You can return true or false to determine whether to refresh the token. + +Let's take a look at all three in action. + +```dart +class ApiService extends NyApiService { + ApiService({BuildContext? buildContext}) : + super(buildContext, decoders: modelDecoders); + + Future getUser() async { + return await network( + request: (request) => request.get("/user") + ); + } + + @override + refreshToken(Dio dio) async { + dynamic response = (await dio.get("https://example.com/refresh-token")).data(); + // Save the new token to local storage + User user = Auth.user(); + user.token = Token.fromJson(response['token']); + await user.save(); + } + + @override + Future setAuthHeaders(RequestHeaders headers) async { + User? user = Auth.user(); + if (user != null) { + headers.addBearerToken( user.token ); + } + return headers; + } + + @override + Future shouldRefreshToken() async { + User? user = Auth.user(); + + if (user.token.expiredAt.isPast()) { + // Check if the token is expired + // This will trigger the refreshToken method + return true; + } + return false; + } +``` + +Now when you call the `getUser` method, the API Service will check if the token is expired and then refresh it if needed. + + +
+ +## Singleton API Service + +You can create an API Service as a singleton by updating the **apiDecoders** variable in your `config/decoders.dart` file. + +```dart +final Map apiDecoders = { + ApiService: () => ApiService(), // from this + + ApiService: ApiService(), // to this + ... +}; +``` + +Now when you call the `api` helper, it will return the same instance of the API Service. + +```dart +api((request) => request.fetchUsers()); +``` + +You can switch between singleton and non-singleton API Services if you need to. diff --git a/resources/docs/5.20.0/ny-future-builder.md b/resources/docs/5.20.0/ny-future-builder.md new file mode 100644 index 0000000..5ea6da0 --- /dev/null +++ b/resources/docs/5.20.0/ny-future-builder.md @@ -0,0 +1,76 @@ +# NyFutureBuilder + +--- + + +- [Introduction](#introduction "Introduction") +- [Customizing the NyFutureBuilder](#customizing-the-nyfuturebuilder "Customizing the NyFutureBuilder") + + + +
+ +## Introduction to NyFutureBuilder + +The **NyFutureBuilder** is a helpful widget for handling `Future`'s in your Flutter projects. +It will display a loader while the future is in progress, after the Future completes, it will return the data via the `child` parameter. + +Let's dive into some code. + +1. We have a Future that returns a String +2. We want to display the data on the UI for our user + +``` dart +// 1. Example future that takes 3 seconds to complete +Future _findUserName() async { + await Future.delayed(Duration(seconds: 3)); + return "John Doe"; +} + +// 2. Use the NyFutureBuilder widget +@override +Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Container( + child: NyFutureBuilder(future: _findUserName(), child: (context, data) { + // data = "John Doe" + return Text(data!); + },), + ), + ), + ); +} +``` + +> This widget will handle the loading on the UI for your users until the future completes. + + +
+ +## Customizing the NyFutureBuilder + +You can pass the following parameters to the `NyFutureBuilder` class to customize it for your needs. + +#### Options: + +``` dart +@override +Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: NyFutureBuilder( + future: NyStorage.read("product_name"), + child: (context, data) { + return Text(data); + }, + loading: CupertinoActivityIndicator(), // change the default loader + onError: (AsyncSnapshot snapshot) { // handle exceptions thrown from your future. + print(snapshot.error.toString()); + return Text("Error"); + }, + ) + ), + ); +} +``` diff --git a/resources/docs/5.20.0/ny-list-view.md b/resources/docs/5.20.0/ny-list-view.md new file mode 100644 index 0000000..6438199 --- /dev/null +++ b/resources/docs/5.20.0/ny-list-view.md @@ -0,0 +1,105 @@ +# NyListView + +--- + + +- [Introduction](#introduction "Introduction") +- [Usage](#usage "Usage") +- [Parameters](#parameters "Parameters") +- [Updating The State](#updating-the-state "Updating The State") + + + +
+ +## Introduction + +In this section, we will learn about the `NyListView` widget. + +The `NyListView` widget is a helpful widget for handling List Views in your Flutter projects. + +It works in the same way as the regular `ListView` widget, but it has some extra features that make it easier to use. + +Let's take a look at some code. + + +
+ +## Usage + +Here's how you can start using the `NyListView`. + +``` dart +@override +Widget build(BuildContext context) { +return NyListView(child: (BuildContext context, dynamic data) { + return ListTile( + title: Text(data['title']) + ); +}, data: () async { + return [ + {"title": "Clean Room"}, + {"title": "Go to the airport"}, + {"title": "Buy new shoes"}, + {"title": "Go shopping"}, + {"title": "Find my keys"} + ]; +}); +} +``` + +The `NyListView` widget requires two parameters: +- **child** - This is the widget that will be displayed for each item in the list. +- **data** - This is the data that will be displayed in the list. + +> Tip: You can use the `NyListView.separated` widget to add a divider between each item in the list. + + +
+ +## Parameters + +Here are some important parameters you should know about before using the `NyPullToRefresh` widget. + +| Property | Type | Description | +| --- | --- | --- | +| child | Widget Function(BuildContext context, dynamic data) {} | The child widget that will be displayed when the data is available. | +| data | Future Function() data | The list of data you want the list view to use. | +| stateName | String? stateName | You can name the state using `stateName`, later you will need this key to update the state. | + +If you would like to know all the parameters available, visit this link [here](https://github.com/nylo-core/support/blob/{{$version}}/lib/widgets/ny_list_view.dart). + + +
+ +## Updating the State + +You can update the state of a `NyListView` widget by referencing the `stateName` parameter. + +``` dart +// e.g. +@override + Widget build(BuildContext context) { + return NyListView( + child: (BuildContext context, dynamic data) { + return ListTile(title: Text(data['title'])); + }, + data: () async { + return [ + {"title": "Clean Room"}, + {"title": "Go to the airport"}, + {"title": "Buy new shoes"}, + {"title": "Go shopping"}, + ]; + }, + stateName: "my_list_of_todos", + ); + } + + +_updateListView() { + updateState("my_list_of_todos"); +} +``` + +This will trigger the State to reboot and load fresh data from the `data` parameter. diff --git a/resources/docs/5.20.0/ny-pull-to-refresh.md b/resources/docs/5.20.0/ny-pull-to-refresh.md new file mode 100644 index 0000000..31ffeb7 --- /dev/null +++ b/resources/docs/5.20.0/ny-pull-to-refresh.md @@ -0,0 +1,105 @@ +# NyPullToRefresh + +--- + + +- [Introduction](#introduction "Introduction") +- [Usage](#usage "Usage") +- [Parameters](#parameters "Parameters") +- [Updating The State](#updating-the-state "Updating The State") + + + +
+ +## Introduction + +In this section, we will learn about the `NyPullToRefresh` widget. + +The `NyPullToRefresh` widget is a helpful widget for handling 'pull to refresh' in your Flutter projects. + +If you're not familiar with 'pull to refresh', it's essentially a `ListView` that can fetch more data when a user scrolls to the bottom of the list. + +This makes it a great option for those with big data because you'll be able to paginate through the data in chunks. + +Let's dive into some code. + + +
+ +## Usage + +``` dart +@override + Widget build(BuildContext context) { + return NyPullToRefresh( + child: (context, data) { + return ListTile(title: Text(data['title'])); + }, + data: (int iteration) async { + return [ + {"title": "Clean Room"}, + {"title": "Go to the airport"}, + {"title": "Buy new shoes"}, + {"title": "Go shopping"}, + {"title": "Find my keys"}, + {"title": "Clear the garden"} + ].paginate(itemsPerPage: 2, page: iteration).toList(); + }, + stateName: "todo_list_view", + ); + } + +// or from an API Service +// this example uses the Separated ListView, it will add a divider between each item +@override + Widget build(BuildContext context) { + return NyPullToRefresh.separated( + child: (context, data) { + return ListTile(title: Text(data.title)); + }, + data: (int iteration) async { + // Example: List returned from an APIService + // the iteration parameter can be used for pagination + // each time the user pulls to refresh, the iteration will increase by 1 + return api((request) => request.getListOfTodos(), page: iteration); + }, + separatorBuilder: (context, index) { + return Divider(); + }, + stateName: "todo_list_view", + ); + } +``` + +When the returned data is an empty array, it will stop the pagination. + + +
+ +## Parameters + +Here are some important parameters you should know about before using the `NyPullToRefresh` widget. + +| Property | Type | Description | +| --- | --- | --- | +| child | Widget Function(BuildContext context, dynamic data) {} | The child widget that will be displayed when the data is available. | +| data | Future Function(int iteration) data | The list of data you want the list view to use. | +| stateName | String? stateName | You can name the state using `stateName`, later you will need this key to update the state. | + +If you would like to know all the parameters available, visit this link [here](https://github.com/nylo-core/support/blob/{{$version}}/lib/widgets/ny_pull_to_refresh.dart). + + +
+ +## Updating the State + +You can update the state of a `NyPullToRefresh` widget by referencing the `stateName` parameter. + +``` dart +_updateListView() { + updateState("todo_list_view"); +} +``` + +This will trigger the State to reboot and load fresh data from the `data` parameter. diff --git a/resources/docs/5.20.0/ny-state.md b/resources/docs/5.20.0/ny-state.md new file mode 100644 index 0000000..c5b1a32 --- /dev/null +++ b/resources/docs/5.20.0/ny-state.md @@ -0,0 +1,811 @@ +# NyState + +--- + + +- [Introduction](#introduction "Introduction") +- [How to use NyState](#how-to-use-nystate "How to use NyState") +- [State Management](#state-management "State Management") +- [Helpers](#helpers "Helpers") + + + +
+## Introduction + +When you create a page in {{ config('app.name') }}, it will extend the `NyState` class. This class provides useful utilities to make development easier. + +The `NyState` class can help you with the following: + +- Networking +- Loading data +- Themes +- Validation +- Navigation +- Managing the state +- Changing Language + + +
+ +## How to use NyState + +You can start using this class by extending it. + +Example + +``` dart +class _HomePageState extends NyState { + + @override + init() async { + + } +``` + +Once your page extends `NyState` you can initialize the widget using the `init` method. +This method is called inside `initState` from within your Flutter state and it makes it easier to call async functions. + +To create a new page in {{ config('app.name') }}, you can run the below command. + +``` bash +dart run nylo_framework:main make:page product_page +``` + +Or with the alias metro + +``` bash +metro make:page product_page +``` + + +
+ +## State Management + +``` dart +class _SettingsTabState extends NyState { + + _SettingsTabState() { + stateName = SettingsTab.state; + } + + @override + init() async { + + } + + @override + void stateUpdated(data) { + // e.g. to update this state from another class + // updateState(SettingsTab.state, data: "example payload"); + } + + @override + Widget build(BuildContext context) { + return Container( + child: Cart(), + ); + } +} +``` + +Learn more about state management here. +You can also watch our YouTube video on State Management here. + + + +
+ +## Helpers + +| | | +| --- | ----------- | +| [color](#color "color") | [lockRelease](#lock-release "lockRelease") | +| [boot](#boot "boot") | [reboot](#reboot "reboot") | +| [showToast](#showToast "showToast") | [isLoading](#is-loading "isLoading") | +| [validate](#validate "validate") | [afterLoad](#after-load "afterLoad") | +| [afterNotLocked](#afterNotLocked "afterNotLocked") | [afterNotNull](#after-not-null "afterNotNull") | +| [whenEnv](#when-env "whenEnv") | [setLoading](#set-loading "setLoading") | +| [pop](#pop "pop") | [isLocked](#is-locked "isLocked") | +| [loading](#loading "loading") | [view](#view "view") | +| [changeLanguage](#change-language "changeLanguage") | [confirmAction](#confirm-action "confirmAction") | +| [showToastSuccess](#show-toast-success "showToastSuccess") | [showToastOops](#show-toast-oops "showToastOops") | +| [showToastDanger](#show-toast-danger "showToastDanger") | [showToastInfo](#show-toast-info "showToastInfo") | +| [showToastWarning](#show-toast-warning "showToastWarning") | [showToastSorry](#show-toast-sorry "showToastSorry") | +| [useSkeletonizer](#use-skeletonizer "useSkeletonizer") | | + + + +
+ +### Color + +Returns a color from your current theme. + +Example + +```dart +class _HomePageState extends NyState { + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Text("The page loaded", style: TextStyle( + color: color().primaryContent + ) + ) + ); + } +``` + + +
+ +### Boot + +The `boot` method is used in conjunction with `afterLoad` to make async calls easier. You can call **await** on Future methods inside `boot` and while waiting, the afterLoad method will display a loader (from your **config/design.dart** file). After the boot method has finished, it will display the **child** Widget of your choice. + +Example + +```dart +class _HomePageState extends NyState { + + @override + boot() async { + await Future.delayed(Duration(seconds: 4)); + print('After 4 seconds...'); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: afterLoad(child: () { + return Text("The page loaded"); + }) + ) + ); + } +``` + + +
+ +### Reboot + +This method will re-run the `boot` method in your state. It's useful if you want to refresh the data on the page. + +Example +```dart +class _HomePageState extends NyState { + + List users = []; + + @override + boot() async { + users = await api((request) => request.fetchUsers()); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Users"), + actions: [ + IconButton( + icon: Icon(Icons.refresh), + onPressed: () { + reboot(); // refresh the data + }, + ) + ], + ), + body: ListView.builder( + itemCount: users.length, + itemBuilder: (context, index) { + return Text(users[index].firstName); + } + ), + ); + } +} +``` + + +
+### Pop + +`pop` - Remove the current page from the stack. + +Example + +``` dart +class _HomePageState extends NyState { + + popView() { + pop(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: InkWell( + onTap: popView, + child: Text("Pop current view") + ) + ); + } +``` + + + +
+ +### showToast + +Show a toast notification on the context. + +Example + +```dart +class _HomePageState extends NyState { + + displayToast() { + showToast( + title: "Hello", + description: "World", + icon: Icons.account_circle, + duration: Duration(seconds: 2), + style: ToastNotificationStyleType.INFO // SUCCESS, INFO, DANGER, WARNING + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: InkWell( + onTap: displayToast, + child: Text("Display a toast") + ) + ); + } +``` + + + +
+ +### validate + +The `validate` helper performs a validation check on data. + +You can learn more about the validator here. + +Example + +``` dart +class _HomePageState extends NyState { +TextEditingController _textFieldControllerEmail = TextEditingController(); + + handleForm() { + String textEmail = _textFieldControllerEmail.text; + + validate(rules: { + "email address": "email" + }, data: { + "email address": textEmail + }, onSuccess: () { + print('passed validation') + }); + } +``` + + + +
+ +### changeLanguage + +You can call `changeLanguage` to change the json **/lang** file used on the device. + +Learn more about localization here. + +Example + +``` dart +class _HomePageState extends NyState { + + changeLanguageES() { + await changeLanguage('es'); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: InkWell( + onTap: changeLanguageES, + child: Text("Change Language".tr()) + ) + ); + } +``` + + + +
+ +### whenEnv + +You can use `whenEnv` to run a function when your application is in a certain state. +E.g. your **APP_ENV** variable inside your `.env` file is set to 'developing', `APP_ENV=developing`. + +Example + +```dart +class _HomePageState extends NyState { + + TextEditingController _textEditingController = TextEditingController(); + + @override + init() async { + super.init(); + whenEnv('developing', perform: () { + _textEditingController.text = 'test-email@gmail.com'; + }); + } +``` + + +
+ +### lockRelease + +This method will lock the state after a function is called, only until the method has finished will it allow the user to make subsequent requests. This method will also update the state, use `isLocked` to check. + +The best example to showcase `lockRelease` is to imagine that we have a login screen when the user taps 'Login'. We want to perform an async call to login the user but we don't want the method called multiple times as it could create an undesired experience. + +Here's an example below. + +```dart +class _LoginPageState extends NyState { + + _login() async { + await lockRelease('login_to_app', perform: () async { + + await Future.delayed(Duration(seconds: 4), () { + print('Pretend to login...'); + }); + + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (isLocked('login_to_app')) + AppLoader(), + Center( + child: InkWell( + onTap: _login, + child: Text("Login"), + ), + ) + ], + ) + ); + } +``` + +Once you tap the **_login** method, it will block any subsequent requests until the original request has finished. The `isLocked('login_to_app')` helper is used to check if the button is locked. In the example above, you can see we use that to determine when to display our loading Widget. + + +
+ +### isLocked + +This method will check if the state is locked using the [`lockRelease`](#lock-release) helper. + +Example +```dart +class _HomePageState extends NyState { + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (isLocked('login_to_app')) + AppLoader(), + ], + ) + ); + } +``` + + +
+ +### loading + +If your page uses the `boot` command, you can use the `loading` helper. + +This method returns the loading widget while the `boot` method is running. + +Here's an example +```dart +class _HomePageState extends NyState { + + @override + boot() async { + // run some async code e.g. fetch data + await Future.delayed(Duration(seconds: 3)); + print('After 3 seconds...'); + } + + @override + Widget loading(BuildContext context) async { + return Scaffold( + body: Center( + child: Text("Loading...") + ) + ); + } + + @override + Widget view(BuildContext context) { + return Scaffold( + body: Center( + child: Text("My UI") + ) + ); + } +``` + + +
+ +### view + +The `view` method is used to display the UI for the page. + +Example +```dart +class _HomePageState extends NyState { + + @override + Widget view(BuildContext context) { + return Scaffold( + body: Center( + child: Text("My Page") + ) + ); + } +} +``` + + +
+ +### confirmAction + +The `confirmAction` method will display a dialog to the user to confirm an action. +This method is useful if you want the user to confirm an action before proceeding. + +Example + +``` dart +_logout() { + confirmAction(() { + // logout(); + }, title: "Logout of the app?"); +} +``` + + +
+ +### showToastSuccess + +The `showToastSuccess` method will display a success toast notification to the user. + +Example +``` dart +_login() { + ... + showToastSuccess( + description: "You have successfully logged in" + ); +} +``` + + +
+ +### showToastOops + +The `showToastOops` method will display an oops toast notification to the user. + +Example +``` dart +_error() { + ... + showToastOops( + description: "Something went wrong" + ); +} +``` + + +
+ +### showToastDanger + +The `showToastDanger` method will display a danger toast notification to the user. + +Example +``` dart +_error() { + ... + showToastDanger( + description: "Something went wrong" + ); +} +``` + + +
+ +### showToastInfo + +The `showToastInfo` method will display an info toast notification to the user. + +Example +``` dart +_info() { + ... + showToastInfo( + description: "Your account has been updated" + ); +} +``` + + +
+ +### showToastWarning + +The `showToastWarning` method will display a warning toast notification to the user. + +Example +``` dart +_warning() { + ... + showToastWarning( + description: "Your account is about to expire" + ); +} +``` + + +
+ +### showToastSorry + +The `showToastSorry` method will display a sorry toast notification to the user. + +Example +``` dart +_sorry() { + ... + showToastSorry( + description: "Your account has been suspended" + ); +} +``` + + +
+ +### useSkeletonizer + +The `useSkeletonizer` method will display a skeleton loader while the page is loading. + +Example +``` dart +class _HomePageState extends NyState { + + bool get useSkeletonizer => true; + + @override + boot() async { + await Future.delayed(Duration(seconds: 3)); + print('After 3 seconds...'); + } + + @override + Widget view(BuildContext context) { + return Scaffold( + body: Column( + children: [ + Text("My Page"), + Text("Terms"), + Text("Privacy"), + Text("Contact"), + ] + ) + ); + } +``` + + +
+ +### isLoading + +The `isLoading` method will check if the state is loading. + +Example +```dart +class _HomePageState extends NyState { + + @override + Widget build(BuildContext context) { + if (isLoading()) { + return AppLoader(); + } + + return Scaffold( + body: Text("The page loaded", style: TextStyle( + color: colors().primaryContent + ) + ) + ); + } +``` + + +
+ +### afterLoad + +The `afterLoad` method can be used to display a loader until the state has finished 'loading'. + +You can also check other loading keys using the **loadingKey** parameter `afterLoad(child: () {}, loadingKey: 'home_data')`. + +Example +```dart +class _HomePageState extends NyState { + + @override + init() async { + super.init(); + + awaitData(perform: () async { + await Future.delayed(Duration(seconds: 4)); + print('4 seconds after...'); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: afterLoad(child: () { + return Text("Loaded"); + }) + ); + } +``` + + +
+ +### afterNotLocked + +The `afterNotLocked` method will check if the state is locked. + +If the state is locked it will display the [loading] widget. + +Example +```dart +class _HomePageState extends NyState { + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + alignment: Alignment.center, + child: afterNotLocked('login', child: () { + return MaterialButton( + onPressed: () { + login(); + }, + child: Text("Login"), + ); + }), + ) + ); + } + + login() async { + await lockRelease('login', perform: () async { + await Future.delayed(Duration(seconds: 4)); + print('4 seconds after...'); + }); + } +} +``` + + +
+ +### afterNotNull + +You can use `afterNotNull` to show a loading widget until a variable has been set. + +Imagine you need to fetch a user's account from a DB using a Future call which might take 1-2 seconds, you can use afterNotNull on that value until you have the data. + +Example + +```dart +class _HomePageState extends NyState { + + User? _user; + + @override + init() async { + super.init(); + _user = await api((request) => request.fetchUser()); // example + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: afterNotNull(_user, child: () { + return Text(_user!.firstName); + }) + ); + } +``` + + +
+ +### setLoading + +You can change to a 'loading' state by using `setLoading`. + +The first parameter accepts a `bool` for if it's loading or not, the next parameter allows you to set a name for the loading state, e.g. `setLoading(true, name: 'refreshing_content');`. + +Example +```dart +class _HomePageState extends NyState { + + @override + init() async { + super.init(); + setLoading(true, name: 'refreshing_content'); + + await Future.delayed(Duration(seconds: 4)); + + setLoading(false, name: 'refreshing_content'); + } + + @override + Widget build(BuildContext context) { + if (isLoading(name: 'refreshing_content')) { + return AppLoader(); + } + + return Scaffold( + body: Text("The page loaded") + ); + } +``` diff --git a/resources/docs/5.20.0/ny-switch.md b/resources/docs/5.20.0/ny-switch.md new file mode 100644 index 0000000..07f843c --- /dev/null +++ b/resources/docs/5.20.0/ny-switch.md @@ -0,0 +1,65 @@ +# NySwitch + +--- + + +- [Introduction](#introduction "Introduction") +- [Usage](#usage "Usage") +- [Parameters](#parameters "Parameters") + + + +
+ +## Introduction + +In this section, we will learn about the `NySwitch` widget. + +This widget can perform a 'switch' statement on the widgets that are passed to it. +It makes it easy to switch between different widgets based on the index. + +Let's take a look at some code. + + +
+ +## Usage + +``` dart +int _currentIndex = 1; + +@override +Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Dashboard") + ), + bottomNavigationBar: BottomNavigationBar(items: [ + BottomNavigationBarItem(icon: Icon(Icons.account_circle_outlined), label: "Account"), + BottomNavigationBarItem(icon: Icon(Icons.settings), label: "Settings"), + ], onTap: (index) { + setState(() { + _currentIndex = index; + }); + }, currentIndex: _currentIndex), + body: SafeArea( + child: NySwitch(widgets: [ + AccountTab(), + SettingsTab(), + ], indexSelected: _currentIndex), + ), + ); +} +``` + + +
+ +## Parameters + +The `NySwitch` widget requires two parameters: +- **widgets** - This is the list of widgets that will be displayed. +- **indexSelected** - This is the index of the widget that should be displayed. + +If you would like to know all the parameters available, visit this link [here](https://github.com/nylo-core/support/blob/{{$version}}/lib/widgets/ny_switch.dart). + diff --git a/resources/docs/5.20.0/ny-text-field.md b/resources/docs/5.20.0/ny-text-field.md new file mode 100644 index 0000000..96c067f --- /dev/null +++ b/resources/docs/5.20.0/ny-text-field.md @@ -0,0 +1,116 @@ +# NyTextField + +--- + + +- [Introduction](#introduction "Introduction") +- [Validation](#validation "Validation") + - [Validation error message](#validation-error-message "Validation error message") +- [Faking data](#faking-data "Faking data") + + + +
+ +## Introduction to NyTextField + +The `NyTextField` class is a text field widget that provides extra utility. + +It provides the additional features: +- Validation +- Handling fake data (e.g. development) + +The NyTextField widget behaves like the TextField, but it features the above additional utilities to make handing text fields easier. + + +
+ +## Validation + +You can handle validation for your text fields by providing the `validationRules` parameter like in the below example. + +``` dart +TextEditingController _textEditingController = TextEditingController(); + +@override +Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Container( + child: NyTextField( + controller: _textEditingController, + validationRules: "not_empty|postcode_uk" + ), + ), + ), + ); +} +``` + +You can pass your validation rules into the `validationRules` parameter. +See all the available validation rules [here](/docs/5.x/validation#custom-validation-rules). + + +
+ +### Validation Error Messages + +Error messages will be thrown when the validation fails on the text field. +You can update the error message by setting the `validationErrorMessage` parameter. All you need to do is pass the message you want to display when an error occurs. + +``` dart +TextEditingController _textEditingController = TextEditingController(); + +@override +Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Container( + child: NyTextField( + controller: _textEditingController, + validationRules: "not_empty|postcode_uk", + validationErrorMessage: "Data is not valid" + ), + ), + ), + ); +} +``` + + +
+ +## Faking data + +When testing/developing your application, you may want to display some fake dummy data inside your text fields to speed up development. + +First make sure your `.env` file is set to 'developing' mode. + +``` dart +// .env +APP_ENV="developing" +... +``` + +You can set the `dummyData` parameter to populate fake data. + +``` dart +TextEditingController _textEditingController = TextEditingController(); + +@override +Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Container( + child: NyTextField( + controller: _textEditingController, + validationRules: "not_empty|postcode_uk", + dummyData: "B3 1JJ" // This value will be displayed + ), + ), + ), + ); +} +``` + +If you need to dynamically set **dummyData**, try a package like faker. diff --git a/resources/docs/5.20.0/providers.md b/resources/docs/5.20.0/providers.md new file mode 100644 index 0000000..30c5c83 --- /dev/null +++ b/resources/docs/5.20.0/providers.md @@ -0,0 +1,99 @@ +# Providers + +--- + + +- [Introduction](#introduction "Introduction") +- [Create a provider](#create-a-provider "Create a provider") +- [Provider object](#provider-object "Provider object") + + + +
+ +## Introduction to Providers + +In {{ config('app.name') }}, providers are booted initially from your main.dart file when your application runs. All your providers reside in `/lib/app/providers/*`, you can modify these files or create your providers using Metro. + +Providers can be used when you need to initialize a class, package or create something before the app initially loads. I.e. the `route_provider.dart` class is responsible for adding all the routes to {{ config('app.name') }}. + +### Deep dive + +```dart +import 'package:flutter/material.dart'; +import 'package:flutter_app/bootstrap/app.dart'; +import 'package:nylo_framework/nylo_framework.dart'; +import 'bootstrap/boot.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + {{ config('app.name') }} nylo = await {{ config('app.name') }}.init(setup: Boot.nylo, setupFinished: Boot.finished); // This is where providers are booted + + runApp( + AppBuild( + navigatorKey: NyNavigator.instance.router.navigatorKey, + onGenerateRoute: nylo.router!.generator(), + debugShowCheckedModeBanner: false, + ), + ); +} +``` + +### Lifecycle + +- `Boot.{{ config('app.name') }}` will loop through your registered providers inside config/providers.dart file and boot them. + +- `Boot.Finished` is called straight after **"Boot.{{ config('app.name') }}"** is finished, this method will bind the {{ config('app.name') }} instance to `Backpack` with the value 'nylo'. + +E.g. Backpack.instance.read('nylo'); // {{ config('app.name') }} instance + + + +
+ +## Create a new Provider + +You can create new providers by running the below command in the terminal. + +```dart +dart run nylo_framework:main make:provider cache_provider +``` + + +
+ +## Provider Object + +Your provider will have two methods, `boot({{ config('app.name') }} nylo)` and `afterBoot({{ config('app.name') }} nylo)`. + +When the app runs for the first time, any code inside your **boot** method will be executed first. You can also manipulate the `Nylo` object like in the below example. + +Example: `lib/app/providers/app_provider.dart` + +```dart +class AppProvider implements NyProvider { + + boot({{ config('app.name') }} nylo) async { + await NyLocalization.instance.init( + localeType: localeType, + languageCode: languageCode, + languagesList: languagesList, + assetsDirectory: assetsDirectory, + valuesAsMap: valuesAsMap); + + return nylo; + } + + afterBoot({{ config('app.name') }} nylo) async { + User user = await Auth.user(); + if (!user.isSubscribed) { + await Auth.remove(); + } + } +} +``` + +The `boot` method provides an instance of {{ config('app.name') }} as a parameter. +The `afterBoot` method is called after {{ config('app.name') }} has finished booting. + +> Inside the `boot` method, you must **return** an instance of `{{ config('app.name') }}` or `null` like the above. diff --git a/resources/docs/5.20.0/requirements.md b/resources/docs/5.20.0/requirements.md new file mode 100644 index 0000000..2bf5b79 --- /dev/null +++ b/resources/docs/5.20.0/requirements.md @@ -0,0 +1,44 @@ +# Requirements + +--- + + +- [Installing Flutter](#installing-flutter "Installing Flutter") +- [Set up an editor](#set-up-an-editor "Set up an editor") + + +
+## Installing Flutter + +To use {{ config('app.name') }}, you'll need to have Flutter installed. Check out the Flutter docs for how to get set up. + +**Minimum Flutter version: v3.16.7** + +**Minimum Dart version: v3.2.4** + +--- + +You can check your Flutter version by running the below command. + +``` bash +flutter --version +``` + +If your version is not higher than v3.16.7 then you can run the below to get a stable release. + +``` bash +flutter channel stable +flutter upgrade +flutter doctor -v +``` + + +
+ +## Set up an editor + +Android Studio or VSCode are great options for a Flutter environment. +- Set up Android Studio +- Set up VS Code + +When your editor is ready, you can start building Flutter applications. diff --git a/resources/docs/5.20.0/router.md b/resources/docs/5.20.0/router.md new file mode 100644 index 0000000..efcb3e0 --- /dev/null +++ b/resources/docs/5.20.0/router.md @@ -0,0 +1,530 @@ +# Router + +--- + + +- [Introduction](#introduction "Introduction") +- Basics + - [Adding routes](#adding-routes "Adding routes") + - [Navigating to pages](#navigating-to-pages "Navigating to pages") + - [Multiple routers](#add-multiple-routers "Multiple routers") + - [Initial route](#initial-route "Initial route") + - [Route Guards](#route-guards "Route Guards") +- Features + - [Passing data to another page](#passing-data-to-another-page "Passing data to another page") + - [Query Parameters](#query-parameters "Query Parameters") + - [Page transitions](#page-transitions "Page transitions") + - [Navigation types](#navigation-types "Navigation types") + - [Navigating back](#navigating-back "Navigating back") + - [Auth page](#auth-page "Auth page") + - [Route History](#route-history "Route History") + + + +
+## Introduction + +Routes help us navigate users to pages in our app. + +You can add routes inside the `lib/routers/router.dart` file. + +``` dart +appRouter() => nyRoutes((router) { + ... + router.route(SettingsPage.path, (context) => SettingsPage()); + + // add more routes + // router.route(HomePage.path, (context) => HomePage()); + +}); +``` + +> You can create your routes manually or use the Metro CLI tool to create them for you. + +Here's an example of creating an 'account' page using Metro. + +``` bash +# Run this command in your terminal +dart run nylo_framework:main make:page account_page +``` + +``` dart +// Adds your new route automatically to /lib/routes/router.dart +appRouter() => nyRoutes((router) { + ... + router.route(AccountPage.path, (context) => AccountPage()); +}); +``` + +You may also need to pass data from one view to another. In {{ config('app.name') }}, that’s possible using the `NyStatefulWidget`. We’ll dive deeper into this to explain how it works. + + + +
+ +## Adding routes + +This is the easiest way to add new routes to your project. + +Run the below command to create a new page. + +```dart +dart run nylo_framework:main make:page profile_page +``` + +After running the above, it will create a new Widget named `ProfilePage` and add it to your `resources/pages/` directory. +It will also add the new route to your `lib/routes/router.dart` file. + +File: /lib/routes/router.dart + +``` dart +appRouter() => nyRoutes((router) { + ... + router.route(HomePage.path, (context) => HomePage(), initialRoute: true); + + // My new route + router.route(ProfilePage.path, (context) => ProfilePage()); +}); +``` + + +
+ +## Navigating to pages + +You can navigate to new pages using the `routeTo` helper, like in the example below. + +``` dart +void _pressedSettings() { + routeTo(SettingsPage.path); +} +``` + + +
+ +## Multiple routers + +If your `routes/router.dart` file is getting big, or you need to separate your routes, you can. First, define your routes in a new file like the below example. + +
+ +#### Example new routes file: `/lib/routes/dashboard_router.dart` + +``` dart +NyRouter dashboardRouter() => nyRoutes((router) { + + // example dashboard routes + router.route(AccountPage.path, (context) => AccountPage()); + + router.route(NotificationsPage.path, (context) => NotificationsPage()); +}); +``` + +Then, open `/lib/app/providers/route_provider.dart` and add the new router. + +``` dart +import 'package:flutter_app/routes/router.dart'; +import 'package:flutter_app/routes/dashboard_router.dart'; +import 'package:nylo_framework/nylo_framework.dart'; + +class RouteProvider implements NyProvider { + + boot({{ config('app.name') }} nylo) async { + nylo.addRouter(appRouter()); + + nylo.addRouter(dashboardRouter()); // new routes + + return nylo; + } +} + +... +``` + + +
+ +## Initial route + +In your routers, you can set a page to be the initial route by passing the `initialRoute` parameter to the route you want to use. + +Once you've set the initial route, it will be the first page that loads when you open the app. + +``` dart +appRouter() => nyRoutes((router) { + router.route(HomePage.path, (_) => HomePage()); + + router.route(SettingsPage.path, (_) => SettingsPage()); + + router.route(ProfilePage.path, (_) => ProfilePage(), initialRoute: true); + // new initial route +}); +``` + +Or like this + +``` dart +appRouter() => nyRoutes((router) { + ... + router.route(HomePage.path, (_) => HomePage()).initialRoute(); +}); +``` + + +
+ +## Route guards + +In {{ config('app.name') }}, you can set guards on your routes. + +This will allow or prevent a user from accessing a page. + +Here's an example below: + +Your project has 3 pages, you need to check they are authorized to view the **Dashboard Page**. + +1. Create a new Route Guard, your class should implement `canOpen` and `redirectTo`. +2. Add the new Route Guard to your route + +To create a new Route Guard, run the below command. + +``` bash +# Run this command in your terminal to create a new Route Guard +dart run nylo_framework:main make:route_guard dashboard +``` + +Next, add the new Route Guard to your route. + +``` dart +// File: /routes/router.dart +appRouter() => nyRoutes((router) { + router.route(HomePage.path, (context) => HomePage()); + + router.route(LoginPage.path, (context) => LoginPage()); + + router.route(DashboardPage.path, (context) => DashboardPage(), + routeGuards: [ + DashboardRouteGuard() // Add your guard + ] + ); // restricted page +}); +``` + +You can modify the `canOpen` and `redirectTo` methods to suit your needs. + +File: **/routes/guards/dashboard_route_guard.dart** +``` dart +class DashboardRouteGuard extends NyRouteGuard { + DashboardRouteGuard(); + + @override + Future canOpen(BuildContext? context, NyArgument? data) async { + // Perform a check if they can access the page + return (await Auth.loggedIn()); + } + + @override + redirectTo(BuildContext? context, NyArgument? data) async { + // set the redirect page if canOpen fails + await routeTo(HomePage.path); + } +} +``` + +You can also set route guards using the `routeGuard` extension helper like in the below example. + +``` dart +// File: /routes/router.dart +appRouter() => nyRoutes((router) { + router.route(DashboardPage.path, (context) => DashboardPage()) + .addRouteGuard(MyRouteGuard()); + + // or add multiple guards + + router.route(DashboardPage.path, (context) => DashboardPage()) + .addRouteGuards([MyRouteGuard(), MyOtherRouteGuard()]); +}) +``` + +### Creating a route guard + +You can create a new route guard using the Metro CLI. + +```dart +dart run nylo_framework:main make:route_guard subscription +``` + + +
+ +## Passing data to another page + +In this section, we'll show how you can pass data from one widget to another. + +From your Widget, use the `routeTo` helper and pass the `data` you want to send to the new page. + +``` dart +// HomePage Widget +void _pressedSettings() { + routeTo(SettingsPage.path, data: "Hello World"); +} +... +// SettingsPage Widget (other page) +class _SettingsPageState extends NyState { + ... + @override + init() async { + print(widget.data()); // Hello World + } +``` + +More examples + +``` dart +// Home page widget +class _HomePageState extends NyState { + + _showProfile() { + User user = new User(); + user.firstName = 'Anthony'; + + routeTo(ProfilePage.path, data: user); + } + +... + +// Profile page widget (other page) +class _ProfilePageState extends NyState { + + @override + init() { + User user = widget.data(); + print(user.firstName); // Anthony + + } +``` + + +
+ +## Query Parameters + +When navigating to a new page, you can also provide query parameters. + +Let's take a look. + +```dart + // Home page + routeTo(ProfilePage.path, queryParameters: {"user": "7"}); + // navigate to profile page + + ... + + // Profile Page + @override + init() async { + print(widget.queryParameters()); // {"user": 7} + // or + print(queryParameters()); // {"user": 7} + } +``` +As long as your page widget extends the `NyStatefulWidget` and `NyState` class, then you can call `widget.queryParameters()` to fetch all the query parameters from the route name. + +```dart +// Example page +routeTo(ProfilePage.path, queryParameters: {"hello": "world", "say": "I love code"}); +... + +// Home page +class MyHomePage extends NyStatefulWidget { + ... +} + +class _MyHomePageState extends NyState { + + @override + init() async { + widget.queryParameters(); // {"hello": "World", "say": "I love code"} + // or + queryParameters(); // {"hello": "World", "say": "I love code"} + } +``` + +> Query parameters must follow the HTTP protocol, E.g. /account?userId=1&tab=2 + + +
+ +## Page Transitions + +You can add transitions when you navigate from one page by modifying your `router.dart` file. + +``` dart +import 'package:page_transition/page_transition.dart'; + +appRouter() => nyRoutes((router) { + + // bottomToTop + router.route(SettingsPage.path, (_) => SettingsPage(), + transition: PageTransitionType.bottomToTop + ); + + // leftToRightWithFade + router.route(HomePage.path, (_) => HomePage(), + transition: PageTransitionType.leftToRightWithFade + ); + +}); +``` + +Available transitions: +- PageTransitionType.fade +- PageTransitionType.rightToLeft +- PageTransitionType.leftToRight +- PageTransitionType.topToBottom +- PageTransitionType.bottomToTop +- PageTransitionType.scale (with alignment) +- PageTransitionType.rotate (with alignment) +- PageTransitionType.size (with alignment) +- PageTransitionType.rightToLeftWithFade +- PageTransitionType.leftToRightWithFade +- PageTransitionType.leftToRightJoined +- PageTransitionType.rightToLeftJoined + +You can also apply a transition when navigating to a new page in your project. + +``` dart +// Home page widget +class _HomePageState extends NyState { + + _showProfile() { + routeTo(ProfilePage.path, + pageTransition: PageTransitionType.bottomToTop + ); + } +... +``` + +{{ config('app.name') }} uses the page_transition under the hood to make this possible. + + +
+ +## Navigation Types + +When navigating, you can specify one of the following if you are using the `routeTo` helper. + +- **NavigationType.push** - push a new page to your apps' route stack. +- **NavigationType.pushReplace** - Replace the current route, which disposes of the previous route once the new route has finished. +- **NavigationType.popAndPushNamed** - Pop the current route off the navigator and push a named route in its place. +- **NavigationType.pushAndForgetAll** - push to a new page and dispose of any other pages on the stack. + +``` dart +// Home page widget +class _HomePageState extends NyState { + + _showProfile() { + routeTo( + ProfilePage.path, + navigationType: NavigationType.pushReplace + ); + } +... +``` + + +
+ +## Navigating back + +Once you're on the new page, you can use the `pop()` helper to go back to the existing page. + +``` dart +// SettingsPage Widget +class _SettingsPageState extends NyState { + + _back() { + pop(); + // or + Navigator.pop(context); + } +... +``` + +If you want to return a value to the previous widget, provide a `result` like in the below example. + +``` dart +// SettingsPage Widget +class _SettingsPageState extends NyState { + + _back() { + pop(result: {"status": "COMPLETE"}); + } + +... + +// Get the value from the previous widget using the `onPop` parameter +// HomePage Widget +class _HomePageState extends NyState { + + _viewSettings() { + routeTo(SettingsPage.path, onPop: (value) { + print(value); // {"status": "COMPLETE"} + }); + } +... + +``` + + +
+ +## Auth page + +You can set a route as your '**auth page**', this will make that route the default initial route when they open the app. +First, your user should be logged using the `Auth.set(user)` helper. + +Once they have been added to auth, the next time they visit the application, it will use the auth page instead of the default index page. + +``` dart +appRouter() => nyRoutes((router) { + + router.route(HomePage.path, (_) => HomePage()); + + router.route(ProfilePage.path, (_) => ProfilePage(), authPage: true); + // auth page +}); +``` + +Learn more about authentication [here](/docs/{{ $version }}/authentication). + + +
+ +## Route History + +In {{ config('app.name') }}, you can access the route history information using a few helpers. + +``` dart +// Get route history +Nylo.getRouteHistory(); // List> + +// Get the current route +Nylo.getCurrentRoute(); // Route? + +// Get the previous route +Nylo.getPreviousRoute(); // Route? + +// Get the current route name +Nylo.getCurrentRouteName(); // String? + +// Get the previous route name +Nylo.getPreviousRouteName(); // String? + +// Get the current route arguments +Nylo.getCurrentRouteArguments(); // dynamic + +// Get the previous route arguments +Nylo.getPreviousRouteArguments(); // dynamic +``` diff --git a/resources/docs/5.20.0/slates.md b/resources/docs/5.20.0/slates.md new file mode 100644 index 0000000..fb0514c --- /dev/null +++ b/resources/docs/5.20.0/slates.md @@ -0,0 +1,52 @@ +# Slates + +--- + + +- [Introduction](#introduction "Introduction") +- [Creating a Slate package](#creating-a-slate-package "Creating a Slate package") + + + +
+## Introduction + +Slates are packages you can download from [pub.dev](https://:pub.dev) to quickly scaffold your app. + +Once you install your slate package, you can run from the **terminal** using the `publish:all` command. +Each package you install will use a different command to publish all the files. + +Here's an example below, i.e. installing the [ny_auth_slate](https://pub.dev/packages/ny_auth_slate). + +``` dart +dart run nylo_framework:main publish:slate example_slate_package +// or with Metro +metro publish:slate example_slate_package +``` + +Download a fresh copy of {{ config('app.name') }} and try it in your project [ny_auth_slate](https://pub.dev/packages/ny_auth_slate) + + +
+ +## Creating a Slate package + +You can build a new Slate package by first using our public template here to get started. + +Navigate to the **my_slate_template.dart** file and modify the run() method. + +``` dart +List run() => [ + /// Example + NyTemplate( + name: "login_page", // name of the file + saveTo: pagesFolder, // folder to save to + pluginsRequired: [], // dependencies that are required for the stub + stub: stubLoginPage(), // stub you want to generate in you /stubs directory + ), + + /// add more templates... +]; +``` + +Once you've built your Slate package, publish it to pub.dev as a package for the community to download. diff --git a/resources/docs/5.20.0/state-management.md b/resources/docs/5.20.0/state-management.md new file mode 100644 index 0000000..63b2116 --- /dev/null +++ b/resources/docs/5.20.0/state-management.md @@ -0,0 +1,175 @@ +# State Management + +--- + + +- [Introduction](#introduction "Introduction") +- [When to Use State Management](#when-to-use-state-management "When to Use State Management") +- [Lifecycle](#lifecycle "Lifecycle") +- Basics + - [Updating a State](#updating-a-state "Updating a State") +- [Building Your First Widget](#building-your-first-widget "Building Your First Widget") + + +
+ +## Introduction + +In Nylo 5, you can build Widgets that use State Management. + +In this section, we will learn about the `NyState` class, we'll also dive into some examples. + + +### Let's first understand State Management + +Everything in Flutter is a widget, they are just tiny chunks of UI that you can combine to make a complete app. + +When you start building complex pages, you will need to manage the state of your widgets. This means when something changes, e.g. data, you can update that widget without having to rebuild the entire page. + +There are a lot of reasons why this is important, but the main reason is performance. If you have a widget that is constantly changing, you don't want to rebuild the entire page every time it changes. + +This is where State Management comes in, it allows you to manage the state of a widget in your application. + + + +
+ +### When to Use State Management + +You should use State Management when you have a widget that needs to be updated without rebuilding the entire page. + +For example, let's imagine you have created an ecommerce app. You have built a widget to display the total amount of items in the users' cart. +Let's call this widget `Cart()`. + +A state managed `Cart` widget in Nylo would look something like this. + +``` dart +/// The Cart widget +class Cart extends StatefulWidget { + + Cart({Key? key}) : super(key: key); + + static String state = "cart"; + + @override + _CartState createState() => _CartState(); +} + +/// The state class for the Cart widget +class _CartState extends NyState { + + String? _cartValue; + + _CartState() { + stateName = Cart.state; + } + + @override + boot() async { + _cartValue = await getCartValue(); + } + + @override + void stateUpdated(data) { + reboot(); // Reboot the widget + } + + @override + Widget build(BuildContext context) { + return afterLoad(child: () => Badge( + child: Icon(Icons.shopping_cart), + label: Text(_cartValue ?? "1"), + )); + } +} + +/// Get the cart value from storage +Future getCartValue() async { + return (await NyStorage.read(StorageKey.cart)); +} + +/// Set the cart value +Future setCartValue(int value) async { + await NyStorage.store(StorageKey.cart, value.toString()); + updateState(Cart.state); +} +``` + +Let's break this down. + +1. The `Cart` widget is a `StatefulWidget`. + +2. `_CartState` extends `NyState`. + +3. You need to define a name for the `state`, this is used to identify the state. + +4. The `boot()` method is called when the widget is first loaded. + +5. The `stateUpdate()` methods handle what happens when the state is updated. + +If you want to try this example in your {{ config('app.name') }} project, create a new widget called `Cart`. + +``` dart +dart run nylo_framework:main make:stateful_widget cart +``` + +Then you can copy the example above and try it in your project. + +Now, to update the cart, you can call the following. + +```dart +_updateCart() async { + String count = (await getCartValue() ?? "1"); + String countIncremented = (int.parse(count) + 1).toString(); + + await NyStorage.store(StorageKey.cart, countIncremented); + + updateState(Cart.state); +} +``` + + +
+ +## Lifecycle + +The lifecycle of a `NyState` widget is as follows: + +1. `init()` - This method is called when the state is initialized. + +2. `stateUpdated(data)` - This method is called when the state is updated. + + If you call `updateState(MyStateName.state, data: "The Data")`, it will trigger **stateUpdated(data)** to be called. + +Once the state is first initialized, you will need to implement how you want to manage the state. + + +
+ +## Updating a State + +You can update a state by calling the `updateState()` method. + +``` dart +updateState(MyStateName.state); + +// or with data +updateState(MyStateName.state, data: "The Data"); +``` + +This can be called anywhere in your application. + + +
+ +## Building Your First Widget + +In your Nylo project, run the following command to create a new widget. + +``` dart +dart run nylo_framework:main make:stateful_widget todo_list +``` + +This will create a new `NyState` widget called `TodoList`. + +> Note: The new widget will be created in the `lib/resources/widgets/` directory. diff --git a/resources/docs/5.20.0/storage.md b/resources/docs/5.20.0/storage.md new file mode 100644 index 0000000..920ab9c --- /dev/null +++ b/resources/docs/5.20.0/storage.md @@ -0,0 +1,298 @@ +# Storage + +--- + + +- [Introduction to storage](#introduction "Introduction to storage") +- Storing and retrieving data + - [Store values](#store-values "Store values") + - [Retrieve values](#retrieve-values "Retrieve values") + - [Storage keys](#storage-keys "Storage keys") +- Lightweight Storage + - [Backpack Storage](#backpack-storage "Backpack Storage") + - [Persist Data with Backpack](#persist-data-with-backpack "Persist Data with Backpack") +- Collections + - [Introduction to collections](#introduction-to-collections "Introduction to collections") + - [Add to a collection](#add-to-a-collection "Add to a collection") + - [Retrieve a collection](#retrieve-a-collection "Retrieve to a collection") + - [Delete a collection](#delete-a-collection "Delete a collection") + + +
+ +## Introduction + +In {{ config('app.name') }} you can save data to the users device using the `NyStorage` class. + +Under the hood, {{ config('app.name') }} uses the flutter_secure_storage package to save and retrieve data. + + +
+ +## Store values + +To store values, you can use the below helper. + +``` dart +import 'package:nylo_framework/nylo_framework.dart'; +... + +NyStorage.store("com.company.myapp.coins", "10"); +``` + +Data will persist on the user's device using NyStorage. E.g. if they exit the app, you can retrieve the same data that was stored previously. + + +
+ +## Retrieve values + +To retrieve values, you can use the below helper. + +``` dart +import 'package:nylo_framework/nylo_framework.dart'; +... + +// Default +String coins = await NyStorage.read("com.company.myapp.coins"); // 10 + +// String +String coins = await NyStorage.read("com.company.myapp.coins"); // 10 + +// Integer +int coins = await NyStorage.read("com.company.myapp.coins"); // 10 + +// double +double coins = await NyStorage.read("com.company.myapp.coins"); // 10.00 +``` + + + +
+ +## Storage Keys + +This class is useful to reference **Strings** which you can later use in your `NyStorage` or `Backpack` class. +You can use the `StorageKey` class to organise all the shared preference Strings in your project. + +Open your {{ config('app.name') }} project and open the **"config/storage_keys.dart"** file. + +```dart +/* Storage Keys +|-------------------------------------------------------------------------- */ + +class StorageKey { + static String userToken = "USER_TOKEN"; + + /// Add your storage keys here... + +} +``` + +This class helps organise all your String keys for your Storage variables. + +#### How to use Storage Keys in your project + +```dart +import 'package:flutter_app/config/storage_keys.dart'; +... + +class _MyHomePageState extends NyState { + + // Example storing values in NyStorage + _storeValues() async { + await NyStorage.store(StorageKey.userToken , 'Anthony'); + // or + await StorageKey.userToken.store('Anthony'); + } + + // Example reading values from NyStorage + _readValues() async { + String? userName = await NyStorage.read(StorageKey.userToken); // Anthony + // or + String? userName = await StorageKey.userToken.read(); // Anthony + } +``` + + +
+ +## Backpack Storage + +{{ config('app.name') }} includes a lightweight storage class called `Backpack`. +This class is designed for storing small-sized pieces of data during a user's session. + +The Backpack class isn't asynchronous so you can set/get data on the fly. + +Here's the Backpack class in action. + +### Set data + +```dart +// storing a string +Backpack.instance.set('user_api_token', 'a secure token'); + +// storing an object +User user = User(); +Backpack.instance.set('user', user); + +// storing an int +Backpack.instance.set('my_lucky_no', 7); +``` + +### Read data + +```dart +Backpack.instance.read('user_api_token'); // a secure token + +Backpack.instance.read('user'); // User instance + +Backpack.instance.read('my_lucky_no'); // 7 +``` + +### Real world usage + +A great example for when you might want to use this class over the [NyStorage](/docs/{{$version}}/storage#store-values) class is when e.g. storing a user's `api_token` for authentication. + +```dart +// login a user +LoginResponse loginResponse = await _apiService.loginUser('email': '...', 'password': '...'); + +String userToken = loginResponse.token; +// Store the user's token to NyStorage for persisted storage +await NyStorage.store('user_token', userToken); + +// Store the token to the Backpack class to ensure the user is authenticated for subsequent API requests +Backpack.instance.set('user_token', userToken); +``` + +Now in our API Service, we can set the auth header from our Backpack class without having to wait on the async response. + +```dart +class ApiService extends BaseApiService { + ... + Future accountDetails() async { + return await network( + request: (request) { + String userToken = Backpack.instance.read('user_api_token'); + + // Set auth header + request.options.headers = { + 'Authorization': "Bearer " + userToken + }; + + return request.get("/account/1"); + }, + ); + } +} +``` + + +
+ +## Persist data with Backpack + +You can use the [`NyStorage`](/docs/{{$version}}/storage#store-values) class to persist data but if you also need to save it to your App's Backpack storage, use the below parameter "**inBackpack**". + +Here's an example. + +```dart +// Store data in secure storage & in memory using Backpack +await NyStorage.store('user_token', 'a token 123', inBackpack: true); + +// Fetch data back with Backpack +Backpack.instance.read('user_token'); // "a token 123" +``` + +> By default, NyStorge will not store data in Backpack unless the `inBackpack` parameter is set to `true` + + +
+ +## Introduction to Collections + +Collections can be used when you want to store a collection of things. E.g. a list of strings, objects or ints. +Here's an example of setting, getting and deleting values from a collection. + +Here's an example. + +1. We want to store a list of product ids each time a user taps 'add product' +2. Show the list of product ids on a different page +3. Delete the product id from the collection + +``` dart +// 1 - Adding an item to a collection +_addProduct(int productId) async { + await NyStorage.addToCollection("product_ids", newItem: productId); // adds productId to the collection +} + +// 2 - Page to display the data, e.g. cart_page.dart +@override +Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: NyFutureBuilder(future: NyStorage.readCollection("product_ids"), child: (context, data) { + return ListView( + children: data.map((productId) { + return Text(productId.toString()); + }).toList(), + ); + },) + ), + ); +} + +// 3 - Example removing an item from the collection +_removeItemFromCollection(int index) async { + await NyStorage.deleteFromCollection(index, key: "product_ids"); +} +``` + + +
+ +## Add to a collection + +You can add new items to your collections by calling `NyStorage.addToCollection("a_storage_key", newItem: "1");`. + +Example + +``` dart +await NyStorage.addToCollection("a_storage_key", newItem: "1"); +await NyStorage.addToCollection("a_storage_key", newItem: "2"); +await NyStorage.addToCollection("a_storage_key", newItem: "3"); + +await NyStorage.readCollection("a_storage_key"); // ["1", "2", "3"] +``` + + +
+ +## Retrieve a collection + +You can retrieve a collections by calling `NyStorage.readCollection("a_storage_key");`. + +Example + +``` dart +await NyStorage.addToCollection("a_storage_key", newItem: "Anthony"); +await NyStorage.addToCollection("a_storage_key", newItem: "Kyle"); + +await NyStorage.readCollection("a_storage_key"); // ["Anthony", "Kyle"] +``` + + +
+ +## Delete a collection + +You can delete a collections by calling `NyStorage.deleteCollection("a_storage_key");`. + +Example + +``` dart +await NyStorage.readCollection("a_storage_key"); // ["Anthony", "Kyle"] + +await NyStorage.deleteFromCollection(0, "a_storage_key"); // ["Kyle"] +``` diff --git a/resources/docs/5.20.0/themes-and-styling.md b/resources/docs/5.20.0/themes-and-styling.md new file mode 100644 index 0000000..67a48a3 --- /dev/null +++ b/resources/docs/5.20.0/themes-and-styling.md @@ -0,0 +1,495 @@ +# Themes & Styling + +--- + + +- [Introduction](#introduction "Introduction to themes") +- Themes + - [Light & Dark themes](#light-and-dark-themes "Light and dark themes") + - [Creating a theme](#creating-a-theme "Creating a theme") +- Configuration + - [Theme colors](#theme-colors "Theme colours") + - [Using colors](#using-colors "Using colours") + - [Base styles](#base-styles "Base styles") + - [Switching theme](#switching-theme "Switching theme") + - [Fonts](#fonts "Fonts") + - [Design](#design "Design") +- [Text Extensions](#text-extensions "Text extensions") + + + +
+## Introduction + +You can manage your application's UI styles using themes. Themes allow us to change i.e. the font size of text, how buttons appear and the general appearance of our application. + +If you are new to themes, the examples on the Flutter website will help you get started here. + +Out of the box, {{ config('app.name') }} includes pre-configured themes for `Light mode` and `Dark mode`. + +The theme will also update if the device enters 'light/dark' mode. + + +
+ +## Light & Dark themes + +- Light theme - `lib/resources/themes/light_theme.dart` +- Dark theme - `lib/resources/themes/dark_theme.dart` + +Inside these files, you'll find the ThemeData and ThemeStyle pre-defined. + + + + +
+ +## Creating a theme + +If you want to have multiple themes for your app, we have an easy way for you to do this. If you're new to themes, follow along. + +First, run the below command from the terminal + +``` bash +dart run nylo_framework:main make:theme bright_theme +# or with metro alias +metro make:theme bright_theme +``` + +Note: replace **bright_theme** with the name of your new theme. + +This creates a new theme in your `/resources/themes/` directory and also a theme colors file in `/resources/themes/styles/`. + +``` dart +// App Themes +final List> appThemes = [ + BaseThemeConfig( + id: getEnv('LIGHT_THEME_ID'), + description: "Light theme", + theme: lightTheme, + colors: LightThemeColors(), + ), + BaseThemeConfig( + id: getEnv('DARK_THEME_ID'), + description: "Dark theme", + theme: darkTheme, + colors: DarkThemeColors(), + ), + + BaseThemeConfig( // new theme automatically added + id: 'Bright Theme', + description: "Bright Theme", + theme: brightTheme, + colors: BrightThemeColors(), + ), +]; +``` + +You can modify the colors for your new theme in the **/resources/themes/styles/bright_theme_colors.dart** file. + + +
+ +## Theme Colors + +To manage the theme colors in your project, check out the `lib/resources/themes/styles` directory. +This directory contains the style colors for the light_theme_colors.dart and dark_theme_colors.dart. + +In this file, you should have something similar to the below. + +``` dart +// e.g Light Theme colors +class LightThemeColors implements ColorStyles { + // general + Color get background => const Color(0xFFFFFFFF); + Color get primaryContent => const Color(0xFF000000); + Color get primaryAccent => const Color(0xFF87c694); + + Color get surfaceBackground => Colors.white; + Color get surfaceContent => Colors.black; + + // app bar + Color get appBarBackground => Colors.blue; + Color get appBarPrimaryContent => Colors.white; + + // buttons + Color get buttonBackground => Colors.blueAccent; + Color get buttonPrimaryContent => Colors.white; + + // bottom tab bar + Color get bottomTabBarBackground => Colors.white; + + // bottom tab bar - icons + Color get bottomTabBarIconSelected => Colors.blue; + Color get bottomTabBarIconUnselected => Colors.black54; + + // bottom tab bar - label + Color get bottomTabBarLabelUnselected => Colors.black45; + Color get bottomTabBarLabelSelected => Colors.black; +} +``` + + +
+ +## Using colors in widgets + +``` dart +import 'package:flutter_app/config/theme.dart'; +... + +// gets the light/dark background colour depending on the theme +ThemeColor.get(context).background + +// e.g. of using the "ThemeColor" class +Text( + "Hello World", + style: TextStyle( + color: ThemeColor.get(context).primaryContent // Color - primary content + ), +), + +// or + +Text( + "Hello World", + style: TextStyle( + color: ThemeConfig.light().colors.primaryContent // Light theme colors - primary content + ), +), +``` + + +
+ +## Base styles + +Base styles allow you to customize various widget colors from one area in your code. + +{{ config('app.name') }} ships with pre-configured base styles for your project located `lib/resources/themes/styles/color_styles.dart`. + +These styles provide an interface for your theme colors in `light_theme_colors.dart` and `dart_theme_colors.dart`. + +
+ +File `lib/resources/themes/styles/color_styles.dart` + +``` dart +abstract class ColorStyles { + // general + Color get background; + Color get primaryContent; + Color get primaryAccent; + + // app bar + Color get appBarBackground; + Color get appBarPrimaryContent; + + // buttons + Color get buttonBackground; + Color get buttonPrimaryContent; + + // bottom tab bar + Color get bottomTabBarBackground; + + // bottom tab bar - icons + Color get bottomTabBarIconSelected; + Color get bottomTabBarIconUnselected; + + // bottom tab bar - label + Color get bottomTabBarLabelUnselected; + Color get bottomTabBarLabelSelected; +} +``` + +You can add additional styles here and then implement the colors in your theme. + + +
+ +## Switching theme + +{{ config('app.name') }} supports the ability to switch themes on the fly. + +E.g. If you need to switch the theme if a user taps a button to activate the "dark theme". + +You can support that by doing the below: + +``` dart +import 'package:nylo_framework/theme/helper/ny_theme.dart'; +... + +TextButton(onPressed: () { + + // set theme to use the "dark theme" + NyTheme.set(context, id: "dark_theme"); + setState(() { }); + + }, child: Text("Dark Theme") +), + +// or + +TextButton(onPressed: () { + + // set theme to use the "light theme" + NyTheme.set(context, id: "light_theme"); + setState(() { }); + + }, child: Text("Light Theme") +), +``` + + + +
+ +## Fonts + +Updating your primary font throughout the app is easy in {{ config('app.name') }}. Open the `lib/config/design.dart` file and update the below. + +``` dart +final TextStyle appThemeFont = GoogleFonts.lato(); +``` + +We include the GoogleFonts library in the repository, so you can start using all the fonts with little effort. +To update the font to something else, you can do the following: +``` dart +// OLD +// final TextStyle appThemeFont = GoogleFonts.lato(); + +// NEW +final TextStyle appThemeFont = GoogleFonts.montserrat(); +``` + +Check out the fonts on the official Google Fonts library to understand more + +Need to use a custom font? Check out this guide - https://flutter.dev/docs/cookbook/design/fonts + +Once you've added your font, change the variable like the below example. + +``` dart +final TextStyle appThemeFont = TextStyle(fontFamily: "ZenTokyoZoo"); // ZenTokyoZoo used as an example for the custom font +``` + + +
+ +## Design + +The **config/design.dart** file is used for managing the design elements for your app. + +`appFont` variable contains the font for your app. + +`logo` variable is used to display your app's Logo. + +You can modify **resources/widgets/logo_widget.dart** to customize how you want to display your Logo. + +`loader` variable is used to display a loader. {{ config('app.name') }} will use this variable in some helper methods as the default loader widget. + +You can modify **resources/widgets/loader_widget.dart** to customize how you want to display your Loader. + + +
+ +## Text Extensions + +Here are the available text extensions that you can use in {{ config('app.name') }}. + +| Rule Name | Usage | Info | +|---|---|---| +| Display Large | displayLarge(context) | Applies the **displayLarge** textTheme | +| Display Medium | displayMedium(context) | Applies the **displayMedium** textTheme | +| Display Small | displaySmall(context) | Applies the **displaySmall** textTheme | +| Heading Large | headingLarge(context) | Applies the **headingLarge** textTheme | +| Heading Medium | headingMedium(context) | Applies the **headingMedium** textTheme | +| Heading Small | headingSmall(context) | Applies the **headingSmall** textTheme | +| Title Large | titleLarge(context) | Applies the **titleLarge** textTheme | +| Title Medium | titleMedium(context) | Applies the **titleMedium** textTheme | +| Title Small | titleSmall(context) | Applies the **titleSmall** textTheme | +| Body Large | bodyLarge(context) | Applies the **bodyLarge** textTheme | +| Body Medium | bodyMedium(context) | Applies the **bodyMedium** textTheme | +| Body Small | bodySmall(context) | Applies the **bodySmall** textTheme | +| Font Weight Bold | fontWeightBold | Applies font weight bold to a Text widget | +| Font Weight Light | fontWeightLight | Applies font weight light to a Text widget | +| Set Color | setColor(context, (color) => colors.primaryAccent) | Set a different text color on the Text widget | +| Align Left | alignLeft | Align the font to the left | +| Align Right | alignRight | Align the font to the right | +| Align Center | alignCenter | Align the font to the center | +| Set Max Lines | setMaxLines(int maxLines) | Set the maximum lines for the text widget | + +
+ +--- + + +
+ +#### Display large + +``` dart +Text("Hello World").displayLarge(context) +``` + + +
+ +#### Display medium + +``` dart +Text("Hello World").displayMedium(context) +``` + + +
+ +#### Display small + +``` dart +Text("Hello World").displaySmall(context) +``` + + +
+ +#### Heading large + +``` dart +Text("Hello World").headingLarge(context) +``` + + +
+ +#### Heading medium + +``` dart +Text("Hello World").headingMedium(context) +``` + + +
+ +#### Heading small + +``` dart +Text("Hello World").headingSmall(context) +``` + + +
+ +#### Title large + +``` dart +Text("Hello World").titleLarge(context) +``` + + +
+ +#### Title medium + +``` dart +Text("Hello World").titleMedium(context) +``` + + +
+ +#### Title small + +``` dart +Text("Hello World").titleSmall(context) +``` + + +
+ +#### Body large + +``` dart +Text("Hello World").bodyLarge(context) +``` + + +
+ +#### Body medium + +``` dart +Text("Hello World").bodyMedium(context) +``` + + +
+ +#### Body small + +``` dart +Text("Hello World").bodySmall(context) +``` + + +
+ +#### Font weight bold + +``` dart +Text("Hello World").fontWeightBold() +``` + + +
+ +#### Font weight light + +``` dart +Text("Hello World").fontWeightLight() +``` + + +
+ +#### Set color + +``` dart +Text("Hello World").setColor(context, (color) => colors.primaryAccent) +// Color from your colorStyles +``` + + +
+ +#### Align left + +``` dart +Text("Hello World").alignLeft() +``` + + +
+ +#### Align right + +``` dart +Text("Hello World").alignRight() +``` + + +
+ +#### Align center + +``` dart +Text("Hello World").alignCenter() +``` + + +
+ +#### Set max lines + +``` dart +Text("Hello World").setMaxLines(5) +``` diff --git a/resources/docs/5.20.0/upgrade-guide.md b/resources/docs/5.20.0/upgrade-guide.md new file mode 100644 index 0000000..65228d7 --- /dev/null +++ b/resources/docs/5.20.0/upgrade-guide.md @@ -0,0 +1,22 @@ +# Upgrade Guide + +--- + + +- [What's changed in {{ $version }}](#whats-changed-in-nylo-5 "What's Changed in {{ $version }}") +- [How to upgrade](#how-to-upgrade "How to upgrade") + + + +
+## What's Changed in Nylo {{ $version }} + +You can understand all the changes by clicking the below link. + +View changes + + +
+## How to upgrade + +You can check the changes in {{ $version }} by clicking the above link "**View changes**" and then implement all the changes into your {{ config('app.name') }} project. diff --git a/resources/docs/5.20.0/validation.md b/resources/docs/5.20.0/validation.md new file mode 100644 index 0000000..f0e5aa3 --- /dev/null +++ b/resources/docs/5.20.0/validation.md @@ -0,0 +1,809 @@ +# Validation + +--- + + +- [Introduction](#introduction "Introduction to validation") +- Basics + - [Validating Data](#validating-data "Validating Data") + - [Multiple Validation Rules](#multiple-validation-rules "Multiple Validation Rules") + - [Validating Text Fields](#validating-text-fields "Validating Text Fields") + - [Validation checks](#validation-checks "Validation checks") +- [Validation Rules](#validation-rules "Validation Rules") +- [Creating Custom Validation Rules](#creating-custom-validation-rules "Creating Custom Validation Rules") + + + +
+ +## Introduction + +In {{ config('app.name') }}, you can handle validating data using the `validate` helper. + +It contains some useful [validation rules](#validation-rules) you can use in your project. + +If you need to add custom validation rules, you can do that too. In this section, we'll give an overview of how validation works in {{ config('app.name') }}. + + +
+ +## Validating Data + +In your project, you will often need to validate data. For example, you may need to validate a user's email address or phone number when they sign up to your app. + +You can use the `validate` helper to validate data. + +``` dart +class _ExampleState extends NyState { + ... + + handleFormSuccess() { + String textFieldPass = 'agordon@web.com'; + + validate(rules: { + "email address": [textFieldPass, "email|lowercase"] + }, onSuccess: () { + print('looks good'); + // do something... + }); + + // or like this + + validate(rules: { + "email address": "email" // validation rule 'email' + }, data: { + "email address": textFieldPass + }, onSuccess: () { + print('looks good'); + // do something... + }); + } +} +``` + +When the validation passes, the `onSuccess` callback will be called. + +If the validation fails, the `onFailure` callback will be called. + +``` dart +class _ExampleState extends NyState { + ... + handleFormFail() { + String textFieldFail = 'agordon.dodgy.data'; + + validate(rules: { + "email address": [textFieldFail, "email"] + }, onSuccess: () { + // onSuccess would not be called + print("success..."); + }, onFailure: (Exception exception) { + /// handle the validation error + print("failed validation"); + }, showAlert: false); + } +``` + +> When the validation fails, a toast notification will be displayed to the user. You can override this by setting the `showAlert` parameter to false. + + +**Example using the `phone_number_uk` validation rule** + +``` dart +class _ExampleState extends NyState { + TextEditingController _textFieldController = TextEditingController(); + ... + + handleForm() { + String textFieldValue = _textFieldController.text; + + validate(rules: { + "phone number": [textFieldValue, "phone_number_uk"] // validation rule 'phone_number_uk' + }, onSuccess: () { + print('looks good'); + // do something... + }); + } +} +``` + +**Example using the `contains` validation rule** + +``` dart +class _ExampleState extends NyState { + ... + + handleForm() { + String carModel = 'ferrari'; + + validate(rules: { + "car model": [carModel, "contains:lamborghini,ferrari"] // validation rule 'contains' + }, onSuccess: () { + print("Success! It's a ferrari or lamborghini"); + // do something... + }, onFailure: (Exception exception) { + print('No match found'); + }); + } +} +``` + +**Options:** +``` dart +validate( + rules: { + "email address": "email|max:10" // checks data is an email and maximum of 10 characters + }, data: { + "email address": textEmail // data to be validated + }, message: { + "email address": "oops|it failed" // first section is title, then add a " | " and then provide the description + }, + showAlert: true, // if you want {{ config('app.name') }} to display the alert, default : true + alertStyle: ToastNotificationStyleType.DANGER // choose from SUCCESS, INFO, WARNING and DANGER +); +``` + +This method is handy if you want to quickly validate the user's data and display some feedback to the user. + + + +
+ +## Multiple Validation Rules + +You can pass multiple validation rules into the `validate` helper. + +``` dart +validate(rules: { + "email address": ["john.mail@gmail.com" "email|max:10"] + // checks data is an email and maximum of 10 characters +}, onSuccess: () { + print("Success! It's a valid email and maximum of 10 characters"); +}); +``` + +You can also pass multiple validation rules into the `validate` helper like this: + +``` dart +validate(rules: { + "email address": ["anthony@mail.com", "email|max:10|lowercase"], + // checks data is an email, lowercased and maximum of 10 characters + "phone number": ["123456", "phone_number_uk"] + // checks data is a UK phone number +}, onSuccess: () { + print("Success! It's a valid email, lowercased and maximum of 10 characters"); +}, onFailure: (Exception exception) { + print('No match found'); +}); +``` + + +
+ +## Validating Text Fields + +You can validate Text Fields by using the `NyTextField` widget. + +Use the `validationRules` parameter to pass your validation rules into the TextField. + +``` dart +NyTextField( + controller: _textEditingController, + validationRules: "not_empty|postcode_uk" +) +``` + + +
+ +## Validation checks + +If you need to perform a validation check on data, you can use the `NyValidator.isSuccessful()` helper. + +``` dart +String helloWorld = "HELLO WORLD"; + +bool isSuccessful = NyValidator.isSuccessful( + rules: { + "Test": [helloWorld, "uppercase|max:12"] + } +); + +if (isSuccessful) { + print("Success! It's a valid"); +} +``` + +This will return a boolean value. If the validation passes, it will return `true` and `false` if it fails. + + +
+ +## Validation Rules + +Here are the available validation rules that you can use in {{ config('app.name') }}. + +| Rule Name | Usage | Info | +|---|---|---| +| Email | email | Checks if the data is a valid email | +| Contains | contains:jeff,cup,example | Checks if the data contains a value | +| URL | url | Checks if the data is a valid url | +| Boolean | boolean | Checks if the data is a valid boolean | +| Min | min:5 | Checks if the data is a minimum of x characters | +| Max | max:11 | Checks if the data is a maximum of x characters | +| Not empty | not_empty | Checks if the data is not empty | +| Regex | r'regex:([0-9]+)' | Checks if the data matches a regex pattern | +| Numeric | numeric | Checks if the data is numeric | +| Date | date | Checks if the data is a date | +| Capitalized | capitalized | Checks if the data is capitalized | +| Lowercase | lowercase | Checks if the data is lowercase | +| Uppercase | uppercase | Checks if the data is uppercase | +| US Phone Number | phone_number_us | Checks if the data is a valid phone US phone number | +| UK Phone Number | phone_number_uk | Checks if the data is a valid phone UK phone number | +| US Zipcode | zipcode_us | Checks if the data is a valid zipcode for the US | +| UK Postcode | postcode_uk | Checks if the data is a valid postcode for the UK | +| Date age is younger | date_age_is_younger:18 | Checks if the date is younger than a age | +| Date age is older | date_age_is_older:30 | Checks if the date is older than a age | +| Date in past | date_in_past | Check if a date is in the past | +| Date in future | date_in_future | Check if a date is in the future | +| Is True | is_true | Checks if a value is true | +| Is False | is_false | Checks if a value is false | + +
+ +--- + + +
+ +### email + +This allows you to validate if the input is an email. + +Usage: `email` + +``` dart +String email = "agordon@mail.com"; + +validate(rules: { + "Email": [email, "email"] +}, onSuccess: () { + print("Success! The input is an email"); +}); +``` + + + +
+ +### boolean + +This allows you to validate if the input is a boolean. + +Usage: `boolean` + +``` dart +bool isTrue = true; + +validate(rules: { + "Is True": [isTrue, "boolean"] +}, onSuccess: () { + print("Success! The input is a boolean"); +}); +``` + + +
+ +### contains + +Check if the input contains a particular value. + +Usage: `contains:dog,cat` + +``` dart +String favouriteAnimal = "dog"; +validate(rules: { + "Favourite Animal": [favouriteAnimal, "contains:dog,cat"] +}, onSuccess: () { + print("Success! The input contains dog or cat"); +}); +``` + + +
+ +### url + +Check if the input is a URL. + +Usage: `url` + +``` dart +String url = "https://www.google.com"; +validate(rules: { + "Website": [url, "url"] +}, onSuccess: () { + print("Success! The URL is valid"); +}); +``` + + +
+ +### min + +Check if the input is a minimum of characters. + +Usage: `min:7` - will fail if the user's input is less than 7 characters. + +``` dart +// String +String password = "Password1"; +validate(rules: { + "Password": [password, "min:3"] +}, onSuccess: () { + print("Success! The password is more than 3 characters"); +}); + +// List +List favouriteCountries = ['Spain', 'USA', 'Canada']; +validate(rules: { + "Favourite Countries": [favouriteCountries, "min:2"] +}, onSuccess: () { + print("Success! You have more than 2 favourite countries"); +}); + +// Integer/Double +int age = 21; +validate(rules: { + "Age": [age, "min:18"] +}, onSuccess: () { + print("Success! You are more than 18 years old"); +}); +``` + + +
+ +### max + +Check if the input is a maximum of characters. + +Usage: `max:10` - will fail if the user's input is more than 10 characters. + +``` dart +// String +String password = "Password1"; +validate(rules: { + "Password": [password, "max:10"] +}, onSuccess: () { + print("Success! The password is less than 10 characters"); +}); + +// List +List favouriteCountries = ['Spain', 'USA', 'Canada']; +validate(rules: { + "Favourite Countries": [favouriteCountries, "max:4"] +}, onSuccess: () { + print("Success! You have less than 4 favourite countries"); +}); + +// Integer/Double +int age = 18; +validate(rules: { + "Age": [age, "max:21"] +}, onSuccess: () { + print("Success! You are less than 18 years old"); +}); +``` + + +
+ +### Not Empty + +Check if the input is not empty. + +Usage: `not_empty` - will fail if the user's input is empty. + +``` dart +String score = "10"; +validate(rules: { + "Score": [score, "not_empty"] +}, onSuccess: () { + print("Success! The input is not empty"); +}); +``` + + +
+ +### Regex + +Check the input against a regex pattern. + +Usage: `r'regex:([0-9]+)'` - will fail if the user's input does not match the regex pattern. + +``` dart +String password = "Password1!"; +validate(rules: { + "Password": [password, r'regex:^(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[^\w\d\s:])([^\s]){8,16}$'] +}, onSuccess: () { + print("Success! The password is valid"); +}); +``` + + +
+ +### numeric + +Check if the input is a numeric match. + +Usage: `numeric` - will fail if the user's input is not numeric. + +``` dart +String age = '18'; +validate(rules: { + "Age": [age, "numeric"] +}, onSuccess: () { + print("Success! The age is a number"); +}); +``` + + +
+ +### date + +Check if the input is a date, e.g. 2020-02-29. + +Usage: `date` - will fail if the user's input is not date. + +``` dart +// String +String birthday = '1990-01-01'; +validate(rules: { + "Birthday": [birthday, "date"] +}, onSuccess: () { + print("Success! The birthday is a valid date"); +}); + +// DateTime +DateTime birthday = DateTime(1990, 1, 1); +validate(rules: { + "Birthday": [birthday, "date"] +}, onSuccess: () { + print("Success! The birthday is a valid date"); +}); +``` + + +
+ +### capitalized + +Check if the input is capitalized, e.g. "Hello world". + +Usage: `capitalized` - will fail if the user's input is not capitalized. + +``` dart +String helloWorld = "Hello world"; +validate(rules: { + "Hello World": [helloWorld, "capitalized"] +}, onSuccess: () { + print("Success! The input is capitalized"); +}); +``` + + +
+ +### lowercase + +Check if the input is lowercase, e.g. "hello world". + +Usage: `lowercase` - will fail if the user's input is not lowercased. + +``` dart +String helloWorld = "hello world"; +validate(rules: { + "Hello World": [helloWorld, "lowercase"] +}, onSuccess: () { + print("Success! The input is lowercase"); +}); +``` + + +
+ +### uppercase + +Check if the input is uppercase, e.g. "HELLO WORLD". + +Usage: `uppercase` - will fail if the user's input is not uppercase. + +``` dart +String helloWorld = "HELLO WORLD"; +validate(rules: { + "Hello World": [helloWorld, "uppercase"] +}, onSuccess: () { + print("Success! The input is uppercase"); +}); +``` + + +
+ +### US Phone Number + +Check if the input is a valid US Phone Number, e.g. "123-456-7890". + +Usage: `phone_number_us` - will fail if the user's input is not a US phone number. + +``` dart +String phoneNumber = '123-456-7890'; +validate(rules: { + "Phone Number": [phoneNumber, "phone_number_us"] +}, onSuccess: () { + print("Success! The phone number is a valid format"); +}); +``` + + +
+ +### UK Phone Number + +Check if the input is a valid UK Phone Number, e.g. "07123456789". + +Usage: `phone_number_uk` - will fail if the user's input is not a UK phone number. + +``` dart +String phoneNumber = '07123456789'; +validate(rules: { + "Phone Number": [phoneNumber, "phone_number_uk"] +}, onSuccess: () { + print("Success! The phone number is a valid format"); +}); +``` + + +
+ +### US Zipcode + +Check if the input is a valid US Zipcode, e.g. "33125". + +Usage: `zipcode_us` - will fail if the user's input is not a US Zipcode. + +``` dart +String zipcode = '33120'; +validate(rules: { + "Zipcode": [zipcode, "zipcode_us"] +}, onSuccess: () { + print("Success! The zipcode is valid"); +}); +``` + + +
+ +### UK Postcode + +Check if the input is a valid UK Postcode, e.g. "B3 1JJ". + +Usage: `postcode_uk` - will fail if the user's input is not a UK Postcode. + +``` dart +String postcode = 'B3 1JJ'; +validate(rules: { + "Postcode": [postcode, "postcode_uk"] +}, onSuccess: () { + print("Success! The postcode is valid"); +}); +``` + + +
+ +### Date age is younger + +Check if the input is a date and is younger than a certain age, e.g. "18". + +Usage: `date_age_is_younger:21` - will fail if the user's input is not a date and is younger than 21. + +You can validate against a `DateTime` or a `String` + +``` dart +// DateTime +DateTime userBithday = DateTime(2000, 1, 1); +validate(rules: { + "Birthday": [userBithday, "date_age_is_older:30"] +}, onSuccess: () { + print("Success! You're younger than 30"); +}, onFailure: (Exception exception) { + print('You are not younger than 30'); +}); + +// String +String userBithday = '2000-01-01'; +validate(rules: { + "Birthday": [userBithday, "date_age_is_older:30"] +}, onSuccess: () { + print("Success! You're younger than 30"); +}, onFailure: (Exception exception) { + print('You are not younger than 30'); +}); +``` + + +
+ +### Date age is older + +Check if the input is a date and is older than a certain age, e.g. "18". + +Usage: `date_age_is_older:40` - will fail if the user's input is not a date and is older than 40. + +You can validate against a `DateTime` or a `String` + +``` dart +// DateTime +DateTime userBithday = DateTime(2000, 1, 1); +validate(rules: { + "Birthday": [userBithday, "date_age_is_older:18"] +}, onSuccess: () { + print("Success! You're older than 18"); +}, onFailure: (Exception exception) { + print('You are not older than 18'); +}); + +// String +String userBithday = '2000-01-01'; +validate(rules: { + "Birthday": [userBithday, "date_age_is_older:18"] +}, onSuccess: () { + print("Success! You're older than 18"); +}, onFailure: (Exception exception) { + print('You are not older than 18'); +}); +``` + + +
+ +### Date in past + +Check if the input is a date and is in the past. + +Usage: `date_in_past` - will fail if the user's input is not a date and is in the past. + +``` dart +// String +String birthday = '1990-01-01'; +validate(rules: { + "Birthday": [birthday, "date_in_past"] +}, onSuccess: () { + print("Success! The birthday is in the past"); +}); + +// DateTime +DateTime birthday = DateTime(2030, 1, 1); +validate(rules: { + "Coupon Date": [birthday, "date_in_past"] +}, onSuccess: () { + print("Success! The birthday is in the past"); +}); +``` + + +
+ +### Date in future + +Check if the input is a date and is in the future. + +Usage: `date_in_future` - will fail if the user's input is not a date and is in the future. + +``` dart +// String +String couponDate = '2030-01-01'; +validate(rules: { + "Coupon Date": [couponDate, "date_in_future"] +}, onSuccess: () { + print("Success! The coupon date is in the future"); +}); + +// DateTime +DateTime couponDate = DateTime(2030, 1, 1); +validate(rules: { + "Coupon Date": [couponDate, "date_in_future"] +}, onSuccess: () { + print("Success! The coupon date is in the future"); +}); +``` + + +
+ +### Is True + +Check if the input is true. + +Usage: `is_true` - will fail if the user's input is not true. + +``` dart +bool hasAgreedToTerms = true; +validate(rules: { + "Terms of service": [hasAgreedToTerms, "is_true"] +}, onSuccess: () { + print("Success! You have agreed to the terms of service"); +}); +``` + + +
+ +### Is False + +Check if the input is false. + +Usage: `is_false` - will fail if the user's input is not false. + +``` dart +bool hasIphone = false; +String brand = "Google"; + +validate(rules: { + "Phone Compatible": [hasIphone, "is_false"], + "Brand": [brand, "contains:apple,samsung,google"] +}, onSuccess: () { + // onSuccess would not be called +}); +``` + + +
+ +## Custom Validation Rules + +You can add custom validation rules for your project by opening the `config/valdiation_rules.dart` file. + +The `validationRules` variable contains all your custom validation rules. + +``` dart +final Map validationRules = { + /// Example + // "simple_password": (attribute) => SimplePassword(attribute) +}; +``` + +To define a new validation rule, first create a new class that extends the `ValidationRule` class. Your validation class should implement the `handle` method like in the below example. + +``` dart +class SimplePassword extends ValidationRule { + SimplePassword(String attribute) + : super( + attribute: attribute, + signature: "simple_password", // Signature for the validator + description: "The $attribute field must be between 4 and 8 digits long and include at least one numeric digit", // Toast description when an error occurs + textFieldMessage: "Must be between 4 and 8 digits long with one numeric digit"); // TextField validator description when an error occurs + + @override + handle(Map info) { + super.handle(info); + + RegExp regExp = RegExp(r'^(?=.*\d).{4,8}$'); + return regExp.hasMatch(info['data']); + } +} +``` + +The `Map info` object: +``` dart +/// info['rule'] = Validation rule i.e "max:12". +/// info['data'] = Data the user has passed into the validation rule. +``` + +The `handle` method expects a `boolean` return type, if the data passes validation return `true` and `false` if it doesn't. diff --git a/resources/docs/5.20.0/what-is-nylo.md b/resources/docs/5.20.0/what-is-nylo.md new file mode 100644 index 0000000..226b687 --- /dev/null +++ b/resources/docs/5.20.0/what-is-nylo.md @@ -0,0 +1,77 @@ +# What is {{ config('app.name') }}? + +--- + + +- [Introduction](#introduction "Introduction") +- App Development + - [New to Flutter?](#new-to-flutter "New to Flutter?") + - [Maintenance and release schedule](#maintenance-and-release-schedule "Maintenance and release schedule") +- Credits + - [Framework Dependencies](#framework-dependencies "Framework Dependencies") + - [Contributors](#contributors "Contributors") + + + +
+## Introduction + +{{ config('app.name') }} is a micro-framework for Flutter which is designed to help simplify app development. Every project provides a simple boilerplate to help you build apps easier. {{ config('app.name') }} is an open source and MIT-licenced too, so you can freely build your projects. + +New installs of {{ config('app.name') }} contain standardized file structure and config, so you can jump straight into developing your project. + + +
+ +## New to Flutter? + +A great place to start learning Flutter is through the Flutter website. +They have tons of documentation to help you understand how things work from the core. + +You can also subscribe to their YouTube channel for updates, tutorials and information on Flutter. + + + +
+ +## Maintenance and release schedule + +The release schedule for {{ config('app.name') }} will be once or twice a year if there are breaking changes in the upcoming releases. Bugs will be dealt with ad hoc via the GitHub repositories. + +The {{ config('app.name') }} framework and support repository follow Semantic Versioning. + + +
+ +## Framework dependencies + +{{ config('app.name') }}'s framework + support library use the below open source libraries: + + +| | | +--- | --- | +| [url\_launcher](https://pub.dev/packages/url_launcher) | [validated](https://pub.dev/packages/validated) | +| [google\_fonts](https://pub.dev/packages/google_fonts) | [flutter\_styled\_toast](https://pub.dev/packages/flutter_styled_toast) | +| [flutter\_launcher\_icons](https://pub.dev/packages/flutter_launcher_icons) | [animate_do](https://pub.dev/packages/animate_do) | +| [theme\_provider](https://pub.dev/packages/theme_provider) | [collection](https://pub.dev/packages/collection) | +| [intl](https://pub.dev/packages/intl) | [equatable](https://pub.dev/packages/equatable) | +| [flutter\_secure\_storage](https://pub.dev/packages/flutter_secure_storage) | [cli\_dialog](https://pub.dev/packages/cli_dialog) | +| [flutter\_dotenv](https://pub.dev/packages/flutter_dotenv) | [flutter_dotenv](https://pub.dev/packages/flutter_dotenv) | +| [page\_transition](https://pub.dev/packages/page_transition) | [pull_to_refresh_flutter3](https://pub.dev/packages/pull_to_refresh_flutter3) | +| [args](https://pub.dev/packages/args) | [pretty\_dio\_logger](https://pub.dev/packages/pretty_dio_logger) | +| [analyzer](https://pub.dev/packages/analyzer) | [skeletonizer](https://pub.dev/packages/skeletonizer) | +| [recase](https://pub.dev/packages/recase) | [dio](https://pub.dev/packages/dio) | + + + +
+ +## Contributors + +Here's a shout out to the contributors who have helped build {{ config('app.name') }}! If you've contributed, reach out via support@nylo.dev to request us to add you here. + +- Anthony Gordon (Creator) +- Abdulrasheed1729 +- Rashid-Khabeer +- lpdevit +- youssefKadaouiAbbassi diff --git a/resources/docs/5.x/metro.md b/resources/docs/5.x/metro.md index b621c7a..1c6bb1a 100644 --- a/resources/docs/5.x/metro.md +++ b/resources/docs/5.x/metro.md @@ -34,21 +34,10 @@ It provides a lot of helpful tools to speed up development. Mac guide -1. **Open your bash\_profile** +**Run the below command from the terminal** ``` bash -sudo open ~/.bash_profile -``` - -2. **Add this alias to your bash\_profile** -``` bash -... -alias metro='dart run nylo_framework:main' -``` - -3. **Then run the following** -``` bash -source ~/.bash_profile +echo "alias metro='dart run nylo_framework:main'" >>~/.bash_profile && source ~/.bash_profile ``` If you open a project that uses {{ config('app.name') }}, try to run the following in the terminal. diff --git a/resources/views/docs/nav.blade.php b/resources/views/docs/nav.blade.php index fabc59f..13dbf51 100644 --- a/resources/views/docs/nav.blade.php +++ b/resources/views/docs/nav.blade.php @@ -1,5 +1,4 @@
-
@yield('content') @@ -132,6 +150,36 @@ class="absolute font-normal bg-white shadow overflow-hidden rounded w-24 border
+ + + +