mirror of
https://github.com/flutter/flutter
synced 2024-10-01 05:54:08 +00:00
62586dc09d
Part of https://github.com/flutter/flutter/issues/145009 Finishes updating links in the repo that pointed to the old wiki
585 lines
19 KiB
Dart
585 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 File 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 File 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 File newCodepointsFile = File(argResults[_newCodepointsPathOption] as String);
|
|
if (!newCodepointsFile.existsSync()) {
|
|
stderr.writeln('Error: New codepoints file not found: ${newCodepointsFile.path}');
|
|
exit(1);
|
|
}
|
|
final File 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 SplayTreeMap<String, String> 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 = 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 Map<String, String> pairs = <String, String>{};
|
|
|
|
for (final String 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 StringBuffer buf = StringBuffer();
|
|
bool 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 List<String> platformAdaptiveDeclarations = <String>[];
|
|
_platformAdaptiveIdentifiers.forEach((String flutterId, List<String> ids) {
|
|
// Automatically finds and generates all icon declarations.
|
|
for (final String 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 List<String> 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 StringBuffer 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 StringBuffer newIconUsages = StringBuffer();
|
|
for (final MapEntry<String, String> entry in tokenPairMap.entries) {
|
|
newIconUsages.writeln(Icon(entry).usage);
|
|
}
|
|
final String 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('__', '_');
|
|
}
|
|
}
|