refactor: extract action button widget
This commit is contained in:
parent
eac2cc5470
commit
0396ac7f7b
|
@ -28,7 +28,6 @@ class LongListPayload<T, K> {
|
|||
// e.g. https://github.com/reactjs/rfcs/pull/68
|
||||
class LongListScaffold<T, K> extends StatefulWidget {
|
||||
final Widget title;
|
||||
final List<Widget> 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<T, K> extends StatefulWidget {
|
|||
|
||||
LongListScaffold({
|
||||
@required this.title,
|
||||
this.actionsBuilder,
|
||||
this.trailingBuilder,
|
||||
@required this.headerBuilder,
|
||||
@required this.itemBuilder,
|
||||
|
@ -217,8 +215,9 @@ class _LongListScaffoldState<T, K> extends State<LongListScaffold<T, K>> {
|
|||
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,
|
||||
|
|
|
@ -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<T> extends StatefulWidget {
|
||||
|
@ -12,7 +11,7 @@ class RefreshScaffold<T> extends StatefulWidget {
|
|||
final Widget Function(T payload) bodyBuilder;
|
||||
final Future<T> Function() onRefresh;
|
||||
final Widget Function(T payload) trailingBuilder;
|
||||
final List<Widget> Function(T payload) actionsBuilder;
|
||||
// final List<Widget> Function(T payload) actionsBuilder;
|
||||
final PreferredSizeWidget bottom;
|
||||
|
||||
RefreshScaffold({
|
||||
|
@ -20,7 +19,7 @@ class RefreshScaffold<T> extends StatefulWidget {
|
|||
@required this.bodyBuilder,
|
||||
@required this.onRefresh,
|
||||
this.trailingBuilder,
|
||||
this.actionsBuilder,
|
||||
// this.actionsBuilder,
|
||||
this.bottom,
|
||||
});
|
||||
|
||||
|
@ -67,6 +66,18 @@ class _RefreshScaffoldState<T> extends State<RefreshScaffold<T>> {
|
|||
}
|
||||
}
|
||||
|
||||
Widget _buildTrailing() {
|
||||
if (payload == null) return null;
|
||||
|
||||
return widget.trailingBuilder(payload);
|
||||
}
|
||||
|
||||
List<Widget> _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<T> extends State<RefreshScaffold<T>> {
|
|||
return CupertinoPageScaffold(
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
middle: widget.title,
|
||||
trailing: widget.trailingBuilder(payload),
|
||||
trailing: _buildTrailing(),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: CustomScrollView(
|
||||
|
@ -89,7 +100,7 @@ class _RefreshScaffoldState<T> extends State<RefreshScaffold<T>> {
|
|||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: widget.title,
|
||||
actions: widget.actionsBuilder(payload),
|
||||
actions: _buildActions(),
|
||||
bottom: widget.bottom,
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
|
|
|
@ -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<void> _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: <Widget>[
|
||||
Container(
|
||||
|
|
|
@ -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<RepoScreen> {
|
|||
return str;
|
||||
}
|
||||
|
||||
Future<void> _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),
|
||||
|
|
|
@ -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<UserScreen> {
|
|||
return Container();
|
||||
}
|
||||
|
||||
Future<void> _openActions(payload) async {
|
||||
if (payload == null) return;
|
||||
|
||||
List<Action> 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<UserScreen> {
|
|||
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<Action> 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) {
|
||||
|
|
|
@ -118,54 +118,6 @@ Future<T> showOptions<T>(BuildContext context, List<DialogOption<T>> options) {
|
|||
}
|
||||
}
|
||||
|
||||
class Action {
|
||||
String text;
|
||||
Function onPress;
|
||||
|
||||
Action({@required this.text, @required this.onPress});
|
||||
}
|
||||
|
||||
Future showActions(
|
||||
BuildContext context, {
|
||||
@required String title,
|
||||
@required List<Action> actions,
|
||||
}) async {
|
||||
int result;
|
||||
|
||||
switch (SettingsProvider.of(context).theme) {
|
||||
case ThemeMap.cupertino:
|
||||
result = await showCupertinoModalPopup<int>(
|
||||
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,
|
||||
|
|
|
@ -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<Action> 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<int>(
|
||||
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<int>(
|
||||
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();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue