{"action":"created","comment":{"url":"https://api.github.com/repos/flutter/flutter/pulls/comments/175961812","pull_request_review_id":105568130,"id":175961812,"diff_hunk":"@@ -103,81 +111,658 @@ class Chip extends StatelessWidget {\n \n   /// Color for delete icon, the default being black.\n   ///\n-  /// This has no effect when [onDelete] is null since no delete icon will be\n-  /// shown.\n+  /// This has no effect when [onDeleted] or [deleteIcon] are null since no\n+  /// delete icon will be shown.\n+  ///\n+  /// If [deleteIcon] is set to something other than its default, then this\n+  /// will have no effect, since the color specified in the [deleteIcon] widget\n+  /// will take precedence.\n   final Color deleteIconColor;\n \n   /// Message to be used for the chip delete button's tooltip.\n   ///\n-  /// This has no effect when [onDelete] is null since no delete icon will be\n-  /// shown.\n+  /// This has no effect when [onDeleted] or [deleteIcon] are null since no\n+  /// delete icon will be shown.\n   final String deleteButtonTooltipMessage;\n \n   @override\n   Widget build(BuildContext context) {\n     assert(debugCheckHasMaterial(context));\n-    final bool deletable = onDeleted != null;\n-    double startPadding = 12.0;\n-    double endPadding = 12.0;\n+    final ThemeData theme = Theme.of(context);\n+    return new DefaultTextStyle(\n+      overflow: TextOverflow.fade,\n+      textAlign: TextAlign.start,\n+      maxLines: 1,\n+      softWrap: false,\n+      style: labelStyle ??\n+          theme.textTheme.body2.copyWith(\n+            color: theme.primaryColorDark.withAlpha(_textLabelAlpha),\n+          ),\n+      child: new _ChipRenderWidget(\n+        theme: new _ChipRenderTheme(\n+          label: label,\n+          avatar: avatar,\n+          deleteIcon: onDeleted != null ? new Tooltip(\n+            message: deleteButtonTooltipMessage ??\n+                MaterialLocalizations.of(context).deleteButtonTooltip,\n+            child: deleteIcon ??\n+                new Icon(\n+                  Icons.cancel,\n+                  size: _deleteIconSize,\n+                  color: deleteIconColor ?? theme.primaryColorDark.withAlpha(_deleteIconAlpha),\n+                ),\n+          ) : null,\n+          container: new Container(\n+            decoration: new ShapeDecoration(\n+              shape: border,\n+              color: backgroundColor ?? theme.primaryColorDark.withAlpha(_containerAlpha),\n+            ),\n+          ),\n+          padding: const EdgeInsetsDirectional.fromSTEB(\n+            _edgePadding,\n+            _edgePadding,\n+            _edgePadding,\n+            _edgePadding,\n+          ),\n+          labelPadding: const EdgeInsetsDirectional.only(start: _edgePadding, end: _edgePadding),\n+        ),\n+        key: key,\n+        onDeleted: Feedback.wrapForTap(onDeleted, context),\n+      ),\n+    );\n+  }\n+}\n+\n+class _ChipRenderWidget extends RenderObjectWidget {\n+  const _ChipRenderWidget({\n+    Key key,\n+    @required this.theme,\n+    this.onDeleted,\n+  })  : assert(theme != null),\n+        super(key: key);\n+\n+  final _ChipRenderTheme theme;\n+  final VoidCallback onDeleted;\n+\n+  @override\n+  _RenderChipElement createElement() => new _RenderChipElement(this);\n+\n+  @override\n+  void updateRenderObject(BuildContext context, _RenderChip renderObject) {\n+    renderObject\n+      ..theme = theme\n+      ..textDirection = Directionality.of(context);\n+  }\n+\n+  @override\n+  RenderObject createRenderObject(BuildContext context) {\n+    return new _RenderChip(\n+      theme: theme,\n+      textDirection: Directionality.of(context),\n+      onDeleted: onDeleted,\n+    );\n+  }\n+}\n+\n+enum _ChipSlot {\n+  label,\n+  avatar,\n+  deleteIcon,\n+  container,\n+}\n+\n+class _RenderChipElement extends RenderObjectElement {\n+  _RenderChipElement(_ChipRenderWidget chip) : super(chip);\n+\n+  final Map<_ChipSlot, Element> slotToChild = <_ChipSlot, Element>{};\n+  final Map<Element, _ChipSlot> childToSlot = <Element, _ChipSlot>{};\n+\n+  @override\n+  _ChipRenderWidget get widget => super.widget;\n+\n+  @override\n+  _RenderChip get renderObject => super.renderObject;\n+\n+  @override\n+  void visitChildren(ElementVisitor visitor) {\n+    slotToChild.values.forEach(visitor);\n+  }\n+\n+  @override\n+  void forgetChild(Element child) {\n+    assert(slotToChild.values.contains(child));\n+    assert(childToSlot.keys.contains(child));\n+    final _ChipSlot slot = childToSlot[child];\n+    childToSlot.remove(child);\n+    slotToChild.remove(slot);\n+  }\n+\n+  void _mountChild(Widget widget, _ChipSlot slot) {\n+    final Element oldChild = slotToChild[slot];\n+    final Element newChild = updateChild(oldChild, widget, slot);\n+    if (oldChild != null) {\n+      slotToChild.remove(slot);\n+      childToSlot.remove(oldChild);\n+    }\n+    if (newChild != null) {\n+      slotToChild[slot] = newChild;\n+      childToSlot[newChild] = slot;\n+    }\n+  }\n+\n+  @override\n+  void mount(Element parent, dynamic newSlot) {\n+    super.mount(parent, newSlot);\n+    _mountChild(widget.theme.avatar, _ChipSlot.avatar);\n+    _mountChild(widget.theme.deleteIcon, _ChipSlot.deleteIcon);\n+    _mountChild(widget.theme.label, _ChipSlot.label);\n+    _mountChild(widget.theme.container, _ChipSlot.container);\n+  }\n+\n+  void _updateChild(Widget widget, _ChipSlot slot) {\n+    final Element oldChild = slotToChild[slot];\n+    final Element newChild = updateChild(oldChild, widget, slot);\n+    if (oldChild != null) {\n+      childToSlot.remove(oldChild);\n+      slotToChild.remove(slot);\n+    }\n+    if (newChild != null) {\n+      slotToChild[slot] = newChild;\n+      childToSlot[newChild] = slot;\n+    }\n+  }\n+\n+  @override\n+  void update(_ChipRenderWidget newWidget) {\n+    super.update(newWidget);\n+    assert(widget == newWidget);\n+    _updateChild(widget.theme.label, _ChipSlot.label);\n+    _updateChild(widget.theme.avatar, _ChipSlot.avatar);\n+    _updateChild(widget.theme.deleteIcon, _ChipSlot.deleteIcon);\n+    _updateChild(widget.theme.container, _ChipSlot.container);\n+  }\n+\n+  void _updateRenderObject(RenderObject child, _ChipSlot slot) {\n+    switch (slot) {\n+      case _ChipSlot.avatar:\n+        renderObject.avatar = child;\n+        break;\n+      case _ChipSlot.label:\n+        renderObject.label = child;\n+        break;\n+      case _ChipSlot.deleteIcon:\n+        renderObject.deleteIcon = child;\n+        break;\n+      case _ChipSlot.container:\n+        renderObject.container = child;\n+        break;\n+    }\n+  }\n+\n+  @override\n+  void insertChildRenderObject(RenderObject child, dynamic slotValue) {\n+    assert(child is RenderBox);\n+    assert(slotValue is _ChipSlot);\n+    final _ChipSlot slot = slotValue;\n+    _updateRenderObject(child, slot);\n+    assert(renderObject.childToSlot.keys.contains(child));\n+    assert(renderObject.slotToChild.keys.contains(slot));\n+  }\n+\n+  @override\n+  void removeChildRenderObject(RenderObject child) {\n+    assert(child is RenderBox);\n+    assert(renderObject.childToSlot.keys.contains(child));\n+    _updateRenderObject(null, renderObject.childToSlot[child]);\n+    assert(!renderObject.childToSlot.keys.contains(child));\n+    assert(!renderObject.slotToChild.keys.contains(slot));\n+  }\n+\n+  @override\n+  void moveChildRenderObject(RenderObject child, dynamic slotValue) {\n+    assert(false, 'not reachable');\n+  }\n+}\n+\n+class _ChipRenderTheme {\n+  const _ChipRenderTheme({\n+    @required this.avatar,\n+    @required this.label,\n+    @required this.deleteIcon,\n+    @required this.container,\n+    @required this.padding,\n+    @required this.labelPadding,\n+  });\n+\n+  final Widget avatar;\n+  final Widget label;\n+  final Widget deleteIcon;\n+  final Widget container;\n+  final EdgeInsetsDirectional padding;\n+  final EdgeInsetsDirectional labelPadding;\n+\n+  @override\n+  bool operator ==(dynamic other) {\n+    if (identical(this, other)) {\n+      return true;\n+    }\n+    if (other.runtimeType != runtimeType) {\n+      return false;\n+    }\n+    final _ChipRenderTheme typedOther = other;\n+    return typedOther.avatar == avatar &&\n+        typedOther.label == label &&\n+        typedOther.deleteIcon == deleteIcon &&\n+        typedOther.container == container &&\n+        typedOther.padding == padding &&\n+        typedOther.labelPadding == labelPadding;\n+  }\n+\n+  @override\n+  int get hashCode {\n+    return hashValues(\n+      avatar,\n+      label,\n+      deleteIcon,\n+      container,\n+      padding,\n+      labelPadding,\n+    );\n+  }\n+}\n+\n+class _RenderChip extends RenderBox {\n+  _RenderChip({\n+    @required _ChipRenderTheme theme,\n+    @required TextDirection textDirection,\n+    this.onDeleted,\n+  })  : assert(theme != null),\n+        assert(textDirection != null),\n+        _theme = theme,\n+        _textDirection = textDirection {\n+    _tap = new TapGestureRecognizer(debugOwner: this)\n+      ..onTapDown = _handleTapDown\n+      ..onTap = _handleTap;\n+  }\n+\n+  // Set this to true to have outlines of the tap targets drawn over\n+  // the chip.  This should never be checked in while set to 'true'.\n+  static const bool _debugShowTapTargetOutlines = false;\n+  static const EdgeInsetsDirectional _iconPadding = const EdgeInsetsDirectional.fromSTEB(\n+    _edgePadding,\n+    _edgePadding,\n+    _edgePadding,\n+    _edgePadding,\n+  );\n+\n+  final Map<_ChipSlot, RenderBox> slotToChild = <_ChipSlot, RenderBox>{};\n+  final Map<RenderBox, _ChipSlot> childToSlot = <RenderBox, _ChipSlot>{};\n+\n+  TapGestureRecognizer _tap;\n+\n+  VoidCallback onDeleted;\n+  Rect _deleteButtonRegion;\n+  Rect _actionRegion;\n+  Offset _tapDownLocation;\n+\n+  RenderBox _updateChild(RenderBox oldChild, RenderBox newChild, _ChipSlot slot) {\n+    if (oldChild != null) {\n+      dropChild(oldChild);\n+      childToSlot.remove(oldChild);\n+      slotToChild.remove(slot);\n+    }\n+    if (newChild != null) {\n+      childToSlot[newChild] = slot;\n+      slotToChild[slot] = newChild;\n+      adoptChild(newChild);\n+    }\n+    return newChild;\n+  }\n+\n+  RenderBox _avatar;\n+  RenderBox get avatar => _avatar;\n+  set avatar(RenderBox value) {\n+    _avatar = _updateChild(_avatar, value, _ChipSlot.avatar);\n+  }\n+\n+  RenderBox _deleteIcon;\n+  RenderBox get deleteIcon => _deleteIcon;\n+  set deleteIcon(RenderBox value) {\n+    _deleteIcon = _updateChild(_deleteIcon, value, _ChipSlot.deleteIcon);\n+  }\n \n-    final List<Widget> children = <Widget>[];\n+  RenderBox _label;\n+  RenderBox get label => _label;\n+  set label(RenderBox value) {\n+    _label = _updateChild(_label, value, _ChipSlot.label);\n+  }\n \n+  RenderBox _container;\n+  RenderBox get container => _container;\n+  set container(RenderBox value) {\n+    _container = _updateChild(_container, value, _ChipSlot.container);\n+  }\n+\n+  _ChipRenderTheme get theme => _theme;\n+  _ChipRenderTheme _theme;\n+  set theme(_ChipRenderTheme value) {\n+    if (_theme == value) {\n+      return;\n+    }\n+    _theme = value;\n+    markNeedsLayout();\n+  }\n+\n+  TextDirection get textDirection => _textDirection;\n+  TextDirection _textDirection;\n+  set textDirection(TextDirection value) {\n+    if (_textDirection == value) {\n+      return;\n+    }\n+    _textDirection = value;\n+    markNeedsLayout();\n+  }\n+\n+  // The returned list is ordered for hit testing.\n+  Iterable<RenderBox> get _children sync* {\n     if (avatar != null) {\n-      startPadding = 0.0;\n-      children.add(new ExcludeSemantics(\n-        child: new Container(\n-          margin: const EdgeInsetsDirectional.only(end: 8.0),\n-          width: _chipHeight,\n-          height: _chipHeight,\n-          child: avatar,\n-        ),\n-      ));\n+      yield avatar;\n     }\n+    if (label != null) {\n+      yield label;\n+    }\n+    if (deleteIcon != null) {\n+      yield deleteIcon;\n+    }\n+    if (container != null) {\n+      yield container;\n+    }\n+  }\n \n-    children.add(new Flexible(\n-      child: new DefaultTextStyle(\n-        overflow: TextOverflow.ellipsis,\n-        style: labelStyle,\n-        child: label,\n-      ),\n-    ));\n-\n-    if (deletable) {\n-      endPadding = 0.0;\n-      children.add(new GestureDetector(\n-        onTap: Feedback.wrapForTap(onDeleted, context),\n-        child: new Tooltip(\n-          message: deleteButtonTooltipMessage ?? MaterialLocalizations.of(context).deleteButtonTooltip,\n-          child: new Container(\n-            padding: const EdgeInsets.symmetric(horizontal: 4.0),\n-            child: new Icon(\n-              Icons.cancel,\n-              size: 24.0,\n-              color: deleteIconColor ?? Colors.black54,\n-            ),\n-          ),\n-        ),\n-      ));\n+  @override\n+  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {\n+    assert(debugHandleEvent(event, entry));\n+    if (event is PointerDownEvent && deleteIcon != null) {\n+      _tap.addPointer(event);\n     }\n+  }\n \n-    return new Semantics(\n-      container: true,\n-      child: new Container(\n-        constraints: const BoxConstraints(minHeight: _chipHeight),\n-        padding: new EdgeInsetsDirectional.only(start: startPadding, end: endPadding),\n-        decoration: new ShapeDecoration(\n-          color: backgroundColor ?? Colors.grey.shade300,\n-          shape: border,\n-        ),\n-        child: new Center(\n-          widthFactor: 1.0,\n-          heightFactor: 1.0,\n-          child: new Row(\n-            children: children,\n-            mainAxisSize: MainAxisSize.min,\n-          ),\n-        ),\n-      ),\n+  void _handleTapDown(TapDownDetails details) {\n+    if (deleteIcon != null) {\n+      _tapDownLocation = globalToLocal(details.globalPosition);\n+    }\n+  }\n+\n+  void _handleTap() {\n+    if (_tapDownLocation == null) {\n+      return;\n+    }\n+    if (deleteIcon != null && _deleteButtonRegion.contains(_tapDownLocation)) {\n+      onDeleted();\n+    }\n+  }\n+\n+  @override\n+  void attach(PipelineOwner owner) {\n+    super.attach(owner);\n+    for (RenderBox child in _children) {\n+      child.attach(owner);\n+    }\n+  }\n+\n+  @override\n+  void detach() {\n+    super.detach();\n+    for (RenderBox child in _children) {\n+      child.detach();\n+    }\n+  }\n+\n+  @override\n+  void redepthChildren() {\n+    _children.forEach(redepthChild);\n+  }\n+\n+  @override\n+  void visitChildren(RenderObjectVisitor visitor) {\n+    _children.forEach(visitor);\n+  }\n+\n+  @override\n+  List<DiagnosticsNode> debugDescribeChildren() {\n+    final List<DiagnosticsNode> value = <DiagnosticsNode>[];\n+    void add(RenderBox child, String name) {\n+      if (child != null) {\n+        value.add(child.toDiagnosticsNode(name: name));\n+      }\n+    }\n+\n+    add(avatar, 'avatar');\n+    add(label, 'label');\n+    add(deleteIcon, 'deleteIcon');\n+    add(container, 'container');\n+    return value;\n+  }\n+\n+  @override\n+  bool get sizedByParent => false;\n+\n+  static double _minWidth(RenderBox box, double height) {\n+    return box == null ? 0.0 : box.getMinIntrinsicWidth(height);\n+  }\n+\n+  static double _maxWidth(RenderBox box, double height) {\n+    return box == null ? 0.0 : box.getMaxIntrinsicWidth(height);\n+  }\n+\n+  static double _minHeight(RenderBox box, double width) {\n+    return box == null ? 0.0 : box.getMinIntrinsicWidth(width);\n+  }\n+\n+  static Size _boxSize(RenderBox box) => box == null ? Size.zero : box.size;\n+\n+  static BoxParentData _boxParentData(RenderBox box) => box.parentData;\n+\n+  @override\n+  double computeMinIntrinsicWidth(double height) {\n+    // The overall padding isn't affected by missing avatar or delete icon\n+    // because we add the padding regardless to give extra padding for the label\n+    // when they're missing.\n+    final double overallPadding = theme.labelPadding.horizontal + _iconPadding.horizontal * 2.0;\n+    return overallPadding + _minWidth(avatar, height) + _minWidth(label, height)\n+        + _minWidth(deleteIcon, height);\n+  }\n+\n+  @override\n+  double computeMaxIntrinsicWidth(double height) {\n+    // The overall padding isn't affected by missing avatar or delete icon\n+    // because we add the padding regardless to give extra padding for the label\n+    // when they're missing.\n+    final double overallPadding = theme.labelPadding.horizontal + _iconPadding.horizontal * 2.0;\n+    return overallPadding + _maxWidth(avatar, height) + _maxWidth(label, height)\n+        + _maxWidth(deleteIcon, height);\n+  }\n+\n+  @override\n+  double computeMinIntrinsicHeight(double width) {\n+    // This widget is sized to the height of the label only.  The other\n+    // widgets are sized to match.\n+    return theme.labelPadding.vertical + _minHeight(label, width);\n+  }\n+\n+  @override\n+  double computeMaxIntrinsicHeight(double width) => computeMinIntrinsicHeight(width);\n+\n+  @override\n+  double computeDistanceToActualBaseline(TextBaseline baseline) {\n+    // The baseline of this widget is the baseline of the label.\n+    return label.computeDistanceToActualBaseline(baseline);\n+  }\n+\n+  @override\n+  void performLayout() {\n+    double iconHeight = _chipHeight;\n+    if (label != null) {\n+      label.layout(constraints.loosen(), parentUsesSize: true);\n+      // Now that we know the height, we can determine how much to shrink the\n+      // constraints by for the \"real\" layout. Ignored if the constraints are\n+      // infinite.\n+      iconHeight = math.max(iconHeight, _boxSize(label).height);","path":"packages/flutter/lib/src/material/chip.dart","position":661,"original_position":661,"commit_id":"7aa243a25164c79f62a0d0003c447e09f3188a1a","original_commit_id":"7aa243a25164c79f62a0d0003c447e09f3188a1a","user":{"login":"HansMuller","id":1377460,"avatar_url":"https://avatars2.githubusercontent.com/u/1377460?v=4","gravatar_id":"","url":"https://api.github.com/users/HansMuller","html_url":"https://github.com/HansMuller","followers_url":"https://api.github.com/users/HansMuller/followers","following_url":"https://api.github.com/users/HansMuller/following{/other_user}","gists_url":"https://api.github.com/users/HansMuller/gists{/gist_id}","starred_url":"https://api.github.com/users/HansMuller/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/HansMuller/subscriptions","organizations_url":"https://api.github.com/users/HansMuller/orgs","repos_url":"https://api.github.com/users/HansMuller/repos","events_url":"https://api.github.com/users/HansMuller/events{/privacy}","received_events_url":"https://api.github.com/users/HansMuller/received_events","type":"User","site_admin":false},"body":"This is confusing in light of the comment on line 588 about the widget being sized to the height of the label only. If that were true, shouldn't this be `math.min(...)`?","created_at":"2018-03-21T00:25:28Z","updated_at":"2018-03-21T00:53:19Z","html_url":"https://github.com/flutter/flutter/pull/15596#discussion_r175961812","pull_request_url":"https://api.github.com/repos/flutter/flutter/pulls/15596","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/flutter/flutter/pulls/comments/175961812"},"html":{"href":"https://github.com/flutter/flutter/pull/15596#discussion_r175961812"},"pull_request":{"href":"https://api.github.com/repos/flutter/flutter/pulls/15596"}}},"pull_request":{"url":"https://api.github.com/repos/flutter/flutter/pulls/15596","id":175409244,"html_url":"https://github.com/flutter/flutter/pull/15596","diff_url":"https://github.com/flutter/flutter/pull/15596.diff","patch_url":"https://github.com/flutter/flutter/pull/15596.patch","issue_url":"https://api.github.com/repos/flutter/flutter/issues/15596","number":15596,"state":"open","locked":false,"title":"Creating chips custom renderer, updating visual look.","user":{"login":"gspencergoog","id":8867023,"avatar_url":"https://avatars2.githubusercontent.com/u/8867023?v=4","gravatar_id":"","url":"https://api.github.com/users/gspencergoog","html_url":"https://github.com/gspencergoog","followers_url":"https://api.github.com/users/gspencergoog/followers","following_url":"https://api.github.com/users/gspencergoog/following{/other_user}","gists_url":"https://api.github.com/users/gspencergoog/gists{/gist_id}","starred_url":"https://api.github.com/users/gspencergoog/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gspencergoog/subscriptions","organizations_url":"https://api.github.com/users/gspencergoog/orgs","repos_url":"https://api.github.com/users/gspencergoog/repos","events_url":"https://api.github.com/users/gspencergoog/events{/privacy}","received_events_url":"https://api.github.com/users/gspencergoog/received_events","type":"User","site_admin":false},"body":"This updates the visual look of the Chip class, so that it scales properly in the face of text scale (and `label` widget size) changes, and bases its height off of the `label` widget's height, constraining the other widgets it contains to be the same height.\r\n\r\nTo do this properly, I had to implement a custom render object that will measure the height of the label, so the guts of this class are basically rewritten.\r\n\r\nIn addition, to allow the circle avatar to increase in size when the chip does, I added minRadius and maxRadius arguments to it, and I updated its color handling to use the light/dark primary colors of the theme in a smart way instead of just using black and white.\r\n\r\nUpdated and added tests.\r\n![screenshot_20180316-124504](https://user-images.githubusercontent.com/8867023/37541559-4bfecfce-2918-11e8-9b35-a676528b6254.png)\r\n","created_at":"2018-03-15T23:58:27Z","updated_at":"2018-03-21T00:53:19Z","closed_at":null,"merged_at":null,"merge_commit_sha":"d8472891e0da2a13200460b09ce5f60821086276","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":487496476,"url":"https://api.github.com/repos/flutter/flutter/labels/cla:%20yes","name":"cla: yes","color":"ffffff","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/flutter/flutter/pulls/15596/commits","review_comments_url":"https://api.github.com/repos/flutter/flutter/pulls/15596/comments","review_comment_url":"https://api.github.com/repos/flutter/flutter/pulls/comments{/number}","comments_url":"https://api.github.com/repos/flutter/flutter/issues/15596/comments","statuses_url":"https://api.github.com/repos/flutter/flutter/statuses/7aa243a25164c79f62a0d0003c447e09f3188a1a","head":{"label":"gspencergoog:m2_chips","ref":"m2_chips","sha":"7aa243a25164c79f62a0d0003c447e09f3188a1a","user":{"login":"gspencergoog","id":8867023,"avatar_url":"https://avatars2.githubusercontent.com/u/8867023?v=4","gravatar_id":"","url":"https://api.github.com/users/gspencergoog","html_url":"https://github.com/gspencergoog","followers_url":"https://api.github.com/users/gspencergoog/followers","following_url":"https://api.github.com/users/gspencergoog/following{/other_user}","gists_url":"https://api.github.com/users/gspencergoog/gists{/gist_id}","starred_url":"https://api.github.com/users/gspencergoog/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gspencergoog/subscriptions","organizations_url":"https://api.github.com/users/gspencergoog/orgs","repos_url":"https://api.github.com/users/gspencergoog/repos","events_url":"https://api.github.com/users/gspencergoog/events{/privacy}","received_events_url":"https://api.github.com/users/gspencergoog/received_events","type":"User","site_admin":false},"repo":{"id":92983227,"name":"flutter","full_name":"gspencergoog/flutter","owner":{"login":"gspencergoog","id":8867023,"avatar_url":"https://avatars2.githubusercontent.com/u/8867023?v=4","gravatar_id":"","url":"https://api.github.com/users/gspencergoog","html_url":"https://github.com/gspencergoog","followers_url":"https://api.github.com/users/gspencergoog/followers","following_url":"https://api.github.com/users/gspencergoog/following{/other_user}","gists_url":"https://api.github.com/users/gspencergoog/gists{/gist_id}","starred_url":"https://api.github.com/users/gspencergoog/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gspencergoog/subscriptions","organizations_url":"https://api.github.com/users/gspencergoog/orgs","repos_url":"https://api.github.com/users/gspencergoog/repos","events_url":"https://api.github.com/users/gspencergoog/events{/privacy}","received_events_url":"https://api.github.com/users/gspencergoog/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/gspencergoog/flutter","description":"Flutter makes it easy and fast to build beautiful mobile apps.","fork":true,"url":"https://api.github.com/repos/gspencergoog/flutter","forks_url":"https://api.github.com/repos/gspencergoog/flutter/forks","keys_url":"https://api.github.com/repos/gspencergoog/flutter/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gspencergoog/flutter/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gspencergoog/flutter/teams","hooks_url":"https://api.github.com/repos/gspencergoog/flutter/hooks","issue_events_url":"https://api.github.com/repos/gspencergoog/flutter/issues/events{/number}","events_url":"https://api.github.com/repos/gspencergoog/flutter/events","assignees_url":"https://api.github.com/repos/gspencergoog/flutter/assignees{/user}","branches_url":"https://api.github.com/repos/gspencergoog/flutter/branches{/branch}","tags_url":"https://api.github.com/repos/gspencergoog/flutter/tags","blobs_url":"https://api.github.com/repos/gspencergoog/flutter/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gspencergoog/flutter/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gspencergoog/flutter/git/refs{/sha}","trees_url":"https://api.github.com/repos/gspencergoog/flutter/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gspencergoog/flutter/statuses/{sha}","languages_url":"https://api.github.com/repos/gspencergoog/flutter/languages","stargazers_url":"https://api.github.com/repos/gspencergoog/flutter/stargazers","contributors_url":"https://api.github.com/repos/gspencergoog/flutter/contributors","subscribers_url":"https://api.github.com/repos/gspencergoog/flutter/subscribers","subscription_url":"https://api.github.com/repos/gspencergoog/flutter/subscription","commits_url":"https://api.github.com/repos/gspencergoog/flutter/commits{/sha}","git_commits_url":"https://api.github.com/repos/gspencergoog/flutter/git/commits{/sha}","comments_url":"https://api.github.com/repos/gspencergoog/flutter/comments{/number}","issue_comment_url":"https://api.github.com/repos/gspencergoog/flutter/issues/comments{/number}","contents_url":"https://api.github.com/repos/gspencergoog/flutter/contents/{+path}","compare_url":"https://api.github.com/repos/gspencergoog/flutter/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gspencergoog/flutter/merges","archive_url":"https://api.github.com/repos/gspencergoog/flutter/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gspencergoog/flutter/downloads","issues_url":"https://api.github.com/repos/gspencergoog/flutter/issues{/number}","pulls_url":"https://api.github.com/repos/gspencergoog/flutter/pulls{/number}","milestones_url":"https://api.github.com/repos/gspencergoog/flutter/milestones{/number}","notifications_url":"https://api.github.com/repos/gspencergoog/flutter/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gspencergoog/flutter/labels{/name}","releases_url":"https://api.github.com/repos/gspencergoog/flutter/releases{/id}","deployments_url":"https://api.github.com/repos/gspencergoog/flutter/deployments","created_at":"2017-05-31T19:52:38Z","updated_at":"2018-03-20T21:07:45Z","pushed_at":"2018-03-21T00:21:01Z","git_url":"git://github.com/gspencergoog/flutter.git","ssh_url":"git@github.com:gspencergoog/flutter.git","clone_url":"https://github.com/gspencergoog/flutter.git","svn_url":"https://github.com/gspencergoog/flutter","homepage":"https://flutter.io","size":36145,"stargazers_count":0,"watchers_count":0,"language":"Dart","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"flutter:master","ref":"master","sha":"7edd5c8d6c2bfec885c387b8a8adea0417f7f665","user":{"login":"flutter","id":14101776,"avatar_url":"https://avatars3.githubusercontent.com/u/14101776?v=4","gravatar_id":"","url":"https://api.github.com/users/flutter","html_url":"https://github.com/flutter","followers_url":"https://api.github.com/users/flutter/followers","following_url":"https://api.github.com/users/flutter/following{/other_user}","gists_url":"https://api.github.com/users/flutter/gists{/gist_id}","starred_url":"https://api.github.com/users/flutter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/flutter/subscriptions","organizations_url":"https://api.github.com/users/flutter/orgs","repos_url":"https://api.github.com/users/flutter/repos","events_url":"https://api.github.com/users/flutter/events{/privacy}","received_events_url":"https://api.github.com/users/flutter/received_events","type":"Organization","site_admin":false},"repo":{"id":31792824,"name":"flutter","full_name":"flutter/flutter","owner":{"login":"flutter","id":14101776,"avatar_url":"https://avatars3.githubusercontent.com/u/14101776?v=4","gravatar_id":"","url":"https://api.github.com/users/flutter","html_url":"https://github.com/flutter","followers_url":"https://api.github.com/users/flutter/followers","following_url":"https://api.github.com/users/flutter/following{/other_user}","gists_url":"https://api.github.com/users/flutter/gists{/gist_id}","starred_url":"https://api.github.com/users/flutter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/flutter/subscriptions","organizations_url":"https://api.github.com/users/flutter/orgs","repos_url":"https://api.github.com/users/flutter/repos","events_url":"https://api.github.com/users/flutter/events{/privacy}","received_events_url":"https://api.github.com/users/flutter/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/flutter/flutter","description":"Flutter makes it easy and fast to build beautiful mobile apps.","fork":false,"url":"https://api.github.com/repos/flutter/flutter","forks_url":"https://api.github.com/repos/flutter/flutter/forks","keys_url":"https://api.github.com/repos/flutter/flutter/keys{/key_id}","collaborators_url":"https://api.github.com/repos/flutter/flutter/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/flutter/flutter/teams","hooks_url":"https://api.github.com/repos/flutter/flutter/hooks","issue_events_url":"https://api.github.com/repos/flutter/flutter/issues/events{/number}","events_url":"https://api.github.com/repos/flutter/flutter/events","assignees_url":"https://api.github.com/repos/flutter/flutter/assignees{/user}","branches_url":"https://api.github.com/repos/flutter/flutter/branches{/branch}","tags_url":"https://api.github.com/repos/flutter/flutter/tags","blobs_url":"https://api.github.com/repos/flutter/flutter/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/flutter/flutter/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/flutter/flutter/git/refs{/sha}","trees_url":"https://api.github.com/repos/flutter/flutter/git/trees{/sha}","statuses_url":"https://api.github.com/repos/flutter/flutter/statuses/{sha}","languages_url":"https://api.github.com/repos/flutter/flutter/languages","stargazers_url":"https://api.github.com/repos/flutter/flutter/stargazers","contributors_url":"https://api.github.com/repos/flutter/flutter/contributors","subscribers_url":"https://api.github.com/repos/flutter/flutter/subscribers","subscription_url":"https://api.github.com/repos/flutter/flutter/subscription","commits_url":"https://api.github.com/repos/flutter/flutter/commits{/sha}","git_commits_url":"https://api.github.com/repos/flutter/flutter/git/commits{/sha}","comments_url":"https://api.github.com/repos/flutter/flutter/comments{/number}","issue_comment_url":"https://api.github.com/repos/flutter/flutter/issues/comments{/number}","contents_url":"https://api.github.com/repos/flutter/flutter/contents/{+path}","compare_url":"https://api.github.com/repos/flutter/flutter/compare/{base}...{head}","merges_url":"https://api.github.com/repos/flutter/flutter/merges","archive_url":"https://api.github.com/repos/flutter/flutter/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/flutter/flutter/downloads","issues_url":"https://api.github.com/repos/flutter/flutter/issues{/number}","pulls_url":"https://api.github.com/repos/flutter/flutter/pulls{/number}","milestones_url":"https://api.github.com/repos/flutter/flutter/milestones{/number}","notifications_url":"https://api.github.com/repos/flutter/flutter/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/flutter/flutter/labels{/name}","releases_url":"https://api.github.com/repos/flutter/flutter/releases{/id}","deployments_url":"https://api.github.com/repos/flutter/flutter/deployments","created_at":"2015-03-06T22:54:58Z","updated_at":"2018-03-20T23:44:27Z","pushed_at":"2018-03-21T00:40:29Z","git_url":"git://github.com/flutter/flutter.git","ssh_url":"git@github.com:flutter/flutter.git","clone_url":"https://github.com/flutter/flutter.git","svn_url":"https://github.com/flutter/flutter","homepage":"https://flutter.io","size":36090,"stargazers_count":19454,"watchers_count":19454,"language":"Dart","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1560,"mirror_url":null,"archived":false,"open_issues_count":2613,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":1560,"open_issues":2613,"watchers":19454,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/flutter/flutter/pulls/15596"},"html":{"href":"https://github.com/flutter/flutter/pull/15596"},"issue":{"href":"https://api.github.com/repos/flutter/flutter/issues/15596"},"comments":{"href":"https://api.github.com/repos/flutter/flutter/issues/15596/comments"},"review_comments":{"href":"https://api.github.com/repos/flutter/flutter/pulls/15596/comments"},"review_comment":{"href":"https://api.github.com/repos/flutter/flutter/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/flutter/flutter/pulls/15596/commits"},"statuses":{"href":"https://api.github.com/repos/flutter/flutter/statuses/7aa243a25164c79f62a0d0003c447e09f3188a1a"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"HansMuller"}}