From e7b01a0c23195a6aa2d19ee3111bd379ef53194b Mon Sep 17 00:00:00 2001 From: Rongjian Zhang Date: Mon, 30 Sep 2019 17:13:12 +0800 Subject: [PATCH] refactor: refresh scaffold --- lib/scaffolds/refresh_stateful.dart | 34 +++++++++---- lib/screens/object.dart | 8 +-- lib/screens/organization.dart | 42 ++++++++-------- lib/screens/repository.dart | 75 +++++++++++++++-------------- lib/screens/user.dart | 45 ++++++++--------- 5 files changed, 111 insertions(+), 93 deletions(-) diff --git a/lib/scaffolds/refresh_stateful.dart b/lib/scaffolds/refresh_stateful.dart index 4e0a914..46edc39 100644 --- a/lib/scaffolds/refresh_stateful.dart +++ b/lib/scaffolds/refresh_stateful.dart @@ -2,17 +2,28 @@ import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:git_touch/scaffolds/utils.dart'; +class RefreshStatefulScaffoldPayload { + bool loading; + String error; + T data; + void Function() refresh; + + RefreshStatefulScaffoldPayload( + this.loading, this.error, this.data, this.refresh); +} + class RefreshStatefulScaffold extends StatefulWidget { final Widget title; - final Widget Function(T payload) bodyBuilder; - final Future Function() onRefresh; - final Widget Function(T payload) trailingBuilder; + final Widget Function(RefreshStatefulScaffoldPayload payload) bodyBuilder; + final Future Function() fetchData; + final Widget Function(RefreshStatefulScaffoldPayload 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 extends StatefulWidget { class _RefreshStatefulScaffoldState extends State> { 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 _error = ''; _loading = true; }); - _payload = await widget.onRefresh(); + _data = await widget.fetchData(); } catch (err) { _error = err.toString(); throw err; @@ -52,8 +66,8 @@ class _RefreshStatefulScaffoldState } 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 body: ErrorLoadingWrapper( bodyBuilder: () => widget.bodyBuilder(_payload), error: _error, - loading: _payload == null, + loading: _data == null, reload: _refresh, ), ), diff --git a/lib/screens/object.dart b/lib/screens/object.dart index 67bc39a..a60d36f 100644 --- a/lib/screens/object.dart +++ b/lib/screens/object.dart @@ -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(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(context).pushRoute(context, - (_) => CodeThemeScreen(payload['text'], _language)); + (_) => CodeThemeScreen(payload.data['text'], _language)); } }, ); diff --git a/lib/screens/organization.dart b/lib/screens/organization.dart index 3b53423..ee195bc 100644 --- a/lib/screens/organization.dart +++ b/lib/screens/organization.dart @@ -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(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: [ UserItem( login: login, - name: payload['name'], - avatarUrl: payload['avatarUrl'], - bio: payload['description'], + name: data['name'], + avatarUrl: data['avatarUrl'], + bio: data['description'], ), borderView, Row(children: [ 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), ], ); }, diff --git a/lib/screens/repository.dart b/lib/screens/repository.dart index 84f94ae..217f0f8 100644 --- a/lib/screens/repository.dart +++ b/lib/screens/repository.dart @@ -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: [ - RepositoryItem(payload, inRepoScreen: true), + RepositoryItem(data, inRepoScreen: true), borderView, Row( children: [ 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(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'])), ), ], ), diff --git a/lib/screens/user.dart b/lib/screens/user.dart index 43fcd4f..4b8f09c 100644 --- a/lib/screens/user.dart +++ b/lib/screens/user.dart @@ -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; + bodyBuilder: (payload) { + var data = payload.data[0]; + var contributions = payload.data[1] as List; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - UserItem.fromData(payload, inUserScreen: true), + UserItem.fromData(data, inUserScreen: true), borderView, Row(children: [ 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, ], );