refactor: refresh scaffold

This commit is contained in:
Rongjian Zhang 2019-09-30 17:13:12 +08:00
parent 0fce330d70
commit e7b01a0c23
5 changed files with 111 additions and 93 deletions

View File

@ -2,17 +2,28 @@ import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:git_touch/scaffolds/utils.dart';
class RefreshStatefulScaffoldPayload<T> {
bool loading;
String error;
T data;
void Function() refresh;
RefreshStatefulScaffoldPayload(
this.loading, this.error, this.data, this.refresh);
}
class RefreshStatefulScaffold<T> extends StatefulWidget {
final Widget title;
final Widget Function(T payload) bodyBuilder;
final Future<T> Function() onRefresh;
final Widget Function(T payload) trailingBuilder;
final Widget Function(RefreshStatefulScaffoldPayload<T> payload) bodyBuilder;
final Future<T> Function() fetchData;
final Widget Function(RefreshStatefulScaffoldPayload<T> payload)
actionBuilder;
RefreshStatefulScaffold({
@required this.title,
@required this.bodyBuilder,
@required this.onRefresh,
this.trailingBuilder,
@required this.fetchData,
this.actionBuilder,
});
@override
@ -23,9 +34,12 @@ class RefreshStatefulScaffold<T> extends StatefulWidget {
class _RefreshStatefulScaffoldState<T>
extends State<RefreshStatefulScaffold<T>> {
bool _loading;
T _payload;
T _data;
String _error = '';
RefreshStatefulScaffoldPayload get _payload =>
RefreshStatefulScaffoldPayload(_loading, _error, _data, _refresh);
@override
void initState() {
super.initState();
@ -38,7 +52,7 @@ class _RefreshStatefulScaffoldState<T>
_error = '';
_loading = true;
});
_payload = await widget.onRefresh();
_data = await widget.fetchData();
} catch (err) {
_error = err.toString();
throw err;
@ -52,8 +66,8 @@ class _RefreshStatefulScaffoldState<T>
}
Widget get _trailing {
if (widget.trailingBuilder == null) return null;
return widget.trailingBuilder(_payload);
if (widget.actionBuilder == null) return null;
return widget.actionBuilder(_payload);
}
@override
@ -65,7 +79,7 @@ class _RefreshStatefulScaffoldState<T>
body: ErrorLoadingWrapper(
bodyBuilder: () => widget.bodyBuilder(_payload),
error: _error,
loading: _payload == null,
loading: _data == null,
reload: _refresh,
),
),

View File

