From e9454c3dce9137931856da2775347578b45212f3 Mon Sep 17 00:00:00 2001 From: Shreyas Thirumalai Date: Sat, 9 Jan 2021 08:53:52 +0530 Subject: [PATCH] feat(gitee): comment actions (issue/PR) (#150) --- lib/models/auth.dart | 58 ++++++++++++++++++------- lib/models/gitee.dart | 1 + lib/models/gitee.g.dart | 2 + lib/router.dart | 22 +++++++--- lib/screens/ge_commit.dart | 1 - lib/screens/ge_issue.dart | 52 ++++++++++++++++++---- lib/screens/ge_issue_comment.dart | 72 +++++++++++++++++++++---------- lib/screens/ge_issue_form.dart | 2 +- lib/screens/ge_pull.dart | 52 ++++++++++++++++++---- lib/widgets/action_button.dart | 28 +++++++++--- lib/widgets/comment_item.dart | 14 +++++- 11 files changed, 234 insertions(+), 70 deletions(-) diff --git a/lib/models/auth.dart b/lib/models/auth.dart index c9e4c0b..311ed3e 100644 --- a/lib/models/auth.dart +++ b/lib/models/auth.dart @@ -278,25 +278,53 @@ class AuthModel with ChangeNotifier { Future fetchGitee( String p, { - isPost = false, + requestType = 'GET', Map body = const {}, }) async { http.Response res; - if (isPost) { - res = await http.post( - '${activeAccount.domain}/api/v5$p', - headers: { - 'Authorization': 'token $token', - HttpHeaders.contentTypeHeader: 'application/json' - }, - body: jsonEncode(body), - ); - } else { - res = await http.get('${activeAccount.domain}/api/v5$p', - headers: {'Authorization': 'token $token'}); + Map headers = { + 'Authorization': 'token $token', + HttpHeaders.contentTypeHeader: 'application/json' + }; + switch (requestType) { + case 'DELETE': + { + await http.delete( + '${activeAccount.domain}/api/v5$p', + headers: headers, + ); + break; + } + case 'POST': + { + res = await http.post( + '${activeAccount.domain}/api/v5$p', + headers: headers, + body: jsonEncode(body), + ); + break; + } + case 'PATCH': + { + res = await http.patch( + '${activeAccount.domain}/api/v5$p', + headers: headers, + body: jsonEncode(body), + ); + break; + } + default: + { + res = await http.get('${activeAccount.domain}/api/v5$p', + headers: headers); + break; + } } - final info = json.decode(utf8.decode(res.bodyBytes)); - return info; + if (requestType != 'DELETE') { + final info = json.decode(utf8.decode(res.bodyBytes)); + return info; + } + return; } Future fetchGiteeWithPage(String path, diff --git a/lib/models/gitee.dart b/lib/models/gitee.dart index 8a08f1e..4ac10dc 100644 --- a/lib/models/gitee.dart +++ b/lib/models/gitee.dart @@ -164,6 +164,7 @@ class GiteePull { @JsonSerializable(fieldRename: FieldRename.snake) class GiteeComment { + int id; String body; String createdAt; GiteeRepoOwner user; diff --git a/lib/models/gitee.g.dart b/lib/models/gitee.g.dart index 9808196..05870a4 100644 --- a/lib/models/gitee.g.dart +++ b/lib/models/gitee.g.dart @@ -277,6 +277,7 @@ Map _$GiteePullToJson(GiteePull instance) => { GiteeComment _$GiteeCommentFromJson(Map json) { return GiteeComment() + ..id = json['id'] as int ..body = json['body'] as String ..createdAt = json['created_at'] as String ..user = json['user'] == null @@ -286,6 +287,7 @@ GiteeComment _$GiteeCommentFromJson(Map json) { Map _$GiteeCommentToJson(GiteeComment instance) => { + 'id': instance.id, 'body': instance.body, 'created_at': instance.createdAt, 'user': instance.user, diff --git a/lib/router.dart b/lib/router.dart index 16aea65..fccc269 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -505,9 +505,14 @@ class GiteeRouter { }); static final issueComment = RouterScreen( '/:owner/:name/issues/:number/comment', (context, parameters) { - return GeIssueCommentScreen(parameters['owner'].first, - parameters['name'].first, parameters['number'].first, - isPr: false); + return GeIssueCommentScreen( + parameters['owner'].first, + parameters['name'].first, + parameters['number'].first, + isPr: false, + body: parameters['body'] != null ? parameters['body'].first : '', + id: parameters['id'] != null ? parameters['id'].first : '', + ); }); static final pull = RouterScreen( '/:owner/:name/pulls/:number', @@ -519,9 +524,14 @@ class GiteeRouter { ); static final pullComment = RouterScreen('/:owner/:name/pulls/:number/comment', (context, parameters) { - return GeIssueCommentScreen(parameters['owner'].first, - parameters['name'].first, parameters['number'].first, - isPr: true); + return GeIssueCommentScreen( + parameters['owner'].first, + parameters['name'].first, + parameters['number'].first, + isPr: true, + body: parameters['body'] != null ? parameters['body'].first : '', + id: parameters['id'] != null ? parameters['id'].first : '', + ); }); static final files = RouterScreen('/:owner/:name/pulls/:number/files', (context, parameters) { diff --git a/lib/screens/ge_commit.dart b/lib/screens/ge_commit.dart index 194f875..c6955a5 100644 --- a/lib/screens/ge_commit.dart +++ b/lib/screens/ge_commit.dart @@ -24,7 +24,6 @@ class GeCommitScreen extends StatelessWidget { fetch: () async { final auth = context.read(); final items = await auth.fetchGitee('/repos/$owner/$name/commits/$sha'); - print(GiteeCommit.fromJson(items)); return GiteeCommit.fromJson(items); }, actionBuilder: (data, _) => ActionButton( diff --git a/lib/screens/ge_issue.dart b/lib/screens/ge_issue.dart index d88bdc7..9f9ad7b 100644 --- a/lib/screens/ge_issue.dart +++ b/lib/screens/ge_issue.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:git_touch/models/gitee.dart'; import 'package:git_touch/scaffolds/refresh_stateful.dart'; import 'package:git_touch/utils/utils.dart'; +import 'package:git_touch/widgets/action_button.dart'; import 'package:git_touch/widgets/action_entry.dart'; import 'package:git_touch/widgets/avatar.dart'; import 'package:git_touch/widgets/link.dart'; @@ -20,6 +21,38 @@ class GeIssueScreen extends StatelessWidget { GeIssueScreen(this.owner, this.name, this.number, {this.isPr: false}); + List _buildCommentActionItem( + BuildContext context, GiteeComment comment) { + final auth = context.read(); + final theme = context.read(); + return [ + ActionItem( + iconData: Octicons.pencil, + text: 'Edit', + onTap: (_) { + final uri = Uri( + path: '/gitee/$owner/$name/issues/$number/comment', + queryParameters: { + 'body': comment.body, + 'id': comment.id.toString(), + }, + ).toString(); + theme.push(context, uri); + }), + ActionItem( + iconData: Octicons.trashcan, + text: 'Delete', + onTap: (_) async { + await auth.fetchGitee( + '/repos/$owner/$name/issues/comments/${comment.id}', + requestType: 'DELETE'); + await theme.push(context, '/gitee/$owner/$name/issues/$number', + replace: true); + }, + ), + ]; + } + @override Widget build(BuildContext context) { return RefreshStatefulScaffold>>( @@ -97,14 +130,17 @@ class GeIssueScreen extends StatelessWidget { Padding( padding: EdgeInsets.only(left: 10), child: CommentItem( - avatar: Avatar( - url: comment.user.avatarUrl, - linkUrl: '/gitee/${comment.user.login}', - ), - createdAt: DateTime.parse(comment.createdAt), - body: comment.body, - login: comment.user.login, - prefix: 'gitee')), + avatar: Avatar( + url: comment.user.avatarUrl, + linkUrl: '/gitee/${comment.user.login}', + ), + createdAt: DateTime.parse(comment.createdAt), + body: comment.body, + login: comment.user.login, + prefix: 'gitee', + commentActionItemList: + _buildCommentActionItem(context, comment), + )), CommonStyle.border, SizedBox(height: 16), ], diff --git a/lib/screens/ge_issue_comment.dart b/lib/screens/ge_issue_comment.dart index 1f2524e..feb090c 100644 --- a/lib/screens/ge_issue_comment.dart +++ b/lib/screens/ge_issue_comment.dart @@ -1,7 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:git_touch/models/auth.dart'; -import 'package:git_touch/models/gitee.dart'; import 'package:git_touch/models/theme.dart'; import 'package:git_touch/scaffolds/common.dart'; import 'package:git_touch/utils/utils.dart'; @@ -12,33 +11,42 @@ class GeIssueCommentScreen extends StatefulWidget { final String name; final String number; final bool isPr; - GeIssueCommentScreen(this.owner, this.name, this.number, {this.isPr: false}); + final String body; + final String id; + GeIssueCommentScreen(this.owner, this.name, this.number, + {this.isPr: false, this.body: '', this.id: ''}); @override _GeIssueCommentScreenState createState() => _GeIssueCommentScreenState(); } class _GeIssueCommentScreenState extends State { - var _body = ''; + bool isEdit = false; + TextEditingController _controller = new TextEditingController(); + + @override + void initState() { + super.initState(); + _controller.text = widget.body; + if (_controller.text != '') { + isEdit = true; + } + } @override Widget build(BuildContext context) { final theme = Provider.of(context); final auth = Provider.of(context); return CommonScaffold( - title: Text('New Comment'), + title: Text(isEdit ? 'Update Comment' : 'New Comment'), body: Column( children: [ Padding( padding: CommonStyle.padding, child: CupertinoTextField( + controller: _controller, style: TextStyle(color: theme.palette.text), placeholder: 'Body', - onChanged: (v) { - setState(() { - _body = v; - }); - }, maxLines: 10, ), ), @@ -46,13 +54,19 @@ class _GeIssueCommentScreenState extends State { child: Text('Comment'), onPressed: () async { if (!widget.isPr) { - final res = await auth.fetchGitee( - '/repos/${widget.owner}/${widget.name}/issues/${widget.number}/comments', - isPost: true, - body: {'body': _body, 'repo': widget.name}, - ).then((v) { - return GiteeIssue.fromJson(v); - }); + if (!isEdit) { + final res = await auth.fetchGitee( + '/repos/${widget.owner}/${widget.name}/issues/${widget.number}/comments', + requestType: 'POST', + body: {'body': _controller.text, 'repo': widget.name}, + ); + } else { + final res = await auth.fetchGitee( + '/repos/${widget.owner}/${widget.name}/issues/comments/${int.parse(widget.id)}', + requestType: 'PATCH', + body: {'body': _controller.text, 'repo': widget.name}, + ); + } Navigator.pop(context, ''); await theme.push( context, @@ -60,13 +74,25 @@ class _GeIssueCommentScreenState extends State { replace: true, ); } else { - final res = await auth.fetchGitee( - '/repos/${widget.owner}/${widget.name}/pulls/${widget.number}/comments', - isPost: true, - body: {'body': _body, 'repo': widget.name}, - ).then((v) { - return GiteePull.fromJson(v); - }); + if (!isEdit) { + final res = await auth.fetchGitee( + '/repos/${widget.owner}/${widget.name}/pulls/${widget.number}/comments', + requestType: 'POST', + body: {'body': _controller.text, 'repo': widget.name}, + ); + Navigator.pop(context, ''); + await theme.push( + context, + '/gitee/${widget.owner}/${widget.name}/pulls/${widget.number}', + replace: true, + ); + } else { + final res = await auth.fetchGitee( + '/repos/${widget.owner}/${widget.name}/pulls/comments/${int.parse(widget.id)}', + requestType: 'PATCH', + body: {'body': _controller.text, 'repo': widget.name}, + ); + } Navigator.pop(context, ''); await theme.push( context, diff --git a/lib/screens/ge_issue_form.dart b/lib/screens/ge_issue_form.dart index 17136fb..9071778 100644 --- a/lib/screens/ge_issue_form.dart +++ b/lib/screens/ge_issue_form.dart @@ -58,7 +58,7 @@ class _GeIssueFormScreenState extends State { onPressed: () async { final res = await auth.fetchGitee( '/repos/${widget.owner}/issues', - isPost: true, + requestType: 'POST', body: {'body': _body, 'title': _title, 'repo': widget.name}, ).then((v) { return GiteeIssue.fromJson(v); diff --git a/lib/screens/ge_pull.dart b/lib/screens/ge_pull.dart index ed87dce..de03a51 100644 --- a/lib/screens/ge_pull.dart +++ b/lib/screens/ge_pull.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:git_touch/models/gitee.dart'; import 'package:git_touch/scaffolds/refresh_stateful.dart'; import 'package:git_touch/utils/utils.dart'; +import 'package:git_touch/widgets/action_button.dart'; import 'package:git_touch/widgets/action_entry.dart'; import 'package:git_touch/widgets/avatar.dart'; import 'package:git_touch/widgets/link.dart'; @@ -20,6 +21,38 @@ class GePullScreen extends StatelessWidget { GePullScreen(this.owner, this.name, this.number, {this.isPr: false}); + List _buildCommentActionItem( + BuildContext context, GiteeComment comment) { + final auth = context.read(); + final theme = context.read(); + return [ + ActionItem( + iconData: Octicons.pencil, + text: 'Edit', + onTap: (_) { + final uri = Uri( + path: '/gitee/$owner/$name/pulls/$number/comment', + queryParameters: { + 'body': comment.body, + 'id': comment.id.toString(), + }, + ).toString(); + theme.push(context, uri); + }), + ActionItem( + iconData: Octicons.trashcan, + text: 'Delete', + onTap: (_) async { + await auth.fetchGitee( + '/repos/$owner/$name/pulls/comments/${comment.id}', + requestType: 'DELETE'); + await theme.push(context, '/gitee/$owner/$name/pulls/$number', + replace: true); + }, + ), + ]; + } + @override Widget build(BuildContext context) { return RefreshStatefulScaffold< @@ -206,14 +239,17 @@ class GePullScreen extends StatelessWidget { Padding( padding: EdgeInsets.only(left: 10), child: CommentItem( - avatar: Avatar( - url: comment.user.avatarUrl, - linkUrl: '/gitee/${comment.user.login}', - ), - createdAt: DateTime.parse(comment.createdAt), - body: comment.body, - login: comment.user.login, - prefix: 'gitee')), + avatar: Avatar( + url: comment.user.avatarUrl, + linkUrl: '/gitee/${comment.user.login}', + ), + createdAt: DateTime.parse(comment.createdAt), + body: comment.body, + login: comment.user.login, + prefix: 'gitee', + commentActionItemList: + _buildCommentActionItem(context, comment), + )), CommonStyle.border, SizedBox(height: 16), ], diff --git a/lib/widgets/action_button.dart b/lib/widgets/action_button.dart index 5beb123..66593df 100644 --- a/lib/widgets/action_button.dart +++ b/lib/widgets/action_button.dart @@ -24,12 +24,14 @@ class ActionItem { return [ ActionItem( text: 'Share', + iconData: Octicons.rocket, onTap: (_) { Share.share(url); }, ), ActionItem( text: 'Open in Browser', + iconData: Octicons.globe, onTap: (_) { launchUrl(url); }, @@ -68,12 +70,18 @@ class ActionButton extends StatelessWidget { title: Text(title), actions: items.asMap().entries.map((entry) { return CupertinoActionSheetAction( - child: Text( - entry.value.text, - style: TextStyle( - fontWeight: selected == entry.key - ? FontWeight.w500 - : FontWeight.w400), + child: Row( + children: [ + Icon(entry.value.iconData), + SizedBox(width: 10), + Text( + entry.value.text, + style: TextStyle( + fontWeight: selected == entry.key + ? FontWeight.w500 + : FontWeight.w400), + ), + ], ), onPressed: () { Navigator.pop(context, entry.key); @@ -106,7 +114,13 @@ class ActionButton extends StatelessWidget { return items.asMap().entries.map((entry) { return PopupMenuItem( value: entry.key, - child: Text(entry.value.text), + child: Row( + children: [ + Icon(entry.value.iconData), + SizedBox(width: 10), + Text(entry.value.text) + ], + ), ); }).toList(); }, diff --git a/lib/widgets/comment_item.dart b/lib/widgets/comment_item.dart index e566acf..fa2a8a9 100644 --- a/lib/widgets/comment_item.dart +++ b/lib/widgets/comment_item.dart @@ -139,6 +139,7 @@ class CommentItem extends StatelessWidget { final String body; final String prefix; final List widgets; + final List commentActionItemList; CommentItem.gh(Map payload) : avatar = Avatar( @@ -149,7 +150,8 @@ class CommentItem extends StatelessWidget { createdAt = DateTime.parse(payload['createdAt']), body = payload['body'], widgets = [GhEmojiAction(payload)], - prefix = 'github'; + prefix = 'github', + commentActionItemList = []; // TODO CommentItem({ @required this.avatar, @@ -158,6 +160,7 @@ class CommentItem extends StatelessWidget { @required this.body, @required this.prefix, this.widgets, + this.commentActionItemList, }); @override @@ -183,6 +186,15 @@ class CommentItem extends StatelessWidget { ], ), ), + Align( + alignment: Alignment.centerRight, + child: ActionButton( + iconData: Octicons.kebab_horizontal, + title: 'Comment Actions', + items: [ + if (commentActionItemList != null) ...commentActionItemList + ], + )), ]), SizedBox(height: 12), MarkdownFlutterView(body, padding: EdgeInsets.zero), // TODO: link