refactor: extract action button widget

This commit is contained in:
Rongjian Zhang 2019-02-20 16:31:22 +08:00
parent eac2cc5470
commit 0396ac7f7b
7 changed files with 182 additions and 207 deletions

View File

@ -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,

View File

@ -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(

View File

@ -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>[

View File

@ -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),

View File

@ -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) {

View File

@ -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,

78
lib/widgets/action.dart Normal file
View File

@ -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();
},
);
}
}
}