Unverified Commit 2f17d4e0 authored by Pierre-Louis's avatar Pierre-Louis Committed by GitHub

Adaptive icons (#69119)

* refactor update_icons

* fix trailing space

* address feedback

* add platform adaptive icons

* fix merge conflict

* Update dartdoc

* Address feedback

* Specify types

* Add tests

* fix indentation

* Remove trailing space

* Remove isCupertino static bool
parent c1042314
......@@ -20,8 +20,20 @@ const String _defaultNewCodepointsPath = 'codepoints';
const String _defaultOldCodepointsPath = 'bin/cache/artifacts/material_fonts/codepoints';
const String _defaultIconsPath = 'packages/flutter/lib/src/material/icons.dart';
const String _beginGeneratedMark = '// BEGIN GENERATED';
const String _endGeneratedMark = '// END GENERATED';
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'],
};
const Map<String, String> _identifierRewrites = <String, String>{
'360': 'threesixty',
......@@ -231,20 +243,54 @@ Map<String, String> stringToTokenPairMap(String codepointData) {
// Do not make this method private as it is used by g3 roll.
String regenerateIconsFile(String iconData, Map<String, String> tokenPairMap) {
final Iterable<_Icon> newIcons = tokenPairMap.entries.map((MapEntry<String, String> entry) => _Icon(entry));
final StringBuffer buf = StringBuffer();
bool generating = false;
for (final String line in LineSplitter.split(iconData)) {
if (!generating) {
buf.writeln(line);
}
if (line.contains(_beginGeneratedMark)) {
// Generate for _PlatformAdaptiveIcons
if (line.contains(_beginPlatformAdaptiveGeneratedMark)) {
generating = true;
final String iconDeclarationsString = <String>[
for (MapEntry<String, String> entry in tokenPairMap.entries)
_Icon(entry).fullDeclaration
].join();
final List<String> platformAdaptiveDeclarations = <String>[];
_platformAdaptiveIdentifiers.forEach((String flutterId, List<String> ids) {
// Automatically finds and generates styled icon declarations.
for (final IconStyle iconStyle in IconStyle.values) {
final String style = iconStyle.idSuffix();
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(_Icon.platformAdaptiveDeclaration('$flutterId$style', agnosticIcon, iOSIcon));
} catch (e) {
if (iconStyle == IconStyle.regular) {
stderr.writeln("Error while generating platformAdaptiveDeclarations: Icon '$e' not found.");
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;
......@@ -275,9 +321,9 @@ enum IconStyle {
sharp,
}
extension IconStyleSuffix on IconStyle {
extension IconStyleExtension on IconStyle {
// The suffix for the 'material-icons' HTML class.
String suffix() {
String htmlSuffix() {
switch (this) {
case IconStyle.outlined: return '-outlined';
case IconStyle.rounded: return '-round';
......@@ -285,6 +331,17 @@ extension IconStyleSuffix on IconStyle {
default: return '';
}
}
// The suffix for icon ids.
String idSuffix() {
switch (this) {
case IconStyle.outlined:
case IconStyle.rounded:
case IconStyle.sharp:
return '_' + toString().split('.').last;
default: return '';
}
}
}
class _Icon {
......@@ -332,12 +389,25 @@ class _Icon {
String get name => id.replaceAll('_', ' ');
String get dartDoc =>
'/// <i class="material-icons${style.suffix()} md-36">$shortId</i> &#x2014; material icon named "$name".';
'<i class="material-icons${style.htmlSuffix()} md-36">$shortId</i> &#x2014; material icon named "$name"';
String get declaration =>
"static const IconData $flutterId = IconData(0x$hexCodepoint, fontFamily: 'MaterialIcons'$mirroredInRTL);";
String get fullDeclaration => '''\n $dartDoc\n $declaration\n''';
String get fullDeclaration => '''
/// $dartDoc.
$declaration
''';
static String platformAdaptiveDeclaration(String flutterId, _Icon agnosticIcon, _Icon iOSIcon) => '''
/// Platform-adaptive icon for ${agnosticIcon.dartDoc} and ${iOSIcon.dartDoc}.;
IconData get $flutterId => !_isCupertino() ? Icons.${agnosticIcon.flutterId} : Icons.${iOSIcon.flutterId};
''';
@override
String toString() => id;
}
// Replace the old codepoints file with the new.
......
......@@ -2,8 +2,81 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart' show defaultTargetPlatform;
import 'package:flutter/widgets.dart';
// ignore_for_file: non_constant_identifier_names
class _PlatformAdaptiveIcons {
static bool _isCupertino() {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return false;
case TargetPlatform.iOS:
case TargetPlatform.macOS:
return true;
}
}
// Generated code: do not hand-edit.
// See https://github.com/flutter/flutter/wiki/Updating-Material-Design-Fonts
// BEGIN GENERATED PLATFORM ADAPTIVE ICONS
/// Platform-adaptive icon for <i class="material-icons md-36">arrow_back</i> &#x2014; material icon named "arrow back" and <i class="material-icons md-36">arrow_back_ios</i> &#x2014; material icon named "arrow back ios".;
IconData get arrow_back => !_isCupertino() ? Icons.arrow_back : Icons.arrow_back_ios;
/// Platform-adaptive icon for <i class="material-icons-outlined md-36">arrow_back</i> &#x2014; material icon named "arrow back outlined" and <i class="material-icons-outlined md-36">arrow_back_ios</i> &#x2014; material icon named "arrow back ios outlined".;
IconData get arrow_back_outlined => !_isCupertino() ? Icons.arrow_back_outlined : Icons.arrow_back_ios_outlined;
/// Platform-adaptive icon for <i class="material-icons-round md-36">arrow_back</i> &#x2014; material icon named "arrow back rounded" and <i class="material-icons-round md-36">arrow_back_ios</i> &#x2014; material icon named "arrow back ios rounded".;
IconData get arrow_back_rounded => !_isCupertino() ? Icons.arrow_back_rounded : Icons.arrow_back_ios_rounded;
/// Platform-adaptive icon for <i class="material-icons-sharp md-36">arrow_back</i> &#x2014; material icon named "arrow back sharp" and <i class="material-icons-sharp md-36">arrow_back_ios</i> &#x2014; material icon named "arrow back ios sharp".;
IconData get arrow_back_sharp => !_isCupertino() ? Icons.arrow_back_sharp : Icons.arrow_back_ios_sharp;
/// Platform-adaptive icon for <i class="material-icons md-36">arrow_forward</i> &#x2014; material icon named "arrow forward" and <i class="material-icons md-36">arrow_forward_ios</i> &#x2014; material icon named "arrow forward ios".;
IconData get arrow_forward => !_isCupertino() ? Icons.arrow_forward : Icons.arrow_forward_ios;
/// Platform-adaptive icon for <i class="material-icons-outlined md-36">arrow_forward</i> &#x2014; material icon named "arrow forward outlined" and <i class="material-icons-outlined md-36">arrow_forward_ios</i> &#x2014; material icon named "arrow forward ios outlined".;
IconData get arrow_forward_outlined => !_isCupertino() ? Icons.arrow_forward_outlined : Icons.arrow_forward_ios_outlined;
/// Platform-adaptive icon for <i class="material-icons-round md-36">arrow_forward</i> &#x2014; material icon named "arrow forward rounded" and <i class="material-icons-round md-36">arrow_forward_ios</i> &#x2014; material icon named "arrow forward ios rounded".;
IconData get arrow_forward_rounded => !_isCupertino() ? Icons.arrow_forward_rounded : Icons.arrow_forward_ios_rounded;
/// Platform-adaptive icon for <i class="material-icons-sharp md-36">arrow_forward</i> &#x2014; material icon named "arrow forward sharp" and <i class="material-icons-sharp md-36">arrow_forward_ios</i> &#x2014; material icon named "arrow forward ios sharp".;
IconData get arrow_forward_sharp => !_isCupertino() ? Icons.arrow_forward_sharp : Icons.arrow_forward_ios_sharp;
/// Platform-adaptive icon for <i class="material-icons md-36">flip_camera_android</i> &#x2014; material icon named "flip camera android" and <i class="material-icons md-36">flip_camera_ios</i> &#x2014; material icon named "flip camera ios".;
IconData get flip_camera => !_isCupertino() ? Icons.flip_camera_android : Icons.flip_camera_ios;
/// Platform-adaptive icon for <i class="material-icons-outlined md-36">flip_camera_android</i> &#x2014; material icon named "flip camera android outlined" and <i class="material-icons-outlined md-36">flip_camera_ios</i> &#x2014; material icon named "flip camera ios outlined".;
IconData get flip_camera_outlined => !_isCupertino() ? Icons.flip_camera_android_outlined : Icons.flip_camera_ios_outlined;
/// Platform-adaptive icon for <i class="material-icons-round md-36">flip_camera_android</i> &#x2014; material icon named "flip camera android rounded" and <i class="material-icons-round md-36">flip_camera_ios</i> &#x2014; material icon named "flip camera ios rounded".;
IconData get flip_camera_rounded => !_isCupertino() ? Icons.flip_camera_android_rounded : Icons.flip_camera_ios_rounded;
/// Platform-adaptive icon for <i class="material-icons-sharp md-36">flip_camera_android</i> &#x2014; material icon named "flip camera android sharp" and <i class="material-icons-sharp md-36">flip_camera_ios</i> &#x2014; material icon named "flip camera ios sharp".;
IconData get flip_camera_sharp => !_isCupertino() ? Icons.flip_camera_android_sharp : Icons.flip_camera_ios_sharp;
/// Platform-adaptive icon for <i class="material-icons md-36">more_vert</i> &#x2014; material icon named "more vert" and <i class="material-icons md-36">more_horiz</i> &#x2014; material icon named "more horiz".;
IconData get more => !_isCupertino() ? Icons.more_vert : Icons.more_horiz;
/// Platform-adaptive icon for <i class="material-icons-outlined md-36">more_vert</i> &#x2014; material icon named "more vert outlined" and <i class="material-icons-outlined md-36">more_horiz</i> &#x2014; material icon named "more horiz outlined".;
IconData get more_outlined => !_isCupertino() ? Icons.more_vert_outlined : Icons.more_horiz_outlined;
/// Platform-adaptive icon for <i class="material-icons-round md-36">more_vert</i> &#x2014; material icon named "more vert rounded" and <i class="material-icons-round md-36">more_horiz</i> &#x2014; material icon named "more horiz rounded".;
IconData get more_rounded => !_isCupertino() ? Icons.more_vert_rounded : Icons.more_horiz_rounded;
/// Platform-adaptive icon for <i class="material-icons-sharp md-36">more_vert</i> &#x2014; material icon named "more vert sharp" and <i class="material-icons-sharp md-36">more_horiz</i> &#x2014; material icon named "more horiz sharp".;
IconData get more_sharp => !_isCupertino() ? Icons.more_vert_sharp : Icons.more_horiz_sharp;
/// Platform-adaptive icon for <i class="material-icons md-36">share</i> &#x2014; material icon named "share" and <i class="material-icons md-36">ios_share</i> &#x2014; material icon named "ios share".;
IconData get share => !_isCupertino() ? Icons.share : Icons.ios_share;
// END GENERATED PLATFORM ADAPTIVE ICONS
}
/// Identifiers for the supported material design icons.
///
/// Use with the [Icon] class to show specific icons.
......@@ -64,9 +137,35 @@ class Icons {
// ignore: unused_element
Icons._();
/// A set of platform-adaptive material design icons.
///
/// Provides a convenient way to show a certain set of platform-appropriate
/// icons on Apple platforms.
///
/// Use with the [Icon] class to show specific icons.
///
/// {@tool snippet}
/// This example shows how to create a share icon that uses the material icon
/// named "share" on non-Apple platforms, and the icon named "ios share" on
/// Apple platforms.
///
/// ```dart
/// Icon(
/// Icons.adaptive.share,
/// )
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [Icon]
/// * [IconButton]
/// * <https://design.google.com/icons/>
static _PlatformAdaptiveIcons get adaptive => _PlatformAdaptiveIcons();
// Generated code: do not hand-edit.
// See https://github.com/flutter/flutter/wiki/Updating-Material-Design-Fonts
// BEGIN GENERATED
// BEGIN GENERATED ICONS
/// <i class="material-icons md-36">10k</i> &#x2014; material icon named "10k".
static const IconData ten_k = IconData(0xe52a, fontFamily: 'MaterialIcons');
......@@ -17029,5 +17128,5 @@ class Icons {
/// <i class="material-icons-sharp md-36">zoom_out</i> &#x2014; material icon named "zoom out sharp".
static const IconData zoom_out_sharp = IconData(0xf02d, fontFamily: 'MaterialIcons');
// END GENERATED
// END GENERATED ICONS
}
......@@ -16,4 +16,29 @@ void main() {
expect(Icons.clear.fontFamily, 'MaterialIcons');
expect(Icons.search.fontFamily, 'MaterialIcons');
});
testWidgets('Adaptive icons are correct on cupertino platforms',
(WidgetTester tester) async {
expect(Icons.adaptive.arrow_back, Icons.arrow_back_ios);
expect(Icons.adaptive.arrow_back_outlined, Icons.arrow_back_ios_outlined);
},
variant: const TargetPlatformVariant(<TargetPlatform>{
TargetPlatform.iOS,
TargetPlatform.macOS,
}),
);
testWidgets(
'Adaptive icons are correct on non-cupertino platforms',
(WidgetTester tester) async {
expect(Icons.adaptive.arrow_back, Icons.arrow_back);
expect(Icons.adaptive.arrow_back_outlined, Icons.arrow_back_outlined);
},
variant: const TargetPlatformVariant(<TargetPlatform>{
TargetPlatform.android,
TargetPlatform.fuchsia,
TargetPlatform.windows,
TargetPlatform.linux,
}),
);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment