@@ -15,6 +15,7 @@ import '../base/process.dart';
15
15
import '../base/utils.dart' ;
16
16
import '../build_info.dart' ;
17
17
import '../convert.dart' ;
18
+ import '../doctor_validator.dart' ;
18
19
import '../globals.dart' as globals;
19
20
import '../ios/application_package.dart' ;
20
21
import '../ios/mac.dart' ;
@@ -277,7 +278,27 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
277
278
.toList ();
278
279
}
279
280
280
- Future <void > _validateIconAssetsAfterArchive (StringBuffer messageBuffer) async {
281
+ ValidationResult ? _createValidationResult (String title, List <ValidationMessage > messages) {
282
+ if (messages.isEmpty) {
283
+ return null ;
284
+ }
285
+ final bool anyInvalid = messages.any ((ValidationMessage message) => message.type != ValidationMessageType .information);
286
+ return ValidationResult (
287
+ anyInvalid ? ValidationType .partial : ValidationType .success,
288
+ messages,
289
+ statusInfo: title,
290
+ );
291
+ }
292
+
293
+ ValidationMessage _createValidationMessage ({
294
+ required bool isValid,
295
+ required String message,
296
+ }) {
297
+ // Use "information" type for valid message, and "hint" type for invalid message.
298
+ return isValid ? ValidationMessage (message) : ValidationMessage .hint (message);
299
+ }
300
+
301
+ Future <List <ValidationMessage >> _validateIconAssetsAfterArchive () async {
281
302
final BuildableIOSApp app = await buildableIOSApp;
282
303
283
304
final Map <_ImageAssetFileKey , String > templateInfoMap = _parseImageAssetContentsJson (
@@ -287,24 +308,35 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
287
308
app.projectAppIconDirName,
288
309
requiresSize: true );
289
310
311
+ final List <ValidationMessage > validationMessages = < ValidationMessage > [];
312
+
290
313
final bool usesTemplate = _isAssetStillUsingTemplateFiles (
291
314
templateImageInfoMap: templateInfoMap,
292
315
projectImageInfoMap: projectInfoMap,
293
316
templateImageDirName: await app.templateAppIconDirNameForImages,
294
317
projectImageDirName: app.projectAppIconDirName);
318
+
295
319
if (usesTemplate) {
296
- messageBuffer.writeln ('\n Warning: App icon is set to the default placeholder icon. Replace with unique icons.' );
320
+ validationMessages.add (_createValidationMessage (
321
+ isValid: false ,
322
+ message: 'App icon is set to the default placeholder icon. Replace with unique icons.' ,
323
+ ));
297
324
}
298
325
299
326
final List <String > filesWithWrongSize = _imageFilesWithWrongSize (
300
327
imageInfoMap: projectInfoMap,
301
328
imageDirName: app.projectAppIconDirName);
329
+
302
330
if (filesWithWrongSize.isNotEmpty) {
303
- messageBuffer.writeln ('\n Warning: App icon is using the wrong size (e.g. ${filesWithWrongSize .first }).' );
331
+ validationMessages.add (_createValidationMessage (
332
+ isValid: false ,
333
+ message: 'App icon is using the incorrect size (e.g. ${filesWithWrongSize .first }).' ,
334
+ ));
304
335
}
336
+ return validationMessages;
305
337
}
306
338
307
- Future <void > _validateLaunchImageAssetsAfterArchive (StringBuffer messageBuffer ) async {
339
+ Future <List < ValidationMessage >> _validateLaunchImageAssetsAfterArchive () async {
308
340
final BuildableIOSApp app = await buildableIOSApp;
309
341
310
342
final Map <_ImageAssetFileKey , String > templateInfoMap = _parseImageAssetContentsJson (
@@ -314,25 +346,32 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
314
346
app.projectLaunchImageDirName,
315
347
requiresSize: false );
316
348
349
+ final List <ValidationMessage > validationMessages = < ValidationMessage > [];
350
+
317
351
final bool usesTemplate = _isAssetStillUsingTemplateFiles (
318
352
templateImageInfoMap: templateInfoMap,
319
353
projectImageInfoMap: projectInfoMap,
320
354
templateImageDirName: await app.templateLaunchImageDirNameForImages,
321
355
projectImageDirName: app.projectLaunchImageDirName);
322
356
323
357
if (usesTemplate) {
324
- messageBuffer.writeln ('\n Warning: Launch image is set to the default placeholder. Replace with unique launch images.' );
358
+ validationMessages.add (_createValidationMessage (
359
+ isValid: false ,
360
+ message: 'Launch image is set to the default placeholder icon. Replace with unique launch image.' ,
361
+ ));
325
362
}
363
+
364
+ return validationMessages;
326
365
}
327
366
328
- Future <void > _validateXcodeBuildSettingsAfterArchive (StringBuffer messageBuffer ) async {
367
+ Future <List < ValidationMessage >> _validateXcodeBuildSettingsAfterArchive () async {
329
368
final BuildableIOSApp app = await buildableIOSApp;
330
369
331
370
final String plistPath = app.builtInfoPlistPathAfterArchive;
332
371
333
372
if (! globals.fs.file (plistPath).existsSync ()) {
334
373
globals.printError ('Invalid iOS archive. Does not contain Info.plist.' );
335
- return ;
374
+ return < ValidationMessage > [] ;
336
375
}
337
376
338
377
final Map <String , String ?> xcodeProjectSettingsMap = < String , String ? > {};
@@ -343,17 +382,32 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
343
382
xcodeProjectSettingsMap['Deployment Target' ] = globals.plistParser.getStringValueFromFile (plistPath, PlistParser .kMinimumOSVersionKey);
344
383
xcodeProjectSettingsMap['Bundle Identifier' ] = globals.plistParser.getStringValueFromFile (plistPath, PlistParser .kCFBundleIdentifierKey);
345
384
346
- xcodeProjectSettingsMap.forEach ((String title, String ? info) {
347
- messageBuffer.writeln ('$title : ${info ?? "Missing" }' );
348
- });
385
+ final List <ValidationMessage > validationMessages = xcodeProjectSettingsMap.entries.map ((MapEntry <String , String ?> entry) {
386
+ final String title = entry.key;
387
+ final String ? info = entry.value;
388
+ return _createValidationMessage (
389
+ isValid: info != null ,
390
+ message: '$title : ${info ?? "Missing" }' ,
391
+ );
392
+ }).toList ();
349
393
350
- if (xcodeProjectSettingsMap.values.any ((String ? element) => element == null )) {
351
- messageBuffer.writeln ('\n You must set up the missing settings.' );
394
+ final bool hasMissingSettings = xcodeProjectSettingsMap.values.any ((String ? element) => element == null );
395
+ if (hasMissingSettings) {
396
+ validationMessages.add (_createValidationMessage (
397
+ isValid: false ,
398
+ message: 'You must set up the missing app settings.' ),
399
+ );
352
400
}
353
401
354
- if (xcodeProjectSettingsMap['Bundle Identifier' ]? .startsWith ('com.example' ) ?? false ) {
355
- messageBuffer.writeln ('\n Warning: Your application still contains the default "com.example" bundle identifier.' );
402
+ final bool usesDefaultBundleIdentifier = xcodeProjectSettingsMap['Bundle Identifier' ]? .startsWith ('com.example' ) ?? false ;
403
+ if (usesDefaultBundleIdentifier) {
404
+ validationMessages.add (_createValidationMessage (
405
+ isValid: false ,
406
+ message: 'Your application still contains the default "com.example" bundle identifier.' ),
407
+ );
356
408
}
409
+
410
+ return validationMessages;
357
411
}
358
412
359
413
@override
@@ -362,13 +416,26 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
362
416
displayNullSafetyMode (buildInfo);
363
417
final FlutterCommandResult xcarchiveResult = await super .runCommand ();
364
418
365
- final StringBuffer validationMessageBuffer = StringBuffer ();
366
- await _validateXcodeBuildSettingsAfterArchive (validationMessageBuffer);
367
- await _validateIconAssetsAfterArchive (validationMessageBuffer);
368
- await _validateLaunchImageAssetsAfterArchive (validationMessageBuffer);
369
-
370
- validationMessageBuffer.write ('\n To update the settings, please refer to https://docs.flutter.dev/deployment/ios' );
371
- globals.printBox (validationMessageBuffer.toString (), title: 'App Settings' );
419
+ final List <ValidationResult ?> validationResults = < ValidationResult ? > [];
420
+ validationResults.add (_createValidationResult (
421
+ 'App Settings Validation' ,
422
+ await _validateXcodeBuildSettingsAfterArchive (),
423
+ ));
424
+ validationResults.add (_createValidationResult (
425
+ 'App Icon and Launch Image Assets Validation' ,
426
+ await _validateIconAssetsAfterArchive () + await _validateLaunchImageAssetsAfterArchive (),
427
+ ));
428
+
429
+ for (final ValidationResult result in validationResults.whereType <ValidationResult >()) {
430
+ globals.printStatus ('\n ${result .coloredLeadingBox } ${result .statusInfo }' );
431
+ for (final ValidationMessage message in result.messages) {
432
+ globals.printStatus (
433
+ '${message .coloredIndicator } ${message .message }' ,
434
+ indent: result.leadingBox.length + 1 ,
435
+ );
436
+ }
437
+ }
438
+ globals.printStatus ('\n To update the settings, please refer to https://docs.flutter.dev/deployment/ios\n ' );
372
439
373
440
// xcarchive failed or not at expected location.
374
441
if (xcarchiveResult.exitStatus != ExitStatus .success) {
0 commit comments