From ddc0f69e953a55a4817ce0f66f2ac4987d126c38 Mon Sep 17 00:00:00 2001 From: Rongjian Zhang Date: Fri, 20 Dec 2019 20:52:49 +0800 Subject: [PATCH] improvement: add event card --- lib/models/github.dart | 68 ++++++++- lib/models/github.g.dart | 101 +++++++++++++- lib/widgets/event_item.dart | 267 +++++++++++++++++++----------------- 3 files changed, 299 insertions(+), 137 deletions(-) diff --git a/lib/models/github.dart b/lib/models/github.dart index 995a8e8..231ceef 100644 --- a/lib/models/github.dart +++ b/lib/models/github.dart @@ -6,11 +6,11 @@ part 'github.g.dart'; @JsonSerializable(fieldRename: FieldRename.snake) class GithubEvent { - GithubEventActor actor; + GithubEventUser actor; String type; GithubEventRepo repo; - String createdAt; - Map payload; + DateTime createdAt; + GithubEventPayload payload; Tuple2 _repo; String get repoOwner { @@ -34,14 +34,14 @@ class GithubEvent { } @JsonSerializable(fieldRename: FieldRename.snake) -class GithubEventActor { +class GithubEventUser { String login; String avatarUrl; - GithubEventActor(); + GithubEventUser(); - factory GithubEventActor.fromJson(Map json) => - _$GithubEventActorFromJson(json); + factory GithubEventUser.fromJson(Map json) => + _$GithubEventUserFromJson(json); } @JsonSerializable(fieldRename: FieldRename.snake) @@ -54,6 +54,60 @@ class GithubEventRepo { _$GithubEventRepoFromJson(json); } +@JsonSerializable(fieldRename: FieldRename.snake) +class GithubEventPayload { + GithubEventIssue issue; + GithubEventIssue pullRequest; + GithubEventComment comment; + String action; + String ref; + String before; + String after; + List commits; + Map forkee; + + GithubEventPayload(); + + factory GithubEventPayload.fromJson(Map json) => + _$GithubEventPayloadFromJson(json); +} + +@JsonSerializable(fieldRename: FieldRename.snake) +class GithubEventIssue { + String title; + GithubEventUser user; + int number; + String body; + dynamic pullRequest; + + GithubEventIssue(); + + factory GithubEventIssue.fromJson(Map json) => + _$GithubEventIssueFromJson(json); +} + +@JsonSerializable() +class GithubEventComment { + String body; + GithubEventUser user; + + GithubEventComment(); + + factory GithubEventComment.fromJson(Map json) => + _$GithubEventCommentFromJson(json); +} + +@JsonSerializable() +class GithubEventCommit { + String sha; + String message; + + GithubEventCommit(); + + factory GithubEventCommit.fromJson(Map json) => + _$GithubEventCommitFromJson(json); +} + @JsonSerializable() class GithubTrendingItem { String author; diff --git a/lib/models/github.g.dart b/lib/models/github.g.dart index e8f511e..5425d13 100644 --- a/lib/models/github.g.dart +++ b/lib/models/github.g.dart @@ -10,13 +10,17 @@ GithubEvent _$GithubEventFromJson(Map json) { return GithubEvent() ..actor = json['actor'] == null ? null - : GithubEventActor.fromJson(json['actor'] as Map) + : GithubEventUser.fromJson(json['actor'] as Map) ..type = json['type'] as String ..repo = json['repo'] == null ? null : GithubEventRepo.fromJson(json['repo'] as Map) - ..createdAt = json['created_at'] as String - ..payload = json['payload'] as Map; + ..createdAt = json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String) + ..payload = json['payload'] == null + ? null + : GithubEventPayload.fromJson(json['payload'] as Map); } Map _$GithubEventToJson(GithubEvent instance) => @@ -24,17 +28,17 @@ Map _$GithubEventToJson(GithubEvent instance) => 'actor': instance.actor, 'type': instance.type, 'repo': instance.repo, - 'created_at': instance.createdAt, + 'created_at': instance.createdAt?.toIso8601String(), 'payload': instance.payload, }; -GithubEventActor _$GithubEventActorFromJson(Map json) { - return GithubEventActor() +GithubEventUser _$GithubEventUserFromJson(Map json) { + return GithubEventUser() ..login = json['login'] as String ..avatarUrl = json['avatar_url'] as String; } -Map _$GithubEventActorToJson(GithubEventActor instance) => +Map _$GithubEventUserToJson(GithubEventUser instance) => { 'login': instance.login, 'avatar_url': instance.avatarUrl, @@ -49,6 +53,89 @@ Map _$GithubEventRepoToJson(GithubEventRepo instance) => 'name': instance.name, }; +GithubEventPayload _$GithubEventPayloadFromJson(Map json) { + return GithubEventPayload() + ..issue = json['issue'] == null + ? null + : GithubEventIssue.fromJson(json['issue'] as Map) + ..pullRequest = json['pull_request'] == null + ? null + : GithubEventIssue.fromJson( + json['pull_request'] as Map) + ..comment = json['comment'] == null + ? null + : GithubEventComment.fromJson(json['comment'] as Map) + ..action = json['action'] as String + ..ref = json['ref'] as String + ..before = json['before'] as String + ..after = json['after'] as String + ..commits = (json['commits'] as List) + ?.map((e) => e == null + ? null + : GithubEventCommit.fromJson(e as Map)) + ?.toList() + ..forkee = json['forkee'] as Map; +} + +Map _$GithubEventPayloadToJson(GithubEventPayload instance) => + { + 'issue': instance.issue, + 'pull_request': instance.pullRequest, + 'comment': instance.comment, + 'action': instance.action, + 'ref': instance.ref, + 'before': instance.before, + 'after': instance.after, + 'commits': instance.commits, + 'forkee': instance.forkee, + }; + +GithubEventIssue _$GithubEventIssueFromJson(Map json) { + return GithubEventIssue() + ..title = json['title'] as String + ..user = json['user'] == null + ? null + : GithubEventUser.fromJson(json['user'] as Map) + ..number = json['number'] as int + ..body = json['body'] as String + ..pullRequest = json['pull_request']; +} + +Map _$GithubEventIssueToJson(GithubEventIssue instance) => + { + 'title': instance.title, + 'user': instance.user, + 'number': instance.number, + 'body': instance.body, + 'pull_request': instance.pullRequest, + }; + +GithubEventComment _$GithubEventCommentFromJson(Map json) { + return GithubEventComment() + ..body = json['body'] as String + ..user = json['user'] == null + ? null + : GithubEventUser.fromJson(json['user'] as Map); +} + +Map _$GithubEventCommentToJson(GithubEventComment instance) => + { + 'body': instance.body, + 'user': instance.user, + }; + +GithubEventCommit _$GithubEventCommitFromJson(Map json) { + return GithubEventCommit() + ..sha = json['sha'] as String + ..message = json['message'] as String; +} + +Map _$GithubEventCommitToJson(GithubEventCommit instance) => + { + 'sha': instance.sha, + 'message': instance.message, + }; + GithubTrendingItem _$GithubTrendingItemFromJson(Map json) { return GithubTrendingItem() ..author = json['author'] as String diff --git a/lib/widgets/event_item.dart b/lib/widgets/event_item.dart index 5e06068..21c3c74 100644 --- a/lib/widgets/event_item.dart +++ b/lib/widgets/event_item.dart @@ -3,6 +3,7 @@ import 'package:flutter/cupertino.dart'; import 'package:git_touch/models/github.dart'; import 'package:git_touch/models/theme.dart'; import 'package:git_touch/widgets/action_button.dart'; +import 'package:primer/primer.dart'; import 'package:provider/provider.dart'; import 'package:timeago/timeago.dart' as timeago; import 'avatar.dart'; @@ -19,7 +20,6 @@ class EventItem extends StatelessWidget { text: text, style: TextStyle( color: theme.palette.primary, - fontWeight: FontWeight.w600, ), ); } @@ -50,71 +50,73 @@ class EventItem extends StatelessWidget { Text(detail.trim(), overflow: TextOverflow.ellipsis, maxLines: 5); } - return Link( - url: url, - child: Container( - padding: CommonStyle.padding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Link( - url: '/' + event.actor.login, - child: Avatar.medium(url: event.actor.avatarUrl), - ), - SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: join(SizedBox(height: 8), [ - RichText( - text: TextSpan( - style: TextStyle( - fontSize: 16, - color: theme.palette.text, - fontWeight: FontWeight.w500, - ), - children: [ - _buildLinkSpan(theme, event.actor.login), - ...spans, - ], + return Container( + padding: CommonStyle.padding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Link( + url: '/' + event.actor.login, + child: Avatar.small(url: event.actor.avatarUrl), + ), + SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: join(SizedBox(height: 8), [ + RichText( + text: TextSpan( + style: TextStyle( + fontSize: 16, + color: theme.palette.text, ), + children: [ + _buildLinkSpan(theme, event.actor.login), + ...spans, + ], ), - if (detailWidget != null) - DefaultTextStyle( + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(iconData, + color: theme.palette.tertiaryText, size: 14), + SizedBox(width: 4), + Text(timeago.format(event.createdAt), + style: TextStyle( + fontSize: 13, + color: theme.palette.tertiaryText, + )), + // Expanded(child: Container()), + // GestureDetector( + // child: Icon(Icons.more_horiz), + // onTap: () { + // theme.showActions(context, actionItems); + // }, + // ), + ], + ), + if (detailWidget != null) + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: PrimerColors.gray100, + borderRadius: BorderRadius.all(Radius.circular(4))), + child: DefaultTextStyle( style: TextStyle( color: theme.palette.text, fontSize: 14), child: detailWidget, ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Icon(iconData, - color: theme.palette.tertiaryText, size: 14), - SizedBox(width: 4), - Text(timeago.format(DateTime.parse(event.createdAt)), - style: TextStyle( - fontSize: 13, - color: theme.palette.tertiaryText, - )), - Expanded(child: Container()), - GestureDetector( - child: Icon(Icons.more_horiz), - onTap: () { - theme.showActions(context, actionItems); - }, - ), - ], ), - ]), - ), + ]), ), - ], - ), - ], - ), + ), + ], + ), + ], ), ); } @@ -134,6 +136,22 @@ class EventItem extends StatelessWidget { ); } + Widget _buildIssueCard(GithubEventIssue issue, String body) { + return Column( + children: [ + Row( + children: [ + Icon(Octicons.issue_opened), + Text('#' + issue.number.toString()), + Text(issue.title), + ], + ), + SizedBox(height: 4), + if (body != null) Text(body), + ], + ); + } + @override build(BuildContext context) { final theme = Provider.of(context); @@ -154,8 +172,8 @@ class EventItem extends StatelessWidget { // TODO: return _buildDefaultItem(context); case 'ForkEvent': - final forkeeOwner = event.payload['forkee']['owner']['login'] as String; - final forkeeName = event.payload['forkee']['name'] as String; + final forkeeOwner = event.payload.forkee['owner']['login'] as String; + final forkeeName = event.payload.forkee['name'] as String; return _buildItem( context: context, spans: [ @@ -181,47 +199,46 @@ class EventItem extends StatelessWidget { // TODO: return _buildDefaultItem(context); case 'IssueCommentEvent': - final isPullRequest = event.payload['issue']['pull_request'] != null; - final resource = isPullRequest ? 'pull request' : 'issue'; - final number = event.payload['issue']['number'] as int; + final isPullRequest = event.payload.issue.pullRequest != null; return _buildItem( context: context, spans: [ - TextSpan(text: ' commented on $resource '), - _buildLinkSpan(theme, '#$number'), + TextSpan( + text: + ' commented on ${isPullRequest ? 'pull request' : 'issue'} '), + _buildLinkSpan(theme, '#${event.payload.issue.number}'), TextSpan(text: ' at '), _buildRepo(theme), - // TextSpan(text: event.payload['comment']['body']) ], - detail: event.payload['comment']['body'], + detailWidget: + _buildIssueCard(event.payload.issue, event.payload.comment.body), iconData: Octicons.comment_discussion, url: - '/${event.repoOwner}/${event.repoName}/${isPullRequest ? 'pulls' : 'issues'}/$number', + '/${event.repoOwner}/${event.repoName}/${isPullRequest ? 'pulls' : 'issues'}/${event.payload.issue.number}', actionItems: [ ..._getUserActions([event.actor.login, event.repoOwner]), - ActionItem.pullRequest(event.repoOwner, event.repoName, number), + ActionItem.pullRequest( + event.repoOwner, event.repoName, event.payload.issue.number), ], ); case 'IssuesEvent': - final action = event.payload['action']; - final number = event.payload['issue']['number'] as int; - + final issue = event.payload.issue; return _buildItem( context: context, spans: [ - TextSpan(text: ' $action issue '), - _buildLinkSpan(theme, '#$number'), + TextSpan(text: ' ${event.payload.action} issue '), + _buildLinkSpan(theme, '#${issue.number}'), TextSpan(text: ' at '), _buildRepo(theme), ], iconData: Octicons.issue_opened, - detail: event.payload['issue']['title'], - url: '/${event.repoOwner}/${event.repoName}/issues/$number', + detailWidget: _buildIssueCard(issue, issue.body), + url: '/${event.repoOwner}/${event.repoName}/issues/${issue.number}', actionItems: [ ..._getUserActions([event.actor.login, event.repoOwner]), ActionItem.repository(event.repoOwner, event.repoName), - ActionItem.issue(event.repoOwner, event.repoName, number), + ActionItem.issue(event.repoOwner, event.repoName, issue.number), ], ); case 'LabelEvent': @@ -239,89 +256,93 @@ class EventItem extends StatelessWidget { // TODO: return _buildDefaultItem(context); case 'PullRequestEvent': - final action = event.payload['action']; - final number = event.payload['pull_request']['number'] as int; - + final pr = event.payload.pullRequest; return _buildItem( context: context, spans: [ - TextSpan(text: ' $action pull request '), - _buildLinkSpan(theme, '#$number'), + TextSpan(text: ' ${event.payload.action} pull request '), + _buildLinkSpan(theme, '#${pr.number}'), TextSpan(text: ' at '), _buildRepo(theme), ], iconData: Octicons.git_pull_request, - detail: event.payload['pull_request']['title'], - url: '/${event.repoOwner}/${event.repoName}/pulls/$number', + detailWidget: _buildIssueCard(pr, pr.body), + url: '/${event.repoOwner}/${event.repoName}/pulls/${pr.number}', actionItems: [ ..._getUserActions([event.actor.login, event.repoOwner]), ActionItem.repository(event.repoOwner, event.repoName), - ActionItem.pullRequest(event.repoOwner, event.repoName, number), + ActionItem.pullRequest(event.repoOwner, event.repoName, pr.number), ], ); case 'PullRequestReviewEvent': // TODO: return _buildDefaultItem(context); case 'PullRequestReviewCommentEvent': - final number = event.payload['pull_request']['number'] as int; - + final pr = event.payload.pullRequest; return _buildItem( context: context, spans: [ TextSpan(text: ' reviewed pull request '), - _buildLinkSpan(theme, '#$number'), + _buildLinkSpan(theme, '#${pr.number}'), TextSpan(text: ' at '), _buildRepo(theme), ], - detail: event.payload['comment']['body'], - url: '/${event.repoOwner}/${event.repoName}/pulls/$number', + detailWidget: _buildIssueCard(pr, pr.body), + url: '/${event.repoOwner}/${event.repoName}/pulls/${pr.number}', actionItems: [ ..._getUserActions([event.actor.login, event.repoOwner]), ActionItem.repository(event.repoOwner, event.repoName), - ActionItem.pullRequest(event.repoOwner, event.repoName, number), + ActionItem.pullRequest(event.repoOwner, event.repoName, pr.number), ], ); case 'PushEvent': - final ref = event.payload['ref'] as String; - final commits = event.payload['commits'] as List; - return _buildItem( context: context, - spans: [ - TextSpan(text: ' pushed to '), - WidgetSpan( - child: PrimerBranchName(ref.replaceFirst('refs/heads/', '')), - ), - TextSpan(text: ' at '), - _buildRepo(theme) - ], + spans: [TextSpan(text: ' pushed to '), _buildRepo(theme)], iconData: Octicons.repo_push, detailWidget: Column( - children: commits.map((commit) { - return Row( - children: [ - Text( - (commit['sha'] as String).substring(0, 7), - style: TextStyle( - color: theme.palette.primary, - fontSize: 13, - fontFamily: CommonStyle.monospace, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RichText( + text: TextSpan( + style: TextStyle(color: theme.palette.primary), + children: [ + TextSpan( + text: event.payload.commits.length.toString() + + ' commits to '), + WidgetSpan( + child: PrimerBranchName( + event.payload.ref.replaceFirst('refs/heads/', '')), ), - ), - SizedBox(width: 6), - Expanded( - child: Text( - commit['message'], - overflow: TextOverflow.ellipsis, - maxLines: 1, + ], + ), + ), + ...event.payload.commits.map((commit) { + return Row( + children: [ + Text( + commit.sha.substring(0, 7), + style: TextStyle( + color: theme.palette.primary, + fontSize: 13, + fontFamily: CommonStyle.monospace, + ), ), - ) - ], - ); - }).toList(), + SizedBox(width: 6), + Expanded( + child: Text( + commit.message, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ) + ], + ); + }).toList() + ], ), url: - 'https://github.com/${event.repoOwner}/${event.repoName}/compare/${event.payload['before']}...${event.payload['head']}', + 'https://github.com/${event.repoOwner}/${event.repoName}/compare/${event.payload.before}...${event.payload.after}', actionItems: [ ..._getUserActions([event.actor.login, event.repoOwner]), ActionItem.repository(event.repoOwner, event.repoName),