refactor: extract list scaffold

This commit is contained in:
Rongjian Zhang 2019-02-03 14:42:50 +08:00
parent 9ce9589b2c
commit f7cb6ab068
9 changed files with 343 additions and 345 deletions

View File

@ -1,7 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import '../utils/utils.dart';
import '../widgets/widgets.dart';
import '../widgets/list_scaffold.dart';
import '../widgets/timeline_item.dart';
Future queryIssue(int id, String owner, String name) async {
var data = await query('''
@ -25,23 +26,42 @@ Future queryIssue(int id, String owner, String name) async {
return data['repository']['issue'];
}
class IssueScreen extends StatelessWidget {
class IssueScreen extends StatefulWidget {
final int id;
final String owner;
final String name;
IssueScreen(this.id, this.owner, this.name);
@override
_IssueScreenState createState() => _IssueScreenState();
}
class _IssueScreenState extends State<IssueScreen> {
Map<String, dynamic> payload;
Widget _buildHeader() {
return Text('issue');
}
get _fullName => widget.owner + '/' + widget.name;
List get _items => payload == null ? [] : payload['timeline']['nodes'];
@override
Widget build(BuildContext context) {
return IssuePullRequestScreen(
id: id,
owner: owner,
name: name,
init: () => queryIssue(id, owner, name),
extra: Row(
children: <Widget>[Text('test')],
),
return ListScaffold(
title: _fullName + ' #' + widget.id.toString(),
header: payload == null ? null : _buildHeader(),
itemCount: _items.length,
itemBuilder: (context, index) => TimelineItem(_items[index], payload),
onRefresh: () async {
var _payload = await queryIssue(widget.id, widget.owner, widget.name);
setState(() {
payload = _payload;
});
},
// onLoadMore: () => ,
);
}
}

View File

@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import '../providers/settings.dart';
import '../widgets/list_scaffold.dart';
import '../widgets/event_item.dart';
import '../widgets/loading.dart';
import '../utils/utils.dart';
class NewsScreen extends StatefulWidget {
@ -12,106 +11,28 @@ class NewsScreen extends StatefulWidget {
class NewsScreenState extends State<NewsScreen> {
int page = 1;
bool loading = false;
List<Event> _events = [];
ScrollController _controller = ScrollController();
@override
void initState() {
super.initState();
_refresh();
_controller.addListener(() {
if (_controller.offset + 100 > _controller.position.maxScrollExtent &&
!_controller.position.outOfRange &&
!loading) {
_loadMore();
}
});
}
Future<void> _refresh() async {
setState(() {
loading = true;
});
page = 1;
var items = await fetchEvents(page);
setState(() {
loading = false;
_events = items;
});
}
_loadMore() async {
print('more');
setState(() {
loading = true;
});
page = page + 1;
var items = await fetchEvents(page);
setState(() {
loading = false;
_events.addAll(items);
});
}
Widget _buildItems(BuildContext context, int index) {
// return Loading(more: false);
if (_events.length == 0) {
return Loading(more: false);
}
if (index == _events.length) {
return Loading(more: true);
}
return EventItem(_events[index]);
}
@override
Widget build(context) {
switch (SettingsProvider.of(context).layout) {
case LayoutMap.cupertino:
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(middle: Text('News')),
child: SafeArea(
child: CustomScrollView(
controller: _controller,
slivers: <Widget>[
CupertinoSliverRefreshControl(onRefresh: _refresh),
SliverSafeArea(
top: false,
sliver: SliverList(
delegate: SliverChildBuilderDelegate(_buildItems,
childCount: _events.length + 1),
),
// sliver: SliverList(
// delegate:
// SliverChildBuilderDelegate(_buildItems, childCount: 1),
// ),
),
],
),
),
);
default:
return Scaffold(
appBar: AppBar(title: Text('News')),
body: RefreshIndicator(
onRefresh: () async {
await _refresh();
// Scaffold.of(context).showSnackBar(SnackBar(
// content: Container(
// child: Text('data'),
// padding: EdgeInsets.only(bottom: 10),
// )
// ));
},
child: ListView.builder(
controller: _controller,
itemCount: _events.length + 1,
// itemCount: 1,
itemBuilder: _buildItems,
),
),
);
}
return ListScaffold(
title: 'News',
itemCount: _events.length,
itemBuilder: (context, index) => EventItem(_events[index]),
onRefresh: () async {
page = 1;
var items = await fetchEvents(page);
setState(() {
_events = items;
});
},
onLoadMore: () async {
page = page + 1;
var items = await fetchEvents(page);
setState(() {
_events.addAll(items);
});
},
);
}
}

View File

@ -1,64 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import '../screens/user.dart';
class ProfileScreen extends StatelessWidget {
@override
Widget build(context) {
return CustomScrollView(
slivers: <Widget>[
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
Text("profile"),
],
);
Widget build(BuildContext context) {
return UserScreen('pd4d10');
}
}

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import '../utils/utils.dart';
import '../widgets/widgets.dart';
import '../widgets/list_scaffold.dart';
import '../widgets/timeline_item.dart';
import '../widgets/comment_item.dart';
Future queryPullRequest(int id, String owner, String name) async {
var data = await query('''
@ -68,48 +70,86 @@ Future queryPullRequest(int id, String owner, String name) async {
return data['repository']['pullRequest'];
}
class PullRequestScreen extends StatelessWidget {
class PullRequestScreen extends StatefulWidget {
final int id;
final String owner;
final String name;
PullRequestScreen(this.id, this.owner, this.name);
Widget _buildBadge() {
// bool merged = payload['merged'];
// int bgColor = merged ? Palette.purple : Palette.green;
// IconData iconData = merged ? Octicons.git_merge : Octicons.git_pull_request;
// String text = merged ? 'Merged' : 'Open';
// return Container(
// decoration: BoxDecoration(
// color: Color(bgColor),
// borderRadius: BorderRadius.all(Radius.circular(4)),
// ),
// padding: EdgeInsets.all(6),
// child: Row(
// children: <Widget>[
// Icon(iconData, color: Colors.white, size: 15),
// Text(text,
// style: TextStyle(
// color: Colors.white,
// fontWeight: FontWeight.w600,
// )),
// ],
// ),
// );
return Text('test');
}
@override
Widget build(BuildContext context) {
return IssuePullRequestScreen(
id: id,
owner: owner,
name: name,
init: () => queryPullRequest(id, owner, name),
extra: Row(
children: <Widget>[_buildBadge()],
_PullRequestScreenState createState() => _PullRequestScreenState();
}
class _PullRequestScreenState extends State<PullRequestScreen> {
Map<String, dynamic> payload;
Widget _buildBadge() {
bool merged = payload['merged'];
int bgColor = merged ? Palette.purple : Palette.green;
IconData iconData = merged ? Octicons.git_merge : Octicons.git_pull_request;
String text = merged ? 'Merged' : 'Open';
return Container(
decoration: BoxDecoration(
color: Color(bgColor),
borderRadius: BorderRadius.all(Radius.circular(4)),
),
padding: EdgeInsets.all(6),
child: Row(
children: <Widget>[
Icon(iconData, color: Colors.white, size: 15),
Text(text,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
)),
],
),
);
}
get _fullName => widget.owner + '/' + widget.name;
Widget _buildHeader() {
return Column(children: <Widget>[
Container(
// padding: EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_buildBadge(),
Text(
payload['title'],
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
height: 1.2,
),
),
CommentItem(payload),
],
),
)
]);
}
List get _items => payload == null ? [] : payload['timeline']['nodes'];
@override
Widget build(BuildContext context) {
return ListScaffold(
title: _fullName + ' #' + widget.id.toString(),
header: payload == null ? null : _buildHeader(),
itemCount: _items.length,
itemBuilder: (context, index) => TimelineItem(_items[index], payload),
onRefresh: () async {
var _payload =
await queryPullRequest(widget.id, widget.owner, widget.name);
setState(() {
payload = _payload;
});
},
// onLoadMore: () => ,
);
}
}

View File

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import '../widgets/loading.dart';
import '../providers/settings.dart';
import '../utils/utils.dart';
Future queryUser(String login) async {
@ -40,67 +42,84 @@ class UserScreen extends StatefulWidget {
}
class _UserScreenState extends State<UserScreen> {
var user = null;
var payload;
@override
void initState() {
super.initState();
queryUser(widget.login).then((_user) {
_refresh();
}
Future _refresh() async {
queryUser(widget.login).then((_payload) {
setState(() {
user = _user;
payload = _payload;
});
});
}
Widget _buildBody(BuildContext context) {
if (payload == null) {
return Loading(more: false);
}
return Text('loaded');
// return SafeArea(
// child: CupertinoSliverRefreshControl(
// // key: _refreshIndicatorKey,
// onRefresh: _refresh,
// child: Column(
// children: <Widget>[
// Container(
// margin: EdgeInsets.symmetric(vertical: 20.0),
// child: ClipOval(
// child: Image.network(
// payload['avatarUrl'],
// fit: BoxFit.fill,
// width: 64,
// height: 64,
// ),
// ),
// ),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// children: <Widget>[
// GestureDetector(
// child: Column(
// children: <Widget>[
// Text(payload['followers']['totalCount'].toString()),
// Text('Followers'),
// ],
// ),
// onTap: () {
// // print(1);
// },
// ),
// Column(
// children: <Widget>[
// Text(payload['following']['totalCount'].toString()),
// Text('Following')
// ],
// )
// ],
// ),
// ],
// ),
// ),
// );
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: () async {
var _user = await queryUser(widget.login);
setState(() {
user = _user;
});
},
child: Column(
children: <Widget>[
Container(
margin: EdgeInsets.symmetric(vertical: 20.0),
child: ClipOval(
child: Image.network(
user['avatarUrl'],
fit: BoxFit.fill,
width: 64,
height: 64,
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
GestureDetector(
child: Column(
children: <Widget>[
Text(user['followers']['totalCount'].toString()),
Text('Followers'),
],
),
onTap: () {
// print(1);
},
),
Column(
children: <Widget>[
Text(user['following']['totalCount'].toString()),
Text('Following')
],
)
],
),
],
),
),
);
switch (SettingsProvider.of(context).layout) {
case LayoutMap.cupertino:
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(middle: Text(widget.login)),
child: _buildBody(context),
);
default:
return Scaffold(appBar: AppBar(title: Text(widget.login)));
}
}
}

View File

@ -38,7 +38,7 @@ class Palette {
static const gray = 0xff959da5;
}
final pageSize = 100;
final pageSize = 20;
final graphqlChunk1 = '''
title

View File

@ -1,90 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import '../widgets/widgets.dart';
import '../widgets/loading.dart';
// Widget of issue screen and pull request screen
class IssuePullRequestScreen extends StatefulWidget {
final int id;
final String owner;
final String name;
final Function init;
final Widget extra;
IssuePullRequestScreen({
this.id,
this.owner,
this.name,
this.init,
this.extra,
});
@override
_IssuePullRequestScreenState createState() => _IssuePullRequestScreenState();
}
class _IssuePullRequestScreenState extends State<IssuePullRequestScreen> {
Map<String, dynamic> payload;
@override
void initState() {
super.initState();
widget.init().then((_payload) {
setState(() {
payload = _payload;
});
});
}
get _fullName => widget.owner + '/' + widget.name;
Widget _buildBody(BuildContext context) {
if (payload == null) {
return Loading(more: false);
}
List items = payload['timeline']['nodes'];
return Column(children: <Widget>[
Container(
// padding: EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
widget.extra,
Text(payload['title'],
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
height: 1.2,
)),
// CommentItem(payload),
// ListView.builder(
// shrinkWrap: true,
// itemCount: comments.length,
// itemBuilder: _buildCommentItem,
// ),
Column(
children:
items.map((item) => TimelineItem(item, payload)).toList()),
],
),
)
]);
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(_fullName + ' #' + widget.id.toString()),
trailing: Icon(Icons.more_vert, size: 24),
),
child: SafeArea(
child: SingleChildScrollView(
child: _buildBody(context),
),
),
);
}
}

View File

@ -0,0 +1,143 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart';
import '../providers/settings.dart';
import 'loading.dart';
typedef RefreshCallback = Future<void> Function();
class ListScaffold extends StatefulWidget {
final String title;
final Widget header;
final int itemCount;
final IndexedWidgetBuilder itemBuilder;
final RefreshCallback onRefresh;
final RefreshCallback onLoadMore;
ListScaffold({
@required this.title,
this.header,
@required this.itemCount,
@required this.itemBuilder,
@required this.onRefresh,
this.onLoadMore,
});
@override
_ListScaffoldState createState() => _ListScaffoldState();
}
class _ListScaffoldState extends State<ListScaffold> {
bool loading = false;
bool loadingMore = false;
ScrollController _controller = ScrollController();
@override
void initState() {
super.initState();
_refresh();
_controller.addListener(() {
if (_controller.offset + 100 > _controller.position.maxScrollExtent &&
!_controller.position.outOfRange &&
!loading &&
!loadingMore) {
_loadMore();
}
});
}
Future<void> _refresh() async {
print('refresh');
setState(() {
loading = true;
});
try {
await widget.onRefresh();
} catch (err) {
print(err);
} finally {
setState(() {
loading = false;
});
}
}
Future<void> _loadMore() async {
print('more');
setState(() {
loadingMore = true;
});
try {
await widget.onLoadMore();
} catch (err) {
print(err);
} finally {
loadingMore = false;
}
}
Widget _buildItem(BuildContext context, int index) {
if (index == widget.itemCount) {
return Loading(more: true);
}
return widget.itemBuilder(context, index);
}
Widget _buildSliver(BuildContext context) {
if (loading) {
return SliverToBoxAdapter(child: Loading(more: false));
} else {
return SliverList(
delegate: SliverChildBuilderDelegate(
_buildItem,
childCount: widget.itemCount + 1,
),
);
}
}
Widget _buildBody(BuildContext context) {
if (loading) {
return Loading(more: false);
} else {
return ListView.builder(
controller: _controller,
itemCount: widget.itemCount + 1,
itemBuilder: _buildItem,
);
}
}
@override
Widget build(BuildContext context) {
switch (SettingsProvider.of(context).layout) {
case LayoutMap.cupertino:
List<Widget> slivers = [
CupertinoSliverRefreshControl(onRefresh: widget.onRefresh)
];
if (widget.header != null) {
slivers.add(SliverToBoxAdapter(child: widget.header));
}
slivers.add(_buildSliver(context));
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(middle: Text(widget.title)),
child: SafeArea(
child: CustomScrollView(
controller: _controller,
slivers: slivers,
),
),
);
default:
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: RefreshIndicator(
onRefresh: widget.onRefresh,
child: _buildBody(context),
),
);
}
}
}

View File

@ -2,4 +2,3 @@ export 'avatar.dart';
export 'user_name.dart';
export 'timeline_item.dart';
export 'comment_item.dart';
export 'issue_pull_request.dart';