diff --git a/lib/scaffolds/long_list.dart b/lib/scaffolds/long_list.dart index 4c809e9..1d6181a 100644 --- a/lib/scaffolds/long_list.dart +++ b/lib/scaffolds/long_list.dart @@ -28,7 +28,6 @@ class LongListPayload { // e.g. https://github.com/reactjs/rfcs/pull/68 class LongListScaffold extends StatefulWidget { final Widget title; - final List Function(T headerPayload) actionsBuilder; final Widget Function(T headerPayload) trailingBuilder; final Widget Function(T headerPayload) headerBuilder; final Widget Function(K itemPayload) itemBuilder; @@ -37,7 +36,6 @@ class LongListScaffold extends StatefulWidget { LongListScaffold({ @required this.title, - this.actionsBuilder, this.trailingBuilder, @required this.headerBuilder, @required this.itemBuilder, @@ -217,8 +215,9 @@ class _LongListScaffoldState extends State> { return Scaffold( appBar: AppBar( title: widget.title, - actions: - payload == null ? null : widget.actionsBuilder(payload.header), + actions: payload == null + ? null + : [widget.trailingBuilder(payload.header)], ), body: RefreshIndicator( onRefresh: widget.onRefresh, diff --git a/lib/scaffolds/refresh.dart b/lib/scaffolds/refresh.dart index 362f865..f24e7e6 100644 --- a/lib/scaffolds/refresh.dart +++ b/lib/scaffolds/refresh.dart @@ -4,7 +4,6 @@ import 'package:flutter/widgets.dart'; import '../providers/settings.dart'; import '../widgets/loading.dart'; import '../widgets/error_reload.dart'; -import '../utils/utils.dart'; // This is a scaffold for pull to refresh class RefreshScaffold extends StatefulWidget { @@ -12,7 +11,7 @@ class RefreshScaffold extends StatefulWidget { final Widget Function(T payload) bodyBuilder; final Future Function() onRefresh; final Widget Function(T payload) trailingBuilder; - final List Function(T payload) actionsBuilder; + // final List Function(T payload) actionsBuilder; final PreferredSizeWidget bottom; RefreshScaffold({ @@ -20,7 +19,7 @@ class RefreshScaffold extends StatefulWidget { @required this.bodyBuilder, @required this.onRefresh, this.trailingBuilder, - this.actionsBuilder, + // this.actionsBuilder, this.bottom, }); @@ -67,6 +66,18 @@ class _RefreshScaffoldState extends State> { } } + Widget _buildTrailing() { + if (payload == null) return null; + + return widget.trailingBuilder(payload); + } + + List _buildActions() { + if (payload == null) return null; + + return [widget.trailingBuilder(payload)]; + } + @override Widget build(BuildContext context) { switch (SettingsProvider.of(context).theme) { @@ -74,7 +85,7 @@ class _RefreshScaffoldState extends State> { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( middle: widget.title, - trailing: widget.trailingBuilder(payload), + trailing: _buildTrailing(), ), child: SafeArea( child: CustomScrollView( @@ -89,7 +100,7 @@ class _RefreshScaffoldState extends State> { return Scaffold( appBar: AppBar( title: widget.title, - actions: widget.actionsBuilder(payload), + actions: _buildActions(), bottom: widget.bottom, ), body: RefreshIndicator( diff --git a/lib/screens/issue.dart b/lib/screens/issue.dart index 9060125..05612f8 100644 --- a/lib/screens/issue.dart +++ b/lib/screens/issue.dart @@ -7,7 +7,7 @@ import '../scaffolds/long_list.dart'; import '../widgets/timeline_item.dart'; import '../widgets/comment_item.dart'; import '../providers/settings.dart'; -import '../widgets/link.dart'; +import '../widgets/action.dart'; /// Screen for issue and pull request class IssueScreen extends StatefulWidget { @@ -333,48 +333,29 @@ __typename ); } - Future _openActions(payload) async { - if (payload == null) return; - - showActions( - context, - title: (isPullRequest ? 'Pull Request' : 'Issue') + ' Actions', - actions: [ - Action( - text: 'Share', - onPress: () { - Share.share(payload['url']); - }, - ), - Action( - text: 'Open in Browser', - onPress: () { - launch(payload['url']); - }, - ), - ], - ); - } - @override Widget build(BuildContext context) { return LongListScaffold( title: Text('$owner/$name #$number'), trailingBuilder: (payload) { - return Link( - child: Icon(Icons.more_vert, size: 24), - material: false, - beforeRedirect: () => _openActions(payload), + return ActionButton( + title: (isPullRequest ? 'Pull Request' : 'Issue') + ' Actions', + actions: [ + Action( + text: 'Share', + onPress: () { + Share.share(payload['url']); + }, + ), + Action( + text: 'Open in Browser', + onPress: () { + launch(payload['url']); + }, + ), + ], ); }, - actionsBuilder: (payload) { - return [ - Link( - iconButton: Icon(Icons.more_vert), - beforeRedirect: () => _openActions(payload), - ), - ]; - }, headerBuilder: (payload) { return Column(children: [ Container( diff --git a/lib/screens/repo.dart b/lib/screens/repo.dart index d07a3c0..3c54802 100644 --- a/lib/screens/repo.dart +++ b/lib/screens/repo.dart @@ -9,8 +9,7 @@ import '../scaffolds/refresh.dart'; import '../widgets/repo_item.dart'; import '../widgets/entry_item.dart'; import '../screens/issues.dart'; -import '../widgets/link.dart'; -import '../utils/utils.dart'; +import '../widgets/action.dart'; class RepoScreen extends StatefulWidget { final String owner; @@ -82,59 +81,42 @@ class _RepoScreenState extends State { return str; } - Future _openActions(data) async { - if (data == null) return; - var payload = data[0]; - - showActions(context, title: 'Repository Actions', actions: [ - Action( - text: payload['viewerHasStarred'] ? 'Unstar' : 'Star', - onPress: () async { - if (payload['viewerHasStarred']) { - await SettingsProvider.of(context) - .deleteWithCredentials('/user/starred/$owner/$name'); - payload['viewerHasStarred'] = false; - } else { - SettingsProvider.of(context) - .putWithCredentials('/user/starred/$owner/$name'); - payload['viewerHasStarred'] = true; - } - }, - ), - // TODO: watch - Action( - text: 'Share', - onPress: () { - Share.share(payload['url']); - }, - ), - Action( - text: 'Open in Browser', - onPress: () { - launch(payload['url']); - }, - ), - ]); - } - @override Widget build(BuildContext context) { return RefreshScaffold( title: Text(widget.owner + '/' + widget.name), - trailingBuilder: (payload) { - return Link( - child: Icon(Icons.more_vert, size: 24), - material: false, - beforeRedirect: () => _openActions(payload), - ); - }, - actionsBuilder: (payload) { - return [ - Link( - iconButton: Icon(Icons.more_vert), - beforeRedirect: () => _openActions(payload), + trailingBuilder: (data) { + var payload = data[0]; + + return ActionButton(title: 'Repository Actions', actions: [ + Action( + text: payload['viewerHasStarred'] ? 'Unstar' : 'Star', + onPress: () async { + if (payload['viewerHasStarred']) { + await SettingsProvider.of(context) + .deleteWithCredentials('/user/starred/$owner/$name'); + payload['viewerHasStarred'] = false; + } else { + SettingsProvider.of(context) + .putWithCredentials('/user/starred/$owner/$name'); + payload['viewerHasStarred'] = true; + } + }, ), - ]; + // TODO: watch + Action( + text: 'Share', + onPress: () { + Share.share(payload['url']); + }, + ), + Action( + text: 'Open in Browser', + onPress: () { + launch(payload['url']); + }, + ), + ]); }, onRefresh: () => Future.wait([ queryRepo(context), diff --git a/lib/screens/user.dart b/lib/screens/user.dart index 177c699..7b8b38d 100644 --- a/lib/screens/user.dart +++ b/lib/screens/user.dart @@ -9,6 +9,7 @@ import '../widgets/entry_item.dart'; import '../widgets/list_group.dart'; import '../widgets/repo_item.dart'; import '../widgets/link.dart'; +import '../widgets/action.dart'; import '../screens/repos.dart'; import '../screens/users.dart'; import '../screens/settings.dart'; @@ -135,46 +136,6 @@ class _UserScreenState extends State { return Container(); } - Future _openActions(payload) async { - if (payload == null) return; - - List actions = []; - - if (payload['viewerCanFollow']) { - actions.add(Action( - text: payload['viewerIsFollowing'] ? 'Unfollow' : 'Follow', - onPress: () async { - if (payload['viewerIsFollowing']) { - await SettingsProvider.of(context) - .deleteWithCredentials('/user/following/${widget.login}'); - payload['viewerIsFollowing'] = false; - } else { - SettingsProvider.of(context) - .putWithCredentials('/user/following/${widget.login}'); - payload['viewerIsFollowing'] = true; - } - }, - )); - } - - actions.addAll([ - Action( - text: 'Share', - onPress: () { - Share.share(payload['url']); - }, - ), - Action( - text: 'Open in Browser', - onPress: () { - launch(payload['url']); - }, - ), - ]); - - showActions(context, title: 'User Actions', actions: actions); - } - @override Widget build(BuildContext context) { return RefreshScaffold( @@ -189,30 +150,41 @@ class _UserScreenState extends State { fullscreenDialog: true, ); } else { - return Link( - child: Icon(Icons.more_vert, size: 24), - material: false, - beforeRedirect: () => _openActions(payload), - ); - } - }, - actionsBuilder: (payload) { - if (widget.showSettings) { - return [ - Link( - iconButton: Icon(Icons.settings), - screenBuilder: (_) => SettingsScreen(), - fullscreenDialog: true, - ) - ]; - } else { - return [ - Link( - iconButton: Icon(Icons.more_vert), - material: false, - beforeRedirect: () => _openActions(payload), - ) - ]; + List actions = []; + + if (payload['viewerCanFollow']) { + actions.add(Action( + text: payload['viewerIsFollowing'] ? 'Unfollow' : 'Follow', + onPress: () async { + if (payload['viewerIsFollowing']) { + await SettingsProvider.of(context) + .deleteWithCredentials('/user/following/${widget.login}'); + payload['viewerIsFollowing'] = false; + } else { + SettingsProvider.of(context) + .putWithCredentials('/user/following/${widget.login}'); + payload['viewerIsFollowing'] = true; + } + }, + )); + } + + actions.addAll([ + Action( + text: 'Share', + onPress: () { + Share.share(payload['url']); + }, + ), + Action( + text: 'Open in Browser', + onPress: () { + launch(payload['url']); + }, + ), + ]); + + return ActionButton(title: 'User Actions', actions: actions); } }, bodyBuilder: (payload) { diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index bbb14ca..f3acdc0 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -118,54 +118,6 @@ Future showOptions(BuildContext context, List> options) { } } -class Action { - String text; - Function onPress; - - Action({@required this.text, @required this.onPress}); -} - -Future showActions( - BuildContext context, { - @required String title, - @required List actions, -}) async { - int result; - - switch (SettingsProvider.of(context).theme) { - case ThemeMap.cupertino: - result = await showCupertinoModalPopup( - context: context, - builder: (BuildContext context) { - return CupertinoActionSheet( - title: Text(title), - actions: actions.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); - }, - ), - ); - }, - ); - break; - default: - } - - if (result != null) { - actions[result].onPress(); - } -} - TextSpan createLinkSpan(BuildContext context, String text, Function handle) { return TextSpan( text: text, diff --git a/lib/widgets/action.dart b/lib/widgets/action.dart new file mode 100644 index 0000000..eb368f7 --- /dev/null +++ b/lib/widgets/action.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import '../providers/settings.dart'; + +class Action { + String text; + Function onPress; + + Action({@required this.text, @required this.onPress}); +} + +class ActionButton extends StatelessWidget { + final String title; + final List actions; + final IconData iconData; + + ActionButton({ + @required this.title, + @required this.actions, + this.iconData = Icons.more_vert, + }); + + void _onSelected(int value) { + if (value != null) { + actions[value].onPress(); + } + } + + @override + Widget build(BuildContext context) { + switch (SettingsProvider.of(context).theme) { + case ThemeMap.cupertino: + return GestureDetector( + child: Icon(iconData, size: 24), + onTap: () async { + int value = await showCupertinoModalPopup( + context: context, + builder: (BuildContext context) { + return CupertinoActionSheet( + title: Text(title), + actions: actions.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); + }, + ), + ); + }, + ); + + _onSelected(value); + }, + ); + default: + return PopupMenuButton( + icon: Icon(iconData), + onSelected: _onSelected, + itemBuilder: (BuildContext context) { + return actions.asMap().entries.map((entry) { + return PopupMenuItem( + value: entry.key, + child: Text(entry.value.text), + ); + }).toList(); + }, + ); + } + } +}