Skip to content

Commit

Permalink
feat(macos): added support for macos icons
Browse files Browse the repository at this point in the history
closes #393
  • Loading branch information
RatakondalaArun authored and MarkOSullivan94 committed Aug 27, 2022
1 parent 768c7d4 commit e1fe1dd
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 38 deletions.
3 changes: 3 additions & 0 deletions lib/abs/icon_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ class IconGeneratorContext {

/// Shortcut for `config.windowsConfig`
WindowsConfig? get windowsConfig => config.windowsConfig;

/// Shortcut for `config.macOSConfig`
MacOSConfig? get macOSConfig => config.macOSConfig;
}

/// Generates Icon for given platforms
Expand Down
11 changes: 11 additions & 0 deletions lib/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ String windowsIconFilePath = path.join(windowsResourcesDirPath, 'app_icon.ico');
///
const int windowsDefaultIconSize = 48;

// MacOS

/// Relative path to macos folder
final macOSDirPath = path.join('macos');

/// Relative path to macos icons folder
final macOSIconsDirPath = path.join(macOSDirPath, 'Runner', 'Assets.xcassets', 'AppIcon.appiconset');

/// Relative path to macos contents.json
final macOSContentsFilePath = path.join(macOSIconsDirPath, 'Contents.json');

const String errorMissingImagePath =
'Missing "image_path" or "image_path_android" + "image_path_ios" within configuration';
const String errorMissingPlatform = 'No platform specified within config to generate icons for.';
Expand Down
100 changes: 66 additions & 34 deletions lib/flutter_launcher_icons_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class FlutterLauncherIconsConfig {
final String? adaptiveIconBackground;

/// Android min_sdk_android
@JsonKey(name: 'min_sdk_android', defaultValue: constants.androidDefaultAndroidMinSDK)
@JsonKey(name: 'min_sdk_android')
final int minSdkAndroid;

/// IOS remove_alpha_ios
Expand All @@ -58,6 +58,10 @@ class FlutterLauncherIconsConfig {
@JsonKey(name: 'windows')
final WindowsConfig? windowsConfig;

/// MacOS platform config
@JsonKey(name: 'macos')
final MacOSConfig? macOSConfig;

/// Creates an instance of [FlutterLauncherIconsConfig]
const FlutterLauncherIconsConfig({
this.imagePath,
Expand All @@ -71,11 +75,48 @@ class FlutterLauncherIconsConfig {
this.removeAlphaIOS = false,
this.webConfig,
this.windowsConfig,
this.macOSConfig,
});

/// Creates [FlutterLauncherIconsConfig] icons from [json]
factory FlutterLauncherIconsConfig.fromJson(Map json) => _$FlutterLauncherIconsConfigFromJson(json);

bool get hasAndroidAdaptiveConfig =>
isNeedingNewAndroidIcon && adaptiveIconForeground != null && adaptiveIconBackground != null;

/// Checks if contains any platform config
bool get hasPlatformConfig {
return ios != false || android != false || webConfig != null || windowsConfig != null || macOSConfig != null;
}

/// Check to see if specified Android config is a string or bool
/// String - Generate new launcher icon with the string specified
/// bool - override the default flutter project icon
bool get isCustomAndroidFile => android is String;

bool get isNeedingNewAndroidIcon => android != false;

bool get isNeedingNewIOSIcon => ios != false;

/// Method for the retrieval of the Android icon path
/// If image_path_android is found, this will be prioritised over the image_path
/// value.
String? getImagePathAndroid() => imagePathAndroid ?? imagePath;
// todo: refactor after Android & iOS configs will be refactored to the new schema
// https://github.com/fluttercommunity/flutter_launcher_icons/issues/394
String? getImagePathIOS() => imagePathIOS ?? imagePath;

/// Converts config to [Map]
Map<String, dynamic> toJson() => _$FlutterLauncherIconsConfigToJson(this);

@override
String toString() => 'FlutterLauncherIconsConfig: ${toJson()}';

/// Creates [FlutterLauncherIconsConfig] for given [flavor] and [prefixPath]
static FlutterLauncherIconsConfig? loadConfigFromFlavor(String flavor, String prefixPath) {
return FlutterLauncherIconsConfig.loadConfigFromPath(utils.flavorConfigFile(flavor), prefixPath);
}

/// Loads flutter launcher icons configs from given [filePath]
static FlutterLauncherIconsConfig? loadConfigFromPath(String filePath, String prefixPath) {
final configFile = File(path.join(prefixPath, filePath));
Expand Down Expand Up @@ -125,45 +166,36 @@ class FlutterLauncherIconsConfig {
rethrow;
}
}
}

/// Creates [FlutterLauncherIconsConfig] for given [flavor] and [prefixPath]
static FlutterLauncherIconsConfig? loadConfigFromFlavor(String flavor, String prefixPath) {
return FlutterLauncherIconsConfig.loadConfigFromPath(utils.flavorConfigFile(flavor), prefixPath);
}

/// Converts config to [Map]
Map<String, dynamic> toJson() => _$FlutterLauncherIconsConfigToJson(this);

@override
String toString() => 'FlutterLauncherIconsConfig: ${toJson()}';
/// A Configs for Windows
@JsonSerializable(
anyMap: true,
checked: true,
)
class MacOSConfig {
/// Specifies weather to generate icons for macos
@JsonKey()
final bool generate;

bool get isNeedingNewIOSIcon => ios != false;
bool get isNeedingNewAndroidIcon => android != false;
bool get hasAndroidAdaptiveConfig =>
isNeedingNewAndroidIcon &&
adaptiveIconForeground != null &&
adaptiveIconBackground != null;
/// Image path for macos
@JsonKey(name: 'image_path')
final String? imagePath;

// todo: refactor after Android & iOS configs will be refactored to the new schema
// https://github.com/fluttercommunity/flutter_launcher_icons/issues/394
String? getImagePathIOS() => imagePathIOS ?? imagePath;
/// Creates a instance of [MacOSConfig]
const MacOSConfig({
this.generate = false,
this.imagePath,
});

/// Method for the retrieval of the Android icon path
/// If image_path_android is found, this will be prioritised over the image_path
/// value.
String? getImagePathAndroid() => imagePathAndroid ?? imagePath;
/// Creates [WebConfig] from [json]
factory MacOSConfig.fromJson(Map json) => _$MacOSConfigFromJson(json);

/// Check to see if specified Android config is a string or bool
/// String - Generate new launcher icon with the string specified
/// bool - override the default flutter project icon
bool get isCustomAndroidFile => android is String;
/// Creates [Map] from [WebConfig]
Map<String, dynamic> toJson() => _$MacOSConfigToJson(this);

/// Checks if contains any platform config
bool get hasPlatformConfig =>
ios != false ||
android != false ||
webConfig != null ||
windowsConfig != null;
@override
String toString() => '$runtimeType: ${toJson()}';
}

/// Parse `web` config from `flutter_launcher_icons.yaml`
Expand Down
29 changes: 26 additions & 3 deletions lib/flutter_launcher_icons_config.g.dart

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

110 changes: 110 additions & 0 deletions lib/macos/macos_icon_generator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import 'dart:convert';
import 'dart:io';

import 'package:flutter_launcher_icons/abs/icon_generator.dart';
import 'package:flutter_launcher_icons/constants.dart' as constants;
import 'package:flutter_launcher_icons/custom_exceptions.dart';
import 'package:flutter_launcher_icons/macos/macos_icon_template.dart';
import 'package:flutter_launcher_icons/utils.dart' as utils;
import 'package:image/image.dart';
import 'package:path/path.dart' as path;

/// A [IconGenerator] implementation for macos
class MacOSIconGenerator extends IconGenerator {
static const _iconSizeTemplates = <MacOSIconTemplate>[
MacOSIconTemplate(16, 1),
MacOSIconTemplate(16, 2),
MacOSIconTemplate(32, 1),
MacOSIconTemplate(32, 2),
MacOSIconTemplate(128, 1),
MacOSIconTemplate(128, 2),
MacOSIconTemplate(256, 1),
MacOSIconTemplate(256, 2),
MacOSIconTemplate(512, 1),
MacOSIconTemplate(512, 2),
];

/// Creates a instance of [MacOSIconGenerator]
MacOSIconGenerator(IconGeneratorContext context) : super(context, 'MacOS');

@override
void createIcons() {
final imgFilePath = path.join(
context.prefixPath,
context.config.macOSConfig!.imagePath ?? context.config.imagePath,
);

context.logger.verbose('Decoding and loading image file at $imgFilePath...');
final imgFile = utils.decodeImageFile(imgFilePath);
if (imgFile == null) {
context.logger.error('Image File not found at give path $imgFilePath...');
throw FileNotFoundException(imgFilePath);
}

context.logger.verbose('Generating icons $imgFilePath...');
_generateIcons(imgFile);
context.logger.verbose('Updating contents.json');
_updateContentsFile();
}

@override
bool validateRequirements() {
context.logger.verbose('Checking $platformName config...');
final macOSConfig = context.macOSConfig;

if (macOSConfig == null || !macOSConfig.generate) {
context.logger
..verbose('$platformName config is missing or "flutter_icons.macos.generate" is false. Skipped...')
..verbose(macOSConfig);
return false;
}

if (macOSConfig.imagePath == null && context.config.imagePath == null) {
context.logger
..verbose({
'flutter_icons.macos.image_path': macOSConfig.imagePath,
'flutter_icons.image_path': context.config.imagePath,
})
..error(
'Missing image_path. Either provide "flutter_icons.macos.image_path" or "flutter_icons.image_path"',
);

return false;
}

// this files and folders should exist to create macos icons
final enitiesToCheck = [
path.join(context.prefixPath, constants.macOSDirPath),
path.join(context.prefixPath, constants.macOSIconsDirPath),
path.join(context.prefixPath, constants.macOSContentsFilePath),
];

final failedEntityPath = utils.areFSEntiesExist(enitiesToCheck);
if (failedEntityPath != null) {
context.logger.error('$failedEntityPath this file or folder is required to generate $platformName icons');
return false;
}

return true;
}

void _generateIcons(Image image) {
final iconsDir = utils.createDirIfNotExist(path.join(context.prefixPath, constants.macOSIconsDirPath));

for (final template in _iconSizeTemplates) {
final resizedImg = utils.createResizedImage(template.scaledSize, image);
final iconFile = utils.createFileIfNotExist(path.join(context.prefixPath, iconsDir.path, template.iconFile));
iconFile.writeAsBytesSync(encodePng(resizedImg));
}
}

void _updateContentsFile() {
final contentsFilePath = File(path.join(context.prefixPath, constants.macOSContentsFilePath));
final contentsConfig = jsonDecode(contentsFilePath.readAsStringSync()) as Map<String, dynamic>;
contentsConfig
..remove('images')
..['images'] = _iconSizeTemplates.map<Map<String, dynamic>>((e) => e.iconContent).toList();

contentsFilePath.writeAsStringSync(utils.prettifyJsonEncode(contentsConfig));
}
}
39 changes: 39 additions & 0 deletions lib/macos/macos_icon_template.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/// A macOS icon template
class MacOSIconTemplate {
/// Icon size
final int size;

/// Icon scale
final int scale;

/// Creates an instance of [MacOSIconTemplate]
///
const MacOSIconTemplate(this.size, this.scale);

/// Icon content for contents.json' s images
///
/// ```json
/// {
/// "size" : "16x16",
/// "idiom" : "mac",
/// "filename" : "app_icon_16.png",
/// "scale" : "1x"
/// }
/// ```
Map<String, dynamic> get iconContent {
return <String, dynamic>{
'size': '${size}x$size',
'idiom': 'mac',
'filename': iconFile,
'scale': '${scale}x',
};
}

/// Icon file name with extension
///
/// `app_icon_16.png`
String get iconFile => 'app_icon_$scaledSize.png';

/// Image size after computing scale
int get scaledSize => size * scale;
}
4 changes: 3 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:flutter_launcher_icons/custom_exceptions.dart';
import 'package:flutter_launcher_icons/flutter_launcher_icons_config.dart';
import 'package:flutter_launcher_icons/ios.dart' as ios_launcher_icons;
import 'package:flutter_launcher_icons/logger.dart';
import 'package:flutter_launcher_icons/macos/macos_icon_generator.dart';
import 'package:flutter_launcher_icons/web/web_icon_generator.dart';
import 'package:flutter_launcher_icons/windows/windows_icon_generator.dart';
import 'package:path/path.dart' as path;
Expand Down Expand Up @@ -74,7 +75,7 @@ Future<void> createIconsFromArguments(List<String> arguments) async {
if (flutterLauncherIconsConfigs == null) {
throw NoConfigFoundException(
'No configuration found in $defaultConfigFile or in ${constants.pubspecFilePath}. '
'In case file exists in different directory use --file option',
'In case file exists in different directory use --file option',
);
}
try {
Expand Down Expand Up @@ -135,6 +136,7 @@ Future<void> createIconsFromConfig(
platforms: (context) => [
WebIconGenerator(context),
WindowsIconGenerator(context),
MacOSIconGenerator(context),
// todo: add other platforms
],
);
Expand Down

0 comments on commit e1fe1dd

Please sign in to comment.