Merge pull request #2916 from chinmaygarde/master
Add: flutter build ios [--[no-]simulator]
diff --git a/dev/manual_tests/drag_and_drop.dart b/dev/manual_tests/drag_and_drop.dart
index f7c5a76..a8fd7b7 100644
--- a/dev/manual_tests/drag_and_drop.dart
+++ b/dev/manual_tests/drag_and_drop.dart
@@ -64,7 +64,7 @@
height: config.size,
decoration: new BoxDecoration(
backgroundColor: config.color,
- border: new Border.all(color: const Color(0xFF000000), width: taps.toDouble()),
+ border: new Border.all(width: taps.toDouble()),
shape: BoxShape.circle
),
child: config.child
diff --git a/examples/layers/widgets/sectors.dart b/examples/layers/widgets/sectors.dart
index 33ac584..7e5fc9f 100644
--- a/examples/layers/widgets/sectors.dart
+++ b/examples/layers/widgets/sectors.dart
@@ -134,7 +134,7 @@
child: new Container(
margin: new EdgeInsets.all(8.0),
decoration: new BoxDecoration(
- border: new Border.all(color: new Color(0xFF000000))
+ border: new Border.all()
),
padding: new EdgeInsets.all(8.0),
child: new WidgetToRenderBoxAdapter(
diff --git a/examples/material_gallery/flutter.yaml b/examples/material_gallery/flutter.yaml
index c7e9081..3e0f871 100644
--- a/examples/material_gallery/flutter.yaml
+++ b/examples/material_gallery/flutter.yaml
@@ -23,3 +23,4 @@
- packages/flutter_gallery_assets/top_10_australian_beaches.png
- packages/flutter_gallery_assets/jumpingjack.json
- packages/flutter_gallery_assets/jumpingjack.png
+ - packages/flutter_gallery_assets/grain.png
diff --git a/examples/material_gallery/lib/demo/flexible_space_demo.dart b/examples/material_gallery/lib/demo/flexible_space_demo.dart
index 3a39971..762d639 100644
--- a/examples/material_gallery/lib/demo/flexible_space_demo.dart
+++ b/examples/material_gallery/lib/demo/flexible_space_demo.dart
@@ -82,7 +82,7 @@
@override
Widget build(BuildContext context) {
- final double statusBarHeight = (MediaQuery.of(context)?.padding ?? EdgeInsets.zero).top;
+ final double statusBarHeight = MediaQuery.of(context).padding.top;
return new Theme(
data: new ThemeData(
brightness: ThemeBrightness.light,
diff --git a/examples/material_gallery/lib/demo/list_demo.dart b/examples/material_gallery/lib/demo/list_demo.dart
index daa92ff..903226e 100644
--- a/examples/material_gallery/lib/demo/list_demo.dart
+++ b/examples/material_gallery/lib/demo/list_demo.dart
@@ -42,7 +42,7 @@
_bottomSheet = scaffoldKey.currentState.showBottomSheet((BuildContext bottomSheetContext) {
return new Container(
decoration: new BoxDecoration(
- border: new Border(top: new BorderSide(color: Colors.black26, width: 1.0))
+ border: new Border(top: new BorderSide(color: Colors.black26))
),
child: new Column(
mainAxisAlignment: MainAxisAlignment.collapse,
diff --git a/examples/material_gallery/lib/demo/persistent_bottom_sheet_demo.dart b/examples/material_gallery/lib/demo/persistent_bottom_sheet_demo.dart
index 5709593..0d11a95 100644
--- a/examples/material_gallery/lib/demo/persistent_bottom_sheet_demo.dart
+++ b/examples/material_gallery/lib/demo/persistent_bottom_sheet_demo.dart
@@ -16,7 +16,7 @@
Scaffold.of(context).showBottomSheet((_) {
return new Container(
decoration: new BoxDecoration(
- border: new Border(top: new BorderSide(color: Colors.black26, width: 1.0))
+ border: new Border(top: new BorderSide(color: Colors.black26))
),
child: new Padding(
padding: const EdgeInsets.all(32.0),
diff --git a/examples/material_gallery/lib/demo/tabs_demo.dart b/examples/material_gallery/lib/demo/tabs_demo.dart
index d6ddae9..633b72d 100644
--- a/examples/material_gallery/lib/demo/tabs_demo.dart
+++ b/examples/material_gallery/lib/demo/tabs_demo.dart
@@ -35,7 +35,7 @@
@override
Widget build(BuildContext context) {
- final double statusBarHeight = (MediaQuery.of(context)?.padding ?? EdgeInsets.zero).top;
+ final double statusBarHeight = MediaQuery.of(context).padding.top;
return new TabBarSelection<_Page>(
values: _pages,
onChanged: (_Page value) {
diff --git a/examples/material_gallery/lib/gallery/app.dart b/examples/material_gallery/lib/gallery/app.dart
index 57a5d81..f8a0c42 100644
--- a/examples/material_gallery/lib/gallery/app.dart
+++ b/examples/material_gallery/lib/gallery/app.dart
@@ -28,10 +28,20 @@
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Material Gallery',
- theme: lightTheme ? new ThemeData.light() : new ThemeData.dark(),
+ theme: lightTheme ? _kGalleryLightTheme : _kGalleryDarkTheme,
routes: {
'/': (BuildContext context) => new GalleryHome()
}
);
}
}
+
+ThemeData _kGalleryLightTheme = new ThemeData(
+ brightness: ThemeBrightness.light,
+ primarySwatch: Colors.purple
+);
+
+ThemeData _kGalleryDarkTheme = new ThemeData(
+ brightness: ThemeBrightness.dark,
+ primarySwatch: Colors.purple
+);
diff --git a/examples/material_gallery/lib/gallery/demo.dart b/examples/material_gallery/lib/gallery/demo.dart
deleted file mode 100644
index 0416bab..0000000
--- a/examples/material_gallery/lib/gallery/demo.dart
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2016 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/widgets.dart';
-
-typedef Widget GalleryDemoBuilder();
-
-class GalleryDemo {
- GalleryDemo({ this.title, this.builder }) {
- assert(title != null);
- assert(builder != null);
- }
-
- final String title;
- final GalleryDemoBuilder builder;
-}
diff --git a/examples/material_gallery/lib/gallery/header.dart b/examples/material_gallery/lib/gallery/header.dart
new file mode 100644
index 0000000..fcfb0dd
--- /dev/null
+++ b/examples/material_gallery/lib/gallery/header.dart
@@ -0,0 +1,189 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_sprites/flutter_sprites.dart';
+
+class GalleryHeader extends StatefulWidget {
+ @override
+ _GalleryHeaderState createState() => new _GalleryHeaderState();
+}
+
+class _GalleryHeaderState extends State<GalleryHeader> {
+ _FlutterHeaderNode _headerNode;
+ ImageMap _images;
+
+ Future<Null> _loadAssets() async {
+ final AssetBundle bundle = DefaultAssetBundle.of(context);
+ _images = new ImageMap(bundle);
+ await _images.load(<String>[
+ 'packages/flutter_gallery_assets/grain.png',
+ ]);
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ _loadAssets().then((_) {
+ setState(() {
+ _headerNode = new _FlutterHeaderNode(_images);
+ });
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return _headerNode == null ? new Container() : new SpriteWidget(_headerNode);
+ }
+}
+
+const Size _kCanvasSize = const Size(1024.0, 1024.0);
+const Point _kCenterPoint = const Point(512.0, 512.0);
+
+class _FlutterHeaderNode extends NodeWithSize {
+ _FlutterHeaderNode(this._images) : super(_kCanvasSize) {
+ clippingLayer.opacity = 0.0;
+ clippingLayer.actions.run(new ActionTween((double a) => clippingLayer.opacity = a, 0.0, 1.0, 0.5));
+ addChild(clippingLayer);
+
+ clippingLayer.addChild(new _BackgroundBox());
+
+ paperAnimation.position = _kCenterPoint;
+ clippingLayer.addChild(paperAnimation);
+
+ final Sprite grain = new Sprite.fromImage(_images['packages/flutter_gallery_assets/grain.png'])
+ ..position = _kCenterPoint;
+ clippingLayer.addChild(grain);
+
+ userInteractionEnabled = true;
+ }
+
+ final ImageMap _images;
+ final Layer clippingLayer = new Layer();
+ final _PaperAnimation paperAnimation = new _PaperAnimation();
+
+ @override
+ void spriteBoxPerformedLayout() {
+ clippingLayer.layerRect = spriteBox.visibleArea;
+ }
+}
+
+final List<_PaperConfig> _kPaperConfigs = <_PaperConfig>[
+ new _PaperConfig(
+ color: Colors.deepPurple[500],
+ startPosition: const Point(-300.0, -300.0),
+ startRotation: -10.0,
+ rotationSpeed: -1.0,
+ parallaxDepth: 0.0,
+ rect: new Rect.fromLTRB(-1024.0, -280.0, 1024.0, 280.0)
+ ),
+ new _PaperConfig(
+ color: Colors.purple[400],
+ startPosition: const Point(550.0, 0.0),
+ startRotation: 45.0,
+ rotationSpeed: 0.7,
+ parallaxDepth: 1.0,
+ rect: new Rect.fromLTRB(-512.0, -512.0, 512.0, 512.0)
+ ),
+ new _PaperConfig(
+ color: Colors.purple[600],
+ startPosition: const Point(550.0, 0.0),
+ startRotation: 55.0,
+ rotationSpeed: 0.9,
+ parallaxDepth: 2.0,
+ rect: new Rect.fromLTRB(-512.0, -512.0, 512.0, 512.0)
+ ),
+ new _PaperConfig(
+ color: Colors.purple[700],
+ startPosition: const Point(550.0, 0.0),
+ startRotation: 65.0,
+ rotationSpeed: 1.1,
+ parallaxDepth: 3.0,
+ rect: new Rect.fromLTRB(-512.0, -512.0, 512.0, 512.0)
+ )
+];
+
+class _PaperAnimation extends Node {
+ _PaperAnimation() {
+ for (_PaperConfig config in _kPaperConfigs) {
+
+ final _PaperSheet sheet = new _PaperSheet(config);
+ final _PaperSheetShadow shadow = new _PaperSheetShadow(config);
+
+ addChild(shadow);
+ addChild(sheet);
+ _sheets.add(sheet);
+
+ shadow.constraints = <Constraint>[
+ new ConstraintRotationToNodeRotation(sheet),
+ new ConstraintPositionToNode(sheet, offset: const Offset(0.0, 10.0))
+ ];
+ }
+ }
+
+ final List<_PaperSheet> _sheets = <_PaperSheet>[];
+}
+
+class _PaperConfig {
+ _PaperConfig({
+ this.color,
+ this.startPosition,
+ this.startRotation,
+ this.rotationSpeed,
+ this.parallaxDepth,
+ this.rect
+ });
+
+ final Color color;
+ final Point startPosition;
+ final double startRotation;
+ final double rotationSpeed;
+ final double parallaxDepth;
+ final Rect rect;
+}
+
+class _PaperSheet extends Node {
+ _PaperSheet(this._config) {
+ _paperPaint.color = _config.color;
+
+ position = _config.startPosition;
+ rotation = _config.startRotation;
+ }
+
+ final _PaperConfig _config;
+ final Paint _paperPaint = new Paint();
+
+ @override
+ void paint(Canvas canvas) {
+ canvas.drawRect(_config.rect, _paperPaint);
+ }
+
+ @override
+ void update(double dt) {
+ rotation += _config.rotationSpeed * dt;
+ }
+}
+
+class _PaperSheetShadow extends Node {
+ _PaperSheetShadow(this._config) {
+ _paperPaint.color = Colors.black45;
+ _paperPaint.maskFilter = new MaskFilter.blur(BlurStyle.normal, 10.0);
+ }
+
+ final _PaperConfig _config;
+ final Paint _paperPaint = new Paint();
+
+ @override
+ void paint(Canvas canvas) {
+ canvas.drawRect(_config.rect, _paperPaint);
+ }
+}
+
+class _BackgroundBox extends Node {
+ final Paint _boxPaint = new Paint()..color = Colors.purple[500];
+
+ @override
+ void paint(Canvas canvas) {
+ canvas.drawRect(new Rect.fromLTWH(0.0, 0.0, _kCanvasSize.width, _kCanvasSize.height), _boxPaint);
+ }
+}
diff --git a/examples/material_gallery/lib/gallery/home.dart b/examples/material_gallery/lib/gallery/home.dart
index e71eb9d..70263e9 100644
--- a/examples/material_gallery/lib/gallery/home.dart
+++ b/examples/material_gallery/lib/gallery/home.dart
@@ -5,9 +5,9 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
-import 'demo.dart';
import 'drawer.dart';
-import 'section.dart';
+import 'header.dart';
+import 'item.dart';
import '../demo/buttons_demo.dart';
import '../demo/cards_demo.dart';
@@ -28,7 +28,6 @@
import '../demo/persistent_bottom_sheet_demo.dart';
import '../demo/progress_indicator_demo.dart';
import '../demo/toggle_controls_demo.dart';
-import '../demo/scrolling_techniques_demo.dart';
import '../demo/slider_demo.dart';
import '../demo/snack_bar_demo.dart';
import '../demo/scrollable_tabs_demo.dart';
@@ -41,6 +40,8 @@
import '../demo/typography_demo.dart';
import '../demo/weather_demo.dart';
+const double _kFlexibleSpaceMaxHeight = 256.0;
+
class GalleryHome extends StatefulWidget {
GalleryHome({ Key key }) : super(key: key);
@@ -49,109 +50,75 @@
}
class GalleryHomeState extends State<GalleryHome> {
+ final Key _scrollableKey = new UniqueKey();
+
@override
Widget build(BuildContext context) {
- final double appBarHeight = 128.0;
+ final double statusBarHight = (MediaQuery.of(context)?.padding ?? EdgeInsets.zero).top;
+
return new Scaffold(
drawer: new GalleryDrawer(),
appBar: new AppBar(
- expandedHeight: appBarHeight,
- flexibleSpace: (BuildContext context) {
- return new Container(
- padding: const EdgeInsets.only(left: 64.0),
- height: appBarHeight,
- child: new Align(
- alignment: const FractionalOffset(0.0, 1.0),
- child: new Text('Flutter Gallery', style: Typography.white.headline)
- )
- );
- }
+ expandedHeight: _kFlexibleSpaceMaxHeight,
+ flexibleSpace: (BuildContext context) => new FlexibleSpaceBar(
+ image: new GalleryHeader(),
+ title: new Text("Flutter Gallery")
+ )
),
- body: new Block(
- padding: const EdgeInsets.all(4.0),
- children: <Widget>[
- new Row(
+ scrollableKey: _scrollableKey,
+ appBarBehavior: AppBarBehavior.under,
+ body: new TwoLevelList(
+ scrollablePadding: new EdgeInsets.only(top: _kFlexibleSpaceMaxHeight + statusBarHight),
+ key: _scrollableKey,
+ type: MaterialListType.oneLine,
+ scrollableKey: _scrollableKey,
+ items: <Widget>[
+ new TwoLevelSublist(
+ leading: new Icon(icon: Icons.star),
+ title: new Text("Demos"),
children: <Widget>[
- new GallerySection(
- title: 'Animation',
- image: 'assets/section_animation.png',
- colors: Colors.purple,
- demos: <GalleryDemo>[
- new GalleryDemo(title: 'Weather', builder: () => new WeatherDemo()),
- new GalleryDemo(title: 'Fitness', builder: () => new FitnessDemo())
- ]
- ),
- new GallerySection(
- title: 'Style',
- image: 'assets/section_style.png',
- colors: Colors.green,
- demos: <GalleryDemo>[
- new GalleryDemo(title: 'Colors', builder: () => new ColorsDemo()),
- new GalleryDemo(title: 'Typography', builder: () => new TypographyDemo())
- ]
- )
+ new GalleryItem(title: "Weather", builder: () => new WeatherDemo()),
+ new GalleryItem(title: "Fitness", builder: () => new FitnessDemo()),
]
),
- new Row(
+ new TwoLevelSublist(
+ leading: new Icon(icon: Icons.extension),
+ title: new Text("Components"),
children: <Widget>[
- new GallerySection(
- title: 'Layout',
- image: 'assets/section_layout.png',
- colors: Colors.pink
- ),
- new GallerySection(
- title: 'Components',
- image: 'assets/section_components.png',
- colors: Colors.amber,
- demos: <GalleryDemo>[
- new GalleryDemo(title: 'App Bar', builder: () => new FlexibleSpaceDemo()),
- new GalleryDemo(title: 'Buttons', builder: () => new ButtonsDemo()),
- new GalleryDemo(title: 'Buttons: Floating Action Button', builder: () => new TabsFabDemo()),
- new GalleryDemo(title: 'Cards', builder: () => new CardsDemo()),
- new GalleryDemo(title: 'Chips', builder: () => new ChipDemo()),
- new GalleryDemo(title: 'Date Picker', builder: () => new DatePickerDemo()),
- new GalleryDemo(title: 'Dialog', builder: () => new DialogDemo()),
- new GalleryDemo(title: 'Dropdown Button', builder: () => new DropDownDemo()),
- new GalleryDemo(title: 'Expand/Collapse List Control', builder: () => new TwoLevelListDemo()),
- new GalleryDemo(title: 'Grid', builder: () => new GridListDemo()),
- new GalleryDemo(title: 'Icons', builder: () => new IconsDemo()),
- new GalleryDemo(title: 'Leave-behind List Items', builder: () => new LeaveBehindDemo()),
- new GalleryDemo(title: 'List', builder: () => new ListDemo()),
- new GalleryDemo(title: 'Modal Bottom Sheet', builder: () => new ModalBottomSheetDemo()),
- new GalleryDemo(title: 'Menus', builder: () => new MenuDemo()),
- new GalleryDemo(title: 'Page Selector', builder: () => new PageSelectorDemo()),
- new GalleryDemo(title: 'Persistent Bottom Sheet', builder: () => new PersistentBottomSheetDemo()),
- new GalleryDemo(title: 'Progress Indicators', builder: () => new ProgressIndicatorDemo()),
- new GalleryDemo(title: 'Scrollable Tabs', builder: () => new ScrollableTabsDemo()),
- new GalleryDemo(title: 'Selection Controls', builder: () => new ToggleControlsDemo()),
- new GalleryDemo(title: 'Sliders', builder: () => new SliderDemo()),
- new GalleryDemo(title: 'SnackBar', builder: () => new SnackBarDemo()),
- new GalleryDemo(title: 'Tabs', builder: () => new TabsDemo()),
- new GalleryDemo(title: 'Text Fields', builder: () => new TextFieldDemo()),
- new GalleryDemo(title: 'Time Picker', builder: () => new TimePickerDemo()),
- new GalleryDemo(title: 'Tooltips', builder: () => new TooltipDemo())
- ]
- )
+ new GalleryItem(title: 'App Bar', builder: () => new FlexibleSpaceDemo()),
+ new GalleryItem(title: 'Buttons', builder: () => new ButtonsDemo()),
+ new GalleryItem(title: 'Buttons: Floating Action Button', builder: () => new TabsFabDemo()),
+ new GalleryItem(title: 'Cards', builder: () => new CardsDemo()),
+ new GalleryItem(title: 'Chips', builder: () => new ChipDemo()),
+ new GalleryItem(title: 'Date Picker', builder: () => new DatePickerDemo()),
+ new GalleryItem(title: 'Dialog', builder: () => new DialogDemo()),
+ new GalleryItem(title: 'Dropdown Button', builder: () => new DropDownDemo()),
+ new GalleryItem(title: 'Expand/Collapse List Control', builder: () => new TwoLevelListDemo()),
+ new GalleryItem(title: 'Grid', builder: () => new GridListDemo()),
+ new GalleryItem(title: 'Icons', builder: () => new IconsDemo()),
+ new GalleryItem(title: 'Leave-behind List Items', builder: () => new LeaveBehindDemo()),
+ new GalleryItem(title: 'List', builder: () => new ListDemo()),
+ new GalleryItem(title: 'Modal Bottom Sheet', builder: () => new ModalBottomSheetDemo()),
+ new GalleryItem(title: 'Menus', builder: () => new MenuDemo()),
+ new GalleryItem(title: 'Page Selector', builder: () => new PageSelectorDemo()),
+ new GalleryItem(title: 'Persistent Bottom Sheet', builder: () => new PersistentBottomSheetDemo()),
+ new GalleryItem(title: 'Progress Indicators', builder: () => new ProgressIndicatorDemo()),
+ new GalleryItem(title: 'Scrollable Tabs', builder: () => new ScrollableTabsDemo()),
+ new GalleryItem(title: 'Selection Controls', builder: () => new ToggleControlsDemo()),
+ new GalleryItem(title: 'Sliders', builder: () => new SliderDemo()),
+ new GalleryItem(title: 'SnackBar', builder: () => new SnackBarDemo()),
+ new GalleryItem(title: 'Tabs', builder: () => new TabsDemo()),
+ new GalleryItem(title: 'Text Fields', builder: () => new TextFieldDemo()),
+ new GalleryItem(title: 'Time Picker', builder: () => new TimePickerDemo()),
+ new GalleryItem(title: 'Tooltips', builder: () => new TooltipDemo()),
]
),
- new Row(
+ new TwoLevelSublist(
+ leading: new Icon(icon: Icons.color_lens),
+ title: new Text("Style"),
children: <Widget>[
- new GallerySection(
- title: 'Patterns',
- image: 'assets/section_patterns.png',
- colors: Colors.cyan,
- demos: <GalleryDemo>[
- new GalleryDemo(title: 'Scrolling Techniques', builder: () => new ScrollingTechniquesDemo())
- ]
- ),
- new GallerySection(
- title: 'Usability',
- image: 'assets/section_usability.png',
- colors: Colors.lightGreen,
- demos: <GalleryDemo>[
- new GalleryDemo(title: 'Tooltips', builder: () => new TooltipDemo())
- ]
- )
+ new GalleryItem(title: 'Colors', builder: () => new ColorsDemo()),
+ new GalleryItem(title: 'Typography', builder: () => new TypographyDemo()),
]
)
]
diff --git a/examples/material_gallery/lib/gallery/item.dart b/examples/material_gallery/lib/gallery/item.dart
new file mode 100644
index 0000000..0651068
--- /dev/null
+++ b/examples/material_gallery/lib/gallery/item.dart
@@ -0,0 +1,28 @@
+import 'package:flutter/material.dart';
+
+typedef Widget GalleryDemoBuilder();
+
+class GalleryItem extends StatelessWidget {
+ GalleryItem({ this.title, this.icon, this.builder });
+
+ final String title;
+ final IconData icon;
+ final GalleryDemoBuilder builder;
+
+ @override
+ Widget build(BuildContext context) {
+ Widget leading = icon == null ? new Container() : new Icon(icon: icon);
+
+ return new TwoLevelListItem(
+ leading: leading,
+ title: new Text(title),
+ onTap: () {
+ if (builder != null) {
+ Navigator.push(context, new MaterialPageRoute<Null>(
+ builder: (BuildContext context) => builder()
+ ));
+ }
+ }
+ );
+ }
+}
diff --git a/examples/material_gallery/lib/gallery/section.dart b/examples/material_gallery/lib/gallery/section.dart
deleted file mode 100644
index e3d6cdd..0000000
--- a/examples/material_gallery/lib/gallery/section.dart
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2016 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/widgets.dart';
-
-import 'demo.dart';
-
-class GallerySection extends StatelessWidget {
- GallerySection({ this.title, this.image, this.colors, this.demos });
-
- final String title;
- final String image;
- final Map<int, Color> colors;
- final List<GalleryDemo> demos;
-
- void showDemo(GalleryDemo demo, BuildContext context, ThemeData theme) {
- Navigator.push(context, new MaterialPageRoute<Null>(
- builder: (BuildContext context) {
- Widget child = (demo.builder == null) ? null : demo.builder();
- return new Theme(data: theme, child: child);
- }
- ));
- }
-
- void showDemos(BuildContext context) {
- final double statusBarHeight = (MediaQuery.of(context)?.padding ?? EdgeInsets.zero).top;
- final ThemeData theme = new ThemeData(
- brightness: Theme.of(context).brightness,
- primarySwatch: colors
- );
- final double appBarHeight = 200.0;
- final Key scrollableKey = new ValueKey<String>(title); // assume section titles differ
- Navigator.push(context, new MaterialPageRoute<Null>(
- builder: (BuildContext context) {
- return new Theme(
- data: theme,
- child: new Scaffold(
- appBarBehavior: AppBarBehavior.under,
- scrollableKey: scrollableKey,
- appBar: new AppBar(
- expandedHeight: appBarHeight,
- flexibleSpace: (BuildContext context) => new FlexibleSpaceBar(title: new Text(title))
- ),
- body: new Material(
- child: new MaterialList(
- scrollableKey: scrollableKey,
- scrollablePadding: new EdgeInsets.only(top: appBarHeight + statusBarHeight),
- type: MaterialListType.oneLine,
- children: (demos ?? const <GalleryDemo>[]).map((GalleryDemo demo) {
- return new ListItem(
- title: new Text(demo.title),
- onTap: () { showDemo(demo, context, theme); }
- );
- })
- )
- )
- )
- );
- }
- ));
- }
-
- @override
- Widget build (BuildContext context) {
- final ThemeData theme = new ThemeData(
- brightness: Theme.of(context).brightness,
- primarySwatch: colors
- );
- final TextStyle titleTextStyle = theme.textTheme.title.copyWith(
- color: Colors.white
- );
- return new Flexible(
- child: new GestureDetector(
- behavior: HitTestBehavior.opaque,
- onTap: () { showDemos(context); },
- child: new Container(
- height: 256.0,
- margin: const EdgeInsets.all(4.0),
- decoration: new BoxDecoration(backgroundColor: theme.primaryColor),
- child: new Column(
- children: <Widget>[
- new Flexible(
- child: new Padding(
- padding: const EdgeInsets.symmetric(horizontal: 12.0),
- child: new AssetImage(
- name: image,
- alignment: const FractionalOffset(0.5, 0.5),
- fit: ImageFit.contain
- )
- )
- ),
- new Padding(
- padding: const EdgeInsets.all(16.0),
- child: new Align(
- alignment: const FractionalOffset(0.0, 1.0),
- child: new Text(title, style: titleTextStyle)
- )
- )
- ]
- )
- )
- )
- );
- }
-}
diff --git a/examples/material_gallery/pubspec.yaml b/examples/material_gallery/pubspec.yaml
index 06c5b03..158ebf7 100644
--- a/examples/material_gallery/pubspec.yaml
+++ b/examples/material_gallery/pubspec.yaml
@@ -7,4 +7,4 @@
path: ../../packages/flutter
flutter_sprites:
path: ../../packages/flutter_sprites
- flutter_gallery_assets: '0.0.9'
+ flutter_gallery_assets: '0.0.12'
diff --git a/examples/stocks/flutter.yaml b/examples/stocks/flutter.yaml
index ee90ca7..c2cedb4 100644
--- a/examples/stocks/flutter.yaml
+++ b/examples/stocks/flutter.yaml
@@ -1,4 +1,2 @@
name: stocks
-version: 0.0.2
-update-url: http://localhost:9888/
uses-material-design: true
diff --git a/examples/stocks/lib/stock_symbol_viewer.dart b/examples/stocks/lib/stock_symbol_viewer.dart
index 5f2ff07..6812fed 100644
--- a/examples/stocks/lib/stock_symbol_viewer.dart
+++ b/examples/stocks/lib/stock_symbol_viewer.dart
@@ -100,7 +100,7 @@
return new Container(
padding: new EdgeInsets.all(10.0),
decoration: new BoxDecoration(
- border: new Border(top: new BorderSide(color: Colors.black26, width: 1.0))
+ border: new Border(top: new BorderSide(color: Colors.black26))
),
child: new StockSymbolView(stock: stock)
);
diff --git a/packages/flutter/lib/src/http/mojo_client.dart b/packages/flutter/lib/src/http/mojo_client.dart
index 3665f42..af1d8f0 100644
--- a/packages/flutter/lib/src/http/mojo_client.dart
+++ b/packages/flutter/lib/src/http/mojo_client.dart
@@ -149,8 +149,14 @@
ByteData data = await mojo.DataPipeDrainer.drainHandle(response.body);
Uint8List bodyBytes = new Uint8List.view(data.buffer);
return new Response(bodyBytes: bodyBytes, statusCode: response.statusCode);
- } catch (e) {
- print("NetworkService unavailable $e");
+ } catch (exception) {
+ assert(() {
+ debugPrint('-- EXCEPTION CAUGHT BY NETWORKING HTTP LIBRARY -------------------------');
+ debugPrint('An exception was raised while sending bytes to the Mojo network library:');
+ debugPrint('$exception');
+ debugPrint('------------------------------------------------------------------------');
+ return true;
+ });
return new Response(statusCode: 500);
} finally {
loader.close();
diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart
index 936ae25..40c76eb 100644
--- a/packages/flutter/lib/src/material/app_bar.dart
+++ b/packages/flutter/lib/src/material/app_bar.dart
@@ -110,7 +110,7 @@
@override
Widget build(BuildContext context) {
- final double statusBarHeight = (MediaQuery.of(context)?.padding ?? EdgeInsets.zero).top;
+ final double statusBarHeight = MediaQuery.of(context).padding.top;
final ThemeData theme = Theme.of(context);
IconThemeData iconTheme = theme.primaryIconTheme;
@@ -134,16 +134,18 @@
}
final List<Widget> toolBarRow = <Widget>[];
- if (leading != null)
- toolBarRow.add(leading);
- toolBarRow.add(
- new Flexible(
- child: new Padding(
- padding: new EdgeInsets.only(left: 24.0),
- child: title != null ? new DefaultTextStyle(style: centerStyle, child: title) : null
- )
+ if (leading != null) {
+ toolBarRow.add(new Padding(
+ padding: new EdgeInsets.only(right: 16.0),
+ child: leading
+ ));
+ }
+ toolBarRow.add(new Flexible(
+ child: new Padding(
+ padding: new EdgeInsets.only(left: 8.0),
+ child: title != null ? new DefaultTextStyle(style: centerStyle, child: title) : null
)
- );
+ ));
if (actions != null)
toolBarRow.addAll(actions);
diff --git a/packages/flutter/lib/src/material/drawer_header.dart b/packages/flutter/lib/src/material/drawer_header.dart
index f74efeb..95dab00 100644
--- a/packages/flutter/lib/src/material/drawer_header.dart
+++ b/packages/flutter/lib/src/material/drawer_header.dart
@@ -20,7 +20,7 @@
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
- final double statusBarHeight = (MediaQuery.of(context)?.padding ?? EdgeInsets.zero).top;
+ final double statusBarHeight = MediaQuery.of(context).padding.top;
return new Container(
height: statusBarHeight + kMaterialDrawerHeight,
decoration: new BoxDecoration(
diff --git a/packages/flutter/lib/src/material/flexible_space_bar.dart b/packages/flutter/lib/src/material/flexible_space_bar.dart
index a9cd896..28f7b71 100644
--- a/packages/flutter/lib/src/material/flexible_space_bar.dart
+++ b/packages/flutter/lib/src/material/flexible_space_bar.dart
@@ -25,7 +25,7 @@
@override
Widget build(BuildContext context) {
assert(debugCheckHasScaffold(context));
- final double statusBarHeight = (MediaQuery.of(context)?.padding ?? EdgeInsets.zero).top;
+ final double statusBarHeight = MediaQuery.of(context).padding.top;
final Animation<double> animation = Scaffold.of(context).appBarAnimation;
final double appBarHeight = Scaffold.of(context).appBarHeight + statusBarHeight;
final double toolBarHeight = kToolBarHeight + statusBarHeight;
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart
index b1bfaa9..1d248de 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -453,7 +453,7 @@
}
Widget _buildScrollableAppBar(BuildContext context) {
- final EdgeInsets padding = MediaQuery.of(context)?.padding ?? EdgeInsets.zero;
+ final EdgeInsets padding = MediaQuery.of(context).padding;
final double expandedHeight = (config.appBar?.expandedHeight ?? 0.0) + padding.top;
final double collapsedHeight = (config.appBar?.collapsedHeight ?? 0.0) + padding.top;
final double minimumHeight = (config.appBar?.minimumHeight ?? 0.0) + padding.top;
@@ -500,7 +500,7 @@
@override
Widget build(BuildContext context) {
- final EdgeInsets padding = MediaQuery.of(context)?.padding ?? EdgeInsets.zero;
+ final EdgeInsets padding = MediaQuery.of(context).padding;
if (_snackBars.length > 0) {
final ModalRoute<dynamic> route = ModalRoute.of(context);
diff --git a/packages/flutter/lib/src/material/two_level_list.dart b/packages/flutter/lib/src/material/two_level_list.dart
index 90301fd..a242976 100644
--- a/packages/flutter/lib/src/material/two_level_list.dart
+++ b/packages/flutter/lib/src/material/two_level_list.dart
@@ -154,14 +154,25 @@
}
class TwoLevelList extends StatelessWidget {
- TwoLevelList({ Key key, this.scrollableKey, this.items, this.type: MaterialListType.twoLine }) : super(key: key);
+ TwoLevelList({
+ Key key,
+ this.scrollableKey,
+ this.items,
+ this.type: MaterialListType.twoLine,
+ this.scrollablePadding
+ }) : super(key: key);
final List<Widget> items;
final MaterialListType type;
final Key scrollableKey;
+ final EdgeInsets scrollablePadding;
@override
Widget build(BuildContext context) {
- return new Block(children: KeyedSubtree.ensureUniqueKeysForList(items), scrollableKey: scrollableKey);
+ return new Block(
+ padding: scrollablePadding,
+ children: KeyedSubtree.ensureUniqueKeysForList(items),
+ scrollableKey: scrollableKey
+ );
}
}
diff --git a/packages/flutter/lib/src/painting/box_painter.dart b/packages/flutter/lib/src/painting/box_painter.dart
index 651cefb..97cbb6a 100644
--- a/packages/flutter/lib/src/painting/box_painter.dart
+++ b/packages/flutter/lib/src/painting/box_painter.dart
@@ -25,30 +25,52 @@
return borderRadius > shortestSide ? shortestSide : borderRadius;
}
+/// The style of line to draw for a [BorderSide] in a [Border].
+enum BorderStyle {
+ /// Skip the border.
+ none,
+
+ /// Draw the border as a solid line.
+ solid,
+
+ // if you add more, think about how they will lerp
+}
+
/// A side of a border of a box.
class BorderSide {
const BorderSide({
this.color: const Color(0xFF000000),
- this.width: 1.0
+ this.width: 1.0,
+ this.style: BorderStyle.solid
});
/// The color of this side of the border.
final Color color;
- /// The width of this side of the border.
+ /// The width of this side of the border, in logical pixels. A
+ /// zero-width border is a hairline border. To omit the border
+ /// entirely, set the [style] to [BorderStyle.none].
final double width;
- /// A black border side of zero width.
- static const BorderSide none = const BorderSide(width: 0.0);
+ /// The style of this side of the border.
+ ///
+ /// To omit a side, set [style] to [BorderStyle.none]. This skips
+ /// painting the border, but the border still has a [width].
+ final BorderStyle style;
+
+ /// A hairline black border that is not rendered.
+ static const BorderSide none = const BorderSide(width: 0.0, style: BorderStyle.none);
/// Creates a copy of this border but with the given fields replaced with the new values.
BorderSide copyWith({
Color color,
- double width
+ double width,
+ BorderStyle style
}) {
return new BorderSide(
color: color ?? this.color,
- width: width ?? this.width
+ width: width ?? this.width,
+ style: style ?? this.style
);
}
@@ -56,9 +78,38 @@
static BorderSide lerp(BorderSide a, BorderSide b, double t) {
assert(a != null);
assert(b != null);
+ if (t == 0.0)
+ return a;
+ if (t == 1.0)
+ return b;
+ if (a.style == b.style) {
+ return new BorderSide(
+ color: Color.lerp(a.color, b.color, t),
+ width: ui.lerpDouble(a.width, b.width, t),
+ style: a.style // == b.style
+ );
+ }
+ Color colorA, colorB;
+ switch (a.style) {
+ case BorderStyle.solid:
+ colorA = a.color;
+ break;
+ case BorderStyle.none:
+ colorA = a.color.withAlpha(0x00);
+ break;
+ }
+ switch (b.style) {
+ case BorderStyle.solid:
+ colorB = b.color;
+ break;
+ case BorderStyle.none:
+ colorB = b.color.withAlpha(0x00);
+ break;
+ }
return new BorderSide(
- color: Color.lerp(a.color, b.color, t),
- width: ui.lerpDouble(a.width, b.width, t)
+ color: Color.lerp(colorA, colorB, t),
+ width: ui.lerpDouble(a.width, b.width, t),
+ style: BorderStyle.solid
);
}
@@ -70,14 +121,15 @@
return false;
final BorderSide typedOther = other;
return color == typedOther.color &&
- width == typedOther.width;
+ width == typedOther.width &&
+ style == typedOther.style;
}
@override
- int get hashCode => hashValues(color, width);
+ int get hashCode => hashValues(color, width, style);
@override
- String toString() => 'BorderSide($color, $width)';
+ String toString() => 'BorderSide($color, $width, $style)';
}
/// A border of a box, comprised of four sides.
@@ -92,9 +144,10 @@
/// A uniform border with all sides the same color and width.
factory Border.all({
Color color: const Color(0xFF000000),
- double width: 1.0
+ double width: 1.0,
+ BorderStyle style: BorderStyle.solid
}) {
- final BorderSide side = new BorderSide(color: color, width: width);
+ final BorderSide side = new BorderSide(color: color, width: width, style: style);
return new Border(top: side, right: side, bottom: side, left: side);
}
@@ -134,6 +187,12 @@
left.width != topWidth)
return false;
+ final BorderStyle topStyle = top.style;
+ if (right.style != topStyle ||
+ bottom.style != topStyle ||
+ left.style != topStyle)
+ return false;
+
return true;
}
@@ -186,57 +245,99 @@
assert(bottom != null);
assert(left != null);
- Paint paint = new Paint();
+ Paint paint = new Paint()
+ ..strokeWidth = 0.0; // used for hairline borders
Path path;
- // TODO(ianh): Handle hairline border by drawing a single line instead of a wedge
+ switch (top.style) {
+ case BorderStyle.solid:
+ paint.color = top.color;
+ path = new Path();
+ path.moveTo(rect.left, rect.top);
+ path.lineTo(rect.right, rect.top);
+ if (top.width == 0.0) {
+ paint.style = PaintingStyle.stroke;
+ } else {
+ paint.style = PaintingStyle.fill;
+ path.lineTo(rect.right - right.width, rect.top + top.width);
+ path.lineTo(rect.left + left.width, rect.top + top.width);
+ }
+ canvas.drawPath(path, paint);
+ break;
+ case BorderStyle.none: ;
+ }
- paint.color = top.color;
- path = new Path();
- path.moveTo(rect.left, rect.top);
- path.lineTo(rect.left + left.width, rect.top + top.width);
- path.lineTo(rect.right - right.width, rect.top + top.width);
- path.lineTo(rect.right, rect.top);
- path.close();
- canvas.drawPath(path, paint);
+ switch (right.style) {
+ case BorderStyle.solid:
+ paint.color = right.color;
+ path = new Path();
+ path.moveTo(rect.right, rect.top);
+ path.lineTo(rect.right, rect.bottom);
+ if (right.width == 0.0) {
+ paint.style = PaintingStyle.stroke;
+ } else {
+ paint.style = PaintingStyle.fill;
+ path.lineTo(rect.right - right.width, rect.bottom - bottom.width);
+ path.lineTo(rect.right - right.width, rect.top + top.width);
+ }
+ canvas.drawPath(path, paint);
+ break;
+ case BorderStyle.none: ;
+ }
- paint.color = right.color;
- path = new Path();
- path.moveTo(rect.right, rect.top);
- path.lineTo(rect.right - right.width, rect.top + top.width);
- path.lineTo(rect.right - right.width, rect.bottom - bottom.width);
- path.lineTo(rect.right, rect.bottom);
- path.close();
- canvas.drawPath(path, paint);
+ switch (bottom.style) {
+ case BorderStyle.solid:
+ paint.color = bottom.color;
+ path = new Path();
+ path.moveTo(rect.right, rect.bottom);
+ path.lineTo(rect.left, rect.bottom);
+ if (bottom.width == 0.0) {
+ paint.style = PaintingStyle.stroke;
+ } else {
+ paint.style = PaintingStyle.fill;
+ path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
+ path.lineTo(rect.right - right.width, rect.bottom - bottom.width);
+ }
+ canvas.drawPath(path, paint);
+ break;
+ case BorderStyle.none: ;
+ }
- paint.color = bottom.color;
- path = new Path();
- path.moveTo(rect.right, rect.bottom);
- path.lineTo(rect.right - right.width, rect.bottom - bottom.width);
- path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
- path.lineTo(rect.left, rect.bottom);
- path.close();
- canvas.drawPath(path, paint);
-
- paint.color = left.color;
- path = new Path();
- path.moveTo(rect.left, rect.bottom);
- path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
- path.lineTo(rect.left + left.width, rect.top + top.width);
- path.lineTo(rect.left, rect.top);
- path.close();
- canvas.drawPath(path, paint);
+ switch (left.style) {
+ case BorderStyle.solid:
+ paint.color = left.color;
+ path = new Path();
+ path.moveTo(rect.left, rect.bottom);
+ path.lineTo(rect.left, rect.top);
+ if (right.width == 0.0) {
+ paint.style = PaintingStyle.stroke;
+ } else {
+ paint.style = PaintingStyle.fill;
+ path.lineTo(rect.left + left.width, rect.top + top.width);
+ path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
+ }
+ canvas.drawPath(path, paint);
+ break;
+ case BorderStyle.none: ;
+ }
}
void _paintBorderWithRadius(Canvas canvas, Rect rect, double borderRadius) {
assert(isUniform);
- Color color = top.color;
- double width = top.width;
+ Paint paint = new Paint()
+ ..color = top.color;
double radius = _getEffectiveBorderRadius(rect, borderRadius);
- // TODO(ianh): Handle hairline borders by just drawing an RRect instead
RRect outer = new RRect.fromRectXY(rect, radius, radius);
- RRect inner = new RRect.fromRectXY(rect.deflate(width), radius - width, radius - width);
- canvas.drawDRRect(outer, inner, new Paint()..color = color);
+ double width = top.width;
+ if (width == 0.0) {
+ paint
+ ..style = PaintingStyle.stroke
+ ..strokeWidth = 0.0;
+ canvas.drawRRect(outer, paint);
+ } else {
+ RRect inner = new RRect.fromRectXY(rect.deflate(width), radius - width, radius - width);
+ canvas.drawDRRect(outer, inner, paint);
+ }
}
void _paintBorderWithCircle(Canvas canvas, Rect rect) {
@@ -568,6 +669,14 @@
/// As small as possible while still covering the entire box.
cover,
+ /// Make sure the full width of the image is shown, regardless of
+ /// whether this means the image overflows the box vertically.
+ fitWidth,
+
+ /// Make sure the full height of the image is shown, regardless of
+ /// whether this means the image overflows the box horizontally.
+ fitHeight,
+
/// Center the image within the box and discard any portions of the image that
/// lie outside the box.
none,
@@ -672,6 +781,16 @@
}
destinationSize = outputSize;
break;
+ case ImageFit.fitWidth:
+ sourceSize = new Size(inputSize.width, inputSize.width * outputSize.height / outputSize.width);
+ sourcePosition = new Point(0.0, (inputSize.height - sourceSize.height) * (alignment?.dy ?? 0.5));
+ destinationSize = new Size(outputSize.width, sourceSize.height * outputSize.width / sourceSize.width);
+ break;
+ case ImageFit.fitHeight:
+ sourceSize = new Size(inputSize.height * outputSize.width / outputSize.height, inputSize.height);
+ sourcePosition = new Point((inputSize.width - sourceSize.width) * (alignment?.dx ?? 0.5), 0.0);
+ destinationSize = new Size(sourceSize.width * outputSize.height / sourceSize.height, outputSize.height);
+ break;
case ImageFit.none:
sourceSize = new Size(math.min(inputSize.width, outputSize.width),
math.min(inputSize.height, outputSize.height));
diff --git a/packages/flutter/lib/src/painting/edge_insets.dart b/packages/flutter/lib/src/painting/edge_insets.dart
index 619b67c..3749faa 100644
--- a/packages/flutter/lib/src/painting/edge_insets.dart
+++ b/packages/flutter/lib/src/painting/edge_insets.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:ui' as ui show lerpDouble;
+import 'dart:ui' as ui show lerpDouble, WindowPadding;
import 'basic_types.dart';
@@ -23,20 +23,26 @@
/// Constructs insets where all the offsets are value.
const EdgeInsets.all(double value)
- : top = value, right = value, bottom = value, left = value;
+ : left = value, top = value, right = value, bottom = value;
/// Constructs insets with only the given values non-zero.
const EdgeInsets.only({
+ this.left: 0.0,
this.top: 0.0,
this.right: 0.0,
- this.bottom: 0.0,
- this.left: 0.0
+ this.bottom: 0.0
});
/// Constructs insets with symmetrical vertical and horizontal offsets.
const EdgeInsets.symmetric({ double vertical: 0.0,
double horizontal: 0.0 })
- : top = vertical, left = horizontal, bottom = vertical, right = horizontal;
+ : left = horizontal, top = vertical, right = horizontal, bottom = vertical;
+
+ EdgeInsets.fromWindowPadding(ui.WindowPadding padding)
+ : left = padding.left, top = padding.top, right = padding.right, bottom = padding.bottom;
+
+ /// The offset from the left.
+ final double left;
/// The offset from the top.
final double top;
@@ -47,11 +53,8 @@
/// The offset from the bottom.
final double bottom;
- /// The offset from the left.
- final double left;
-
/// Whether every dimension is non-negative.
- bool get isNonNegative => top >= 0.0 && right >= 0.0 && bottom >= 0.0 && left >= 0.0;
+ bool get isNonNegative => left >= 0.0 && top >= 0.0 && right >= 0.0 && bottom >= 0.0;
/// The total offset in the vertical direction.
double get horizontal => left + right;
@@ -151,15 +154,15 @@
if (other is! EdgeInsets)
return false;
final EdgeInsets typedOther = other;
- return top == typedOther.top &&
+ return left == typedOther.left &&
+ top == typedOther.top &&
right == typedOther.right &&
- bottom == typedOther.bottom &&
- left == typedOther.left;
+ bottom == typedOther.bottom;
}
@override
- int get hashCode => hashValues(top, left, bottom, right);
+ int get hashCode => hashValues(left, top, right, bottom);
@override
- String toString() => "EdgeInsets($top, $right, $bottom, $left)";
+ String toString() => "EdgeInsets($left, $top, $right, $bottom)";
}
diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart
index 9e2fae3..b44badd 100644
--- a/packages/flutter/lib/src/rendering/box.dart
+++ b/packages/flutter/lib/src/rendering/box.dart
@@ -410,7 +410,7 @@
/// Parent data used by [RenderBox] and its subclasses.
class BoxParentData extends ParentData {
- /// The offset at which to paint the child in the parent's coordinate system
+ /// The offset at which to paint the child in the parent's coordinate system.
Offset offset = Offset.zero;
@override
diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart
index 2b946dc..104e4a1 100644
--- a/packages/flutter/lib/src/rendering/proxy_box.dart
+++ b/packages/flutter/lib/src/rendering/proxy_box.dart
@@ -567,11 +567,14 @@
/// This class paints its child into an intermediate buffer and then blends the
/// child back into the scene partially transparent.
///
-/// This class is relatively expensive because it requires painting the child
-/// into an intermediate buffer.
+/// For values of opacity other than 0.0 and 1.0, this class is relatively
+/// expensive because it requires painting the child into an intermediate
+/// buffer. For the value 0.0, the child is simply not painted at all. For the
+/// value 1.0, the child is painted immediately without an intermediate buffer.
class RenderOpacity extends RenderProxyBox {
RenderOpacity({ RenderBox child, double opacity: 1.0 })
: _opacity = opacity, _alpha = _getAlphaFromOpacity(opacity), super(child) {
+ assert(opacity != null);
assert(opacity >= 0.0 && opacity <= 1.0);
}
@@ -582,6 +585,12 @@
///
/// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
/// (i.e., invisible).
+ ///
+ /// The opacity must not be null.
+ ///
+ /// Values 1.0 and 0.0 are painted with a fast path. Other values
+ /// require painting the child into an intermediate buffer, which is
+ /// expensive.
double get opacity => _opacity;
double _opacity;
void set opacity (double newOpacity) {
diff --git a/packages/flutter/lib/src/services/fetch.dart b/packages/flutter/lib/src/services/fetch.dart
index fbe8b59..bf2f7fc 100644
--- a/packages/flutter/lib/src/services/fetch.dart
+++ b/packages/flutter/lib/src/services/fetch.dart
@@ -4,32 +4,47 @@
import 'dart:async';
-import 'package:mojo/mojo/url_request.mojom.dart';
-import 'package:mojo/mojo/url_response.mojom.dart';
-import 'package:mojo_services/mojo/url_loader.mojom.dart';
+import 'package:mojo/mojo/url_request.mojom.dart' as mojom;
+import 'package:mojo/mojo/url_response.mojom.dart' as mojom;
+import 'package:mojo_services/mojo/url_loader.mojom.dart' as mojom;
import '../http/mojo_client.dart';
+import 'print.dart';
export 'package:mojo/mojo/url_response.mojom.dart' show UrlResponse;
-Future<UrlResponse> fetch(UrlRequest request) async {
- UrlLoaderProxy loader = new UrlLoaderProxy.unbound();
+Future<mojom.UrlResponse> fetch(mojom.UrlRequest request, { bool require200: false }) async {
+ mojom.UrlLoaderProxy loader = new mojom.UrlLoaderProxy.unbound();
try {
MojoClient.networkService.ptr.createUrlLoader(loader);
- UrlResponse response = (await loader.ptr.start(request)).response;
+ mojom.UrlResponse response = (await loader.ptr.start(request)).response;
+ if (require200 && (response.error != null || response.statusCode != 200)) {
+ StringBuffer message = new StringBuffer();
+ message.writeln('Could not ${request.method ?? "fetch"} ${request.url ?? "resource"}');
+ if (response.error != null)
+ message.writeln('Network error: ${response.error.code} ${response.error.description ?? "<unknown network error>"}');
+ if (response.statusCode != 200)
+ message.writeln('Protocol error: ${response.statusCode} ${response.statusLine ?? "<no server message>"}');
+ if (response.url != request.url)
+ message.writeln('Final URL after redirects was: ${response.url}');
+ throw message;
+ }
return response;
- } catch (e) {
- print("NetworkService unavailable $e");
- return new UrlResponse()..statusCode = 500;
+ } catch (exception) {
+ debugPrint('-- EXCEPTION CAUGHT BY NETWORKING HTTP LIBRARY -------------------------');
+ debugPrint('An exception was raised while sending bytes to the Mojo network library:');
+ debugPrint('$exception');
+ debugPrint('------------------------------------------------------------------------');
+ return null;
} finally {
loader.close();
}
}
-Future<UrlResponse> fetchUrl(String relativeUrl) {
+Future<mojom.UrlResponse> fetchUrl(String relativeUrl, { bool require200: false }) {
String url = Uri.base.resolve(relativeUrl).toString();
- UrlRequest request = new UrlRequest()
+ mojom.UrlRequest request = new mojom.UrlRequest()
..url = url
..autoFollowRedirects = true;
- return fetch(request);
+ return fetch(request, require200: require200);
}
diff --git a/packages/flutter/lib/src/services/image_cache.dart b/packages/flutter/lib/src/services/image_cache.dart
index 4141210..560e182 100644
--- a/packages/flutter/lib/src/services/image_cache.dart
+++ b/packages/flutter/lib/src/services/image_cache.dart
@@ -52,15 +52,14 @@
@override
Future<ImageInfo> loadImage() async {
- UrlResponse response = await fetchUrl(_url);
- if (response.statusCode >= 400) {
- print("Failed (${response.statusCode}) to load image $_url");
- return null;
+ UrlResponse response = await fetchUrl(_url, require200: true);
+ if (response != null) {
+ return new ImageInfo(
+ image: await decodeImageFromDataPipe(response.body),
+ scale: _scale
+ );
}
- return new ImageInfo(
- image: await decodeImageFromDataPipe(response.body),
- scale: _scale
- );
+ return null;
}
@override
diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart
index 30d436a..5abee0c 100644
--- a/packages/flutter/lib/src/widgets/app.dart
+++ b/packages/flutter/lib/src/widgets/app.dart
@@ -104,10 +104,6 @@
WidgetsAppState<WidgetsApp> createState() => new WidgetsAppState<WidgetsApp>();
}
-EdgeInsets _getPadding(ui.WindowPadding padding) {
- return new EdgeInsets.fromLTRB(padding.left, padding.top, padding.right, padding.bottom);
-}
-
class WidgetsAppState<T extends WidgetsApp> extends State<T> implements BindingObserver {
GlobalObjectKey _navigator;
@@ -173,11 +169,7 @@
}
Widget result = new MediaQuery(
- data: new MediaQueryData(
- size: ui.window.size,
- devicePixelRatio: ui.window.devicePixelRatio,
- padding: _getPadding(ui.window.padding)
- ),
+ data: new MediaQueryData.fromWindow(ui.window),
child: new LocaleQuery(
data: _localeData,
child: new AssetVendor(
diff --git a/packages/flutter/lib/src/widgets/child_view.dart b/packages/flutter/lib/src/widgets/child_view.dart
index 4e43bee..9b19630 100644
--- a/packages/flutter/lib/src/widgets/child_view.dart
+++ b/packages/flutter/lib/src/widgets/child_view.dart
@@ -4,7 +4,6 @@
import 'package:flutter/rendering.dart';
-import 'debug.dart';
import 'framework.dart';
import 'media_query.dart';
@@ -17,7 +16,6 @@
@override
Widget build(BuildContext context) {
- assert(debugCheckHasMediaQuery(context));
return new _ChildViewWidget(
child: child,
scale: MediaQuery.of(context).devicePixelRatio
diff --git a/packages/flutter/lib/src/widgets/debug.dart b/packages/flutter/lib/src/widgets/debug.dart
index ac290ad..4e52d03 100644
--- a/packages/flutter/lib/src/widgets/debug.dart
+++ b/packages/flutter/lib/src/widgets/debug.dart
@@ -5,25 +5,6 @@
import 'dart:collection';
import 'framework.dart';
-import 'media_query.dart';
-
-bool debugCheckHasMediaQuery(BuildContext context) {
- assert(() {
- if (MediaQuery.of(context) == null) {
- Element element = context;
- throw new FlutterError(
- 'No MediaQuery widget found.\n'
- '${element.widget.runtimeType} widgets require a MediaQuery widget ancestor.\n'
- 'The specific widget that could not find a MediaQuery ancestor was:\n'
- ' ${element.widget}'
- 'The ownership chain for the affected widget is:\n'
- ' ${element.debugGetOwnershipChain(10)}'
- );
- }
- return true;
- });
- return true;
-}
Key _firstNonUniqueKey(Iterable<Widget> widgets) {
Set<Key> keySet = new HashSet<Key>();
diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart
index 1d8fe0b..1332da2 100644
--- a/packages/flutter/lib/src/widgets/drag_target.dart
+++ b/packages/flutter/lib/src/widgets/drag_target.dart
@@ -15,6 +15,9 @@
typedef void DragTargetAccept<T>(T data);
typedef Widget DragTargetBuilder<T>(BuildContext context, List<T> candidateData, List<dynamic> rejectedData);
+/// Called when a [Draggable] is dropped without being accepted by a [DragTarget].
+typedef void OnDraggableCanceled(Velocity velocity, Offset offset);
+
/// Where the [Draggable] should be anchored during a drag.
enum DragAnchor {
/// Display the feedback anchored at the position of the original child. If
@@ -45,7 +48,8 @@
this.feedback,
this.feedbackOffset: Offset.zero,
this.dragAnchor: DragAnchor.child,
- this.maxSimultaneousDrags
+ this.maxSimultaneousDrags,
+ this.onDraggableCanceled
}) : super(key: key) {
assert(child != null);
assert(feedback != null);
@@ -80,6 +84,9 @@
/// dragged at a time.
final int maxSimultaneousDrags;
+ /// Called when the draggable is dropped without being accepted by a [DragTarget].
+ final OnDraggableCanceled onDraggableCanceled;
+
/// Should return a new MultiDragGestureRecognizer instance
/// constructed with the given arguments.
MultiDragGestureRecognizer<MultiDragPointerState> createRecognizer(GestureMultiDragStartCallback onStart);
@@ -98,7 +105,8 @@
Widget feedback,
Offset feedbackOffset: Offset.zero,
DragAnchor dragAnchor: DragAnchor.child,
- int maxSimultaneousDrags
+ int maxSimultaneousDrags,
+ OnDraggableCanceled onDraggableCanceled
}) : super(
key: key,
data: data,
@@ -107,7 +115,8 @@
feedback: feedback,
feedbackOffset: feedbackOffset,
dragAnchor: dragAnchor,
- maxSimultaneousDrags: maxSimultaneousDrags
+ maxSimultaneousDrags: maxSimultaneousDrags,
+ onDraggableCanceled: onDraggableCanceled
);
@override
@@ -127,7 +136,8 @@
Widget feedback,
Offset feedbackOffset: Offset.zero,
DragAnchor dragAnchor: DragAnchor.child,
- int maxSimultaneousDrags
+ int maxSimultaneousDrags,
+ OnDraggableCanceled onDraggableCanceled
}) : super(
key: key,
data: data,
@@ -136,7 +146,8 @@
feedback: feedback,
feedbackOffset: feedbackOffset,
dragAnchor: dragAnchor,
- maxSimultaneousDrags: maxSimultaneousDrags
+ maxSimultaneousDrags: maxSimultaneousDrags,
+ onDraggableCanceled: onDraggableCanceled
);
@override
@@ -156,7 +167,8 @@
Widget feedback,
Offset feedbackOffset: Offset.zero,
DragAnchor dragAnchor: DragAnchor.child,
- int maxSimultaneousDrags
+ int maxSimultaneousDrags,
+ OnDraggableCanceled onDraggableCanceled
}) : super(
key: key,
data: data,
@@ -165,7 +177,8 @@
feedback: feedback,
feedbackOffset: feedbackOffset,
dragAnchor: dragAnchor,
- maxSimultaneousDrags: maxSimultaneousDrags
+ maxSimultaneousDrags: maxSimultaneousDrags,
+ onDraggableCanceled: onDraggableCanceled
);
@override
@@ -184,7 +197,8 @@
Widget feedback,
Offset feedbackOffset: Offset.zero,
DragAnchor dragAnchor: DragAnchor.child,
- int maxSimultaneousDrags
+ int maxSimultaneousDrags,
+ OnDraggableCanceled onDraggableCanceled
}) : super(
key: key,
data: data,
@@ -193,7 +207,8 @@
feedback: feedback,
feedbackOffset: feedbackOffset,
dragAnchor: dragAnchor,
- maxSimultaneousDrags: maxSimultaneousDrags
+ maxSimultaneousDrags: maxSimultaneousDrags,
+ onDraggableCanceled: onDraggableCanceled
);
@override
@@ -248,9 +263,11 @@
dragStartPoint: dragStartPoint,
feedback: config.feedback,
feedbackOffset: config.feedbackOffset,
- onDragEnd: () {
+ onDragEnd: (Velocity velocity, Offset offset, bool wasAccepted) {
setState(() {
_activeCount -= 1;
+ if (!wasAccepted && config.onDraggableCanceled != null)
+ config.onDraggableCanceled(velocity, offset);
});
}
);
@@ -341,6 +358,7 @@
enum _DragEndKind { dropped, canceled }
+typedef void _OnDragEnd(Velocity velocity, Offset offset, bool wasAccepted);
// The lifetime of this object is a little dubious right now. Specifically, it
// lives as long as the pointer is down. Arguably it should self-immolate if the
@@ -370,7 +388,7 @@
final Point dragStartPoint;
final Widget feedback;
final Offset feedbackOffset;
- final VoidCallback onDragEnd;
+ final _OnDragEnd onDragEnd;
_DragTargetState<T> _activeTarget;
bool _activeTargetWillAcceptDrop = false;
@@ -387,7 +405,7 @@
@override
void end(Velocity velocity) {
- finish(_DragEndKind.dropped);
+ finish(_DragEndKind.dropped, velocity);
}
@override
@@ -422,19 +440,23 @@
return null;
}
- void finish(_DragEndKind endKind) {
+ void finish(_DragEndKind endKind, [Velocity velocity]) {
+ bool wasAccepted = false;
if (_activeTarget != null) {
- if (endKind == _DragEndKind.dropped && _activeTargetWillAcceptDrop)
+ if (endKind == _DragEndKind.dropped && _activeTargetWillAcceptDrop) {
_activeTarget.didDrop(data);
- else
+ wasAccepted = true;
+ } else {
_activeTarget.didLeave(data);
+ }
}
_activeTarget = null;
_activeTargetWillAcceptDrop = false;
_entry.remove();
_entry = null;
+ // TODO(ianh): consider passing _entry as well so the client can perform an animation.
if (onDragEnd != null)
- onDragEnd();
+ onDragEnd(velocity ?? Velocity.zero, _lastOffset, wasAccepted);
}
Widget _build(BuildContext context) {
diff --git a/packages/flutter/lib/src/widgets/focus.dart b/packages/flutter/lib/src/widgets/focus.dart
index ef82659..79f3130 100644
--- a/packages/flutter/lib/src/widgets/focus.dart
+++ b/packages/flutter/lib/src/widgets/focus.dart
@@ -300,14 +300,12 @@
@override
Widget build(BuildContext context) {
MediaQueryData data = MediaQuery.of(context);
- if (data != null) {
- Size newMediaSize = data.size;
- EdgeInsets newMediaPadding = data.padding;
- if (newMediaSize != _mediaSize || newMediaPadding != _mediaPadding) {
- _mediaSize = newMediaSize;
- _mediaPadding = newMediaPadding;
- scheduleMicrotask(_ensureVisibleIfFocused);
- }
+ Size newMediaSize = data.size;
+ EdgeInsets newMediaPadding = data.padding;
+ if (newMediaSize != _mediaSize || newMediaPadding != _mediaPadding) {
+ _mediaSize = newMediaSize;
+ _mediaPadding = newMediaPadding;
+ scheduleMicrotask(_ensureVisibleIfFocused);
}
return new Semantics(
container: true,
diff --git a/packages/flutter/lib/src/widgets/media_query.dart b/packages/flutter/lib/src/widgets/media_query.dart
index 5afd18e..e4acefd 100644
--- a/packages/flutter/lib/src/widgets/media_query.dart
+++ b/packages/flutter/lib/src/widgets/media_query.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:ui' as ui;
+
import 'basic.dart';
import 'framework.dart';
@@ -18,6 +20,11 @@
class MediaQueryData {
const MediaQueryData({ this.size, this.devicePixelRatio, this.padding });
+ MediaQueryData.fromWindow(ui.Window window)
+ : size = window.size,
+ devicePixelRatio = window.devicePixelRatio,
+ padding = new EdgeInsets.fromWindowPadding(window.padding);
+
/// The size of the media (e.g, the size of the screen).
final Size size;
@@ -76,7 +83,7 @@
/// keeping your widget up-to-date.
static MediaQueryData of(BuildContext context) {
MediaQuery query = context.inheritFromWidgetOfExactType(MediaQuery);
- return query == null ? null : query.data;
+ return query?.data ?? new MediaQueryData.fromWindow(ui.window);
}
@override
diff --git a/packages/flutter/test/widget/draggable_test.dart b/packages/flutter/test/widget/draggable_test.dart
index b297c9a..1bd599e 100644
--- a/packages/flutter/test/widget/draggable_test.dart
+++ b/packages/flutter/test/widget/draggable_test.dart
@@ -9,7 +9,7 @@
void main() {
test('Drag and drop - control test', () {
testWidgets((WidgetTester tester) {
- List<dynamic> accepted = <dynamic>[];
+ List<int> accepted = <int>[];
tester.pumpWidget(new MaterialApp(
routes: <String, WidgetBuilder>{
@@ -62,7 +62,7 @@
gesture.up();
tester.pump();
- expect(accepted, equals([1]));
+ expect(accepted, equals(<int>[1]));
expect(tester.findText('Source'), isNotNull);
expect(tester.findText('Dragging'), isNull);
expect(tester.findText('Target'), isNotNull);
@@ -553,4 +553,204 @@
});
});
+
+ test('Drag and drop - onDraggableDropped not called if dropped on accepting target', () {
+ testWidgets((WidgetTester tester) {
+ List<int> accepted = <int>[];
+ bool onDraggableCanceledCalled = false;
+
+ tester.pumpWidget(new MaterialApp(
+ routes: <String, WidgetBuilder>{
+ '/': (BuildContext context) { return new Column(
+ children: <Widget>[
+ new Draggable<int>(
+ data: 1,
+ child: new Text('Source'),
+ feedback: new Text('Dragging'),
+ onDraggableCanceled: (Velocity velocity, Offset offset) {
+ onDraggableCanceledCalled = true;
+ }
+ ),
+ new DragTarget<int>(
+ builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
+ return new Container(
+ height: 100.0,
+ child: new Text('Target')
+ );
+ },
+ onAccept: (int data) {
+ accepted.add(data);
+ }
+ ),
+ ]);
+ },
+ }
+ ));
+
+ expect(accepted, isEmpty);
+ expect(tester.findText('Source'), isNotNull);
+ expect(tester.findText('Dragging'), isNull);
+ expect(tester.findText('Target'), isNotNull);
+ expect(onDraggableCanceledCalled, isFalse);
+
+ Point firstLocation = tester.getCenter(tester.findText('Source'));
+ TestGesture gesture = tester.startGesture(firstLocation, pointer: 7);
+ tester.pump();
+
+ expect(accepted, isEmpty);
+ expect(tester.findText('Source'), isNotNull);
+ expect(tester.findText('Dragging'), isNotNull);
+ expect(tester.findText('Target'), isNotNull);
+ expect(onDraggableCanceledCalled, isFalse);
+
+ Point secondLocation = tester.getCenter(tester.findText('Target'));
+ gesture.moveTo(secondLocation);
+ tester.pump();
+
+ expect(accepted, isEmpty);
+ expect(tester.findText('Source'), isNotNull);
+ expect(tester.findText('Dragging'), isNotNull);
+ expect(tester.findText('Target'), isNotNull);
+ expect(onDraggableCanceledCalled, isFalse);
+
+ gesture.up();
+ tester.pump();
+
+ expect(accepted, equals(<int>[1]));
+ expect(tester.findText('Source'), isNotNull);
+ expect(tester.findText('Dragging'), isNull);
+ expect(tester.findText('Target'), isNotNull);
+ expect(onDraggableCanceledCalled, isFalse);
+ });
+ });
+
+ test('Drag and drop - onDraggableDropped called if dropped on non-accepting target', () {
+ testWidgets((WidgetTester tester) {
+ List<int> accepted = <int>[];
+ bool onDraggableCanceledCalled = false;
+ Velocity onDraggableCanceledVelocity;
+ Offset onDraggableCanceledOffset;
+
+ tester.pumpWidget(new MaterialApp(
+ routes: <String, WidgetBuilder>{
+ '/': (BuildContext context) { return new Column(
+ children: <Widget>[
+ new Draggable<int>(
+ data: 1,
+ child: new Text('Source'),
+ feedback: new Text('Dragging'),
+ onDraggableCanceled: (Velocity velocity, Offset offset) {
+ onDraggableCanceledCalled = true;
+ onDraggableCanceledVelocity = velocity;
+ onDraggableCanceledOffset = offset;
+ }
+ ),
+ new DragTarget<int>(
+ builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
+ return new Container(
+ height: 100.0,
+ child: new Text('Target')
+ );
+ },
+ onWillAccept: (int data) => false
+ ),
+ ]);
+ },
+ }
+ ));
+
+ expect(accepted, isEmpty);
+ expect(tester.findText('Source'), isNotNull);
+ expect(tester.findText('Dragging'), isNull);
+ expect(tester.findText('Target'), isNotNull);
+ expect(onDraggableCanceledCalled, isFalse);
+
+ Point firstLocation = tester.getTopLeft(tester.findText('Source'));
+ TestGesture gesture = tester.startGesture(firstLocation, pointer: 7);
+ tester.pump();
+
+ expect(accepted, isEmpty);
+ expect(tester.findText('Source'), isNotNull);
+ expect(tester.findText('Dragging'), isNotNull);
+ expect(tester.findText('Target'), isNotNull);
+ expect(onDraggableCanceledCalled, isFalse);
+
+ Point secondLocation = tester.getCenter(tester.findText('Target'));
+ gesture.moveTo(secondLocation);
+ tester.pump();
+
+ expect(accepted, isEmpty);
+ expect(tester.findText('Source'), isNotNull);
+ expect(tester.findText('Dragging'), isNotNull);
+ expect(tester.findText('Target'), isNotNull);
+ expect(onDraggableCanceledCalled, isFalse);
+
+ gesture.up();
+ tester.pump();
+
+ expect(accepted, isEmpty);
+ expect(tester.findText('Source'), isNotNull);
+ expect(tester.findText('Dragging'), isNull);
+ expect(tester.findText('Target'), isNotNull);
+ expect(onDraggableCanceledCalled, isTrue);
+ expect(onDraggableCanceledVelocity, equals(Velocity.zero));
+ expect(onDraggableCanceledOffset, equals(new Offset(secondLocation.x, secondLocation.y)));
+ });
+ });
+
+ test('Drag and drop - onDraggableDropped called if dropped on non-accepting target with correct velocity', () {
+ testWidgets((WidgetTester tester) {
+ List<int> accepted = <int>[];
+ bool onDraggableCanceledCalled = false;
+ Velocity onDraggableCanceledVelocity;
+ Offset onDraggableCanceledOffset;
+
+ tester.pumpWidget(new MaterialApp(
+ routes: <String, WidgetBuilder>{
+ '/': (BuildContext context) { return new Column(
+ children: <Widget>[
+ new Draggable<int>(
+ data: 1,
+ child: new Text('Source'),
+ feedback: new Text('Source'),
+ onDraggableCanceled: (Velocity velocity, Offset offset) {
+ onDraggableCanceledCalled = true;
+ onDraggableCanceledVelocity = velocity;
+ onDraggableCanceledOffset = offset;
+ }
+ ),
+ new DragTarget<int>(
+ builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
+ return new Container(
+ height: 100.0,
+ child: new Text('Target')
+ );
+ },
+ onWillAccept: (int data) => false
+ ),
+ ]);
+ },
+ }
+ ));
+
+ expect(accepted, isEmpty);
+ expect(tester.findText('Source'), isNotNull);
+ expect(tester.findText('Dragging'), isNull);
+ expect(tester.findText('Target'), isNotNull);
+ expect(onDraggableCanceledCalled, isFalse);
+
+ Point flingStart = tester.getTopLeft(tester.findText('Source'));
+ tester.flingFrom(flingStart, new Offset(0.0,100.0), 1000.0);
+ tester.pump();
+
+ expect(accepted, isEmpty);
+ expect(tester.findText('Source'), isNotNull);
+ expect(tester.findText('Dragging'), isNull);
+ expect(tester.findText('Target'), isNotNull);
+ expect(onDraggableCanceledCalled, isTrue);
+ expect(onDraggableCanceledVelocity.pixelsPerSecond.dx.abs(), lessThan(0.0000001));
+ expect((onDraggableCanceledVelocity.pixelsPerSecond.dy - 1000.0).abs(), lessThan(0.0000001));
+ expect(onDraggableCanceledOffset, equals(new Offset(flingStart.x, flingStart.y) + new Offset(0.0, 100.0)));
+ });
+ });
}
diff --git a/packages/flutter/test/widget/media_query_test.dart b/packages/flutter/test/widget/media_query_test.dart
new file mode 100644
index 0000000..81ad5fe
--- /dev/null
+++ b/packages/flutter/test/widget/media_query_test.dart
@@ -0,0 +1,28 @@
+// 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 'dart:ui' as ui;
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter/widgets.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('MediaQuery has a default', () {
+ testWidgets((WidgetTester tester) {
+ Size size;
+
+ tester.pumpWidget(
+ new Builder(
+ builder: (BuildContext context) {
+ size = MediaQuery.of(context).size;
+ return new Container();
+ }
+ )
+ );
+
+ expect(size, equals(ui.window.size));
+ });
+ });
+}
diff --git a/packages/flutter_sprites/lib/src/constraint.dart b/packages/flutter_sprites/lib/src/constraint.dart
index 343cff5..05dfd53 100644
--- a/packages/flutter_sprites/lib/src/constraint.dart
+++ b/packages/flutter_sprites/lib/src/constraint.dart
@@ -66,6 +66,29 @@
}
}
+/// A [Constraint] that copies a node's rotation, optionally with [dampening].
+class ConstraintRotationToNodeRotation extends Constraint {
+ /// Creates a new constraint that copies a node's rotation, optionally
+ /// with a [baseRotation] added and using [dampening].
+ ConstraintRotationToNodeRotation(this.targetNode, { this.baseRotation: 0.0, this.dampening });
+
+ /// The node to copy the rotation from
+ final Node targetNode;
+
+ /// The base rotation will be added to the rotation that copied from the targetNode
+ final double baseRotation;
+
+ /// The filter factor used when constraining the rotation of the node. Valid
+ /// values are in the range 0.0 to 1.0
+ final double dampening;
+
+ @override
+ void constrain(Node node, double dt) {
+ double target = targetNode.rotation + baseRotation;
+ node.rotation = _dampenRotation(node.rotation, target, dampening);
+ }
+}
+
/// A [Constraint] that rotates a node to point towards another node. The target
/// node is allowed to have a different parent, but they must be in the same
/// [SpriteBox].
diff --git a/packages/flutter_test/lib/src/instrumentation.dart b/packages/flutter_test/lib/src/instrumentation.dart
index b926465..6a3c418 100644
--- a/packages/flutter_test/lib/src/instrumentation.dart
+++ b/packages/flutter_test/lib/src/instrumentation.dart
@@ -190,7 +190,7 @@
final double timeStampDelta = 1000.0 * offset.distance / (kMoveCount * velocity);
double timeStamp = 0.0;
dispatchEvent(p.down(startLocation, timeStamp: new Duration(milliseconds: timeStamp.round())), result);
- for(int i = 0; i < kMoveCount; i++) {
+ for(int i = 0; i <= kMoveCount; i++) {
final Point location = startLocation + Offset.lerp(Offset.zero, offset, i / kMoveCount);
dispatchEvent(p.move(location, timeStamp: new Duration(milliseconds: timeStamp.round())), result);
timeStamp += timeStampDelta;
diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart
index 2db94d8..a2870eb 100644
--- a/packages/flutter_tools/lib/executable.dart
+++ b/packages/flutter_tools/lib/executable.dart
@@ -6,6 +6,7 @@
import 'dart:io';
import 'package:args/command_runner.dart';
+import 'package:path/path.dart' as path;
import 'package:stack_trace/stack_trace.dart';
import 'src/base/context.dart';
@@ -31,6 +32,7 @@
import 'src/commands/upgrade.dart';
import 'src/device.dart';
import 'src/doctor.dart';
+import 'src/globals.dart';
import 'src/runner/flutter_command_runner.dart';
/// Main entry point for commands.
@@ -89,9 +91,65 @@
// We've caught an exit code.
exit(error.exitCode);
} else {
- stderr.writeln(error);
- stderr.writeln(chain.terse);
+ // We've crashed; emit a log report.
+ stderr.writeln();
+ stderr.writeln('Oops; flutter has crashed: "$error"');
+
+ File file = _createCrashReport(args, error, chain);
+
+ stderr.writeln();
+ stderr.writeln('Crash report written to ${path.relative(file.path)}.');
+ stderr.writeln('Please let us know at https://github.com/flutter/flutter/issues!');
exit(1);
}
});
}
+
+File _createCrashReport(List<String> args, dynamic error, Chain chain) {
+ File crashFile = _createCrashFileName(Directory.current, 'flutter');
+
+ StringBuffer buf = new StringBuffer();
+
+ buf.writeln('Flutter crash report; please file at https://github.com/flutter/flutter/issues.\n');
+
+ buf.writeln('## command\n');
+ buf.writeln('flutter ${args.join(' ')}\n');
+
+ buf.writeln('## exception\n');
+ buf.writeln('$error\n');
+ buf.writeln('${chain.terse}');
+
+ buf.writeln('## flutter doctor\n');
+ buf.writeln(_doctorText());
+
+ crashFile.writeAsStringSync(buf.toString());
+
+ return crashFile;
+}
+
+File _createCrashFileName(Directory dir, String baseName) {
+ int i = 1;
+
+ while (true) {
+ String name = '${baseName}_${i.toString().padLeft(2, '0')}.log';
+ File file = new File(path.join(dir.path, name));
+ if (!file.existsSync())
+ return file;
+ i++;
+ }
+}
+
+String _doctorText() {
+ try {
+ BufferLogger logger = new BufferLogger();
+ AppContext appContext = new AppContext();
+
+ appContext[Logger] = logger;
+
+ appContext.runInZone(() => doctor.diagnose());
+
+ return logger.statusText;
+ } catch (error) {
+ return '';
+ }
+}
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index 6cdc73e..f03a92c 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -223,9 +223,9 @@
ServiceProtocolDiscovery serviceProtocolDiscovery =
new ServiceProtocolDiscovery(logReader);
- // We take this future here but do not wait for completion until *after*
- // we start the bundle.
- Future<int> serviceProtocolPort = serviceProtocolDiscovery.nextPort();
+ // We take this future here but do not wait for completion until *after* we
+ // start the bundle.
+ Future<int> scrapeServicePort = serviceProtocolDiscovery.nextPort();
List<String> cmd = adbCommandForDevice(<String>[
'shell', 'am', 'start',
@@ -250,13 +250,23 @@
return false;
}
- // Wait for the service protocol port here. This will complete once
- // the device has printed "Observatory is listening on..."
- int devicePort = await serviceProtocolPort;
- printTrace('service protocol port = $devicePort');
- await _forwardObservatoryPort(devicePort, debugPort);
+ // Wait for the service protocol port here. This will complete once the
+ // device has printed "Observatory is listening on...".
+ printTrace('Waiting for observatory port to be available...');
- return true;
+ try {
+ int devicePort = await scrapeServicePort.timeout(new Duration(seconds: 12));
+ printTrace('service protocol port = $devicePort');
+ await _forwardObservatoryPort(devicePort, debugPort);
+ return true;
+ } catch (error) {
+ if (error is TimeoutException)
+ printError('Timed out while waiting for a debug connection.');
+ else
+ printError('Error waiting for a debug connection: $error');
+
+ return false;
+ }
}
@override
@@ -303,7 +313,7 @@
return runCommandAndStreamOutput(command).then((int exitCode) => exitCode == 0);
}
- // TODO(devoncarew): Return android_arm or android_x64 based on [isLocalEmulator].
+ // TODO(devoncarew): Use isLocalEmulator to return android_arm or android_x64.
@override
TargetPlatform get platform => TargetPlatform.android_arm;
@@ -533,7 +543,9 @@
String lastTimestamp = device.lastLogcatTimestamp;
if (lastTimestamp != null)
args.addAll(<String>['-T', lastTimestamp]);
- args.addAll(<String>['-s', 'flutter:V', 'ActivityManager:W', 'System.err:W', '*:F']);
+ args.addAll(<String>[
+ '-s', 'flutter:V', 'SkyMain:V', 'AndroidRuntime:W', 'ActivityManager:W', 'System.err:W', '*:F'
+ ]);
_process = await runCommand(device.adbCommandForDevice(args));
_stdoutSubscription =
_process.stdout.transform(UTF8.decoder)
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index 7e9f9c9..e918a63 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -125,8 +125,7 @@
for (BuildConfiguration config in configs) {
switch (config.targetPlatform) {
case TargetPlatform.android_arm:
- assert(android == null);
- android = new AndroidApk.fromBuildConfiguration(config);
+ android ??= new AndroidApk.fromBuildConfiguration(config);
break;
case TargetPlatform.ios:
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
index 513f059..2c5e375 100644
--- a/packages/flutter_tools/lib/src/artifacts.dart
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -72,12 +72,15 @@
class ArtifactStore {
static const List<Artifact> knownArtifacts = const <Artifact>[
+ // tester
const Artifact._(
name: 'Flutter Tester',
fileName: 'sky_shell',
type: ArtifactType.shell,
targetPlatform: TargetPlatform.linux_x64
),
+
+ // snapshotters
const Artifact._(
name: 'Sky Snapshot',
fileName: 'sky_snapshot',
@@ -90,6 +93,8 @@
type: ArtifactType.snapshot,
hostPlatform: HostPlatform.mac
),
+
+ // mojo
const Artifact._(
name: 'Flutter for Mojo',
fileName: 'flutter.mojo',
@@ -102,6 +107,8 @@
type: ArtifactType.mojo,
targetPlatform: TargetPlatform.linux_x64
),
+
+ // android-arm
const Artifact._(
name: 'Compiled Java code',
fileName: 'classes.dex.jar',
@@ -126,6 +133,8 @@
type: ArtifactType.androidLibSkyShell,
targetPlatform: TargetPlatform.android_arm
),
+
+ // iOS
const Artifact._(
name: 'iOS Runner (Xcode Project)',
fileName: 'FlutterXcode.zip',
@@ -202,7 +211,7 @@
return cacheDir;
}
- static Future<String> getPath(Artifact artifact) async {
+ static String getPath(Artifact artifact) {
File cachedFile = new File(path.join(
getBaseCacheDir().path, 'engine', artifact.platform, artifact.fileName
));
diff --git a/packages/flutter_tools/lib/src/base/process.dart b/packages/flutter_tools/lib/src/base/process.dart
index 25b8bb3..aa5302a 100644
--- a/packages/flutter_tools/lib/src/base/process.dart
+++ b/packages/flutter_tools/lib/src/base/process.dart
@@ -116,22 +116,28 @@
printTrace(cmdText);
ProcessResult results =
Process.runSync(cmd[0], cmd.getRange(1, cmd.length).toList(), workingDirectory: workingDirectory);
- if (results.exitCode != 0) {
- String errorDescription = 'Error code ${results.exitCode} '
- 'returned when attempting to run command: ${cmd.join(' ')}';
- printTrace(errorDescription);
- if (results.stderr.length > 0) {
- if (noisyErrors) {
- printError(results.stderr.trim());
- } else {
- printTrace('Errors logged: ${results.stderr.trim()}');
- }
- }
- if (checked)
- throw errorDescription;
+
+ printTrace('Exit code ${results.exitCode} from: ${cmd.join(' ')}');
+
+ if (results.stdout.isNotEmpty) {
+ if (results.exitCode != 0 && noisyErrors)
+ printStatus(results.stdout.trim());
+ else
+ printTrace(results.stdout.trim());
}
- if (results.stdout.trim().isNotEmpty)
- printTrace(results.stdout.trim());
+
+ if (results.exitCode != 0) {
+ if (results.stderr.isNotEmpty) {
+ if (noisyErrors)
+ printError(results.stderr.trim());
+ else
+ printTrace(results.stderr.trim());
+ }
+
+ if (checked)
+ throw 'Exit code ${results.exitCode} from: ${cmd.join(' ')}';
+ }
+
return results.stdout.trim();
}
diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart
index 6c4319c9..f974270 100644
--- a/packages/flutter_tools/lib/src/commands/build_apk.dart
+++ b/packages/flutter_tools/lib/src/commands/build_apk.dart
@@ -187,7 +187,10 @@
await downloadToolchain();
+ // TODO(devoncarew): This command should take an arg for the output type (arm / x64).
+
return await buildAndroid(
+ TargetPlatform.android_arm,
toolchain: toolchain,
configs: buildConfigurations,
enginePath: runner.enginePath,
@@ -212,6 +215,7 @@
) async {
List<String> artifactPaths;
if (enginePath != null) {
+ // TODO(devoncarew): Support x64.
artifactPaths = [
'$enginePath/third_party/icu/android/icudtl.dat',
'${config.buildDir}/gen/sky/shell/shell/classes.dex.jar',
@@ -225,10 +229,13 @@
ArtifactType.androidLibSkyShell,
ArtifactType.androidKeystore,
];
- Iterable<Future<String>> pathFutures = artifactTypes.map(
- (ArtifactType type) => ArtifactStore.getPath(ArtifactStore.getArtifact(
- type: type, targetPlatform: TargetPlatform.android_arm)));
- artifactPaths = await Future.wait(pathFutures);
+ Iterable<String> pathFutures = artifactTypes.map((ArtifactType type) {
+ return ArtifactStore.getPath(ArtifactStore.getArtifact(
+ type: type,
+ targetPlatform: config.targetPlatform
+ ));
+ });
+ artifactPaths = pathFutures.toList();
}
_ApkComponents components = new _ApkComponents();
@@ -254,9 +261,14 @@
}
int _buildApk(
- _ApkComponents components, String flxPath, ApkKeystoreInfo keystore, String outputFile
+ TargetPlatform platform,
+ _ApkComponents components,
+ String flxPath,
+ ApkKeystoreInfo keystore,
+ String outputFile
) {
Directory tempDir = Directory.systemTemp.createTempSync('flutter_tools');
+
try {
_ApkBuilder builder = new _ApkBuilder(androidSdk.latestVersion);
@@ -273,7 +285,9 @@
_AssetBuilder artifactBuilder = new _AssetBuilder(tempDir, 'artifacts');
artifactBuilder.add(classesDex, 'classes.dex');
- artifactBuilder.add(components.libSkyShell, 'lib/armeabi-v7a/libsky_shell.so');
+ // x86? x86_64?
+ String abiDir = platform == TargetPlatform.android_arm ? 'armeabi-v7a' : 'x86';
+ artifactBuilder.add(components.libSkyShell, 'lib/$abiDir/libsky_shell.so');
File unalignedApk = new File('${tempDir.path}/app.apk.unaligned');
builder.package(
@@ -359,7 +373,8 @@
return false;
}
-Future<int> buildAndroid({
+Future<int> buildAndroid(
+ TargetPlatform platform, {
Toolchain toolchain,
List<BuildConfiguration> configs,
String enginePath,
@@ -367,7 +382,7 @@
String manifest: _kDefaultAndroidManifestPath,
String resources,
String outputFile: _kDefaultOutputPath,
- String target: '',
+ String target,
String flxPath,
ApkKeystoreInfo keystore
}) async {
@@ -399,10 +414,9 @@
resources = _kDefaultResourcesPath;
}
- BuildConfiguration config = configs.firstWhere(
- (BuildConfiguration bc) => bc.targetPlatform == TargetPlatform.android_arm
- );
+ BuildConfiguration config = configs.firstWhere((BuildConfiguration bc) => bc.targetPlatform == platform);
_ApkComponents components = await _findApkComponents(config, enginePath, manifest, resources);
+
if (components == null) {
printError('Failure building APK. Unable to find components.');
return 1;
@@ -416,36 +430,34 @@
printError('(Omit the --flx option to build the FLX automatically)');
return 1;
}
- return _buildApk(components, flxPath, keystore, outputFile);
+
+ return _buildApk(platform, components, flxPath, keystore, outputFile);
} else {
// Find the path to the main Dart file; build the FLX.
String mainPath = findMainDartFile(target);
String localBundlePath = await flx.buildFlx(toolchain, mainPath: mainPath);
- return _buildApk(components, localBundlePath, keystore, outputFile);
+ return _buildApk(platform, components, localBundlePath, keystore, outputFile);
}
}
// TODO(mpcomplete): move this to Device?
/// This is currently Android specific.
-Future<int> buildAll(
- List<Device> devices,
+Future<int> buildForDevice(
+ Device device,
ApplicationPackageStore applicationPackages,
Toolchain toolchain,
List<BuildConfiguration> configs, {
String enginePath,
String target: ''
}) async {
- for (Device device in devices) {
- ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
- if (package == null)
- continue;
+ ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
+ if (package == null)
+ return 0;
- // TODO(mpcomplete): Temporary hack. We only support the apk builder atm.
- if (package != applicationPackages.android)
- continue;
-
- int result = await build(toolchain, configs, enginePath: enginePath, target: target);
+ // TODO(mpcomplete): Temporary hack. We only support the apk builder atm.
+ if (package == applicationPackages.android) {
+ int result = await build(device.platform, toolchain, configs, enginePath: enginePath, target: target);
if (result != 0)
return result;
}
@@ -454,10 +466,11 @@
}
Future<int> build(
+ TargetPlatform platform,
Toolchain toolchain,
List<BuildConfiguration> configs, {
String enginePath,
- String target: ''
+ String target
}) async {
if (!FileSystemEntity.isFileSync(_kDefaultAndroidManifestPath)) {
printError('Cannot build APK. Missing $_kDefaultAndroidManifestPath.');
@@ -465,6 +478,7 @@
}
int result = await buildAndroid(
+ platform,
toolchain: toolchain,
configs: configs,
enginePath: enginePath,
diff --git a/packages/flutter_tools/lib/src/commands/drive.dart b/packages/flutter_tools/lib/src/commands/drive.dart
index 18581e2..83171af 100644
--- a/packages/flutter_tools/lib/src/commands/drive.dart
+++ b/packages/flutter_tools/lib/src/commands/drive.dart
@@ -245,8 +245,10 @@
// TODO(devoncarew): We should remove the need to special case here.
if (command.device is AndroidDevice) {
printTrace('Building an APK.');
- int result = await build_apk.build(command.toolchain, command.buildConfigurations,
- enginePath: command.runner.enginePath, target: command.target);
+ int result = await build_apk.build(
+ command.device.platform, command.toolchain, command.buildConfigurations,
+ enginePath: command.runner.enginePath, target: command.target
+ );
if (result != 0)
return result;
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index 9aafbee..aff777a 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -182,8 +182,8 @@
if (install) {
printTrace('Running build command.');
- int result = await buildAll(
- <Device>[device], applicationPackages, toolchain, configs,
+ int result = await buildForDevice(
+ device, applicationPackages, toolchain, configs,
enginePath: enginePath,
target: target
);
@@ -232,15 +232,8 @@
platformArgs: platformArgs
);
- if (!result) {
+ if (!result)
printError('Error running application on ${device.name}.');
- } else {
- // If the user specified --start-paused (and the device supports it) then
- // wait for the observatory port to become available before returning from
- // `startApp()`.
- if (startPaused && device.supportsStartPaused)
- await delayUntilObservatoryAvailable('localhost', debugPort);
- }
return result ? 0 : 2;
}
diff --git a/packages/flutter_tools/lib/src/commands/run_mojo.dart b/packages/flutter_tools/lib/src/commands/run_mojo.dart
index cf2d535..c1e4034 100644
--- a/packages/flutter_tools/lib/src/commands/run_mojo.dart
+++ b/packages/flutter_tools/lib/src/commands/run_mojo.dart
@@ -99,7 +99,7 @@
if (config == null || config.type == BuildType.prebuilt) {
TargetPlatform targetPlatform = argResults['android'] ? TargetPlatform.android_arm : TargetPlatform.linux_x64;
Artifact artifact = ArtifactStore.getArtifact(type: ArtifactType.mojo, targetPlatform: targetPlatform);
- flutterPath = _makePathAbsolute(await ArtifactStore.getPath(artifact));
+ flutterPath = _makePathAbsolute(ArtifactStore.getPath(artifact));
} else {
String localPath = path.join(config.buildDir, 'flutter.mojo');
flutterPath = _makePathAbsolute(localPath);
diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart
index 50d1e8e..4828cbb 100644
--- a/packages/flutter_tools/lib/src/commands/test.dart
+++ b/packages/flutter_tools/lib/src/commands/test.dart
@@ -49,7 +49,7 @@
if (config.type == BuildType.prebuilt) {
Artifact artifact = ArtifactStore.getArtifact(
type: ArtifactType.shell, targetPlatform: config.targetPlatform);
- return await ArtifactStore.getPath(artifact);
+ return ArtifactStore.getPath(artifact);
} else {
switch (config.targetPlatform) {
case TargetPlatform.linux_x64:
diff --git a/packages/flutter_tools/lib/src/flx.dart b/packages/flutter_tools/lib/src/flx.dart
index d9e89da..635423c 100644
--- a/packages/flutter_tools/lib/src/flx.dart
+++ b/packages/flutter_tools/lib/src/flx.dart
@@ -8,6 +8,7 @@
import 'package:flx/bundle.dart';
import 'package:flx/signing.dart';
+import 'package:json_schema/json_schema.dart';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';
@@ -108,6 +109,20 @@
return loadYaml(manifestDescriptor);
}
+Future<int> _validateManifest(Object manifest) async {
+ String schemaPath = path.join(path.absolute(ArtifactStore.flutterRoot),
+ 'packages', 'flutter_tools', 'schema', 'flutter_yaml.json');
+ Schema schema = await Schema.createSchemaFromUrl('file://$schemaPath');
+
+ Validator validator = new Validator(schema);
+ if (validator.validate(manifest))
+ return 0;
+
+ printError('Error in flutter.yaml:');
+ printError(validator.errors.join('\n'));
+ return 1;
+}
+
ZipEntry _createAssetEntry(_Asset asset) {
String source = asset.source ?? asset.key;
File file = new File('${asset.base}/$source');
@@ -184,7 +199,14 @@
String workingDirPath: defaultWorkingDirPath,
bool precompiledSnapshot: false
}) async {
- Map<String, dynamic> manifestDescriptor = _loadManifest(manifestPath);
+ Object manifest = _loadManifest(manifestPath);
+ if (manifest != null) {
+ int result = await _validateManifest(manifest);
+ if (result != 0)
+ return result;
+ }
+ Map<String, dynamic> manifestDescriptor = manifest;
+
String assetBasePath = path.dirname(path.absolute(manifestPath));
File snapshotFile;
diff --git a/packages/flutter_tools/lib/src/ios/setup_xcodeproj.dart b/packages/flutter_tools/lib/src/ios/setup_xcodeproj.dart
index 5de3a94..0777f82 100644
--- a/packages/flutter_tools/lib/src/ios/setup_xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/setup_xcodeproj.dart
@@ -104,8 +104,8 @@
targetPlatform: TargetPlatform.ios
);
- String xcodeProjectPath = await ArtifactStore.getPath(xcodeProject);
- List<int> archiveBytes = await new File(xcodeProjectPath).readAsBytes();
+ String xcodeProjectPath = ArtifactStore.getPath(xcodeProject);
+ List<int> archiveBytes = new File(xcodeProjectPath).readAsBytesSync();
if (archiveBytes.isEmpty) {
printError('Error: No archive bytes received.');
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 1b89bc4..151f808 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -459,9 +459,9 @@
ServiceProtocolDiscovery serviceProtocolDiscovery =
new ServiceProtocolDiscovery(logReader);
- // We take this future here but do not wait for completion until *after*
- // we start the application.
- Future<int> serviceProtocolPort = serviceProtocolDiscovery.nextPort();
+ // We take this future here but do not wait for completion until *after* we
+ // start the application.
+ Future<int> scrapeServicePort = serviceProtocolDiscovery.nextPort();
// Prepare launch arguments.
List<String> args = <String>[
@@ -487,14 +487,23 @@
return false;
}
- // Wait for the service protocol port here. This will complete once
- // the device has printed "Observatory is listening on..."
- int devicePort = await serviceProtocolPort;
- printTrace('service protocol port = $devicePort');
- printTrace('Successfully started ${app.name} on $id.');
- printStatus('Observatory listening on http://127.0.0.1:$devicePort');
+ // Wait for the service protocol port here. This will complete once the
+ // device has printed "Observatory is listening on..."
+ printTrace('Waiting for observatory port to be available...');
- return true;
+ try {
+ int devicePort = await scrapeServicePort.timeout(new Duration(seconds: 12));
+ printTrace('service protocol port = $devicePort');
+ printStatus('Observatory listening on http://127.0.0.1:$devicePort');
+ return true;
+ } catch (error) {
+ if (error is TimeoutException)
+ printError('Timed out while waiting for a debug connection.');
+ else
+ printError('Error waiting for a debug connection: $error');
+
+ return false;
+ }
}
bool _applicationIsInstalledAndRunning(ApplicationPackage app) {
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
index 6a3b797..12677af0 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
@@ -167,8 +167,9 @@
@override
Future<dynamic> run(Iterable<String> args) {
return super.run(args).then((dynamic result) {
- logger.flush();
return result;
+ }).whenComplete(() {
+ logger.flush();
});
}
diff --git a/packages/flutter_tools/lib/src/toolchain.dart b/packages/flutter_tools/lib/src/toolchain.dart
index 7684a49..9f4a9a5 100644
--- a/packages/flutter_tools/lib/src/toolchain.dart
+++ b/packages/flutter_tools/lib/src/toolchain.dart
@@ -36,7 +36,7 @@
}
}
-Future<String> _getCompilerPath(BuildConfiguration config) async {
+String _getCompilerPath(BuildConfiguration config) {
if (config.type != BuildType.prebuilt) {
String compilerPath = path.join(config.buildDir, 'clang_x64', 'sky_snapshot');
if (FileSystemEntity.isFileSync(compilerPath))
@@ -48,7 +48,7 @@
}
Artifact artifact = ArtifactStore.getArtifact(
type: ArtifactType.snapshot, hostPlatform: config.hostPlatform);
- return await ArtifactStore.getPath(artifact);
+ return ArtifactStore.getPath(artifact);
}
class Toolchain {
@@ -58,7 +58,7 @@
static Future<Toolchain> forConfigs(List<BuildConfiguration> configs) async {
for (BuildConfiguration config in configs) {
- String compilerPath = await _getCompilerPath(config);
+ String compilerPath = _getCompilerPath(config);
if (compilerPath != null)
return new Toolchain(compiler: new SnapshotCompiler(compilerPath));
}
diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml
index da787ce..ec753bd 100644
--- a/packages/flutter_tools/pubspec.yaml
+++ b/packages/flutter_tools/pubspec.yaml
@@ -14,6 +14,7 @@
crypto: ^0.9.2
den_api: ^0.1.0
file: ^0.1.0
+ json_schema: ^1.0.3
mustache4dart: ^1.0.0
path: ^1.3.0
pub_semver: ^1.0.0
diff --git a/packages/flutter_tools/schema/flutter_yaml.json b/packages/flutter_tools/schema/flutter_yaml.json
new file mode 100644
index 0000000..2342990
--- /dev/null
+++ b/packages/flutter_tools/schema/flutter_yaml.json
@@ -0,0 +1,36 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "flutter.yaml",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "name": { "type": "string" },
+ "uses-material-design": { "type": "boolean" },
+ "assets": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "fonts": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "family": { "type": "string" },
+ "fonts": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "asset": { "type": "string" },
+ "weight": { "type": "integer" },
+ "style": { "enum": [ "normal", "italic" ] }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}