{"action":"created","comment":{"url":"https://api.github.com/repos/flutter/flutter/pulls/comments/158349150","pull_request_review_id":85147832,"id":158349150,"diff_hunk":"@@ -0,0 +1,551 @@\n+// Copyright 2017 The Chromium Authors. All rights reserved.\n+// Use of this source code is governed by a BSD-style license that can be\n+// found in the LICENSE file.\n+\n+import 'dart:io';\n+import 'dart:math';\n+\n+import 'package:collection/collection.dart';\n+import 'package:meta/meta.dart';\n+import 'package:vector_math/vector_math_64.dart';\n+import 'package:xml/xml.dart' as xml show parse;\n+import 'package:xml/xml.dart' hide parse;\n+\n+// String to use for a single indentation.\n+const String kIndent = '  ';\n+\n+/// Represents an animation, and provides logic to generate dart code for it.\n+class Animation {\n+  const Animation(this.size, this.paths);\n+\n+  factory Animation.fromFrameData(List<FrameData> frames) {\n+    _validateFramesData(frames);\n+    final Point<double> size = frames[0].size;\n+    final List<PathAnimation> paths = <PathAnimation>[];\n+    for (int i = 0; i < frames[0].paths.length; i += 1) {\n+      paths.add(new PathAnimation.fromFrameData(frames, i));\n+    }\n+    return new Animation(size, paths);\n+  }\n+\n+  /// The size of the animation (width, height) in pixels.\n+  final Point<double> size;\n+\n+  /// List of paths in the animation.\n+  final List<PathAnimation> paths;\n+\n+  static void _validateFramesData(List<FrameData> frames) {\n+    final Point<double> size = frames[0].size;\n+    final int numPaths = frames[0].paths.length;\n+    for (int i = 0; i < frames.length; i += 1) {\n+      final FrameData frame = frames[i];\n+      if (size != frame.size)\n+        throw new Exception(\n+            'All animation frames must have the same size,\\n'\n+            'first frame size was: (${size.x}, ${size.y})\\n'\n+            'frame $i size was: (${frame.size.x}, ${frame.size.y})'\n+        );\n+      if (numPaths != frame.paths.length)\n+        throw new Exception(\n+            'All animation frames must have the same number of paths,\\n'\n+            'first frame has $numPaths paths\\n'\n+            'frame $i has ${frame.paths.length} paths'\n+        );\n+    }\n+  }\n+\n+  String toDart(String className, String varName) {\n+    final StringBuffer sb = new StringBuffer();\n+    sb.write('const $className $varName = const $className(\\n');\n+    sb.write('${kIndent}const Size(${size.x}, ${size.y}),\\n');\n+    sb.write('${kIndent}const <_PathFrames>[\\n');\n+    for (PathAnimation path in paths)\n+      sb.write(path.toDart());\n+    sb.write('$kIndent],\\n');\n+    sb.write(');');\n+    return sb.toString();\n+  }\n+}\n+\n+/// Represents the animation of a single path.\n+class PathAnimation {\n+  const PathAnimation(this.commands, {@required this.opacities});\n+\n+  factory PathAnimation.fromFrameData(List<FrameData> frames, int pathIdx) {\n+    if (frames.isEmpty)\n+      return const PathAnimation(const <PathCommandAnimation>[], opacities: const <double>[]);\n+\n+    final List<PathCommandAnimation> commands = <PathCommandAnimation>[];\n+    for (int commandIdx = 0; commandIdx < frames[0].paths[pathIdx].commands.length; commandIdx += 1) {\n+      final int numPointsInCommand = frames[0].paths[pathIdx].commands[commandIdx].points.length;\n+      final List<List<Point<double>>> points = new List<List<Point<double>>>(numPointsInCommand);\n+      for (int j = 0; j < numPointsInCommand; j += 1)\n+        points[j] = <Point<double>>[];\n+      final String commandType = frames[0].paths[pathIdx].commands[commandIdx].type;\n+      for (int i = 0; i < frames.length; i += 1) {\n+        final FrameData frame = frames[i];\n+        final String currentCommandType = frame.paths[pathIdx].commands[commandIdx].type;\n+        if (commandType != currentCommandType)\n+          throw new Exception(\n+              'Paths must be built from the same commands in all frames'\n+              'command $commandIdx at frame 0 was of type \\'$commandType\\''\n+              'command $commandIdx at frame $i was of type \\'$currentCommandType\\''\n+          );\n+        for (int j = 0; j < numPointsInCommand; j += 1)\n+          points[j].add(frame.paths[pathIdx].commands[commandIdx].points[j]);\n+      }\n+      commands.add(new PathCommandAnimation(commandType, points));\n+    }\n+\n+    final List<double> opacities =\n+      frames.map<double>((FrameData d) => d.paths[pathIdx].opacity).toList();\n+\n+    return new PathAnimation(commands, opacities: opacities);\n+  }\n+\n+  /// List of commands for drawing the path.\n+  final List<PathCommandAnimation> commands;\n+  /// The path opacity for each animation frame.\n+  final List<double> opacities;\n+\n+  @override\n+  String toString() {\n+    return 'PathAnimation(commands: $commands, opacities: $opacities)';\n+  }\n+\n+  String toDart() {\n+    final StringBuffer sb = new StringBuffer();\n+    sb.write('${kIndent * 2}const _PathFrames(\\n');\n+    sb.write('${kIndent * 3}opacities: const <double>[\\n');\n+    for (double opacity in opacities)\n+      sb.write('${kIndent * 4}$opacity,\\n');\n+    sb.write('${kIndent * 3}],\\n');\n+    sb.write('${kIndent * 3}commands: const <_PathCommand>[\\n');\n+    for (PathCommandAnimation command in commands)\n+      sb.write(command.toDart());\n+    sb.write('${kIndent * 3}],\\n');\n+    sb.write('${kIndent * 2}),\\n');\n+    return sb.toString();\n+  }\n+}\n+\n+/// Represents the animation of a single path command.\n+class PathCommandAnimation {\n+  const PathCommandAnimation(this.type, this.points);\n+\n+  /// The command type.\n+  final String type;\n+\n+  /// A matrix with the command's points in different frames.\n+  ///\n+  /// points[i][j] is the i-th point of the command at frame j.\n+  final List<List<Point<double>>> points;\n+\n+  @override\n+  String toString() {\n+    return 'PathCommandAnimation(type: $type, points: $points)';\n+  }\n+\n+  String toDart() {\n+    String dartCommandClass;\n+    switch (type) {\n+      case 'M':\n+        dartCommandClass = '_PathMoveTo';\n+        break;\n+      case 'C':\n+        dartCommandClass = '_PathCubicTo';\n+        break;\n+      case 'L':\n+        dartCommandClass = '_PathLineTo';\n+        break;\n+      case 'Z':\n+        dartCommandClass = '_PathClose';\n+        break;\n+      default:\n+        throw new Exception('unsupported path command: $type');\n+    }\n+    final StringBuffer sb = new StringBuffer();\n+    sb.write('${kIndent * 4}const $dartCommandClass(\\n');\n+    for (List<Point<double>> pointFrames in points) {\n+      sb.write('${kIndent * 5}const <Offset>[\\n');\n+      for (Point<double> point in pointFrames)\n+        sb.write('${kIndent * 6}const Offset(${point.x}, ${point.y}),\\n');\n+      sb.write('${kIndent * 5}],\\n');\n+    }\n+    sb.write('${kIndent * 4}),\\n');\n+    return sb.toString();\n+  }\n+}\n+\n+/// Interprets some subset of an SVG file.\n+///\n+/// Recursively goes over the SVG tree, applying transforms and opacities,\n+/// and build a FrameData which is a flat representation of the paths in the SVG\n+/// file, after applying transformations and converting relative coordinates to\n+/// absolute.\n+///\n+/// This does not support the SVG specification, but is just built to\n+/// support SVG files exported by a specific tool the motion design team is\n+/// using.\n+FrameData interpretSvg(String svgFilePath) {\n+  final File file = new File(svgFilePath);\n+  final String fileData = file.readAsStringSync();\n+  final XmlElement svgElement = _extractSvgElement(xml.parse(fileData));\n+  final double width = parsePixels(_extractAttr(svgElement, 'width')).toDouble();\n+  final double height = parsePixels(_extractAttr(svgElement, 'height')).toDouble();\n+\n+  final List<SvgPath> paths =\n+    _interpretSvgGroup(svgElement.children, new _Transform());\n+  return new FrameData(new Point<double>(width, height), paths);\n+}\n+\n+List<SvgPath> _interpretSvgGroup(List<XmlNode> children, _Transform transform) {\n+  final List<SvgPath> paths = <SvgPath>[];\n+  for (XmlNode node in children) {\n+    if (node.nodeType != XmlNodeType.ELEMENT)\n+      continue;\n+    final XmlElement element = node;\n+\n+    if (element.name.local == 'path') {\n+      paths.add(SvgPath.fromElement(element).applyTransform(transform));\n+    }\n+\n+    if (element.name.local == 'g') {\n+      double opacity = transform.opacity;\n+      if (_hasAttr(element, 'opacity'))\n+        opacity *= double.parse(_extractAttr(element, 'opacity'));\n+\n+      Matrix3 transformMatrix = transform.transformMatrix;\n+      if (_hasAttr(element, 'transform'))\n+        transformMatrix = transformMatrix.multiplied(\n+          _parseSvgTransform(_extractAttr(element, 'transform')));\n+\n+      final _Transform subtreeTransform = new _Transform(\n+        transformMatrix: transformMatrix,\n+        opacity: opacity\n+      );\n+      paths.addAll(_interpretSvgGroup(element.children, subtreeTransform));\n+    }\n+  }\n+  return paths;\n+}\n+\n+// Given a points list in the form e.g: \"25.0, 1.0 12.0, 12.0 23.0, 9.0\" matches\n+// the coordinated of the first point and the rest of the string, for the\n+// example above:\n+// group 1 will match \"25.0\"\n+// group 2 will match \"1.0\"\n+// group 3 will match \"12.0, 12.0 23.0, 9.0\"\n+//\n+// Commas are optional.\n+final RegExp _pointMatcher = new RegExp(r' *([\\-\\.0-9]+) *,? *([\\-\\.0-9]+)(.*)');\n+\n+/// Parse a string with a list of points, e.g:\n+/// '25.0, 1.0 12.0, 12.0 23.0, 9.0' will be parsed to:\n+/// [Point(25.0, 1.0), Point(12.0, 12.0), Point(23.0, 9.0)].\n+///\n+/// Commas are optional.\n+List<Point<double>> parsePoints(String points) {\n+  String unParsed = points;\n+  final List<Point<double>> result = <Point<double>>[];\n+  while(unParsed.isNotEmpty && _pointMatcher.hasMatch(unParsed)) {\n+    final Match m = _pointMatcher.firstMatch(unParsed);\n+    result.add(new Point<double>(\n+        double.parse(m.group(1)),\n+        double.parse(m.group(2))\n+    ));\n+    unParsed = m.group(3);\n+  }\n+  return result;\n+}\n+\n+/// Data for a single animation frame.\n+class FrameData {\n+  const FrameData(this.size, this.paths);\n+\n+  final Point<double> size;\n+  final List<SvgPath> paths;\n+\n+  @override\n+  bool operator ==(Object other){\n+    if (runtimeType != other.runtimeType)\n+      return false;\n+    final FrameData typedOther = other;\n+    return size == typedOther.size\n+        && const ListEquality<SvgPath>().equals(paths, typedOther.paths);\n+  }\n+\n+  @override\n+  int get hashCode => size.hashCode ^ paths.hashCode;\n+\n+  @override\n+  String toString() {\n+    return 'FrameData(size: $size, paths: $paths)';\n+  }\n+}\n+\n+/// Represents an SVG path element.\n+class SvgPath {\n+  const SvgPath(this.id, this.commands, {this.opacity = 1.0});\n+\n+  final String id;\n+  final List<SvgPathCommand> commands;\n+  final double opacity;\n+\n+  static final RegExp _pathCommandMatcher = new RegExp(r'([MmLlQqCcAZz]) *([\\-\\.0-9 ,]*)');\n+\n+  static SvgPath fromElement(XmlElement pathElement) {\n+    assert(pathElement.name.local == 'path');\n+    final String id = _extractAttr(pathElement, 'id');\n+    final String dAttr = _extractAttr(pathElement, 'd');\n+    final List<SvgPathCommand> commands = <SvgPathCommand>[];\n+    final SvgPathCommandBuilder commandsBuilder = new SvgPathCommandBuilder();\n+    for (Match match in _pathCommandMatcher.allMatches(dAttr)) {\n+      final String commandType = match.group(1);\n+      final String pointStr = match.group(2);\n+      commands.add(commandsBuilder.build(commandType, parsePoints(pointStr)));\n+    }","path":"dev/tools/vitool/lib/vitool.dart","position":307,"original_position":307,"commit_id":"98c3a23deb7cde8893accb844543d65f2c1ff43f","original_commit_id":"98c3a23deb7cde8893accb844543d65f2c1ff43f","user":{"login":"amirh","id":1024117,"avatar_url":"https://avatars2.githubusercontent.com/u/1024117?v=4","gravatar_id":"","url":"https://api.github.com/users/amirh","html_url":"https://github.com/amirh","followers_url":"https://api.github.com/users/amirh/followers","following_url":"https://api.github.com/users/amirh/following{/other_user}","gists_url":"https://api.github.com/users/amirh/gists{/gist_id}","starred_url":"https://api.github.com/users/amirh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/amirh/subscriptions","organizations_url":"https://api.github.com/users/amirh/orgs","repos_url":"https://api.github.com/users/amirh/repos","events_url":"https://api.github.com/users/amirh/events{/privacy}","received_events_url":"https://api.github.com/users/amirh/received_events","type":"User","site_admin":false},"body":"done","created_at":"2017-12-21T18:40:40Z","updated_at":"2017-12-21T18:40:40Z","html_url":"https://github.com/flutter/flutter/pull/13530#discussion_r158349150","pull_request_url":"https://api.github.com/repos/flutter/flutter/pulls/13530","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/flutter/flutter/pulls/comments/158349150"},"html":{"href":"https://github.com/flutter/flutter/pull/13530#discussion_r158349150"},"pull_request":{"href":"https://api.github.com/repos/flutter/flutter/pulls/13530"}},"in_reply_to_id":158167826},"pull_request":{"url":"https://api.github.com/repos/flutter/flutter/pulls/13530","id":157975545,"html_url":"https://github.com/flutter/flutter/pull/13530","diff_url":"https://github.com/flutter/flutter/pull/13530.diff","patch_url":"https://github.com/flutter/flutter/pull/13530.patch","issue_url":"https://api.github.com/repos/flutter/flutter/issues/13530","number":13530,"state":"open","locked":false,"title":"Add an AnimatedIcon class and an svg2dart tool to generate data for it","user":{"login":"amirh","id":1024117,"avatar_url":"https://avatars2.githubusercontent.com/u/1024117?v=4","gravatar_id":"","url":"https://api.github.com/users/amirh","html_url":"https://github.com/amirh","followers_url":"https://api.github.com/users/amirh/followers","following_url":"https://api.github.com/users/amirh/following{/other_user}","gists_url":"https://api.github.com/users/amirh/gists{/gist_id}","starred_url":"https://api.github.com/users/amirh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/amirh/subscriptions","organizations_url":"https://api.github.com/users/amirh/orgs","repos_url":"https://api.github.com/users/amirh/repos","events_url":"https://api.github.com/users/amirh/events{/privacy}","received_events_url":"https://api.github.com/users/amirh/received_events","type":"User","site_admin":false},"body":"Following the request I merged all the incremental CLs into one CL to be reviewed in one chunk.\r\n\r\nFixes #13345","created_at":"2017-12-13T00:32:57Z","updated_at":"2017-12-21T18:40:40Z","closed_at":null,"merged_at":null,"merge_commit_sha":"00939b50549bc60d5816675e8c6a8f4259c23229","assignee":null,"assignees":[],"requested_reviewers":[],"milestone":null,"commits_url":"https://api.github.com/repos/flutter/flutter/pulls/13530/commits","review_comments_url":"https://api.github.com/repos/flutter/flutter/pulls/13530/comments","review_comment_url":"https://api.github.com/repos/flutter/flutter/pulls/comments{/number}","comments_url":"https://api.github.com/repos/flutter/flutter/issues/13530/comments","statuses_url":"https://api.github.com/repos/flutter/flutter/statuses/98c3a23deb7cde8893accb844543d65f2c1ff43f","head":{"label":"amirh:animated-icons-big-cl","ref":"animated-icons-big-cl","sha":"98c3a23deb7cde8893accb844543d65f2c1ff43f","user":{"login":"amirh","id":1024117,"avatar_url":"https://avatars2.githubusercontent.com/u/1024117?v=4","gravatar_id":"","url":"https://api.github.com/users/amirh","html_url":"https://github.com/amirh","followers_url":"https://api.github.com/users/amirh/followers","following_url":"https://api.github.com/users/amirh/following{/other_user}","gists_url":"https://api.github.com/users/amirh/gists{/gist_id}","starred_url":"https://api.github.com/users/amirh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/amirh/subscriptions","organizations_url":"https://api.github.com/users/amirh/orgs","repos_url":"https://api.github.com/users/amirh/repos","events_url":"https://api.github.com/users/amirh/events{/privacy}","received_events_url":"https://api.github.com/users/amirh/received_events","type":"User","site_admin":false},"repo":{"id":78148410,"name":"flutter","full_name":"amirh/flutter","owner":{"login":"amirh","id":1024117,"avatar_url":"https://avatars2.githubusercontent.com/u/1024117?v=4","gravatar_id":"","url":"https://api.github.com/users/amirh","html_url":"https://github.com/amirh","followers_url":"https://api.github.com/users/amirh/followers","following_url":"https://api.github.com/users/amirh/following{/other_user}","gists_url":"https://api.github.com/users/amirh/gists{/gist_id}","starred_url":"https://api.github.com/users/amirh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/amirh/subscriptions","organizations_url":"https://api.github.com/users/amirh/orgs","repos_url":"https://api.github.com/users/amirh/repos","events_url":"https://api.github.com/users/amirh/events{/privacy}","received_events_url":"https://api.github.com/users/amirh/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/amirh/flutter","description":"Framework for building high-performance, high-fidelity iOS and Android apps.","fork":true,"url":"https://api.github.com/repos/amirh/flutter","forks_url":"https://api.github.com/repos/amirh/flutter/forks","keys_url":"https://api.github.com/repos/amirh/flutter/keys{/key_id}","collaborators_url":"https://api.github.com/repos/amirh/flutter/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/amirh/flutter/teams","hooks_url":"https://api.github.com/repos/amirh/flutter/hooks","issue_events_url":"https://api.github.com/repos/amirh/flutter/issues/events{/number}","events_url":"https://api.github.com/repos/amirh/flutter/events","assignees_url":"https://api.github.com/repos/amirh/flutter/assignees{/user}","branches_url":"https://api.github.com/repos/amirh/flutter/branches{/branch}","tags_url":"https://api.github.com/repos/amirh/flutter/tags","blobs_url":"https://api.github.com/repos/amirh/flutter/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/amirh/flutter/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/amirh/flutter/git/refs{/sha}","trees_url":"https://api.github.com/repos/amirh/flutter/git/trees{/sha}","statuses_url":"https://api.github.com/repos/amirh/flutter/statuses/{sha}","languages_url":"https://api.github.com/repos/amirh/flutter/languages","stargazers_url":"https://api.github.com/repos/amirh/flutter/stargazers","contributors_url":"https://api.github.com/repos/amirh/flutter/contributors","subscribers_url":"https://api.github.com/repos/amirh/flutter/subscribers","subscription_url":"https://api.github.com/repos/amirh/flutter/subscription","commits_url":"https://api.github.com/repos/amirh/flutter/commits{/sha}","git_commits_url":"https://api.github.com/repos/amirh/flutter/git/commits{/sha}","comments_url":"https://api.github.com/repos/amirh/flutter/comments{/number}","issue_comment_url":"https://api.github.com/repos/amirh/flutter/issues/comments{/number}","contents_url":"https://api.github.com/repos/amirh/flutter/contents/{+path}","compare_url":"https://api.github.com/repos/amirh/flutter/compare/{base}...{head}","merges_url":"https://api.github.com/repos/amirh/flutter/merges","archive_url":"https://api.github.com/repos/amirh/flutter/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/amirh/flutter/downloads","issues_url":"https://api.github.com/repos/amirh/flutter/issues{/number}","pulls_url":"https://api.github.com/repos/amirh/flutter/pulls{/number}","milestones_url":"https://api.github.com/repos/amirh/flutter/milestones{/number}","notifications_url":"https://api.github.com/repos/amirh/flutter/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/amirh/flutter/labels{/name}","releases_url":"https://api.github.com/repos/amirh/flutter/releases{/id}","deployments_url":"https://api.github.com/repos/amirh/flutter/deployments","created_at":"2017-01-05T21:08:15Z","updated_at":"2017-10-31T16:35:14Z","pushed_at":"2017-12-20T02:25:35Z","git_url":"git://github.com/amirh/flutter.git","ssh_url":"git@github.com:amirh/flutter.git","clone_url":"https://github.com/amirh/flutter.git","svn_url":"https://github.com/amirh/flutter","homepage":"https://flutter.io","size":32454,"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":8,"license":null,"forks":0,"open_issues":8,"watchers":0,"default_branch":"master"}},"base":{"label":"flutter:master","ref":"master","sha":"05dba60c63c1660f474f1f4251ba24dca12a2751","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":"2017-12-21T18:29:39Z","pushed_at":"2017-12-21T11:05:16Z","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":32495,"stargazers_count":9265,"watchers_count":9265,"language":"Dart","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":864,"mirror_url":null,"archived":false,"open_issues_count":2036,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":864,"open_issues":2036,"watchers":9265,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/flutter/flutter/pulls/13530"},"html":{"href":"https://github.com/flutter/flutter/pull/13530"},"issue":{"href":"https://api.github.com/repos/flutter/flutter/issues/13530"},"comments":{"href":"https://api.github.com/repos/flutter/flutter/issues/13530/comments"},"review_comments":{"href":"https://api.github.com/repos/flutter/flutter/pulls/13530/comments"},"review_comment":{"href":"https://api.github.com/repos/flutter/flutter/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/flutter/flutter/pulls/13530/commits"},"statuses":{"href":"https://api.github.com/repos/flutter/flutter/statuses/98c3a23deb7cde8893accb844543d65f2c1ff43f"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"amirh"}}