@ -146,7 +146,7 @@ class ObjectScreen extends StatelessWidget {
Widget build(BuildContext context) {
return RefreshStatefulScaffold(
title: AppBarTitle(paths.join('/')),
onRefresh: () async {
fetchData: () async {
var data = await Provider.of<AuthModel>(context).query('''{
repository(owner: "$owner", name: "$name") {
object(expression: "$_expression") {
@ -170,15 +170,15 @@ class ObjectScreen extends StatelessWidget {
return data['repository']['object'];
},
trailingBuilder: (payload) {
actionBuilder: (payload) {
switch (type) {
case 'blob':
return ActionEntry(
iconData: Octicons.settings,
onTap: () {
if (payload != null) {
if (payload.data != null) {
Provider.of<ThemeModel>(context).pushRoute(context,
(_) => CodeThemeScreen(payload['text'], _language));
(_) => CodeThemeScreen(payload.data['text'], _language));
}
},
);

View File

@ -53,7 +53,7 @@ class OrganizationScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RefreshStatefulScaffold(
onRefresh: () async {
fetchData: () async {
// Use pinnableItems instead of organization here due to token permission
var data = await Provider.of<AuthModel>(context).query('''
{
@ -89,37 +89,39 @@ class OrganizationScreen extends StatelessWidget {
return data['organization'];
},
title: AppBarTitle('Organization'),
trailingBuilder: (payload) {
actionBuilder: (payload) {
return ActionButton(
title: 'Organization Actions',
items: [
if (payload != null) ...[
ActionItem.share(payload['url']),
ActionItem.launch(payload['url']),
if (payload.data != null) ...[
ActionItem.share(payload.data['url']),
ActionItem.launch(payload.data['url']),
],
],
);
},
bodyBuilder: (payload) {
var data = payload.data;
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
UserItem(
login: login,
name: payload['name'],
avatarUrl: payload['avatarUrl'],
bio: payload['description'],
name: data['name'],
avatarUrl: data['avatarUrl'],
bio: data['description'],
),
borderView,
Row(children: <Widget>[
EntryItem(
count: payload['pinnableItems']['totalCount'],
count: data['pinnableItems']['totalCount'],
text: 'Repositories',
screenBuilder: (context) =>
RepositoriesScreen.ofOrganization(login),
),
EntryItem(
count: payload['membersWithRole']['totalCount'],
count: data['membersWithRole']['totalCount'],
text: 'Members',
screenBuilder: (context) => UsersScreen.members(login),
),
@ -128,30 +130,30 @@ class OrganizationScreen extends StatelessWidget {
TableView(
hasIcon: true,
items: [
if (isNotNullOrEmpty(payload['location']))
if (isNotNullOrEmpty(data['location']))
TableViewItem(
leftIconData: Octicons.location,
text: Text(payload['location']),
text: Text(data['location']),
onTap: () {
launchUrl('https://www.google.com/maps/place/' +
(payload['location'] as String)
(data['location'] as String)
.replaceAll(RegExp(r'\s+'), ''));
},
),
if (isNotNullOrEmpty(payload['email']))
if (isNotNullOrEmpty(data['email']))
TableViewItem(
leftIconData: Octicons.mail,
text: Text(payload['email']),
text: Text(data['email']),
onTap: () {
launchUrl('mailto:' + payload['email']);
launchUrl('mailto:' + data['email']);
},
),
if (isNotNullOrEmpty(payload['websiteUrl']))
if (isNotNullOrEmpty(data['websiteUrl']))
TableViewItem(
leftIconData: Octicons.link,
text: Text(payload['websiteUrl']),
text: Text(data['websiteUrl']),
onTap: () {
var url = payload['websiteUrl'] as String;
var url = data['websiteUrl'] as String;
if (!url.startsWith('http')) {
url = 'http://$url';
}
@ -160,7 +162,7 @@ class OrganizationScreen extends StatelessWidget {
),
],
),
..._buildRepos(payload),
..._buildRepos(data),
],
);
},

View File

@ -141,11 +141,12 @@ class RepositoryScreen extends StatelessWidget {
Widget build(BuildContext context) {
return RefreshStatefulScaffold(
title: AppBarTitle('Repository'),
onRefresh: () => Future.wait([
fetchData: () => Future.wait([
queryRepo(context),
fetchReadme(context),
]),
trailingBuilder: (data) {
actionBuilder: (payload) {
var data = payload.data;
return ActionButton(
title: 'Repository Actions',
items: [
@ -206,41 +207,41 @@ class RepositoryScreen extends StatelessWidget {
],
);
},
bodyBuilder: (data) {
var payload = data[0];
var readme = data[1] as String;
bodyBuilder: (payload) {
var data = payload.data[0];
var readme = payload.data[1] as String;
final langWidth = MediaQuery.of(context).size.width -
_languageBarPadding * 2 -
(payload['languages']['edges'] as List).length +
(data['languages']['edges'] as List).length +
1;
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
RepositoryItem(payload, inRepoScreen: true),
RepositoryItem(data, inRepoScreen: true),
borderView,
Row(
children: <Widget>[
EntryItem(
count: payload['watchers']['totalCount'],
count: data['watchers']['totalCount'],
text: 'Watchers',
screenBuilder: (context) => UsersScreen.watchers(owner, name),
),
EntryItem(
count: payload['stargazers']['totalCount'],
count: data['stargazers']['totalCount'],
text: 'Stars',
screenBuilder: (context) => UsersScreen.stars(owner, name),
),
EntryItem(
count: payload['forks']['totalCount'],
count: data['forks']['totalCount'],
text: 'Forks',
// screenBuilder: (context) => UsersScreen(),
),
],
),
borderView1,
if ((payload['languages']['edges'] as List).isNotEmpty)
if ((data['languages']['edges'] as List).isNotEmpty)
Container(
padding: const EdgeInsets.all(_languageBarPadding),
child: ClipRRect(
@ -250,12 +251,12 @@ class RepositoryScreen extends StatelessWidget {
child: Row(
children: join(
SizedBox(width: 1),
(payload['languages']['edges'] as List)
(data['languages']['edges'] as List)
.map((lang) => Container(
color: convertColor(lang['node']['color']),
width: langWidth *
lang['size'] /
payload['languages']['totalSize']))
data['languages']['totalSize']))
.toList())),
),
),
@ -263,41 +264,41 @@ class RepositoryScreen extends StatelessWidget {
TableView(
hasIcon: true,
items: [
if (payload[branchInfoKey] != null)
if (data[branchInfoKey] != null)
TableViewItem(
leftIconData: Octicons.code,
text: Text('Code'),
rightWidget:
Text(filesize((payload['diskUsage'] as int) * 1000)),
Text(filesize((data['diskUsage'] as int) * 1000)),
screenBuilder: (_) => ObjectScreen(
owner: owner,
name: name,
branch: payload[branchInfoKey]['name'],
branch: data[branchInfoKey]['name'],
),
),
if (payload['hasIssuesEnabled'] as bool)
if (data['hasIssuesEnabled'] as bool)
TableViewItem(
leftIconData: Octicons.issue_opened,
text: Text('Issues'),
rightWidget: Text(
numberFormat.format(payload['issues']['totalCount'])),
rightWidget:
Text(numberFormat.format(data['issues']['totalCount'])),
screenBuilder: (_) =>
IssuesScreen(owner: owner, name: name),
),
TableViewItem(
leftIconData: Octicons.git_pull_request,
text: Text('Pull requests'),
rightWidget: Text(numberFormat
.format(payload['pullRequests']['totalCount'])),
rightWidget: Text(
numberFormat.format(data['pullRequests']['totalCount'])),
screenBuilder: (_) => IssuesScreen(
owner: owner, name: name, isPullRequest: true),
),
TableViewItem(
leftIconData: Octicons.project,
text: Text('Projects'),
rightWidget: Text(
numberFormat.format(payload['projects']['totalCount'])),
url: 'https://github.com' + payload['projectsResourcePath'],
rightWidget:
Text(numberFormat.format(data['projects']['totalCount'])),
url: 'https://github.com' + data['projectsResourcePath'],
),
],
),
@ -305,30 +306,30 @@ class RepositoryScreen extends StatelessWidget {
TableView(
hasIcon: true,
items: [
if (payload[branchInfoKey] != null) ...[
if (data[branchInfoKey] != null) ...[
TableViewItem(
leftIconData: Octicons.history,
text: Text('Commits'),
rightWidget: Text(numberFormat.format(payload[branchInfoKey]
rightWidget: Text(numberFormat.format(data[branchInfoKey]
['target']['history']['totalCount'])),
screenBuilder: (_) =>
CommitsScreen(owner, name, branch: branch),
),
if (payload['refs'] != null)
if (data['refs'] != null)
TableViewItem(
leftIconData: Octicons.git_branch,
text: Text('Branches'),
rightWidget: Text(payload[branchInfoKey]['name'] +
rightWidget: Text(data[branchInfoKey]['name'] +
'' +
numberFormat.format(payload['refs']['totalCount'])),
numberFormat.format(data['refs']['totalCount'])),
onTap: () async {
var refs = payload['refs']['nodes'] as List;
var refs = data['refs']['nodes'] as List;
if (refs.length < 2) return;
await Provider.of<ThemeModel>(context).showPicker(
context,
PickerGroupItem(
value: payload[branchInfoKey]['name'],
value: data[branchInfoKey]['name'],
items: refs
.map((b) => PickerItem(b['name'] as String,
text: (b['name'] as String)))
@ -349,17 +350,17 @@ class RepositoryScreen extends StatelessWidget {
TableViewItem(
leftIconData: Octicons.tag,
text: Text('Releases'),
rightWidget: Text(
(payload['releases']['totalCount'] as int).toString()),
url: payload['url'] + '/releases',
rightWidget:
Text((data['releases']['totalCount'] as int).toString()),
url: data['url'] + '/releases',
),
TableViewItem(
leftIconData: Octicons.law,
text: Text('License'),
rightWidget: Text(payload['licenseInfo'] == null
rightWidget: Text(data['licenseInfo'] == null
? 'Unknown'
: (payload['licenseInfo']['spdxId'] ??
payload['licenseInfo']['name'])),
: (data['licenseInfo']['spdxId'] ??
data['licenseInfo']['name'])),
),
],
),

View File

@ -141,13 +141,14 @@ class UserScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RefreshStatefulScaffold(
onRefresh: () {
fetchData: () {
return Future.wait(
[query(context), getContributions(login)],
);
},
title: AppBarTitle('User'),
trailingBuilder: (data) {
actionBuilder: (payload) {
var data = payload.data;
if (isMe) {
return ActionEntry(
iconData: Icons.settings,
@ -184,33 +185,33 @@ class UserScreen extends StatelessWidget {
);
}
},
bodyBuilder: (data) {
var payload = data[0];
var contributions = data[1] as List<ContributionsInfo>;
bodyBuilder: (payload) {
var data = payload.data[0];
var contributions = payload.data[1] as List<ContributionsInfo>;
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
UserItem.fromData(payload, inUserScreen: true),
UserItem.fromData(data, inUserScreen: true),
borderView,
Row(children: <Widget>[
EntryItem(
count: payload['repositories']['totalCount'],
count: data['repositories']['totalCount'],
text: 'Repositories',
screenBuilder: (context) => RepositoriesScreen(login),
),
EntryItem(
count: payload['starredRepositories']['totalCount'],
count: data['starredRepositories']['totalCount'],
text: 'Stars',
screenBuilder: (context) => RepositoriesScreen.stars(login),
),
EntryItem(
count: payload['followers']['totalCount'],
count: data['followers']['totalCount'],
text: 'Followers',
screenBuilder: (context) => UsersScreen.followers(login),
),
EntryItem(
count: payload['following']['totalCount'],
count: data['following']['totalCount'],
text: 'Following',
screenBuilder: (context) => UsersScreen.following(login),
),
@ -224,38 +225,38 @@ class UserScreen extends StatelessWidget {
TableView(
hasIcon: true,
items: [
if (isNotNullOrEmpty(payload['company']))
if (isNotNullOrEmpty(data['company']))
TableViewItem(
leftIconData: Octicons.organization,
text: TextContainsOrganization(payload['company'],
text: TextContainsOrganization(data['company'],
style: TextStyle(
fontSize: 16, color: PrimerColors.gray900),
overflow: TextOverflow.ellipsis),
),
if (isNotNullOrEmpty(payload['location']))
if (isNotNullOrEmpty(data['location']))
TableViewItem(
leftIconData: Octicons.location,
text: Text(payload['location']),
text: Text(data['location']),
onTap: () {
launchUrl('https://www.google.com/maps/place/' +
(payload['location'] as String)
(data['location'] as String)
.replaceAll(RegExp(r'\s+'), ''));
},
),
if (isNotNullOrEmpty(payload['email']))
if (isNotNullOrEmpty(data['email']))
TableViewItem(
leftIconData: Octicons.mail,
text: Text(payload['email']),
text: Text(data['email']),
onTap: () {
launchUrl('mailto:' + payload['email']);
launchUrl('mailto:' + data['email']);
},
),
if (isNotNullOrEmpty(payload['websiteUrl']))
if (isNotNullOrEmpty(data['websiteUrl']))
TableViewItem(
leftIconData: Octicons.link,
text: Text(payload['websiteUrl']),
text: Text(data['websiteUrl']),
onTap: () {
var url = payload['websiteUrl'] as String;
var url = data['websiteUrl'] as String;
if (!url.startsWith('http')) {
url = 'http://$url';
}
@ -264,7 +265,7 @@ class UserScreen extends StatelessWidget {
),
],
),
..._buildRepos(payload),
..._buildRepos(data),
borderView1,
],
);