mirror of
https://github.com/git-touch/git-touch
synced 2025-03-05 19:57:42 +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;
|
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 {
|
Future<dynamic> deleteWithCredentials(String url) async {
|
||||||
var headers = {HttpHeaders.authorizationHeader: 'token $token'};
|
var headers = {HttpHeaders.authorizationHeader: 'token $token'};
|
||||||
final res = await http
|
final res = await http
|
||||||
|
@ -53,6 +53,7 @@ class _IssueScreenState extends State<IssueScreen> {
|
|||||||
|
|
||||||
String get issueChunk {
|
String get issueChunk {
|
||||||
var base = '''
|
var base = '''
|
||||||
|
id
|
||||||
title
|
title
|
||||||
createdAt
|
createdAt
|
||||||
body
|
body
|
||||||
@ -82,6 +83,7 @@ commits {
|
|||||||
var base = '''
|
var base = '''
|
||||||
__typename
|
__typename
|
||||||
... on IssueComment {
|
... on IssueComment {
|
||||||
|
id
|
||||||
createdAt
|
createdAt
|
||||||
body
|
body
|
||||||
author {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LongListScaffold(
|
return LongListScaffold(
|
||||||
@ -394,13 +416,17 @@ __typename
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Padding(padding: EdgeInsets.only(bottom: 16)),
|
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 {
|
onRefresh: () async {
|
||||||
var res = await _queryIssue();
|
var res = await _queryIssue();
|
||||||
int totalCount = res['timeline']['totalCount'];
|
int totalCount = res['timeline']['totalCount'];
|
||||||
|
@ -91,9 +91,18 @@ class DialogOption<T> {
|
|||||||
DialogOption({this.value, this.widget});
|
DialogOption({this.value, this.widget});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<T> showOptions<T>(BuildContext context, List<DialogOption<T>> options) {
|
Future<T> showDialogOptions<T>(
|
||||||
var builder = (BuildContext context) {
|
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: (BuildContext context) {
|
||||||
return CupertinoAlertDialog(
|
return CupertinoAlertDialog(
|
||||||
|
title: title,
|
||||||
actions: options.map((option) {
|
actions: options.map((option) {
|
||||||
return CupertinoDialogAction(
|
return CupertinoDialogAction(
|
||||||
child: option.widget,
|
child: option.widget,
|
||||||
@ -101,27 +110,39 @@ Future<T> showOptions<T>(BuildContext context, List<DialogOption<T>> options) {
|
|||||||
Navigator.pop(context, option.value);
|
Navigator.pop(context, option.value);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList()
|
||||||
|
..add(
|
||||||
|
CupertinoDialogAction(
|
||||||
|
child: cancelWidget,
|
||||||
|
isDestructiveAction: true,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context, null);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
};
|
},
|
||||||
|
|
||||||
switch (SettingsProvider.of(context).theme) {
|
|
||||||
case ThemeMap.cupertino:
|
|
||||||
return showCupertinoDialog<T>(
|
|
||||||
context: context,
|
|
||||||
builder: builder,
|
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return Dialog(
|
return SimpleDialog(
|
||||||
child: Column(
|
title: title,
|
||||||
children: <Widget>[
|
children: options.map<Widget>((option) {
|
||||||
PopupMenuItem(child: Text('a')),
|
return SimpleDialogOption(
|
||||||
PopupMenuItem(child: Text('b')),
|
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 link = Color(0xff0366d6);
|
||||||
static const branchName = Palette.link;
|
static const branchName = Palette.link;
|
||||||
static const branchBackground = Color(0xffeaf5ff);
|
static const branchBackground = Color(0xffeaf5ff);
|
||||||
|
static const emojiBackground = Color(0xfff1f8ff);
|
||||||
}
|
}
|
||||||
|
|
||||||
// final pageSize = 5;
|
// final pageSize = 5;
|
||||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import '../utils/utils.dart';
|
import '../utils/utils.dart';
|
||||||
import 'avatar.dart';
|
import 'avatar.dart';
|
||||||
|
import 'link.dart';
|
||||||
import 'user_name.dart';
|
import 'user_name.dart';
|
||||||
|
|
||||||
final emojiMap = {
|
final emojiMap = {
|
||||||
@ -17,8 +18,21 @@ final emojiMap = {
|
|||||||
|
|
||||||
class CommentItem extends StatelessWidget {
|
class CommentItem extends StatelessWidget {
|
||||||
final Map<String, dynamic> payload;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -55,13 +69,19 @@ class CommentItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Wrap(
|
Wrap(
|
||||||
children: emojiMap.entries
|
children: emojiMap.entries
|
||||||
.where((entry) => payload[entry.key]['totalCount'] != 0)
|
.where((entry) => payload[entry.key]['totalCount'] as int != 0)
|
||||||
.map((entry) {
|
.map<Widget>((entry) {
|
||||||
|
var emojiKey = entry.key;
|
||||||
var emoji = entry.value;
|
var emoji = entry.value;
|
||||||
int count = payload[entry.key]['totalCount'];
|
var count = payload[entry.key]['totalCount'] as int;
|
||||||
|
|
||||||
return Container(
|
return Link(
|
||||||
|
onTap: () {
|
||||||
|
onReaction(emojiKey, _hasReacted(emojiKey));
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
padding: EdgeInsets.all(6),
|
padding: EdgeInsets.all(6),
|
||||||
|
decoration: _getDecorationByKey(emojiKey),
|
||||||
child: RichText(
|
child: RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: TextStyle(fontSize: 16),
|
style: TextStyle(fontSize: 16),
|
||||||
@ -75,8 +95,37 @@ class CommentItem extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).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(),
|
}).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});
|
Loading({this.more = false});
|
||||||
|
|
||||||
Widget _buildIndicator(BuildContext context) {
|
Widget _buildIndicator(BuildContext context) {
|
||||||
|
// return Image.asset('images/loading.webp');
|
||||||
|
|
||||||
switch (SettingsProvider.of(context).theme) {
|
switch (SettingsProvider.of(context).theme) {
|
||||||
case ThemeMap.cupertino:
|
case ThemeMap.cupertino:
|
||||||
return CupertinoActivityIndicator(radius: 12);
|
return CupertinoActivityIndicator(radius: 12);
|
||||||
|
@ -7,8 +7,9 @@ import 'user_name.dart';
|
|||||||
|
|
||||||
class TimelineItem extends StatelessWidget {
|
class TimelineItem extends StatelessWidget {
|
||||||
final Map<String, dynamic> payload;
|
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) {
|
TextSpan _buildReviewText(BuildContext context, item) {
|
||||||
switch (item['state']) {
|
switch (item['state']) {
|
||||||
@ -95,7 +96,7 @@ class TimelineItem extends StatelessWidget {
|
|||||||
item: payload,
|
item: payload,
|
||||||
);
|
);
|
||||||
case 'IssueComment':
|
case 'IssueComment':
|
||||||
return CommentItem(payload);
|
return CommentItem(payload, onReaction: onReaction);
|
||||||
case 'CrossReferencedEvent':
|
case 'CrossReferencedEvent':
|
||||||
return _buildItem(
|
return _buildItem(
|
||||||
actor: payload['actor']['login'],
|
actor: payload['actor']['login'],
|
||||||
@ -295,7 +296,7 @@ class TimelineItem extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.all(10),
|
padding: EdgeInsets.all(12),
|
||||||
child: _buildByType(context),
|
child: _buildByType(context),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user