diff --git a/tool/brand.dart b/tool/brand.dart new file mode 100644 index 0000000..165fdf7 --- /dev/null +++ b/tool/brand.dart @@ -0,0 +1,143 @@ +import 'dart:io'; + +const appName = 'ACME Client'; +const packageId = 'com.acme.client'; +const version = '1.0.0+1'; +const url = "https://example.com"; +final iconPath = '${Platform.environment['HOME']}/Downloads/icon.png'; + +const keystoreFilePath = 'android/android.keystore'; +const keystoreAlias = 'key'; +const keystorePassword = 'password'; + +Future main() async { + await _generateIcons(iconPath); + await _updateTitle(appName); + await _updatePackageId(packageId); + await _updateVersion(version); + await _updateUrl(url); + await _createKeystore(); + stdout.writeln('Please run `flutterfire configure` now.'); +} + +Future _generateIcons(String icon) async { + final dir = Directory('ios/Runner/Assets.xcassets/AppIcon.appiconset'); + dir.deleteSync(recursive: true); + dir.createSync(); + + File('android/app/src/main/res/drawable/ic_launcher_foreground.xml').delete(); + File('android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml').delete(); + + final f = await _writeTempYaml('flutter_launcher_icons.yaml', ''' +flutter_launcher_icons: + android: true + ios: true + image_path: "$icon" + remove_alpha_ios: true +'''); + await _run('flutter', ['pub', 'run', 'flutter_launcher_icons']); + await f.delete(); + + await _replaceInFile( + 'android/app/src/main/AndroidManifest.xml', + RegExp(r'\s*', multiLine: true), + '', + ); +} + +Future _updateTitle(String name) async { + await _replaceInFile( + 'android/app/src/main/AndroidManifest.xml', + RegExp(r'android:label="[^"]*"'), + 'android:label="$name"', + ); + + await _replaceInFile( + 'ios/Runner/Info.plist', + RegExp(r'CFBundleDisplayName\s*.*?'), + 'CFBundleDisplayName\n\t$name', + ); +} + +Future _updatePackageId(String id) async { + await _replaceInFile( + 'android/app/build.gradle.kts', + RegExp(r'applicationId = ".*?"'), + 'applicationId = "$id"', + ); + + await _replaceInFile( + 'ios/Runner.xcodeproj/project.pbxproj', + RegExp(r'PRODUCT_BUNDLE_IDENTIFIER = org\.traccar.*?;'), + 'PRODUCT_BUNDLE_IDENTIFIER = $id;', + ); +} + +Future _updateVersion(String version) async { + await _replaceInFile( + 'pubspec.yaml', + RegExp(r'^version:\s*.*$', multiLine: true), + 'version: $version', + ); +} + +Future _updateUrl(String url) async { + await _replaceInFile( + 'lib/screens/main_screen.dart', + RegExp(r'https://demo\.traccar\.org'), + url, + ); +} + +Future _createKeystore() async { + final args = [ + '-genkeypair', + '-v', + '-keystore', keystoreFilePath, + '-alias', keystoreAlias, + '-keyalg', 'RSA', + '-keysize', '2048', + '-validity', '10000', + '-storepass', keystorePassword, + '-keypass', keystorePassword, + '-dname', 'CN=Brand, OU=Dev, O=Company, L=City, S=State, C=US', + ]; + await _run('keytool', args); + + final file = File('android/key.properties'); + final content = ''' +storePassword=$keystorePassword +keyPassword=$keystorePassword +keyAlias=$keystoreAlias +storeFile=../../$keystoreFilePath +'''; + await file.writeAsString(content); + + await _replaceInFile( + 'android/app/build.gradle.kts', + RegExp(r'val\s+keystorePropertiesFile\s*=.*'), + 'val keystorePropertiesFile = rootProject.file("key.properties")', + ); +} + +Future _writeTempYaml(String name, String content) async { + final file = File(name); + await file.writeAsString(content); + return file; +} + +Future _replaceInFile(String path, RegExp pattern, String replacement) async { + final file = File(path); + if (!await file.exists()) return; + final text = await file.readAsString(); + final newText = text.replaceAll(pattern, replacement); + if (newText != text) await file.writeAsString(newText); +} + +Future _run(String cmd, List args) async { + final proc = await Process.start(cmd, args); + await stdout.addStream(proc.stdout); + await stderr.addStream(proc.stderr); + final code = await proc.exitCode; + if (code != 0) throw Exception('$cmd ${args.join(" ")} failed ($code)'); +}