Remove all inline templates and provide a way to specify templates as resources
@ -6,14 +6,12 @@ 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 '../dart/pub.dart';
|
||||
import '../globals.dart';
|
||||
import '../ios/setup_xcodeproj.dart';
|
||||
import '../template.dart';
|
||||
|
||||
class CreateCommand extends Command {
|
||||
final String name = 'create';
|
||||
@ -69,35 +67,28 @@ class CreateCommand extends Command {
|
||||
return 2;
|
||||
}
|
||||
|
||||
Directory out;
|
||||
Directory projectDir;
|
||||
|
||||
if (argResults.wasParsed('out')) {
|
||||
out = new Directory(argResults['out']);
|
||||
projectDir = new Directory(argResults['out']);
|
||||
} else {
|
||||
out = new Directory(argResults.rest.first);
|
||||
projectDir = new Directory(argResults.rest.first);
|
||||
}
|
||||
|
||||
FlutterSimpleTemplate template = new FlutterSimpleTemplate();
|
||||
|
||||
if (argResults['with-driver-test'])
|
||||
template.withDriverTest();
|
||||
|
||||
template.generateInto(
|
||||
dir: out,
|
||||
flutterPackagesDirectory: flutterPackagesDirectory
|
||||
);
|
||||
_renderTemplates(projectDir, flutterPackagesDirectory,
|
||||
renderDriverTest: argResults['with-driver-test']);
|
||||
|
||||
printStatus('');
|
||||
|
||||
String message = '''
|
||||
All done! To run your application:
|
||||
|
||||
\$ cd ${out.path}
|
||||
\$ cd ${projectDir.path}
|
||||
\$ flutter run
|
||||
''';
|
||||
|
||||
if (argResults['pub']) {
|
||||
int code = await pubGet(directory: out.path);
|
||||
int code = await pubGet(directory: projectDir.path);
|
||||
if (code != 0)
|
||||
return code;
|
||||
}
|
||||
@ -106,74 +97,38 @@ All done! To run your application:
|
||||
printStatus(message);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Template {
|
||||
final String name;
|
||||
final String description;
|
||||
final Map<String, String> files = <String, String>{};
|
||||
final Map<String, dynamic> additionalTemplateVariables = <String, dynamic>{};
|
||||
|
||||
Template(this.name, this.description);
|
||||
|
||||
void generateInto({
|
||||
Directory dir,
|
||||
String flutterPackagesDirectory
|
||||
}) {
|
||||
String dirPath = path.normalize(dir.absolute.path);
|
||||
void _renderTemplates(Directory projectDir, String flutterPackagesDirectory,
|
||||
{ bool renderDriverTest: false }) {
|
||||
String dirPath = path.normalize(projectDir.absolute.path);
|
||||
String projectName = _normalizeProjectName(path.basename(dirPath));
|
||||
String projectIdentifier = _createProjectIdentifier(path.basename(dirPath));
|
||||
String relativeFlutterPackagesDirectory = path.relative(flutterPackagesDirectory, from: dirPath);
|
||||
|
||||
printStatus('Creating ${path.basename(projectName)}...');
|
||||
dir.createSync(recursive: true);
|
||||
|
||||
String relativeFlutterPackagesDirectory =
|
||||
path.relative(flutterPackagesDirectory, from: dirPath);
|
||||
Iterable<String> paths = files.keys.toList()..sort();
|
||||
projectDir.createSync(recursive: true);
|
||||
|
||||
for (String filePath in paths) {
|
||||
String contents = files[filePath];
|
||||
Map m = <String, String>{
|
||||
'projectName': projectName,
|
||||
'projectIdentifier': projectIdentifier,
|
||||
'description': description,
|
||||
'flutterPackagesDirectory': relativeFlutterPackagesDirectory,
|
||||
};
|
||||
m.addAll(additionalTemplateVariables);
|
||||
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}');
|
||||
Map templateContext = <String, dynamic>{
|
||||
'projectName': projectName,
|
||||
'projectIdentifier': projectIdentifier,
|
||||
'description': description,
|
||||
'flutterPackagesDirectory': relativeFlutterPackagesDirectory,
|
||||
};
|
||||
|
||||
if (renderDriverTest)
|
||||
templateContext['withDriverTest?'] = {};
|
||||
|
||||
Template createTemplate = new Template.fromName('create');
|
||||
createTemplate.render(new Directory(dirPath), templateContext,
|
||||
overwriteExisting: false);
|
||||
|
||||
if (renderDriverTest) {
|
||||
Template driverTemplate = new Template.fromName('driver');
|
||||
driverTemplate.render(new Directory(path.join(dirPath, 'test_driver')),
|
||||
templateContext, overwriteExisting: false);
|
||||
}
|
||||
}
|
||||
|
||||
String toString() => name;
|
||||
}
|
||||
|
||||
class FlutterSimpleTemplate extends Template {
|
||||
FlutterSimpleTemplate() : super('flutter-simple', 'A simple Flutter app.') {
|
||||
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);
|
||||
}
|
||||
|
||||
void withDriverTest() {
|
||||
additionalTemplateVariables['withDriverTest?'] = {};
|
||||
files['test_driver/e2e.dart'] = _e2eApp;
|
||||
files['test_driver/e2e_test.dart'] = _e2eTest;
|
||||
}
|
||||
}
|
||||
|
||||
String _normalizeProjectName(String name) {
|
||||
@ -191,192 +146,3 @@ String _createProjectIdentifier(String name) {
|
||||
name = name.length == 0 ? 'untitled' : name;
|
||||
return 'com.yourcompany.$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: {{flutterPackagesDirectory}}/flutter
|
||||
{{#withDriverTest?}}
|
||||
dev_dependencies:
|
||||
flutter_driver:
|
||||
path: {{flutterPackagesDirectory}}/flutter_driver
|
||||
{{/withDriverTest?}}
|
||||
''';
|
||||
|
||||
const String _flutterYaml = r'''
|
||||
name: {{projectName}}
|
||||
material-design-icons:
|
||||
- name: content/add
|
||||
- name: navigation/arrow_back
|
||||
- name: navigation/menu
|
||||
- name: navigation/more_vert
|
||||
''';
|
||||
|
||||
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 {
|
||||
State createState() => new _FlutterDemoState();
|
||||
}
|
||||
|
||||
class _FlutterDemoState extends State<FlutterDemo> {
|
||||
int _counter = 0;
|
||||
|
||||
void _incrementCounter() {
|
||||
setState(() {
|
||||
_counter++;
|
||||
});
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new Scaffold(
|
||||
toolBar: new ToolBar(
|
||||
center: new Text('Flutter Demo')
|
||||
),
|
||||
body: new Center(
|
||||
child: new Text(
|
||||
'Button tapped $_counter times.',
|
||||
key: const ValueKey('counter')
|
||||
)
|
||||
),
|
||||
floatingActionButton: new FloatingActionButton(
|
||||
key: const ValueKey('fab'),
|
||||
child: new Icon(
|
||||
icon: 'content/add'
|
||||
),
|
||||
onPressed: _incrementCounter
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
const String _e2eApp = '''
|
||||
// Starts the app with Flutter Driver extension enabled to allow Flutter Driver
|
||||
// to test the app.
|
||||
import 'package:{{projectName}}/main.dart' as app;
|
||||
import 'package:flutter_driver/driver_extension.dart';
|
||||
|
||||
main() {
|
||||
enableFlutterDriverExtension();
|
||||
app.main();
|
||||
}
|
||||
''';
|
||||
|
||||
const String _e2eTest = '''
|
||||
// This is a basic Flutter Driver test for the application. A Flutter Driver
|
||||
// test is an end-to-end test that "drives" your application from another
|
||||
// process or even from another computer. If you are familiar with
|
||||
// Selenium/WebDriver for web, Espresso for Android or UI Automation for iOS,
|
||||
// this is simply Flutter's version of that.
|
||||
//
|
||||
// To start the test run the following command from the root of your application
|
||||
// package:
|
||||
//
|
||||
// flutter drive --target=test_driver/e2e.dart
|
||||
//
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
main() {
|
||||
group('end-to-end test', () {
|
||||
FlutterDriver driver;
|
||||
|
||||
setUpAll(() async {
|
||||
// Connect to a running Flutter application instance.
|
||||
driver = await FlutterDriver.connect();
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
if (driver != null) driver.close();
|
||||
});
|
||||
|
||||
test('find the floating action button by value key', () async {
|
||||
ObjectRef elem = await driver.findByValueKey('fab');
|
||||
expect(elem, isNotNull);
|
||||
expect(elem.objectReferenceKey, isNotNull);
|
||||
});
|
||||
|
||||
test('tap on the floating action button; verify counter', () async {
|
||||
ObjectRef fab = await driver.findByValueKey('fab');
|
||||
expect(fab, isNotNull);
|
||||
await driver.tap(fab);
|
||||
ObjectRef counter = await driver.findByValueKey('counter');
|
||||
expect(counter, isNotNull);
|
||||
String text = await driver.getText(counter);
|
||||
expect(text, contains("Button tapped 1 times."));
|
||||
});
|
||||
});
|
||||
}
|
||||
''';
|
||||
|
||||
final String _apkManifest = '''
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="{{projectIdentifier}}"
|
||||
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).
|
||||
''';
|
||||
|
@ -12,13 +12,6 @@ import '../base/process.dart';
|
||||
import '../globals.dart';
|
||||
import '../runner/flutter_command_runner.dart';
|
||||
|
||||
/// A map from file path to file contents.
|
||||
final Map<String, String> iosTemplateFiles = <String, String>{
|
||||
'ios/Info.plist': _infoPlistInitialContents,
|
||||
'ios/LaunchScreen.storyboard': _launchScreenInitialContents,
|
||||
'ios/Assets.xcassets/AppIcon.appiconset/Contents.json': _iconAssetInitialContents
|
||||
};
|
||||
|
||||
Uri _xcodeProjectUri(String revision) {
|
||||
String uriString = 'https://storage.googleapis.com/flutter_infra/flutter/$revision/ios/FlutterXcode.zip';
|
||||
return Uri.parse(uriString);
|
||||
@ -162,159 +155,3 @@ Future<int> setupXcodeProjectHarness(String flutterProjectPath) async {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
final String _infoPlistInitialContents = '''
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>Runner</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>{{projectIdentifier}}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>{{projectName}}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
''';
|
||||
|
||||
final String _launchScreenInitialContents = '''
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
''';
|
||||
|
||||
final String _iconAssetInitialContents = '''
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "83.5x83.5",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
132
packages/flutter_tools/lib/src/template.dart
Normal file
@ -0,0 +1,132 @@
|
||||
// Copyright 2016 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:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:mustache4dart/mustache4dart.dart' as mustache;
|
||||
|
||||
import 'artifacts.dart';
|
||||
import 'globals.dart';
|
||||
|
||||
const String _kTemplateExtension = '.tmpl';
|
||||
const String _kCopyTemplateExtension = '.copy.tmpl';
|
||||
|
||||
/// Expands templates in a directory to a destination. All files that must
|
||||
/// undergo template expansion should end with the '.tmpl' extension. All other
|
||||
/// files are ignored. In case the contents of entire directories must be copied
|
||||
/// as is, the directory itself can end with '.tmpl' extension. Files within
|
||||
/// such a directory may also contain the '.tmpl' extension and will be
|
||||
/// considered for expansion. In case certain files need to be copied but
|
||||
/// without template expansion (images, data files, etc.), the '.copy.tmpl'
|
||||
/// extension may be used.
|
||||
///
|
||||
/// Files in the destination will not contain either the '.tmpl' or '.copy.tmpl'
|
||||
/// extensions.
|
||||
class Template {
|
||||
factory Template.fromName(String name) {
|
||||
// All named templates are placed in the 'templates' directory
|
||||
Directory templateDir = _templateDirectoryInPackage(name);
|
||||
return new Template(templateDir, templateDir);
|
||||
}
|
||||
|
||||
Template(Directory templateSource, Directory baseDir) {
|
||||
_templateFilePaths = new Map<String, String>();
|
||||
|
||||
if (!templateSource.existsSync()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<FileSystemEntity> templateFiles =
|
||||
templateSource.listSync(recursive: true);
|
||||
|
||||
for (FileSystemEntity entity in templateFiles) {
|
||||
if (entity is! File) {
|
||||
// We are only interesting in template *file* URIs.
|
||||
continue;
|
||||
}
|
||||
|
||||
String relativePath = path.relative(entity.path,
|
||||
from: baseDir.absolute.path);
|
||||
|
||||
if (relativePath.contains(_kTemplateExtension)) {
|
||||
// If '.tmpl' appears anywhere within the path of this entity, it is
|
||||
// is a candidate for rendering. This catches cases where the folder
|
||||
// itself is a template.
|
||||
_templateFilePaths[relativePath] = path.absolute(entity.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<String /* relative */, String /* absolute source */> _templateFilePaths;
|
||||
|
||||
void render(Directory destination, Map<String, dynamic> context,
|
||||
{ bool overwriteExisting: true }) {
|
||||
destination.createSync(recursive: true);
|
||||
|
||||
String destinationDirPath = destination.absolute.path;
|
||||
|
||||
_templateFilePaths.forEach((String relativeDestPath,
|
||||
String absoluteSrcPath) {
|
||||
String finalDestinationPath = path
|
||||
.join(destinationDirPath, relativeDestPath)
|
||||
.replaceAll(_kCopyTemplateExtension, '')
|
||||
.replaceAll(_kTemplateExtension, '');
|
||||
File finalDestinationFile = new File(finalDestinationPath);
|
||||
String relativePathForLogging = relativeDestPath
|
||||
.replaceAll(_kCopyTemplateExtension, '')
|
||||
.replaceAll(_kTemplateExtension, '');
|
||||
|
||||
// Step 1: Check if the file needs to be overwritten.
|
||||
|
||||
if (finalDestinationFile.existsSync()) {
|
||||
if (overwriteExisting) {
|
||||
finalDestinationFile.delete(recursive: true);
|
||||
printStatus('$relativePathForLogging exists. Overwriting.');
|
||||
} else {
|
||||
// The file exists but we cannot overwrite it, move on.
|
||||
printStatus('$relativePathForLogging exists. Skipping.');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
printStatus('$relativePathForLogging created.');
|
||||
}
|
||||
|
||||
finalDestinationFile.createSync(recursive: true);
|
||||
File sourceFile = new File(absoluteSrcPath);
|
||||
|
||||
// Step 2: If the absolute paths ends with a 'copy.tmpl', this file does
|
||||
// not need mustache rendering but needs to be directly copied.
|
||||
|
||||
if (sourceFile.path.endsWith(_kCopyTemplateExtension)) {
|
||||
finalDestinationFile.writeAsBytesSync(sourceFile.readAsBytesSync());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 3: If the absolute path ends with a '.tmpl', this file needs
|
||||
// rendering via mustache.
|
||||
|
||||
if (sourceFile.path.endsWith(_kTemplateExtension)) {
|
||||
String templateContents = sourceFile.readAsStringSync();
|
||||
String renderedContents = mustache.render(templateContents, context);
|
||||
|
||||
finalDestinationFile.writeAsStringSync(renderedContents);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 4: This file does not end in .tmpl but is in a directory that
|
||||
// does. Directly copy the file to the destination.
|
||||
|
||||
finalDestinationFile.writeAsBytesSync(sourceFile.readAsBytesSync());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Directory _templateDirectoryInPackage(String name) {
|
||||
String templatesDir = path.join(ArtifactStore.flutterRoot,
|
||||
'packages', 'flutter_tools', 'templates');
|
||||
return new Directory(path.join(templatesDir, name));
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
analyzer:
|
||||
exclude:
|
||||
- 'ios/.generated/**'
|
9
packages/flutter_tools/templates/create/.gitignore.tmpl
Normal file
@ -0,0 +1,9 @@
|
||||
.DS_Store
|
||||
.atom/
|
||||
.idea
|
||||
.packages
|
||||
.pub/
|
||||
build/
|
||||
ios/.generated/
|
||||
packages
|
||||
pubspec.lock
|
8
packages/flutter_tools/templates/create/README.md.tmpl
Normal file
@ -0,0 +1,8 @@
|
||||
# {{projectName}}
|
||||
|
||||
{{description}}
|
||||
|
||||
## Getting Started
|
||||
|
||||
For help getting started with Flutter, view our online
|
||||
[documentation](http://flutter.io/).
|
@ -0,0 +1,22 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="{{projectIdentifier}}"
|
||||
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>
|
@ -0,0 +1 @@
|
||||
Place Android resources here (http://developer.android.com/guide/topics/resources/overview.html).
|
@ -0,0 +1,6 @@
|
||||
name: {{projectName}}
|
||||
material-design-icons:
|
||||
- name: content/add
|
||||
- name: navigation/arrow_back
|
||||
- name: navigation/menu
|
||||
- name: navigation/more_vert
|
@ -0,0 +1,142 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Small@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Small@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Small-40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-Small-40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-Small.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-Small@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-Small-40.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-Small-40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-76.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "16x16",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_16x16.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "16x16",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_16x16@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "32x32",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_32x32.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "32x32",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_32x32@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "128x128",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_128x128.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "128x128",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_128x128@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "256x256",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_256x256.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "256x256",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_256x256@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "512x512",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_512x512.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "512x512",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_512x512@2x.png",
|
||||
"scale" : "2x"
|
||||
}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 6.1 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 869 B |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 425 B |
After Width: | Height: | Size: 952 B |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 952 B |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 56 KiB |
47
packages/flutter_tools/templates/create/ios/Info.plist.tmpl
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>Runner</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>{{projectIdentifier}}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>{{projectName}}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
47
packages/flutter_tools/templates/create/lib/main.dart.tmpl
Normal file
@ -0,0 +1,47 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() {
|
||||
runApp(
|
||||
new MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
routes: <String, RouteBuilder>{
|
||||
'/': (RouteArguments args) => new FlutterDemo()
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
class FlutterDemo extends StatefulComponent {
|
||||
State createState() => new _FlutterDemoState();
|
||||
}
|
||||
|
||||
class _FlutterDemoState extends State<FlutterDemo> {
|
||||
int _counter = 0;
|
||||
|
||||
void _incrementCounter() {
|
||||
setState(() {
|
||||
_counter++;
|
||||
});
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new Scaffold(
|
||||
toolBar: new ToolBar(
|
||||
center: new Text('Flutter Demo')
|
||||
),
|
||||
body: new Center(
|
||||
child: new Text(
|
||||
'Button tapped $_counter times.',
|
||||
key: const ValueKey('counter')
|
||||
)
|
||||
),
|
||||
floatingActionButton: new FloatingActionButton(
|
||||
key: const ValueKey('fab'),
|
||||
child: new Icon(
|
||||
icon: 'content/add'
|
||||
),
|
||||
onPressed: _incrementCounter
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
10
packages/flutter_tools/templates/create/pubspec.yaml.tmpl
Normal file
@ -0,0 +1,10 @@
|
||||
name: {{projectName}}
|
||||
description: {{description}}
|
||||
dependencies:
|
||||
flutter:
|
||||
path: {{flutterPackagesDirectory}}/flutter
|
||||
{{#withDriverTest?}}
|
||||
dev_dependencies:
|
||||
flutter_driver:
|
||||
path: {{flutterPackagesDirectory}}/flutter_driver
|
||||
{{/withDriverTest?}}
|
9
packages/flutter_tools/templates/driver/e2e.dart.tmpl
Normal file
@ -0,0 +1,9 @@
|
||||
// Starts the app with Flutter Driver extension enabled to allow Flutter Driver
|
||||
// to test the app.
|
||||
import 'package:{{projectName}}/main.dart' as app;
|
||||
import 'package:flutter_driver/driver_extension.dart';
|
||||
|
||||
main() {
|
||||
enableFlutterDriverExtension();
|
||||
app.main();
|
||||
}
|
44
packages/flutter_tools/templates/driver/e2e_test.dart.tmpl
Normal file
@ -0,0 +1,44 @@
|
||||
// This is a basic Flutter Driver test for the application. A Flutter Driver
|
||||
// test is an end-to-end test that "drives" your application from another
|
||||
// process or even from another computer. If you are familiar with
|
||||
// Selenium/WebDriver for web, Espresso for Android or UI Automation for iOS,
|
||||
// this is simply Flutter's version of that.
|
||||
//
|
||||
// To start the test run the following command from the root of your application
|
||||
// package:
|
||||
//
|
||||
// flutter drive --target=test_driver/e2e.dart
|
||||
//
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
main() {
|
||||
group('end-to-end test', () {
|
||||
FlutterDriver driver;
|
||||
|
||||
setUpAll(() async {
|
||||
// Connect to a running Flutter application instance.
|
||||
driver = await FlutterDriver.connect();
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
if (driver != null) driver.close();
|
||||
});
|
||||
|
||||
test('find the floating action button by value key', () async {
|
||||
ObjectRef elem = await driver.findByValueKey('fab');
|
||||
expect(elem, isNotNull);
|
||||
expect(elem.objectReferenceKey, isNotNull);
|
||||
});
|
||||
|
||||
test('tap on the floating action button; verify counter', () async {
|
||||
ObjectRef fab = await driver.findByValueKey('fab');
|
||||
expect(fab, isNotNull);
|
||||
await driver.tap(fab);
|
||||
ObjectRef counter = await driver.findByValueKey('counter');
|
||||
expect(counter, isNotNull);
|
||||
String text = await driver.getText(counter);
|
||||
expect(text, contains("Button tapped 1 times."));
|
||||
});
|
||||
});
|
||||
}
|