web_shell_flutter/tool/generate_app.dart

289 lines
8.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:io';
import 'package:yaml/yaml.dart';
Future<void> main(List<String> args) async {
if (args.isEmpty) {
print(
'\x1B[31m用法dart run tool/generate_app.dart <品牌名>\x1B[0m',
);
print('\x1B[33m示例dart run tool/generate_app.dart quanxue\x1B[0m');
exit(1);
}
final String brand = args.first;
final File configFile = File('flavors/$brand.yaml');
if (!configFile.existsSync()) {
print(
'\x1B[31m[错误] 未找到配置文件:${configFile.path}\x1B[0m',
);
exit(1);
}
print('\x1B[34m[信息] 正在为品牌生成应用:$brand...\x1B[0m');
// 1. 解析 YAML 配置
final String yamlString = await configFile.readAsString();
final YamlMap config = loadYaml(yamlString) as YamlMap;
final String appName = config['app_name'] as String;
final String applicationId = config['application_id'] as String;
final String appKey = config['app_key'] as String;
final YamlMap theme = config['theme'] as YamlMap;
final String accentColor = theme['accent_color'] as String;
final String bgColor = theme['bg_color'] as String;
final String textColor = theme['text_color'] as String;
final String mutedTextColor = theme['muted_text_color'] as String;
final String appDir = 'apps/$brand';
print('\x1B[32m✔ 配置加载完成。\x1B[0m');
// 2. 创建 Flutter 应用
await _createFlutterApp(brand, appDir, applicationId);
// 3. 添加核心依赖
await _addCoreDependency(appDir);
// 4. 覆盖 MainActivity.java 以继承 CoreShellActivity
await _overwriteMainActivity(appDir, applicationId);
// 5. 覆盖 AndroidManifest.xml 中的应用名称
await _overwriteManifestLabel(appDir, appName);
// 6. 生成 lib/main.dart
await _generateDartEntrypoint(
appDir,
appName,
appKey,
accentColor,
bgColor,
textColor,
mutedTextColor,
);
// 7. 生成图标与启动页配置
await _generateBrandingAssets(brand, appDir, config);
print('\x1B[32m✔ 应用 $brand 已生成到 $appDir\x1B[0m');
print('\x1B[34m构建应用请执行\x1B[0m');
print(' cd $appDir && flutter build apk');
}
Future<void> _createFlutterApp(
String brand,
String appDir,
String applicationId,
) async {
print('\x1B[34m[信息] 正在执行 flutter create...\x1B[0m');
final Directory dir = Directory(appDir);
if (dir.existsSync()) {
print(
'\x1B[33m[警告] 目录 $appDir 已存在,正在清理...\x1B[0m',
);
dir.deleteSync(recursive: true);
}
// 提取组织名
// 例如com.wanmake.quanxue -> org: com.wanmake
final List<String> segments = applicationId.split('.');
final String org = segments.sublist(0, segments.length - 1).join('.');
final ProcessResult result = await Process.run('flutter', [
'create',
'--org',
org,
'--project-name',
brand.replaceAll('-', '_'),
'--platforms',
'android',
appDir,
]);
if (result.exitCode != 0) {
print('\x1B[31m[错误] flutter create 执行失败:\n${result.stderr}\x1B[0m');
exit(1);
}
}
Future<void> _addCoreDependency(String appDir) async {
print('\x1B[34m[信息] 正在添加 web_shell_core 依赖...\x1B[0m');
final ProcessResult result = await Process.run('flutter', [
'pub',
'add',
'web_shell_core',
'--path',
'../../packages/web_shell_core',
], workingDirectory: appDir);
if (result.exitCode != 0) {
print(
'\x1B[31m[错误] 添加依赖失败:\n${result.stderr}\x1B[0m',
);
exit(1);
}
}
Future<void> _overwriteMainActivity(String appDir, String applicationId) async {
print('\x1B[34m[信息] 正在注入 CoreShellActivity 继承关系...\x1B[0m');
final String mainActivityPath =
"$appDir/android/app/src/main/java/${applicationId.replaceAll('.', '/')}/MainActivity.java";
final File mainActivityFile = File(mainActivityPath);
if (!mainActivityFile.existsSync()) {
print(
'\x1B[31m[错误] 未找到 MainActivity.java${mainActivityFile.path}\x1B[0m',
);
// Flutter 可能会按配置生成 Kotlin这里同时兼容两种情况。
final String ktPath =
"$appDir/android/app/src/main/kotlin/${applicationId.replaceAll('.', '/')}/MainActivity.kt";
final File ktFile = File(ktPath);
if (ktFile.existsSync()) {
ktFile.deleteSync(); // 这里直接重新生成 Java 版本。
} else {
print('\x1B[31m[错误] Kotlin 版本也未找到。\x1B[0m');
exit(1);
}
}
// 准备 Java 文件内容
final String javaContent =
'''
package $applicationId;
import com.yuanxuan.webshell.core.web_shell_core.CoreShellActivity;
public class MainActivity extends CoreShellActivity {
}
''';
await File(mainActivityPath).create(recursive: true);
await File(mainActivityPath).writeAsString(javaContent);
}
Future<void> _overwriteManifestLabel(String appDir, String appName) async {
print('\x1B[34m[信息] 正在更新 AndroidManifest.xml 应用名...\x1B[0m');
final File manifestFile = File(
'$appDir/android/app/src/main/AndroidManifest.xml',
);
String content = await manifestFile.readAsString();
// 使用简单正则替换 android:label="..."
content = content.replaceAll(
RegExp(r'android:label="[^"]*"'),
'android:label="$appName"',
);
await manifestFile.writeAsString(content);
}
Future<void> _generateDartEntrypoint(
String appDir,
String appName,
String appKey,
String accentColor,
String bgColor,
String textColor,
String mutedTextColor,
) async {
print('\x1B[34m[信息] 正在生成 lib/main.dart...\x1B[0m');
final File mainFile = File('$appDir/lib/main.dart');
final String dartContent =
'''
import 'package:flutter/material.dart';
import 'package:web_shell_core/web_shell_core.dart';
void main() {
runShellApp(
ShellEnvironment(
appName: '$appName',
appKey: '$appKey',
accentColor: const Color($accentColor),
backgroundColor: const Color($bgColor),
textColor: const Color($textColor),
mutedTextColor: const Color($mutedTextColor),
),
);
}
''';
await mainFile.writeAsString(dartContent);
}
Future<void> _generateBrandingAssets(
String brand,
String appDir,
YamlMap config,
) async {
print('\x1B[34m[信息] 正在配置图标与启动页...\x1B[0m');
if (config['branding'] == null) {
print(
'\x1B[33m[警告] 配置中未找到 branding 段,跳过资源生成。\x1B[0m',
);
return;
}
final YamlMap branding = config['branding'] as YamlMap;
// 在 appDir 下创建 flutter_launcher_icons.yaml
final String iconsYaml =
'''
flutter_launcher_icons:
android: true
image_path: "${branding['icon']}"
adaptive_icon_background: "${branding['icon_background']}"
adaptive_icon_foreground: "${branding['icon_foreground']}"
''';
await File(
'$appDir/flutter_launcher_icons-config.yaml',
).writeAsString(iconsYaml);
// 在 appDir 下创建 flutter_native_splash.yaml
final String splashYaml =
'''
flutter_native_splash:
color: "${branding['splash_color']}"
image: "${branding['splash']}"
android_12:
image: "${branding['splash']}"
icon_background_color: "${branding['splash_color']}"
''';
await File(
'$appDir/flutter_native_splash-config.yaml',
).writeAsString(splashYaml);
// 复制品牌资源文件
print('\x1B[34m[信息] 正在复制资源...\x1B[0m');
final Directory brandingDir = Directory('assets/branding/$brand');
if (brandingDir.existsSync()) {
final Directory targetDir = Directory('$appDir/assets/branding/$brand');
await targetDir.create(recursive: true);
for (final entity in brandingDir.listSync(recursive: true)) {
if (entity is File) {
final relativePath = entity.path.substring(brandingDir.path.length + 1);
final targetFile = File('${targetDir.path}/$relativePath');
await targetFile.parent.create(recursive: true);
await entity.copy(targetFile.path);
}
}
}
print('\x1B[34m[信息] 正在执行资源生成器...\x1B[0m');
// 执行 flutter_launcher_icons
await Process.run('dart', [
'run',
'flutter_launcher_icons',
'-f',
'flutter_launcher_icons-config.yaml',
], workingDirectory: appDir);
// 执行 flutter_native_splash
await Process.run('dart', [
'run',
'flutter_native_splash:create',
'--path=flutter_native_splash-config.yaml',
], workingDirectory: appDir);
}