244 lines
8.0 KiB
Dart
244 lines
8.0 KiB
Dart
import 'dart:io';
|
|
import 'package:yaml/yaml.dart';
|
|
|
|
Future<void> main(List<String> args) async {
|
|
if (args.isEmpty) {
|
|
print('\x1BM[31mUsage: dart run tool/generate_app.dart <brand_name>\x1B[0m');
|
|
print('\x1BM[33mExample: 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('\x1BM[31m[Error] Configuration file not found: ${configFile.path}\x1B[0m');
|
|
exit(1);
|
|
}
|
|
|
|
print('\x1BM[34m[Info] Generating app for brand: $brand...\x1B[0m');
|
|
|
|
// 1. Parse YAML Configuration
|
|
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✔ Configuration loaded successfully.\x1B[0m');
|
|
|
|
// 2. Create the Flutter App
|
|
await _createFlutterApp(brand, appDir, applicationId);
|
|
|
|
// 3. Add Core Dependency
|
|
await _addCoreDependency(appDir);
|
|
|
|
// 4. Overwrite MainActivity.java to extend CoreShellActivity
|
|
await _overwriteMainActivity(appDir, applicationId);
|
|
|
|
// 5. Overwrite AndroidManifest.xml for Label
|
|
await _overwriteManifestLabel(appDir, appName);
|
|
|
|
// 6. Generate lib/main.dart
|
|
await _generateDartEntrypoint(
|
|
appDir,
|
|
appName,
|
|
appKey,
|
|
accentColor,
|
|
bgColor,
|
|
textColor,
|
|
mutedTextColor
|
|
);
|
|
|
|
// 7. Generate Icons and Splash
|
|
await _generateBrandingAssets(brand, appDir, config);
|
|
|
|
print('\x1B[32m✔ App $brand generated successfully at $appDir!\x1B[0m');
|
|
print('\x1B[34mTo build the app:\x1B[0m');
|
|
print(' cd $appDir && flutter build apk');
|
|
}
|
|
|
|
Future<void> _createFlutterApp(String brand, String appDir, String applicationId) async {
|
|
print('\x1BM[34m[Info] Running flutter create...\x1B[0m');
|
|
final Directory dir = Directory(appDir);
|
|
if (dir.existsSync()) {
|
|
print('\x1BM[33m[Warning] Directory $appDir already exists. Cleaning up...\x1B[0m');
|
|
dir.deleteSync(recursive: true);
|
|
}
|
|
|
|
// Extract org
|
|
// e.g., 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,ios',
|
|
appDir,
|
|
]);
|
|
|
|
if (result.exitCode != 0) {
|
|
print('\x1BM[31m[Error] flutter create failed:\n${result.stderr}\x1B[0m');
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
Future<void> _addCoreDependency(String appDir) async {
|
|
print('\x1BM[34m[Info] Adding web_shell_core dependency...\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('\x1BM[31m[Error] Failed to add dependency:\n${result.stderr}\x1B[0m');
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
Future<void> _overwriteMainActivity(String appDir, String applicationId) async {
|
|
print('\x1BM[34m[Info] Injecting CoreShellActivity inheritance...\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[Error] MainActivity.java not found at ${mainActivityFile.path}\x1B[0m');
|
|
// Flutter might have generated Kotlin depending on settings. Let's handle both.
|
|
final String ktPath = "$appDir/android/app/src/main/kotlin/${applicationId.replaceAll('.', '/')}/MainActivity.kt";
|
|
final File ktFile = File(ktPath);
|
|
if (ktFile.existsSync()) {
|
|
ktFile.deleteSync(); // We just recreate the java one.
|
|
} else {
|
|
print('\x1B[31m[Error] Not found Kotlin either.\x1B[0m');
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
// Prepare the Java file
|
|
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('\x1BM[34m[Info] Updating AndroidManifest.xml label...\x1B[0m');
|
|
final File manifestFile = File('$appDir/android/app/src/main/AndroidManifest.xml');
|
|
String content = await manifestFile.readAsString();
|
|
|
|
// A simple regex to replace 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('\x1BM[34m[Info] Generating 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('\x1BM[34m[Info] Configuring icons and splash screens...\x1B[0m');
|
|
if (config['branding'] == null) {
|
|
print('\x1BM[33m[Warning] No branding section found in config. Skipping asset generation.\x1B[0m');
|
|
return;
|
|
}
|
|
final YamlMap branding = config['branding'] as YamlMap;
|
|
|
|
// Create flutter_launcher_icons.yaml in appDir
|
|
final String iconsYaml = '''
|
|
flutter_launcher_icons:
|
|
android: true
|
|
ios: 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);
|
|
|
|
// Create flutter_native_splash.yaml in appDir
|
|
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);
|
|
|
|
// Copy branding assets
|
|
print('\x1BM[34m[Info] Copying assets...\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('\x1BM[34m[Info] Running asset generators...\x1B[0m');
|
|
|
|
// Run flutter_launcher_icons
|
|
await Process.run('dart', [
|
|
'run', 'flutter_launcher_icons', '-f', 'flutter_launcher_icons-config.yaml'
|
|
], workingDirectory: appDir);
|
|
|
|
// Run flutter_native_splash
|
|
await Process.run('dart', [
|
|
'run', 'flutter_native_splash:create', '--path=flutter_native_splash-config.yaml'
|
|
], workingDirectory: appDir);
|
|
}
|