Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RatakondalaArun/web-support #374

Merged
merged 24 commits into from Jul 23, 2022
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
180a0a5
style(analysis): formatted
RatakondalaArun Jul 8, 2022
8b925d0
chore(lints): disabled `sort_constructors_first`
RatakondalaArun Jul 8, 2022
2db23d4
chore(vscode): add vscode workspace config
RatakondalaArun Jul 8, 2022
9087923
vendor(deps): added new dependencies
RatakondalaArun Jul 8, 2022
3a5cbff
feat(web): constants for web platform
RatakondalaArun Jul 8, 2022
e597498
feat(logger): added logger
RatakondalaArun Jul 8, 2022
4a49ff8
feat(web): support for web icons
RatakondalaArun Jul 8, 2022
b2b8982
feat(cli-option): added --prefix option
RatakondalaArun Jul 10, 2022
9a1f828
fix(config): type error when invalid config is passed
RatakondalaArun Jul 10, 2022
542e7fe
vendor(deps): added test dependencies
RatakondalaArun Jul 10, 2022
d3c061d
test(fli_config): added tests for fli config
RatakondalaArun Jul 10, 2022
c2aaaa8
test(utils): added tests for utils
RatakondalaArun Jul 10, 2022
bae9be2
test(web): added tests for web icon templates
RatakondalaArun Jul 10, 2022
a769629
fix(logging): added verbose logging to platform failure
RatakondalaArun Jul 10, 2022
12da70f
vendor(deps): added mokito for tests
RatakondalaArun Jul 10, 2022
3e2578e
test(icon_generator): added tests for IconGenerator
RatakondalaArun Jul 10, 2022
96a54a5
test(web): added tests for web icon generator
RatakondalaArun Jul 10, 2022
24bc6e7
test: added common entrypoint to all tests
RatakondalaArun Jul 10, 2022
4fac239
style: add missing space
RatakondalaArun Jul 20, 2022
4aa4162
fix(generator): fixed typo in a method
RatakondalaArun Jul 20, 2022
af10715
fix(lint): filxed lint warnings for `public_member_api_docs`
RatakondalaArun Jul 20, 2022
610fcb8
fix(lint): filxed lint warnings for `require_trailing_commas`
RatakondalaArun Jul 20, 2022
6713bd4
style: sorted imports
RatakondalaArun Jul 20, 2022
3a5822e
style: sort variable order
RatakondalaArun Jul 20, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 5 additions & 5 deletions analysis_options.yaml
Expand Up @@ -13,12 +13,12 @@ analyzer:
# Please see https://github.com/flutter/flutter/pull/24528 for details.
RatakondalaArun marked this conversation as resolved.
Show resolved Hide resolved
sdk_version_async_exported_from_core: ignore
exclude:
- 'bin/cache/**'
- "bin/cache/**"
# the following two are relative to the stocks example and the flutter package respectively
# see https://github.com/dart-lang/sdk/issues/28463
- 'lib/i18n/stock_messages_*.dart'
- 'lib/src/http/**'
- 'example/**'
- "lib/i18n/stock_messages_*.dart"
- "lib/src/http/**"
- "example/**"

linter:
rules:
Expand Down Expand Up @@ -128,7 +128,7 @@ linter:
# - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml
- recursive_getters
- slash_for_doc_comments
- sort_constructors_first
# - sort_constructors_first: vscode's command "dart:sort members" does not follow this
- sort_pub_dependencies
- sort_unnamed_constructors_first
# - super_goes_last # no longer needed w/ Dart 2
Expand Down
24 changes: 24 additions & 0 deletions flutter_launcher_icons.code-workspace
@@ -0,0 +1,24 @@
{
"folders": [
{
"name": "Flutter Launcher Icons",
"path": "."
}
],
"settings": {
"[dart]": {
"editor.formatOnSave": true,
// "editor.formatOnType": true,
"editor.rulers": [
120
],
"editor.selectionHighlight": false,
"editor.suggest.snippetsPreventQuickSuggestions": false,
"editor.suggestSelection": "first",
"editor.tabCompletion": "onlySnippets",
"editor.wordBasedSuggestions": false,
"files.insertFinalNewline": true
},
"dart.lineLength": 120,
}
}
90 changes: 90 additions & 0 deletions lib/abs/icon_generator.dart
@@ -0,0 +1,90 @@
import 'dart:io';

import 'package:flutter_launcher_icons/flutter_launcher_icons_config.dart';
import 'package:flutter_launcher_icons/logger.dart';

///A base class to generate icons
RatakondalaArun marked this conversation as resolved.
Show resolved Hide resolved
abstract class IconGenerator {
final IconGeneratorContext context;
final String platformName;

IconGenerator(this.context, this.platformName);

/// Creates icons for this platform.
void createIcons();

/// Should return `true` if this platform
/// has all the requirments to create icons.
/// This runs before to [createIcons]
bool validateRequirments();
RatakondalaArun marked this conversation as resolved.
Show resolved Hide resolved
}

