feat:1.generate_app 兼容windows;2.支持windows 打包apk。

This commit is contained in:
TangJo 2026-03-23 16:34:02 +08:00
parent 435d99772c
commit 0a17e1c4f7
43 changed files with 400 additions and 42 deletions

View File

@ -45,31 +45,56 @@ web_android_shell/
### 运行已有品牌
建议优先使用仓库内 `.fvm` 的 Flutter / Dart避免全局版本不一致。
Windows PowerShell
```powershell
cd apps/aixue
..\..\.fvm\flutter_sdk\bin\flutter.bat run
```
macOS / zsh
```bash
cd apps/aixue
flutter run
../../.fvm/flutter_sdk/bin/flutter run
```
也可以替换为:
```bash
```powershell
cd apps/test
flutter run
..\..\.fvm\flutter_sdk\bin\flutter.bat run
```
```bash
cd apps/yunxiao
flutter run
../../.fvm/flutter_sdk/bin/flutter run
```
### 生成或重建品牌应用
```bash
dart run tool/generate_app.dart aixue
dart run tool/generate_app.dart test
dart run tool/generate_app.dart yunxiao
请在仓库根目录执行。
Windows PowerShell
```powershell
.\.fvm\flutter_sdk\bin\dart.bat run tool\generate_app.dart aixue
.\.fvm\flutter_sdk\bin\dart.bat run tool\generate_app.dart test
.\.fvm\flutter_sdk\bin\dart.bat run tool\generate_app.dart yunxiao
```
macOS / zsh
```bash
./.fvm/flutter_sdk/bin/dart run tool/generate_app.dart aixue
./.fvm/flutter_sdk/bin/dart run tool/generate_app.dart test
./.fvm/flutter_sdk/bin/dart run tool/generate_app.dart yunxiao
```
注意:生成脚本会删除并重建已有的 `apps/<品牌名>/` 目录。
生成脚本会自动完成:
- 创建 Flutter Android 应用到 `apps/<品牌名>/`

View File

@ -5,15 +5,40 @@ plugins {
id("dev.flutter.flutter-gradle-plugin")
}
import java.io.File
import java.util.Properties
import java.io.FileInputStream
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
FileInputStream(keystorePropertiesFile).use { keystoreProperties.load(it) }
}
val defaultReleaseStoreFile = rootProject.file("../../../tool/key.jks")
fun resolveKeystoreFile(rawPath: String?): File {
val candidate = rawPath?.trim().orEmpty()
if (candidate.isEmpty()) {
return defaultReleaseStoreFile
}
val expandedHome = if (candidate.startsWith("~/") || candidate == "~") {
candidate.replaceFirst("~", System.getProperty("user.home"))
} else {
candidate
}
val normalized = expandedHome.replace('\\', File.separatorChar).replace('/', File.separatorChar)
val storeFile = File(normalized)
return if (storeFile.isAbsolute) storeFile else rootProject.file(normalized)
}
val releaseStoreFile =
resolveKeystoreFile(keystoreProperties["storeFile"] as String?)
val releaseKeyAlias = keystoreProperties["keyAlias"] as String? ?: "my-key-alias"
val releaseKeyPassword = keystoreProperties["keyPassword"] as String? ?: "123456"
val releaseStorePassword = keystoreProperties["storePassword"] as String? ?: "123456"
android {
namespace = "com.yuanxuan.aixue"
@ -41,10 +66,10 @@ android {
}
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String?
keyPassword = keystoreProperties["keyPassword"] as String?
storeFile = keystoreProperties["storeFile"]?.let { file(it as String) }
storePassword = keystoreProperties["storePassword"] as String?
keyAlias = releaseKeyAlias
keyPassword = releaseKeyPassword
storeFile = releaseStoreFile
storePassword = releaseStorePassword
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 490 KiB

View File

@ -4,4 +4,4 @@
"portraitUp",
"portraitDown"
]
}
}

View File

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View File

