├── .gitignore ├── images ├── init.gif ├── views.gif └── flutter.png ├── .vscodeignore ├── .vscode ├── extensions.json ├── tasks.json ├── settings.json └── launch.json ├── tslint.json ├── src ├── test │ ├── suite │ │ ├── extension.test.ts │ │ └── index.ts │ └── runTest.ts ├── dart_snippets │ ├── architecture │ │ ├── base_model.ts │ │ ├── base_service.ts │ │ ├── base.ts │ │ ├── locator.ts │ │ ├── providers.ts │ │ ├── main.ts │ │ ├── logger.ts │ │ ├── base_view_model.ts │ │ ├── navigator_service.ts │ │ └── pubspec.ts │ ├── widgets │ │ ├── view_model.ts │ │ ├── desktop.ts │ │ ├── mobile.ts │ │ ├── tablet.ts │ │ └── widget.ts │ └── views │ │ ├── view_model.ts │ │ ├── view.ts │ │ ├── desktop.ts │ │ ├── mobile.ts │ │ └── tablet.ts ├── utils │ ├── vs_code_actions.ts │ ├── file_system_manager.ts │ ├── utils.ts │ ├── widget_file.ts │ ├── view_file.ts │ ├── architecture.ts │ └── yaml_helper.ts └── extension.ts ├── CHANGELOG.md ├── tsconfig.json ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── LICENSE ├── package.json ├── CODE_OF_CONDUCT.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | -------------------------------------------------------------------------------- /images/init.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhukesh048/Flutter-MVVM-VS-Code-extension-Pack/HEAD/images/init.gif -------------------------------------------------------------------------------- /images/views.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhukesh048/Flutter-MVVM-VS-Code-extension-Pack/HEAD/images/views.gif -------------------------------------------------------------------------------- /images/flutter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhukesh048/Flutter-MVVM-VS-Code-extension-Pack/HEAD/images/flutter.png -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/tslint.json 9 | **/*.map 10 | **/*.ts -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "ms-vscode.vscode-typescript-tslint-plugin" 6 | ] 7 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-string-throw": true, 4 | "no-unused-expression": true, 5 | "no-duplicate-variable": true, 6 | "curly": true, 7 | "class-name": true, 8 | "semicolon": [ 9 | true, 10 | "always" 11 | ], 12 | "triple-equals": true 13 | }, 14 | "defaultSeverity": "warning" 15 | } 16 | -------------------------------------------------------------------------------- /.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": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.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 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off" 11 | } -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.equal(-1, [1, 2, 3].indexOf(5)); 13 | assert.equal(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "flutter-mvvm-architecture-generator" extension will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [unreleased] 9 | - Create folders for the `Create Widget` command 10 | 11 | ## [1.4.0] 12 | - Create Folder if `CreateViews` command is given a parameter like `demo/login` 13 | 14 | ## [1.0.0] - 2019-12-17 15 | - Initial release -------------------------------------------------------------------------------- /src/dart_snippets/architecture/base_model.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { Base } from './base'; 3 | 4 | export class BaseModel extends Base { 5 | 6 | private _dartString: string; 7 | 8 | constructor(fileName: string, suffix?: string) { 9 | super(fileName, suffix); 10 | 11 | this._dartString = `import 'package:equatable/equatable.dart'; 12 | 13 | abstract class BaseModel implements Equatable { 14 | Map toMap(); 15 | }`; 16 | } 17 | 18 | get dartString(): string { 19 | return this._dartString; 20 | } 21 | } -------------------------------------------------------------------------------- /src/dart_snippets/widgets/view_model.ts: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import { Base } from "../architecture/base"; 3 | 4 | export class ViewModel extends Base { 5 | private _dartString: string; 6 | 7 | constructor(fileName: string, suffix: string) { 8 | 9 | super(fileName, suffix); 10 | 11 | this._dartString = `import '../../core/base/base_view_model.dart'; 12 | 13 | class ${this.className} extends BaseViewModel { 14 | 15 | ${this.className}(); 16 | 17 | // Add ViewModel specific code here 18 | }`; 19 | } 20 | 21 | get dartString(): string { 22 | return this._dartString; 23 | } 24 | } -------------------------------------------------------------------------------- /src/dart_snippets/widgets/desktop.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import {Base} from '../architecture/base'; 3 | 4 | export class Desktop extends Base { 5 | 6 | private _dartString: string; 7 | 8 | constructor(fileName: string, suffix: string) { 9 | super(fileName, suffix); 10 | 11 | this._dartString = `part of ${fileName}_widget; 12 | 13 | class _${this.className} extends StatelessWidget { 14 | @override 15 | Widget build(BuildContext context) { 16 | return Center( 17 | child: Text('${fileName}_desktop'), 18 | ); 19 | } 20 | }`; 21 | } 22 | 23 | get dartString(): string { 24 | return this._dartString; 25 | } 26 | } -------------------------------------------------------------------------------- /src/dart_snippets/widgets/mobile.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { Base } from '../architecture/base'; 3 | 4 | export class Mobile extends Base { 5 | 6 | private _dartString: string; 7 | 8 | constructor(fileName: string, suffix: string) { 9 | super(fileName, suffix); 10 | 11 | this._dartString = `part of ${fileName}_widget; 12 | 13 | class _${this.className} extends StatelessWidget { 14 | @override 15 | Widget build(BuildContext context) { 16 | return Center( 17 | child: Text('${fileName}_mobile'), 18 | ); 19 | } 20 | }`; 21 | } 22 | 23 | get dartString(): string { 24 | return this._dartString; 25 | } 26 | } -------------------------------------------------------------------------------- /src/dart_snippets/widgets/tablet.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { Base } from '../architecture/base'; 3 | 4 | export class Tablet extends Base { 5 | 6 | private _dartString: string; 7 | 8 | constructor(fileName: string, suffix: string) { 9 | super(fileName, suffix); 10 | 11 | this._dartString = `part of ${fileName}_widget; 12 | 13 | class _${this.className} extends StatelessWidget { 14 | @override 15 | Widget build(BuildContext context) { 16 | return Center( 17 | child: Text('${fileName}_tablet'), 18 | ); 19 | } 20 | }`; 21 | } 22 | 23 | get dartString(): string { 24 | return this._dartString; 25 | } 26 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/dart_snippets/architecture/base_service.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { Base } from './base'; 3 | 4 | export class BaseService extends Base { 5 | 6 | private _dartString: string; 7 | 8 | constructor(fileName: string, suffix?: string) { 9 | super(fileName, suffix); 10 | 11 | this._dartString = `import 'package:logger/logger.dart'; 12 | 13 | import '../logger.dart'; 14 | 15 | class BaseService { 16 | Logger log; 17 | 18 | BaseService({String title}) { 19 | this.log = getLogger( 20 | title ?? this.runtimeType.toString(), 21 | ); 22 | } 23 | }`; 24 | } 25 | 26 | get dartString(): string { 27 | return this._dartString; 28 | } 29 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from 'vscode-test'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/dart_snippets/architecture/base.ts: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | 3 | export abstract class Base { 4 | 5 | constructor(private fileName: string, private classSuffix?: string) { } 6 | 7 | get className(): string { 8 | return this.getClassName(this.fileName, this.classSuffix); 9 | } 10 | 11 | private getClassName(fileName: string, suffix?: string): string { 12 | let camelCaseString = _.camelCase(fileName); 13 | let className = this.convertStringToUpperCamelCase(camelCaseString); 14 | if (suffix === undefined) { return className; } 15 | return className += suffix; 16 | } 17 | 18 | private convertStringToUpperCamelCase(fileName: string): string { 19 | let camelCaseString = _.camelCase(fileName); 20 | return _.upperFirst(camelCaseString); 21 | } 22 | } -------------------------------------------------------------------------------- /src/dart_snippets/architecture/locator.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { Base } from './base'; 3 | 4 | export class Locator extends Base { 5 | 6 | private _dartString: string; 7 | 8 | constructor(fileName: string, suffix?: string) { 9 | super(fileName, suffix); 10 | 11 | this._dartString = `import '../core/logger.dart'; 12 | import '../core/services/navigator_service.dart'; 13 | import 'package:get_it/get_it.dart'; 14 | import 'package:logger/logger.dart'; 15 | 16 | GetIt locator = GetIt.instance; 17 | 18 | class LocatorInjector { 19 | static Logger _log = getLogger('LocatorInjector'); 20 | 21 | static Future setupLocator() async { 22 | _log.d('Initializing Navigator Service'); 23 | locator.registerLazySingleton(() => NavigatorService()); 24 | } 25 | }`; 26 | } 27 | 28 | get dartString(): string { 29 | return this._dartString; 30 | } 31 | } -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | }); 10 | mocha.useColors(true); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | e(err); 34 | } 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /src/dart_snippets/architecture/providers.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { Base } from './base'; 3 | 4 | export class Providers extends Base { 5 | 6 | private _dartString: string; 7 | 8 | constructor(fileName: string, suffix?: string) { 9 | super(fileName, suffix); 10 | 11 | this._dartString = `import '../core/locator.dart'; 12 | import '../core/services/navigator_service.dart'; 13 | import 'package:provider/provider.dart'; 14 | 15 | class ProviderInjector { 16 | static List providers = [ 17 | ..._independentServices, 18 | ..._dependentServices, 19 | ..._consumableServices, 20 | ]; 21 | 22 | static List _independentServices = [ 23 | Provider.value(value: locator()), 24 | ]; 25 | 26 | static List _dependentServices = []; 27 | 28 | static List _consumableServices = []; 29 | }`; 30 | } 31 | 32 | get dartString(): string { 33 | return this._dartString; 34 | } 35 | } -------------------------------------------------------------------------------- /src/dart_snippets/architecture/main.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { Base } from './base'; 3 | 4 | export class Main extends Base { 5 | 6 | private _dartString: string; 7 | 8 | constructor(fileName: string, suffix?: string) { 9 | super(fileName, suffix); 10 | 11 | this._dartString = `import 'core/locator.dart'; 12 | import 'core/providers.dart'; 13 | import 'core/services/navigator_service.dart'; 14 | import 'package:flutter/material.dart'; 15 | import 'package:provider/provider.dart'; 16 | import 'views/home/home_view.dart'; 17 | 18 | void main() async { 19 | await LocatorInjector.setupLocator(); 20 | runApp(MainApplication()); 21 | } 22 | 23 | class MainApplication extends StatelessWidget { 24 | @override 25 | Widget build(BuildContext context) { 26 | return MultiProvider( 27 | providers: ProviderInjector.providers, 28 | child: MaterialApp( 29 | navigatorKey: locator().navigatorKey, 30 | home: HomeView(), 31 | ), 32 | ); 33 | } 34 | }`; 35 | } 36 | 37 | get dartString(): string { 38 | return this._dartString; 39 | } 40 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 madhukesh_048 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 | -------------------------------------------------------------------------------- /.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 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/out/**/*.js" 18 | ], 19 | "preLaunchTask": "${defaultBuildTask}" 20 | }, 21 | { 22 | "name": "Extension Tests", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "runtimeExecutable": "${execPath}", 26 | "args": [ 27 | "--extensionDevelopmentPath=${workspaceFolder}", 28 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 29 | ], 30 | "outFiles": [ 31 | "${workspaceFolder}/out/test/**/*.js" 32 | ], 33 | "preLaunchTask": "${defaultBuildTask}" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /src/dart_snippets/widgets/widget.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { Base } from '../architecture/base'; 3 | 4 | export class Widget extends Base { 5 | 6 | private _dartString: string; 7 | 8 | constructor(fileName: string, suffix: string) { 9 | super(fileName, suffix); 10 | 11 | let classPrefixList: string[] = this.className.split('Widget'); 12 | let classPrefix: string | undefined; 13 | if (!_.isEmpty(classPrefixList)) { classPrefix = _.first(classPrefixList); } 14 | 15 | this._dartString = `library ${fileName}_widget; 16 | 17 | import 'package:responsive_builder/responsive_builder.dart'; 18 | import 'package:flutter/material.dart'; 19 | 20 | part '${fileName}_mobile.dart'; 21 | part '${fileName}_tablet.dart'; 22 | part '${fileName}_desktop.dart'; 23 | 24 | class ${this.className} extends StatelessWidget { 25 | @override 26 | Widget build(BuildContext context) { 27 | return ScreenTypeLayout( 28 | mobile: _${classPrefix}Mobile(), 29 | desktop: _${classPrefix}Desktop(), 30 | tablet: _${classPrefix}Tablet(), 31 | ); 32 | } 33 | }`; 34 | } 35 | 36 | get dartString(): string { 37 | return this._dartString; 38 | } 39 | } -------------------------------------------------------------------------------- /src/dart_snippets/views/view_model.ts: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import { Base } from "../architecture/base"; 3 | 4 | export class ViewModel extends Base { 5 | private _dartString: string; 6 | 7 | constructor(fileName: string, suffix: string, private projectName?: string) { 8 | 9 | super(fileName, suffix); 10 | let initialPath = this.projectName === undefined ? '../../' : `package:${this.projectName}/`; 11 | this._dartString = `import '${initialPath}core/base/base_view_model.dart'; 12 | 13 | class ${this.className} extends BaseViewModel { 14 | ${this.className}(); 15 | 16 | // Add ViewModel specific code here 17 | }`; 18 | } 19 | 20 | get dartString(): string { 21 | return this._dartString; 22 | } 23 | 24 | get demoString(): string { 25 | let initialPath = this.projectName === undefined ? '../../' : `package:${this.projectName}/`; 26 | return `import '${initialPath}core/base/base_view_model.dart'; 27 | 28 | class HomeViewModel extends BaseViewModel { 29 | int _counter; 30 | 31 | HomeViewModel({int counter = 0}) : this._counter = counter; 32 | 33 | int get counter => this._counter; 34 | set counter(int value) { 35 | this._counter = value; 36 | notifyListeners(); 37 | } 38 | 39 | void increment() => this.counter += 1; 40 | }`; 41 | } 42 | } -------------------------------------------------------------------------------- /src/dart_snippets/architecture/logger.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { Base } from './base'; 3 | 4 | export class Logger extends Base { 5 | 6 | private _dartString: string; 7 | 8 | constructor(fileName: string, suffix?: string) { 9 | super(fileName, suffix); 10 | 11 | this._dartString = `import 'dart:developer' as prefix0; 12 | import 'package:logger/logger.dart'; 13 | 14 | class SimpleLogPrinter extends LogPrinter { 15 | static int counter = 0; 16 | final String className; 17 | 18 | SimpleLogPrinter(this.className); 19 | 20 | @override 21 | void log(LogEvent event) { 22 | prefix0.log( 23 | event.message, 24 | time: DateTime.now(), 25 | level: () { 26 | switch (event.level) { 27 | case Level.verbose: 28 | return 0; 29 | case Level.debug: 30 | return 500; 31 | case Level.info: 32 | return 0; 33 | case Level.warning: 34 | return 1500; 35 | case Level.error: 36 | return 2000; 37 | case Level.wtf: 38 | return 2000; 39 | default: 40 | return 2000; 41 | } 42 | }(), 43 | name: className, 44 | error: event.error, 45 | sequenceNumber: counter += 1, 46 | ); 47 | } 48 | } 49 | 50 | Logger getLogger(String className) { 51 | return Logger(printer: SimpleLogPrinter(className)); 52 | }`; 53 | } 54 | 55 | get dartString(): string { 56 | return this._dartString; 57 | } 58 | } -------------------------------------------------------------------------------- /src/dart_snippets/views/view.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { Base } from '../architecture/base'; 3 | 4 | export class View extends Base { 5 | 6 | private _dartString: string; 7 | 8 | constructor(fileName: string, suffix: string) { 9 | super(fileName, suffix); 10 | 11 | let classPrefixList: string[] = this.className.split('View'); 12 | let classPrefix: string | undefined; 13 | if (!_.isEmpty(classPrefixList)) { classPrefix = _.first(classPrefixList); } 14 | 15 | this._dartString = `library ${fileName}_view; 16 | 17 | import 'package:provider_architecture/provider_architecture.dart'; 18 | import 'package:responsive_builder/responsive_builder.dart'; 19 | import 'package:flutter/material.dart'; 20 | import '${fileName}_view_model.dart'; 21 | 22 | part '${fileName}_mobile.dart'; 23 | part '${fileName}_tablet.dart'; 24 | part '${fileName}_desktop.dart'; 25 | 26 | class ${this.className} extends StatelessWidget { 27 | @override 28 | Widget build(BuildContext context) { 29 | ${classPrefix}ViewModel viewModel = ${classPrefix}ViewModel(); 30 | return ViewModelProvider<${classPrefix}ViewModel>.withConsumer( 31 | viewModel: viewModel, 32 | onModelReady: (viewModel) { 33 | // Do something once your viewModel is initialized 34 | }, 35 | builder: (context, viewModel, child) { 36 | return ScreenTypeLayout( 37 | mobile: _${classPrefix}Mobile(viewModel), 38 | desktop: _${classPrefix}Desktop(viewModel), 39 | tablet: _${classPrefix}Tablet(viewModel), 40 | ); 41 | } 42 | ); 43 | } 44 | }`; 45 | } 46 | 47 | get dartString(): string { 48 | return this._dartString; 49 | } 50 | } -------------------------------------------------------------------------------- /src/dart_snippets/architecture/base_view_model.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { Base } from './base'; 3 | 4 | export class BaseViewModel extends Base { 5 | 6 | private _dartString: string; 7 | 8 | constructor(fileName: string, suffix?: string) { 9 | super(fileName, suffix); 10 | 11 | this._dartString = `import 'package:flutter/foundation.dart'; 12 | import 'package:logger/logger.dart'; 13 | import '../logger.dart'; 14 | 15 | class BaseViewModel extends ChangeNotifier { 16 | String _title; 17 | bool _busy; 18 | Logger log; 19 | bool _isDisposed = false; 20 | 21 | BaseViewModel({ 22 | bool busy = false, 23 | String title, 24 | }) : _busy = busy, 25 | _title = title { 26 | log = getLogger(title ?? this.runtimeType.toString()); 27 | } 28 | 29 | bool get busy => this._busy; 30 | bool get isDisposed => this._isDisposed; 31 | String get title => _title ?? this.runtimeType.toString(); 32 | 33 | set busy(bool busy) { 34 | log.i( 35 | 'busy: ' 36 | '$title is entering ' 37 | '\${busy ? 'busy' : 'free'} state', 38 | ); 39 | this._busy = busy; 40 | notifyListeners(); 41 | } 42 | 43 | @override 44 | void notifyListeners() { 45 | if (!isDisposed) { 46 | super.notifyListeners(); 47 | } else { 48 | log.w('notifyListeners: Notify listeners called after ' 49 | '\${title ?? this.runtimeType.toString()} has been disposed'); 50 | } 51 | } 52 | 53 | @override 54 | void dispose() { 55 | log.i('dispose'); 56 | _isDisposed = true; 57 | super.dispose(); 58 | } 59 | }`; 60 | } 61 | 62 | get dartString(): string { 63 | return this._dartString; 64 | } 65 | } -------------------------------------------------------------------------------- /src/dart_snippets/architecture/navigator_service.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { Base } from './base'; 3 | 4 | export class NavigatorService extends Base { 5 | 6 | private _dartString: string; 7 | 8 | constructor(fileName: string, suffix?: string) { 9 | super(fileName, suffix); 10 | 11 | this._dartString = `import '../../core/base/base_service.dart'; 12 | import 'package:flutter/material.dart'; 13 | 14 | class NavigatorService extends BaseService { 15 | final GlobalKey navigatorKey = GlobalKey(); 16 | 17 | Future navigateToPage(MaterialPageRoute pageRoute) async { 18 | log.i('navigateToPage: pageRoute: \${pageRoute.settings.name}'); 19 | if (navigatorKey.currentState == null) { 20 | log.e('navigateToPage: Navigator State is null'); 21 | return null; 22 | } 23 | return navigatorKey.currentState.push(pageRoute); 24 | } 25 | 26 | Future navigateToPageWithReplacement( 27 | MaterialPageRoute pageRoute) async { 28 | log.i('navigateToPageWithReplacement: ' 29 | 'pageRoute: \${pageRoute.settings.name}'); 30 | if (navigatorKey.currentState == null) { 31 | log.e('navigateToPageWithReplacement: Navigator State is null'); 32 | return null; 33 | } 34 | return navigatorKey.currentState.pushReplacement(pageRoute); 35 | } 36 | 37 | void pop([T result]) { 38 | log.i('goBack:'); 39 | if (navigatorKey.currentState == null) { 40 | log.e('goBack: Navigator State is null'); 41 | return; 42 | } 43 | navigatorKey.currentState.pop(result); 44 | } 45 | }`; 46 | } 47 | 48 | get dartString(): string { 49 | return this._dartString; 50 | } 51 | } -------------------------------------------------------------------------------- /src/utils/vs_code_actions.ts: -------------------------------------------------------------------------------- 1 | import { window, InputBoxOptions, workspace, WorkspaceConfiguration } from 'vscode'; 2 | import * as _ from 'lodash'; 3 | 4 | export class VsCodeActions { 5 | 6 | /** 7 | * Accepts an input string in the current Vscode context 8 | * @param placeHolder The placeholder string to display in the input box 9 | * @param validateInput The function to validate if the text entered in the input box is of a valid format or not 10 | */ 11 | public static async getInputString(placeHolder?: string, validateInput?: InputBoxOptions["validateInput"]): Promise { 12 | let input = await window.showInputBox({ 13 | placeHolder: placeHolder, 14 | validateInput: validateInput, 15 | }); 16 | if (input === undefined) { 17 | return ""; 18 | } 19 | return input; 20 | } 21 | 22 | /** 23 | * Get the root path of the current context 24 | */ 25 | public static get rootPath(): string { 26 | let rootPath = workspace.rootPath; 27 | if (_.isUndefined(rootPath)) { return ''; } 28 | return rootPath; 29 | } 30 | 31 | /** 32 | * Display an error message in the current VsCode context 33 | * @param message The message to display 34 | */ 35 | public static showErrorMessage(message: string) { 36 | window.showErrorMessage(message); 37 | } 38 | 39 | /** 40 | * Display an information message in the current VsCode context 41 | * @param message The message to display 42 | */ 43 | public static showInformationMessage(message: string) { 44 | window.showInformationMessage(message); 45 | } 46 | 47 | public static getEditorConfiguration(): WorkspaceConfiguration { 48 | let configuration = workspace.getConfiguration('editor'); 49 | return configuration; 50 | 51 | } 52 | } -------------------------------------------------------------------------------- /src/utils/file_system_manager.ts: -------------------------------------------------------------------------------- 1 | import { WriteFileOptions, writeFileSync, existsSync, readFile, readFileSync } from "fs"; 2 | import * as path from 'path'; 3 | import { Utils } from './utils'; 4 | import * as shell from "shelljs"; 5 | import { VsCodeActions } from "./vs_code_actions"; 6 | import { YamlHelper } from "./yaml_helper"; 7 | 8 | export class FileSystemManager { 9 | public static createFile(pathValue: string, fileName: string, data: string) { 10 | let filePath = path.join(pathValue, fileName); 11 | writeFileSync(filePath, data); 12 | Utils.openFile(filePath); 13 | } 14 | 15 | public static createFolder(pathValue: string): boolean { 16 | if (!existsSync(pathValue)) { 17 | try { 18 | shell.mkdir('-p', pathValue); 19 | 20 | } catch (error) { 21 | console.error(`Unable to create folder: ${error}`); 22 | return false; 23 | } 24 | } 25 | return true; 26 | } 27 | 28 | public static doesFileExist(filePath: string, fileName: string): boolean { 29 | return existsSync(path.join(filePath, fileName)); 30 | } 31 | 32 | public static readFileAsString(filePath: string, fileName: string): string | undefined { 33 | if (!this.doesFileExist(filePath, fileName)) { return undefined; } 34 | let fileBuffer = readFileSync(path.join(filePath, fileName)); 35 | let fileData = fileBuffer.toString(); 36 | return fileData; 37 | } 38 | 39 | public static isFlutterProject(): boolean { 40 | let rootPath = VsCodeActions.rootPath; 41 | if (!existsSync(path.join(rootPath, 'pubspec.yaml'))) { 42 | VsCodeActions.showErrorMessage('Pubspec.yaml not found'); 43 | return false; 44 | } 45 | let errorMessage = YamlHelper.isValidFlutterPubspec(); 46 | console.error(errorMessage); 47 | if (errorMessage !== undefined) { 48 | VsCodeActions.showErrorMessage(errorMessage); 49 | return false; 50 | } 51 | 52 | return true; 53 | } 54 | } -------------------------------------------------------------------------------- /src/dart_snippets/views/desktop.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import {Base} from '../architecture/base'; 3 | 4 | export class Desktop extends Base { 5 | 6 | private _dartString: string; 7 | 8 | constructor(fileName: string, suffix: string) { 9 | super(fileName, suffix); 10 | 11 | let classPrefixList: string[] = this.className.split('Desktop'); 12 | let classPrefix: string | undefined; 13 | if (!_.isEmpty(classPrefixList)) { classPrefix = _.first(classPrefixList); } 14 | 15 | this._dartString = `part of ${fileName}_view; 16 | 17 | class _${this.className} extends StatelessWidget { 18 | final ${classPrefix}ViewModel viewModel; 19 | 20 | _${this.className}(this.viewModel); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Scaffold( 25 | body: Center(child: Text('${this.className}')), 26 | ); 27 | } 28 | }`; 29 | } 30 | 31 | get dartString(): string { 32 | return this._dartString; 33 | } 34 | 35 | get demoString(): string { 36 | return `part of home_view; 37 | 38 | class _HomeDesktop extends StatelessWidget { 39 | final HomeViewModel viewModel; 40 | 41 | _HomeDesktop(this.viewModel); 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Scaffold( 46 | appBar: AppBar( 47 | title: Text('Desktop'), 48 | backgroundColor: Colors.amber, 49 | ), 50 | body: Center( 51 | child: Column( 52 | mainAxisAlignment: MainAxisAlignment.center, 53 | children: [ 54 | Text( 55 | 'You have pushed the button this many times: ', 56 | style: TextStyle(fontSize: 14), 57 | ), 58 | Text( 59 | '\${viewModel.counter}', 60 | style: Theme.of(context).textTheme.display1, 61 | ), 62 | ], 63 | ), 64 | ), 65 | floatingActionButton: FloatingActionButton( 66 | child: Icon(Icons.add), 67 | onPressed: viewModel.increment, 68 | backgroundColor: Colors.amber, 69 | ), 70 | ); 71 | } 72 | }`; 73 | } 74 | } -------------------------------------------------------------------------------- /src/dart_snippets/views/mobile.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { Base } from '../architecture/base'; 3 | 4 | export class Mobile extends Base { 5 | 6 | private _dartString: string; 7 | 8 | constructor(fileName: string, suffix: string) { 9 | super(fileName, suffix); 10 | 11 | let classPrefixList: string[] = this.className.split('Mobile'); 12 | let classPrefix: string | undefined; 13 | if (!_.isEmpty(classPrefixList)) { classPrefix = _.first(classPrefixList); } 14 | 15 | this._dartString = `part of ${fileName}_view; 16 | 17 | class _${this.className} extends StatelessWidget { 18 | final ${classPrefix}ViewModel viewModel; 19 | 20 | _${this.className}(this.viewModel); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Scaffold( 25 | body: Center(child: Text('${this.className}')), 26 | ); 27 | } 28 | }`; 29 | } 30 | 31 | get dartString(): string { 32 | return this._dartString; 33 | } 34 | 35 | get demoString(): string { 36 | return `part of home_view; 37 | 38 | class _HomeMobile extends StatelessWidget { 39 | final HomeViewModel viewModel; 40 | 41 | _HomeMobile(this.viewModel); 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Scaffold( 46 | appBar: AppBar( 47 | title: Text('Mobile'), 48 | backgroundColor: Colors.black, 49 | ), 50 | body: Center( 51 | child: Column( 52 | mainAxisAlignment: MainAxisAlignment.center, 53 | children: [ 54 | Text( 55 | 'You have pushed the button this many times: ', 56 | style: TextStyle(fontSize: 14), 57 | ), 58 | Text( 59 | '\${viewModel.counter}', 60 | style: Theme.of(context).textTheme.display1, 61 | ), 62 | ], 63 | ), 64 | ), 65 | floatingActionButton: FloatingActionButton( 66 | child: Icon(Icons.add), 67 | onPressed: viewModel.increment, 68 | backgroundColor: Colors.black, 69 | ), 70 | ); 71 | } 72 | } 73 | `; 74 | } 75 | } -------------------------------------------------------------------------------- /src/dart_snippets/views/tablet.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | import { Base } from '../architecture/base'; 3 | 4 | export class Tablet extends Base { 5 | 6 | private _dartString: string; 7 | 8 | constructor(fileName: string, suffix: string) { 9 | super(fileName, suffix); 10 | 11 | let classPrefixList: string[] = this.className.split('Tablet'); 12 | let classPrefix: string | undefined; 13 | if (!_.isEmpty(classPrefixList)) { classPrefix = _.first(classPrefixList); } 14 | 15 | this._dartString = `part of ${fileName}_view; 16 | 17 | class _${this.className} extends StatelessWidget { 18 | final ${classPrefix}ViewModel viewModel; 19 | 20 | _${this.className}(this.viewModel); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Scaffold( 25 | body: Center(child: Text('${this.className}')), 26 | ); 27 | } 28 | }`; 29 | } 30 | 31 | get dartString(): string { 32 | return this._dartString; 33 | } 34 | 35 | get demoString(): string { 36 | return `part of home_view; 37 | 38 | class _HomeTablet extends StatelessWidget { 39 | final HomeViewModel viewModel; 40 | 41 | _HomeTablet(this.viewModel); 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Scaffold( 46 | appBar: AppBar( 47 | title: Text('Tablet'), 48 | backgroundColor: Colors.indigo, 49 | ), 50 | body: Center( 51 | child: Column( 52 | mainAxisAlignment: MainAxisAlignment.center, 53 | children: [ 54 | Text( 55 | 'You have pushed the button this many times: ', 56 | style: TextStyle(fontSize: 14), 57 | ), 58 | Text( 59 | '\${viewModel.counter}', 60 | style: Theme.of(context).textTheme.display1, 61 | ), 62 | ], 63 | ), 64 | ), 65 | floatingActionButton: FloatingActionButton( 66 | child: Icon(Icons.add), 67 | onPressed: viewModel.increment, 68 | backgroundColor: Colors.indigo, 69 | ), 70 | ); 71 | } 72 | } 73 | `; 74 | } 75 | } -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import _ = require('lodash'); 3 | 4 | export class Utils { 5 | public static isValidClassName(className: string): string | undefined { 6 | if (className.length === 0) { 7 | return "File name should have atleast 1 character"; 8 | } 9 | if (className.toLowerCase() === "view") { 10 | return "View is not a valid file name"; 11 | } 12 | 13 | if (className.toLowerCase() === "widget") { 14 | return "Widget is not a valid file name"; 15 | } 16 | 17 | if ( 18 | !className 19 | .substring(0, 1) 20 | .match(new RegExp("([a-zA-Z$][w$]*.)*[a-zA-Z_$][w$]*")) 21 | ) { 22 | return "Invalid class name format"; 23 | } 24 | return undefined; 25 | } 26 | 27 | public static openFile(filePath: string) { 28 | console.info(`openFile: ${filePath}`); 29 | let openPath = vscode.Uri.file(filePath); 30 | 31 | vscode.workspace.openTextDocument(openPath).then((document) => { 32 | vscode.window.showTextDocument(document); 33 | }); 34 | } 35 | 36 | public static processFileName(fileName: string): string { 37 | 38 | if (fileName.length < 4) { 39 | return fileName; 40 | } 41 | fileName = _.lowerCase(fileName); 42 | 43 | let viewFileName = fileName 44 | .substring(fileName.length - 4, fileName.length) 45 | .toLowerCase(); 46 | 47 | let widgetFileName = fileName 48 | .substring(fileName.length - 6, fileName.length) 49 | .toLowerCase(); 50 | 51 | 52 | 53 | if (viewFileName === "view") { 54 | let truncatedFileName = fileName.substring(0, fileName.length - 4); 55 | return truncatedFileName.trim(); 56 | } 57 | 58 | if (widgetFileName === "widget") { 59 | let truncatedFileName = fileName.substring(0, fileName.length - 6); 60 | console.debug('Widget testing'); 61 | return truncatedFileName.trim(); 62 | } 63 | 64 | return fileName.trim(); 65 | } 66 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutter-mvvm-architecture-generator", 3 | "displayName": "Flutter MVVM Architecture Generator", 4 | "description": "VsCode extension to generate boilerplate code when using FilledStack's responsive architecture using Providers", 5 | "version": "1.6.0", 6 | "engines": { 7 | "vscode": "^1.41.0" 8 | }, 9 | "categories": [ 10 | "Other", 11 | "Snippets" 12 | ], 13 | "keywords": [ 14 | "flutter", 15 | "filledstacks", 16 | "MVVM", 17 | "dart" 18 | ], 19 | "publisher": "madhukesh040011", 20 | "icon": "images/flutter.png", 21 | "author": { 22 | "email": "madhukesh04@gmail.com", 23 | "name": "Madhukesh", 24 | "url": "https://github.com/madhukesh048" 25 | }, 26 | "license": "MIT", 27 | "contributors": [ 28 | { 29 | "name": "Ajil Oommen", 30 | "email": "ajilo297@gmail.com", 31 | "url": "https://github.com/ajilo297" 32 | } 33 | ], 34 | "repository": { 35 | "url": "https://github.com/madhukesh048/Flutter-MVVM-VS-Code-extension-Pack" 36 | }, 37 | "activationEvents": [ 38 | "onCommand:extension.createViews", 39 | "onCommand:extension.initializeArchitecture", 40 | "onCommand:extension.createWidget" 41 | ], 42 | "main": "./out/extension.js", 43 | "contributes": { 44 | "commands": [ 45 | { 46 | "command": "extension.createViews", 47 | "title": "Create Views", 48 | "category": "Flutter Architecture" 49 | }, 50 | { 51 | "command": "extension.initializeArchitecture", 52 | "title": "Initialize Architecture", 53 | "category": "Flutter Architecture" 54 | }, 55 | { 56 | "command": "extension.createWidget", 57 | "title": "Create Widgets", 58 | "category": "Flutter Architecture" 59 | } 60 | ] 61 | }, 62 | "scripts": { 63 | "vscode:prepublish": "npm run compile", 64 | "compile": "tsc -p ./", 65 | "watch": "tsc -watch -p ./", 66 | "pretest": "npm run compile", 67 | "test": "node ./out/test/runTest.js" 68 | }, 69 | "devDependencies": { 70 | "@types/glob": "^7.1.1", 71 | "@types/js-yaml": "^3.12.1", 72 | "@types/lodash": "^4.14.149", 73 | "@types/mocha": "^5.2.7", 74 | "@types/node": "^12.11.7", 75 | "@types/shelljs": "^0.8.6", 76 | "@types/vscode": "^1.41.0", 77 | "glob": "^7.1.5", 78 | "mocha": "^6.2.2", 79 | "tslint": "^5.20.1", 80 | "vscode-test": "^1.2.2" 81 | }, 82 | "dependencies": { 83 | "js-yaml": "^3.13.1", 84 | "lodash": "^4.17.15", 85 | "shelljs": "^0.8.3", 86 | "typescript": "^3.7.4" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/utils/widget_file.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as _ from 'lodash'; 3 | import { existsSync } from 'fs'; 4 | import { FileSystemManager } from './file_system_manager'; 5 | import { ViewModel } from '../dart_snippets/widgets/view_model'; 6 | import { Mobile } from '../dart_snippets/widgets/mobile'; 7 | import { Desktop } from '../dart_snippets/widgets/desktop'; 8 | import { Tablet } from '../dart_snippets/widgets/tablet'; 9 | import { Widget } from '../dart_snippets/widgets/widget'; 10 | 11 | export class WidgetFile { 12 | 13 | constructor(private rootPath: string, private fileName: string) { 14 | console.debug(`WidgetFile(rootPath: ${rootPath}, fileName: ${fileName})`); 15 | let folderCreated = FileSystemManager.createFolder(this.pathValue); 16 | if (!folderCreated) { return; } 17 | } 18 | 19 | public createResponsiveWidgets() { 20 | this.createFiles(this.snakeCasedFileName + '_widget.dart', new Widget(this.snakeCasedFileName, 'Widget').dartString); 21 | this.createMobile(); 22 | this.createTablet(); 23 | this.createDesktop(); 24 | } 25 | 26 | public createView() { 27 | 28 | } 29 | 30 | public createMobile() { 31 | this.createFiles(this.snakeCasedFileName + '_mobile.dart', new Mobile(this.snakeCasedFileName, 'Mobile').dartString); 32 | } 33 | 34 | public createDesktop() { 35 | this.createFiles(this.snakeCasedFileName + '_desktop.dart', new Desktop(this.snakeCasedFileName, 'Desktop').dartString); 36 | } 37 | 38 | public createTablet() { 39 | this.createFiles(this.snakeCasedFileName + '_tablet.dart', new Tablet(this.snakeCasedFileName, 'Tablet').dartString); 40 | } 41 | 42 | public createWithViewModel() { 43 | this.createFiles(this.snakeCasedFileName + '_view_model.dart', new ViewModel(this.snakeCasedFileName, 'ViewModel').dartString); 44 | } 45 | 46 | private get snakeCasedFileName(): string { 47 | let snakeCasedFileName = _.snakeCase(this.fileName); 48 | console.debug(`get snakeCasedFileName: ${snakeCasedFileName}`); 49 | return snakeCasedFileName; 50 | } 51 | 52 | private get pathValue(): string { 53 | return path.join( 54 | this.rootPath, 55 | 'lib', 56 | 'widgets', 57 | this.snakeCasedFileName, 58 | ); 59 | } 60 | 61 | private createFiles(fileName: string, data: string) { 62 | if (existsSync(path.join(this.pathValue, this.snakeCasedFileName))) { 63 | console.warn(`${fileName} already exists`); 64 | return; 65 | } 66 | 67 | FileSystemManager.createFile(this.pathValue, fileName, data); 68 | } 69 | } -------------------------------------------------------------------------------- /src/dart_snippets/architecture/pubspec.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as _ from 'lodash'; 3 | import { Base } from './base'; 4 | 5 | export class Pubspec extends Base { 6 | 7 | private _dartString: string; 8 | 9 | constructor(fileName: string, suffix?: string) { 10 | super(fileName, suffix); 11 | 12 | this._dartString = `name: test_app 13 | description: A new Flutter project. 14 | 15 | # The following defines the version and build number for your application. 16 | # A version number is three numbers separated by dots, like 1.2.43 17 | # followed by an optional build number separated by a +. 18 | # Both the version and the builder number may be overridden in flutter 19 | # build by specifying --build-name and --build-number, respectively. 20 | # In Android, build-name is used as versionName while build-number used as versionCode. 21 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 22 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 23 | # Read more about iOS versioning at 24 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 25 | version: 1.0.0+1 26 | 27 | environment: 28 | sdk: ">=2.3.0 <3.0.0" 29 | 30 | dependencies: 31 | flutter: 32 | sdk: flutter 33 | responsive_builder: ^0.1.4 34 | provider: ^3.2.0 35 | logger: ^0.7.0+2 36 | get_it: ^3.0.3 37 | equatable: ^1.0.1 38 | 39 | # The following adds the Cupertino Icons font to your application. 40 | # Use with the CupertinoIcons class for iOS style icons. 41 | cupertino_icons: ^0.1.2 42 | 43 | dev_dependencies: 44 | flutter_test: 45 | sdk: flutter 46 | 47 | 48 | # For information on the generic Dart part of this file, see the 49 | # following page: https://dart.dev/tools/pub/pubspec 50 | 51 | # The following section is specific to Flutter. 52 | flutter: 53 | 54 | # The following line ensures that the Material Icons font is 55 | # included with your application, so that you can use the icons in 56 | # the material Icons class. 57 | uses-material-design: true 58 | 59 | # To add assets to your application, add an assets section, like this: 60 | # assets: 61 | # - images/a_dot_burr.jpeg 62 | # - images/a_dot_ham.jpeg 63 | 64 | # An image asset can refer to one or more resolution-specific "variants", see 65 | # https://flutter.dev/assets-and-images/#resolution-aware. 66 | 67 | # For details regarding adding assets from package dependencies, see 68 | # https://flutter.dev/assets-and-images/#from-packages 69 | 70 | # To add custom fonts to your application, add a fonts section here, 71 | # in this "flutter" section. Each entry in this list should have a 72 | # "family" key with the font family name, and a "fonts" key with a 73 | # list giving the asset and other descriptors for the font. For 74 | # example: 75 | # fonts: 76 | # - family: Schyler 77 | # fonts: 78 | # - asset: fonts/Schyler-Regular.ttf 79 | # - asset: fonts/Schyler-Italic.ttf 80 | # style: italic 81 | # - family: Trajan Pro 82 | # fonts: 83 | # - asset: fonts/TrajanPro.ttf 84 | # - asset: fonts/TrajanPro_Bold.ttf 85 | # weight: 700 86 | # 87 | # For details regarding fonts from package dependencies, 88 | # see https://flutter.dev/custom-fonts/#from-packages 89 | 90 | 91 | `; 92 | } 93 | 94 | get dartString(): string { 95 | return this._dartString; 96 | } 97 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at madhukesh04@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/utils/view_file.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as _ from 'lodash'; 3 | import { existsSync } from 'fs'; 4 | import { FileSystemManager } from './file_system_manager'; 5 | import { View } from '../dart_snippets/views/view'; 6 | import { ViewModel } from '../dart_snippets/views/view_model'; 7 | import { Mobile } from '../dart_snippets/views/mobile'; 8 | import { Desktop } from '../dart_snippets/views/desktop'; 9 | import { Tablet } from '../dart_snippets/views/tablet'; 10 | import { YamlHelper } from './yaml_helper'; 11 | 12 | export class ViewFile { 13 | 14 | constructor(private rootPath: string, private fileName: string, private folders?: string[]) { 15 | console.debug(`ViewFile(rootPath: ${rootPath}, fileName: ${fileName})`); 16 | let folderCreated = FileSystemManager.createFolder(this.pathValue); 17 | if (!folderCreated) { return; } 18 | } 19 | 20 | public createResponsiveViews() { 21 | this.createFiles(this.snakeCasedFileName + '_view.dart', new View(this.snakeCasedFileName, 'View').dartString); 22 | this.createMobile(); 23 | this.createTablet(); 24 | this.createDesktop(); 25 | this.createWithViewModel(); 26 | } 27 | 28 | public createDemoViews() { 29 | this.createFiles(this.snakeCasedFileName + '_view.dart', new View(this.snakeCasedFileName, 'View').dartString); 30 | this.createFiles(this.snakeCasedFileName + '_mobile.dart', new Mobile(this.snakeCasedFileName, 'Mobile').demoString); 31 | this.createFiles(this.snakeCasedFileName + '_desktop.dart', new Desktop(this.snakeCasedFileName, 'Desktop').demoString); 32 | this.createFiles(this.snakeCasedFileName + '_tablet.dart', new Tablet(this.snakeCasedFileName, 'Tablet').demoString); 33 | this.createFiles(this.snakeCasedFileName + '_view_model.dart', new ViewModel(this.snakeCasedFileName, 'ViewModel', YamlHelper.getProjectName()).demoString); 34 | } 35 | 36 | public createView() { 37 | 38 | } 39 | 40 | public createMobile() { 41 | this.createFiles(this.snakeCasedFileName + '_mobile.dart', new Mobile(this.snakeCasedFileName, 'Mobile').dartString); 42 | } 43 | 44 | public createDesktop() { 45 | this.createFiles(this.snakeCasedFileName + '_desktop.dart', new Desktop(this.snakeCasedFileName, 'Desktop').dartString); 46 | } 47 | 48 | public createTablet() { 49 | this.createFiles(this.snakeCasedFileName + '_tablet.dart', new Tablet(this.snakeCasedFileName, 'Tablet').dartString); 50 | } 51 | 52 | public createWithViewModel() { 53 | this.createFiles(this.snakeCasedFileName + '_view_model.dart', new ViewModel(this.snakeCasedFileName, 'ViewModel', YamlHelper.getProjectName()).dartString); 54 | } 55 | 56 | private get snakeCasedFileName(): string { 57 | let snakeCasedFileName = _.snakeCase(this.fileName); 58 | console.debug(`get snakeCasedFileName: ${snakeCasedFileName}`); 59 | return snakeCasedFileName; 60 | } 61 | 62 | private get pathValue(): string { 63 | if (this.folders === undefined) { 64 | return path.join( 65 | this.rootPath, 66 | 'lib', 67 | 'views', 68 | this.snakeCasedFileName 69 | ); 70 | } 71 | return path.join(this.rootPath, 'lib', 'views', ...this.folders, this.snakeCasedFileName); 72 | } 73 | 74 | private createFiles(fileName: string, data: string) { 75 | if (existsSync(path.join(this.pathValue, this.snakeCasedFileName))) { 76 | console.warn(`${fileName} already exists`); 77 | return; 78 | } 79 | 80 | FileSystemManager.createFile(this.pathValue, fileName, data); 81 | } 82 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Flutter Extensions](https://img.shields.io/badge/Flutter-grey?style=flat-square&logo=flutter&logoColor=blue)](https://flutter.dev) 2 | [![GitHub](https://img.shields.io/github/license/madhukesh048/Flutter-MVVM-VS-Code-extension-Pack?color=blue&style=flat-square)](https://raw.githubusercontent.com/madhukesh048/Flutter-MVVM-VS-Code-extension-Pack/master/LICENSE) 3 | [![Visual Studio Marketplace Downloads](https://img.shields.io/visual-studio-marketplace/d/madhukesh040011.flutter-mvvm-architecture-generator?color=green&label=VS%20Code%20Downloads&style=flat-square)](https://marketplace.visualstudio.com/items?itemName=madhukesh040011.flutter-mvvm-architecture-generator) 4 | [![Visual Studio Marketplace Rating (Stars)](https://img.shields.io/visual-studio-marketplace/stars/madhukesh040011.flutter-mvvm-architecture-generator?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=madhukesh040011.flutter-mvvm-architecture-generator&ssr=false#review-details) 5 | 6 | # Flutter MVVM Architecture Generator [![Visual Studio Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/madhukesh040011.flutter-mvvm-architecture-generator?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=madhukesh040011.flutter-mvvm-architecture-generator) 7 | 8 | VsCode extension to generate boilerplate code when using [FilledStacks' responsive architecture](https://www.filledstacks.com/tutorials) using Providers 9 | 10 | ## Features 11 | 12 | ### Initialize Architecture 13 | 14 | Initialize the project with the following project structure: 15 | 16 | ```bash 17 | --root 18 | |-- android 19 | |-- build 20 | |-- ios 21 | |-- lib 22 | |-- core 23 | |-- base 24 | |-- base_model.dart 25 | |-- base_service.dart 26 | |-- base_view_model.dart 27 | |-- models 28 | |-- services 29 | |-- navigation_service.dart 30 | locator.dart 31 | logger.dart 32 | providers.dart 33 | |-- theme 34 | |-- views 35 | |-- home 36 | |-- home_desktop.dart 37 | |-- home_mobile.dart 38 | |-- home_tablet.dart 39 | |-- home_view_model.dart 40 | |-- home_view.dart 41 | |-- widgets 42 | main.dart 43 | |-- test 44 | |-- .gitignore 45 | |-- pubspec.yaml 46 | ``` 47 | 48 | It will also add the following dependencies to the `pubspec.yaml` file 49 | 50 | - responsive_builder: ^0.1.4 51 | - provider: ^3.2.0 52 | - logger: ^0.7.0+2 53 | - get_it: ^3.0.3 54 | - equatable: ^1.0.1 55 | 56 | ![Initializing the architecture](images/init.gif) 57 | 58 | ### Create View 59 | 60 | The create view command will add a **View**, a **ViewModel** and the responsive variants for Tablet, Mobile and Desktop. 61 | 62 | ![Create View](images/views.gif) 63 | 64 | As of version 1.4.0 you can create sub folders for views. 65 | 66 | #### Example 67 | 68 | If you give parameter for class name as `intro/splash/demo`, the extension will create a directory structure like this 69 | 70 | ```bash 71 | --root 72 | |-- android 73 | |-- ios 74 | |-- lib 75 | |-- core 76 | |-- views 77 | |-- intro 78 | |-- splash 79 | |-- demo 80 | |-- demo_view.dart 81 | |-- demo_view_model.dart 82 | |-- demo_tablet.dart 83 | |-- demo_mobile.dart 84 | |-- demo_desktop.dart 85 | |-- main.dart 86 | |-- theme 87 | |-- main.dart 88 | |-- test 89 | |-- pubspec.yaml 90 | ``` 91 | 92 | ### Create Widget 93 | 94 | This command will create a Widget in the `lib/widgets` folder with the initial boilerplate and responsive variants. 95 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as path from "path"; 3 | import * as _ from "lodash"; 4 | import { Utils } from './utils/utils'; 5 | import { Architecture } from './utils/architecture'; 6 | import { ViewFile } from './utils/view_file'; 7 | import { VsCodeActions } from './utils/vs_code_actions'; 8 | import { WidgetFile } from './utils/widget_file'; 9 | import { FileSystemManager } from './utils/file_system_manager'; 10 | 11 | export function activate(context: vscode.ExtensionContext) { 12 | 13 | let initializeDisposable = vscode.commands.registerCommand('extension.initializeArchitecture', async () => { 14 | if (!FileSystemManager.isFlutterProject()) { return; } 15 | 16 | let rootPath = VsCodeActions.rootPath; 17 | if (_.isUndefined(rootPath)) { return; } 18 | new Architecture(path.join(rootPath, 'lib')).init(); 19 | new ViewFile(rootPath, "home").createDemoViews(); 20 | }); 21 | 22 | let viewDisposable = vscode.commands.registerCommand('extension.createViews', async () => { 23 | 24 | if (!FileSystemManager.isFlutterProject()) { return; } 25 | 26 | let inputString = await VsCodeActions.getInputString('Enter class name', async (value) => { 27 | if (value.length === 0) { 28 | return 'Enter class name'; 29 | } 30 | 31 | if (value.toLowerCase() === 'view') { 32 | return 'View is not a valid class name'; 33 | } 34 | 35 | return undefined; 36 | }); 37 | 38 | if (inputString.length === 0 || inputString.toLowerCase() === 'view') { 39 | console.warn("activate: inputString length is 0"); 40 | VsCodeActions.showErrorMessage("Invalid name for file"); 41 | return; 42 | } 43 | 44 | console.debug(`fileName: { ${inputString} }`); 45 | 46 | let nameArray = inputString.trim().split('/'); 47 | let folders: string[] = []; 48 | if (nameArray.length > 1) { 49 | let folderList = nameArray.splice(0, nameArray.length - 1).map(element => { return element; }); 50 | console.debug(`folderlist: { ${folderList} }`); 51 | folders = folderList; 52 | } 53 | 54 | let formattedInputString = _.last(nameArray); 55 | if (formattedInputString === undefined) { 56 | console.error('formattedInputString is undefined'); 57 | return; 58 | } 59 | let fileName = Utils.processFileName(formattedInputString); 60 | console.debug(`activate: fileName: ${fileName}`); 61 | 62 | let rootPath = VsCodeActions.rootPath; 63 | if (rootPath === undefined) { return; } 64 | new ViewFile(rootPath, fileName, folders).createResponsiveViews(); 65 | }); 66 | 67 | 68 | 69 | let widgetDisposable = vscode.commands.registerCommand('extension.createWidget', async () => { 70 | 71 | if (!FileSystemManager.isFlutterProject()) { return; } 72 | 73 | let inputString = await VsCodeActions.getInputString('Enter class name', async (value) => { 74 | if (value.length === 0) { 75 | return 'Enter class name'; 76 | } 77 | if (value.toLowerCase() === 'widget') { 78 | return 'Widget is not a valid class name'; 79 | } 80 | return undefined; 81 | }); 82 | 83 | if (inputString.length === 0 || inputString.toLowerCase() === 'widget') { 84 | console.warn("activate: inputString length is 0"); 85 | VsCodeActions.showErrorMessage("Invalid name for file"); 86 | return; 87 | } 88 | 89 | let fileName = Utils.processFileName(inputString.trim()); 90 | console.debug(`activate: fileName: ${fileName}`); 91 | 92 | let rootPath = VsCodeActions.rootPath; 93 | if (rootPath === undefined) { return; } 94 | new WidgetFile(rootPath, fileName).createResponsiveWidgets(); 95 | }); 96 | 97 | context.subscriptions.push(viewDisposable); 98 | context.subscriptions.push(initializeDisposable); 99 | context.subscriptions.push(widgetDisposable); 100 | } 101 | 102 | export function deactivate() { 103 | console.debug('Flutter MVVM Generator: Deactivated'); 104 | } 105 | 106 | /// [test ,splash] -------------------------------------------------------------------------------- /src/utils/architecture.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as _ from "lodash"; 3 | import { FileSystemManager } from './file_system_manager'; 4 | import { WriteFileOptions } from 'fs'; 5 | import { BaseModel } from '../dart_snippets/architecture/base_model'; 6 | import { BaseService } from '../dart_snippets/architecture/base_service'; 7 | import { BaseViewModel } from '../dart_snippets/architecture/base_view_model'; 8 | import { Utils } from './utils'; 9 | import { NavigatorService } from '../dart_snippets/architecture/navigator_service'; 10 | import { Locator } from '../dart_snippets/architecture/locator'; 11 | import { Logger } from '../dart_snippets/architecture/logger'; 12 | import { Providers } from '../dart_snippets/architecture/providers'; 13 | import { Main } from '../dart_snippets/architecture/main'; 14 | import { YamlHelper } from './yaml_helper'; 15 | 16 | export class Architecture { 17 | 18 | constructor(private rootPath: string) { } 19 | 20 | public init() { 21 | this.initCore(); 22 | this.initTheme(); 23 | this.initViews(); 24 | this.initWidgets(); 25 | 26 | YamlHelper.initializeWithDependencies(); 27 | this.createExistingFile(this.rootPath, 'main.dart', new Main('main.dart').dartString); 28 | } 29 | 30 | private initCore() { 31 | let corePath = path.join(this.rootPath, 'core'); 32 | this.initBase(corePath); 33 | this.initServices(corePath); 34 | this.initModels(corePath); 35 | 36 | this.createFile(corePath, 'locator.dart', new Locator('locator.dart').dartString); 37 | this.createFile(corePath, 'logger.dart', new Logger('logger.dart').dartString); 38 | this.createFile(corePath, 'providers.dart', new Providers('providers.dart').dartString); 39 | } 40 | 41 | private initBase(corePath: string) { 42 | let basePath = path.join(corePath, 'base'); 43 | 44 | let folderCreated = FileSystemManager.createFolder(basePath); 45 | if (!folderCreated) { return; } 46 | 47 | this.createFile(basePath, 'base_model.dart', new BaseModel('base_model.dart').dartString); 48 | this.createFile(basePath, 'base_service.dart', new BaseService('base_service.dart').dartString); 49 | this.createFile(basePath, 'base_view_model.dart', new BaseViewModel('base_view_model.dart').dartString); 50 | } 51 | 52 | private initServices(corePath: string) { 53 | let servicesPath = path.join(corePath, 'services'); 54 | 55 | let folderCreated = FileSystemManager.createFolder(servicesPath); 56 | if (!folderCreated) { return; } 57 | 58 | this.createFile(servicesPath, 'navigator_service.dart', new NavigatorService('navigator_service.dart').dartString); 59 | } 60 | 61 | private initModels(corePath: string) { 62 | let modelsPath = path.join(corePath, 'models'); 63 | let folderCreated = FileSystemManager.createFolder(modelsPath); 64 | console.debug(`FolderCreated: ${folderCreated}`); 65 | } 66 | 67 | private initTheme() { 68 | let themePath = path.join(this.rootPath, 'theme'); 69 | let folderCreated = FileSystemManager.createFolder(themePath); 70 | console.debug(`FolderCreated: ${folderCreated}`); 71 | } 72 | 73 | private initViews() { 74 | let viewsPath = path.join(this.rootPath, 'views'); 75 | let folderCreated = FileSystemManager.createFolder(viewsPath); 76 | console.debug(`FolderCreated: ${folderCreated}`); 77 | } 78 | 79 | private initWidgets() { 80 | let widgetsPath = path.join(this.rootPath, 'widgets'); 81 | let folderCreated = FileSystemManager.createFolder(widgetsPath); 82 | console.debug(`FolderCreated: ${folderCreated}`); 83 | } 84 | 85 | private createFile(pathValue: string, fileName: string, data: string, options?: WriteFileOptions) { 86 | if (FileSystemManager.doesFileExist(pathValue, fileName)) { 87 | console.error(`${fileName} already exists`); 88 | return; 89 | } 90 | 91 | FileSystemManager.createFile(pathValue, fileName, data); 92 | Utils.openFile(path.join(pathValue, fileName)); 93 | } 94 | 95 | private createExistingFile(pathValue: string, fileName: string, data: string, options?: WriteFileOptions) { 96 | FileSystemManager.createFile(pathValue, fileName, data); 97 | Utils.openFile(path.join(pathValue, fileName)); 98 | } 99 | } -------------------------------------------------------------------------------- /src/utils/yaml_helper.ts: -------------------------------------------------------------------------------- 1 | import * as yaml from 'js-yaml'; 2 | import { VsCodeActions } from './vs_code_actions'; 3 | import { FileSystemManager } from './file_system_manager'; 4 | import * as _ from 'lodash'; 5 | 6 | export class YamlHelper { 7 | 8 | public static initializeWithDependencies() { 9 | this.upgradeDartVersion(); 10 | this.addDependencyToPubspec('provider_architecture', '1.0.3'); 11 | this.addDependencyToPubspec('responsive_builder', '0.1.4'); 12 | this.addDependencyToPubspec('provider', '3.2.0'); 13 | this.addDependencyToPubspec('logger', '0.7.0+2'); 14 | this.addDependencyToPubspec('get_it', '3.0.3'); 15 | this.addDependencyToPubspec('equatable', '1.0.1'); 16 | this.addAssetComment(); 17 | } 18 | 19 | public static isValidFlutterPubspec(): string | undefined { 20 | let json = this.getPubspecJsonFile(); 21 | if (json === undefined) { return 'Invalid Pubspec format'; } 22 | let object = JSON.parse(json); 23 | 24 | if (object['environment'] === undefined) { 25 | return 'No environment definition found'; 26 | } 27 | if (object['dependencies'] === undefined) { 28 | return 'Definition for dependencies not found'; 29 | } 30 | if (object['dependencies']['flutter'] === undefined) { 31 | return 'Definition for FLutter in dependencies not found'; 32 | } 33 | return undefined; 34 | } 35 | 36 | public static getProjectName(): string | undefined { 37 | let json = this.getPubspecJsonFile(); 38 | if (json === undefined) { return undefined; } 39 | let object = JSON.parse(json); 40 | 41 | return object['name']; 42 | } 43 | 44 | private static addDependencyToPubspec(module: string, version?: string) { 45 | let json = this.getPubspecJsonFile(); 46 | if (json === undefined) { return; } 47 | let object = JSON.parse(json); 48 | object['dependencies'][module] = `^${version}`; 49 | let modifiedString = JSON.stringify(object); 50 | console.debug(`addDependencyToPubspec: modifiledString: ${modifiedString}`); 51 | let updatedYaml = this.toYAML(modifiedString); 52 | if (updatedYaml === undefined) { 53 | return; 54 | } 55 | this.overwritePubspecFile(updatedYaml); 56 | } 57 | 58 | private static upgradeDartVersion() { 59 | let json = this.getPubspecJsonFile(); 60 | if (json === undefined) { return; } 61 | let object = JSON.parse(json); 62 | object['environment']['sdk'] = '>=2.3.0 <3.0.0'; 63 | let modifiedString = JSON.stringify(object); 64 | console.debug(`upgradeDartVersion: modifiledString: ${modifiedString}`); 65 | let updatedYaml = this.toYAML(modifiedString); 66 | if (updatedYaml === undefined) { 67 | return; 68 | } 69 | this.overwritePubspecFile(updatedYaml); 70 | } 71 | 72 | private static addAssetComment() { 73 | let json = this.getPubspecJsonFile(); 74 | if (json === undefined) { return; } 75 | let object = JSON.parse(json); 76 | let modifiedString = JSON.stringify(object); 77 | let updatedYaml = this.toYAML(modifiedString); 78 | if (updatedYaml === undefined) { 79 | return; 80 | } 81 | updatedYaml += `\n\n # To add assets to your application, add an assets section, like this: 82 | # assets: 83 | # - images/a_dot_burr.jpeg 84 | # - images/a_dot_ham.jpeg 85 | 86 | # An image asset can refer to one or more resolution-specific "variants", see 87 | # https://flutter.dev/assets-and-images/#resolution-aware. 88 | 89 | # For details regarding adding assets from package dependencies, see 90 | # https://flutter.dev/assets-and-images/#from-packages 91 | 92 | # To add custom fonts to your application, add a fonts section here, 93 | # in this "flutter" section. Each entry in this list should have a 94 | # "family" key with the font family name, and a "fonts" key with a 95 | # list giving the asset and other descriptors for the font. For 96 | # example: 97 | # fonts: 98 | # - family: Schyler 99 | # fonts: 100 | # - asset: fonts/Schyler-Regular.ttf 101 | # - asset: fonts/Schyler-Italic.ttf 102 | # style: italic 103 | # - family: Trajan Pro 104 | # fonts: 105 | # - asset: fonts/TrajanPro.ttf 106 | # - asset: fonts/TrajanPro_Bold.ttf 107 | # weight: 700 108 | # 109 | # For details regarding fonts from package dependencies, 110 | # see https://flutter.dev/custom-fonts/#from-packages`; 111 | 112 | this.overwritePubspecFile(updatedYaml); 113 | } 114 | 115 | private static getPubspecJsonFile(): string | undefined { 116 | let rootPath = VsCodeActions.rootPath; 117 | let fileData = FileSystemManager.readFileAsString(rootPath, 'pubspec.yaml'); 118 | if (fileData === undefined) { 119 | console.debug('Pubspec.yaml not found'); 120 | return undefined; 121 | } 122 | let data = YamlHelper.toJSON(fileData); 123 | return data; 124 | } 125 | 126 | private static overwritePubspecFile(data: string) { 127 | FileSystemManager.createFile(VsCodeActions.rootPath, 'pubspec.yaml', data); 128 | } 129 | 130 | private static toYAML(text: string): string | undefined { 131 | let json; 132 | try { 133 | console.debug(`toYAML: ${text}`); 134 | json = JSON.parse(text); 135 | } catch (e) { 136 | VsCodeActions.showErrorMessage('Could not parse the selection as JSON.'); 137 | console.error(e); 138 | return undefined; 139 | } 140 | return yaml.safeDump(json, { indent: this.getIndent() }); 141 | } 142 | 143 | private static toJSON(text: string) { 144 | let json; 145 | try { 146 | console.debug(`toJSON: ${text}`); 147 | json = yaml.safeLoad(text, { schema: yaml.JSON_SCHEMA }); 148 | } catch (e) { 149 | VsCodeActions.showErrorMessage('Could not parse the selection as YAML.'); 150 | console.error(e); 151 | return; 152 | } 153 | return JSON.stringify(json, null, this.getIndent()); 154 | } 155 | 156 | private static getIndent(): number { 157 | const editorCfg = VsCodeActions.getEditorConfiguration(); 158 | if (editorCfg && editorCfg.get('insertSpaces')) { 159 | const tabSize = editorCfg.get('tabSize'); 160 | if (tabSize && typeof tabSize === 'number') { 161 | return tabSize; 162 | } 163 | } 164 | return 2; 165 | } 166 | } --------------------------------------------------------------------------------