| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:io'; |
| |
| import 'package:args/command_runner.dart'; |
| import 'package:mustache4dart/mustache4dart.dart' as mustache; |
| import 'package:path/path.dart' as path; |
| |
| import '../android/android.dart' as android; |
| import '../artifacts.dart'; |
| import '../base/globals.dart'; |
| import '../dart/pub.dart'; |
| import '../ios/setup_xcodeproj.dart'; |
| |
| class CreateCommand extends Command { |
| final String name = 'create'; |
| final String description = 'Create a new Flutter project.'; |
| |
| CreateCommand() { |
| argParser.addOption('out', abbr: 'o', help: 'The output directory.'); |
| argParser.addFlag('pub', |
| defaultsTo: true, |
| help: 'Whether to run "pub get" after the project has been created.'); |
| } |
| |
| @override |
| Future<int> run() async { |
| if (!argResults.wasParsed('out')) { |
| printStatus('No option specified for the output directory.'); |
| printStatus(argParser.usage); |
| return 2; |
| } |
| |
| if (ArtifactStore.flutterRoot == null) { |
| printError('Neither the --flutter-root command line flag nor the FLUTTER_ROOT environment'); |
| printError('variable was specified. Unable to find package:flutter.'); |
| return 2; |
| } |
| String flutterRoot = path.absolute(ArtifactStore.flutterRoot); |
| |
| String flutterPackagePath = path.join(flutterRoot, 'packages', 'flutter'); |
| if (!FileSystemEntity.isFileSync(path.join(flutterPackagePath, 'pubspec.yaml'))) { |
| printError('Unable to find package:flutter in $flutterPackagePath'); |
| return 2; |
| } |
| |
| Directory out = new Directory(argResults['out']); |
| |
| new FlutterSimpleTemplate().generateInto(out, flutterPackagePath); |
| |
| printStatus(''); |
| |
| String message = ''' |
| All done! To run your application: |
| |
| \$ cd ${out.path} |
| \$ flutter run |
| '''; |
| |
| if (argResults['pub']) { |
| int code = await pubGet(directory: out.path); |
| if (code != 0) |
| return code; |
| } |
| |
| printStatus(''); |
| printStatus(message); |
| return 0; |
| } |
| } |
| |
| abstract class Template { |
| final String name; |
| final String description; |
| |
| Map<String, String> files = <String, String>{}; |
| |
| Template(this.name, this.description); |
| |
| void generateInto(Directory dir, String flutterPackagePath) { |
| String dirPath = path.normalize(dir.absolute.path); |
| String projectName = _normalizeProjectName(path.basename(dirPath)); |
| printStatus('Creating ${path.basename(projectName)}...'); |
| dir.createSync(recursive: true); |
| |
| String relativeFlutterPackagePath = path.relative(flutterPackagePath, from: dirPath); |
| Iterable<String> paths = files.keys.toList()..sort(); |
| |
| for (String filePath in paths) { |
| String contents = files[filePath]; |
| Map m = <String, String>{ |
| 'projectName': projectName, |
| 'description': description, |
| 'flutterPackagePath': relativeFlutterPackagePath |
| }; |
| contents = mustache.render(contents, m); |
| filePath = filePath.replaceAll('/', Platform.pathSeparator); |
| File file = new File(path.join(dir.path, filePath)); |
| file.parent.createSync(recursive: true); |
| file.writeAsStringSync(contents); |
| printStatus(' ${file.path}'); |
| } |
| } |
| |
| String toString() => name; |
| } |
| |
| class FlutterSimpleTemplate extends Template { |
| FlutterSimpleTemplate() : super('flutter-simple', 'A minimal Flutter project.') { |
| files['.analysis_options'] = _analysis_options; |
| files['.gitignore'] = _gitignore; |
| files['flutter.yaml'] = _flutterYaml; |
| files['pubspec.yaml'] = _pubspec; |
| files['README.md'] = _readme; |
| files['lib/main.dart'] = _libMain; |
| |
| // Android files. |
| files['android/AndroidManifest.xml'] = _apkManifest; |
| // Create a file here in order to create the res/ directory and ensure it gets committed to git. |
| files['android/res/.empty'] = _androidEmptyFile; |
| |
| // iOS files. |
| files.addAll(iosTemplateFiles); |
| } |
| } |
| |
| String _normalizeProjectName(String name) { |
| name = name.replaceAll('-', '_').replaceAll(' ', '_'); |
| // Strip any extension (like .dart). |
| if (name.contains('.')) |
| name = name.substring(0, name.indexOf('.')); |
| return name; |
| } |
| |
| const String _analysis_options = r''' |
| analyzer: |
| exclude: |
| - 'ios/.generated/**' |
| '''; |
| |
| const String _gitignore = r''' |
| .DS_Store |
| .atom/ |
| .idea |
| .packages |
| .pub/ |
| build/ |
| ios/.generated/ |
| packages |
| pubspec.lock |
| '''; |
| |
| const String _readme = r''' |
| # {{projectName}} |
| |
| {{description}} |
| |
| ## Getting Started |
| |
| For help getting started with Flutter, view our online |
| [documentation](http://flutter.io/). |
| '''; |
| |
| const String _pubspec = r''' |
| name: {{projectName}} |
| description: {{description}} |
| dependencies: |
| flutter: |
| path: {{flutterPackagePath}} |
| '''; |
| |
| const String _flutterYaml = r''' |
| name: {{projectName}} |
| material-design-icons: |
| - name: content/add |
| '''; |
| |
| const String _libMain = r''' |
| import 'package:flutter/material.dart'; |
| |
| void main() { |
| runApp( |
| new MaterialApp( |
| title: "Flutter Demo", |
| routes: <String, RouteBuilder>{ |
| '/': (RouteArguments args) => new FlutterDemo() |
| } |
| ) |
| ); |
| } |
| |
| class FlutterDemo extends StatefulComponent { |
| @override |
| State createState() => new FlutterDemoState(); |
| } |
| |
| class FlutterDemoState extends State { |
| int counter = 0; |
| |
| void incrementCounter() { |
| setState(() { |
| counter++; |
| }); |
| } |
| |
| Widget build(BuildContext context) { |
| return new Scaffold( |
| toolBar: new ToolBar( |
| center: new Text("Flutter Demo") |
| ), |
| body: new Material( |
| child: new Center( |
| child: new Text("Button tapped $counter times.") |
| ) |
| ), |
| floatingActionButton: new FloatingActionButton( |
| child: new Icon( |
| icon: 'content/add' |
| ), |
| onPressed: incrementCounter |
| ) |
| ); |
| } |
| } |
| '''; |
| |
| final String _apkManifest = ''' |
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| package="com.example.{{projectName}}" |
| android:versionCode="1" |
| android:versionName="0.0.1"> |
| |
| <uses-sdk android:minSdkVersion="${android.minApiLevel}" android:targetSdkVersion="21" /> |
| <uses-permission android:name="android.permission.INTERNET"/> |
| |
| <application android:name="org.domokit.sky.shell.SkyApplication" android:label="{{projectName}}"> |
| <activity android:name="org.domokit.sky.shell.SkyActivity" |
| android:launchMode="singleTask" |
| android:theme="@android:style/Theme.Black.NoTitleBar" |
| android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection" |
| android:hardwareAccelerated="true" |
| android:windowSoftInputMode="adjustResize"> |
| <intent-filter> |
| <action android:name="android.intent.action.MAIN"/> |
| <category android:name="android.intent.category.LAUNCHER"/> |
| </intent-filter> |
| </activity> |
| </application> |
| </manifest> |
| '''; |
| |
| final String _androidEmptyFile = ''' |
| Place Android resources here (http://developer.android.com/guide/topics/resources/overview.html). |
| '''; |