Skip to content

Commit

Permalink
feat: added support for android 13+ themed icons (#497)
Browse files Browse the repository at this point in the history
* feat: added support for android 13+ themed icons

* fix: linting and analyzer issues
  • Loading branch information
OutdatedGuy authored Dec 29, 2023
1 parent ca79b54 commit 006cb1e
Show file tree
Hide file tree
Showing 20 changed files with 149 additions and 82 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 1 addition & 6 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions bin/generate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions example/default_example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
115 changes: 73 additions & 42 deletions lib/android.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 +=
' <background android:drawable="@drawable/ic_launcher_background"/>\n';
} else {
xmlContent +=
' <background android:drawable="@color/ic_launcher_background"/>\n';
}

xmlContent +=
' <foreground android:drawable="@drawable/ic_launcher_foreground"/>\n';
}

if (config.hasAndroidAdaptiveMonochromeConfig) {
xmlContent +=
' <monochrome android:drawable="@drawable/ic_launcher_monochrome"/>\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
Expand All @@ -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,
Expand All @@ -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
Expand Down
12 changes: 11 additions & 1 deletion lib/config/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Config {
this.imagePathIOS,
this.adaptiveIconForeground,
this.adaptiveIconBackground,
this.adaptiveIconMonochrome,
this.minSdkAndroid = constants.androidDefaultAndroidMinSDK,
this.removeAlphaIOS = false,
this.backgroundColorIOS = '#ffffff',
Expand Down Expand Up @@ -116,14 +117,18 @@ 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;

/// android adaptive_icon_background image
@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;
Expand Down Expand Up @@ -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 ||
Expand Down
4 changes: 4 additions & 0 deletions lib/config/config.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
6 changes: 3 additions & 3 deletions lib/ios.dart
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ void modifyContentsFile(String newIconName) {
String generateContentsFileAsString(String newIconName) {
final Map<String, dynamic> contentJson = <String, dynamic>{
'images': createImageList(newIconName),
'info': ContentsInfoObject(version: 1, author: 'xcode').toJson()
'info': ContentsInfoObject(version: 1, author: 'xcode').toJson(),
};
return json.encode(contentJson);
}
Expand All @@ -220,7 +220,7 @@ class ContentsImageObject {
'size': size,
'idiom': idiom,
'filename': filename,
'scale': scale
'scale': scale,
};
}
}
Expand Down Expand Up @@ -390,7 +390,7 @@ List<Map<String, String>> createImageList(String fileNamePrefix) {
idiom: 'ios-marketing',
filename: '$fileNamePrefix[email protected]',
scale: '1x',
).toJson()
).toJson(),
];
return imageList;
}
Expand Down
12 changes: 12 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,18 @@ Future<void> 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);
}
Expand Down
14 changes: 2 additions & 12 deletions lib/xml_templates.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
// ignore_for_file: public_member_api_docs

const String icLauncherXml = '''
const String mipmapXmlFile = '''
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
''';

const String icLauncherDrawableBackgroundXml = '''
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
{{CONTENT}}</adaptive-icon>
''';

const String colorsXml = '''
Expand Down
7 changes: 6 additions & 1 deletion test/abs/icon_generator_test.mocks.dart
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions test/android_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ void main() {
final Map<String, dynamic> flutterIconsConfig = <String, dynamic>{
'image_path': 'assets/images/icon-710x599.png',
'android': true,
'ios': true
'ios': true,
};
expect(
Config.fromJson(flutterIconsConfig).isCustomAndroidFile,
Expand All @@ -42,7 +42,7 @@ void main() {
final Map<String, dynamic> flutterIconsNewIconConfig = <String, dynamic>{
'image_path': 'assets/images/icon-710x599.png',
'android': 'New Icon',
'ios': true
'ios': true,
};
expect(
Config.fromJson(flutterIconsNewIconConfig).isCustomAndroidFile,
Expand All @@ -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(),
Expand Down
8 changes: 7 additions & 1 deletion test/macos/macos_icon_generator_test.mocks.dart
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 006cb1e

Please sign in to comment.