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 {
class CustomIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
final IconThemeData iconTheme = IconTheme.of(context).fallback();
final IconThemeData iconTheme = IconTheme.of(context);
return new Container(
margin: const EdgeInsets.all(4.0),
width: iconTheme.size - 8.0,
......
......@@ -73,7 +73,7 @@ class FlutterLogo extends StatelessWidget {
@override
Widget build(BuildContext context) {
final IconThemeData iconTheme = IconTheme.of(context).fallback();
final IconThemeData iconTheme = IconTheme.of(context);
final double iconSize = size ?? iconTheme.size;
return new AnimatedContainer(
width: iconSize,
......
......@@ -80,7 +80,7 @@ class Icon extends StatelessWidget {
@override
Widget build(BuildContext context) {
final IconThemeData iconTheme = IconTheme.of(context).fallback();
final IconThemeData iconTheme = IconTheme.of(context);
final double iconSize = size ?? iconTheme.size;
......
......@@ -37,7 +37,7 @@ class IconTheme extends InheritedWidget {
}) {
return new IconTheme(
key: key,
data: IconTheme.of(context).merge(data),
data: _getInheritedIconThemData(context).merge(data),
child: child
);
}
......@@ -56,8 +56,13 @@ class IconTheme extends InheritedWidget {
/// IconThemeData theme = IconTheme.of(context);
/// ```
static IconThemeData of(BuildContext context) {
IconTheme result = context.inheritFromWidgetOfExactType(IconTheme);
return result?.data ?? Theme.of(context).iconTheme;
IconThemeData iconThemeData = _getInheritedIconThemData(context);
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
......
......@@ -19,6 +19,14 @@ class IconThemeData {
/// is clamped between 0.0 and 1.0.
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
/// the new values.
IconThemeData copyWith({ Color color, double opacity, double size }) {
......@@ -42,22 +50,8 @@ class IconThemeData {
);
}
/// Creates an icon theme that is identical to this icon theme but with
/// any null fields filled in. Specific fallbacks can be given, but in their
/// 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
);
}
/// Whether all the properties of this object are non-null.
bool get isConcrete => color != null && opacity != null && size != null;
/// The default color for icons.
final Color color;
......
......@@ -53,7 +53,7 @@ class ImageIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
final IconThemeData iconTheme = IconTheme.of(context).fallback();
final IconThemeData iconTheme = IconTheme.of(context);
final double iconSize = size ?? iconTheme.size;
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'constants.dart';
import 'debug.dart';
......@@ -104,7 +105,7 @@ class ListItem extends StatelessWidget {
/// See also:
///
/// * [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(color != null || context != null);
......
......@@ -256,7 +256,7 @@ void main() {
title: new Text('B'),
icon: new Builder(
builder: (BuildContext context) {
builderIconSize = IconTheme.of(context).fallback().size;
builderIconSize = IconTheme.of(context).size;
return new SizedBox(
width: builderIconSize,
height: builderIconSize,
......
......@@ -4,19 +4,40 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../services/mocks_for_image_cache.dart';
const ImageProvider _kImage = const TestImageProvider(21, 42);
void main() {
testWidgets('ImageIcon sizing - no theme, default size', (WidgetTester tester) async {
await tester.pumpWidget(
new Center(
child: const ImageIcon(null)
child: const ImageIcon(_kImage)
)
);
RenderBox renderObject = tester.renderObject(find.byType(ImageIcon));
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 {
......
// 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 {
Future<Point> startPicker(WidgetTester tester, ValueChanged<TimeOfDay> onChanged) async {
await tester.pumpWidget(new _TimePickerLauncher(onChanged: onChanged));
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(seconds: 1));
await tester.pumpUntilNoTransientCallbacks(const Duration(seconds: 1));
return tester.getCenter(find.byKey(new Key('time-picker-dial')));
}
Future<Null> finishPicker(WidgetTester tester) async {
await tester.tap(find.text('OK'));
await tester.pump(); // start animation
await tester.pump(const Duration(seconds: 1));
await tester.pumpUntilNoTransientCallbacks(const Duration(seconds: 1));
}
void main() {
......
......@@ -12,10 +12,10 @@ void main() {
imageCache.maximumSize = 2;
TestImageInfo a = await extractOneFrame(const TestProvider(1, 1).resolve(ImageConfiguration.empty));
TestImageInfo b = await extractOneFrame(const TestProvider(2, 2).resolve(ImageConfiguration.empty));
TestImageInfo c = await extractOneFrame(const TestProvider(3, 3).resolve(ImageConfiguration.empty));
TestImageInfo d = await extractOneFrame(const TestProvider(1, 4).resolve(ImageConfiguration.empty));
TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1).resolve(ImageConfiguration.empty));
TestImageInfo b = await extractOneFrame(const TestImageProvider(2, 2).resolve(ImageConfiguration.empty));
TestImageInfo c = await extractOneFrame(const TestImageProvider(3, 3).resolve(ImageConfiguration.empty));
TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4).resolve(ImageConfiguration.empty));
expect(a.value, equals(1));
expect(b.value, equals(2));
expect(c.value, equals(3));
......@@ -23,18 +23,18 @@ void main() {
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));
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));
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));
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));
});
......
......@@ -12,69 +12,69 @@ void main() {
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));
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));
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));
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));
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));
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, equals(a));
// 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));
// 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));
// 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));
// 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));
// 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));
// 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));
// 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));
// 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));
// 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));
// 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));
// cache has three entries: 3(14), 4(15), 1(16)
......
......@@ -23,8 +23,8 @@ class TestImageInfo implements ImageInfo {
String toString() => '$runtimeType($value)';
}
class TestProvider extends ImageProvider<int> {
const TestProvider(this.key, this.imageValue);
class TestImageProvider extends ImageProvider<int> {
const TestImageProvider(this.key, this.imageValue);
final int key;
final int imageValue;
......@@ -52,4 +52,4 @@ Future<ImageInfo> extractOneFrame(ImageStream stream) {
}
stream.addListener(listener);
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