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:
parent
e2e0f6ba25
commit
b032ab3d8a
@ -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'];
|
||||||
|
@ -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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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()),
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user