diff --git a/analysis_options.yaml b/analysis_options.yaml index 0b8a4db18c..5e6224aeae 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -57,7 +57,6 @@ linter: - no_duplicate_case_values - non_constant_identifier_names - overridden_fields - - package_api_docs - package_names - package_prefixed_library_names - prefer_adjacent_string_concatenation diff --git a/lib/config/windows_config.dart b/lib/config/windows_config.dart index 0c94e2e585..035b6d1366 100644 --- a/lib/config/windows_config.dart +++ b/lib/config/windows_config.dart @@ -8,14 +8,17 @@ part 'windows_config.g.dart'; checked: true, ) class WindowsConfig { - /// Specifies weather to generate icons for web + /// Specifies whether to generate icons for Windows final bool generate; - /// Image path for web + /// Image path for Windows @JsonKey(name: 'image_path') final String? imagePath; - /// Size of the icon to generate + /// DEPRECATED: kept only for backward-compat. Ignored by the generator. + @Deprecated( + 'Ignored. Windows .ico is generated with multiple sizes by default.', + ) @JsonKey(name: 'icon_size') final int? iconSize; @@ -23,6 +26,9 @@ class WindowsConfig { const WindowsConfig({ this.generate = false, this.imagePath, + @Deprecated( + 'Ignored. Windows .ico is generated with multiple sizes by default.', + ) this.iconSize, }); diff --git a/lib/windows/windows_icon_generator.dart b/lib/windows/windows_icon_generator.dart index a1db8c8b3c..9f94515fb4 100644 --- a/lib/windows/windows_icon_generator.dart +++ b/lib/windows/windows_icon_generator.dart @@ -11,6 +11,9 @@ class WindowsIconGenerator extends IconGenerator { WindowsIconGenerator(IconGeneratorContext context) : super(context, 'Windows'); + // Minimal, sensible defaults for Windows ICOs. + static const List _icoSizes = [16, 24, 32, 48, 256]; + @override Future createIcons() async { final imgFilePath = path.join( @@ -51,14 +54,13 @@ class WindowsIconGenerator extends IconGenerator { 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', + // DEPRECATED: read only to warn, then ignored. + // ignore: deprecated_member_use_from_same_package + if (windowsConfig.iconSize != null) { + context.logger.verbose( + 'DEPRECATED: `windows.icon_size` is ignored. The generator now ' + 'produces a multi-size .ico (16, 24, 32, 48, 256).', ); - return false; } final entitesToCheck = [ path.join(context.prefixPath, constants.windowsDirPath), @@ -80,13 +82,19 @@ class WindowsIconGenerator extends IconGenerator { } Future _generateIcon(Image image) async { - final favIcon = utils.createResizedImage( - context.windowsConfig!.iconSize ?? constants.windowsDefaultIconSize, - image, - ); + // Build a multi-frame ICO: one frame per target size. + Image? multi; + for (final sz in _icoSizes) { + final resized = utils.createResizedImage(sz, image); + if (multi == null) { + multi = resized; + } else { + multi.addFrame(resized); + } + } final favIconFile = await utils.createFileIfNotExist( path.join(context.prefixPath, constants.windowsIconFilePath), ); - await favIconFile.writeAsBytes(encodeIco(favIcon)); + await favIconFile.writeAsBytes(encodeIco(multi!)); } } diff --git a/test/abs/icon_generator_test.mocks.dart b/test/abs/icon_generator_test.mocks.dart index e5abdf1ed8..269a5236b4 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.4.4 from annotations +// Mocks generated by Mockito 5.4.6 from annotations // in flutter_launcher_icons/test/abs/icon_generator_test.dart. // Do not manually edit this file. @@ -18,6 +18,7 @@ import 'package:mockito/src/dummies.dart' as _i4; // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types diff --git a/test/config_test.dart b/test/config_test.dart index acd8b1f8b8..d55c224242 100644 --- a/test/config_test.dart +++ b/test/config_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: deprecated_member_use_from_same_package import 'package:flutter_launcher_icons/config/config.dart'; import 'package:flutter_launcher_icons/custom_exceptions.dart'; import 'package:path/path.dart' as path; diff --git a/test/macos/macos_icon_generator_test.mocks.dart b/test/macos/macos_icon_generator_test.mocks.dart index be401029fa..159156bde2 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.4.4 from annotations +// Mocks generated by Mockito 5.4.6 from annotations // in flutter_launcher_icons/test/macos/macos_icon_generator_test.dart. // Do not manually edit this file. @@ -18,6 +18,7 @@ import 'package:mockito/src/dummies.dart' as _i4; // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types diff --git a/test/main_test.dart b/test/main_test.dart index a2fb25848f..7d0a982262 100644 --- a/test/main_test.dart +++ b/test/main_test.dart @@ -32,19 +32,28 @@ void main() { test( 'iOS image list used to generate Contents.json for icon directory is correct size (with dark icon)', () { - expect(ios.createImageList('blah', 'dark-blah', null).length, 16 * 2 + 1); // 16 normal, 16 dark icons + 1 marketing icon + expect( + ios.createImageList('blah', 'dark-blah', null).length, + 16 * 2 + 1, + ); // 16 normal, 16 dark icons + 1 marketing icon }); test( 'iOS image list used to generate Contents.json for icon directory is correct size (with tinted icon)', () { - expect(ios.createImageList('blah', null, 'tinted-blah').length, 16 * 2 + 1); // 16 normal, 16 tinted icons + 1 marketing icon + expect( + ios.createImageList('blah', null, 'tinted-blah').length, + 16 * 2 + 1, + ); // 16 normal, 16 tinted icons + 1 marketing icon }); test( 'iOS image list used to generate Contents.json for icon directory is correct size (with dark and tinted icon)', () { - expect(ios.createImageList('blah', 'dark-blah', 'tinted-blah').length, 16 * 3 + 1); // 16 normal, 16 dark, 16 tinted icons + 1 marketing icon + expect( + ios.createImageList('blah', 'dark-blah', 'tinted-blah').length, + 16 * 3 + 1, + ); // 16 normal, 16 dark, 16 tinted icons + 1 marketing icon }); group('config file from args', () { diff --git a/test/package_version_test.dart b/test/package_version_test.dart index 50a746fcbe..70cc5885f5 100644 --- a/test/package_version_test.dart +++ b/test/package_version_test.dart @@ -1,13 +1,19 @@ +import 'dart:io'; + import 'package:flutter_launcher_icons/pubspec_parser.dart'; import 'package:flutter_launcher_icons/src/version.dart'; +import 'package:path/path.dart' as p; import 'package:test/test.dart'; void main() { - /// this helps avoid an issue where the pubspec version has been increased but + /// This helps avoid an issue where the pubspec version has been increased but /// build runner has not been run to up the version which is displayed - /// when flutter_launcher_icons is run + /// when flutter_launcher_icons is run. + /// + /// Note: We locate `pubspec.yaml` robustly (CWD can vary in CI/Windows). test('package version is correct', () { - final yamlMap = PubspecParser.fromPathToMap('pubspec.yaml'); + final pubspecPath = _locatePubspec(); + final yamlMap = PubspecParser.fromPathToMap(pubspecPath); final yamlVersion = yamlMap['version'] as String; expect( yamlVersion, @@ -17,3 +23,39 @@ void main() { ); }); } + +/// Locate the repo's pubspec regardless of current working dir. +/// Tries from CWD, then from this test file's directory, walking up. +String _locatePubspec() { + String? searchFrom(Directory start) { + var dir = start; + for (var i = 0; i < 25; i++) { + final candidate = p.join(dir.path, 'pubspec.yaml'); + if (File(candidate).existsSync()) { + return candidate; + } + final parent = dir.parent; + if (parent.path == dir.path) { + break; + } + dir = parent; + } + return null; + } + + final fromCwd = searchFrom(Directory.current); + if (fromCwd != null) { + return fromCwd; + } + + final scriptDir = File.fromUri(Platform.script).parent; + final fromScript = searchFrom(scriptDir); + if (fromScript != null) { + return fromScript; + } + + throw StateError( + 'Could not find pubspec.yaml. Tried starting from: ' + '${Directory.current.path} and ${scriptDir.path}', + ); +} diff --git a/test/windows/windows_icon_generator_test.dart b/test/windows/windows_icon_generator_test.dart index cf4903eda4..532c75e386 100644 --- a/test/windows/windows_icon_generator_test.dart +++ b/test/windows/windows_icon_generator_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: deprecated_member_use_from_same_package import 'dart:io'; import 'package:flutter_launcher_icons/abs/icon_generator.dart'; @@ -85,15 +86,28 @@ void main() { }); test( - 'should return false when windows.icon_size is not between 48 and 256', - () { + 'should warn (deprecated) but not fail when windows.icon_size is set', + () async { + // Ensure required filesystem entities exist so any failure is NOT due to missing files. + await d.dir('fli_test', [ + d.dir('windows'), + d.file('app_icon.png', testImageFile.readAsBytesSync()), + ]).create(); + await expectLater( + d.dir('fli_test', [ + d.dir('windows'), + d.file('app_icon.png', anything), + ]).validate(), + completes, + ); + + // Set any value; it’s ignored but should trigger a deprecation log. when(mockWindowsConfig.iconSize).thenReturn(40); - expect(generator.validateRequirements(), isFalse); - verify(mockWindowsConfig.iconSize).called(equals(3)); - when(mockWindowsConfig.iconSize).thenReturn(257); - expect(generator.validateRequirements(), isFalse); - verify(mockWindowsConfig.iconSize).called(equals(4)); + expect(generator.validateRequirements(), isTrue); + + // We logged a deprecation notice (non-fatal). + verify(mockLogger.verbose(argThat(contains('DEPRECATED')))).called(1); }); test('should return false when windows dir does not exist', () async { diff --git a/test/windows/windows_icon_generator_test.mocks.dart b/test/windows/windows_icon_generator_test.mocks.dart index eb40839341..0db34a4a16 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.4.4 from annotations +// Mocks generated by Mockito 5.4.6 from annotations // in flutter_launcher_icons/test/windows/windows_icon_generator_test.dart. // Do not manually edit this file. @@ -18,6 +18,7 @@ import 'package:mockito/src/dummies.dart' as _i4; // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types