mirror of
https://github.com/git-touch/git-touch
synced 2025-03-04 19:27:40 +01:00
feat: add and remove reactions
This commit is contained in:
parent
af79d33a7f
commit
3bc1e7edcd
@ -346,6 +346,17 @@ class SettingsProviderState extends State<SettingsProvider> {
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<dynamic> postWithCredentials(String url,
|
||||
{String contentType, String body}) async {
|
||||
var headers = {HttpHeaders.authorizationHeader: 'token $token'};
|
||||
final res = await http
|
||||
.post(prefix + url, headers: headers, body: body ?? {})
|
||||
.timeout(_timeoutDuration);
|
||||
|
||||
// print(res.body);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<dynamic> deleteWithCredentials(String url) async {
|
||||
var headers = {HttpHeaders.authorizationHeader: 'token $token'};
|
||||
final res = await http
|
||||
|
@ -53,6 +53,7 @@ class _IssueScreenState extends State<IssueScreen> {
|
||||
|
||||
String get issueChunk {
|
||||
var base = '''
|
||||
id
|
||||
title
|
||||
createdAt
|
||||
body
|
||||
@ -82,6 +83,7 @@ commits {
|
||||
var base = '''
|
||||
__typename
|
||||
... on IssueComment {
|
||||
id
|
||||
createdAt
|
||||
body
|
||||
author {
|
||||
@ -344,6 +346,26 @@ __typename
|
||||
);
|
||||
}
|
||||
|
||||
_handleReaction(payload) {
|
||||
return (String emojiKey, bool isRemove) async {
|
||||
if (emojiKey == null) return;
|
||||
|
||||
var id = payload['id'] as String;
|
||||
var operation = isRemove ? 'remove' : 'add';
|
||||
await SettingsProvider.of(context).query('''
|
||||
mutation {
|
||||
${operation}Reaction(input: {subjectId: "$id", content: $emojiKey}) {
|
||||
clientMutationId
|
||||
}
|
||||
}
|
||||
''');
|
||||
setState(() {
|
||||
payload[emojiKey]['totalCount'] += isRemove ? -1 : 1;
|
||||
payload[emojiKey]['viewerHasReacted'] = !isRemove;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LongListScaffold(
|
||||
@ -394,13 +416,17 @@ __typename
|
||||
],
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(bottom: 16)),
|
||||
CommentItem(payload),
|
||||
CommentItem(
|
||||
payload,
|
||||
onReaction: _handleReaction(payload),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
]);
|
||||
},
|
||||
itemBuilder: (itemPayload) => TimelineItem(itemPayload),
|
||||
itemBuilder: (itemPayload) =>
|
||||
TimelineItem(itemPayload, onReaction: _handleReaction(itemPayload)),
|
||||
onRefresh: () async {
|
||||
var res = await _queryIssue();
|
||||
int totalCount = res['timeline']['totalCount'];
|
||||
|
@ -91,37 +91,58 @@ class DialogOption<T> {
|
||||
DialogOption({this.value, this.widget});
|
||||
}
|
||||
|
||||
Future<T> showOptions<T>(BuildContext context, List<DialogOption<T>> options) {
|
||||
var builder = (BuildContext context) {
|
||||
return CupertinoAlertDialog(
|
||||
actions: options.map((option) {
|
||||
return CupertinoDialogAction(
|
||||
child: option.widget,
|
||||
onPressed: () {
|
||||
Navigator.pop(context, option.value);
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
};
|
||||
Future<T> showDialogOptions<T>(
|
||||
BuildContext context, List<DialogOption<T>> options) {
|
||||
var title = Text('Pick your reaction');
|
||||
var cancelWidget = Text('Cancel');
|
||||
|
||||
switch (SettingsProvider.of(context).theme) {
|
||||
case ThemeMap.cupertino:
|
||||
return showCupertinoDialog<T>(
|
||||
context: context,
|
||||
builder: builder,
|
||||
builder: (BuildContext context) {
|
||||
return CupertinoAlertDialog(
|
||||
title: title,
|
||||
actions: options.map((option) {
|
||||
return CupertinoDialogAction(
|
||||
child: option.widget,
|
||||
onPressed: () {
|
||||
Navigator.pop(context, option.value);
|
||||
},
|
||||
);
|
||||
}).toList()
|
||||
..add(
|
||||
CupertinoDialogAction(
|
||||
child: cancelWidget,
|
||||
isDestructiveAction: true,
|
||||
onPressed: () {
|
||||
Navigator.pop(context, null);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
default:
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return Dialog(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
PopupMenuItem(child: Text('a')),
|
||||
PopupMenuItem(child: Text('b')),
|
||||
],
|
||||
),
|
||||
return SimpleDialog(
|
||||
title: title,
|
||||
children: options.map<Widget>((option) {
|
||||
return SimpleDialogOption(
|
||||
child: option.widget,
|
||||
onPressed: () {
|
||||
Navigator.pop(context, option.value);
|
||||
},
|
||||
);
|
||||
}).toList()
|
||||
..add(SimpleDialogOption(
|
||||
child: cancelWidget,
|
||||
onPressed: () {
|
||||
Navigator.pop(context, null);
|
||||
},
|
||||
)),
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -162,6 +183,7 @@ class Palette {
|
||||
static const link = Color(0xff0366d6);
|
||||
static const branchName = Palette.link;
|
||||
static const branchBackground = Color(0xffeaf5ff);
|
||||
static const emojiBackground = Color(0xfff1f8ff);
|
||||
}
|
||||
|
||||
// final pageSize = 5;
|
||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import '../utils/utils.dart';
|
||||
import 'avatar.dart';
|
||||
import 'link.dart';
|
||||
import 'user_name.dart';
|
||||
|
||||
final emojiMap = {
|
||||
@ -17,8 +18,21 @@ final emojiMap = {
|
||||
|
||||
class CommentItem extends StatelessWidget {
|
||||
final Map<String, dynamic> payload;
|
||||
final Function(String emojiKey, bool isRemove) onReaction;
|
||||
|
||||
CommentItem(this.payload);
|
||||
CommentItem(this.payload, {@required this.onReaction});
|
||||
|
||||
bool _hasReacted(String emojiKey) {
|
||||
if (payload[emojiKey] == null) return false;
|
||||
return payload[emojiKey]['viewerHasReacted'] as bool;
|
||||
}
|
||||
|
||||
Decoration _getDecorationByKey(String emojiKey) {
|
||||
return BoxDecoration(
|
||||
color: _hasReacted(emojiKey)
|
||||
? Palette.emojiBackground
|
||||
: Colors.transparent);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -55,28 +69,63 @@ class CommentItem extends StatelessWidget {
|
||||
),
|
||||
Wrap(
|
||||
children: emojiMap.entries
|
||||
.where((entry) => payload[entry.key]['totalCount'] != 0)
|
||||
.map((entry) {
|
||||
.where((entry) => payload[entry.key]['totalCount'] as int != 0)
|
||||
.map<Widget>((entry) {
|
||||
var emojiKey = entry.key;
|
||||
var emoji = entry.value;
|
||||
int count = payload[entry.key]['totalCount'];
|
||||
var count = payload[entry.key]['totalCount'] as int;
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.all(6),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: TextStyle(fontSize: 16),
|
||||
children: [
|
||||
TextSpan(text: emoji),
|
||||
TextSpan(text: ' '),
|
||||
TextSpan(
|
||||
text: count.toString(),
|
||||
style: TextStyle(color: Palette.link),
|
||||
),
|
||||
],
|
||||
return Link(
|
||||
onTap: () {
|
||||
onReaction(emojiKey, _hasReacted(emojiKey));
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(6),
|
||||
decoration: _getDecorationByKey(emojiKey),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: TextStyle(fontSize: 16),
|
||||
children: [
|
||||
TextSpan(text: emoji),
|
||||
TextSpan(text: ' '),
|
||||
TextSpan(
|
||||
text: count.toString(),
|
||||
style: TextStyle(color: Palette.link),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
}).toList()
|
||||
..add(
|
||||
Link(
|
||||
onTap: () async {
|
||||
var result = await 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(12),
|
||||
child: Icon(
|
||||
Octicons.smiley,
|
||||
color: Palette.link,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -8,6 +8,8 @@ class Loading extends StatelessWidget {
|
||||
Loading({this.more = false});
|
||||
|
||||
Widget _buildIndicator(BuildContext context) {
|
||||
// return Image.asset('images/loading.webp');
|
||||
|
||||
switch (SettingsProvider.of(context).theme) {
|
||||
case ThemeMap.cupertino:
|
||||
return CupertinoActivityIndicator(radius: 12);
|
||||
|
@ -7,8 +7,9 @@ import 'user_name.dart';
|
||||
|
||||
class TimelineItem extends StatelessWidget {
|
||||
final Map<String, dynamic> payload;
|
||||
final Function(String emojiKey, bool isRemove) onReaction;
|
||||
|
||||
TimelineItem(this.payload);
|
||||
TimelineItem(this.payload, {@required this.onReaction});
|
||||
|
||||
TextSpan _buildReviewText(BuildContext context, item) {
|
||||
switch (item['state']) {
|
||||
@ -95,7 +96,7 @@ class TimelineItem extends StatelessWidget {
|
||||
item: payload,
|
||||
);
|
||||
case 'IssueComment':
|
||||
return CommentItem(payload);
|
||||
return CommentItem(payload, onReaction: onReaction);
|
||||
case 'CrossReferencedEvent':
|
||||
return _buildItem(
|
||||
actor: payload['actor']['login'],
|
||||
@ -295,7 +296,7 @@ class TimelineItem extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(10),
|
||||
padding: EdgeInsets.all(12),
|
||||
child: _buildByType(context),
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user