@ -5,15 +5,40 @@ plugins {
id("dev.flutter.flutter-gradle-plugin")
}
import java.io.File
import java.util.Properties
import java.io.FileInputStream
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
FileInputStream(keystorePropertiesFile).use { keystoreProperties.load(it) }
}
val defaultReleaseStoreFile = rootProject.file("../../../tool/key.jks")
fun resolveKeystoreFile(rawPath: String?): File {
val candidate = rawPath?.trim().orEmpty()
if (candidate.isEmpty()) {
return defaultReleaseStoreFile
}
val expandedHome = if (candidate.startsWith("~/") || candidate == "~") {
candidate.replaceFirst("~", System.getProperty("user.home"))
} else {
candidate
}
val normalized = expandedHome.replace('\\', File.separatorChar).replace('/', File.separatorChar)
val storeFile = File(normalized)
return if (storeFile.isAbsolute) storeFile else rootProject.file(normalized)
}
val releaseStoreFile =
resolveKeystoreFile(keystoreProperties["storeFile"] as String?)
val releaseKeyAlias = keystoreProperties["keyAlias"] as String? ?: "my-key-alias"
val releaseKeyPassword = keystoreProperties["keyPassword"] as String? ?: "123456"
val releaseStorePassword = keystoreProperties["storePassword"] as String? ?: "123456"
android {
namespace = "com.yuanxuan.test_shell"
@ -41,10 +66,10 @@ android {
}
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String?
keyPassword = keystoreProperties["keyPassword"] as String?
storeFile = keystoreProperties["storeFile"]?.let { file(it as String) }
storePassword = keystoreProperties["storePassword"] as String?
keyAlias = releaseKeyAlias
keyPassword = releaseKeyPassword
storeFile = releaseStoreFile
storePassword = releaseStorePassword
}
}

View File

@ -1,5 +1,5 @@
{
"initialUrl": "http://192.168.2.57:8080/test_bridge.html",
"initialUrl": "http://192.168.2.54:8080/test_bridge.html",
"preferredOrientations": [
"portraitUp",
"portraitDown"

View File

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View File

@ -5,15 +5,40 @@ plugins {
id("dev.flutter.flutter-gradle-plugin")
}
import java.io.File
import java.util.Properties
import java.io.FileInputStream
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
FileInputStream(keystorePropertiesFile).use { keystoreProperties.load(it) }
}
val defaultReleaseStoreFile = rootProject.file("../../../tool/key.jks")
fun resolveKeystoreFile(rawPath: String?): File {
val candidate = rawPath?.trim().orEmpty()
if (candidate.isEmpty()) {
return defaultReleaseStoreFile
}
val expandedHome = if (candidate.startsWith("~/") || candidate == "~") {
candidate.replaceFirst("~", System.getProperty("user.home"))
} else {
candidate
}
val normalized = expandedHome.replace('\\', File.separatorChar).replace('/', File.separatorChar)
val storeFile = File(normalized)
return if (storeFile.isAbsolute) storeFile else rootProject.file(normalized)
}
val releaseStoreFile =
resolveKeystoreFile(keystoreProperties["storeFile"] as String?)
val releaseKeyAlias = keystoreProperties["keyAlias"] as String? ?: "my-key-alias"
val releaseKeyPassword = keystoreProperties["keyPassword"] as String? ?: "123456"
val releaseStorePassword = keystoreProperties["storePassword"] as String? ?: "123456"
android {
namespace = "com.yuanxuan.yunxiao"
@ -41,10 +66,10 @@ android {
}
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String?
keyPassword = keystoreProperties["keyPassword"] as String?
storeFile = keystoreProperties["storeFile"]?.let { file(it as String) }
storePassword = keystoreProperties["storePassword"] as String?
keyAlias = releaseKeyAlias
keyPassword = releaseKeyPassword
storeFile = releaseStoreFile
storePassword = releaseStorePassword
}
}

View File

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 490 KiB

View File

