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

Ratakondala-arun/windows-support #382

Merged
merged 5 commits into from Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
3 changes: 3 additions & 0 deletions lib/abs/icon_generator.dart
Expand Up @@ -55,6 +55,9 @@ class IconGeneratorContext {

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

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

/// Generates Icon for given platforms
Expand Down
14 changes: 14 additions & 0 deletions lib/constants.dart
Expand Up @@ -42,6 +42,20 @@ String webIndexFilePath = path.join(webDirPath, 'index.html');
/// Relative pubspec.yaml path
String pubspecFilePath = path.join('pubspec.yaml');

// Windows
/// Relative path to windows directory
String windowsDirPath = path.join('windows');

/// Relative path to windows resources directory
String windowsResourcesDirPath = path.join(windowsDirPath, 'runner', 'resources');

/// Relative path to windows icon file path
String windowsIconFilePath = path.join(windowsResourcesDirPath, 'app_icon.ico');

/// Default windows icon size for flutter
///
const int kWindowsIconSize = 48;
RatakondalaArun marked this conversation as resolved.
Show resolved Hide resolved

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
39 changes: 39 additions & 0 deletions lib/flutter_launcher_icons_config.dart
Expand Up @@ -46,6 +46,10 @@ class FlutterLauncherIconsConfig {
@JsonKey(name: 'web')
final WebConfig? webConfig;

/// Windows platform config
@JsonKey(name: 'windows')
final WindowsConfig? windowsConfig;

/// Creates an instance of [FlutterLauncherIconsConfig]
const FlutterLauncherIconsConfig({
this.imagePath,
Expand All @@ -56,6 +60,7 @@ class FlutterLauncherIconsConfig {
this.adaptiveIconForeground,
this.adaptiveIconBackground,
this.webConfig,
this.windowsConfig,
});

/// Creates [FlutterLauncherIconsConfig] icons from [json]
Expand Down Expand Up @@ -161,3 +166,37 @@ class WebConfig {
@override
String toString() => 'WebConfig: ${toJson()}';
}

/// A Configs for Windows
@JsonSerializable(
anyMap: true,
checked: true,
)
class WindowsConfig {
/// Specifies weather to generate icons for web
final bool generate;

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

/// Size of the icon to generate
@JsonKey(name: 'icon_size')
final int? iconSize;

/// Creates a instance of [WindowsConfig]
const WindowsConfig({
this.generate = false,
this.imagePath,
this.iconSize,
});

/// Creates [WindowsConfig] from [json]
factory WindowsConfig.fromJson(Map json) => _$WindowsConfigFromJson(json);

/// Creates [Map] from [WindowsConfig]
Map toJson() => _$WindowsConfigToJson(this);

@override
String toString() => 'WindowsConfig: ${toJson()}';
}
27 changes: 26 additions & 1 deletion lib/flutter_launcher_icons_config.g.dart

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

2 changes: 2 additions & 0 deletions lib/main.dart
Expand Up @@ -10,6 +10,7 @@ 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/web/web_icon_generator.dart';
import 'package:flutter_launcher_icons/windows/windows_icon_generator.dart';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';

Expand Down Expand Up @@ -151,6 +152,7 @@ Future<void> createIconsFromConfig(
flavor: flavor,
platforms: (context) => [
WebIconGenerator(context),
WindowsIconGenerator(context),
// todo: add other platforms
],
);
Expand Down
2 changes: 2 additions & 0 deletions lib/utils.dart
Expand Up @@ -33,6 +33,8 @@ String generateError(Exception e, String? error) {
return '\n✗ ERROR: ${(e).runtimeType.toString()}$errorOutput';
}

// TODO(RatakondalaArun): Remove nullable return type
// this can never return null value since it already throws exception
Image? decodeImageFile(String filePath) {
final image = decodeImage(File(filePath).readAsBytesSync());
if (image == null) {
Expand Down
75 changes: 75 additions & 0 deletions lib/windows/windows_icon_generator.dart
@@ -0,0 +1,75 @@
import 'package:image/image.dart';
import 'package:path/path.dart' as path;

import '../abs/icon_generator.dart';
import '../constants.dart' as constants;
import '../custom_exceptions.dart';
import '../utils.dart' as utils;
RatakondalaArun marked this conversation as resolved.
Show resolved Hide resolved

/// A Implementation of [IconGenerator] for Windows
class WindowsIconGenerator extends IconGenerator {
/// Creates a instance of [WindowsIconGenerator]
WindowsIconGenerator(IconGeneratorContext context) : super(context, 'Windows');

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

context.logger.verbose('Decoding and loading image file from $imgFilePath...');
final imgFile = utils.decodeImageFile(imgFilePath);
// TODO(RatakondalaArun): remove null check
// #utils.decodeImageFile never returns null instead it throws Exception
if (imgFile == null) {
context.logger.error('Image File not found at given path $imgFilePath...');
throw FileNotFoundException(imgFilePath);
}

context.logger.verbose('Generating icon from $imgFilePath...');
_generateIcon(imgFile);
}

@override
bool validateRequirements() {
context.logger.verbose('Validating windows config...');
final windowsConfig = context.windowsConfig;
if (windowsConfig == null || !windowsConfig.generate) {
context.logger.error('Windows config is not provided or windows.generate is false. Skipped...');
return false;
}

if (windowsConfig.imagePath == null && context.config.imagePath == null) {
context.logger.error('Invalid config. Either provide windows.image_path or image_path');
return false;
}

// if icon_size is given it should be between 48<=icon_size<=256
// because .ico only supports this size
if (windowsConfig.iconSize != null && (windowsConfig.iconSize! < 48 || windowsConfig.iconSize! > 256)) {
context.logger.error(
'Invalid windows.icon_size=${windowsConfig.iconSize}. Icon size should be between 48<=icon_size<=256',
);
return false;
}
final entitesToCheck = [
path.join(context.prefixPath, constants.windowsDirPath),
path.join(context.prefixPath, windowsConfig.imagePath ?? context.config.imagePath),
];

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

return true;
}

void _generateIcon(Image image) {
final favIcon = utils.createResizedImage(context.windowsConfig!.iconSize ?? constants.kWindowsIconSize, image);
final favIconFile = utils.createFileIfNotExist(path.join(context.prefixPath, constants.windowsIconFilePath));
favIconFile.writeAsBytesSync(encodeIco(favIcon));
}
}
28 changes: 18 additions & 10 deletions test/abs/icon_generator_test.mocks.dart
Expand Up @@ -3,7 +3,8 @@
// Do not manually edit this file.

import 'package:flutter_launcher_icons/abs/icon_generator.dart' as _i2;
import 'package:flutter_launcher_icons/flutter_launcher_icons_config.dart' as _i3;
import 'package:flutter_launcher_icons/flutter_launcher_icons_config.dart'
as _i3;
import 'package:mockito/mockito.dart' as _i1;

// ignore_for_file: type=lint
Expand All @@ -16,19 +17,22 @@ import 'package:mockito/mockito.dart' as _i1;
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types

class _FakeIconGeneratorContext_0 extends _i1.Fake implements _i2.IconGeneratorContext {}
class _FakeIconGeneratorContext_0 extends _i1.Fake
implements _i2.IconGeneratorContext {}

/// A class which mocks [FlutterLauncherIconsConfig].
///
/// See the documentation for Mockito's code generation for more information.
class MockFlutterLauncherIconsConfig extends _i1.Mock implements _i3.FlutterLauncherIconsConfig {
class MockFlutterLauncherIconsConfig extends _i1.Mock
implements _i3.FlutterLauncherIconsConfig {
MockFlutterLauncherIconsConfig() {
_i1.throwOnMissingStub(this);
}

@override
Map<String, dynamic> toJson() =>
(super.noSuchMethod(Invocation.method(#toJson, []), returnValue: <String, dynamic>{}) as Map<String, dynamic>);
(super.noSuchMethod(Invocation.method(#toJson, []),
returnValue: <String, dynamic>{}) as Map<String, dynamic>);
}

/// A class which mocks [IconGenerator].
Expand All @@ -40,14 +44,18 @@ class MockIconGenerator extends _i1.Mock implements _i2.IconGenerator {
}

@override
_i2.IconGeneratorContext get context =>
(super.noSuchMethod(Invocation.getter(#context), returnValue: _FakeIconGeneratorContext_0())
as _i2.IconGeneratorContext);
_i2.IconGeneratorContext get context => (super.noSuchMethod(
Invocation.getter(#context),
returnValue: _FakeIconGeneratorContext_0()) as _i2.IconGeneratorContext);
@override
String get platformName => (super.noSuchMethod(Invocation.getter(#platformName), returnValue: '') as String);
String get platformName =>
(super.noSuchMethod(Invocation.getter(#platformName), returnValue: '')
as String);
@override
void createIcons() => super.noSuchMethod(Invocation.method(#createIcons, []), returnValueForMissingStub: null);
void createIcons() => super.noSuchMethod(Invocation.method(#createIcons, []),
returnValueForMissingStub: null);
@override
bool validateRequirements() =>
(super.noSuchMethod(Invocation.method(#validateRequirments, []), returnValue: false) as bool);
(super.noSuchMethod(Invocation.method(#validateRequirements, []),
returnValue: false) as bool);
}
5 changes: 4 additions & 1 deletion test/all_tests.dart
Expand Up @@ -5,8 +5,9 @@ import 'android_test.dart' as android_test;
import 'flutter_launcher_icons_config_test.dart' as fli_config;
import 'main_test.dart' as main_test;
import 'utils_test.dart' as utils_test;
import 'web/web_template_test.dart' as web_template_test;
import 'web/web_icon_generator_test.dart' as web_icon_gen_test;
import 'web/web_template_test.dart' as web_template_test;
import 'windows/windows_icon_generator_test.dart' as windows_icon_gen_test;

void main() {
group('Flutter launcher icons', () {
Expand All @@ -21,5 +22,7 @@ void main() {
// web
web_template_test.main();
web_icon_gen_test.main();
// windows
windows_icon_gen_test.main();
});
}
39 changes: 39 additions & 0 deletions test/flutter_launcher_icons_config_test.dart
Expand Up @@ -46,6 +46,19 @@ void main() {
'theme_color': '#0175C2',
}),
);
// windows
expect(configs.windowsConfig, isNotNull);
expect(configs.windowsConfig!.generate, isNotNull);
expect(configs.windowsConfig!.iconSize, isNotNull);
expect(configs.windowsConfig!.imagePath, isNotNull);
expect(
configs.windowsConfig!.toJson(),
equals(<String, dynamic>{
'generate': true,
'image_path': 'app_icon.png',
'icon_size': 48,
}),
);
});

test('should return null when invalid filePath is given', () {
Expand Down Expand Up @@ -96,6 +109,19 @@ void main() {
'theme_color': '#0175C2',
}),
);
// windows
expect(configs.windowsConfig, isNotNull);
expect(configs.windowsConfig!.generate, isNotNull);
expect(configs.windowsConfig!.iconSize, isNotNull);
expect(configs.windowsConfig!.imagePath, isNotNull);
expect(
configs.windowsConfig!.toJson(),
equals(<String, dynamic>{
'generate': true,
'image_path': 'app_icon.png',
'icon_size': 48,
}),
);
});

group('should throw', () {
Expand Down Expand Up @@ -146,6 +172,19 @@ void main() {
'theme_color': '#0175C2',
}),
);
// windows
expect(configs.windowsConfig, isNotNull);
expect(configs.windowsConfig!.generate, isNotNull);
expect(configs.windowsConfig!.iconSize, isNotNull);
expect(configs.windowsConfig!.imagePath, isNotNull);
expect(
configs.windowsConfig!.toJson(),
equals(<String, dynamic>{
'generate': true,
'image_path': 'app_icon.png',
'icon_size': 48,
}),
);
});
});
});
Expand Down