1
0
mirror of https://github.com/git-touch/git-touch synced 2024-12-18 11:19:28 +01:00

refactor: emoji reactions

This commit is contained in:
Rongjian Zhang 2020-01-30 17:20:36 +08:00
parent e2e0f6ba25
commit b032ab3d8a
3 changed files with 135 additions and 109 deletions

View File

@ -353,26 +353,6 @@ fragment ReactableParts on Reactable {
return status; return status;
} }
_handleReaction(payload) {
return (String emojiKey, bool isRemove) async {
if (emojiKey == null) return;
var id = payload['id'] as String;
var operation = isRemove ? 'remove' : 'add';
await Provider.of<AuthModel>(context).query('''
mutation {
${operation}Reaction(input: {subjectId: "$id", content: $emojiKey}) {
clientMutationId
}
}
''');
setState(() {
payload[emojiKey]['totalCount'] += isRemove ? -1 : 1;
payload[emojiKey]['viewerHasReacted'] = !isRemove;
});
};
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final auth = Provider.of<AuthModel>(context); final auth = Provider.of<AuthModel>(context);
@ -497,10 +477,7 @@ mutation {
CommonStyle.border, CommonStyle.border,
], ],
SizedBox(height: 8), SizedBox(height: 8),
CommentItem( CommentItem.gh(p),
p,
onReaction: _handleReaction(p),
),
], ],
), ),
), ),
@ -508,8 +485,7 @@ mutation {
], ],
); );
}, },
itemBuilder: (itemPayload) => itemBuilder: (itemPayload) => TimelineItem(itemPayload),
TimelineItem(itemPayload, onReaction: _handleReaction(itemPayload)),
onRefresh: () async { onRefresh: () async {
var res = await _queryIssue(); var res = await _queryIssue();
int totalCount = res['timelineItems']['totalCount']; int totalCount = res['timelineItems']['totalCount'];

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:git_touch/models/auth.dart';
import 'package:git_touch/models/theme.dart'; import 'package:git_touch/models/theme.dart';
import 'package:git_touch/widgets/markdown_view.dart'; import 'package:git_touch/widgets/markdown_view.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -20,44 +21,153 @@ final emojiMap = {
'EYES': '👀' 'EYES': '👀'
}; };
class CommentItem extends StatelessWidget { class GhEmojiAction extends StatefulWidget {
final Map<String, dynamic> payload; final Map<String, dynamic> payload;
final Function(String emojiKey, bool isRemove) onReaction; GhEmojiAction(this.payload);
@override
_GhEmojiActionState createState() => _GhEmojiActionState();
}
CommentItem(this.payload, {@required this.onReaction}); class _GhEmojiActionState extends State<GhEmojiAction> {
Decoration _getDecorationByKey(String emojiKey) {
return BoxDecoration(
color: _hasReacted(emojiKey) ? PrimerColors.blue000 : Colors.transparent,
);
}
get payload => widget.payload;
onReaction(String emojiKey, bool isRemove) async {
if (emojiKey == null) return;
var id = payload['id'] as String;
var operation = isRemove ? 'remove' : 'add';
await Provider.of<AuthModel>(context).query('''
mutation {
${operation}Reaction(input: {subjectId: "$id", content: $emojiKey}) {
clientMutationId
}
}
''');
setState(() {
payload[emojiKey]['totalCount'] += isRemove ? -1 : 1;
payload[emojiKey]['viewerHasReacted'] = !isRemove;
});
}
bool _hasReacted(String emojiKey) { bool _hasReacted(String emojiKey) {
if (payload[emojiKey] == null) return false; if (payload[emojiKey] == null) return false;
return payload[emojiKey]['viewerHasReacted'] as bool; return payload[emojiKey]['viewerHasReacted'] as bool;
} }
Decoration _getDecorationByKey(String emojiKey) { @override
return BoxDecoration( Widget build(BuildContext context) {
color: final theme = Provider.of<ThemeModel>(context);
_hasReacted(emojiKey) ? PrimerColors.blue000 : Colors.transparent); return Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: [
...emojiMap.entries
.where((entry) => payload[entry.key]['totalCount'] as int != 0)
.map<Widget>((entry) {
var emojiKey = entry.key;
var emoji = entry.value;
var count = payload[entry.key]['totalCount'] as int;
return Link(
onTap: () {
onReaction(emojiKey, _hasReacted(emojiKey));
},
child: Container(
padding: EdgeInsets.all(4),
decoration: _getDecorationByKey(emojiKey),
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: <Widget>[
Text(emoji, style: TextStyle(fontSize: 18)),
SizedBox(width: 4),
Text(numberFormat.format(count),
style:
TextStyle(color: theme.palette.primary, fontSize: 14))
],
),
),
);
}),
Link(
onTap: () async {
final result = await theme.showDialogOptions(
context,
emojiMap.entries.map((entry) {
var emojiKey = entry.key;
return DialogOption(
value: emojiKey,
widget: Container(
decoration: _getDecorationByKey(emojiKey),
child: Text(emojiKey + ' ' + entry.value),
),
);
}).toList(),
);
onReaction(result, _hasReacted(result));
},
child: Container(
padding: EdgeInsets.all(4),
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: <Widget>[
Text('+', style: TextStyle(color: theme.palette.primary)),
Icon(Octicons.smiley, color: theme.palette.primary, size: 18),
],
),
),
),
],
);
} }
}
class CommentItem extends StatelessWidget {
final Avatar avatar;
final String login;
final DateTime createdAt;
final String body;
final List<Widget> widgets;
CommentItem.gh(Map<String, dynamic> payload)
: avatar = Avatar(
url: payload['author']['avatarUrl'], // TODO: deleted user
linkUrl: '/' + payload['author']['login'],
),
login = payload['author']['login'],
createdAt = DateTime.parse(payload['createdAt']),
body = payload['body'],
widgets = [GhEmojiAction(payload)];
CommentItem({
@required this.avatar,
@required this.login,
@required this.createdAt,
@required this.body,
this.widgets,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Provider.of<ThemeModel>(context); final theme = Provider.of<ThemeModel>(context);
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Row(children: <Widget>[ Row(children: <Widget>[
Avatar( avatar,
url: payload['author']['avatarUrl'],
linkUrl: '/' + payload['author']['login'],
),
SizedBox(width: 8), SizedBox(width: 8),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
UserName(payload['author']['login']), UserName(login),
SizedBox(height: 2), SizedBox(height: 2),
Text( Text(
timeago.format(DateTime.parse(payload['createdAt'])), timeago.format(createdAt),
style: TextStyle( style: TextStyle(
color: theme.palette.tertiaryText, fontSize: 13), color: theme.palette.tertiaryText, fontSize: 13),
), ),
@ -66,69 +176,10 @@ class CommentItem extends StatelessWidget {
), ),
]), ]),
SizedBox(height: 12), SizedBox(height: 12),
MarkdownView(payload['body'] as String), // TODO: link MarkdownView(body), // TODO: link
SizedBox(height: 12), SizedBox(height: 12),
Wrap( if (widgets != null)
crossAxisAlignment: WrapCrossAlignment.center, ...widgets
children: [
...emojiMap.entries
.where((entry) => payload[entry.key]['totalCount'] as int != 0)
.map<Widget>((entry) {
var emojiKey = entry.key;
var emoji = entry.value;
var count = payload[entry.key]['totalCount'] as int;
return Link(
onTap: () {
onReaction(emojiKey, _hasReacted(emojiKey));
},
child: Container(
padding: EdgeInsets.all(4),
decoration: _getDecorationByKey(emojiKey),
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: <Widget>[
Text(emoji, style: TextStyle(fontSize: 18)),
SizedBox(width: 4),
Text(numberFormat.format(count),
style: TextStyle(
color: theme.palette.primary, fontSize: 14))
],
),
),
);
}),
Link(
onTap: () async {
var result =
await Provider.of<ThemeModel>(context).showDialogOptions(
context,
emojiMap.entries.map((entry) {
var emojiKey = entry.key;
return DialogOption(
value: emojiKey,
widget: Container(
decoration: _getDecorationByKey(emojiKey),
child: Text(emojiKey + ' ' + entry.value),
),
);
}).toList());
onReaction(result, _hasReacted(result));
},
child: Container(
padding: EdgeInsets.all(4),
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: <Widget>[
Text('+', style: TextStyle(color: theme.palette.primary)),
Icon(Octicons.smiley,
color: theme.palette.primary, size: 18),
],
),
),
),
],
),
], ],
); );
} }