@ -5,6 +5,39 @@ plugins {
id("dev.flutter.flutter-gradle-plugin")
}
import java.io.File
import java.io.FileInputStream
import java.util.Properties
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
FileInputStream(keystorePropertiesFile).use { keystoreProperties.load(it) }
}
val defaultReleaseStoreFile = rootProject.file("../../../tool/key.jks")
fun resolveKeystoreFile(rawPath: String?): File {
val candidate = rawPath?.trim().orEmpty()
if (candidate.isEmpty()) {
return defaultReleaseStoreFile
}
val expandedHome = if (candidate.startsWith("~/") || candidate == "~") {
candidate.replaceFirst("~", System.getProperty("user.home"))
} else {
candidate
}
val normalized = expandedHome.replace('\\', File.separatorChar).replace('/', File.separatorChar)
val storeFile = File(normalized)
return if (storeFile.isAbsolute) storeFile else rootProject.file(normalized)
}
val releaseStoreFile = resolveKeystoreFile(keystoreProperties["storeFile"] as String?)
val releaseKeyAlias = keystoreProperties["keyAlias"] as String? ?: "my-key-alias"
val releaseKeyPassword = keystoreProperties["keyPassword"] as String? ?: "123456"
val releaseStorePassword = keystoreProperties["storePassword"] as String? ?: "123456"
android {
namespace = "com.yuanxuan.webshell.web_android_shell"
compileSdk = flutter.compileSdkVersion
@ -31,11 +64,18 @@ android {
versionName = flutter.versionName
}
signingConfigs {
create("release") {
keyAlias = releaseKeyAlias
keyPassword = releaseKeyPassword
storeFile = releaseStoreFile
storePassword = releaseStorePassword
}
}
buildTypes {
release {
// 待补充:为 release 构建配置正式签名。
// 当前先使用 debug 签名,确保 `flutter run --release` 可直接运行。
signingConfig = signingConfigs.getByName("debug")
signingConfig = signingConfigs.getByName("release")
}
}
}

View File

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View File

