mirror of
https://github.com/flutter/flutter.git
synced 2026-01-20 20:55:29 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
620 lines
19 KiB
Dart
620 lines
19 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// Regenerates a Dart file with a class containing IconData constants.
|
|
// See https://github.com/flutter/flutter/blob/main/docs/libraries/material/Updating-Material-Design-Fonts-%26-Icons.md
|
|
// Should be idempotent with:
|
|
// dart dev/tools/update_icons.dart --new-codepoints bin/cache/artifacts/material_fonts/codepoints
|
|
|
|
import 'dart:collection';
|
|
import 'dart:convert' show LineSplitter;
|
|
import 'dart:io';
|
|
|
|
import 'package:args/args.dart';
|
|
import 'package:meta/meta.dart';
|
|
import 'package:path/path.dart' as path;
|
|
|
|
const String _iconsPathOption = 'icons';
|
|
const String _iconsTemplatePathOption = 'icons-template';
|
|
const String _newCodepointsPathOption = 'new-codepoints';
|
|
const String _oldCodepointsPathOption = 'old-codepoints';
|
|
const String _fontFamilyOption = 'font-family';
|
|
const String _possibleStyleSuffixesOption = 'style-suffixes';
|
|
const String _classNameOption = 'class-name';
|
|
const String _enforceSafetyChecks = 'enforce-safety-checks';
|
|
const String _dryRunOption = 'dry-run';
|
|
|
|
const String _defaultIconsPath = 'packages/flutter/lib/src/material/icons.dart';
|
|
const String _defaultNewCodepointsPath = 'codepoints';
|
|
const String _defaultOldCodepointsPath = 'bin/cache/artifacts/material_fonts/codepoints';
|
|
const String _defaultFontFamily = 'MaterialIcons';
|
|
const List<String> _defaultPossibleStyleSuffixes = <String>['_outlined', '_rounded', '_sharp'];
|
|
const String _defaultClassName = 'Icons';
|
|
const String _defaultDemoFilePath = '/tmp/new_icons_demo.dart';
|
|
|
|
const String _beginGeneratedMark = '// BEGIN GENERATED ICONS';
|
|
const String _endGeneratedMark = '// END GENERATED ICONS';
|
|
const String _beginPlatformAdaptiveGeneratedMark = '// BEGIN GENERATED PLATFORM ADAPTIVE ICONS';
|
|
const String _endPlatformAdaptiveGeneratedMark = '// END GENERATED PLATFORM ADAPTIVE ICONS';
|
|
|
|
const Map<String, List<String>> _platformAdaptiveIdentifiers = <String, List<String>>{
|
|
// Mapping of Flutter IDs to an Android/agnostic ID and an iOS ID.
|
|
// Flutter IDs can be anything, but should be chosen to be agnostic.
|
|
'arrow_back': <String>['arrow_back', 'arrow_back_ios'],
|
|
'arrow_forward': <String>['arrow_forward', 'arrow_forward_ios'],
|
|
'flip_camera': <String>['flip_camera_android', 'flip_camera_ios'],
|
|
'more': <String>['more_vert', 'more_horiz'],
|
|
'share': <String>['share', 'ios_share'],
|
|
};
|
|
|
|
// Rewrite certain Flutter IDs (numbers) using prefix matching.
|
|
const Map<String, String> _identifierPrefixRewrites = <String, String>{
|
|
'1': 'one_',
|
|
'2': 'two_',
|
|
'3': 'three_',
|
|
'4': 'four_',
|
|
'5': 'five_',
|
|
'6': 'six_',
|
|
'7': 'seven_',
|
|
'8': 'eight_',
|
|
'9': 'nine_',
|
|
'10': 'ten_',
|
|
'11': 'eleven_',
|
|
'12': 'twelve_',
|
|
'13': 'thirteen_',
|
|
'14': 'fourteen_',
|
|
'15': 'fifteen_',
|
|
'16': 'sixteen_',
|
|
'17': 'seventeen_',
|
|
'18': 'eighteen_',
|
|
'19': 'nineteen_',
|
|
'20': 'twenty_',
|
|
'21': 'twenty_one_',
|
|
'22': 'twenty_two_',
|
|
'23': 'twenty_three_',
|
|
'24': 'twenty_four_',
|
|
'30': 'thirty_',
|
|
'60': 'sixty_',
|
|
'123': 'onetwothree',
|
|
'360': 'threesixty',
|
|
'2d': 'twod',
|
|
'3d': 'threed',
|
|
'3d_rotation': 'threed_rotation',
|
|
};
|
|
|
|
// Rewrite certain Flutter IDs (reserved keywords) using exact matching.
|
|
const Map<String, String> _identifierExactRewrites = <String, String>{
|
|
'class': 'class_',
|
|
'new': 'new_',
|
|
'switch': 'switch_',
|
|
'try': 'try_sms_star',
|
|
'door_back': 'door_back_door',
|
|
'door_front': 'door_front_door',
|
|
};
|
|
|
|
const Set<String> _iconsMirroredWhenRTL = <String>{
|
|
// This list is obtained from:
|
|
// https://developers.google.com/fonts/docs/material_icons#which_icons_should_be_mirrored_for_rtl
|
|
'arrow_back',
|
|
'arrow_back_ios',
|
|
'arrow_forward',
|
|
'arrow_forward_ios',
|
|
'arrow_left',
|
|
'arrow_right',
|
|
'assignment',
|
|
'assignment_return',
|
|
'backspace',
|
|
'battery_unknown',
|
|
'call_made',
|
|
'call_merge',
|
|
'call_missed',
|
|
'call_missed_outgoing',
|
|
'call_received',
|
|
'call_split',
|
|
'chevron_left',
|
|
'chevron_right',
|
|
'chrome_reader_mode',
|
|
'device_unknown',
|
|
'dvr',
|
|
'event_note',
|
|
'featured_play_list',
|
|
'featured_video',
|
|
'first_page',
|
|
'flight_land',
|
|
'flight_takeoff',
|
|
'format_indent_decrease',
|
|
'format_indent_increase',
|
|
'format_list_bulleted',
|
|
'forward',
|
|
'functions',
|
|
'help',
|
|
'help_outline',
|
|
'input',
|
|
'keyboard_backspace',
|
|
'keyboard_tab',
|
|
'label',
|
|
'label_important',
|
|
'label_outline',
|
|
'last_page',
|
|
'launch',
|
|
'list',
|
|
'live_help',
|
|
'mobile_screen_share',
|
|
'multiline_chart',
|
|
'navigate_before',
|
|
'navigate_next',
|
|
'next_week',
|
|
'note',
|
|
'open_in',
|
|
'playlist_add',
|
|
'queue_music',
|
|
'redo',
|
|
'reply',
|
|
'reply_all',
|
|
'screen_share',
|
|
'send',
|
|
'short_text',
|
|
'show_chart',
|
|
'sort',
|
|
'star_half',
|
|
'subject',
|
|
'trending_flat',
|
|
'toc',
|
|
'trending_down',
|
|
'trending_up',
|
|
'undo',
|
|
'view_list',
|
|
'view_quilt',
|
|
'wrap_text',
|
|
};
|
|
|
|
void main(List<String> args) {
|
|
// If we're run from the `tools` dir, set the cwd to the repo root.
|
|
if (path.basename(Directory.current.path) == 'tools') {
|
|
Directory.current = Directory.current.parent.parent;
|
|
}
|
|
|
|
final ArgResults argResults = _handleArguments(args);
|
|
|
|
final iconsFile = File(path.normalize(path.absolute(argResults[_iconsPathOption] as String)));
|
|
if (!iconsFile.existsSync()) {
|
|
stderr.writeln('Error: Icons file not found: ${iconsFile.path}');
|
|
exit(1);
|
|
}
|
|
final iconsTemplateFile = File(
|
|
path.normalize(path.absolute(argResults[_iconsTemplatePathOption] as String)),
|
|
);
|
|
if (!iconsTemplateFile.existsSync()) {
|
|
stderr.writeln('Error: Icons template file not found: ${iconsTemplateFile.path}');
|
|
exit(1);
|
|
}
|
|
final newCodepointsFile = File(argResults[_newCodepointsPathOption] as String);
|
|
if (!newCodepointsFile.existsSync()) {
|
|
stderr.writeln('Error: New codepoints file not found: ${newCodepointsFile.path}');
|
|
exit(1);
|
|
}
|
|
final oldCodepointsFile = File(argResults[_oldCodepointsPathOption] as String);
|
|
if (!oldCodepointsFile.existsSync()) {
|
|
stderr.writeln('Error: Old codepoints file not found: ${oldCodepointsFile.path}');
|
|
exit(1);
|
|
}
|
|
|
|
final String newCodepointsString = newCodepointsFile.readAsStringSync();
|
|
final Map<String, String> newTokenPairMap = stringToTokenPairMap(newCodepointsString);
|
|
|
|
final String oldCodepointsString = oldCodepointsFile.readAsStringSync();
|
|
final Map<String, String> oldTokenPairMap = stringToTokenPairMap(oldCodepointsString);
|
|
|
|
stderr.writeln('Performing safety checks');
|
|
final bool isSuperset = testIsSuperset(newTokenPairMap, oldTokenPairMap);
|
|
final bool isStable = testIsStable(newTokenPairMap, oldTokenPairMap);
|
|
if ((!isSuperset || !isStable) && argResults[_enforceSafetyChecks] as bool) {
|
|
exit(1);
|
|
}
|
|
final String iconsTemplateContents = iconsTemplateFile.readAsStringSync();
|
|
|
|
stderr.writeln(
|
|
"Generating icons ${argResults[_dryRunOption] as bool ? '' : 'to ${iconsFile.path}'}",
|
|
);
|
|
final String newIconsContents = _regenerateIconsFile(
|
|
iconsTemplateContents,
|
|
newTokenPairMap,
|
|
argResults[_fontFamilyOption] as String,
|
|
argResults[_classNameOption] as String,
|
|
argResults[_enforceSafetyChecks] as bool,
|
|
);
|
|
|
|
if (argResults[_dryRunOption] as bool) {
|
|
stdout.write(newIconsContents);
|
|
} else {
|
|
iconsFile.writeAsStringSync(newIconsContents);
|
|
|
|
final sortedNewTokenPairMap = SplayTreeMap<String, String>.of(newTokenPairMap);
|
|
_regenerateCodepointsFile(oldCodepointsFile, sortedNewTokenPairMap);
|
|
|
|
sortedNewTokenPairMap.removeWhere(
|
|
(String key, String value) => oldTokenPairMap.containsKey(key),
|
|
);
|
|
_generateIconDemo(File(_defaultDemoFilePath), sortedNewTokenPairMap);
|
|
}
|
|
}
|
|
|
|
ArgResults _handleArguments(List<String> args) {
|
|
final argParser = ArgParser()
|
|
..addOption(
|
|
_iconsPathOption,
|
|
defaultsTo: _defaultIconsPath,
|
|
help: 'Location of the material icons file',
|
|
)
|
|
..addOption(
|
|
_iconsTemplatePathOption,
|
|
defaultsTo: _defaultIconsPath,
|
|
help: 'Location of the material icons file template. Usually the same as --$_iconsPathOption',
|
|
)
|
|
..addOption(
|
|
_newCodepointsPathOption,
|
|
defaultsTo: _defaultNewCodepointsPath,
|
|
help: 'Location of the new codepoints directory',
|
|
)
|
|
..addOption(
|
|
_oldCodepointsPathOption,
|
|
defaultsTo: _defaultOldCodepointsPath,
|
|
help: 'Location of the existing codepoints directory',
|
|
)
|
|
..addOption(
|
|
_fontFamilyOption,
|
|
defaultsTo: _defaultFontFamily,
|
|
help: 'The font family to use for the IconData constants',
|
|
)
|
|
..addMultiOption(
|
|
_possibleStyleSuffixesOption,
|
|
defaultsTo: _defaultPossibleStyleSuffixes,
|
|
help:
|
|
'A comma-separated list of suffixes (typically an optional '
|
|
'family + a style) e.g. _outlined, _monoline_filled',
|
|
)
|
|
..addOption(
|
|
_classNameOption,
|
|
defaultsTo: _defaultClassName,
|
|
help: 'The containing class for all icons',
|
|
)
|
|
..addFlag(
|
|
_enforceSafetyChecks,
|
|
defaultsTo: true,
|
|
help: 'Whether to exit if safety checks fail (e.g. codepoints are missing or unstable',
|
|
)
|
|
..addFlag(_dryRunOption);
|
|
argParser.addFlag(
|
|
'help',
|
|
abbr: 'h',
|
|
negatable: false,
|
|
callback: (bool help) {
|
|
if (help) {
|
|
print(argParser.usage);
|
|
exit(1);
|
|
}
|
|
},
|
|
);
|
|
return argParser.parse(args);
|
|
}
|
|
|
|
Map<String, String> stringToTokenPairMap(String codepointData) {
|
|
final Iterable<String> cleanData = LineSplitter.split(
|
|
codepointData,
|
|
).map((String line) => line.trim()).where((String line) => line.isNotEmpty);
|
|
|
|
final pairs = <String, String>{};
|
|
|
|
for (final line in cleanData) {
|
|
final List<String> tokens = line.split(' ');
|
|
if (tokens.length != 2) {
|
|
throw FormatException('Unexpected codepoint data: $line');
|
|
}
|
|
pairs.putIfAbsent(tokens[0], () => tokens[1]);
|
|
}
|
|
|
|
return pairs;
|
|
}
|
|
|
|
String _regenerateIconsFile(
|
|
String templateFileContents,
|
|
Map<String, String> tokenPairMap,
|
|
String fontFamily,
|
|
String className,
|
|
bool enforceSafetyChecks,
|
|
) {
|
|
final List<Icon> newIcons = tokenPairMap.entries
|
|
.map(
|
|
(MapEntry<String, String> entry) =>
|
|
Icon(entry, fontFamily: fontFamily, className: className),
|
|
)
|
|
.toList();
|
|
newIcons.sort((Icon a, Icon b) => a._compareTo(b));
|
|
|
|
final buf = StringBuffer();
|
|
var generating = false;
|
|
|
|
for (final String line in LineSplitter.split(templateFileContents)) {
|
|
if (!generating) {
|
|
buf.writeln(line);
|
|
}
|
|
|
|
// Generate for PlatformAdaptiveIcons
|
|
if (line.contains(_beginPlatformAdaptiveGeneratedMark)) {
|
|
generating = true;
|
|
final platformAdaptiveDeclarations = <String>[];
|
|
_platformAdaptiveIdentifiers.forEach((String flutterId, List<String> ids) {
|
|
// Automatically finds and generates all icon declarations.
|
|
for (final style in <String>['', '_outlined', '_rounded', '_sharp']) {
|
|
try {
|
|
final Icon agnosticIcon = newIcons.firstWhere(
|
|
(Icon icon) => icon.id == '${ids[0]}$style',
|
|
orElse: () => throw ids[0],
|
|
);
|
|
final Icon iOSIcon = newIcons.firstWhere(
|
|
(Icon icon) => icon.id == '${ids[1]}$style',
|
|
orElse: () => throw ids[1],
|
|
);
|
|
platformAdaptiveDeclarations.add(
|
|
agnosticIcon.platformAdaptiveDeclaration('$flutterId$style', iOSIcon),
|
|
);
|
|
} catch (e) {
|
|
if (style == '') {
|
|
// Throw an error for baseline icons.
|
|
stderr.writeln("❌ Platform adaptive icon '$e' not found.");
|
|
if (enforceSafetyChecks) {
|
|
stderr.writeln('Safety checks failed');
|
|
exit(1);
|
|
}
|
|
} else {
|
|
// Ignore errors for styled icons since some don't exist.
|
|
}
|
|
}
|
|
}
|
|
});
|
|
buf.write(platformAdaptiveDeclarations.join());
|
|
} else if (line.contains(_endPlatformAdaptiveGeneratedMark)) {
|
|
generating = false;
|
|
buf.writeln(line);
|
|
}
|
|
|
|
// Generate for Icons
|
|
if (line.contains(_beginGeneratedMark)) {
|
|
generating = true;
|
|
final String iconDeclarationsString = newIcons
|
|
.map((Icon icon) => icon.fullDeclaration)
|
|
.join();
|
|
buf.write(iconDeclarationsString);
|
|
} else if (line.contains(_endGeneratedMark)) {
|
|
generating = false;
|
|
buf.writeln(line);
|
|
}
|
|
}
|
|
return buf.toString();
|
|
}
|
|
|
|
@visibleForTesting
|
|
bool testIsSuperset(Map<String, String> newCodepoints, Map<String, String> oldCodepoints) {
|
|
final Set<String> newCodepointsSet = newCodepoints.keys.toSet();
|
|
final Set<String> oldCodepointsSet = oldCodepoints.keys.toSet();
|
|
|
|
final int diff = newCodepointsSet.length - oldCodepointsSet.length;
|
|
if (diff > 0) {
|
|
stderr.writeln('🆕 $diff new codepoints: ${newCodepointsSet.difference(oldCodepointsSet)}');
|
|
}
|
|
if (!newCodepointsSet.containsAll(oldCodepointsSet)) {
|
|
stderr.writeln(
|
|
'❌ new codepoints file does not contain all ${oldCodepointsSet.length} '
|
|
'existing codepoints. Missing: ${oldCodepointsSet.difference(newCodepointsSet)}',
|
|
);
|
|
return false;
|
|
} else {
|
|
stderr.writeln(
|
|
'✅ new codepoints file contains all ${oldCodepointsSet.length} existing codepoints',
|
|
);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@visibleForTesting
|
|
bool testIsStable(Map<String, String> newCodepoints, Map<String, String> oldCodepoints) {
|
|
final int oldCodepointsCount = oldCodepoints.length;
|
|
final unstable = <String>[
|
|
for (final MapEntry<String, String>(:String key, :String value) in oldCodepoints.entries)
|
|
if (newCodepoints.containsKey(key) && value != newCodepoints[key]) key,
|
|
];
|
|
|
|
if (unstable.isNotEmpty) {
|
|
stderr.writeln(
|
|
'❌ out of $oldCodepointsCount existing codepoints, ${unstable.length} were unstable: $unstable',
|
|
);
|
|
return false;
|
|
} else {
|
|
stderr.writeln('✅ all existing $oldCodepointsCount codepoints are stable');
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void _regenerateCodepointsFile(File oldCodepointsFile, Map<String, String> tokenPairMap) {
|
|
stderr.writeln('Regenerating old codepoints file ${oldCodepointsFile.path}');
|
|
|
|
final buf = StringBuffer();
|
|
tokenPairMap.forEach((String key, String value) => buf.writeln('$key $value'));
|
|
oldCodepointsFile.writeAsStringSync(buf.toString());
|
|
}
|
|
|
|
void _generateIconDemo(File demoFilePath, Map<String, String> tokenPairMap) {
|
|
if (tokenPairMap.isEmpty) {
|
|
stderr.writeln('No new icons, skipping generating icon demo');
|
|
return;
|
|
}
|
|
stderr.writeln('Generating icon demo at $_defaultDemoFilePath');
|
|
|
|
final newIconUsages = StringBuffer();
|
|
for (final MapEntry<String, String> entry in tokenPairMap.entries) {
|
|
newIconUsages.writeln(Icon(entry).usage);
|
|
}
|
|
final demoFileContents =
|
|
'''
|
|
import 'package:flutter/material.dart';
|
|
|
|
void main() => runApp(const IconDemo());
|
|
|
|
class IconDemo extends StatelessWidget {
|
|
const IconDemo({ Key? key }) : super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
debugShowCheckedModeBanner: false,
|
|
home: Scaffold(
|
|
body: Wrap(
|
|
children: const [
|
|
$newIconUsages
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
''';
|
|
demoFilePath.writeAsStringSync(demoFileContents);
|
|
}
|
|
|
|
class Icon {
|
|
// Parse tokenPair (e.g. {"6_ft_apart_outlined": "e004"}).
|
|
Icon(
|
|
MapEntry<String, String> tokenPair, {
|
|
this.fontFamily = _defaultFontFamily,
|
|
this.possibleStyleSuffixes = _defaultPossibleStyleSuffixes,
|
|
this.className = _defaultClassName,
|
|
}) {
|
|
id = tokenPair.key;
|
|
hexCodepoint = tokenPair.value;
|
|
|
|
// Determine family and HTML class suffix for Dartdoc.
|
|
if (id.endsWith('_gm_outlined')) {
|
|
dartdocFamily = 'GM';
|
|
dartdocHtmlSuffix = '-outlined';
|
|
} else if (id.endsWith('_gm_filled')) {
|
|
dartdocFamily = 'GM';
|
|
dartdocHtmlSuffix = '-filled';
|
|
} else if (id.endsWith('_monoline_outlined')) {
|
|
dartdocFamily = 'Monoline';
|
|
dartdocHtmlSuffix = '-outlined';
|
|
} else if (id.endsWith('_monoline_filled')) {
|
|
dartdocFamily = 'Monoline';
|
|
dartdocHtmlSuffix = '-filled';
|
|
} else {
|
|
dartdocFamily = 'material';
|
|
if (id.endsWith('_baseline')) {
|
|
id = _removeLast(id, '_baseline');
|
|
dartdocHtmlSuffix = '';
|
|
} else if (id.endsWith('_outlined')) {
|
|
dartdocHtmlSuffix = '-outlined';
|
|
} else if (id.endsWith('_rounded')) {
|
|
dartdocHtmlSuffix = '-round';
|
|
} else if (id.endsWith('_sharp')) {
|
|
dartdocHtmlSuffix = '-sharp';
|
|
}
|
|
}
|
|
|
|
_generateShortId();
|
|
_generateFlutterId();
|
|
}
|
|
|
|
late String id; // e.g. 5g, 5g_outlined, 5g_rounded, 5g_sharp
|
|
late String shortId; // e.g. 5g
|
|
late String flutterId; // e.g. five_g, five_g_outlined, five_g_rounded, five_g_sharp
|
|
late String hexCodepoint; // e.g. e547
|
|
late String dartdocFamily; // e.g. material
|
|
late String dartdocHtmlSuffix = ''; // The suffix for the 'material-icons' HTML class.
|
|
String fontFamily; // The IconData font family.
|
|
List<String>
|
|
possibleStyleSuffixes; // A list of possible suffixes e.g. _outlined, _monoline_filled.
|
|
String className; // The containing class.
|
|
|
|
String get name => shortId.replaceAll('_', ' ').trim();
|
|
|
|
String get style =>
|
|
dartdocHtmlSuffix == '' ? '' : ' (${dartdocHtmlSuffix.replaceFirst('-', '')})';
|
|
|
|
String get dartDoc =>
|
|
'<i class="material-icons$dartdocHtmlSuffix md-36">$shortId</i> — $dartdocFamily icon named "$name"$style';
|
|
|
|
String get usage => 'Icon($className.$flutterId),';
|
|
|
|
bool get isMirroredInRTL {
|
|
// Remove common suffixes (e.g. "_new" or "_alt") from the shortId.
|
|
final String normalizedShortId = shortId.replaceAll(RegExp(r'_(new|alt|off|on)$'), '');
|
|
return _iconsMirroredWhenRTL.any(
|
|
(String shortIdMirroredWhenRTL) => normalizedShortId == shortIdMirroredWhenRTL,
|
|
);
|
|
}
|
|
|
|
String get declaration =>
|
|
"static const IconData $flutterId = IconData(0x$hexCodepoint, fontFamily: '$fontFamily'${isMirroredInRTL ? ', matchTextDirection: true' : ''});";
|
|
|
|
String get fullDeclaration =>
|
|
'''
|
|
|
|
/// $dartDoc.
|
|
$declaration
|
|
''';
|
|
|
|
String platformAdaptiveDeclaration(String fullFlutterId, Icon iOSIcon) =>
|
|
'''
|
|
|
|
/// Platform-adaptive icon for $dartDoc and ${iOSIcon.dartDoc}.;
|
|
IconData get $fullFlutterId => !_isCupertino() ? $className.$flutterId : $className.${iOSIcon.flutterId};
|
|
''';
|
|
|
|
@override
|
|
String toString() => id;
|
|
|
|
/// Analogous to [String.compareTo]
|
|
int _compareTo(Icon b) {
|
|
if (shortId == b.shortId) {
|
|
// Sort a regular icon before its variants.
|
|
return id.length - b.id.length;
|
|
}
|
|
return shortId.compareTo(b.shortId);
|
|
}
|
|
|
|
static String _removeLast(String string, String toReplace) {
|
|
return string.replaceAll(RegExp('$toReplace\$'), '');
|
|
}
|
|
|
|
/// See [shortId].
|
|
void _generateShortId() {
|
|
shortId = id;
|
|
for (final String styleSuffix in possibleStyleSuffixes) {
|
|
shortId = _removeLast(shortId, styleSuffix);
|
|
if (shortId != id) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// See [flutterId].
|
|
void _generateFlutterId() {
|
|
flutterId = id;
|
|
// Exact identifier rewrites.
|
|
for (final MapEntry<String, String> rewritePair in _identifierExactRewrites.entries) {
|
|
if (shortId == rewritePair.key) {
|
|
flutterId = id.replaceFirst(rewritePair.key, _identifierExactRewrites[rewritePair.key]!);
|
|
}
|
|
}
|
|
// Prefix identifier rewrites.
|
|
for (final MapEntry<String, String> rewritePair in _identifierPrefixRewrites.entries) {
|
|
if (id.startsWith(rewritePair.key)) {
|
|
flutterId = id.replaceFirst(rewritePair.key, _identifierPrefixRewrites[rewritePair.key]!);
|
|
}
|
|
}
|
|
|
|
// Prevent double underscores.
|
|
flutterId = flutterId.replaceAll('__', '_');
|
|
}
|
|
}
|