/// Provides easy access to user arguments and configuration
class IconGeneratorContext {
/// Contains configuration from configuration file
final FlutterLauncherIconsConfig config;
final FLILogger logger;
final String? flavor;
final String prefixPath;

IconGeneratorContext({
required this.config,
this.flavor,
required this.prefixPath,
required this.logger,
RatakondalaArun marked this conversation as resolved.
Show resolved Hide resolved
});

/// Shortcut for `config.webConfig`
WebConfig? get webConfig => config.webConfig;
}

/// Generates Icon for given platforms
void generateIconsFor({
required FlutterLauncherIconsConfig config,
required String? flavor,
required String prefixPath,
required FLILogger logger,
required List<IconGenerator> Function(IconGeneratorContext context) platforms,
}) {
try {
final platformList = platforms(IconGeneratorContext(
config: config,
logger: logger,
prefixPath: prefixPath,
flavor: flavor,
));
if (platformList.isEmpty) {
// ? maybe we can print help
logger.info('No platform provided');
}

for (final platform in platformList) {
final progress = logger.progress('Creating Icons for ${platform.platformName}');
logger.verbose('Validating platform requirments for ${platform.platformName}');
// in case a platform throws an exception it should not effect other platforms
try {
if (!platform.validateRequirments()) {
logger.error('Requirments failed for platform ${platform.platformName}. Skipped');
progress.cancel();
continue;
}
platform.createIcons();
progress.finish(message: 'done', showTiming: true);
} catch (e, st) {
progress.cancel();
logger
..error(e.toString())
..verbose(st);
continue;
}
}
} catch (e, st) {
// todo: better error handling
// stacktrace should only print when verbose is turned on
RatakondalaArun marked this conversation as resolved.
Show resolved Hide resolved
// else a normal help line
logger
..error(e.toString())
..verbose(st);
exit(1);
}
}
36 changes: 20 additions & 16 deletions lib/constants.dart
@@ -1,35 +1,39 @@
String androidResFolder(String? flavor) =>
"android/app/src/${flavor ?? 'main'}/res/";
String androidColorsFile(String? flavor) =>
"android/app/src/${flavor ?? 'main'}/res/values/colors.xml";
import 'package:path/path.dart' as path;

String androidResFolder(String? flavor) => "android/app/src/${flavor ?? 'main'}/res/";
String androidColorsFile(String? flavor) => "android/app/src/${flavor ?? 'main'}/res/values/colors.xml";
const String androidManifestFile = 'android/app/src/main/AndroidManifest.xml';
const String androidGradleFile = 'android/app/build.gradle';
const String androidLocalPropertiesFile = 'android/local.properties';
const String androidFileName = 'ic_launcher.png';
const String androidAdaptiveForegroundFileName = 'ic_launcher_foreground.png';
const String androidAdaptiveBackgroundFileName = 'ic_launcher_background.png';
String androidAdaptiveXmlFolder(String? flavor) =>
androidResFolder(flavor) + 'mipmap-anydpi-v26/';
String androidAdaptiveXmlFolder(String? flavor) => androidResFolder(flavor) + 'mipmap-anydpi-v26/';
const String androidDefaultIconName = 'ic_launcher';

const String iosDefaultIconFolder =
'ios/Runner/Assets.xcassets/AppIcon.appiconset/';
const String iosDefaultIconFolder = 'ios/Runner/Assets.xcassets/AppIcon.appiconset/';
const String iosAssetFolder = 'ios/Runner/Assets.xcassets/';
const String iosConfigFile = 'ios/Runner.xcodeproj/project.pbxproj';
const String iosDefaultIconName = 'Icon-App';

// web
const int kFaviconSize = 16;
String webDirPath = path.join('web');
String webIconsDirPath = path.join(webDirPath, 'icons');
String webManifestFilePath = path.join(webDirPath, 'manifest.json');
// todo: support for other images formats
String webFaviconFilePath = path.join(webDirPath, 'favicon.png');
String webIndexFilePath = path.join(webDirPath, 'index.html');
String pubspecFilePath = path.join('pubspec.yaml');

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.';
const String errorMissingRegularAndroid =
'Adaptive icon config found but no regular Android config. '
const String errorMissingPlatform = 'No platform specified within config to generate icons for.';
const String errorMissingRegularAndroid = 'Adaptive icon config found but no regular Android config. '
'Below API 26 the regular Android config is required';
const String errorMissingMinSdk =
'Cannot not find minSdk from android/app/build.gradle or android/local.properties'
const String errorMissingMinSdk = 'Cannot not find minSdk from android/app/build.gradle or android/local.properties'
'Specify minSdk in either android/app/build.gradle or android/local.properties';
const String errorIncorrectIconName =
'The icon name must contain only lowercase a-z, 0-9, or underscore: '
const String errorIncorrectIconName = 'The icon name must contain only lowercase a-z, 0-9, or underscore: '
'E.g. "ic_my_new_icon"';

