Remove --template=skeleton
and add a placeholder error message instead. (#160695)
Closes https://github.com/flutter/flutter/issues/160673. Does the following: - Renames `FlutterProjectType` to `FlutterTemplateType`; did some enhanced enum cleanups while at it - Creates a hierarchy of `RemovedFlutterTemplateType` from `ParsedFlutterTemplateType` - Removes the `skeleton` directory - Merges `app_shared` back into `app` (no longer required now that `skeleton` is removed) Final cleanups are tracked in https://github.com/flutter/flutter/issues/160692. (Added @zanderso just to spot check this is what he meant by https://github.com/flutter/flutter/issues/160673#issuecomment-2557742347)
@ -2443,7 +2443,7 @@ final String _kTemplateRelativePath = path.join(
|
||||
'packages',
|
||||
'flutter_tools',
|
||||
'templates',
|
||||
'app_shared',
|
||||
'app',
|
||||
'windows.tmpl',
|
||||
'runner',
|
||||
);
|
||||
|
@ -19,7 +19,7 @@ See also:
|
||||
|
||||
1. [Flutter tool's Windows logic](https://github.com/flutter/flutter/tree/master/packages/flutter_tools/lib/src/windows) - Builds and runs Flutter Windows apps on
|
||||
the command line.
|
||||
1. [Windows app template](https://github.com/flutter/flutter/tree/master/packages/flutter_tools/templates/app_shared/windows.tmpl) - The entrypoint for Flutter Windows app. This
|
||||
1. [Windows app template](https://github.com/flutter/flutter/tree/master/packages/flutter_tools/templates/app/windows.tmpl) - The entrypoint for Flutter Windows app. This
|
||||
launches the Windows embedder.
|
||||
1. [`platform-windows` GitHub issues label](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3Aplatform-windows+sort%3Aupdated-desc)
|
||||
1. [`#hackers-desktop` Discord channel](https://discord.com/channels/608014603317936148/608020180177780791)
|
||||
|
6
packages/flutter_tools/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# Normally (from the root) we ignore .idea folders, but the ones present
|
||||
# in ide_templates/ and templates/ are real folders we intend to copy as part of
|
||||
# "flutter create".
|
||||
|
||||
!ide_templates/intellij/.idea
|
||||
!templates/**/.idea
|
@ -104,13 +104,16 @@ class CreateCommand extends FlutterCommand with CreateBase {
|
||||
hide: !verboseHelp,
|
||||
);
|
||||
addPlatformsOptions(customHelp: kPlatformHelp);
|
||||
|
||||
final List<ParsedFlutterTemplateType> enabledTemplates =
|
||||
ParsedFlutterTemplateType.enabledValues(featureFlags);
|
||||
argParser.addOption(
|
||||
'template',
|
||||
abbr: 't',
|
||||
allowed: FlutterProjectType.enabledValues.map<String>((FlutterProjectType e) => e.cliName),
|
||||
allowed: enabledTemplates.map((ParsedFlutterTemplateType t) => t.cliName),
|
||||
help: 'Specify the type of project to create.',
|
||||
valueHelp: 'type',
|
||||
allowedHelp: CliEnum.allowedHelp(FlutterProjectType.enabledValues),
|
||||
allowedHelp: CliEnum.allowedHelp(enabledTemplates),
|
||||
);
|
||||
argParser.addOption(
|
||||
'sample',
|
||||
@ -228,13 +231,28 @@ class CreateCommand extends FlutterCommand with CreateBase {
|
||||
}
|
||||
}
|
||||
|
||||
FlutterProjectType _getProjectType(Directory projectDir) {
|
||||
FlutterProjectType? template;
|
||||
FlutterProjectType? detectedProjectType;
|
||||
FlutterTemplateType _getProjectType(Directory projectDir) {
|
||||
FlutterTemplateType? template;
|
||||
FlutterTemplateType? detectedProjectType;
|
||||
final bool metadataExists = projectDir.absolute.childFile('.metadata').existsSync();
|
||||
final String? templateArgument = stringArg('template');
|
||||
if (templateArgument != null) {
|
||||
template = FlutterProjectType.fromCliName(templateArgument);
|
||||
final ParsedFlutterTemplateType? parsedTemplate = ParsedFlutterTemplateType.fromCliName(
|
||||
templateArgument,
|
||||
);
|
||||
switch (parsedTemplate) {
|
||||
case RemovedFlutterTemplateType():
|
||||
throwToolExit(
|
||||
'The template ${parsedTemplate.cliName} is no longer available. For '
|
||||
'your convenience the former help text is repeated below with context '
|
||||
'about the removal and other possible resources:\n\n'
|
||||
'${parsedTemplate.helpText}',
|
||||
);
|
||||
case FlutterTemplateType():
|
||||
template = parsedTemplate;
|
||||
case null:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If the project directory exists and isn't empty, then try to determine the template
|
||||
// type from the project directory.
|
||||
@ -250,7 +268,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
|
||||
);
|
||||
}
|
||||
}
|
||||
template ??= detectedProjectType ?? FlutterProjectType.app;
|
||||
template ??= detectedProjectType ?? FlutterTemplateType.app;
|
||||
if (detectedProjectType != null && template != detectedProjectType && metadataExists) {
|
||||
// We can only be definitive that this is the wrong type if the .metadata file
|
||||
// exists and contains a type that doesn't match.
|
||||
@ -279,27 +297,27 @@ class CreateCommand extends FlutterCommand with CreateBase {
|
||||
String? sampleCode;
|
||||
final String? sampleArgument = stringArg('sample');
|
||||
final bool emptyArgument = boolArg('empty');
|
||||
final FlutterProjectType template = _getProjectType(projectDir);
|
||||
final FlutterTemplateType template = _getProjectType(projectDir);
|
||||
if (sampleArgument != null) {
|
||||
if (template != FlutterProjectType.app) {
|
||||
if (template != FlutterTemplateType.app) {
|
||||
throwToolExit(
|
||||
'Cannot specify --sample with a project type other than '
|
||||
'"${FlutterProjectType.app.cliName}"',
|
||||
'"${FlutterTemplateType.app.cliName}"',
|
||||
);
|
||||
}
|
||||
// Fetch the sample from the server.
|
||||
sampleCode = await _fetchSampleFromServer(sampleArgument);
|
||||
}
|
||||
if (emptyArgument && template != FlutterProjectType.app) {
|
||||
if (emptyArgument && template != FlutterTemplateType.app) {
|
||||
throwToolExit('The --empty flag is only supported for the app template.');
|
||||
}
|
||||
|
||||
final bool generateModule = template == FlutterProjectType.module;
|
||||
final bool generateMethodChannelsPlugin = template == FlutterProjectType.plugin;
|
||||
final bool generateFfiPackage = template == FlutterProjectType.packageFfi;
|
||||
final bool generateFfiPlugin = template == FlutterProjectType.pluginFfi;
|
||||
final bool generateModule = template == FlutterTemplateType.module;
|
||||
final bool generateMethodChannelsPlugin = template == FlutterTemplateType.plugin;
|
||||
final bool generateFfiPackage = template == FlutterTemplateType.packageFfi;
|
||||
final bool generateFfiPlugin = template == FlutterTemplateType.pluginFfi;
|
||||
final bool generateFfi = generateFfiPlugin || generateFfiPackage;
|
||||
final bool generatePackage = template == FlutterProjectType.package;
|
||||
final bool generatePackage = template == FlutterTemplateType.package;
|
||||
|
||||
final List<String> platforms = stringsArg('platforms');
|
||||
// `--platforms` does not support module or package.
|
||||
@ -359,7 +377,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
|
||||
final bool includeLinux;
|
||||
final bool includeMacos;
|
||||
final bool includeWindows;
|
||||
if (template == FlutterProjectType.module) {
|
||||
if (template == FlutterTemplateType.module) {
|
||||
// The module template only supports iOS and Android.
|
||||
includeIos = true;
|
||||
includeAndroid = true;
|
||||
@ -367,7 +385,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
|
||||
includeLinux = false;
|
||||
includeMacos = false;
|
||||
includeWindows = false;
|
||||
} else if (template == FlutterProjectType.package) {
|
||||
} else if (template == FlutterTemplateType.package) {
|
||||
// The package template does not supports any platform.
|
||||
includeIos = false;
|
||||
includeAndroid = false;
|
||||
@ -443,7 +461,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
|
||||
int generatedFileCount = 0;
|
||||
final PubContext pubContext;
|
||||
switch (template) {
|
||||
case FlutterProjectType.app:
|
||||
case FlutterTemplateType.app:
|
||||
final bool skipWidgetTestsGeneration = sampleCode != null || emptyArgument;
|
||||
|
||||
generatedFileCount += await generateApp(
|
||||
@ -455,17 +473,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
|
||||
projectType: template,
|
||||
);
|
||||
pubContext = PubContext.create;
|
||||
case FlutterProjectType.skeleton:
|
||||
generatedFileCount += await generateApp(
|
||||
<String>['skeleton'],
|
||||
relativeDir,
|
||||
templateContext,
|
||||
overwrite: overwrite,
|
||||
printStatusWhenWriting: !creatingNewProject,
|
||||
generateMetadata: false,
|
||||
);
|
||||
pubContext = PubContext.create;
|
||||
case FlutterProjectType.module:
|
||||
case FlutterTemplateType.module:
|
||||
generatedFileCount += await _generateModule(
|
||||
relativeDir,
|
||||
templateContext,
|
||||
@ -473,7 +481,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
|
||||
printStatusWhenWriting: !creatingNewProject,
|
||||
);
|
||||
pubContext = PubContext.create;
|
||||
case FlutterProjectType.package:
|
||||
case FlutterTemplateType.package:
|
||||
generatedFileCount += await _generatePackage(
|
||||
relativeDir,
|
||||
templateContext,
|
||||
@ -481,7 +489,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
|
||||
printStatusWhenWriting: !creatingNewProject,
|
||||
);
|
||||
pubContext = PubContext.createPackage;
|
||||
case FlutterProjectType.plugin:
|
||||
case FlutterTemplateType.plugin:
|
||||
generatedFileCount += await _generateMethodChannelPlugin(
|
||||
relativeDir,
|
||||
templateContext,
|
||||
@ -490,7 +498,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
|
||||
projectType: template,
|
||||
);
|
||||
pubContext = PubContext.createPlugin;
|
||||
case FlutterProjectType.pluginFfi:
|
||||
case FlutterTemplateType.pluginFfi:
|
||||
generatedFileCount += await _generateFfiPlugin(
|
||||
relativeDir,
|
||||
templateContext,
|
||||
@ -499,7 +507,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
|
||||
projectType: template,
|
||||
);
|
||||
pubContext = PubContext.createPlugin;
|
||||
case FlutterProjectType.packageFfi:
|
||||
case FlutterTemplateType.packageFfi:
|
||||
generatedFileCount += await _generateFfiPackage(
|
||||
relativeDir,
|
||||
templateContext,
|
||||
@ -667,7 +675,7 @@ Your $application code is in $relativeAppMain.
|
||||
Map<String, Object?> templateContext, {
|
||||
bool overwrite = false,
|
||||
bool printStatusWhenWriting = true,
|
||||
required FlutterProjectType projectType,
|
||||
required FlutterTemplateType projectType,
|
||||
}) async {
|
||||
// Plugins only add a platform if it was requested explicitly by the user.
|
||||
if (!argResults!.wasParsed('platforms')) {
|
||||
@ -763,7 +771,7 @@ Your $application code is in $relativeAppMain.
|
||||
Map<String, Object?> templateContext, {
|
||||
bool overwrite = false,
|
||||
bool printStatusWhenWriting = true,
|
||||
required FlutterProjectType projectType,
|
||||
required FlutterTemplateType projectType,
|
||||
}) async {
|
||||
// Plugins only add a platform if it was requested explicitly by the user.
|
||||
if (!argResults!.wasParsed('platforms')) {
|
||||
@ -844,7 +852,7 @@ Your $application code is in $relativeAppMain.
|
||||
Map<String, Object?> templateContext, {
|
||||
bool overwrite = false,
|
||||
bool printStatusWhenWriting = true,
|
||||
required FlutterProjectType projectType,
|
||||
required FlutterTemplateType projectType,
|
||||
}) async {
|
||||
int generatedCount = 0;
|
||||
final String? description =
|
||||
@ -1025,7 +1033,7 @@ void _printIncompatibleJavaAgpGradleVersionsWarning({
|
||||
required String templateGradleVersion,
|
||||
required String templateAgpVersion,
|
||||
required String templateAgpVersionForModule,
|
||||
required FlutterProjectType projectType,
|
||||
required FlutterTemplateType projectType,
|
||||
required String projectDirPath,
|
||||
}) {
|
||||
// Determine if the Java version specified conflicts with the template Gradle or AGP version.
|
||||
@ -1041,7 +1049,7 @@ void _printIncompatibleJavaAgpGradleVersionsWarning({
|
||||
);
|
||||
String relevantTemplateAgpVersion = templateAgpVersion;
|
||||
|
||||
if (projectType == FlutterProjectType.module &&
|
||||
if (projectType == FlutterTemplateType.module &&
|
||||
Version.parse(templateAgpVersion)! < Version.parse(templateAgpVersionForModule)!) {
|
||||
// If a module is being created, make sure to check for Java/AGP compatibility between the highest used version of AGP in the module template.
|
||||
javaAgpVersionsCompatible = gradle.validateJavaAndAgp(
|
||||
@ -1066,7 +1074,7 @@ void _printIncompatibleJavaAgpGradleVersionsWarning({
|
||||
);
|
||||
|
||||
if (!javaGradleVersionsCompatible) {
|
||||
if (projectType == FlutterProjectType.plugin || projectType == FlutterProjectType.pluginFfi) {
|
||||
if (projectType == FlutterTemplateType.plugin || projectType == FlutterTemplateType.pluginFfi) {
|
||||
// Only impacted files could be in sample code.
|
||||
return;
|
||||
}
|
||||
@ -1157,24 +1165,26 @@ globally for Flutter by running:
|
||||
|
||||
// Returns path of the gradle-wrapper.properties file for the specified
|
||||
// generated project type.
|
||||
String? _getGradleWrapperPropertiesFilePath(FlutterProjectType projectType, String projectDirPath) {
|
||||
String? _getGradleWrapperPropertiesFilePath(
|
||||
FlutterTemplateType projectType,
|
||||
String projectDirPath,
|
||||
) {
|
||||
String gradleWrapperPropertiesFilePath = '';
|
||||
switch (projectType) {
|
||||
case FlutterProjectType.app:
|
||||
case FlutterProjectType.skeleton:
|
||||
case FlutterTemplateType.app:
|
||||
gradleWrapperPropertiesFilePath = globals.fs.path.join(
|
||||
projectDirPath,
|
||||
'android/gradle/wrapper/gradle-wrapper.properties',
|
||||
);
|
||||
case FlutterProjectType.module:
|
||||
case FlutterTemplateType.module:
|
||||
gradleWrapperPropertiesFilePath = globals.fs.path.join(
|
||||
projectDirPath,
|
||||
'.android/gradle/wrapper/gradle-wrapper.properties',
|
||||
);
|
||||
case FlutterProjectType.plugin:
|
||||
case FlutterProjectType.pluginFfi:
|
||||
case FlutterProjectType.package:
|
||||
case FlutterProjectType.packageFfi:
|
||||
case FlutterTemplateType.plugin:
|
||||
case FlutterTemplateType.pluginFfi:
|
||||
case FlutterTemplateType.package:
|
||||
case FlutterTemplateType.packageFfi:
|
||||
// TODO(camsim99): Add relevant file path for packageFfi when Android is supported.
|
||||
// No gradle-wrapper.properties files not part of sample code that
|
||||
// can be determined.
|
||||
@ -1186,18 +1196,17 @@ String? _getGradleWrapperPropertiesFilePath(FlutterProjectType projectType, Stri
|
||||
// Returns the path(s) of the build.gradle file(s) for the specified generated
|
||||
// project type.
|
||||
List<String>? _getBuildGradleConfigurationFilePaths(
|
||||
FlutterProjectType projectType,
|
||||
FlutterTemplateType projectType,
|
||||
String projectDirPath,
|
||||
) {
|
||||
final List<String> buildGradleConfigurationFilePaths = <String>[];
|
||||
switch (projectType) {
|
||||
case FlutterProjectType.app:
|
||||
case FlutterProjectType.skeleton:
|
||||
case FlutterProjectType.pluginFfi:
|
||||
case FlutterTemplateType.app:
|
||||
case FlutterTemplateType.pluginFfi:
|
||||
buildGradleConfigurationFilePaths.add(
|
||||
globals.fs.path.join(projectDirPath, 'android/build.gradle'),
|
||||
);
|
||||
case FlutterProjectType.module:
|
||||
case FlutterTemplateType.module:
|
||||
const String moduleBuildGradleFilePath = '.android/build.gradle';
|
||||
const String moduleAppBuildGradleFlePath = '.android/app/build.gradle';
|
||||
const String moduleFlutterBuildGradleFilePath = '.android/Flutter/build.gradle';
|
||||
@ -1206,12 +1215,12 @@ List<String>? _getBuildGradleConfigurationFilePaths(
|
||||
globals.fs.path.join(projectDirPath, moduleAppBuildGradleFlePath),
|
||||
globals.fs.path.join(projectDirPath, moduleFlutterBuildGradleFilePath),
|
||||
]);
|
||||
case FlutterProjectType.plugin:
|
||||
case FlutterTemplateType.plugin:
|
||||
buildGradleConfigurationFilePaths.add(
|
||||
globals.fs.path.join(projectDirPath, 'android/app/build.gradle'),
|
||||
);
|
||||
case FlutterProjectType.package:
|
||||
case FlutterProjectType.packageFfi:
|
||||
case FlutterTemplateType.package:
|
||||
case FlutterTemplateType.packageFfi:
|
||||
// TODO(camsim99): Add any relevant file paths for packageFfi when Android is supported.
|
||||
// No build.gradle file because there is no platform-specific implementation.
|
||||
return null;
|
||||
|
@ -158,7 +158,7 @@ mixin CreateBase on FlutterCommand {
|
||||
/// Throws assertion if [projectDir] does not exist or empty.
|
||||
/// Returns null if no project type can be determined.
|
||||
@protected
|
||||
FlutterProjectType? determineTemplateType() {
|
||||
FlutterTemplateType? determineTemplateType() {
|
||||
assert(projectDir.existsSync() && projectDir.listSync().isNotEmpty);
|
||||
final File metadataFile = globals.fs.file(
|
||||
globals.fs.path.join(projectDir.absolute.path, '.metadata'),
|
||||
@ -167,7 +167,7 @@ mixin CreateBase on FlutterCommand {
|
||||
metadataFile,
|
||||
globals.logger,
|
||||
);
|
||||
final FlutterProjectType? projectType = projectMetadata.projectType;
|
||||
final FlutterTemplateType? projectType = projectMetadata.projectType;
|
||||
if (projectType != null) {
|
||||
return projectType;
|
||||
}
|
||||
@ -184,7 +184,7 @@ mixin CreateBase on FlutterCommand {
|
||||
if (exists(<String>['android', 'app']) ||
|
||||
exists(<String>['ios', 'Runner']) ||
|
||||
exists(<String>['ios', 'Flutter'])) {
|
||||
return FlutterProjectType.app;
|
||||
return FlutterTemplateType.app;
|
||||
}
|
||||
// Since we can't really be definitive on nearly-empty directories, err on
|
||||
// the side of prudence and just say we don't know.
|
||||
@ -472,11 +472,11 @@ mixin CreateBase on FlutterCommand {
|
||||
bool pluginExampleApp = false,
|
||||
bool printStatusWhenWriting = true,
|
||||
bool generateMetadata = true,
|
||||
FlutterProjectType? projectType,
|
||||
FlutterTemplateType? projectType,
|
||||
}) async {
|
||||
int generatedCount = 0;
|
||||
generatedCount += await renderMerged(
|
||||
<String>[...templateNames, 'app_shared'],
|
||||
<String>[...templateNames],
|
||||
directory,
|
||||
templateContext,
|
||||
overwrite: overwrite,
|
||||
|
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
import 'base/file_system.dart';
|
||||
@ -12,73 +13,122 @@ import 'project.dart';
|
||||
import 'template.dart';
|
||||
import 'version.dart';
|
||||
|
||||
enum FlutterProjectType implements CliEnum {
|
||||
/// This is the default project with the user-managed host code.
|
||||
/// It is different than the "module" template in that it exposes and doesn't
|
||||
/// manage the platform code.
|
||||
app,
|
||||
/// The result of parsing `--template=` for `flutter create` and related commands.
|
||||
@immutable
|
||||
sealed class ParsedFlutterTemplateType implements CliEnum {
|
||||
static const List<ParsedFlutterTemplateType> _values = <ParsedFlutterTemplateType>[
|
||||
...FlutterTemplateType.values,
|
||||
...RemovedFlutterTemplateType.values,
|
||||
];
|
||||
|
||||
/// A List/Detail app template that follows community best practices.
|
||||
skeleton,
|
||||
|
||||
/// The is a project that has managed platform host code. It is an application with
|
||||
/// ephemeral .ios and .android directories that can be updated automatically.
|
||||
module,
|
||||
|
||||
/// This is a Flutter Dart package project. It doesn't have any native
|
||||
/// components, only Dart.
|
||||
package,
|
||||
|
||||
/// This is a Dart package project with external builds for native components.
|
||||
packageFfi,
|
||||
|
||||
/// This is a native plugin project.
|
||||
plugin,
|
||||
|
||||
/// This is an FFI native plugin project.
|
||||
pluginFfi;
|
||||
|
||||
@override
|
||||
String get cliName => snakeCase(name);
|
||||
|
||||
@override
|
||||
String get helpText => switch (this) {
|
||||
FlutterProjectType.app => '(default) Generate a Flutter application.',
|
||||
FlutterProjectType.skeleton =>
|
||||
'Generate a List View / Detail View Flutter application that follows community best practices.',
|
||||
FlutterProjectType.package =>
|
||||
'Generate a shareable Flutter project containing modular Dart code.',
|
||||
FlutterProjectType.plugin =>
|
||||
'Generate a shareable Flutter project containing an API '
|
||||
'in Dart code with a platform-specific implementation through method channels for Android, iOS, '
|
||||
'Linux, macOS, Windows, web, or any combination of these.',
|
||||
FlutterProjectType.pluginFfi =>
|
||||
'Generate a shareable Flutter project containing an API '
|
||||
'in Dart code with a platform-specific implementation through dart:ffi for Android, iOS, '
|
||||
'Linux, macOS, Windows, or any combination of these.',
|
||||
FlutterProjectType.packageFfi =>
|
||||
'Generate a shareable Dart/Flutter project containing an API '
|
||||
'in Dart code with a platform-specific implementation through dart:ffi for Android, iOS, '
|
||||
'Linux, macOS, and Windows.',
|
||||
FlutterProjectType.module =>
|
||||
'Generate a project to add a Flutter module to an existing Android or iOS application.',
|
||||
};
|
||||
|
||||
static FlutterProjectType? fromCliName(String value) {
|
||||
for (final FlutterProjectType type in FlutterProjectType.values) {
|
||||
if (value == type.cliName) {
|
||||
/// Parses and returns a [ParsedFlutterTemplateType], if any, for [cliName].
|
||||
///
|
||||
/// If no match was found `null` is returned.
|
||||
static ParsedFlutterTemplateType? fromCliName(String cliName) {
|
||||
for (final ParsedFlutterTemplateType type in _values) {
|
||||
if (cliName == type.cliName) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<FlutterProjectType> get enabledValues {
|
||||
return <FlutterProjectType>[
|
||||
for (final FlutterProjectType value in values)
|
||||
if (value != FlutterProjectType.packageFfi || featureFlags.isNativeAssetsEnabled) value,
|
||||
];
|
||||
/// Returns template types that are enabled based on the current [featureFlags].
|
||||
static List<ParsedFlutterTemplateType> enabledValues(FeatureFlags featureFlags) {
|
||||
return _values.toList()..retainWhere((ParsedFlutterTemplateType templateType) {
|
||||
return templateType.isEnabled(featureFlags);
|
||||
});
|
||||
}
|
||||
|
||||
/// Whether the flag is enabled based on a flag being set.
|
||||
bool isEnabled(FeatureFlags featureFlags) => true;
|
||||
}
|
||||
|
||||
/// A [ParsedFlutterTemplateType] that is no longer operable.
|
||||
///
|
||||
/// The CLI can give a hint to a developer that the [cliName] _did_ use to exist,
|
||||
/// but does no longer, and provides [helpText] for other resources to use instead.
|
||||
enum RemovedFlutterTemplateType implements ParsedFlutterTemplateType {
|
||||
skeleton(
|
||||
helpText:
|
||||
'Formerly generated a list view / detail view Flutter application that '
|
||||
'followed some community best practices. For up to date resources, see '
|
||||
'https://flutter.github.io/samples, https://docs.flutter.dev/codelabs, '
|
||||
'and external resources such as https://flutter-builder.app/.',
|
||||
);
|
||||
|
||||
const RemovedFlutterTemplateType({required this.helpText});
|
||||
|
||||
@override
|
||||
bool isEnabled(FeatureFlags featureFlags) => true;
|
||||
|
||||
@override
|
||||
final String helpText;
|
||||
|
||||
@override
|
||||
String get cliName => snakeCase(name);
|
||||
}
|
||||
|
||||
/// The result of parsing a recognized `--template` for `flutter create` and related commands.
|
||||
enum FlutterTemplateType implements ParsedFlutterTemplateType {
|
||||
/// The default project with the user-managed host code.
|
||||
///
|
||||
/// It is different than the "module" template in that it exposes and doesn't
|
||||
/// manage the platform code.
|
||||
app(helpText: '(default) Generate a Flutter application.'),
|
||||
|
||||
/// A project that has managed platform host code.
|
||||
///
|
||||
/// It is an application with ephemeral .ios and .android directories that can be updated automatically.
|
||||
module(
|
||||
helpText:
|
||||
'Generate a project to add a Flutter module to an existing Android or iOS application.',
|
||||
),
|
||||
|
||||
/// A Flutter Dart package project.
|
||||
///
|
||||
/// It doesn't have any native components, only Dart.
|
||||
package(helpText: 'Generate a shareable Flutter project containing modular Dart code.'),
|
||||
|
||||
/// A Dart package project with external builds for native components.
|
||||
packageFfi(
|
||||
helpText:
|
||||
'Generate a shareable Dart/Flutter project containing an API '
|
||||
'in Dart code with a platform-specific implementation through dart:ffi for Android, iOS, '
|
||||
'Linux, macOS, and Windows.',
|
||||
),
|
||||
|
||||
/// A native plugin project.
|
||||
plugin(
|
||||
helpText:
|
||||
'Generate a shareable Flutter project containing an API '
|
||||
'in Dart code with a platform-specific implementation through method channels for Android, iOS, '
|
||||
'Linux, macOS, Windows, web, or any combination of these.',
|
||||
),
|
||||
|
||||
/// This is an FFI native plugin project.
|
||||
pluginFfi(
|
||||
helpText:
|
||||
'Generate a shareable Flutter project containing an API '
|
||||
'in Dart code with a platform-specific implementation through dart:ffi for Android, iOS, '
|
||||
'Linux, macOS, Windows, or any combination of these.',
|
||||
);
|
||||
|
||||
const FlutterTemplateType({required this.helpText});
|
||||
|
||||
@override
|
||||
bool isEnabled(FeatureFlags featureFlags) {
|
||||
return switch (this) {
|
||||
FlutterTemplateType.packageFfi => featureFlags.isNativeAssetsEnabled,
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
final String helpText;
|
||||
|
||||
@override
|
||||
String get cliName => snakeCase(name);
|
||||
}
|
||||
|
||||
/// Verifies the expected yaml keys are present in the file.
|
||||
@ -135,7 +185,13 @@ class FlutterProjectMetadata {
|
||||
}
|
||||
}
|
||||
if (_validateMetadataMap(yamlRoot, <String, Type>{'project_type': String}, _logger)) {
|
||||
_projectType = FlutterProjectType.fromCliName(yamlRoot['project_type'] as String);
|
||||
final ParsedFlutterTemplateType? templateType = ParsedFlutterTemplateType.fromCliName(
|
||||
yamlRoot['project_type'] as String,
|
||||
);
|
||||
_projectType = switch (templateType) {
|
||||
RemovedFlutterTemplateType() || null => null,
|
||||
FlutterTemplateType() => templateType,
|
||||
};
|
||||
}
|
||||
final Object? migrationYaml = yamlRoot['migration'];
|
||||
if (migrationYaml is YamlMap) {
|
||||
@ -148,7 +204,7 @@ class FlutterProjectMetadata {
|
||||
required this.file,
|
||||
required String? versionRevision,
|
||||
required String? versionChannel,
|
||||
required FlutterProjectType? projectType,
|
||||
required FlutterTemplateType? projectType,
|
||||
required this.migrateConfig,
|
||||
required Logger logger,
|
||||
}) : _logger = logger,
|
||||
@ -165,8 +221,8 @@ class FlutterProjectMetadata {
|
||||
String? _versionChannel;
|
||||
String? get versionChannel => _versionChannel;
|
||||
|
||||
FlutterProjectType? _projectType;
|
||||
FlutterProjectType? get projectType => _projectType;
|
||||
FlutterTemplateType? _projectType;
|
||||
FlutterTemplateType? get projectType => _projectType;
|
||||
|
||||
/// Metadata and configuration for the migrate command.
|
||||
MigrateConfig migrateConfig;
|
||||
|
@ -201,7 +201,7 @@ class BuildableIOSApp extends IOSApp {
|
||||
}
|
||||
|
||||
String _templateImageAssetDirNameSuffix(String asset) =>
|
||||
globals.fs.path.join('app_shared', 'ios.tmpl', 'Runner', 'Assets.xcassets', asset);
|
||||
globals.fs.path.join('app', 'ios.tmpl', 'Runner', 'Assets.xcassets', asset);
|
||||
|
||||
String get _appIconAsset => 'AppIcon.appiconset';
|
||||
String get _launchImageAsset => 'LaunchImage.imageset';
|
||||
|
@ -129,7 +129,7 @@ ${_projectMetadataInformation()}
|
||||
return 'No pubspec in working directory.';
|
||||
}
|
||||
final FlutterProjectMetadata metadata = FlutterProjectMetadata(project.metadataFile, _logger);
|
||||
final FlutterProjectType? projectType = metadata.projectType;
|
||||
final FlutterTemplateType? projectType = metadata.projectType;
|
||||
final StringBuffer description =
|
||||
StringBuffer()
|
||||
..writeln('**Type**: ${projectType == null ? 'malformed' : projectType.cliName}')
|
||||
|
@ -173,13 +173,22 @@ class Template {
|
||||
required Logger logger,
|
||||
required TemplateRenderer templateRenderer,
|
||||
}) async {
|
||||
// TODO(matanl): Remove this once https://github.com/flutter/packages/pull/8336 is merged and published.
|
||||
// See https://github.com/flutter/flutter/issues/160692.
|
||||
final List<String> imageNames;
|
||||
if (names.contains('app')) {
|
||||
// Emulates what used to happen when app_shared existed. It still exist in package:flutter_template_images.
|
||||
imageNames = <String>[...names, 'app_shared'];
|
||||
} else {
|
||||
imageNames = names;
|
||||
}
|
||||
// All named templates are placed in the 'templates' directory
|
||||
return Template._(
|
||||
<Directory>[
|
||||
for (final String name in names) templatePathProvider.directoryInPackage(name, fileSystem),
|
||||
],
|
||||
<Directory>[
|
||||
for (final String name in names)
|
||||
for (final String name in imageNames)
|
||||
if ((await templatePathProvider.imageDirectory(name, fileSystem, logger)).existsSync())
|
||||
await templatePathProvider.imageDirectory(name, fileSystem, logger),
|
||||
],
|
||||
|
@ -1,25 +0,0 @@
|
||||
This directory contains templates for `flutter create`.
|
||||
|
||||
The `*_shared` subdirectories provide files for multiple templates.
|
||||
|
||||
* `app_shared` for `app` and `skeleton`.
|
||||
* `plugin_shared` for (method channel) `plugin` and `plugin_ffi`.
|
||||
|
||||
For example, there are two app templates: `app` (the counter app)
|
||||
and `skeleton` (the more advanced list view/detail view app).
|
||||
|
||||
```plain
|
||||
┌────────────┐
|
||||
│ app_shared │
|
||||
└──┬──────┬──┘
|
||||
│ │
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────┐ ┌──────────┐
|
||||
│ app │ │ skeleton │
|
||||
└─────┘ └──────────┘
|
||||
```
|
||||
|
||||
Thanks to `app_shared`, the templates for `app` and `skeleton` can contain
|
||||
only the files that are specific to them alone, and the rest is automatically
|
||||
kept in sync.
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 544 B |
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 442 B |
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 721 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |