diff --git a/README.md b/README.md index b094fab868..0650989eab 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,8 @@ Shown below is the full list of attributes which you can specify within your Flu be used to fill out the background of the adaptive icon. - `adaptive_icon_foreground`: The image asset which will be used for the icon foreground of the adaptive icon *Note: Adaptive Icons will only be generated when both adaptive_icon_background and adaptive_icon_foreground are specified. (the image_path is not automatically taken as foreground)* +- `adaptive_icon_monochrome`: The image asset which will be used for the icon +foreground of the Android 13+ themed icon. For more information see [Android Adaptive Icons](https://developer.android.com/develop/ui/views/launch/icon_design_adaptive#user-theming) ### IOS diff --git a/analysis_options.yaml b/analysis_options.yaml index 1421ef5bcb..37f1c95755 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -8,10 +8,6 @@ analyzer: missing_return: warning # allow having TODOs in the code todo: ignore - # Ignore analyzer hints for updating pubspecs when using Future or - # Stream and not importing dart:async - # Please see https://github.com/flutter/flutter/pull/24528 for details. - sdk_version_async_exported_from_core: ignore exclude: - "bin/cache/**" # the following two are relative to the stocks example and the flutter package respectively @@ -55,10 +51,9 @@ linter: - flutter_style_todos - hash_and_equals - implementation_imports - - iterable_contains_unrelated_type + - collection_methods_unrelated_type - library_names - library_prefixes - - list_remove_unrelated_type - no_adjacent_strings_in_list - no_duplicate_case_values - non_constant_identifier_names diff --git a/bin/generate.dart b/bin/generate.dart index 7f52e5cec5..b7772d7827 100644 --- a/bin/generate.dart +++ b/bin/generate.dart @@ -91,6 +91,7 @@ flutter_launcher_icons: min_sdk_android: 21 # android min sdk min:16, default 21 # adaptive_icon_background: "assets/icon/background.png" # adaptive_icon_foreground: "assets/icon/foreground.png" + # adaptive_icon_monochrome: "assets/icon/monochrome.png" ios: true # image_path_ios: "assets/icon/icon.png" diff --git a/example/default_example/assets/images/icon-monochrome-432x432.png b/example/default_example/assets/images/icon-monochrome-432x432.png new file mode 100644 index 0000000000..7a07022414 Binary files /dev/null and b/example/default_example/assets/images/icon-monochrome-432x432.png differ diff --git a/example/default_example/pubspec.yaml b/example/default_example/pubspec.yaml index c7d4046e7d..b3aab90ccd 100644 --- a/example/default_example/pubspec.yaml +++ b/example/default_example/pubspec.yaml @@ -21,6 +21,7 @@ flutter_launcher_icons: ios: true # can specify file name here e.g. "My-Launcher-Icon" adaptive_icon_background: "assets/images/christmas-background.png" # only available for Android 8.0 devices and above adaptive_icon_foreground: "assets/images/icon-foreground-432x432.png" # only available for Android 8.0 devices and above + adaptive_icon_monochrome: "assets/images/icon-monochrome-432x432.png" # only available for Android 13 devices and above min_sdk_android: 21 # android min sdk min:16, default 21 remove_alpha_ios: true background_color_ios: "#ffffff" diff --git a/lib/android.dart b/lib/android.dart index a94d7ae773..a9e8327b9a 100644 --- a/lib/android.dart +++ b/lib/android.dart @@ -123,11 +123,83 @@ void createAdaptiveIcons( flavor, ); } else { - createAdaptiveIconMipmapXmlFile(config, flavor); updateColorsXmlFile(backgroundConfig, flavor); } } +void createAdaptiveMonochromeIcons( + Config config, + String? flavor, +) { + utils.printStatus('Creating adaptive monochrome icons Android'); + + // Retrieve the necessary Flutter Launcher Icons configuration from the pubspec.yaml file + final String? monochromeImagePath = config.adaptiveIconMonochrome; + if (monochromeImagePath == null) { + throw const InvalidConfigException(errorMissingImagePath); + } + final Image? monochromeImage = utils.decodeImageFile(monochromeImagePath); + if (monochromeImage == null) { + return; + } + + // Create adaptive icon monochrome images + for (AndroidIconTemplate androidIcon in adaptiveForegroundIcons) { + overwriteExistingIcons( + androidIcon, + monochromeImage, + constants.androidAdaptiveMonochromeFileName, + flavor, + ); + } +} + +void createMipmapXmlFile( + Config config, + String? flavor, +) { + utils.printStatus('Creating mipmap xml file Android'); + + String xmlContent = ''; + + if (config.hasAndroidAdaptiveConfig) { + if (isAdaptiveIconConfigPngFile(config.adaptiveIconBackground!)) { + xmlContent += + ' \n'; + } else { + xmlContent += + ' \n'; + } + + xmlContent += + ' \n'; + } + + if (config.hasAndroidAdaptiveMonochromeConfig) { + xmlContent += + ' \n'; + } + + late File mipmapXmlFile; + if (config.isCustomAndroidFile) { + mipmapXmlFile = File( + constants.androidAdaptiveXmlFolder(flavor) + config.android + '.xml', + ); + } else { + mipmapXmlFile = File( + constants.androidAdaptiveXmlFolder(flavor) + + constants.androidDefaultIconName + + '.xml', + ); + } + + mipmapXmlFile.create(recursive: true).then((File adaptiveIconFile) { + adaptiveIconFile.writeAsString( + xml_template.mipmapXmlFile.replaceAll('{{CONTENT}}', xmlContent), + ); + }); +} + /// Retrieves the colors.xml file for the project. /// /// If the colors.xml file is found, it is updated with a new color item for the @@ -151,29 +223,6 @@ void updateColorsXmlFile(String backgroundConfig, String? flavor) { } } -/// Creates the xml file required for the adaptive launcher icon -/// FILE LOCATED HERE: res/mipmap-anydpi/{icon-name-from-yaml-config}.xml -void createAdaptiveIconMipmapXmlFile( - Config config, - String? flavor, -) { - if (config.isCustomAndroidFile) { - File( - constants.androidAdaptiveXmlFolder(flavor) + config.android + '.xml', - ).create(recursive: true).then((File adaptiveIcon) { - adaptiveIcon.writeAsString(xml_template.icLauncherXml); - }); - } else { - File( - constants.androidAdaptiveXmlFolder(flavor) + - constants.androidDefaultIconName + - '.xml', - ).create(recursive: true).then((File adaptiveIcon) { - adaptiveIcon.writeAsString(xml_template.icLauncherXml); - }); - } -} - /// creates adaptive background using png image void _createAdaptiveBackgrounds( Config config, @@ -196,24 +245,6 @@ void _createAdaptiveBackgrounds( flavor, ); } - - // Creates the xml file required for the adaptive launcher icon - // FILE LOCATED HERE: res/mipmap-anydpi/{icon-name-from-yaml-config}.xml - if (config.isCustomAndroidFile) { - File( - constants.androidAdaptiveXmlFolder(flavor) + config.android + '.xml', - ).create(recursive: true).then((File adaptiveIcon) { - adaptiveIcon.writeAsString(xml_template.icLauncherDrawableBackgroundXml); - }); - } else { - File( - constants.androidAdaptiveXmlFolder(flavor) + - constants.androidDefaultIconName + - '.xml', - ).create(recursive: true).then((File adaptiveIcon) { - adaptiveIcon.writeAsString(xml_template.icLauncherDrawableBackgroundXml); - }); - } } /// Creates a colors.xml file if it was missing from android/app/src/main/res/values/colors.xml diff --git a/lib/config/config.dart b/lib/config/config.dart index db28cc559d..ba03809b68 100644 --- a/lib/config/config.dart +++ b/lib/config/config.dart @@ -27,6 +27,7 @@ class Config { this.imagePathIOS, this.adaptiveIconForeground, this.adaptiveIconBackground, + this.adaptiveIconMonochrome, this.minSdkAndroid = constants.androidDefaultAndroidMinSDK, this.removeAlphaIOS = false, this.backgroundColorIOS = '#ffffff', @@ -116,7 +117,7 @@ class Config { @JsonKey(name: 'image_path_ios') final String? imagePathIOS; - /// android adaptive icon foreground image + /// android adaptive_icon_foreground image @JsonKey(name: 'adaptive_icon_foreground') final String? adaptiveIconForeground; @@ -124,6 +125,10 @@ class Config { @JsonKey(name: 'adaptive_icon_background') final String? adaptiveIconBackground; + /// android adaptive_icon_background image + @JsonKey(name: 'adaptive_icon_monochrome') + final String? adaptiveIconMonochrome; + /// Android min_sdk_android @JsonKey(name: 'min_sdk_android') final int minSdkAndroid; @@ -157,6 +162,11 @@ class Config { adaptiveIconForeground != null && adaptiveIconBackground != null; + /// whether or not there is configuration for monochrome icons for android + bool get hasAndroidAdaptiveMonochromeConfig { + return isNeedingNewAndroidIcon && adaptiveIconMonochrome != null; + } + /// Checks if contains any platform config bool get hasPlatformConfig { return ios != false || diff --git a/lib/config/config.g.dart b/lib/config/config.g.dart index 82f2f481e9..2be66ae374 100644 --- a/lib/config/config.g.dart +++ b/lib/config/config.g.dart @@ -21,6 +21,8 @@ Config _$ConfigFromJson(Map json) => $checkedCreate( $checkedConvert('adaptive_icon_foreground', (v) => v as String?), adaptiveIconBackground: $checkedConvert('adaptive_icon_background', (v) => v as String?), + adaptiveIconMonochrome: + $checkedConvert('adaptive_icon_monochrome', (v) => v as String?), minSdkAndroid: $checkedConvert('min_sdk_android', (v) => v as int? ?? constants.androidDefaultAndroidMinSDK), removeAlphaIOS: @@ -42,6 +44,7 @@ Config _$ConfigFromJson(Map json) => $checkedCreate( 'imagePathIOS': 'image_path_ios', 'adaptiveIconForeground': 'adaptive_icon_foreground', 'adaptiveIconBackground': 'adaptive_icon_background', + 'adaptiveIconMonochrome': 'adaptive_icon_monochrome', 'minSdkAndroid': 'min_sdk_android', 'removeAlphaIOS': 'remove_alpha_ios', 'backgroundColorIOS': 'background_color_ios', @@ -59,6 +62,7 @@ Map _$ConfigToJson(Config instance) => { 'image_path_ios': instance.imagePathIOS, 'adaptive_icon_foreground': instance.adaptiveIconForeground, 'adaptive_icon_background': instance.adaptiveIconBackground, + 'adaptive_icon_monochrome': instance.adaptiveIconMonochrome, 'min_sdk_android': instance.minSdkAndroid, 'remove_alpha_ios': instance.removeAlphaIOS, 'background_color_ios': instance.backgroundColorIOS, diff --git a/lib/constants.dart b/lib/constants.dart index 9f51dab39b..6ef7d01c6b 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -24,6 +24,7 @@ const int androidDefaultAndroidMinSDK = 21; const String androidFileName = 'ic_launcher.png'; const String androidAdaptiveForegroundFileName = 'ic_launcher_foreground.png'; const String androidAdaptiveBackgroundFileName = 'ic_launcher_background.png'; +const String androidAdaptiveMonochromeFileName = 'ic_launcher_monochrome.png'; String androidAdaptiveXmlFolder(String? flavor) => androidResFolder(flavor) + 'mipmap-anydpi-v26/'; const String androidDefaultIconName = 'ic_launcher'; diff --git a/lib/ios.dart b/lib/ios.dart index 1339de35e3..5fad0e7da2 100644 --- a/lib/ios.dart +++ b/lib/ios.dart @@ -197,7 +197,7 @@ void modifyContentsFile(String newIconName) { String generateContentsFileAsString(String newIconName) { final Map contentJson = { 'images': createImageList(newIconName), - 'info': ContentsInfoObject(version: 1, author: 'xcode').toJson() + 'info': ContentsInfoObject(version: 1, author: 'xcode').toJson(), }; return json.encode(contentJson); } @@ -220,7 +220,7 @@ class ContentsImageObject { 'size': size, 'idiom': idiom, 'filename': filename, - 'scale': scale + 'scale': scale, }; } } @@ -390,7 +390,7 @@ List> createImageList(String fileNamePrefix) { idiom: 'ios-marketing', filename: '$fileNamePrefix-1024x1024@1x.png', scale: '1x', - ).toJson() + ).toJson(), ]; return imageList; } diff --git a/lib/main.dart b/lib/main.dart index ef957ad565..4b235fa33a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -141,6 +141,18 @@ Future createIconsFromConfig( if (flutterConfigs.hasAndroidAdaptiveConfig) { android_launcher_icons.createAdaptiveIcons(flutterConfigs, flavor); } + if (flutterConfigs.hasAndroidAdaptiveMonochromeConfig) { + android_launcher_icons.createAdaptiveMonochromeIcons( + flutterConfigs, + flavor, + ); + } + if (flutterConfigs.isNeedingNewAndroidIcon) { + android_launcher_icons.createMipmapXmlFile( + flutterConfigs, + flavor, + ); + } if (flutterConfigs.isNeedingNewIOSIcon) { ios_launcher_icons.createIcons(flutterConfigs, flavor); } diff --git a/lib/xml_templates.dart b/lib/xml_templates.dart index 9f6282e148..a21198d14c 100644 --- a/lib/xml_templates.dart +++ b/lib/xml_templates.dart @@ -1,19 +1,9 @@ // ignore_for_file: public_member_api_docs -const String icLauncherXml = ''' +const String mipmapXmlFile = ''' - - - -'''; - -const String icLauncherDrawableBackgroundXml = ''' - - - - - +{{CONTENT}} '''; const String colorsXml = ''' diff --git a/test/abs/icon_generator_test.mocks.dart b/test/abs/icon_generator_test.mocks.dart index adcbe83cf5..0b320f4f56 100644 --- a/test/abs/icon_generator_test.mocks.dart +++ b/test/abs/icon_generator_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.3.2 from annotations +// Mocks generated by Mockito 5.4.2 from annotations // in flutter_launcher_icons/test/abs/icon_generator_test.dart. // Do not manually edit this file. @@ -58,6 +58,11 @@ class MockConfig extends _i1.Mock implements _i3.Config { returnValue: false, ) as bool); @override + bool get hasAndroidAdaptiveMonochromeConfig => (super.noSuchMethod( + Invocation.getter(#hasAndroidAdaptiveMonochromeConfig), + returnValue: false, + ) as bool); + @override bool get hasPlatformConfig => (super.noSuchMethod( Invocation.getter(#hasPlatformConfig), returnValue: false, diff --git a/test/android_test.dart b/test/android_test.dart index c621998d0a..f65fb8b8de 100644 --- a/test/android_test.dart +++ b/test/android_test.dart @@ -32,7 +32,7 @@ void main() { final Map flutterIconsConfig = { 'image_path': 'assets/images/icon-710x599.png', 'android': true, - 'ios': true + 'ios': true, }; expect( Config.fromJson(flutterIconsConfig).isCustomAndroidFile, @@ -42,7 +42,7 @@ void main() { final Map flutterIconsNewIconConfig = { 'image_path': 'assets/images/icon-710x599.png', 'android': 'New Icon', - 'ios': true + 'ios': true, }; expect( Config.fromJson(flutterIconsNewIconConfig).isCustomAndroidFile, @@ -55,7 +55,7 @@ void main() { 'image_path': 'assets/images/icon-710x599.png', 'image_path_android': 'assets/images/icon-android.png', 'android': 'New Icon', - 'ios': true + 'ios': true, }; expect( Config.fromJson(flutterIconsNewIconConfig).getImagePathAndroid(), diff --git a/test/macos/macos_icon_generator_test.mocks.dart b/test/macos/macos_icon_generator_test.mocks.dart index 49de9a8f4a..6d6509666e 100644 --- a/test/macos/macos_icon_generator_test.mocks.dart +++ b/test/macos/macos_icon_generator_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.3.2 from annotations +// Mocks generated by Mockito 5.4.2 from annotations // in flutter_launcher_icons/test/macos/macos_icon_generator_test.dart. // Do not manually edit this file. @@ -69,6 +69,12 @@ class MockConfig extends _i1.Mock implements _i3.Config { returnValueForMissingStub: false, ) as bool); @override + bool get hasAndroidAdaptiveMonochromeConfig => (super.noSuchMethod( + Invocation.getter(#hasAndroidAdaptiveMonochromeConfig), + returnValue: false, + returnValueForMissingStub: false, + ) as bool); + @override bool get hasPlatformConfig => (super.noSuchMethod( Invocation.getter(#hasPlatformConfig), returnValue: false, diff --git a/test/main_test.dart b/test/main_test.dart index 581cf8a565..17bb7a041b 100644 --- a/test/main_test.dart +++ b/test/main_test.dart @@ -111,7 +111,7 @@ flutter_launcher_icons: final Map flutterIconsConfig = { 'image_path': 'assets/images/icon-710x599.png', 'android': true, - 'ios': true + 'ios': true, }; final config = Config.fromJson(flutterIconsConfig); expect( @@ -122,7 +122,7 @@ flutter_launcher_icons: final Map flutterIconsConfigAndroid = { 'image_path_android': 'assets/images/icon-710x599.png', 'android': true, - 'ios': true + 'ios': true, }; final configAndroid = Config.fromJson(flutterIconsConfigAndroid); expect( @@ -134,7 +134,7 @@ flutter_launcher_icons: 'image_path_android': 'assets/images/icon-android.png', 'image_path_ios': 'assets/images/icon-ios.png', 'android': true, - 'ios': true + 'ios': true, }; final configBoth = Config.fromJson(flutterIconsConfigBoth); expect( @@ -148,7 +148,7 @@ flutter_launcher_icons: final Map flutterIconsConfig = { 'image_path': 'assets/images/icon-710x599.png', 'android': true, - 'ios': true + 'ios': true, }; final config = Config.fromJson(flutterIconsConfig); expect(config.hasPlatformConfig, isTrue); @@ -156,7 +156,7 @@ flutter_launcher_icons: test('No platform specified in config', () { final Map flutterIconsConfig = { - 'image_path': 'assets/images/icon-710x599.png' + 'image_path': 'assets/images/icon-710x599.png', }; final config = Config.fromJson(flutterIconsConfig); expect(config.hasPlatformConfig, isFalse); @@ -166,7 +166,7 @@ flutter_launcher_icons: final Map flutterIconsConfig = { 'image_path': 'assets/images/icon-710x599.png', 'android': false, - 'ios': true + 'ios': true, }; final config = Config.fromJson(flutterIconsConfig); expect(config.isNeedingNewAndroidIcon, isFalse); @@ -175,7 +175,7 @@ flutter_launcher_icons: test('No new Android icon needed - no Android config', () { final Map flutterIconsConfig = { 'image_path': 'assets/images/icon-710x599.png', - 'ios': true + 'ios': true, }; final config = Config.fromJson(flutterIconsConfig); expect(config.isNeedingNewAndroidIcon, isFalse); @@ -185,7 +185,7 @@ flutter_launcher_icons: final Map flutterIconsConfig = { 'image_path': 'assets/images/icon-710x599.png', 'android': true, - 'ios': false + 'ios': false, }; final config = Config.fromJson(flutterIconsConfig); expect(config.isNeedingNewIOSIcon, isFalse); @@ -194,7 +194,7 @@ flutter_launcher_icons: test('No new iOS icon needed - no iOS config', () { final Map flutterIconsConfig = { 'image_path': 'assets/images/icon-710x599.png', - 'android': true + 'android': true, }; final config = Config.fromJson(flutterIconsConfig); expect(config.isNeedingNewIOSIcon, isFalse); diff --git a/test/templates.dart b/test/templates.dart index aa1537a168..69caf54f3e 100644 --- a/test/templates.dart +++ b/test/templates.dart @@ -7,6 +7,7 @@ flutter_launcher_icons: image_path_ios: "assets/images/icon-1024x1024.png" adaptive_icon_background: "assets/images/christmas-background.png" adaptive_icon_foreground: "assets/images/icon-foreground-432x432.png" + adaptive_icon_monochrome: "assets/images/icon-monochrome-432x432.png" min_sdk_android: 21 remove_alpha_ios: false web: @@ -56,6 +57,7 @@ image_path_android: "assets/images/icon-710x599-android.png" image_path_ios: "assets/images/icon-1024x1024.png" adaptive_icon_background: "assets/images/christmas-background.png" adaptive_icon_foreground: "assets/images/icon-foreground-432x432.png" +adaptive_icon_monochrome: "assets/images/icon-monochrome-432x432.png" web: generate: true image_path: "app_icon.png" # filepath @@ -111,6 +113,7 @@ flutter_launcher_icons: image_path_ios: "assets/images/icon-1024x1024.png" adaptive_icon_background: "assets/images/christmas-background.png" adaptive_icon_foreground: "assets/images/icon-foreground-432x432.png" + adaptive_icon_monochrome: "assets/images/icon-monochrome-432x432.png" min_sdk_android: 21 remove_alpha_ios: false web: @@ -176,6 +179,7 @@ flutter_launcher_icons: image_path_ios: "assets/images/icon-1024x1024.png" adaptive_icon_background: "assets/images/christmas-background.png" adaptive_icon_foreground: "assets/images/icon-foreground-432x432.png" + adaptive_icon_monochrome: "assets/images/icon-monochrome-432x432.png" web: generate: true image_path: "app_icon.png" # filepath diff --git a/test/web/web_icon_generator_test.dart b/test/web/web_icon_generator_test.dart index 6872ffa6a0..453492e48d 100644 --- a/test/web/web_icon_generator_test.dart +++ b/test/web/web_icon_generator_test.dart @@ -65,7 +65,7 @@ void main() { d.file('manifest.json', anything), ]), d.file('flutter_launcher_icons.yaml', anything), - d.file('pubspec.yaml', templates.pubspecTemplate) + d.file('pubspec.yaml', templates.pubspecTemplate), ]).validate(), completes, ); diff --git a/test/web/web_template_test.dart b/test/web/web_template_test.dart index 452c033923..58317b92ca 100644 --- a/test/web/web_template_test.dart +++ b/test/web/web_template_test.dart @@ -22,7 +22,7 @@ void main() { equals({ 'src': 'icons/Icon-512.png', 'sizes': '512x512', - 'type': 'image/png' + 'type': 'image/png', }), ); expect( @@ -31,7 +31,7 @@ void main() { 'src': 'icons/Icon-maskable-512.png', 'sizes': '512x512', 'type': 'image/png', - 'purpose': 'maskable' + 'purpose': 'maskable', }), ); }); diff --git a/test/windows/windows_icon_generator_test.mocks.dart b/test/windows/windows_icon_generator_test.mocks.dart index 8c7a5d02b2..1a6463b7c8 100644 --- a/test/windows/windows_icon_generator_test.mocks.dart +++ b/test/windows/windows_icon_generator_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.3.2 from annotations +// Mocks generated by Mockito 5.4.2 from annotations // in flutter_launcher_icons/test/windows/windows_icon_generator_test.dart. // Do not manually edit this file. @@ -69,6 +69,11 @@ class MockConfig extends _i1.Mock implements _i3.Config { returnValue: false, ) as bool); @override + bool get hasAndroidAdaptiveMonochromeConfig => (super.noSuchMethod( + Invocation.getter(#hasAndroidAdaptiveMonochromeConfig), + returnValue: false, + ) as bool); + @override bool get hasPlatformConfig => (super.noSuchMethod( Invocation.getter(#hasPlatformConfig), returnValue: false,