Commit 4955eef8 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Refactor IconThemeData.fallback (#7490)

Now IconThemeData.fallback is a factory constructor and IconThemeData.of() does
the work of computing the fallback for its clients.

Also, add tests for ImageIcon and ListItems.
parent 3150e3fb
...@@ -66,7 +66,7 @@ class NavigationIconView { ...@@ -66,7 +66,7 @@ class NavigationIconView {
class CustomIcon extends StatelessWidget { class CustomIcon extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final IconThemeData iconTheme = IconTheme.of(context).fallback(); final IconThemeData iconTheme = IconTheme.of(context);
return new Container( return new Container(
margin: const EdgeInsets.all(4.0), margin: const EdgeInsets.all(4.0),
width: iconTheme.size - 8.0, width: iconTheme.size - 8.0,
......
...@@ -73,7 +73,7 @@ class FlutterLogo extends StatelessWidget { ...@@ -73,7 +73,7 @@ class FlutterLogo extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final IconThemeData iconTheme = IconTheme.of(context).fallback(); final IconThemeData iconTheme = IconTheme.of(context);
final double iconSize = size ?? iconTheme.size; final double iconSize = size ?? iconTheme.size;
return new AnimatedContainer( return new AnimatedContainer(
width: iconSize, width: iconSize,
......
...@@ -80,7 +80,7 @@ class Icon extends StatelessWidget { ...@@ -80,7 +80,7 @@ class Icon extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final IconThemeData iconTheme = IconTheme.of(context).fallback(); final IconThemeData iconTheme = IconTheme.of(context);
final double iconSize = size ?? iconTheme.size; final double iconSize = size ?? iconTheme.size;
......
...@@ -37,7 +37,7 @@ class IconTheme extends InheritedWidget { ...@@ -37,7 +37,7 @@ class IconTheme extends InheritedWidget {
}) { }) {
return new IconTheme( return new IconTheme(
key: key, key: key,
data: IconTheme.of(context).merge(data), data: _getInheritedIconThemData(context).merge(data),
child: child child: child
); );
} }
...@@ -56,8 +56,13 @@ class IconTheme extends InheritedWidget { ...@@ -56,8 +56,13 @@ class IconTheme extends InheritedWidget {
/// IconThemeData theme = IconTheme.of(context); /// IconThemeData theme = IconTheme.of(context);
/// ``` /// ```
static IconThemeData of(BuildContext context) { static IconThemeData of(BuildContext context) {
IconTheme result = context.inheritFromWidgetOfExactType(IconTheme); IconThemeData iconThemeData = _getInheritedIconThemData(context);
return result?.data ?? Theme.of(context).iconTheme; return iconThemeData.isConcrete ? iconThemeData : const IconThemeData.fallback().merge(iconThemeData);
}
static IconThemeData _getInheritedIconThemData(BuildContext context) {
IconTheme iconTheme = context.inheritFromWidgetOfExactType(IconTheme);
return iconTheme?.data ?? Theme.of(context).iconTheme;
} }
@override @override
......
...@@ -19,6 +19,14 @@ class IconThemeData { ...@@ -19,6 +19,14 @@ class IconThemeData {
/// is clamped between 0.0 and 1.0. /// is clamped between 0.0 and 1.0.
const IconThemeData({ this.color, double opacity, this.size }) : _opacity = opacity; const IconThemeData({ this.color, double opacity, this.size }) : _opacity = opacity;
/// Creates an icon them with some reasonable default values.
///
/// The [color] is black, the [opacity] is 1.0, and the [size] is 24.0.
const IconThemeData.fallback()
: color = const Color(0xFF000000),
_opacity = 1.0,
size = 24.0;
/// Creates a copy of this icon theme but with the given fields replaced with /// Creates a copy of this icon theme but with the given fields replaced with
/// the new values. /// the new values.
IconThemeData copyWith({ Color color, double opacity, double size }) { IconThemeData copyWith({ Color color, double opacity, double size }) {
...@@ -42,22 +50,8 @@ class IconThemeData { ...@@ -42,22 +50,8 @@ class IconThemeData {
); );
} }
/// Creates an icon theme that is identical to this icon theme but with /// Whether all the properties of this object are non-null.
/// any null fields filled in. Specific fallbacks can be given, but in their bool get isConcrete => color != null && opacity != null && size != null;
/// absence, this method defaults to black, fully opaque, and size 24.0.
IconThemeData fallback({
Color color: const Color(0xFF000000),
double opacity: 1.0,
double size: 24.0
}) {
if (this.color != null && this.opacity != null && this.size != null)
return this;
return new IconThemeData(
color: this.color ?? color,
opacity: this.opacity ?? opacity,
size: this.size ?? size
);
}
/// The default color for icons. /// The default color for icons.
final Color color; final Color color;
......
...@@ -53,7 +53,7 @@ class ImageIcon extends StatelessWidget { ...@@ -53,7 +53,7 @@ class ImageIcon extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final IconThemeData iconTheme = IconTheme.of(context).fallback(); final IconThemeData iconTheme = IconTheme.of(context);
final double iconSize = size ?? iconTheme.size; final double iconSize = size ?? iconTheme.size;
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'constants.dart'; import 'constants.dart';
import 'debug.dart'; import 'debug.dart';
...@@ -104,7 +105,7 @@ class ListItem extends StatelessWidget { ...@@ -104,7 +105,7 @@ class ListItem extends StatelessWidget {
/// See also: /// See also:
/// ///
/// * [Divider], which you can use to obtain this effect manually. /// * [Divider], which you can use to obtain this effect manually.
static Iterable<Widget> divideItems({ BuildContext context, Iterable<Widget> items, Color color }) sync* { static Iterable<Widget> divideItems({ BuildContext context, @required Iterable<Widget> items, Color color }) sync* {
assert(items != null); assert(items != null);
assert(color != null || context != null); assert(color != null || context != null);
......
...@@ -256,7 +256,7 @@ void main() { ...@@ -256,7 +256,7 @@ void main() {
title: new Text('B'), title: new Text('B'),
icon: new Builder( icon: new Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
builderIconSize = IconTheme.of(context).fallback().size; builderIconSize = IconTheme.of(context).size;
return new SizedBox( return new SizedBox(
width: builderIconSize, width: builderIconSize,
height: builderIconSize, height: builderIconSize,
......
...@@ -4,19 +4,40 @@ ...@@ -4,19 +4,40 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../services/mocks_for_image_cache.dart';
const ImageProvider _kImage = const TestImageProvider(21, 42);
void main() { void main() {
testWidgets('ImageIcon sizing - no theme, default size', (WidgetTester tester) async { testWidgets('ImageIcon sizing - no theme, default size', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new Center( new Center(
child: const ImageIcon(null) child: const ImageIcon(_kImage)
) )
); );
RenderBox renderObject = tester.renderObject(find.byType(ImageIcon)); RenderBox renderObject = tester.renderObject(find.byType(ImageIcon));
expect(renderObject.size, equals(const Size.square(24.0))); expect(renderObject.size, equals(const Size.square(24.0)));
expect(find.byType(Image), findsOneWidget);
});
testWidgets('Icon opacity', (WidgetTester tester) async {
await tester.pumpWidget(
new Center(
child: new IconTheme(
data: new IconThemeData(opacity: 0.5),
child: const ImageIcon(_kImage),
),
),
);
Image image = tester.widget(find.byType(Image));
expect(image, isNotNull);
expect(image.color.alpha, equals(128));
}); });
testWidgets('ImageIcon sizing - no theme, explicit size', (WidgetTester tester) async { testWidgets('ImageIcon sizing - no theme, explicit size', (WidgetTester tester) async {
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('ListItem control test', (WidgetTester tester) async {
await tester.pumpWidget(new MaterialApp(
home: new Material(
child: new Center(
child: new ListItem(
leading: new Icon(Icons.thumb_up),
title: new Text('Title'),
subtitle: new Text('Subtitle'),
trailing: new Icon(Icons.info),
enabled: false,
),
),
),
));
expect(find.text('Title'), findsOneWidget);
expect(find.text('Subtitle'), findsOneWidget);
});
testWidgets('ListItem control test', (WidgetTester tester) async {
List<String> titles = <String>[ 'first', 'second', 'third' ];
await tester.pumpWidget(new MaterialApp(
home: new Material(
child: new Builder(
builder: (BuildContext context) {
return new Block(
children: ListItem.divideItems(
context: context,
items: titles.map((String title) => new ListItem(title: new Text(title))),
).toList(),
);
},
),
),
));
expect(find.text('first'), findsOneWidget);
expect(find.text('second'), findsOneWidget);
expect(find.text('third'), findsOneWidget);
});
}
...@@ -37,18 +37,13 @@ class _TimePickerLauncher extends StatelessWidget { ...@@ -37,18 +37,13 @@ class _TimePickerLauncher extends StatelessWidget {
Future<Point> startPicker(WidgetTester tester, ValueChanged<TimeOfDay> onChanged) async { Future<Point> startPicker(WidgetTester tester, ValueChanged<TimeOfDay> onChanged) async {
await tester.pumpWidget(new _TimePickerLauncher(onChanged: onChanged)); await tester.pumpWidget(new _TimePickerLauncher(onChanged: onChanged));
await tester.tap(find.text('X')); await tester.tap(find.text('X'));
await tester.pumpUntilNoTransientCallbacks(const Duration(seconds: 1));
await tester.pump(); // start animation
await tester.pump(const Duration(seconds: 1));
return tester.getCenter(find.byKey(new Key('time-picker-dial'))); return tester.getCenter(find.byKey(new Key('time-picker-dial')));
} }
Future<Null> finishPicker(WidgetTester tester) async { Future<Null> finishPicker(WidgetTester tester) async {
await tester.tap(find.text('OK')); await tester.tap(find.text('OK'));
await tester.pumpUntilNoTransientCallbacks(const Duration(seconds: 1));
await tester.pump(); // start animation
await tester.pump(const Duration(seconds: 1));
} }
void main() { void main() {
......
...@@ -12,10 +12,10 @@ void main() { ...@@ -12,10 +12,10 @@ void main() {
imageCache.maximumSize = 2; imageCache.maximumSize = 2;
TestImageInfo a = await extractOneFrame(const TestProvider(1, 1).resolve(ImageConfiguration.empty)); TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1).resolve(ImageConfiguration.empty));
TestImageInfo b = await extractOneFrame(const TestProvider(2, 2).resolve(ImageConfiguration.empty)); TestImageInfo b = await extractOneFrame(const TestImageProvider(2, 2).resolve(ImageConfiguration.empty));
TestImageInfo c = await extractOneFrame(const TestProvider(3, 3).resolve(ImageConfiguration.empty)); TestImageInfo c = await extractOneFrame(const TestImageProvider(3, 3).resolve(ImageConfiguration.empty));
TestImageInfo d = await extractOneFrame(const TestProvider(1, 4).resolve(ImageConfiguration.empty)); TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4).resolve(ImageConfiguration.empty));
expect(a.value, equals(1)); expect(a.value, equals(1));
expect(b.value, equals(2)); expect(b.value, equals(2));
expect(c.value, equals(3)); expect(c.value, equals(3));
...@@ -23,18 +23,18 @@ void main() { ...@@ -23,18 +23,18 @@ void main() {
imageCache.maximumSize = 0; imageCache.maximumSize = 0;
TestImageInfo e = await extractOneFrame(const TestProvider(1, 5).resolve(ImageConfiguration.empty)); TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5).resolve(ImageConfiguration.empty));
expect(e.value, equals(5)); expect(e.value, equals(5));
TestImageInfo f = await extractOneFrame(const TestProvider(1, 6).resolve(ImageConfiguration.empty)); TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6).resolve(ImageConfiguration.empty));
expect(f.value, equals(6)); expect(f.value, equals(6));
imageCache.maximumSize = 3; imageCache.maximumSize = 3;
TestImageInfo g = await extractOneFrame(const TestProvider(1, 7).resolve(ImageConfiguration.empty)); TestImageInfo g = await extractOneFrame(const TestImageProvider(1, 7).resolve(ImageConfiguration.empty));
expect(g.value, equals(7)); expect(g.value, equals(7));
TestImageInfo h = await extractOneFrame(const TestProvider(1, 8).resolve(ImageConfiguration.empty)); TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8).resolve(ImageConfiguration.empty));
expect(h.value, equals(7)); expect(h.value, equals(7));
}); });
......
...@@ -12,69 +12,69 @@ void main() { ...@@ -12,69 +12,69 @@ void main() {
imageCache.maximumSize = 3; imageCache.maximumSize = 3;
TestImageInfo a = await extractOneFrame(const TestProvider(1, 1).resolve(ImageConfiguration.empty)); TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1).resolve(ImageConfiguration.empty));
expect(a.value, equals(1)); expect(a.value, equals(1));
TestImageInfo b = await extractOneFrame(const TestProvider(1, 2).resolve(ImageConfiguration.empty)); TestImageInfo b = await extractOneFrame(const TestImageProvider(1, 2).resolve(ImageConfiguration.empty));
expect(b.value, equals(1)); expect(b.value, equals(1));
TestImageInfo c = await extractOneFrame(const TestProvider(1, 3).resolve(ImageConfiguration.empty)); TestImageInfo c = await extractOneFrame(const TestImageProvider(1, 3).resolve(ImageConfiguration.empty));
expect(c.value, equals(1)); expect(c.value, equals(1));
TestImageInfo d = await extractOneFrame(const TestProvider(1, 4).resolve(ImageConfiguration.empty)); TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4).resolve(ImageConfiguration.empty));
expect(d.value, equals(1)); expect(d.value, equals(1));
TestImageInfo e = await extractOneFrame(const TestProvider(1, 5).resolve(ImageConfiguration.empty)); TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5).resolve(ImageConfiguration.empty));
expect(e.value, equals(1)); expect(e.value, equals(1));
TestImageInfo f = await extractOneFrame(const TestProvider(1, 6).resolve(ImageConfiguration.empty)); TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6).resolve(ImageConfiguration.empty));
expect(f.value, equals(1)); expect(f.value, equals(1));
expect(f, equals(a)); expect(f, equals(a));
// cache still only has one entry in it: 1(1) // cache still only has one entry in it: 1(1)
TestImageInfo g = await extractOneFrame(const TestProvider(2, 7).resolve(ImageConfiguration.empty)); TestImageInfo g = await extractOneFrame(const TestImageProvider(2, 7).resolve(ImageConfiguration.empty));
expect(g.value, equals(7)); expect(g.value, equals(7));
// cache has two entries in it: 1(1), 2(7) // cache has two entries in it: 1(1), 2(7)
TestImageInfo h = await extractOneFrame(const TestProvider(1, 8).resolve(ImageConfiguration.empty)); TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8).resolve(ImageConfiguration.empty));
expect(h.value, equals(1)); expect(h.value, equals(1));
// cache still has two entries in it: 2(7), 1(1) // cache still has two entries in it: 2(7), 1(1)
TestImageInfo i = await extractOneFrame(const TestProvider(3, 9).resolve(ImageConfiguration.empty)); TestImageInfo i = await extractOneFrame(const TestImageProvider(3, 9).resolve(ImageConfiguration.empty));
expect(i.value, equals(9)); expect(i.value, equals(9));
// cache has three entries in it: 2(7), 1(1), 3(9) // cache has three entries in it: 2(7), 1(1), 3(9)
TestImageInfo j = await extractOneFrame(const TestProvider(1, 10).resolve(ImageConfiguration.empty)); TestImageInfo j = await extractOneFrame(const TestImageProvider(1, 10).resolve(ImageConfiguration.empty));
expect(j.value, equals(1)); expect(j.value, equals(1));
// cache still has three entries in it: 2(7), 3(9), 1(1) // cache still has three entries in it: 2(7), 3(9), 1(1)
TestImageInfo k = await extractOneFrame(const TestProvider(4, 11).resolve(ImageConfiguration.empty)); TestImageInfo k = await extractOneFrame(const TestImageProvider(4, 11).resolve(ImageConfiguration.empty));
expect(k.value, equals(11)); expect(k.value, equals(11));
// cache has three entries: 3(9), 1(1), 4(11) // cache has three entries: 3(9), 1(1), 4(11)
TestImageInfo l = await extractOneFrame(const TestProvider(1, 12).resolve(ImageConfiguration.empty)); TestImageInfo l = await extractOneFrame(const TestImageProvider(1, 12).resolve(ImageConfiguration.empty));
expect(l.value, equals(1)); expect(l.value, equals(1));
// cache has three entries: 3(9), 4(11), 1(1) // cache has three entries: 3(9), 4(11), 1(1)
TestImageInfo m = await extractOneFrame(const TestProvider(2, 13).resolve(ImageConfiguration.empty)); TestImageInfo m = await extractOneFrame(const TestImageProvider(2, 13).resolve(ImageConfiguration.empty));
expect(m.value, equals(13)); expect(m.value, equals(13));
// cache has three entries: 4(11), 1(1), 2(13) // cache has three entries: 4(11), 1(1), 2(13)
TestImageInfo n = await extractOneFrame(const TestProvider(3, 14).resolve(ImageConfiguration.empty)); TestImageInfo n = await extractOneFrame(const TestImageProvider(3, 14).resolve(ImageConfiguration.empty));
expect(n.value, equals(14)); expect(n.value, equals(14));
// cache has three entries: 1(1), 2(13), 3(14) // cache has three entries: 1(1), 2(13), 3(14)
TestImageInfo o = await extractOneFrame(const TestProvider(4, 15).resolve(ImageConfiguration.empty)); TestImageInfo o = await extractOneFrame(const TestImageProvider(4, 15).resolve(ImageConfiguration.empty));
expect(o.value, equals(15)); expect(o.value, equals(15));
// cache has three entries: 2(13), 3(14), 4(15) // cache has three entries: 2(13), 3(14), 4(15)
TestImageInfo p = await extractOneFrame(const TestProvider(1, 16).resolve(ImageConfiguration.empty)); TestImageInfo p = await extractOneFrame(const TestImageProvider(1, 16).resolve(ImageConfiguration.empty));
expect(p.value, equals(16)); expect(p.value, equals(16));
// cache has three entries: 3(14), 4(15), 1(16) // cache has three entries: 3(14), 4(15), 1(16)
......
...@@ -23,8 +23,8 @@ class TestImageInfo implements ImageInfo { ...@@ -23,8 +23,8 @@ class TestImageInfo implements ImageInfo {
String toString() => '$runtimeType($value)'; String toString() => '$runtimeType($value)';
} }
class TestProvider extends ImageProvider<int> { class TestImageProvider extends ImageProvider<int> {
const TestProvider(this.key, this.imageValue); const TestImageProvider(this.key, this.imageValue);
final int key; final int key;
final int imageValue; final int imageValue;
...@@ -52,4 +52,4 @@ Future<ImageInfo> extractOneFrame(ImageStream stream) { ...@@ -52,4 +52,4 @@ Future<ImageInfo> extractOneFrame(ImageStream stream) {
} }
stream.addListener(listener); stream.addListener(listener);
return completer.future; return completer.future;
} }
\ No newline at end of file
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