@ -8,8 +8,14 @@ const List<String> _defaultPreferredOrientations = <String>[
'portraitUp',
'portraitDown',
];
final Directory _repoRootDir = File.fromUri(Platform.script).parent.parent;
final String _currentDartExecutable = Platform.resolvedExecutable;
final String _flutterExecutable = _resolveFlutterExecutable();
final String _dartExecutable = _resolveDartExecutable();
Future<void> main(List<String> args) async {
Directory.current = _repoRootDir.path;
if (args.isEmpty) {
print('\x1B[31m用法dart run tool/generate_app.dart <品牌名>\x1B[0m');
print('\x1B[33m示例dart run tool/generate_app.dart aixue\x1B[0m');
@ -29,6 +35,8 @@ Future<void> main(List<String> args) async {
}
print('\x1B[34m[信息] 正在为品牌生成应用:$brand...\x1B[0m');
print('\x1B[34m[信息] Flutter 命令:$_flutterExecutable\x1B[0m');
print('\x1B[34m[信息] Dart 命令:$_dartExecutable\x1B[0m');
final yamlString = await configFile.readAsString();
final config = loadYaml(yamlString) as YamlMap;
@ -77,7 +85,180 @@ Future<void> main(List<String> args) async {
print('\x1B[32m✔ 应用 $brand 已生成到 $appDir\x1B[0m');
print('\x1B[34m构建应用请执行\x1B[0m');
print(' cd $appDir && flutter build apk');
print(' cd $appDir');
print(' ${_buildApkCommandForCurrentPlatform()}');
}
String _resolveFlutterExecutable() {
final candidates = <String>[
..._toolCandidatesFromFlutterRoot(_flutterToolRelativePaths),
..._toolCandidatesFromRepo(_flutterToolRelativePaths),
..._flutterCandidatesFromCurrentDart(),
'flutter',
];
return _firstAvailableExecutable(candidates, fallback: 'flutter');
}
String _resolveDartExecutable() {
final candidates = <String>[
..._toolCandidatesFromFlutterRoot(_dartToolRelativePaths),
..._toolCandidatesFromRepo(_dartToolRelativePaths),
_currentDartExecutable,
'dart',
];
return _firstAvailableExecutable(candidates, fallback: 'dart');
}
List<String> _flutterCandidatesFromCurrentDart() {
final dartExecutable = File(_currentDartExecutable);
final dartBinDir = dartExecutable.parent;
final dartSdkDir = dartBinDir.parent;
final cacheDir = dartSdkDir.parent;
final flutterBinDir = cacheDir.parent;
if (_basename(dartSdkDir.path) != 'dart-sdk' ||
_basename(cacheDir.path) != 'cache' ||
!_isSameDirectoryName(flutterBinDir.path, 'bin')) {
return <String>[];
}
final flutterBat = File.fromUri(flutterBinDir.uri.resolve('flutter.bat'));
final flutterShell = File.fromUri(flutterBinDir.uri.resolve('flutter'));
return <String>[
if (flutterBat.existsSync()) flutterBat.path,
if (flutterShell.existsSync()) flutterShell.path,
];
}
List<String> get _flutterToolRelativePaths => Platform.isWindows
? <String>['bin/flutter.bat', 'bin/flutter']
: <String>['bin/flutter', 'bin/flutter.bat'];
List<String> get _dartToolRelativePaths => Platform.isWindows
? <String>[
'bin/dart.bat',
'bin/cache/dart-sdk/bin/dart.exe',
'bin/dart',
'bin/cache/dart-sdk/bin/dart',
]
: <String>['bin/dart', 'bin/cache/dart-sdk/bin/dart'];
List<String> _toolCandidatesFromFlutterRoot(List<String> relativePaths) {
final flutterRoot = Platform.environment['FLUTTER_ROOT'];
if (flutterRoot == null || flutterRoot.isEmpty) {
return <String>[];
}
return <String>[
for (final relativePath in relativePaths)
if (_fileExists(flutterRoot, relativePath))
_joinPath(flutterRoot, relativePath),
];
}
List<String> _toolCandidatesFromRepo(List<String> relativePaths) {
return <String>[
for (final relativePath in relativePaths)
if (_repoFile(relativePath).existsSync()) _repoFile(relativePath).path,
];
}
String _firstAvailableExecutable(
List<String> candidates, {
required String fallback,
}) {
for (final candidate in candidates) {
if (candidate == fallback || File(candidate).existsSync()) {
return candidate;
}
}
return fallback;
}
bool _fileExists(String? basePath, String childPath) {
if (basePath == null || basePath.isEmpty) {
return false;
}
return File(_joinPath(basePath, childPath)).existsSync();
}
String _joinPath(String basePath, String childPath) {
final normalizedBase = basePath.replaceAll('\\', '/');
final normalizedChild = childPath.replaceAll('\\', '/');
return File.fromUri(
Directory(normalizedBase).uri.resolve(normalizedChild),
).path;
}
File _repoFile(String relativePath) {
return File.fromUri(_repoRootDir.uri.resolve(relativePath));
}
String _basename(String path) {
final normalized = path
.replaceAll('\\', '/')
.replaceFirst(RegExp(r'/+$'), '');
final segments = normalized.split('/');
return segments.isEmpty ? normalized : segments.last;
}
bool _isSameDirectoryName(String path, String name) {
return _basename(path).toLowerCase() == name.toLowerCase();
}
String _buildApkCommandForCurrentPlatform() {
final fvmFlutter = _repoFile(
Platform.isWindows
? '.fvm/flutter_sdk/bin/flutter.bat'
: '.fvm/flutter_sdk/bin/flutter',
);
if (fvmFlutter.existsSync()) {
final commandPath = Platform.isWindows
? '../../.fvm/flutter_sdk/bin/flutter.bat'
: '../../.fvm/flutter_sdk/bin/flutter';
return '$commandPath build apk';
}
return 'flutter build apk';
}
Future<ProcessResult> _runProcess(
String executable,
List<String> arguments, {
String? workingDirectory,
}) async {
try {
return await Process.run(
executable,
arguments,
workingDirectory: workingDirectory,
runInShell:
Platform.isWindows && executable.toLowerCase().endsWith('.bat'),
);
} on ProcessException catch (error) {
print('\x1B[31m[错误] 外部命令启动失败:$executable\x1B[0m');
print('\x1B[33m命令参数${arguments.join(' ')}\x1B[0m');
print('\x1B[33m系统信息${error.message}\x1B[0m');
if (executable == _flutterExecutable || executable == 'flutter') {
print('\x1B[33m建议先执行\x1B[0m');
if (Platform.isWindows) {
print(
' \$env:PATH = "${_repoFile('.fvm/flutter_sdk/bin').path};\$env:PATH"',
);
} else {
print(
' export PATH="${_repoFile('.fvm/flutter_sdk/bin').path}:\$PATH"',
);
}
}
exit(1);
}
}
List<String> _readPreferredOrientations(Object? raw) {
@ -123,7 +304,7 @@ Future<void> _createFlutterApp(
final segments = applicationId.split('.');
final org = segments.sublist(0, segments.length - 1).join('.');
final result = await Process.run('flutter', <String>[
final result = await _runProcess(_flutterExecutable, <String>[
'create',
'--org',
org,
@ -144,7 +325,7 @@ Future<void> _createFlutterApp(
Future<void> _addCoreDependency(String appDir) async {
print('\x1B[34m[信息] 正在添加 web_shell_core 依赖...\x1B[0m');
final result = await Process.run('flutter', <String>[
final result = await _runProcess(_flutterExecutable, <String>[
'pub',
'add',
'web_shell_core',
@ -316,7 +497,7 @@ Future<void> _generateBrandingAssets(
print('\x1B[32m✔ 品牌资源已复制到 ${brandTargetDir.path}\x1B[0m');
print('\x1B[34m[信息] 正在添加资源生成器依赖...\x1B[0m');
final addDevDepsResult = await Process.run('flutter', <String>[
final addDevDepsResult = await _runProcess(_flutterExecutable, <String>[
'pub',
'add',
'--dev',
@ -328,7 +509,7 @@ Future<void> _generateBrandingAssets(
exit(1);
}
await Process.run('flutter', <String>[
await _runProcess(_flutterExecutable, <String>[
'pub',
'get',
], workingDirectory: appDir);
@ -360,7 +541,7 @@ flutter_native_splash:
await File('$appDir/flutter_native_splash.yaml').writeAsString(splashYaml);
print('\x1B[34m[信息] 正在生成应用图标...\x1B[0m');
final iconsResult = await Process.run('dart', <String>[
final iconsResult = await _runProcess(_dartExecutable, <String>[
'run',
'flutter_launcher_icons',
'-f',
@ -373,7 +554,7 @@ flutter_native_splash:
}
print('\x1B[34m[信息] 正在生成启动页...\x1B[0m');
final splashResult = await Process.run('dart', <String>[
final splashResult = await _runProcess(_dartExecutable, <String>[
'run',
'flutter_native_splash:create',
'--path=flutter_native_splash.yaml',
@ -385,7 +566,7 @@ flutter_native_splash:
}
print('\x1B[34m[信息] 正在移除资源生成器依赖...\x1B[0m');
await Process.run('flutter', <String>[
await _runProcess(_flutterExecutable, <String>[
'pub',
'remove',
'flutter_launcher_icons',
@ -428,7 +609,7 @@ Future<void> _configureSigning(String appDir, YamlMap config) async {
final keyPassword = signing?['key_password'] as String? ?? '123456';
final storePassword = signing?['store_password'] as String? ?? '123456';
final storeFile =
signing?['store_file'] as String? ?? '../../../../tool/key.jks';
signing?['store_file'] as String? ?? '../../../tool/key.jks';
final keyPropsContent =
'''
@ -448,15 +629,40 @@ storeFile=$storeFile
var content = await gradleFile.readAsString();
if (!content.contains('val keystoreProperties = Properties()')) {
content = content.replaceFirst('android {', '''
import java.io.File
import java.util.Properties
import java.io.FileInputStream
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
FileInputStream(keystorePropertiesFile).use { keystoreProperties.load(it) }
}
val defaultReleaseStoreFile = rootProject.file("../../../tool/key.jks")
fun resolveKeystoreFile(rawPath: String?): File {
val candidate = rawPath?.trim().orEmpty()
if (candidate.isEmpty()) {
return defaultReleaseStoreFile
}
val expandedHome = if (candidate.startsWith("~/") || candidate == "~") {
candidate.replaceFirst("~", System.getProperty("user.home"))
} else {
candidate
}
val normalized = expandedHome.replace('\\', File.separatorChar).replace('/', File.separatorChar)
val storeFile = File(normalized)
return if (storeFile.isAbsolute) storeFile else rootProject.file(normalized)
}
val releaseStoreFile =
resolveKeystoreFile(keystoreProperties["storeFile"] as String?)
val releaseKeyAlias = keystoreProperties["keyAlias"] as String? ?: "my-key-alias"
val releaseKeyPassword = keystoreProperties["keyPassword"] as String? ?: "123456"
val releaseStorePassword = keystoreProperties["storePassword"] as String? ?: "123456"
android {
''');
}
@ -465,10 +671,10 @@ android {
const newBuildTypes = '''
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String?
keyPassword = keystoreProperties["keyPassword"] as String?
storeFile = keystoreProperties["storeFile"]?.let { file(it as String) }
storePassword = keystoreProperties["storePassword"] as String?
keyAlias = releaseKeyAlias
keyPassword = releaseKeyPassword
storeFile = releaseStoreFile
storePassword = releaseStorePassword
}
}