import 'dart:io'; import 'package:yaml/yaml.dart'; Future main(List 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 _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 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 _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 _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 _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 _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 _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); }