feat: event item long press

This commit is contained in:
Rongjian Zhang 2019-11-02 23:36:49 +08:00
parent 3e3e3d1251
commit 9cd38bc640
6 changed files with 182 additions and 69 deletions

View File

@ -131,7 +131,7 @@ class RepositoryScreen extends StatelessWidget {
if (data != null) ...[ if (data != null) ...[
ActionItem( ActionItem(
text: data[0]['viewerHasStarred'] ? 'Unstar' : 'Star', text: data[0]['viewerHasStarred'] ? 'Unstar' : 'Star',
onPress: () async { onPress: (_) async {
if (data[0]['viewerHasStarred']) { if (data[0]['viewerHasStarred']) {
await Provider.of<AuthModel>(context) await Provider.of<AuthModel>(context)
.deleteWithCredentials('/user/starred/$owner/$name'); .deleteWithCredentials('/user/starred/$owner/$name');
@ -148,7 +148,7 @@ class RepositoryScreen extends StatelessWidget {
text: data[0]['viewerSubscription'] == 'SUBSCRIBED' text: data[0]['viewerSubscription'] == 'SUBSCRIBED'
? 'Unwatch' ? 'Unwatch'
: 'Watch', : 'Watch',
onPress: () async { onPress: (_) async {
if (data[0]['viewerSubscription'] == 'SUBSCRIBED') { if (data[0]['viewerSubscription'] == 'SUBSCRIBED') {
await Provider.of<AuthModel>(context).deleteWithCredentials( await Provider.of<AuthModel>(context).deleteWithCredentials(
'/repos/$owner/$name/subscription'); '/repos/$owner/$name/subscription');

View File

@ -149,7 +149,7 @@ class UserScreen extends StatelessWidget {
ActionItem( ActionItem(
text: text:
data[0]['viewerIsFollowing'] ? 'Unfollow' : 'Follow', data[0]['viewerIsFollowing'] ? 'Unfollow' : 'Follow',
onPress: () async { onPress: (_) async {
if (data[0]['viewerIsFollowing']) { if (data[0]['viewerIsFollowing']) {
await Provider.of<AuthModel>(context) await Provider.of<AuthModel>(context)
.deleteWithCredentials('/user/following/$login'); .deleteWithCredentials('/user/following/$login');

View File

@ -80,11 +80,6 @@ TextSpan createUserSpan(BuildContext context, String login) {
return createLinkSpan(context, login, (_) => UserScreen(login)); return createLinkSpan(context, login, (_) => UserScreen(login));
} }
TextSpan createRepoLinkSpan(BuildContext context, String owner, String name) {
return createLinkSpan(
context, '$owner/$name', (_) => RepositoryScreen(owner, name));
}
class Palette { class Palette {
static const green = Color(0xff2cbe4e); static const green = Color(0xff2cbe4e);
} }

View File

@ -1,5 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:git_touch/screens/issue.dart';
import 'package:git_touch/screens/repository.dart';
import 'package:git_touch/screens/user.dart';
import 'package:git_touch/utils/utils.dart'; import 'package:git_touch/utils/utils.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:git_touch/models/theme.dart'; import 'package:git_touch/models/theme.dart';
@ -7,7 +10,7 @@ import 'package:share/share.dart';
class ActionItem { class ActionItem {
String text; String text;
void Function() onPress; void Function(BuildContext context) onPress;
IconData iconData; IconData iconData;
ActionItem({ ActionItem({
@ -18,14 +21,32 @@ class ActionItem {
ActionItem.share(String url) ActionItem.share(String url)
: text = 'Share', : text = 'Share',
onPress = (() { onPress = ((_) {
Share.share(url); Share.share(url);
}); });
ActionItem.launch(String url) ActionItem.launch(String url)
: text = 'Open in Browser', : text = 'Open in Browser',
onPress = (() { onPress = ((_) {
launchUrl(url); launchUrl(url);
}); });
ActionItem.user(String login)
: text = '@$login',
onPress = ((context) {
Provider.of<ThemeModel>(context)
.pushRoute(context, (_) => UserScreen(login));
});
ActionItem.repository(String owner, String name)
: text = '$owner/$name',
onPress = ((context) {
Provider.of<ThemeModel>(context)
.pushRoute(context, (_) => RepositoryScreen(owner, name));
});
ActionItem.issue(String owner, String name, int number)
: text = '#$number',
onPress = ((context) {
Provider.of<ThemeModel>(context).pushRoute(context,
(_) => IssueScreen(owner: owner, name: name, number: number));
});
} }
class ActionButton extends StatelessWidget { class ActionButton extends StatelessWidget {
@ -79,7 +100,7 @@ class ActionButton extends StatelessWidget {
); );
if (value != null) { if (value != null) {
items[value].onPress(); items[value].onPress(context);
} }
}, },
); );
@ -96,7 +117,7 @@ class ActionButton extends StatelessWidget {
}).toList(); }).toList();
}, },
onSelected: (value) { onSelected: (value) {
items[value].onPress(); items[value].onPress(context);
}, },
); );
} }

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:git_touch/screens/repository.dart'; import 'package:git_touch/screens/repository.dart';
import 'package:git_touch/widgets/action_button.dart';
import 'package:timeago/timeago.dart' as timeago; import 'package:timeago/timeago.dart' as timeago;
import 'package:primer/primer.dart'; import 'package:primer/primer.dart';
import '../screens/issue.dart'; import '../screens/issue.dart';
@ -32,26 +33,24 @@ class EventItem extends StatelessWidget {
EventItem(this.event); EventItem(this.event);
static const linkStyle = TextStyle(
color: PrimerColors.blue500,
fontWeight: FontWeight.w600,
);
TextSpan _buildRepo(BuildContext context) { TextSpan _buildRepo(BuildContext context) {
String name = event.repoFullName; final ls = event.repoFullName.split('/');
var arr = name.split('/'); assert(ls.length == 2);
return createRepoLinkSpan(context, arr[0], arr[1]); final owner = ls[0];
final name = ls[1];
return TextSpan(text: '$owner/$name', style: linkStyle);
} }
TextSpan _buildIssue(BuildContext context, TextSpan _buildIssue(BuildContext context, {@required int number}) {
{@required int number, bool isPullRequest = false}) {
// var resource = isPullRequest ? 'pull_request' : 'issue'; // var resource = isPullRequest ? 'pull_request' : 'issue';
// int number = event.payload['issue']['number']; // int number = event.payload['issue']['number'];
return createLinkSpan( return TextSpan(text: '#$number', style: linkStyle);
context,
'#' + number.toString(),
(_) => IssueScreen.fromFullName(
number: number,
fullName: event.repoFullName,
isPullRequest: isPullRequest,
),
);
} }
Widget _buildItem({ Widget _buildItem({
@ -62,6 +61,7 @@ class EventItem extends StatelessWidget {
IconData iconData = Octicons.octoface, IconData iconData = Octicons.octoface,
WidgetBuilder screenBuilder, WidgetBuilder screenBuilder,
String url, String url,
List<ActionItem> actionItems,
}) { }) {
if (detailWidget == null && detail != null) { if (detailWidget == null && detail != null) {
detailWidget = detailWidget =
@ -71,6 +71,37 @@ class EventItem extends StatelessWidget {
return Link( return Link(
screenBuilder: screenBuilder, screenBuilder: screenBuilder,
url: url, url: url,
onLongPress: () async {
if (actionItems == null) return;
final value = await showCupertinoModalPopup<int>(
context: context,
builder: (BuildContext context) {
return CupertinoActionSheet(
title: Text('Actions'),
actions: actionItems.asMap().entries.map((entry) {
return CupertinoActionSheetAction(
child: Text(entry.value.text),
onPressed: () {
Navigator.pop(context, entry.key);
},
);
}).toList(),
cancelButton: CupertinoActionSheetAction(
child: const Text('Cancel'),
isDefaultAction: true,
onPressed: () {
Navigator.pop(context);
},
),
);
},
);
if (value != null) {
actionItems[value].onPress(context);
}
},
child: Container( child: Container(
padding: CommonStyle.padding, padding: CommonStyle.padding,
child: Column( child: Column(
@ -96,11 +127,7 @@ class EventItem extends StatelessWidget {
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
children: [ children: [
createLinkSpan( TextSpan(text: event.actorLogin, style: linkStyle),
context,
event.actorLogin,
(_) => UserScreen(event.actorLogin),
),
...spans, ...spans,
], ],
), ),
@ -164,21 +191,23 @@ class EventItem extends StatelessWidget {
// TODO: // TODO:
return defaultItem; return defaultItem;
case 'ForkEvent': case 'ForkEvent':
final forkeeOwner = event.payload['forkee']['owner']['login'] as String;
final forkeeName = event.payload['forkee']['name'] as String;
return _buildItem( return _buildItem(
context: context, context: context,
spans: [ spans: [
TextSpan(text: ' forked '), TextSpan(text: ' forked '),
createRepoLinkSpan( TextSpan(text: '$forkeeOwner/$forkeeName', style: linkStyle),
context,
event.payload['forkee']['owner']['login'],
event.payload['forkee']['name']),
TextSpan(text: ' from '), TextSpan(text: ' from '),
_buildRepo(context), _buildRepo(context),
], ],
iconData: Octicons.repo_forked, iconData: Octicons.repo_forked,
screenBuilder: (_) => RepositoryScreen( screenBuilder: (_) => RepositoryScreen(forkeeOwner, forkeeName),
event.payload['forkee']['owner']['login'], actionItems: [
event.payload['forkee']['name']), ActionItem.user(event.actorLogin),
ActionItem.user(forkeeOwner),
ActionItem.repository(forkeeOwner, forkeeName),
],
); );
case 'ForkApplyEvent': case 'ForkApplyEvent':
case 'GitHubAppAuthorizationEvent': case 'GitHubAppAuthorizationEvent':
@ -189,48 +218,67 @@ class EventItem extends StatelessWidget {
// TODO: // TODO:
return defaultItem; return defaultItem;
case 'IssueCommentEvent': case 'IssueCommentEvent':
bool isPullRequest = event.payload['issue']['pull_request'] != null; final isPullRequest = event.payload['issue']['pull_request'] != null;
String resource = isPullRequest ? 'pull request' : 'issue'; final resource = isPullRequest ? 'pull request' : 'issue';
int number = event.payload['issue']['number']; final number = event.payload['issue']['number'] as int;
final ls = event.repoFullName.split('/');
assert(ls.length == 2);
final owner = ls[0];
final name = ls[1];
return _buildItem( return _buildItem(
context: context, context: context,
spans: [ spans: [
TextSpan(text: ' commented on $resource '), TextSpan(text: ' commented on $resource '),
_buildIssue( _buildIssue(context, number: number),
context,
number: number,
isPullRequest: isPullRequest,
),
TextSpan(text: ' at '), TextSpan(text: ' at '),
_buildRepo(context), _buildRepo(context),
// TextSpan(text: event.payload['comment']['body']) // TextSpan(text: event.payload['comment']['body'])
], ],
detail: event.payload['comment']['body'], detail: event.payload['comment']['body'],
iconData: Octicons.comment_discussion, iconData: Octicons.comment_discussion,
screenBuilder: (_) => IssueScreen.fromFullName( screenBuilder: (_) => IssueScreen(
owner: owner,
name: name,
number: number, number: number,
fullName: event.repoFullName,
isPullRequest: isPullRequest, isPullRequest: isPullRequest,
), ),
actionItems: [
ActionItem.user(event.actorLogin),
ActionItem.user(owner),
ActionItem.repository(owner, name),
ActionItem.issue(owner, name, number),
],
); );
case 'IssuesEvent': case 'IssuesEvent':
int number = event.payload['issue']['number']; final action = event.payload['action'];
final number = event.payload['issue']['number'] as int;
final ls = event.repoFullName.split('/');
assert(ls.length == 2);
final owner = ls[0];
final name = ls[1];
return _buildItem( return _buildItem(
context: context, context: context,
spans: [ spans: [
TextSpan(text: ' ${event.payload['action']} issue '), TextSpan(text: ' $action issue '),
_buildIssue(context, number: number), _buildIssue(context, number: number),
TextSpan(text: ' at '), TextSpan(text: ' at '),
_buildRepo(context), _buildRepo(context),
], ],
iconData: Octicons.issue_opened, iconData: Octicons.issue_opened,
detail: event.payload['issue']['title'], detail: event.payload['issue']['title'],
screenBuilder: (_) => IssueScreen.fromFullName( screenBuilder: (_) => IssueScreen(
owner: owner,
name: name,
number: number, number: number,
fullName: event.repoFullName,
), ),
actionItems: [
ActionItem.user(event.actorLogin),
ActionItem.user(owner),
ActionItem.repository(owner, name),
ActionItem.issue(owner, name, number),
],
); );
case 'LabelEvent': case 'LabelEvent':
case 'MarketplacePurchaseEvent': case 'MarketplacePurchaseEvent':
@ -247,47 +295,75 @@ class EventItem extends StatelessWidget {
// TODO: // TODO:
return defaultItem; return defaultItem;
case 'PullRequestEvent': case 'PullRequestEvent':
final action = event.payload['action'];
final number = event.payload['pull_request']['number'] as int;
final ls = event.repoFullName.split('/');
assert(ls.length == 2);
final owner = ls[0];
final name = ls[1];
return _buildItem( return _buildItem(
context: context, context: context,
spans: [ spans: [
TextSpan(text: ' ${event.payload['action']} pull request '), TextSpan(text: ' $action pull request '),
_buildIssue(context, _buildIssue(context, number: number),
number: event.payload['number'], isPullRequest: true),
TextSpan(text: ' at '), TextSpan(text: ' at '),
_buildRepo(context), _buildRepo(context),
], ],
iconData: Octicons.git_pull_request, iconData: Octicons.git_pull_request,
detail: event.payload['pull_request']['title'], detail: event.payload['pull_request']['title'],
screenBuilder: (_) => IssueScreen.fromFullName( screenBuilder: (_) => IssueScreen(
number: event.payload['pull_request']['number'], owner: owner,
fullName: event.repoFullName, name: name,
number: number,
isPullRequest: true, isPullRequest: true,
), ),
actionItems: [
ActionItem.user(event.actorLogin),
ActionItem.user(owner),
ActionItem.repository(owner, name),
ActionItem.issue(owner, name, number),
],
); );
case 'PullRequestReviewEvent': case 'PullRequestReviewEvent':
// TODO: // TODO:
return defaultItem; return defaultItem;
case 'PullRequestReviewCommentEvent': case 'PullRequestReviewCommentEvent':
final number = event.payload['pull_request']['number'] as int;
final ls = event.repoFullName.split('/');
assert(ls.length == 2);
final owner = ls[0];
final name = ls[1];
return _buildItem( return _buildItem(
context: context, context: context,
spans: [ spans: [
TextSpan(text: ' reviewed pull request '), TextSpan(text: ' reviewed pull request '),
_buildIssue(context, _buildIssue(context, number: number),
number: event.payload['pull_request']['number'],
isPullRequest: true),
TextSpan(text: ' at '), TextSpan(text: ' at '),
_buildRepo(context), _buildRepo(context),
], ],
detail: event.payload['comment']['body'], detail: event.payload['comment']['body'],
screenBuilder: (_) => IssueScreen.fromFullName( screenBuilder: (_) => IssueScreen(
number: event.payload['pull_request']['number'], owner: owner,
fullName: event.repoFullName, name: name,
number: number,
isPullRequest: true, isPullRequest: true,
), ),
actionItems: [
ActionItem.user(event.actorLogin),
ActionItem.user(owner),
ActionItem.repository(owner, name),
ActionItem.issue(owner, name, number),
],
); );
case 'PushEvent': case 'PushEvent':
var ref = event.payload['ref'] as String; final ref = event.payload['ref'] as String;
var commits = event.payload['commits'] as List; final commits = event.payload['commits'] as List;
final ls = event.repoFullName.split('/');
assert(ls.length == 2);
final owner = ls[0];
final name = ls[1];
return _buildItem( return _buildItem(
context: context, context: context,
@ -330,6 +406,11 @@ class EventItem extends StatelessWidget {
event.payload['before'] + event.payload['before'] +
'...' + '...' +
event.payload['head'], event.payload['head'],
actionItems: [
ActionItem.user(event.actorLogin),
ActionItem.user(owner),
ActionItem.repository(owner, name),
],
); );
case 'ReleaseEvent': case 'ReleaseEvent':
case 'RepositoryEvent': case 'RepositoryEvent':
@ -342,12 +423,21 @@ class EventItem extends StatelessWidget {
// TODO: // TODO:
return defaultItem; return defaultItem;
case 'WatchEvent': case 'WatchEvent':
final ls = event.repoFullName.split('/');
assert(ls.length == 2);
final owner = ls[0];
final name = ls[1];
return _buildItem( return _buildItem(
context: context, context: context,
spans: [TextSpan(text: ' starred '), _buildRepo(context)], spans: [TextSpan(text: ' starred '), _buildRepo(context)],
iconData: Octicons.star, iconData: Octicons.star,
screenBuilder: (_) => screenBuilder: (_) => RepositoryScreen(owner, name),
RepositoryScreen.fromFullName(event.repoFullName), actionItems: [
ActionItem.user(event.actorLogin),
ActionItem.user(owner),
ActionItem.repository(owner, name),
],
); );
default: default:
return defaultItem; return defaultItem;

View File

@ -9,12 +9,14 @@ class Link extends StatelessWidget {
final String url; final String url;
final WidgetBuilder screenBuilder; final WidgetBuilder screenBuilder;
final Function onTap; final Function onTap;
final VoidCallback onLongPress;
Link({ Link({
this.child, this.child,
this.url, this.url,
this.screenBuilder, this.screenBuilder,
this.onTap, this.onTap,
this.onLongPress,
}) : assert(screenBuilder == null || url == null); }) : assert(screenBuilder == null || url == null);
void _onTap(BuildContext context) { void _onTap(BuildContext context) {
@ -39,6 +41,11 @@ class Link extends StatelessWidget {
// splashColor: // splashColor:
// theme == AppThemeType.cupertino ? Colors.transparent : null, // theme == AppThemeType.cupertino ? Colors.transparent : null,
onTap: () => _onTap(context), onTap: () => _onTap(context),
onLongPress: () {
if (onLongPress != null) {
onLongPress();
}
},
), ),
), ),
); );