diff --git a/lib/screens/repository.dart b/lib/screens/repository.dart index b0de367..0be904c 100644 --- a/lib/screens/repository.dart +++ b/lib/screens/repository.dart @@ -131,7 +131,7 @@ class RepositoryScreen extends StatelessWidget { if (data != null) ...[ ActionItem( text: data[0]['viewerHasStarred'] ? 'Unstar' : 'Star', - onPress: () async { + onPress: (_) async { if (data[0]['viewerHasStarred']) { await Provider.of(context) .deleteWithCredentials('/user/starred/$owner/$name'); @@ -148,7 +148,7 @@ class RepositoryScreen extends StatelessWidget { text: data[0]['viewerSubscription'] == 'SUBSCRIBED' ? 'Unwatch' : 'Watch', - onPress: () async { + onPress: (_) async { if (data[0]['viewerSubscription'] == 'SUBSCRIBED') { await Provider.of(context).deleteWithCredentials( '/repos/$owner/$name/subscription'); diff --git a/lib/screens/user.dart b/lib/screens/user.dart index 9173612..0b5c7e7 100644 --- a/lib/screens/user.dart +++ b/lib/screens/user.dart @@ -149,7 +149,7 @@ class UserScreen extends StatelessWidget { ActionItem( text: data[0]['viewerIsFollowing'] ? 'Unfollow' : 'Follow', - onPress: () async { + onPress: (_) async { if (data[0]['viewerIsFollowing']) { await Provider.of(context) .deleteWithCredentials('/user/following/$login'); diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 9d92d80..1e3af65 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -80,11 +80,6 @@ TextSpan createUserSpan(BuildContext context, String 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 { static const green = Color(0xff2cbe4e); } diff --git a/lib/widgets/action_button.dart b/lib/widgets/action_button.dart index 590c2b5..f97381c 100644 --- a/lib/widgets/action_button.dart +++ b/lib/widgets/action_button.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.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:provider/provider.dart'; import 'package:git_touch/models/theme.dart'; @@ -7,7 +10,7 @@ import 'package:share/share.dart'; class ActionItem { String text; - void Function() onPress; + void Function(BuildContext context) onPress; IconData iconData; ActionItem({ @@ -18,14 +21,32 @@ class ActionItem { ActionItem.share(String url) : text = 'Share', - onPress = (() { + onPress = ((_) { Share.share(url); }); ActionItem.launch(String url) : text = 'Open in Browser', - onPress = (() { + onPress = ((_) { launchUrl(url); }); + ActionItem.user(String login) + : text = '@$login', + onPress = ((context) { + Provider.of(context) + .pushRoute(context, (_) => UserScreen(login)); + }); + ActionItem.repository(String owner, String name) + : text = '$owner/$name', + onPress = ((context) { + Provider.of(context) + .pushRoute(context, (_) => RepositoryScreen(owner, name)); + }); + ActionItem.issue(String owner, String name, int number) + : text = '#$number', + onPress = ((context) { + Provider.of(context).pushRoute(context, + (_) => IssueScreen(owner: owner, name: name, number: number)); + }); } class ActionButton extends StatelessWidget { @@ -79,7 +100,7 @@ class ActionButton extends StatelessWidget { ); if (value != null) { - items[value].onPress(); + items[value].onPress(context); } }, ); @@ -96,7 +117,7 @@ class ActionButton extends StatelessWidget { }).toList(); }, onSelected: (value) { - items[value].onPress(); + items[value].onPress(context); }, ); } diff --git a/lib/widgets/event_item.dart b/lib/widgets/event_item.dart index b169014..e4781ed 100644 --- a/lib/widgets/event_item.dart +++ b/lib/widgets/event_item.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/cupertino.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:primer/primer.dart'; import '../screens/issue.dart'; @@ -32,26 +33,24 @@ class EventItem extends StatelessWidget { EventItem(this.event); + static const linkStyle = TextStyle( + color: PrimerColors.blue500, + fontWeight: FontWeight.w600, + ); + TextSpan _buildRepo(BuildContext context) { - String name = event.repoFullName; - var arr = name.split('/'); - return createRepoLinkSpan(context, arr[0], arr[1]); + final ls = event.repoFullName.split('/'); + assert(ls.length == 2); + final owner = ls[0]; + final name = ls[1]; + return TextSpan(text: '$owner/$name', style: linkStyle); } - TextSpan _buildIssue(BuildContext context, - {@required int number, bool isPullRequest = false}) { + TextSpan _buildIssue(BuildContext context, {@required int number}) { // var resource = isPullRequest ? 'pull_request' : 'issue'; // int number = event.payload['issue']['number']; - return createLinkSpan( - context, - '#' + number.toString(), - (_) => IssueScreen.fromFullName( - number: number, - fullName: event.repoFullName, - isPullRequest: isPullRequest, - ), - ); + return TextSpan(text: '#$number', style: linkStyle); } Widget _buildItem({ @@ -62,6 +61,7 @@ class EventItem extends StatelessWidget { IconData iconData = Octicons.octoface, WidgetBuilder screenBuilder, String url, + List actionItems, }) { if (detailWidget == null && detail != null) { detailWidget = @@ -71,6 +71,37 @@ class EventItem extends StatelessWidget { return Link( screenBuilder: screenBuilder, url: url, + onLongPress: () async { + if (actionItems == null) return; + + final value = await showCupertinoModalPopup( + 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( padding: CommonStyle.padding, child: Column( @@ -96,11 +127,7 @@ class EventItem extends StatelessWidget { fontWeight: FontWeight.w500, ), children: [ - createLinkSpan( - context, - event.actorLogin, - (_) => UserScreen(event.actorLogin), - ), + TextSpan(text: event.actorLogin, style: linkStyle), ...spans, ], ), @@ -164,21 +191,23 @@ class EventItem extends StatelessWidget { // TODO: return defaultItem; case 'ForkEvent': + final forkeeOwner = event.payload['forkee']['owner']['login'] as String; + final forkeeName = event.payload['forkee']['name'] as String; return _buildItem( context: context, spans: [ TextSpan(text: ' forked '), - createRepoLinkSpan( - context, - event.payload['forkee']['owner']['login'], - event.payload['forkee']['name']), + TextSpan(text: '$forkeeOwner/$forkeeName', style: linkStyle), TextSpan(text: ' from '), _buildRepo(context), ], iconData: Octicons.repo_forked, - screenBuilder: (_) => RepositoryScreen( - event.payload['forkee']['owner']['login'], - event.payload['forkee']['name']), + screenBuilder: (_) => RepositoryScreen(forkeeOwner, forkeeName), + actionItems: [ + ActionItem.user(event.actorLogin), + ActionItem.user(forkeeOwner), + ActionItem.repository(forkeeOwner, forkeeName), + ], ); case 'ForkApplyEvent': case 'GitHubAppAuthorizationEvent': @@ -189,48 +218,67 @@ class EventItem extends StatelessWidget { // TODO: return defaultItem; case 'IssueCommentEvent': - bool isPullRequest = event.payload['issue']['pull_request'] != null; - String resource = isPullRequest ? 'pull request' : 'issue'; - int number = event.payload['issue']['number']; + final isPullRequest = event.payload['issue']['pull_request'] != null; + final resource = isPullRequest ? 'pull request' : 'issue'; + 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( context: context, spans: [ TextSpan(text: ' commented on $resource '), - _buildIssue( - context, - number: number, - isPullRequest: isPullRequest, - ), + _buildIssue(context, number: number), TextSpan(text: ' at '), _buildRepo(context), // TextSpan(text: event.payload['comment']['body']) ], detail: event.payload['comment']['body'], iconData: Octicons.comment_discussion, - screenBuilder: (_) => IssueScreen.fromFullName( + screenBuilder: (_) => IssueScreen( + owner: owner, + name: name, number: number, - fullName: event.repoFullName, isPullRequest: isPullRequest, ), + actionItems: [ + ActionItem.user(event.actorLogin), + ActionItem.user(owner), + ActionItem.repository(owner, name), + ActionItem.issue(owner, name, number), + ], ); 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( context: context, spans: [ - TextSpan(text: ' ${event.payload['action']} issue '), + TextSpan(text: ' $action issue '), _buildIssue(context, number: number), TextSpan(text: ' at '), _buildRepo(context), ], iconData: Octicons.issue_opened, detail: event.payload['issue']['title'], - screenBuilder: (_) => IssueScreen.fromFullName( + screenBuilder: (_) => IssueScreen( + owner: owner, + name: name, 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 'MarketplacePurchaseEvent': @@ -247,47 +295,75 @@ class EventItem extends StatelessWidget { // TODO: return defaultItem; 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( context: context, spans: [ - TextSpan(text: ' ${event.payload['action']} pull request '), - _buildIssue(context, - number: event.payload['number'], isPullRequest: true), + TextSpan(text: ' $action pull request '), + _buildIssue(context, number: number), TextSpan(text: ' at '), _buildRepo(context), ], iconData: Octicons.git_pull_request, detail: event.payload['pull_request']['title'], - screenBuilder: (_) => IssueScreen.fromFullName( - number: event.payload['pull_request']['number'], - fullName: event.repoFullName, + screenBuilder: (_) => IssueScreen( + owner: owner, + name: name, + number: number, isPullRequest: true, ), + actionItems: [ + ActionItem.user(event.actorLogin), + ActionItem.user(owner), + ActionItem.repository(owner, name), + ActionItem.issue(owner, name, number), + ], ); case 'PullRequestReviewEvent': // TODO: return defaultItem; 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( context: context, spans: [ TextSpan(text: ' reviewed pull request '), - _buildIssue(context, - number: event.payload['pull_request']['number'], - isPullRequest: true), + _buildIssue(context, number: number), TextSpan(text: ' at '), _buildRepo(context), ], detail: event.payload['comment']['body'], - screenBuilder: (_) => IssueScreen.fromFullName( - number: event.payload['pull_request']['number'], - fullName: event.repoFullName, + screenBuilder: (_) => IssueScreen( + owner: owner, + name: name, + number: number, isPullRequest: true, ), + actionItems: [ + ActionItem.user(event.actorLogin), + ActionItem.user(owner), + ActionItem.repository(owner, name), + ActionItem.issue(owner, name, number), + ], ); case 'PushEvent': - var ref = event.payload['ref'] as String; - var commits = event.payload['commits'] as List; + final ref = event.payload['ref'] as String; + 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( context: context, @@ -330,6 +406,11 @@ class EventItem extends StatelessWidget { event.payload['before'] + '...' + event.payload['head'], + actionItems: [ + ActionItem.user(event.actorLogin), + ActionItem.user(owner), + ActionItem.repository(owner, name), + ], ); case 'ReleaseEvent': case 'RepositoryEvent': @@ -342,12 +423,21 @@ class EventItem extends StatelessWidget { // TODO: return defaultItem; case 'WatchEvent': + final ls = event.repoFullName.split('/'); + assert(ls.length == 2); + final owner = ls[0]; + final name = ls[1]; + return _buildItem( context: context, spans: [TextSpan(text: ' starred '), _buildRepo(context)], iconData: Octicons.star, - screenBuilder: (_) => - RepositoryScreen.fromFullName(event.repoFullName), + screenBuilder: (_) => RepositoryScreen(owner, name), + actionItems: [ + ActionItem.user(event.actorLogin), + ActionItem.user(owner), + ActionItem.repository(owner, name), + ], ); default: return defaultItem; diff --git a/lib/widgets/link.dart b/lib/widgets/link.dart index f305e7f..c3b0492 100644 --- a/lib/widgets/link.dart +++ b/lib/widgets/link.dart @@ -9,12 +9,14 @@ class Link extends StatelessWidget { final String url; final WidgetBuilder screenBuilder; final Function onTap; + final VoidCallback onLongPress; Link({ this.child, this.url, this.screenBuilder, this.onTap, + this.onLongPress, }) : assert(screenBuilder == null || url == null); void _onTap(BuildContext context) { @@ -39,6 +41,11 @@ class Link extends StatelessWidget { // splashColor: // theme == AppThemeType.cupertino ? Colors.transparent : null, onTap: () => _onTap(context), + onLongPress: () { + if (onLongPress != null) { + onLongPress(); + } + }, ), ), );