View File

@ -51,9 +51,7 @@ class TimelineEventItem extends StatelessWidget {
class TimelineItem extends StatelessWidget { class TimelineItem extends StatelessWidget {
final Map<String, dynamic> p; final Map<String, dynamic> p;
final Function(String emojiKey, bool isRemove) onReaction; TimelineItem(this.p);
TimelineItem(this.p, {@required this.onReaction});
TextSpan _buildReviewText(BuildContext context, item) { TextSpan _buildReviewText(BuildContext context, item) {
switch (item['state']) { switch (item['state']) {
@ -102,7 +100,7 @@ class TimelineItem extends StatelessWidget {
p: p, p: p,
); );
case 'IssueComment': case 'IssueComment':
return CommentItem(p, onReaction: onReaction); return CommentItem.gh(p);
case 'CrossReferencedEvent': case 'CrossReferencedEvent':
return TimelineEventItem( return TimelineEventItem(
actor: p['actor']['login'], actor: p['actor']['login'],
@ -336,9 +334,10 @@ class TimelineItem extends StatelessWidget {
Container( Container(
padding: CommonStyle.padding.copyWith(left: 50), padding: CommonStyle.padding.copyWith(left: 50),
child: Column( child: Column(
children: comments.map((v) { children: <Widget>[
return CommentItem(v, onReaction: (_, __) {}); for (var v in comments) CommentItem.gh(v),
}).toList()), ],
),
), ),
], ],
); );