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
|
// e.g. https://github.com/reactjs/rfcs/pull/68
|
||||||
class LongListScaffold<T, K> extends StatefulWidget {
|
class LongListScaffold<T, K> extends StatefulWidget {
|
||||||
final Widget title;
|
final Widget title;
|
||||||
final List<Widget> Function(T headerPayload) actionsBuilder;
|
|
||||||
final Widget Function(T headerPayload) trailingBuilder;
|
final Widget Function(T headerPayload) trailingBuilder;
|
||||||
final Widget Function(T headerPayload) headerBuilder;
|
final Widget Function(T headerPayload) headerBuilder;
|
||||||
final Widget Function(K itemPayload) itemBuilder;
|
final Widget Function(K itemPayload) itemBuilder;
|
||||||
|
@ -37,7 +36,6 @@ class LongListScaffold<T, K> extends StatefulWidget {
|
||||||
|
|
||||||
LongListScaffold({
|
LongListScaffold({
|
||||||
@required this.title,
|
@required this.title,
|
||||||
this.actionsBuilder,
|
|
||||||
this.trailingBuilder,
|
this.trailingBuilder,
|
||||||
@required this.headerBuilder,
|
@required this.headerBuilder,
|
||||||
@required this.itemBuilder,
|
@required this.itemBuilder,
|
||||||
|
@ -217,8 +215,9 @@ class _LongListScaffoldState<T, K> extends State<LongListScaffold<T, K>> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: widget.title,
|
title: widget.title,
|
||||||
actions:
|
actions: payload == null
|
||||||
payload == null ? null : widget.actionsBuilder(payload.header),
|
? null
|
||||||
|
: [widget.trailingBuilder(payload.header)],
|
||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: widget.onRefresh,
|
onRefresh: widget.onRefresh,
|
||||||
|
|
|
@ -4,7 +4,6 @@ import 'package:flutter/widgets.dart';
|
||||||
import '../providers/settings.dart';
|
import '../providers/settings.dart';
|
||||||
import '../widgets/loading.dart';
|
import '../widgets/loading.dart';
|
||||||
import '../widgets/error_reload.dart';
|
import '../widgets/error_reload.dart';
|
||||||
import '../utils/utils.dart';
|
|
||||||
|
|
||||||
// This is a scaffold for pull to refresh
|
// This is a scaffold for pull to refresh
|
||||||
class RefreshScaffold<T> extends StatefulWidget {
|
class RefreshScaffold<T> extends StatefulWidget {
|
||||||
|
@ -12,7 +11,7 @@ class RefreshScaffold<T> extends StatefulWidget {
|
||||||
final Widget Function(T payload) bodyBuilder;
|
final Widget Function(T payload) bodyBuilder;
|
||||||
final Future<T> Function() onRefresh;
|
final Future<T> Function() onRefresh;
|
||||||
final Widget Function(T payload) trailingBuilder;
|
final Widget Function(T payload) trailingBuilder;
|
||||||
final List<Widget> Function(T payload) actionsBuilder;
|
// final List<Widget> Function(T payload) actionsBuilder;
|
||||||
final PreferredSizeWidget bottom;
|
final PreferredSizeWidget bottom;
|
||||||
|
|
||||||
RefreshScaffold({
|
RefreshScaffold({
|
||||||
|
@ -20,7 +19,7 @@ class RefreshScaffold<T> extends StatefulWidget {
|
||||||
@required this.bodyBuilder,
|
@required this.bodyBuilder,
|
||||||
@required this.onRefresh,
|
@required this.onRefresh,
|
||||||
this.trailingBuilder,
|
this.trailingBuilder,
|
||||||
this.actionsBuilder,
|
// this.actionsBuilder,
|
||||||
this.bottom,
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
switch (SettingsProvider.of(context).theme) {
|
switch (SettingsProvider.of(context).theme) {
|
||||||
|
@ -74,7 +85,7 @@ class _RefreshScaffoldState<T> extends State<RefreshScaffold<T>> {
|
||||||
return CupertinoPageScaffold(
|
return CupertinoPageScaffold(
|
||||||
navigationBar: CupertinoNavigationBar(
|
navigationBar: CupertinoNavigationBar(
|
||||||
middle: widget.title,
|
middle: widget.title,
|
||||||
trailing: widget.trailingBuilder(payload),
|
trailing: _buildTrailing(),
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
|
@ -89,7 +100,7 @@ class _RefreshScaffoldState<T> extends State<RefreshScaffold<T>> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: widget.title,
|
title: widget.title,
|
||||||
actions: widget.actionsBuilder(payload),
|
actions: _buildActions(),
|
||||||
bottom: widget.bottom,
|
bottom: widget.bottom,
|
||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
|
|
|
@ -7,7 +7,7 @@ import '../scaffolds/long_list.dart';
|
||||||
import '../widgets/timeline_item.dart';
|
import '../widgets/timeline_item.dart';
|
||||||
import '../widgets/comment_item.dart';
|
import '../widgets/comment_item.dart';
|
||||||
import '../providers/settings.dart';
|
import '../providers/settings.dart';
|
||||||
import '../widgets/link.dart';
|
import '../widgets/action.dart';
|
||||||
|
|
||||||
/// Screen for issue and pull request
|
/// Screen for issue and pull request
|
||||||
class IssueScreen extends StatefulWidget {
|
class IssueScreen extends StatefulWidget {
|
||||||
|
@ -333,11 +333,12 @@ __typename
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _openActions(payload) async {
|
@override
|
||||||
if (payload == null) return;
|
Widget build(BuildContext context) {
|
||||||
|
return LongListScaffold(
|
||||||
showActions(
|
title: Text('$owner/$name #$number'),
|
||||||
context,
|
trailingBuilder: (payload) {
|
||||||
|
return ActionButton(
|
||||||
title: (isPullRequest ? 'Pull Request' : 'Issue') + ' Actions',
|
title: (isPullRequest ? 'Pull Request' : 'Issue') + ' Actions',
|
||||||
actions: [
|
actions: [
|
||||||
Action(
|
Action(
|
||||||
|
@ -354,26 +355,6 @@ __typename
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
@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),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
actionsBuilder: (payload) {
|
|
||||||
return [
|
|
||||||
Link(
|
|
||||||
iconButton: Icon(Icons.more_vert),
|
|
||||||
beforeRedirect: () => _openActions(payload),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
headerBuilder: (payload) {
|
headerBuilder: (payload) {
|
||||||
return Column(children: <Widget>[
|
return Column(children: <Widget>[
|
||||||
|
|
|
@ -9,8 +9,7 @@ import '../scaffolds/refresh.dart';
|
||||||
import '../widgets/repo_item.dart';
|
import '../widgets/repo_item.dart';
|
||||||
import '../widgets/entry_item.dart';
|
import '../widgets/entry_item.dart';
|
||||||
import '../screens/issues.dart';
|
import '../screens/issues.dart';
|
||||||
import '../widgets/link.dart';
|
import '../widgets/action.dart';
|
||||||
import '../utils/utils.dart';
|
|
||||||
|
|
||||||
class RepoScreen extends StatefulWidget {
|
class RepoScreen extends StatefulWidget {
|
||||||
final String owner;
|
final String owner;
|
||||||
|
@ -82,11 +81,14 @@ class _RepoScreenState extends State<RepoScreen> {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _openActions(data) async {
|
@override
|
||||||
if (data == null) return;
|
Widget build(BuildContext context) {
|
||||||
|
return RefreshScaffold(
|
||||||
|
title: Text(widget.owner + '/' + widget.name),
|
||||||
|
trailingBuilder: (data) {
|
||||||
var payload = data[0];
|
var payload = data[0];
|
||||||
|
|
||||||
showActions(context, title: 'Repository Actions', actions: [
|
return ActionButton(title: 'Repository Actions', actions: [
|
||||||
Action(
|
Action(
|
||||||
text: payload['viewerHasStarred'] ? 'Unstar' : 'Star',
|
text: payload['viewerHasStarred'] ? 'Unstar' : 'Star',
|
||||||
onPress: () async {
|
onPress: () async {
|
||||||
|
@ -115,26 +117,6 @@ class _RepoScreenState extends State<RepoScreen> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
}
|
|
||||||
|
|
||||||
@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),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
onRefresh: () => Future.wait([
|
onRefresh: () => Future.wait([
|
||||||
queryRepo(context),
|
queryRepo(context),
|
||||||
|
|
|
@ -9,6 +9,7 @@ import '../widgets/entry_item.dart';
|
||||||
import '../widgets/list_group.dart';
|
import '../widgets/list_group.dart';
|
||||||
import '../widgets/repo_item.dart';
|
import '../widgets/repo_item.dart';
|
||||||
import '../widgets/link.dart';
|
import '../widgets/link.dart';
|
||||||
|
import '../widgets/action.dart';
|
||||||
import '../screens/repos.dart';
|
import '../screens/repos.dart';
|
||||||
import '../screens/users.dart';
|
import '../screens/users.dart';
|
||||||
import '../screens/settings.dart';
|
import '../screens/settings.dart';
|
||||||
|
@ -135,9 +136,20 @@ class _UserScreenState extends State<UserScreen> {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _openActions(payload) async {
|
@override
|
||||||
if (payload == null) return;
|
Widget build(BuildContext context) {
|
||||||
|
return RefreshScaffold(
|
||||||
|
onRefresh: () => queryUser(context),
|
||||||
|
title: Text(widget.login),
|
||||||
|
trailingBuilder: (payload) {
|
||||||
|
if (widget.showSettings) {
|
||||||
|
return Link(
|
||||||
|
child: Icon(Icons.settings, size: 24),
|
||||||
|
screenBuilder: (_) => SettingsScreen(),
|
||||||
|
material: false,
|
||||||
|
fullscreenDialog: true,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
List<Action> actions = [];
|
List<Action> actions = [];
|
||||||
|
|
||||||
if (payload['viewerCanFollow']) {
|
if (payload['viewerCanFollow']) {
|
||||||
|
@ -172,47 +184,7 @@ class _UserScreenState extends State<UserScreen> {
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
showActions(context, title: 'User Actions', actions: actions);
|
return ActionButton(title: 'User Actions', actions: actions);
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return RefreshScaffold(
|
|
||||||
onRefresh: () => queryUser(context),
|
|
||||||
title: Text(widget.login),
|
|
||||||
trailingBuilder: (payload) {
|
|
||||||
if (widget.showSettings) {
|
|
||||||
return Link(
|
|
||||||
child: Icon(Icons.settings, size: 24),
|
|
||||||
screenBuilder: (_) => SettingsScreen(),
|
|
||||||
material: false,
|
|
||||||
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),
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bodyBuilder: (payload) {
|
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) {
|
TextSpan createLinkSpan(BuildContext context, String text, Function handle) {
|
||||||
return TextSpan(
|
return TextSpan(
|
||||||
text: text,
|
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