String introMessage(String currentVersion) => '''
Expand Down
10 changes: 10 additions & 0 deletions lib/custom_exceptions.dart
Expand Up @@ -39,3 +39,13 @@ class NoDecoderForImageFormatException implements Exception {
return generateError(this, message);
}
}

class FileNotFoundException implements Exception {
const FileNotFoundException(this.fileName);

final String fileName;
@override
String toString() {
return generateError(this, '$fileName file not found');
}
}
153 changes: 153 additions & 0 deletions lib/flutter_launcher_icons_config.dart
@@ -0,0 +1,153 @@
import 'dart:io';

import 'package:checked_yaml/checked_yaml.dart' as yaml;
import 'package:json_annotation/json_annotation.dart';
import 'package:path/path.dart' as path;

import 'constants.dart' as constants;
import 'custom_exceptions.dart';
import 'utils.dart' as utils;

part 'flutter_launcher_icons_config.g.dart';

@JsonSerializable(
anyMap: true,
checked: true,
)
class FlutterLauncherIconsConfig {
/// Generic imagepath
@JsonKey(name: 'image_path')
final String? imagePath;

/// Returns true or path if android config is enabled
final dynamic android; // path or bool

/// Returns true or path if ios config is enabled
final dynamic ios; // path or bool

/// Image path specific to android
@JsonKey(name: 'image_path_android')
final String? imagePathAndroid;

/// Image path specific to ios
@JsonKey(name: 'image_path_ios')
final String? imagePathIOS;

/// 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;

/// Web platform config
@JsonKey(name: 'web')
final WebConfig? webConfig;

const FlutterLauncherIconsConfig({
this.imagePath,
this.android = false,
this.ios = false,
this.imagePathAndroid,
this.imagePathIOS,
this.adaptiveIconForeground,
this.adaptiveIconBackground,
this.webConfig,
});

factory FlutterLauncherIconsConfig.fromJson(Map json) => _$FlutterLauncherIconsConfigFromJson(json);

/// Loads flutter launcher icons configs from given [filePath]
static FlutterLauncherIconsConfig? loadConfigFromPath(String filePath, String prefixPath) {
final configFile = File(path.join(prefixPath, filePath));
if (!configFile.existsSync()) {
return null;
}
final configContent = configFile.readAsStringSync();
try {
return yaml.checkedYamlDecode<FlutterLauncherIconsConfig?>(
configContent,
(json) {
// todo: add support for new scheme https://github.com/fluttercommunity/flutter_launcher_icons/issues/373
return json == null || json['flutter_icons'] == null
? null
: FlutterLauncherIconsConfig.fromJson(json['flutter_icons']);
},
allowNull: true,
);
} on yaml.ParsedYamlException catch (e) {
throw InvalidConfigException(e.formattedMessage);
} catch (e) {
rethrow;
}
}

/// Loads flutter launcher icons config from `pubspec.yaml` file
static FlutterLauncherIconsConfig? loadConfigFromPubSpec(String prefix) {
try {
final pubspecFile = File(path.join(prefix, constants.pubspecFilePath));
if (!pubspecFile.existsSync()) {
return null;
}
final pubspecContent = pubspecFile.readAsStringSync();
return yaml.checkedYamlDecode<FlutterLauncherIconsConfig?>(
pubspecContent,
(json) {
// todo: add support for new scheme https://github.com/fluttercommunity/flutter_launcher_icons/issues/373
return json == null || json['flutter_icons'] == null
? null
: FlutterLauncherIconsConfig.fromJson(json['flutter_icons']);
},
allowNull: true,
);
} on yaml.ParsedYamlException catch (e) {
throw InvalidConfigException(e.formattedMessage);
} catch (e) {
rethrow;
}
}

static FlutterLauncherIconsConfig? loadConfigFromFlavor(String flavor, String prefixPath) {
return FlutterLauncherIconsConfig.loadConfigFromPath(utils.flavorConfigFile(flavor), prefixPath);
}

Map<String, dynamic> toJson() => _$FlutterLauncherIconsConfigToJson(this);

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

@JsonSerializable(
anyMap: true,
checked: true,
)
class WebConfig {
final bool generate;

/// Image path for web
@JsonKey(name: 'image_path')
final String? imagePath;

/// manifest.json's background_color
@JsonKey(name: 'background_color')
final String? backgroundColor;

/// manifest.json's theme_color
@JsonKey(name: 'theme_color')
final String? themeColor;

const WebConfig({
this.generate = false,
this.imagePath,
this.backgroundColor,
this.themeColor,
});

factory WebConfig.fromJson(Map json) => _$WebConfigFromJson(json);

Map<String, dynamic> toJson() => _$WebConfigToJson(this);

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