feat(gitee): comment actions (issue/PR) (#150)

This commit is contained in:
Shreyas Thirumalai 2021-01-09 08:53:52 +05:30 committed by GitHub
parent efe882a0df
commit e9454c3dce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 234 additions and 70 deletions

View File

@ -278,25 +278,53 @@ class AuthModel with ChangeNotifier {
Future fetchGitee(
String p, {
isPost = false,
requestType = 'GET',
Map<String, dynamic> 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<String, String> 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<DataWithPage> fetchGiteeWithPage(String path,

View File

@ -164,6 +164,7 @@ class GiteePull {
@JsonSerializable(fieldRename: FieldRename.snake)
class GiteeComment {
int id;
String body;
String createdAt;
GiteeRepoOwner user;

View File

@ -277,6 +277,7 @@ Map<String, dynamic> _$GiteePullToJson(GiteePull instance) => <String, dynamic>{
GiteeComment _$GiteeCommentFromJson(Map<String, dynamic> 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<String, dynamic> json) {
Map<String, dynamic> _$GiteeCommentToJson(GiteeComment instance) =>
<String, dynamic>{
'id': instance.id,
'body': instance.body,
'created_at': instance.createdAt,
'user': instance.user,

View File

@ -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) {

View File

@ -24,7 +24,6 @@ class GeCommitScreen extends StatelessWidget {
fetch: () async {
final auth = context.read<AuthModel>();
final items = await auth.fetchGitee('/repos/$owner/$name/commits/$sha');
print(GiteeCommit.fromJson(items));
return GiteeCommit.fromJson(items);
},
actionBuilder: (data, _) => ActionButton(

View File

@ -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<ActionItem> _buildCommentActionItem(
BuildContext context, GiteeComment comment) {
final auth = context.read<AuthModel>();
final theme = context.read<ThemeModel>();
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<Tuple2<GiteeIssue, List<GiteeComment>>>(
@ -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),
],

View File

@ -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<GeIssueCommentScreen> {
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<ThemeModel>(context);
final auth = Provider.of<AuthModel>(context);
return CommonScaffold(
title: Text('New Comment'),
title: Text(isEdit ? 'Update Comment' : 'New Comment'),
body: Column(
children: <Widget>[
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<GeIssueCommentScreen> {
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<GeIssueCommentScreen> {
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,

View File

@ -58,7 +58,7 @@ class _GeIssueFormScreenState extends State<GeIssueFormScreen> {
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);

View File

@ -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<ActionItem> _buildCommentActionItem(
BuildContext context, GiteeComment comment) {
final auth = context.read<AuthModel>();
final theme = context.read<ThemeModel>();
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),
],

View File

@ -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();
},

View File

@ -139,6 +139,7 @@ class CommentItem extends StatelessWidget {
final String body;
final String prefix;
final List<Widget> widgets;
final List<ActionItem> commentActionItemList;
CommentItem.gh(Map<String, dynamic> 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