├── .eslintrc.json ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── logo.png └── usage.gif ├── generate_code_mappings.py ├── package-lock.json ├── package.json ├── src ├── commands │ ├── clean_architecture_command.ts │ ├── clean_architecture_feat_command.ts │ ├── clean_main_command.ts │ ├── feature_structure_command.ts │ ├── generate_endpoint_command.ts │ ├── horizontal_structure_command.ts │ ├── index.ts │ └── vertical_structure_command.ts ├── extension.ts ├── mappings │ └── code_template_mapping.ts ├── templates │ ├── clean_architecture_feat_template.json │ ├── clean_architecture_template.json │ ├── code │ │ ├── code.ts │ │ ├── extensions │ │ │ ├── date_extension.ts │ │ │ ├── int_extension.ts │ │ │ ├── str_extension.ts │ │ │ └── time_extension.ts │ │ ├── features │ │ │ ├── auth │ │ │ │ ├── logic │ │ │ │ │ ├── authorization │ │ │ │ │ │ ├── authorization_bloc.ts │ │ │ │ │ │ ├── authorization_event.ts │ │ │ │ │ │ └── authorization_state.ts │ │ │ │ │ ├── login │ │ │ │ │ │ ├── login_cubit.ts │ │ │ │ │ │ └── login_state.ts │ │ │ │ │ └── register │ │ │ │ │ │ ├── register_cubit.ts │ │ │ │ │ │ └── register_state.ts │ │ │ │ ├── models │ │ │ │ │ ├── auth_error.ts │ │ │ │ │ ├── login_request.ts │ │ │ │ │ └── register_request.ts │ │ │ │ ├── repo │ │ │ │ │ └── auth_repo.ts │ │ │ │ └── utils │ │ │ │ │ └── auth_error_helper.ts │ │ │ ├── bottom_nav │ │ │ │ ├── blocs │ │ │ │ │ └── navigation │ │ │ │ │ │ └── navigation_cubit.ts │ │ │ │ ├── components │ │ │ │ │ ├── bottom_nav_widget.ts │ │ │ │ │ └── tab_navigator.ts │ │ │ │ ├── models │ │ │ │ │ └── nav_item.ts │ │ │ │ └── views │ │ │ │ │ └── bottom_nav_view.ts │ │ │ ├── form │ │ │ │ ├── components │ │ │ │ │ ├── form_builder.ts │ │ │ │ │ └── password_field.ts │ │ │ │ ├── models │ │ │ │ │ └── form_item.ts │ │ │ │ └── utils │ │ │ │ │ └── validator.ts │ │ │ └── onboarding │ │ │ │ ├── components │ │ │ │ ├── onboarding_content_widget.ts │ │ │ │ └── pagination.ts │ │ │ │ ├── models │ │ │ │ └── onboarding_content.ts │ │ │ │ └── views │ │ │ │ └── onboarding_view.ts │ │ ├── main.ts │ │ ├── res │ │ │ ├── colors.ts │ │ │ ├── dimens.ts │ │ │ └── styles.ts │ │ ├── services │ │ │ ├── network │ │ │ │ └── dio_inst.ts │ │ │ └── storage │ │ │ │ └── prefs.ts │ │ ├── utils.ts │ │ └── views │ │ │ └── home_view.ts │ ├── endpoint │ │ ├── cubit.ts │ │ ├── data_class.ts │ │ ├── interface.ts │ │ └── repo.ts │ ├── feat_template.json │ ├── features │ │ ├── auth_template.json │ │ ├── bottom_nav_template.json │ │ ├── form_template.json │ │ └── onboarding_template.json │ ├── horiz_template.json │ └── vert_template.json ├── types │ └── metadata.ts └── utils │ ├── type_utils.ts │ └── utils.ts ├── tsconfig.json ├── vsc-extension-quickstart.md └── webpack.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | }, 19 | "ignorePatterns": [ 20 | "out", 21 | "dist", 22 | "**/*.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["dbaeumer.vscode-eslint", "amodio.tsl-problem-matcher"] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/dist/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "args": [ 25 | "--extensionDevelopmentPath=${workspaceFolder}", 26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/out/**/*.js", 30 | "${workspaceFolder}/dist/**/*.js" 31 | ], 32 | "preLaunchTask": "tasks: watch-tests" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files 5 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files 6 | }, 7 | "search.exclude": { 8 | "out": true, // set this to false to include "out" folder in search results 9 | "dist": true // set this to false to include "dist" folder in search results 10 | }, 11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 12 | "typescript.tsc.autoDetect": "off" 13 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": [ 10 | "$ts-webpack-watch", 11 | "$tslint-webpack-watch" 12 | ], 13 | "isBackground": true, 14 | "presentation": { 15 | "reveal": "never", 16 | "group": "watchers" 17 | }, 18 | "group": { 19 | "kind": "build", 20 | "isDefault": true 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "watch-tests", 26 | "problemMatcher": "$tsc-watch", 27 | "isBackground": true, 28 | "presentation": { 29 | "reveal": "never", 30 | "group": "watchers" 31 | }, 32 | "group": "build" 33 | }, 34 | { 35 | "label": "tasks: watch-tests", 36 | "dependsOn": [ 37 | "npm: watch", 38 | "npm: watch-tests" 39 | ], 40 | "problemMatcher": [] 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/** 4 | node_modules/** 5 | src/** 6 | .gitignore 7 | .yarnrc 8 | webpack.config.js 9 | vsc-extension-quickstart.md 10 | **/tsconfig.json 11 | **/.eslintrc.json 12 | **/*.map 13 | **/*.ts 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "flutter-folder-structure-generator" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## 1.0.5 8 | 9 | - Fix: Injectable Generate Endpoint Code Syntax 10 | 11 | ## 1.0.4 12 | 13 | - Fix: List case and void case 14 | 15 | ## 1.0.3 16 | 17 | - Feat: Add Generate Endpoint support for injectable 18 | 19 | ## 1.0.2 20 | 21 | - Fix: Minor Issue on Import 22 | - Feat: Generate models if they are custom type 23 | 24 | ## 1.0.1 25 | 26 | - Add Url, Param Type, Return Type to Endpoint Generation 27 | 28 | ## 1.0.0 29 | 30 | - Generate Endpoint boilerplate compatible with Clean Architecture 31 | 32 | ## 0.0.7 33 | 34 | - Adds Functionality to generate boilerplate code 35 | 36 | ## [Unreleased] 37 | 38 | - Initial release 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Sushan Shakya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | Architecture Generator 4 |

5 | 6 | ## Overview 7 | [VSCode](https://code.visualstudio.com/) support for generating [Flutter](https://flutter.dev/) project Architecture 8 | 9 | ## Commands 10 | 11 | | Command | Description | 12 | | ------------------ | -------------------- | 13 | | `Folder : Horizontal` | Generate Horizonal Architecture | 14 | | `Folder : Vertical` | Generate Vertical Architecture | 15 | | `Folder : Feature (Vertical)` | Generate sub-folders for a feature in verticle architecture | 16 | | `Clean : main.dart` | Clean the defaul code for main.dart | 17 | | `Clean Architecture` | Generate the Folder Structure for Clean Architecture | 18 | | `Clean Architecture : Feature` | Genearte sub-folder for a feature in Clean Architecture | 19 | 20 | Use the commands by right clicking a folder in explorer.
21 | Command cannot be used directly from command palette 22 | 23 | ## Usage 24 | 25 | ![demo](https://raw.githubusercontent.com/SushanShakya/flutter_folder_structure_generator/main/assets/usage.gif) 26 | 27 | 28 | Clean : main.dart
29 | -> Right click on lib folder and click on Clean : main.dart 30 | 31 | ## Hidden Features 32 | 33 | Command :
34 | Folder : Feature (Vertical) 35 | 36 | Using `form` or `forms` as feature name will generate code associated with forms.
37 | -> Provides `FormBuilder` Widget which can generate forms easily 38 | 39 | Using `bottom_nav` as feature name will generate code associated with Bottom Navigation.
40 | -> Provides `BottomNavView` widget which can be used as a screen 41 | 42 | Using `onboarding` as feature name will generate code associated with Onboarding Screens.
43 | -> Provides `OnboardingView` widget which can be used as a screen 44 | 45 | Using `auth` as feature name will generate code associated with Authorization, Login and Register.
46 | -> Provides `AuthorizationBloc`, `RegisterCubit`, `LoginCubit` widgets -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SushanShakya/flutter_folder_structure_generator/0024f76ea0d22e4994a291cf57a6ff128086ab1b/assets/logo.png -------------------------------------------------------------------------------- /assets/usage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SushanShakya/flutter_folder_structure_generator/0024f76ea0d22e4994a291cf57a6ff128086ab1b/assets/usage.gif -------------------------------------------------------------------------------- /generate_code_mappings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | dirpath = os.getcwd() 4 | foldername = os.path.basename(dirpath) 5 | 6 | fullPath = dirpath+ "\\" + "src\\templates\\code" 7 | 8 | class DirStructure: 9 | def __init__(self, map): 10 | self.parent = map['parent'] 11 | self.name = map['name'] 12 | 13 | def __str__(self): 14 | return f"{self.parent}/{self.name}" 15 | 16 | 17 | def getListOfFiles(dirName, parent): 18 | # create a list of file and sub directories 19 | # names in the given directory 20 | listOfFile = os.listdir(dirName) 21 | allFiles = list() 22 | # Iterate over all the entries 23 | for entry in listOfFile: 24 | # Create full path 25 | fullPath = os.path.join(dirName, entry) 26 | # If entry is a directory then get the list of files in this directory 27 | if os.path.isdir(fullPath): 28 | allFiles = allFiles + getListOfFiles(fullPath, f"{parent}/{entry}") 29 | else: 30 | allFiles.append(DirStructure({ 31 | 'parent' : parent, 32 | 'name' : entry 33 | })) 34 | 35 | return allFiles 36 | 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutter-folder-structure-generator", 3 | "displayName": "Flutter Architecture Generator", 4 | "description": "Extension to easily generate folder structre associated with flutter", 5 | "version": "1.0.5", 6 | "publisher": "SushanShakya", 7 | "bugs": { 8 | "url": "https://github.com/SushanShakya/flutter_folder_structure_generator/issues", 9 | "email": "sushaanshakya88@gmail.com" 10 | }, 11 | "repository": { 12 | "url": "https://github.com/SushanShakya/flutter_folder_structure_generator", 13 | "type": "git" 14 | }, 15 | "engines": { 16 | "vscode": "^1.65.0" 17 | }, 18 | "categories": [ 19 | "Other", 20 | "Extension Packs" 21 | ], 22 | "keywords": [ 23 | "dart", 24 | "flutter", 25 | "folder-structure", 26 | "productivity" 27 | ], 28 | "icon": "assets/logo.png", 29 | "activationEvents": [ 30 | "onCommand:extension.horizStruct", 31 | "onCommand:extension.vertStruct", 32 | "onCommand:extension.featStruct", 33 | "onCommand:extension.cleanMain", 34 | "onCommand:extension.cleanArchitecture", 35 | "onCommand:extension.cleanArchitectureFeat", 36 | "onCommand:extension.generateEndpointCode", 37 | "onCommand:extension.generateEndpointCodeInjectable" 38 | ], 39 | "main": "./dist/extension.js", 40 | "contributes": { 41 | "commands": [ 42 | { 43 | "command": "extension.generateEndpointCode", 44 | "title": "Generate Endpoint Code", 45 | "icon": "assets/logo.png" 46 | }, 47 | { 48 | "command": "extension.generateEndpointCodeInjectable", 49 | "title": "Generate Endpoint Code (Injectable)", 50 | "icon": "assets/logo.png" 51 | }, 52 | { 53 | "command": "extension.featStruct", 54 | "title": "Folder: Feature (Vertical)", 55 | "icon": "assets/logo.png" 56 | }, 57 | { 58 | "command": "extension.vertStruct", 59 | "title": "Folder: Vertical", 60 | "icon": "assets/logo.png" 61 | }, 62 | { 63 | "command": "extension.horizStruct", 64 | "title": "Folder: Horizontal", 65 | "icon": "assets/logo.png" 66 | }, 67 | { 68 | "command": "extension.cleanMain", 69 | "title": "Clean : main.dart", 70 | "icon": "assets/logo.png" 71 | }, 72 | { 73 | "command": "extension.cleanArchitecture", 74 | "title": "Clean Architecture", 75 | "icon": "assets/logo.png" 76 | }, 77 | { 78 | "command": "extension.cleanArchitectureFeat", 79 | "title": "Clean Architecture: Feature", 80 | "icon": "assets/logo.png" 81 | } 82 | ], 83 | "menus": { 84 | "explorer/context": [ 85 | { 86 | "command": "extension.featStruct", 87 | "group": "structGroup@1", 88 | "when": "explorerResourceIsFolder" 89 | }, 90 | { 91 | "command": "extension.vertStruct", 92 | "group": "structGroup@2", 93 | "when": "explorerResourceIsFolder" 94 | }, 95 | { 96 | "command": "extension.horizStruct", 97 | "group": "structGroup@3", 98 | "when": "explorerResourceIsFolder" 99 | }, 100 | { 101 | "command": "extension.cleanArchitecture", 102 | "group": "cleanGroup@3", 103 | "when": "explorerResourceIsFolder" 104 | }, 105 | { 106 | "command": "extension.cleanArchitectureFeat", 107 | "group": "cleanGroup@3", 108 | "when": "explorerResourceIsFolder" 109 | }, 110 | { 111 | "command": "extension.cleanMain", 112 | "group": "mainGroup@1", 113 | "when": "explorerResourceIsFolder" 114 | }, 115 | { 116 | "command": "extension.generateEndpointCode", 117 | "group": "ep@4", 118 | "when": "explorerResourceIsFolder" 119 | }, 120 | { 121 | "command": "extension.generateEndpointCodeInjectable", 122 | "group": "ep@4", 123 | "when": "explorerResourceIsFolder" 124 | } 125 | ] 126 | } 127 | }, 128 | "scripts": { 129 | "vscode:prepublish": "npm run package", 130 | "compile": "webpack", 131 | "watch": "webpack --watch", 132 | "package": "webpack --mode production --devtool hidden-source-map", 133 | "compile-tests": "tsc -p . --outDir out", 134 | "watch-tests": "tsc -p . -w --outDir out", 135 | "pretest": "npm run compile-tests && npm run compile && npm run lint", 136 | "lint": "eslint src --ext ts", 137 | "test": "node ./out/test/runTest.js" 138 | }, 139 | "devDependencies": { 140 | "@types/glob": "^7.2.0", 141 | "@types/lodash": "^4.14.202", 142 | "@types/mkdirp": "^1.0.2", 143 | "@types/mocha": "^9.1.0", 144 | "@types/node": "14.x", 145 | "@types/vscode": "^1.65.0", 146 | "@typescript-eslint/eslint-plugin": "^5.12.1", 147 | "@typescript-eslint/parser": "^5.12.1", 148 | "@vscode/test-electron": "^2.1.2", 149 | "eslint": "^8.9.0", 150 | "glob": "^7.2.0", 151 | "mocha": "^9.2.1", 152 | "ts-loader": "^9.2.6", 153 | "typescript": "^4.5.5", 154 | "webpack": "^5.69.1", 155 | "webpack-cli": "^4.9.2" 156 | }, 157 | "dependencies": { 158 | "lodash": "^4.17.21", 159 | "mkdirp": "^1.0.4" 160 | } 161 | } -------------------------------------------------------------------------------- /src/commands/clean_architecture_command.ts: -------------------------------------------------------------------------------- 1 | import { Uri } from "vscode"; 2 | import * as template from "../templates/clean_architecture_template.json" 3 | import { generateStructure } from "../utils/utils"; 4 | 5 | export const cleanArchitecture = async (uri: Uri) => { 6 | console.log('---- Clean Architecture') 7 | generateStructure(uri, template) 8 | } 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/commands/clean_architecture_feat_command.ts: -------------------------------------------------------------------------------- 1 | import * as template from "../templates/clean_architecture_feat_template.json" 2 | import { generateTemplate } from "../utils/utils"; 3 | import { InputBoxOptions, Uri, window } from "vscode"; 4 | 5 | export const cleanArchitectureFeat = async (uri: Uri) => { 6 | console.log('---- Clean Architecture feat'); 7 | // [full_path] is the path where we generate the folder structure into. 8 | let full_path: string; 9 | 10 | // Check for when user invokes command from Ctrl + Shift + p 11 | if (typeof uri === "undefined") { 12 | window.showErrorMessage("Please use command from explorer !"); 13 | return; 14 | } else { 15 | full_path = uri.fsPath; 16 | } 17 | 18 | let featureName = await promptForName(); 19 | if ((typeof featureName === "undefined") || (featureName.trim() === "")) { 20 | window.showErrorMessage("The Feature name cannot be empty !"); 21 | return; 22 | } 23 | let new_template: any = {}; 24 | new_template[featureName] = template 25 | 26 | generateTemplate(new_template, full_path); 27 | 28 | } 29 | 30 | 31 | function promptForName(): Thenable { 32 | const options: InputBoxOptions = { 33 | prompt: "Feature Name", 34 | placeHolder: "feature", 35 | }; 36 | return window.showInputBox(options); 37 | } 38 | -------------------------------------------------------------------------------- /src/commands/clean_main_command.ts: -------------------------------------------------------------------------------- 1 | import { InputBoxOptions, Uri, window } from "vscode"; 2 | import { getCleanedMain } from "../templates/code/main"; 3 | import { createFile } from "../utils/utils"; 4 | 5 | export const cleanMain = async (uri: Uri) => { 6 | // [full_path] is the path where we generate the folder structure into. 7 | let full_path: string; 8 | 9 | // Check for when user invokes command from Ctrl + Shift + p 10 | if (typeof uri === "undefined") { 11 | window.showErrorMessage("Please use command from explorer !"); 12 | return; 13 | } else { 14 | full_path = uri.fsPath; 15 | } 16 | 17 | var appName = await promptForName(); 18 | if ((typeof appName === "undefined") || (appName.trim() === "")) { 19 | window.showErrorMessage("The App name cannot be empty !"); 20 | return; 21 | } 22 | 23 | let mainPath = `${full_path}\\main.dart`; 24 | let content = getCleanedMain(appName); 25 | 26 | createFile(mainPath, content); 27 | }; 28 | 29 | function promptForName(): Thenable { 30 | const cubitNamePromptOptions: InputBoxOptions = { 31 | prompt: "App Name", 32 | placeHolder: "app name in snake_case", 33 | }; 34 | return window.showInputBox(cubitNamePromptOptions); 35 | } -------------------------------------------------------------------------------- /src/commands/feature_structure_command.ts: -------------------------------------------------------------------------------- 1 | import { InputBoxOptions, Uri, window } from "vscode"; 2 | import { generateTemplate } from "../utils/utils"; 3 | import * as template from "../templates/feat_template.json"; 4 | import * as form_template from "../templates/features/form_template.json"; 5 | import * as bot_nav_template from "../templates/features/bottom_nav_template.json"; 6 | import * as onboarding_template from "../templates/features/onboarding_template.json"; 7 | import * as auth_template from "../templates/features/auth_template.json"; 8 | 9 | export const featStruct = async (uri: Uri) => { 10 | // [full_path] is the path where we generate the folder structure into. 11 | let full_path: string; 12 | 13 | // Check for when user invokes command from Ctrl + Shift + p 14 | if (typeof uri === "undefined") { 15 | window.showErrorMessage("Please use command from explorer !"); 16 | return; 17 | } else { 18 | full_path = uri.fsPath; 19 | } 20 | 21 | let featureName = await promptForName(); 22 | if ((typeof featureName === "undefined") || (featureName.trim() === "")) { 23 | window.showErrorMessage("The Feature name cannot be empty !"); 24 | return; 25 | } 26 | 27 | let new_template: any = {}; 28 | if (featureName === "form" || featureName === "forms") { 29 | new_template = form_template; 30 | } else if (featureName === "bottom_nav") { 31 | new_template = bot_nav_template; 32 | } else if (featureName === "onboarding") { 33 | new_template = onboarding_template; 34 | } else if (featureName === "auth") { 35 | new_template = auth_template; 36 | } else { 37 | new_template[featureName] = template; 38 | } 39 | generateTemplate(new_template, full_path); 40 | }; 41 | 42 | function promptForName(): Thenable { 43 | const cubitNamePromptOptions: InputBoxOptions = { 44 | prompt: "Feature Name", 45 | placeHolder: "feature", 46 | }; 47 | return window.showInputBox(cubitNamePromptOptions); 48 | } -------------------------------------------------------------------------------- /src/commands/generate_endpoint_command.ts: -------------------------------------------------------------------------------- 1 | import { isNil } from "lodash"; 2 | import { InputBoxOptions, Uri, window } from "vscode"; 3 | import { generateCubit } from "../templates/endpoint/cubit"; 4 | import { generateRepo } from "../templates/endpoint/repo"; 5 | import { generateInterface } from "../templates/endpoint/interface"; 6 | import { getFirstLetterCapital, upperCamelToSnake } from "../templates/code/utils"; 7 | import { createDirectory, createFile } from "../utils/utils"; 8 | import path from "path"; 9 | import { EndpointMetadata, TemplateMetadata } from "../types/metadata"; 10 | import { generateDataClass } from "../templates/endpoint/data_class"; 11 | import { extractListType, isPrimitive } from "../utils/type_utils"; 12 | 13 | type EndpointTemplate = { 14 | cubit: string, 15 | interfaceStr: string, 16 | repo: string, 17 | param: string, 18 | response: string, 19 | } 20 | 21 | 22 | 23 | const generateTemplate = (metadata: TemplateMetadata): EndpointTemplate => { 24 | let cubit = generateCubit(metadata); 25 | let repo = generateRepo(metadata); 26 | let interfaceStr = generateInterface(metadata); 27 | let request = generateDataClass(metadata.endpoint.paramType ?? ""); 28 | let response = generateDataClass(metadata.endpoint.returnType ?? ""); 29 | 30 | return { 31 | cubit, 32 | interfaceStr, 33 | repo, 34 | param: request, 35 | response, 36 | } 37 | } 38 | 39 | const promptName = async (options: InputBoxOptions): Promise => { 40 | let name = await window.showInputBox(options) 41 | if (isNil(name) || name.trim() === "") { 42 | window.showErrorMessage("The name must not be empty"); 43 | return; 44 | } 45 | return name; 46 | } 47 | 48 | const handlePrompts = async (): Promise => { 49 | 50 | let url = await promptName({ 51 | prompt: "Endpoint Url", 52 | placeHolder: "Defaults to /", 53 | }); 54 | 55 | let className = await promptName({ 56 | prompt: "Class Name", 57 | placeHolder: "In Upper Camel Case", 58 | }); 59 | if (!className) return; 60 | 61 | let fnName = await promptName({ 62 | prompt: "Function Name", 63 | placeHolder: "In Lower Camel Case", 64 | }); 65 | if (!fnName) return; 66 | 67 | 68 | let paramType = await window.showInputBox({ 69 | prompt: "Param Type", 70 | placeHolder: "In Upper Camel Case (Optional)", 71 | }); 72 | 73 | if (isNil(paramType) || paramType.trim() === "") { 74 | paramType = undefined; 75 | } 76 | 77 | let returnType = await promptName({ 78 | prompt: "Return Type", 79 | placeHolder: "In Upper Camel Case", 80 | }); 81 | if (!returnType) return; 82 | 83 | return { 84 | url: url ?? '/', 85 | className, fnName, paramType, returnType 86 | } 87 | } 88 | 89 | const generateDataClassFileName = (type: string): string => { 90 | if (type.startsWith("List<")) { 91 | let listType = extractListType(type); 92 | return generateDataClassFileName(listType); 93 | } 94 | return upperCamelToSnake(type); 95 | } 96 | 97 | export const generateEndpointCode = async (uri: Uri, { 98 | injectable = false, 99 | }: { injectable: boolean }) => { 100 | if (typeof uri === 'undefined') { 101 | window.showErrorMessage("Please use command from explorer !"); 102 | return; 103 | } 104 | let dirPath = uri.fsPath; 105 | 106 | let prompts = await handlePrompts(); 107 | if (!prompts) return; 108 | const { 109 | className, 110 | paramType, 111 | returnType 112 | } = prompts; 113 | 114 | let param = prompts.paramType ? `${prompts.paramType} param` : ""; 115 | 116 | let classNameInSnake = upperCamelToSnake(className); 117 | let paramNameInSnake = generateDataClassFileName(paramType ?? ""); 118 | let returnTypeNameInSnake = generateDataClassFileName(returnType); 119 | 120 | let generateParamCode = !isPrimitive(paramType ?? "int"); 121 | let generateResponseCode = !isPrimitive(returnType); 122 | 123 | let cubitPath = path.join(path.join(dirPath, "gui"), "presenters"); 124 | let interfacePath = path.join(path.join(path.join(dirPath, "data"), "repo"), "interface"); 125 | let repoPath = path.join(path.join(dirPath, "data"), "repo"); 126 | let modelPath = path.join(path.join(dirPath, "data"), "models"); 127 | 128 | let cubitFile = `${classNameInSnake}_cubit.dart`; 129 | let interfaceFile = `i${classNameInSnake}_repo.dart`; 130 | let repoFile = `${classNameInSnake}_repo.dart`; 131 | let paramFile = `${paramNameInSnake}.dart`; 132 | let responseFile = `${returnTypeNameInSnake}.dart`; 133 | 134 | 135 | let metadata: TemplateMetadata = { 136 | endpoint: prompts, interfaceFileName: interfaceFile, param, 137 | generateParamCode, generateResponseCode, 138 | paramFileName: paramFile, 139 | responseFileName: responseFile, 140 | injectable, 141 | }; 142 | 143 | let template = generateTemplate(metadata); 144 | 145 | try { 146 | createDirectory(cubitPath); 147 | createDirectory(interfacePath); 148 | createDirectory(repoPath); 149 | if (generateParamCode || generateResponseCode) { 150 | createDirectory(modelPath); 151 | } 152 | createFile(path.join(cubitPath, cubitFile), template.cubit); 153 | createFile(path.join(interfacePath, interfaceFile), template.interfaceStr); 154 | createFile(path.join(repoPath, repoFile), template.repo); 155 | if (generateParamCode) { 156 | createFile(path.join(modelPath, paramFile), template.param); 157 | } 158 | if (generateResponseCode) { 159 | createFile(path.join(modelPath, responseFile), template.response); 160 | } 161 | } catch (e) { 162 | console.log('---- Error while trying to create file'); 163 | console.log(e); 164 | window.showErrorMessage("Failed to generate code"); 165 | return; 166 | } 167 | } 168 | 169 | export const generateEndpointCodeInjectable = async (uri: Uri) => generateEndpointCode(uri, { 170 | injectable: true, 171 | }); -------------------------------------------------------------------------------- /src/commands/horizontal_structure_command.ts: -------------------------------------------------------------------------------- 1 | import { Uri } from "vscode"; 2 | import * as template from "../templates/horiz_template.json" 3 | import { generateStructure } from "../utils/utils"; 4 | 5 | export const horizStruct = async (uri: Uri) => { 6 | generateStructure(uri, template) 7 | } 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/commands/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./feature_structure_command"; 2 | export * from "./horizontal_structure_command"; 3 | export * from "./vertical_structure_command"; 4 | export * from "./clean_main_command"; 5 | export * from "./clean_architecture_command" 6 | export * from "./clean_architecture_feat_command" 7 | export * from "./generate_endpoint_command" -------------------------------------------------------------------------------- /src/commands/vertical_structure_command.ts: -------------------------------------------------------------------------------- 1 | import { Uri } from "vscode"; 2 | import { generateStructure } from "../utils/utils"; 3 | import * as template from "../templates/vert_template.json" 4 | 5 | export const vertStruct = async (uri: Uri) => { 6 | generateStructure(uri, template) 7 | } -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | import { 3 | horizStruct, 4 | vertStruct, 5 | featStruct, 6 | cleanMain, 7 | cleanArchitecture, 8 | cleanArchitectureFeat, 9 | generateEndpointCode, 10 | generateEndpointCodeInjectable, 11 | } from "./commands" 12 | 13 | // Start of the Extension 14 | // -> Called when the extension is active 15 | export function activate(context: vscode.ExtensionContext) { 16 | context.subscriptions.push( 17 | vscode.commands.registerCommand("extension.featStruct", featStruct), 18 | vscode.commands.registerCommand("extension.vertStruct", vertStruct), 19 | vscode.commands.registerCommand("extension.horizStruct", horizStruct), 20 | vscode.commands.registerCommand("extension.cleanArchitecture", cleanArchitecture), 21 | vscode.commands.registerCommand("extension.cleanArchitectureFeat", cleanArchitectureFeat), 22 | vscode.commands.registerCommand("extension.cleanMain", cleanMain), 23 | vscode.commands.registerCommand("extension.generateEndpointCode", generateEndpointCode), 24 | vscode.commands.registerCommand("extension.generateEndpointCodeInjectable", generateEndpointCodeInjectable), 25 | ) 26 | } 27 | 28 | 29 | // Called when the extension is deactivated 30 | export function deactivate() { } 31 | -------------------------------------------------------------------------------- /src/mappings/code_template_mapping.ts: -------------------------------------------------------------------------------- 1 | import * as t from "../templates/code/code" 2 | 3 | let mappings: any = {} 4 | mappings['colors'] = t.colors 5 | mappings['dimens'] = t.dimens 6 | mappings['styles'] = t.styles 7 | mappings['dio_inst'] = t.dio_inst 8 | mappings['prefs'] = t.prefs 9 | mappings['home_view'] = t.home_view 10 | mappings['form_builder'] = t.form_builder 11 | mappings['password_field'] = t.password_field 12 | mappings['form_item'] = t.form_item 13 | mappings['validator'] = t.validator 14 | 15 | mappings['navigation_cubit'] = t.navigation_cubit 16 | mappings['bottom_nav_widget'] = t.bottom_nav_widget 17 | mappings['tab_navigator'] = t.tab_navigator 18 | mappings['nav_item'] = t.nav_item 19 | mappings['bottom_nav_view'] = t.bottom_nav_view 20 | 21 | mappings['onboarding_content_widget'] = t.onboarding_content_widget 22 | mappings['pagination'] = t.pagination 23 | mappings['onboarding_content'] = t.onboarding_content 24 | mappings['onboarding_view'] = t.onboarding_view 25 | 26 | mappings['date_extension'] = t.date_extension 27 | mappings['time_extension'] = t.time_extension 28 | mappings['str_extension'] = t.str_extension 29 | mappings['int_extension'] = t.int_extension 30 | 31 | mappings['authorization_bloc'] = t.authorization_bloc 32 | mappings['authorization_event'] = t.authorization_event 33 | mappings['authorization_state'] = t.authorization_state 34 | mappings['login_cubit'] = t.login_cubit 35 | mappings['login_state'] = t.login_state 36 | mappings['register_cubit'] = t.register_cubit 37 | mappings['register_state'] = t.register_state 38 | mappings['auth_error'] = t.auth_error 39 | mappings['login_request'] = t.login_request 40 | mappings['register_request'] = t.register_request 41 | mappings['auth_repo'] = t.auth_repo 42 | mappings['auth_error_helper'] = t.auth_error_helper 43 | 44 | export default mappings -------------------------------------------------------------------------------- /src/templates/clean_architecture_feat_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "repo": { 4 | "interface": {} 5 | }, 6 | "models": {} 7 | }, 8 | "domain": { 9 | "entities": {}, 10 | "usecases": {} 11 | }, 12 | "gui": { 13 | "views": {}, 14 | "components": {}, 15 | "presenters": {}, 16 | "view_models": {} 17 | } 18 | } -------------------------------------------------------------------------------- /src/templates/clean_architecture_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": { 3 | "core": { 4 | "styles": {} 5 | }, 6 | "modules": { 7 | "common": { 8 | "data": { 9 | "repo": { 10 | "interface": {} 11 | }, 12 | "models": {} 13 | }, 14 | "domain": { 15 | "entities": {}, 16 | "usecases": {} 17 | }, 18 | "gui": { 19 | "views": {}, 20 | "components": {}, 21 | "presenters": {}, 22 | "view_models": {} 23 | } 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/templates/code/code.ts: -------------------------------------------------------------------------------- 1 | // ---- Architecture Base Exports 2 | export { colors } from "./res/colors" 3 | export { dimens } from "./res/dimens" 4 | export { styles } from "./res/styles" 5 | export { dio_inst } from "./services/network/dio_inst" 6 | export { prefs } from "./services/storage/prefs" 7 | export { home_view } from "./views/home_view" 8 | // ---- Architecture Base Exports 9 | 10 | // ---- Form Exports 11 | export { form_builder } from "./features/form/components/form_builder"; 12 | export { password_field } from "./features/form/components/password_field"; 13 | export { form_item } from "./features/form/models/form_item"; 14 | export { validator } from "./features/form/utils/validator"; 15 | // ---- Form Exports 16 | 17 | // ---- Bottom Nav Exports 18 | export { navigation_cubit } from "./features/bottom_nav/blocs/navigation/navigation_cubit"; 19 | export { bottom_nav_widget } from "./features/bottom_nav/components/bottom_nav_widget"; 20 | export { tab_navigator } from "./features/bottom_nav/components/tab_navigator"; 21 | export { nav_item } from "./features/bottom_nav/models/nav_item"; 22 | export { bottom_nav_view } from "./features/bottom_nav/views/bottom_nav_view"; 23 | // ---- Bottom Nav Exports 24 | 25 | 26 | // ---- Onboarding Nav Exports 27 | export { onboarding_content_widget } from './features/onboarding/components/onboarding_content_widget'; 28 | export { pagination } from './features/onboarding/components/pagination'; 29 | export { onboarding_content } from './features/onboarding/models/onboarding_content'; 30 | export { onboarding_view } from './features/onboarding/views/onboarding_view'; 31 | // ---- Onboarding Nav Exports 32 | 33 | // ---- Extension Exports 34 | export { date_extension } from './extensions/date_extension'; 35 | export { int_extension } from './extensions/int_extension'; 36 | export { str_extension } from './extensions/str_extension'; 37 | export { time_extension } from './extensions/time_extension'; 38 | // ---- Extension Exports 39 | 40 | // ---- Authentication Exports 41 | export { authorization_bloc } from './features/auth/logic/authorization/authorization_bloc'; 42 | export { authorization_event } from './features/auth/logic/authorization/authorization_event'; 43 | export { authorization_state } from './features/auth/logic/authorization/authorization_state'; 44 | 45 | export { login_cubit } from './features/auth/logic/login/login_cubit'; 46 | export { login_state } from './features/auth/logic/login/login_state'; 47 | 48 | export { register_cubit } from './features/auth/logic/register/register_cubit'; 49 | export { register_state } from './features/auth/logic/register/register_state'; 50 | 51 | export { auth_error } from './features/auth/models/auth_error'; 52 | export { login_request } from './features/auth/models/login_request'; 53 | export { register_request } from './features/auth/models/register_request'; 54 | 55 | export { auth_repo } from './features/auth/repo/auth_repo'; 56 | 57 | export { auth_error_helper } from './features/auth/utils/auth_error_helper'; 58 | 59 | // ---- Authentication Exports -------------------------------------------------------------------------------- /src/templates/code/extensions/date_extension.ts: -------------------------------------------------------------------------------- 1 | export let date_extension = `import 'package:flutter/material.dart'; 2 | import 'time_extension.dart'; 3 | 4 | extension DateUtils on DateTime { 5 | static final Map _months = { 6 | 1: "Jan", 7 | 2: "Feb", 8 | 3: "Mar", 9 | 4: "Apr", 10 | 5: "May", 11 | 6: "Jun", 12 | 7: "Jul", 13 | 8: "Aug", 14 | 9: "Sep", 15 | 10: "Oct", 16 | 11: "Nov", 17 | 12: "Dec", 18 | }; 19 | // static final Map _fullmonths = { 20 | // 1: "January", 21 | // 2: "February", 22 | // 3: "March", 23 | // 4: "April", 24 | // 5: "May", 25 | // 6: "June", 26 | // 7: "July", 27 | // 8: "August", 28 | // 9: "September", 29 | // 10: "October", 30 | // 11: "November", 31 | // 12: "December", 32 | // }; 33 | 34 | String get stringify => "$year-$month-$day"; 35 | 36 | String get excludeYear => "$month/$day"; 37 | 38 | String get dayFormat { 39 | var m = _months[month]; 40 | return '$m $day'; 41 | } 42 | 43 | String get namedFormat { 44 | var m = _months[month]; 45 | return "$m $day, $year"; 46 | } 47 | 48 | static final Map _weekDay = { 49 | 1: 'Mon', 50 | 2: 'Tue', 51 | 3: 'Wed', 52 | 4: 'Thu', 53 | 5: 'Fri', 54 | 6: 'Sat', 55 | 7: 'Sun' 56 | }; 57 | static final Map _weekDayFull = { 58 | 1: 'Monday', 59 | 2: 'Tuesday', 60 | 3: 'Wednesday', 61 | 4: 'Thursday', 62 | 5: 'Friday', 63 | 6: 'Saturday', 64 | 7: 'Sunday' 65 | }; 66 | 67 | String get weekFormat => _weekDayFull[weekday] ?? ''; 68 | String get weekFormatCut => _weekDay[weekday] ?? ''; 69 | 70 | String get formatted { 71 | var date = this; 72 | return "\${_weekDay[date.weekday]}, \${_months[date.month]} \${date.day}"; 73 | } 74 | 75 | String get msgFormat { 76 | TimeOfDay cur = TimeOfDay.fromDateTime(this); 77 | if (DateTime.now().difference(this).inDays == 0) { 78 | return cur.stringify; 79 | } 80 | return '\${_weekDay[weekday]} @ \${cur.stringify}'; 81 | } 82 | } 83 | ` -------------------------------------------------------------------------------- /src/templates/code/extensions/int_extension.ts: -------------------------------------------------------------------------------- 1 | export let int_extension = `extension IntFormat on int { 2 | String get nepaliFormat { 3 | String str = toString(); 4 | int len = str.length; 5 | List formatted = str.split("").reversed.toList(); 6 | if (len > 3) { 7 | formatted = []; 8 | for (int i = 0; i < len; i++) { 9 | if (i == 3 || (i % 2 != 0 && i > 3)) formatted.add(","); 10 | formatted.add(str[len - i - 1]); 11 | } 12 | } 13 | return formatted.reversed.join(); 14 | } 15 | 16 | static Map nepaliNumbers = { 17 | "0": "०", 18 | "1": "१", 19 | "2": "२", 20 | "3": "३", 21 | "4": "४", 22 | "5": "५", 23 | "6": "६", 24 | "7": "७", 25 | "8": "८", 26 | "9": "९", 27 | ",": ",", 28 | }; 29 | 30 | String get toNepali { 31 | return nepaliFormat.split('').map((e) => nepaliNumbers[e]).join(); 32 | } 33 | 34 | String get cartFormat { 35 | return (this > 9) ? '9+' : toString(); 36 | } 37 | } 38 | ` -------------------------------------------------------------------------------- /src/templates/code/extensions/str_extension.ts: -------------------------------------------------------------------------------- 1 | export let str_extension = `extension StrFormat on String { 2 | String get firstCapital { 3 | List values = split(''); 4 | values[0] = values[0].toUpperCase(); 5 | return values.join(); 6 | } 7 | } 8 | ` -------------------------------------------------------------------------------- /src/templates/code/extensions/time_extension.ts: -------------------------------------------------------------------------------- 1 | export let time_extension = `import 'package:flutter/material.dart'; 2 | 3 | extension TimeUtil on TimeOfDay { 4 | String get stringify { 5 | var time = this; 6 | if (time.hour > 12 || (time.hour == 12 && time.minute > 0)) { 7 | // PM 8 | var hr = time.hour == 12 ? 12 : time.hour - 12; 9 | return "$hr:\${time.minute.toString().padLeft(2, '0')} PM"; 10 | } else { 11 | //AM 12 | var hr = time.hour == 0 ? 12 : time.hour; 13 | return "$hr:\${time.minute.toString().padLeft(2, '0')} AM"; 14 | } 15 | } 16 | } 17 | ` -------------------------------------------------------------------------------- /src/templates/code/features/auth/logic/authorization/authorization_bloc.ts: -------------------------------------------------------------------------------- 1 | export let authorization_bloc = `import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:equatable/equatable.dart'; 5 | 6 | import '../../../../services/network/dio_inst.dart'; 7 | import '../../../../services/storage/prefs.dart'; 8 | 9 | part 'authorization_event.dart'; 10 | part 'authorization_state.dart'; 11 | 12 | class AuthorizationBloc extends Bloc { 13 | AuthorizationBloc() : super(AuthorizedAsGuest()) { 14 | add(CheckAuthorization()); 15 | } 16 | 17 | @override 18 | Stream mapEventToState( 19 | AuthorizationEvent event, 20 | ) async* { 21 | if (event is RemoveAuthorization) { 22 | dio.options.headers['Authorization'] = ''; 23 | await Prefs.deleteString(TOKENKEY); 24 | yield AuthorizedAsGuest(); 25 | } 26 | if (event is CheckAuthorization) { 27 | try { 28 | var token = Prefs.getString(TOKENKEY); 29 | if (token == null) { 30 | yield AuthorizedAsGuest(); 31 | } else { 32 | dio.options.headers['Authorization'] = 'Bearer $token'; 33 | yield AuthorizedAsUser(); 34 | } 35 | } catch (e) { 36 | yield AuthorizedAsGuest(); 37 | } 38 | } 39 | } 40 | } 41 | ` -------------------------------------------------------------------------------- /src/templates/code/features/auth/logic/authorization/authorization_event.ts: -------------------------------------------------------------------------------- 1 | export let authorization_event = `part of 'authorization_bloc.dart'; 2 | 3 | abstract class AuthorizationEvent extends Equatable { 4 | const AuthorizationEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class CheckAuthorization extends AuthorizationEvent {} 11 | 12 | class RemoveAuthorization extends AuthorizationEvent {} 13 | ` -------------------------------------------------------------------------------- /src/templates/code/features/auth/logic/authorization/authorization_state.ts: -------------------------------------------------------------------------------- 1 | export let authorization_state = `part of 'authorization_bloc.dart'; 2 | 3 | abstract class AuthorizationState extends Equatable { 4 | const AuthorizationState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class AuthorizedAsGuest extends AuthorizationState {} 11 | 12 | class AuthorizationFailed extends AuthorizationState {} 13 | 14 | class AuthorizedAsUser extends AuthorizationState {} 15 | 16 | class RequiresVerification extends AuthorizationState {} 17 | ` -------------------------------------------------------------------------------- /src/templates/code/features/auth/logic/login/login_cubit.ts: -------------------------------------------------------------------------------- 1 | export let login_cubit = `import 'package:bloc/bloc.dart'; 2 | import 'package:dio/dio.dart'; 3 | import 'package:equatable/equatable.dart'; 4 | import '../../../../../locator.dart'; 5 | import '../../../../services/storage/prefs.dart'; 6 | import '../../utils/auth_error_helper.dart'; 7 | import '../../repo/auth_repo.dart'; 8 | import '../../models/login_request.dart'; 9 | import '../../models/auth_error.dart'; 10 | 11 | part 'login_state.dart'; 12 | 13 | class LoginCubit extends Cubit { 14 | LoginCubit() : super(LoginInitial()); 15 | 16 | final _r = g(); 17 | 18 | login(LoginRequest request) async { 19 | emit(LoginLoading()); 20 | try { 21 | var token = await _r.login(request); 22 | await Prefs.saveString(TOKENKEY, token); 23 | emit(LoginSuccess()); 24 | } on DioError catch (e) { 25 | var err = AuthErrorHelper.extractLoginError(e); 26 | emit(LoginFailed(err)); 27 | } 28 | } 29 | } 30 | ` -------------------------------------------------------------------------------- /src/templates/code/features/auth/logic/login/login_state.ts: -------------------------------------------------------------------------------- 1 | export let login_state = `part of 'login_cubit.dart'; 2 | 3 | abstract class LoginState extends Equatable { 4 | const LoginState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class LoginInitial extends LoginState {} 11 | 12 | class LoginLoading extends LoginState {} 13 | 14 | class LoginSuccess extends LoginState {} 15 | 16 | class LoginFailed extends LoginState { 17 | final AuthError error; 18 | 19 | const LoginFailed(this.error); 20 | 21 | @override 22 | List get props => [error]; 23 | } 24 | ` -------------------------------------------------------------------------------- /src/templates/code/features/auth/logic/register/register_cubit.ts: -------------------------------------------------------------------------------- 1 | export let register_cubit = ` import 'package:bloc/bloc.dart'; 2 | import 'package:dio/dio.dart'; 3 | import 'package:equatable/equatable.dart'; 4 | import '../../utils/auth_error_helper.dart'; 5 | import '../../repo/auth_repo.dart'; 6 | import '../../models/register_request.dart'; 7 | import '../../models/auth_error.dart'; 8 | import '../../../../../locator.dart'; 9 | 10 | part 'register_state.dart'; 11 | 12 | class RegisterCubit extends Cubit { 13 | RegisterCubit(): super(RegisterInitial()); 14 | 15 | final AuthRepo _r = g(); 16 | 17 | register(RegisterRequest req) async { 18 | emit(RegisterLoading()); 19 | try { 20 | await _r.register(req); 21 | emit(RegisterSuccess()); 22 | } on DioError catch (e) { 23 | var authError = AuthErrorHelper.extractRegisterError(e); 24 | emit(RegisterFailed(authError)); 25 | } 26 | } 27 | } 28 | ` -------------------------------------------------------------------------------- /src/templates/code/features/auth/logic/register/register_state.ts: -------------------------------------------------------------------------------- 1 | export let register_state = `part of 'register_cubit.dart'; 2 | 3 | abstract class RegisterState extends Equatable { 4 | const RegisterState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class RegisterInitial extends RegisterState {} 11 | 12 | class RegisterLoading extends RegisterState {} 13 | 14 | class RegisterSuccess extends RegisterState {} 15 | 16 | class RegisterFailed extends RegisterState { 17 | final AuthError authError; 18 | 19 | const RegisterFailed(this.authError); 20 | 21 | @override 22 | List get props => [authError]; 23 | } 24 | ` -------------------------------------------------------------------------------- /src/templates/code/features/auth/models/auth_error.ts: -------------------------------------------------------------------------------- 1 | export let auth_error = `import 'package:equatable/equatable.dart'; 2 | 3 | class AuthError extends Equatable { 4 | final String title; 5 | final String text; 6 | 7 | const AuthError({ 8 | this.title = 'Error', 9 | required this.text, 10 | }); 11 | 12 | @override 13 | List get props => [title, text]; 14 | } 15 | ` 16 | -------------------------------------------------------------------------------- /src/templates/code/features/auth/models/login_request.ts: -------------------------------------------------------------------------------- 1 | export let login_request = `class LoginRequest { 2 | final String email; 3 | final String password; 4 | 5 | const LoginRequest(this.email, this.password); 6 | 7 | Map toMap() => 8 | {'email': email.trim(), 'password': password.trim()}; 9 | } 10 | ` -------------------------------------------------------------------------------- /src/templates/code/features/auth/models/register_request.ts: -------------------------------------------------------------------------------- 1 | export let register_request = `class RegisterRequest { 2 | final String email; 3 | final String password; 4 | final String confirmPassword; 5 | final String phone; 6 | final String address; 7 | final String fullName; 8 | 9 | const RegisterRequest({ 10 | required this.email, 11 | required this.password, 12 | required this.confirmPassword, 13 | required this.phone, 14 | required this.fullName, 15 | required this.address, 16 | }); 17 | 18 | Map toMap() { 19 | return { 20 | "email": email.trim(), 21 | "password1": password.trim(), 22 | "password2": confirmPassword.trim(), 23 | "phone": phone.trim(), 24 | "name": fullName.trim(), 25 | "address": address.trim(), 26 | }; 27 | } 28 | } 29 | ` -------------------------------------------------------------------------------- /src/templates/code/features/auth/repo/auth_repo.ts: -------------------------------------------------------------------------------- 1 | export let auth_repo = `import '../models/login_request.dart'; 2 | import '../models/register_request.dart'; 3 | 4 | class AuthRepo { 5 | Future login(LoginRequest req) async { 6 | // TODO 7 | return ""; 8 | } 9 | 10 | Future register(RegisterRequest req) async { 11 | // TODO 12 | } 13 | } 14 | ` 15 | -------------------------------------------------------------------------------- /src/templates/code/features/auth/utils/auth_error_helper.ts: -------------------------------------------------------------------------------- 1 | export let auth_error_helper = `import 'package:dio/dio.dart'; 2 | import '../models/auth_error.dart'; 3 | 4 | class AuthErrorHelper { 5 | static AuthError extractLoginError(DioError error) { 6 | AuthError err = const AuthError(text: 'Something went wrong !'); 7 | 8 | if (error.type == DioErrorType.connectTimeout) { 9 | err = const AuthError( 10 | title: 'No Internet', 11 | text: 'Please Check if your Connection.', 12 | ); 13 | } 14 | 15 | if (error.response != null) { 16 | switch (error.response!.statusCode) { 17 | case 400: 18 | try { 19 | err = AuthError( 20 | title: 'Error', 21 | text: error.response!.data['message'], 22 | ); 23 | } catch (e) { 24 | err = const AuthError(text: 'Error While Login !'); 25 | } 26 | 27 | break; 28 | case 401: 29 | err = const AuthError( 30 | title: 'User not recognized', 31 | text: 'Please check your inputs !!', 32 | ); 33 | break; 34 | case 500: 35 | err = const AuthError(text: 'Service Unavailable !!'); 36 | } 37 | } 38 | 39 | return err; 40 | } 41 | 42 | static AuthError extractRegisterError(DioError error) { 43 | AuthError err = const AuthError(text: 'Something went wrong !'); 44 | 45 | if (error.response != null) { 46 | if (error.response!.statusCode == 400) { 47 | try { 48 | err = AuthError( 49 | text: error.response!.data['message'][0], 50 | title: 'Invalid Inputs', 51 | ); 52 | } catch (e) {} 53 | } 54 | } 55 | return err; 56 | } 57 | } 58 | ` -------------------------------------------------------------------------------- /src/templates/code/features/bottom_nav/blocs/navigation/navigation_cubit.ts: -------------------------------------------------------------------------------- 1 | export let navigation_cubit = `import 'package:bloc/bloc.dart'; 2 | 3 | class NavigationCubit extends Cubit { 4 | NavigationCubit(T initial) : super(initial); 5 | 6 | changeView(T newView) { 7 | emit(newView); 8 | } 9 | } 10 | ` 11 | -------------------------------------------------------------------------------- /src/templates/code/features/bottom_nav/components/bottom_nav_widget.ts: -------------------------------------------------------------------------------- 1 | export let bottom_nav_widget = `import 'package:flutter/material.dart'; 2 | import '../models/nav_item.dart'; 3 | 4 | class BottomNavWidget extends StatelessWidget { 5 | final List items; 6 | final String current; 7 | final Function(int) onChange; 8 | 9 | const BottomNavWidget({ 10 | Key? key, 11 | required this.items, 12 | required this.onChange, 13 | this.current = HOME, 14 | }) : super(key: key); 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | height: kToolbarHeight, 19 | decoration: BoxDecoration( 20 | color: Colors.white, 21 | borderRadius: const BorderRadius.only( 22 | topRight: Radius.circular(16), 23 | topLeft: Radius.circular(16), 24 | ), 25 | boxShadow: [ 26 | BoxShadow( 27 | color: Colors.black.withOpacity(0.25), 28 | blurRadius: 4, 29 | spreadRadius: 2, 30 | ), 31 | ], 32 | ), 33 | child: Row( 34 | children: List.generate( 35 | items.length, 36 | (i) { 37 | var cur = items[i]; 38 | bool active = cur.title == current; 39 | return Expanded( 40 | child: InkWell( 41 | onTap: () { 42 | onChange(i); 43 | }, 44 | child: Container( 45 | width: double.infinity, 46 | child: _NavItem( 47 | icon: cur.icon, 48 | active: active, 49 | ), 50 | ), 51 | ), 52 | ); 53 | }, 54 | ), 55 | ), 56 | ); 57 | } 58 | } 59 | 60 | class _NavItem extends StatelessWidget { 61 | final Icon icon; 62 | final bool active; 63 | 64 | const _NavItem({Key? key, required this.icon, this.active = false}) 65 | : super(key: key); 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | return Column( 70 | mainAxisSize: MainAxisSize.min, 71 | children: [ 72 | Container( 73 | padding: const EdgeInsets.all(5), 74 | decoration: BoxDecoration( 75 | shape: BoxShape.circle, 76 | color: active ? Colors.black.withOpacity(0.07) : Colors.transparent, 77 | ), 78 | // duration: const Duration(milliseconds: 500), 79 | child: icon, 80 | ), 81 | const SizedBox(height: 3), 82 | Container( 83 | // duration: const Duration(milliseconds: 500), 84 | decoration: BoxDecoration( 85 | shape: BoxShape.circle, 86 | color: active ? Colors.black : Colors.transparent, 87 | ), 88 | height: 5, 89 | width: 5, 90 | ), 91 | ], 92 | ); 93 | } 94 | } 95 | ` -------------------------------------------------------------------------------- /src/templates/code/features/bottom_nav/components/tab_navigator.ts: -------------------------------------------------------------------------------- 1 | export let tab_navigator = `import 'package:flutter/material.dart'; 2 | import '../models/nav_item.dart'; 3 | 4 | class TabNavigator extends StatefulWidget { 5 | final GlobalKey navigatorKey; 6 | final GlobalKey? scaffoldKey; 7 | final String currentPage; 8 | 9 | const TabNavigator({ 10 | Key? key, 11 | required this.navigatorKey, 12 | this.scaffoldKey, 13 | required this.currentPage, 14 | }) : super(key: key); 15 | 16 | @override 17 | _TabNavigatorState createState() => _TabNavigatorState(); 18 | } 19 | 20 | class _TabNavigatorState extends State { 21 | late TextEditingController searchController; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | searchController = TextEditingController(); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | Widget child; 32 | 33 | switch (widget.currentPage) { 34 | case HOME: 35 | child = Scaffold(); 36 | break; 37 | // Continue likewise 38 | default: 39 | child = Scaffold(); 40 | } 41 | 42 | return Navigator( 43 | key: widget.navigatorKey, 44 | onGenerateRoute: (routeSetting) { 45 | return MaterialPageRoute( 46 | builder: (context) => child, 47 | ); 48 | }, 49 | ); 50 | } 51 | } 52 | ` -------------------------------------------------------------------------------- /src/templates/code/features/bottom_nav/models/nav_item.ts: -------------------------------------------------------------------------------- 1 | export let nav_item = `import 'package:flutter/material.dart'; 2 | 3 | const HOME = 'Home'; 4 | const FAV = 'Favourites'; 5 | const CART = 'Cart'; 6 | const MORE = 'More'; 7 | 8 | class NavItem { 9 | String title; 10 | late Icon icon; 11 | 12 | NavItem({required this.title, required IconData icon}) { 13 | this.icon = Icon( 14 | icon, 15 | size: 18.0, 16 | color: Colors.white, 17 | ); 18 | } 19 | } 20 | 21 | List navItems = [ 22 | NavItem(title: HOME, icon: Icons.home), 23 | NavItem(title: FAV, icon: Icons.favorite), 24 | NavItem(title: CART, icon: Icons.shopping_cart), 25 | NavItem(title: MORE, icon: Icons.more_horiz), 26 | ]; 27 | 28 | ` -------------------------------------------------------------------------------- /src/templates/code/features/bottom_nav/views/bottom_nav_view.ts: -------------------------------------------------------------------------------- 1 | export let bottom_nav_view = `import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import '../components/tab_navigator.dart'; 4 | import '../blocs/navigation/navigation_cubit.dart'; 5 | import '../models/nav_item.dart'; 6 | import '../components/bottom_nav_widget.dart'; 7 | 8 | class BottomNavView extends StatefulWidget { 9 | @override 10 | _BottomNavViewState createState() => _BottomNavViewState(); 11 | } 12 | 13 | class _BottomNavViewState extends State { 14 | late Map> _navigatorKeys; 15 | 16 | late NavigationCubit bottomNavCubit; 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | _navigatorKeys = { 22 | HOME: GlobalKey(), 23 | FAV: GlobalKey(), 24 | CART: GlobalKey(), 25 | MORE: GlobalKey(), 26 | }; 27 | bottomNavCubit = context.read>() 28 | ..changeView(navItems[0]); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return BlocBuilder, NavItem>( 34 | bloc: bottomNavCubit, 35 | builder: (BuildContext context, state) => WillPopScope( 36 | onWillPop: () async { 37 | final isFirstRouteInCurrentTab = 38 | !(await (_navigatorKeys[state]!.currentState)!.maybePop()); 39 | 40 | if (isFirstRouteInCurrentTab) { 41 | if (state.title != HOME) { 42 | selectTab(state, navItems[0]); 43 | return false; 44 | } 45 | } 46 | return isFirstRouteInCurrentTab; 47 | }, 48 | child: Scaffold( 49 | body: Stack( 50 | children: navItems 51 | .map((e) => _buildOffstageNavigator(state.title, e.title)) 52 | .toList(), 53 | ), 54 | bottomNavigationBar: BottomNavWidget( 55 | items: navItems, 56 | current: state.title, 57 | onChange: (v) { 58 | selectTab(state, navItems[v]); 59 | }, 60 | ), 61 | ), 62 | ), 63 | ); 64 | } 65 | 66 | Widget _buildOffstageNavigator(String state, String tabItem) { 67 | return Offstage( 68 | offstage: state != tabItem, 69 | child: TabNavigator( 70 | navigatorKey: _navigatorKeys[tabItem]!, 71 | currentPage: tabItem, 72 | ), 73 | ); 74 | } 75 | 76 | void selectTab(NavItem state, NavItem item) { 77 | if (item == state) { 78 | _navigatorKeys[item.title]! 79 | .currentState! 80 | .popUntil((route) => route.isFirst); 81 | } else { 82 | bottomNavCubit.changeView(item); 83 | } 84 | } 85 | } 86 | ` -------------------------------------------------------------------------------- /src/templates/code/features/form/components/form_builder.ts: -------------------------------------------------------------------------------- 1 | export let form_builder = `import 'package:flutter/material.dart'; 2 | import '../models/form_item.dart'; 3 | import './password_field.dart'; 4 | 5 | class FormBuilder extends StatelessWidget { 6 | final List formItems; 7 | 8 | final GlobalKey? formKey; 9 | final bool removeLastPadding; 10 | 11 | const FormBuilder({ 12 | Key? key, 13 | required this.formItems, 14 | this.removeLastPadding = false, 15 | this.formKey, 16 | }) : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Form( 21 | key: formKey, 22 | child: Column( 23 | children: List.generate(formItems.length, (index) { 24 | var e = formItems[index]; 25 | 26 | var controller = e.controller; 27 | var nextNode = (index < formItems.length - 1) 28 | ? formItems[index + 1].focusNode 29 | : null; 30 | 31 | var currentNode = e.focusNode; 32 | 33 | var keyboardType = e.keyboardType; 34 | 35 | bool obscure = e.obscure; 36 | 37 | bool isLast = index == (formItems.length - 1); 38 | 39 | 40 | if (obscure) { 41 | return Padding( 42 | padding: (isLast && removeLastPadding) 43 | ? EdgeInsets.zero 44 | : const EdgeInsets.only(bottom: 15), 45 | child: PasswordField( 46 | controller: controller, 47 | node: currentNode, 48 | onEditingComplete: () { 49 | FocusScope.of(context).requestFocus( 50 | (nextNode != null) ? nextNode : FocusNode(), 51 | ); 52 | }, 53 | ), 54 | ); 55 | } 56 | 57 | return Padding( 58 | padding: (isLast && removeLastPadding) 59 | ? EdgeInsets.zero 60 | : const EdgeInsets.only(bottom: 15), 61 | child: IgnorePointer( 62 | ignoring: e.ignore ?? false, 63 | child: Column( 64 | crossAxisAlignment: CrossAxisAlignment.start, 65 | children: [ 66 | Text(e.title), 67 | const SizedBox(height: 5), 68 | TextFormField( 69 | controller: controller, 70 | focusNode: currentNode, 71 | onEditingComplete: () { 72 | if (nextNode != null) { 73 | FocusScope.of(context).requestFocus(nextNode); 74 | } else { 75 | FocusScope.of(context).unfocus(); 76 | } 77 | }, 78 | style: TextStyle( 79 | color: (e.ignore ?? false) ? Colors.grey : Colors.black, 80 | ), 81 | decoration: InputDecoration( 82 | contentPadding: const EdgeInsets.symmetric( 83 | horizontal: 10, vertical: 10), 84 | isDense: true, 85 | border: OutlineInputBorder( 86 | borderRadius: BorderRadius.circular(6)), 87 | hintText: e.hint, 88 | ), 89 | ), 90 | ], 91 | ), 92 | ), 93 | ); 94 | })), 95 | ); 96 | } 97 | } 98 | ` -------------------------------------------------------------------------------- /src/templates/code/features/form/components/password_field.ts: -------------------------------------------------------------------------------- 1 | export let password_field = `import 'package:flutter/material.dart'; 2 | import '../utils/validator.dart'; 3 | 4 | class PasswordField extends StatefulWidget { 5 | final TextEditingController? controller; 6 | final FocusNode? node; 7 | final void Function()? onEditingComplete; 8 | 9 | const PasswordField({ 10 | Key? key, 11 | this.controller, 12 | this.node, 13 | this.onEditingComplete, 14 | }) : super(key: key); 15 | @override 16 | _PasswordFieldState createState() => _PasswordFieldState(); 17 | } 18 | 19 | class _PasswordFieldState extends State { 20 | late bool obscure; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | obscure = true; 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return TextFormField( 31 | controller: widget.controller, 32 | focusNode: widget.node, 33 | obscureText: obscure, 34 | validator: Validator.validatePassword, 35 | decoration: InputDecoration( 36 | labelText: "Password", 37 | suffixIcon: IconButton( 38 | icon: Icon(!obscure ? Icons.visibility : Icons.visibility_off), 39 | onPressed: () { 40 | setState(() { 41 | obscure = !obscure; 42 | }); 43 | })), 44 | onEditingComplete: widget.onEditingComplete, 45 | ); 46 | } 47 | } 48 | ` -------------------------------------------------------------------------------- /src/templates/code/features/form/models/form_item.ts: -------------------------------------------------------------------------------- 1 | export let form_item = `import 'package:flutter/material.dart'; 2 | import '../utils/validator.dart'; 3 | 4 | class FormItem { 5 | String title; 6 | TextEditingController controller; 7 | FocusNode? focusNode; 8 | TextInputType? keyboardType; 9 | bool obscure; 10 | String? Function(String)? validator; 11 | int? maxLength; 12 | bool? ignore; 13 | Widget? prefixIcon; 14 | String? hint; 15 | 16 | FormItem({ 17 | this.ignore, 18 | required this.title, 19 | this.hint, 20 | this.prefixIcon, 21 | required this.controller, 22 | this.maxLength, 23 | this.focusNode, 24 | String? Function(String?)? validator, 25 | TextInputType? keyboardType, 26 | this.obscure = false, 27 | }) { 28 | this.keyboardType = keyboardType ?? TextInputType.name; 29 | this.validator = validator ?? Validator.validateEmpty; 30 | } 31 | } 32 | ` -------------------------------------------------------------------------------- /src/templates/code/features/form/utils/validator.ts: -------------------------------------------------------------------------------- 1 | export let validator = `class Validator { 2 | static String? validateEmail(String? value) { 3 | if (value == null) return 'Email cannot be empty'; 4 | if (value.isEmpty) return "Email cannot be empty"; 5 | if (!RegExp( 6 | r"^[a-zA-Z0-9.!#$%&'*+/=?^\`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$") 7 | .hasMatch(value)) return "Email field is not valid"; 8 | return null; 9 | } 10 | 11 | static String? validatePassword(String? value) { 12 | if (value == null) return 'Password cannot be empty'; 13 | if (value.isEmpty) { 14 | return "Password cannot be empty"; 15 | } 16 | if (value.length < 8) { 17 | return "Password should have 8 characters."; 18 | } 19 | 20 | // if (!value.split("").contains("0123456789")) { 21 | // return "Password should have 1 digit"; 22 | // } 23 | 24 | return null; 25 | } 26 | 27 | static String? validateNoSpecial(String? value) { 28 | if (value == null) return 'Cannot be empty'; 29 | var val = validateEmpty(value); 30 | if (val != null) return val; 31 | final re = RegExp(r'\\W'); 32 | if (re.hasMatch(value)) { 33 | return "Cannot contain special characters"; 34 | } 35 | return null; 36 | } 37 | 38 | static String? validatePhoneNumber(String? value) { 39 | if (value == null) return 'Enter valid phone number'; 40 | String? status = validateEmpty(value); 41 | if (status != null) return status; 42 | int? number = int.tryParse(value); 43 | if (number == null) return "Enter valid phone number"; 44 | return value.length == 10 ? null : "Mobile number must be 10 digit"; 45 | } 46 | 47 | static String? validateConfirmPassword(String? value, String password) { 48 | if (value == null) return 'Passwords do not match'; 49 | return value == password ? null : "This does not match with password"; 50 | } 51 | 52 | static String? validateEmpty(String? value) { 53 | if (value == null) return 'The field cannot be empty'; 54 | return value.isNotEmpty ? null : 'The field cannot be empty'; 55 | } 56 | } 57 | ` -------------------------------------------------------------------------------- /src/templates/code/features/onboarding/components/onboarding_content_widget.ts: -------------------------------------------------------------------------------- 1 | export let onboarding_content_widget = `import 'package:flutter/material.dart'; 2 | import '../models/onboarding_content.dart'; 3 | 4 | class OnboardingContentWidget extends StatelessWidget { 5 | final OnboardingContent content; 6 | final bool isLast; 7 | 8 | const OnboardingContentWidget({ 9 | Key? key, 10 | required this.content, 11 | required this.isLast, 12 | }) : super(key: key); 13 | @override 14 | Widget build(BuildContext context) { 15 | return Column( 16 | crossAxisAlignment: CrossAxisAlignment.center, 17 | children: [ 18 | Expanded( 19 | child: Padding( 20 | padding: (isLast) 21 | ? const EdgeInsets.symmetric(vertical: 15) 22 | : EdgeInsets.zero, 23 | child: Image.asset(content.image), 24 | ), 25 | ), 26 | Text( 27 | content.title, 28 | style: const TextStyle( 29 | fontWeight: FontWeight.w700, 30 | fontSize: 34, 31 | color: Color(0xff2c2c2c), 32 | ), 33 | ), 34 | Padding( 35 | padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), 36 | child: Text( 37 | content.subtitle, 38 | textAlign: TextAlign.center, 39 | style: const TextStyle( 40 | fontSize: 17, 41 | color: Color(0xff414141), 42 | ), 43 | ), 44 | ) 45 | ], 46 | ); 47 | } 48 | } 49 | ` -------------------------------------------------------------------------------- /src/templates/code/features/onboarding/components/pagination.ts: -------------------------------------------------------------------------------- 1 | export let pagination = `import 'package:flutter/material.dart'; 2 | 3 | class CustomPagination extends StatelessWidget { 4 | final int active; 5 | final int itemCount; 6 | 7 | const CustomPagination({ 8 | Key? key, 9 | required this.active, 10 | required this.itemCount, 11 | }) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Row( 16 | mainAxisAlignment: MainAxisAlignment.center, 17 | children: List.generate( 18 | itemCount, 19 | (index) => PaginationItem( 20 | active: index == active, 21 | ), 22 | ), 23 | ); 24 | } 25 | } 26 | 27 | class PaginationItem extends StatefulWidget { 28 | final bool active; 29 | const PaginationItem({ 30 | Key? key, 31 | required this.active, 32 | }) : super(key: key); 33 | 34 | @override 35 | _PaginationItemState createState() => _PaginationItemState(); 36 | } 37 | 38 | class _PaginationItemState extends State { 39 | @override 40 | Widget build(BuildContext context) { 41 | return Container( 42 | margin: const EdgeInsets.only(right: 5), 43 | height: 8.0, 44 | width: 8.0, 45 | decoration: 46 | widget.active ? activePaginationDecoration : paginationDecoration, 47 | ); 48 | } 49 | } 50 | 51 | BoxDecoration paginationDecoration = BoxDecoration( 52 | shape: BoxShape.circle, 53 | color: const Color(0xffd1d1d1), 54 | border: Border.all( 55 | color: const Color(0xffd1d1d1), 56 | ), 57 | ); 58 | const BoxDecoration activePaginationDecoration = BoxDecoration( 59 | shape: BoxShape.circle, 60 | color: Colors.blue, 61 | ); 62 | ` -------------------------------------------------------------------------------- /src/templates/code/features/onboarding/models/onboarding_content.ts: -------------------------------------------------------------------------------- 1 | export let onboarding_content = `class OnboardingContent { 2 | final String title; 3 | final String subtitle; 4 | final String image; 5 | 6 | OnboardingContent({ 7 | required this.title, 8 | required this.subtitle, 9 | required this.image, 10 | }); 11 | } 12 | ` -------------------------------------------------------------------------------- /src/templates/code/features/onboarding/views/onboarding_view.ts: -------------------------------------------------------------------------------- 1 | export let onboarding_view = `import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import '../blocs/navigation/navigation_cubit.dart'; 4 | import '../components/onboarding_content_widget.dart'; 5 | import '../components/pagination.dart'; 6 | import '../models/onboarding_content.dart'; 7 | 8 | class OnboardingView extends StatefulWidget { 9 | @override 10 | State createState() => _OnboardingViewState(); 11 | } 12 | 13 | class _OnboardingViewState extends State { 14 | late NavigationCubit navC; 15 | 16 | late List contents; 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | contents = [ 22 | OnboardingContent( 23 | title: "Hey There!", 24 | subtitle: 25 | "Book hotels, get awesome tours, adventures packages, and more.", 26 | image: "", 27 | ), 28 | OnboardingContent( 29 | title: "Travel Freely", 30 | subtitle: "Bibendum sit morbi dolor pharetra.Bibendum sit morbi dolor.", 31 | image: "", 32 | ), 33 | OnboardingContent( 34 | title: "Travel Freely", 35 | subtitle: "Bibendum sit morbi dolor pharetra.Bibendum sit morbi dolor.", 36 | image: "", 37 | ), 38 | ]; 39 | navC = NavigationCubit(0); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return Scaffold( 45 | body: SafeArea( 46 | child: Column( 47 | children: [ 48 | Expanded( 49 | child: PageView.builder( 50 | padEnds: false, 51 | itemCount: contents.length, 52 | onPageChanged: (int page) { 53 | navC.changeView(page); 54 | }, 55 | itemBuilder: (c, i) { 56 | var cur = contents[i]; 57 | return OnboardingContentWidget( 58 | content: cur, 59 | isLast: i == (contents.length - 1), 60 | ); 61 | }, 62 | ), 63 | ), 64 | Padding( 65 | padding: const EdgeInsets.symmetric(vertical: 20), 66 | child: BlocBuilder, int>( 67 | bloc: navC, 68 | builder: (context, state) { 69 | return CustomPagination( 70 | active: state, 71 | itemCount: contents.length, 72 | ); 73 | }, 74 | ), 75 | ), 76 | ], 77 | ), 78 | ), 79 | ); 80 | } 81 | } 82 | ` -------------------------------------------------------------------------------- /src/templates/code/main.ts: -------------------------------------------------------------------------------- 1 | import { getUpperCamelCase, getProperName } from "./utils"; 2 | 3 | export function getCleanedMain(appName: string): string { 4 | const upperCamel = getUpperCamelCase(appName) 5 | const properName = getProperName(appName) 6 | return `import 'package:flutter/material.dart'; 7 | 8 | void main() => runApp(${upperCamel}()); 9 | 10 | class ${upperCamel} extends StatelessWidget { 11 | @override 12 | Widget build(BuildContext context) { 13 | return MaterialApp( 14 | title: '${properName}', 15 | theme: ThemeData( 16 | appBarTheme: const AppBarTheme(elevation: 0), 17 | ), 18 | home: Home(), 19 | ); 20 | } 21 | } 22 | `; 23 | } 24 | -------------------------------------------------------------------------------- /src/templates/code/res/colors.ts: -------------------------------------------------------------------------------- 1 | export let colors = `import 'package:flutter/material.dart'; 2 | 3 | 4 | const Color primaryColor = Colors.blue; 5 | const Color accentColor = Colors.blue; 6 | const Color secondaryColor = Colors.blue; 7 | const Color fontColor = Colors.black; ` -------------------------------------------------------------------------------- /src/templates/code/res/dimens.ts: -------------------------------------------------------------------------------- 1 | export let dimens = `import 'package:flutter/material.dart'; 2 | 3 | const PAD_SYM_H40 = EdgeInsets.symmetric(horizontal: 40.0); 4 | 5 | const PAD_ONLY_T20 = EdgeInsets.only(top: 20); 6 | const PAD_ONLY_T25 = EdgeInsets.only(top: 25); 7 | const PAD_ONLY_R20 = EdgeInsets.only(right: 20); 8 | const PAD_ONLY_R10 = EdgeInsets.only(right: 10); 9 | const PAD_ONLY_R5 = EdgeInsets.only(right: 5); 10 | const PAD_ONLY_L20 = EdgeInsets.only(left: 20); 11 | const PAD_ONLY_B20 = EdgeInsets.only(bottom: 20); 12 | const PAD_ONLY_B30 = EdgeInsets.only(bottom: 30); 13 | const PAD_ONLY_B10 = EdgeInsets.only(bottom: 10); 14 | const PAD_ONLY_B15 = EdgeInsets.only(bottom: 15); 15 | const PAD_ONLY_B5 = EdgeInsets.only(bottom: 5); 16 | const PAD_SYM_H10 = EdgeInsets.symmetric(horizontal: 10); 17 | const PAD_SYM_H16 = EdgeInsets.symmetric(horizontal: 16); 18 | const PAD_SYM_H20 = EdgeInsets.symmetric(horizontal: 20); 19 | const PAD_SYM_H30 = EdgeInsets.symmetric(horizontal: 30); 20 | const PAD_SYM_V20 = EdgeInsets.symmetric(vertical: 20); 21 | const PAD_SYM_V10 = EdgeInsets.symmetric(vertical: 10); 22 | const PAD_SYM_V30 = EdgeInsets.symmetric(vertical: 30); 23 | const PAD_ALL_20 = EdgeInsets.all(20); 24 | const PAD_ASYM_H25_V20 = EdgeInsets.symmetric(horizontal: 25.0, vertical: 20.0); 25 | const PAD_ASYM_H25_V10 = EdgeInsets.symmetric(horizontal: 25.0, vertical: 10.0); 26 | const PAD_ALL_15 = EdgeInsets.all(15); 27 | const PAD_ALL_18 = EdgeInsets.all(18); 28 | const PAD_ALL_10 = EdgeInsets.all(10); 29 | const PAD_ALL_5 = EdgeInsets.all(5); 30 | 31 | const SCREEN_PAD = EdgeInsets.symmetric(vertical: 15.0, horizontal: 30.0); 32 | const SCREEN_PAD_INSIDE = EdgeInsets.symmetric(horizontal: 20.0); 33 | ` -------------------------------------------------------------------------------- /src/templates/code/res/styles.ts: -------------------------------------------------------------------------------- 1 | export let styles = `import 'package:flutter/material.dart'; 2 | 3 | const normalStyle = TextStyle( 4 | fontSize: 14, 5 | ); 6 | 7 | const titleStyle = TextStyle( 8 | fontSize: 35, 9 | ); 10 | 11 | const boldStyle = TextStyle( 12 | fontSize: 18, 13 | fontWeight: FontWeight.w600, 14 | ); 15 | 16 | const errorStyle = TextStyle( 17 | fontSize: 14, 18 | color: Colors.red, 19 | ); 20 | 21 | const smallHintStyle = TextStyle( 22 | fontSize: 11, 23 | fontWeight: FontWeight.w600, 24 | color: Colors.grey.shade400, 25 | ); 26 | ` -------------------------------------------------------------------------------- /src/templates/code/services/network/dio_inst.ts: -------------------------------------------------------------------------------- 1 | export let dio_inst = `import 'dart:convert'; 2 | import 'package:dio/dio.dart'; 3 | 4 | Dio dio = Dio( 5 | BaseOptions( 6 | baseUrl: '', 7 | connectTimeout: 5000, 8 | receiveTimeout: 3000, 9 | headers: { 10 | 'Content-Type': 'application/json', 11 | 'Accept': 'application/json', 12 | }, 13 | ), 14 | ); 15 | ` -------------------------------------------------------------------------------- /src/templates/code/services/storage/prefs.ts: -------------------------------------------------------------------------------- 1 | export let prefs = `import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | // Common Keys (Delete if Unnecessary) 4 | const PROFILEKEY = "PROFILEKEY"; 5 | const TOKENKEY = "TOKENKEY"; 6 | const LOCALEKEY = "LOCALEKEY"; 7 | 8 | // Usage : 9 | // void main() { 10 | // ... 11 | // await Prefs.init() 12 | // ... 13 | // runApp(MyApp()); 14 | // } 15 | class Prefs { 16 | static late SharedPreferences _prefs; 17 | // Hidden Constructor 18 | // i.e : Constructor cannot be called upon this class 19 | // Initialization should be done using [init()] 20 | Prefs._(); 21 | 22 | static Future init() async { 23 | _prefs = await SharedPreferences.getInstance(); 24 | } 25 | 26 | static Future saveString(String key, String value) => 27 | _prefs.setString(key, value); 28 | 29 | static String? getString(String key) => _prefs.getString(key); 30 | 31 | static deleteString(String key) async { 32 | _prefs.remove(key); 33 | } 34 | 35 | } 36 | ` -------------------------------------------------------------------------------- /src/templates/code/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | export function getFirstLetterCapital(text: string): string { 3 | return text.charAt(0).toUpperCase() + text.slice(1); 4 | } 5 | 6 | export function getUpperCamelCase(snakeCase: string): string { 7 | let names = snakeCase.split("_"); 8 | let capitalized = names.map((v, i) => { 9 | return getFirstLetterCapital(v); 10 | }) 11 | return capitalized.join("") 12 | } 13 | export function getProperName(snakeCase: string): string { 14 | let names = snakeCase.split("_"); 15 | let capitalized = names.map((v, i) => { 16 | return getFirstLetterCapital(v); 17 | }) 18 | return capitalized.join(" ") 19 | } 20 | 21 | export function upperCamelToSnake(upperCamelCase: string): string { 22 | return (upperCamelCase.replace(/[A-Z]/g, (match) => "_" + match.toLowerCase())).slice(1); 23 | } -------------------------------------------------------------------------------- /src/templates/code/views/home_view.ts: -------------------------------------------------------------------------------- 1 | export let home_view = `import 'package:flutter/material.dart'; 2 | 3 | class HomeView extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Scaffold(); 7 | } 8 | } 9 | ` 10 | -------------------------------------------------------------------------------- /src/templates/endpoint/cubit.ts: -------------------------------------------------------------------------------- 1 | import { TemplateMetadata } from "../../types/metadata"; 2 | 3 | export function generateCubit(metadata: TemplateMetadata): string { 4 | 5 | 6 | const { interfaceFileName, endpoint, param, generateParamCode, generateResponseCode, paramFileName, responseFileName, injectable } = metadata; 7 | const { className, fnName, returnType: rawReturnType } = endpoint; 8 | const paramImport = generateParamCode ? `import '../../data/models/${paramFileName}';` : ""; 9 | const responseImport = generateResponseCode ? `import '../../data/models/${responseFileName}';` : ""; 10 | 11 | const returnType = rawReturnType === 'void' ? "String" : rawReturnType; 12 | 13 | const body = rawReturnType === 'void' ? 14 | `await repo.${fnName}(${param.length === 0 ? "" : "param"}); 15 | emit(${className}Loaded(data: "Success"));` 16 | : `final data = await repo.${fnName}(${param.length === 0 ? "" : "param"}); 17 | emit(${className}Loaded(data: data));` 18 | 19 | let cubitTemplate = ` 20 | import 'package:warped_bloc/warped_bloc.dart'; 21 | import '../../data/repo/interface/${interfaceFileName}'; 22 | ${injectable ? "import 'package:injectable/injectable.dart';" : ""} 23 | 24 | ${paramImport} 25 | ${responseImport} 26 | 27 | class ${className}Loaded extends DataState<${returnType}> { 28 | const ${className}Loaded({required super.data}); 29 | } 30 | 31 | ${injectable ? "@injectable" : ""} 32 | class ${className}Cubit extends AsyncCubit { 33 | final I${className}Repo repo; 34 | ${className}Cubit({ 35 | required this.repo, 36 | }); 37 | 38 | ${fnName}(${param}) { 39 | handleDefaultStates(() async { 40 | ${body} 41 | }); 42 | } 43 | } 44 | ` 45 | return cubitTemplate 46 | } -------------------------------------------------------------------------------- /src/templates/endpoint/data_class.ts: -------------------------------------------------------------------------------- 1 | import { extractListType } from "../../utils/type_utils"; 2 | 3 | export function generateDataClass(type: string): string { 4 | let className = type; 5 | if (className.startsWith("List")) { 6 | className = extractListType(type); 7 | } 8 | return `class ${className} { 9 | ${className}(); 10 | 11 | factory ${className}.fromMap(Map map){ 12 | return ${className}(); 13 | } 14 | 15 | Map toMap() { 16 | return {}; 17 | } 18 | }`; 19 | } -------------------------------------------------------------------------------- /src/templates/endpoint/interface.ts: -------------------------------------------------------------------------------- 1 | import { TemplateMetadata } from "../../types/metadata"; 2 | 3 | export function generateInterface(metadata: TemplateMetadata): string { 4 | let param = metadata.endpoint.paramType ? `${metadata.endpoint.paramType} param` : ""; 5 | 6 | const { generateParamCode, generateResponseCode, paramFileName, responseFileName } = metadata; 7 | 8 | const paramImport = generateParamCode ? `import '../../models/${paramFileName}';` : ""; 9 | const responseImport = generateResponseCode ? `import '../../models/${responseFileName}';` : ""; 10 | 11 | let interfaceTemplate = ` 12 | ${paramImport} 13 | ${responseImport} 14 | 15 | abstract interface class I${metadata.endpoint.className}Repo { 16 | Future<${metadata.endpoint.returnType}> ${metadata.endpoint.fnName}(${param}); 17 | } 18 | ` 19 | return interfaceTemplate; 20 | } 21 | -------------------------------------------------------------------------------- /src/templates/endpoint/repo.ts: -------------------------------------------------------------------------------- 1 | import { TemplateMetadata } from "../../types/metadata"; 2 | import { extractListType } from "../../utils/type_utils"; 3 | 4 | const generateReturnDefinition = (returnType: string): string => { 5 | let returnDefinition = "data"; 6 | 7 | if (returnType.startsWith("List<")) { 8 | let listType = extractListType(returnType); 9 | returnDefinition = `List<${listType}>.from(data.map((e) => ${listType}.fromMap(e)))`; 10 | } 11 | 12 | return returnDefinition; 13 | } 14 | 15 | const generateBody = (url: string, returnType: string, returnDefinition: string): string => { 16 | if (returnType === 'void') { 17 | return `await dio.get("${url}");` 18 | } 19 | return ` 20 | final res = await dio.get("${url}"); 21 | final data = res.data; 22 | return ${returnDefinition}; 23 | `; 24 | } 25 | 26 | export function generateRepo(metadata: TemplateMetadata): string { 27 | 28 | let param = metadata.endpoint.paramType ? `${metadata.endpoint.paramType} param` : ""; 29 | let returnDefinition = generateReturnDefinition(metadata.endpoint.returnType); 30 | const { generateParamCode, generateResponseCode, paramFileName, responseFileName, injectable } = metadata; 31 | 32 | const paramImport = generateParamCode ? `import '../models/${paramFileName}';` : ""; 33 | const responseImport = generateResponseCode ? `import '../models/${responseFileName}';` : ""; 34 | 35 | const body = generateBody(metadata.endpoint.url, metadata.endpoint.returnType, returnDefinition); 36 | 37 | 38 | let repoTemplate = ` 39 | import 'package:dio/dio.dart'; 40 | import './interface/${metadata.interfaceFileName}'; 41 | ${injectable ? "import 'package:injectable/injectable.dart';" : ""} 42 | ${paramImport} 43 | ${responseImport} 44 | 45 | ${injectable ? `@LazySingleton(as: I${metadata.endpoint.className}Repo)` : ""} 46 | class ${metadata.endpoint.className}Repo implements I${metadata.endpoint.className}Repo { 47 | final Dio dio; 48 | 49 | ${metadata.endpoint.className}Repo({ 50 | required this.dio, 51 | }); 52 | 53 | @override 54 | Future<${metadata.endpoint.returnType}> ${metadata.endpoint.fnName}(${param}) async { 55 | ${body} 56 | } 57 | } 58 | ` 59 | return repoTemplate; 60 | } 61 | -------------------------------------------------------------------------------- /src/templates/feat_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "components": {}, 3 | "views": {}, 4 | "models": {}, 5 | "blocs": {} 6 | } -------------------------------------------------------------------------------- /src/templates/features/auth_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth": { 3 | "blocs": { 4 | "login": { 5 | "login_cubit": "file", 6 | "login_state": "file" 7 | }, 8 | "register": { 9 | "register_cubit": "file", 10 | "register_state": "file" 11 | }, 12 | "authorization": { 13 | "authorization_bloc": "file", 14 | "authorization_event": "file", 15 | "authorization_state": "file" 16 | } 17 | }, 18 | "models": { 19 | "auth_error": "file", 20 | "login_request": "file", 21 | "register_request": "file" 22 | }, 23 | "repo": { 24 | "auth_repo": "file" 25 | }, 26 | "utils": { 27 | "auth_error_helper": "file" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/templates/features/bottom_nav_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "bottom_nav": { 3 | "components": { 4 | "bottom_nav_widget": "file", 5 | "tab_navigator": "file" 6 | }, 7 | "fragments": {}, 8 | "views": { 9 | "bottom_nav_view" : "file" 10 | }, 11 | "models": { 12 | "nav_item": "file" 13 | }, 14 | "blocs": { 15 | "navigation" : { 16 | "navigation_cubit": "file" 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/templates/features/form_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "forms": { 3 | "utils": { 4 | "validator": "file" 5 | }, 6 | "components": { 7 | "form_builder": "file", 8 | "password_field": "file" 9 | }, 10 | "views": {}, 11 | "models": { 12 | "form_item": "form_item" 13 | }, 14 | "blocs": {} 15 | } 16 | } -------------------------------------------------------------------------------- /src/templates/features/onboarding_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "onboarding": { 3 | "components": { 4 | "onboarding_content_widget": "file", 5 | "pagination": "file" 6 | }, 7 | "views": { 8 | "onboarding_view": "file" 9 | }, 10 | "models": { 11 | "onboarding_content": "file" 12 | }, 13 | "blocs": { 14 | "navigation": { 15 | "navigation_cubit": "file" 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/templates/horiz_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": { 3 | "res": { 4 | "colors": "file", 5 | "strings": "file", 6 | "styles": "file", 7 | "dimens": "file" 8 | }, 9 | "extensions": { 10 | "date_extension": "file", 11 | "time_extension": "file", 12 | "int_extension": "file", 13 | "str_extension": "file" 14 | }, 15 | "models": {}, 16 | "components": {}, 17 | "views": { 18 | "home_view": "file" 19 | }, 20 | "data": { 21 | "fake_data": "file" 22 | }, 23 | "services": { 24 | "network": { 25 | "dio_inst": "file" 26 | }, 27 | "storage": { 28 | "prefs": "file" 29 | } 30 | }, 31 | "blocs": {} 32 | } 33 | } -------------------------------------------------------------------------------- /src/templates/vert_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": { 3 | "res": { 4 | "colors": "file", 5 | "strings": "file", 6 | "styles": "file", 7 | "dimens": "file" 8 | }, 9 | "extensions": { 10 | "date_extension": "file", 11 | "time_extension": "file", 12 | "int_extension": "file", 13 | "str_extension": "file" 14 | }, 15 | "data": { 16 | "fake_data": "file" 17 | }, 18 | "services": { 19 | "network": { 20 | "dio_inst": "file" 21 | }, 22 | "storage": { 23 | "prefs": "file" 24 | } 25 | }, 26 | "modules": { 27 | "common": { 28 | "components": {}, 29 | "views": {}, 30 | "models": {}, 31 | "blocs": {} 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/types/metadata.ts: -------------------------------------------------------------------------------- 1 | export type EndpointMetadata = { 2 | url: string, 3 | className: string, 4 | fnName: string, 5 | paramType?: string, 6 | returnType: string 7 | } 8 | 9 | export type TemplateMetadata = { 10 | endpoint: EndpointMetadata, 11 | param: string, 12 | interfaceFileName: string, 13 | paramFileName: string, 14 | responseFileName: string, 15 | generateParamCode: boolean, 16 | generateResponseCode: boolean, 17 | injectable: boolean, 18 | } -------------------------------------------------------------------------------- /src/utils/type_utils.ts: -------------------------------------------------------------------------------- 1 | export const isPrimitive = (type: string): boolean => { 2 | let primitives = ["String", "bool", "int", "double", "Map", "num", "void"] 3 | if (type.startsWith("List<")) { 4 | let listType = extractListType(type); 5 | return isPrimitive(listType) 6 | } 7 | return primitives.reduce((a, b) => a || type.startsWith(b), false); 8 | } 9 | 10 | export const extractListType = (returnType: string) => { 11 | let interim = returnType.replace("List<", ""); 12 | return interim.substring(0, interim.length - 1); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, writeFileSync, mkdirSync, exists, PathLike, readFileSync } from "fs"; 2 | import { Uri, window } from "vscode"; 3 | import path from "path"; 4 | import mappings from "../mappings/code_template_mapping"; 5 | 6 | export function createDirectory(targetDirectory: string) { 7 | if (!existsSync(targetDirectory)) { 8 | mkdirSync(targetDirectory, { recursive: true }); 9 | } 10 | } 11 | 12 | export function createFile(targetPath: string, content: string) { 13 | writeFileSync(targetPath, content, { 14 | "flag": "w+" 15 | }); 16 | } 17 | 18 | function getContent(name: keyof Object): string { 19 | let content: string = mappings[name]; 20 | if (typeof content === "undefined") { 21 | content = ""; 22 | } 23 | return content; 24 | } 25 | 26 | export function generateTemplate(obj: Object, pathx: string) { 27 | // This definition of k is required by ts to work 28 | let k: keyof typeof obj; 29 | for (k in obj) { 30 | if (k.match("default") === null) { 31 | } else { 32 | continue; 33 | } 34 | let value = obj[k]; 35 | let full_path = path.join(pathx, k); 36 | // console.log(full_path) 37 | // console.log(value) 38 | if (typeof value === "string") { 39 | // Fetch Code if exists 40 | let content = getContent(k); 41 | // console.log(content) 42 | // Generate a file 43 | createFile(`${full_path}.dart`, content); 44 | // console.log("I am a file") 45 | } else if (typeof value === "object") { 46 | // Generate a Folder 47 | createDirectory(full_path); 48 | // Recursively generate the sub-folders 49 | // console.log("I am a folder") 50 | generateTemplate(value, full_path); 51 | } else { 52 | // Format Error : Don't do anything 53 | // console.log("I am a nothing") 54 | continue; 55 | } 56 | } 57 | } 58 | 59 | export function generateStructure(uri: Uri, template: object) { 60 | // [full_path] is the path where we generate the folder structure into. 61 | let full_path: string; 62 | 63 | // Check for when user invokes command from Ctrl + Shift + p 64 | if (typeof uri === "undefined") { 65 | window.showErrorMessage("Please use command from explorer !"); 66 | return; 67 | } else { 68 | full_path = uri.fsPath; 69 | } 70 | generateTemplate(template, full_path); 71 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "lib": [ 6 | "ES2020" 7 | ], 8 | "sourceMap": true, 9 | "rootDir": "src", 10 | "strict": true, /* enable all strict type-checking options */ 11 | /* Additional Checks */ 12 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 13 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 14 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 15 | "resolveJsonModule": true, 16 | "esModuleInterop": true 17 | } 18 | } -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Setup 13 | 14 | * install the recommended extensions (amodio.tsl-problem-matcher and dbaeumer.vscode-eslint) 15 | 16 | 17 | ## Get up and running straight away 18 | 19 | * Press `F5` to open a new window with your extension loaded. 20 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 21 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 22 | * Find output from your extension in the debug console. 23 | 24 | ## Make changes 25 | 26 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 27 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 28 | 29 | 30 | ## Explore the API 31 | 32 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. 33 | 34 | ## Run tests 35 | 36 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 37 | * Press `F5` to run the tests in a new window with your extension loaded. 38 | * See the output of the test result in the debug console. 39 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. 40 | * The provided test runner will only consider files matching the name pattern `**.test.ts`. 41 | * You can create folders inside the `test` folder to structure your tests any way you want. 42 | 43 | ## Go further 44 | 45 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). 46 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. 47 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 48 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | //@ts-check 8 | /** @typedef {import('webpack').Configuration} WebpackConfig **/ 9 | 10 | /** @type WebpackConfig */ 11 | const extensionConfig = { 12 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 13 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') 14 | 15 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 16 | output: { 17 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 18 | path: path.resolve(__dirname, 'dist'), 19 | filename: 'extension.js', 20 | libraryTarget: 'commonjs2' 21 | }, 22 | externals: { 23 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 24 | // modules added here also need to be added in the .vscodeignore file 25 | }, 26 | resolve: { 27 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 28 | extensions: ['.ts', '.js'] 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.ts$/, 34 | exclude: /node_modules/, 35 | use: [ 36 | { 37 | loader: 'ts-loader' 38 | } 39 | ] 40 | } 41 | ] 42 | }, 43 | devtool: 'nosources-source-map', 44 | infrastructureLogging: { 45 | level: "log", // enables logging required for problem matchers 46 | }, 47 | }; 48 | module.exports = [ extensionConfig ]; --------------------------------------------------------------------------------