mirror of
https://github.com/git-touch/git-touch
synced 2025-02-10 16:40:37 +01:00
refactor: extract long list scaffold for issue and pull request
This commit is contained in:
parent
73907f51b4
commit
650af30838
BIN
images/progressive-disclosure-line.png
Normal file
BIN
images/progressive-disclosure-line.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 333 B |
@ -7,6 +7,8 @@ import 'screens/notifications.dart';
|
||||
import 'screens/search.dart';
|
||||
import 'screens/profile.dart';
|
||||
import 'screens/login.dart';
|
||||
import 'screens/pull_request.dart';
|
||||
import 'screens/issue.dart';
|
||||
|
||||
class Home extends StatefulWidget {
|
||||
@override
|
||||
@ -58,6 +60,7 @@ class _HomeState extends State<Home> {
|
||||
}
|
||||
|
||||
_buildScreen(int index) {
|
||||
// return IssueScreen(number: 29, owner: 'reactjs', name: 'rfcs');
|
||||
switch (index) {
|
||||
case 0:
|
||||
return NewsScreen();
|
||||
|
@ -1,59 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import '../utils/utils.dart';
|
||||
import '../widgets/list_scaffold.dart';
|
||||
import '../widgets/long_list_scaffold.dart';
|
||||
import '../widgets/timeline_item.dart';
|
||||
import '../widgets/comment_item.dart';
|
||||
import '../providers/settings.dart';
|
||||
|
||||
class IssueScreen extends StatefulWidget {
|
||||
final int id;
|
||||
final int number;
|
||||
final String owner;
|
||||
final String name;
|
||||
|
||||
IssueScreen(this.id, this.owner, this.name);
|
||||
IssueScreen({
|
||||
@required this.number,
|
||||
@required this.owner,
|
||||
@required this.name,
|
||||
});
|
||||
|
||||
IssueScreen.fromFullName({@required this.number, @required String fullName})
|
||||
: this.owner = fullName.split('/')[0],
|
||||
this.name = fullName.split('/')[1];
|
||||
|
||||
@override
|
||||
_IssueScreenState createState() => _IssueScreenState();
|
||||
}
|
||||
|
||||
class _IssueScreenState extends State<IssueScreen> {
|
||||
Map<String, dynamic> payload;
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Column(children: <Widget>[
|
||||
Container(
|
||||
// padding: EdgeInsets.all(10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
payload['title'],
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
CommentItem(payload),
|
||||
],
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
get _fullName => widget.owner + '/' + widget.name;
|
||||
get owner => widget.owner;
|
||||
get id => widget.number;
|
||||
get name => widget.name;
|
||||
|
||||
List get _items => payload == null ? [] : payload['timeline']['nodes'];
|
||||
|
||||
Future queryIssue(
|
||||
BuildContext context, int id, String owner, String name) async {
|
||||
Future queryIssue() async {
|
||||
var data = await SettingsProvider.of(context).query('''
|
||||
{
|
||||
repository(owner: "$owner", name: "$name") {
|
||||
issue(number: $id) {
|
||||
$graphqlChunk1
|
||||
timeline(first: $pageSize) {
|
||||
totalCount
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
@ -69,23 +54,104 @@ class _IssueScreenState extends State<IssueScreen> {
|
||||
return data['repository']['issue'];
|
||||
}
|
||||
|
||||
Future queryMore(String cursor) async {
|
||||
var data = await SettingsProvider.of(context).query('''
|
||||
{
|
||||
repository(owner: "$owner", name: "$name") {
|
||||
issue(number: $id) {
|
||||
timeline(first: $pageSize, after: $cursor) {
|
||||
totalCount
|
||||
pageInfo {
|
||||
endCursor
|
||||
}
|
||||
nodes {
|
||||
$graghqlChunk
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
''');
|
||||
return data['repository']['issue'];
|
||||
}
|
||||
|
||||
Future<List> queryTrailing() async {
|
||||
var data = await SettingsProvider.of(context).query('''
|
||||
{
|
||||
repository(owner: "$owner", name: "$name") {
|
||||
issue(number: $id) {
|
||||
timeline(last: $pageSize) {
|
||||
nodes {
|
||||
$graghqlChunk
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
''');
|
||||
return data['repository']['issue']['timeline']['nodes'];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListScaffold(
|
||||
title: Text(_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(context, widget.id, widget.owner, widget.name);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
payload = _payload;
|
||||
});
|
||||
}
|
||||
return LongListScaffold(
|
||||
title: Text(_fullName + ' #' + widget.number.toString()),
|
||||
headerBuilder: (payload) {
|
||||
return Column(children: <Widget>[
|
||||
Container(
|
||||
// padding: EdgeInsets.all(10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
payload['title'],
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
CommentItem(payload),
|
||||
],
|
||||
),
|
||||
)
|
||||
]);
|
||||
},
|
||||
itemBuilder: (itemPayload) => TimelineItem(itemPayload),
|
||||
onRefresh: () async {
|
||||
var res = await queryIssue();
|
||||
int totalCount = res['timeline']['totalCount'];
|
||||
String cursor = res['timeline']['pageInfo']['endCursor'];
|
||||
List leadingItems = res['timeline']['nodes'];
|
||||
|
||||
var payload = LongListPayload(
|
||||
header: res,
|
||||
totalCount: totalCount,
|
||||
cursor: cursor,
|
||||
leadingItems: leadingItems,
|
||||
trailingItems: [],
|
||||
);
|
||||
|
||||
if (totalCount > 2 * pageSize) {
|
||||
payload.trailingItems = await queryTrailing();
|
||||
}
|
||||
|
||||
return payload;
|
||||
},
|
||||
onLoadMore: (String _cursor) async {
|
||||
var res = await queryMore(_cursor);
|
||||
int totalCount = res['timeline']['totalCount'];
|
||||
String cursor = res['timeline']['pageInfo']['endCursor'];
|
||||
List leadingItems = res['timeline']['nodes'];
|
||||
|
||||
var payload = LongListPayload(
|
||||
totalCount: totalCount,
|
||||
cursor: cursor,
|
||||
leadingItems: leadingItems,
|
||||
);
|
||||
|
||||
return payload;
|
||||
},
|
||||
// onLoadMore: () => ,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,29 +2,76 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import '../providers/settings.dart';
|
||||
import '../utils/utils.dart';
|
||||
import '../widgets/list_scaffold.dart';
|
||||
import '../widgets/long_list_scaffold.dart';
|
||||
import '../widgets/timeline_item.dart';
|
||||
import '../widgets/comment_item.dart';
|
||||
|
||||
class PullRequestScreen extends StatefulWidget {
|
||||
final int id;
|
||||
final int number;
|
||||
final String owner;
|
||||
final String name;
|
||||
|
||||
PullRequestScreen(this.id, this.owner, this.name);
|
||||
PullRequestScreen({
|
||||
@required this.number,
|
||||
@required this.owner,
|
||||
@required this.name,
|
||||
});
|
||||
|
||||
PullRequestScreen.fromFullName(
|
||||
{@required this.number, @required String fullName})
|
||||
: this.owner = fullName.split('/')[0],
|
||||
this.name = fullName.split('/')[1];
|
||||
|
||||
@override
|
||||
_PullRequestScreenState createState() => _PullRequestScreenState();
|
||||
}
|
||||
|
||||
var commonChunk = '''
|
||||
$graghqlChunk
|
||||
... on ReviewRequestedEvent {
|
||||
createdAt
|
||||
actor {
|
||||
login
|
||||
}
|
||||
requestedReviewer {
|
||||
... on User {
|
||||
login
|
||||
}
|
||||
}
|
||||
}
|
||||
... on PullRequestReview {
|
||||
createdAt
|
||||
state
|
||||
author {
|
||||
login
|
||||
}
|
||||
}
|
||||
... on MergedEvent {
|
||||
createdAt
|
||||
mergeRefName
|
||||
actor {
|
||||
login
|
||||
}
|
||||
commit {
|
||||
oid
|
||||
url
|
||||
}
|
||||
}
|
||||
... on HeadRefDeletedEvent {
|
||||
createdAt
|
||||
actor {
|
||||
login
|
||||
}
|
||||
headRefName
|
||||
}
|
||||
''';
|
||||
|
||||
class _PullRequestScreenState extends State<PullRequestScreen> {
|
||||
Map<String, dynamic> payload;
|
||||
|
||||
Future queryPullRequest(BuildContext context) async {
|
||||
var owner = widget.owner;
|
||||
var id = widget.id;
|
||||
var name = widget.name;
|
||||
get owner => widget.owner;
|
||||
get id => widget.number;
|
||||
get name => widget.name;
|
||||
|
||||
Future queryPullRequest() async {
|
||||
var data = await SettingsProvider.of(context).query('''
|
||||
{
|
||||
repository(owner: "$owner", name: "$name") {
|
||||
@ -38,48 +85,12 @@ class _PullRequestScreenState extends State<PullRequestScreen> {
|
||||
totalCount
|
||||
}
|
||||
timeline(first: $pageSize) {
|
||||
totalCount
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
nodes {
|
||||
$graghqlChunk
|
||||
... on ReviewRequestedEvent {
|
||||
createdAt
|
||||
actor {
|
||||
login
|
||||
}
|
||||
requestedReviewer {
|
||||
... on User {
|
||||
login
|
||||
}
|
||||
}
|
||||
}
|
||||
... on PullRequestReview {
|
||||
createdAt
|
||||
state
|
||||
author {
|
||||
login
|
||||
}
|
||||
}
|
||||
... on MergedEvent {
|
||||
createdAt
|
||||
mergeRefName
|
||||
actor {
|
||||
login
|
||||
}
|
||||
commit {
|
||||
oid
|
||||
url
|
||||
}
|
||||
}
|
||||
... on HeadRefDeletedEvent {
|
||||
createdAt
|
||||
actor {
|
||||
login
|
||||
}
|
||||
headRefName
|
||||
}
|
||||
$commonChunk
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -89,7 +100,45 @@ class _PullRequestScreenState extends State<PullRequestScreen> {
|
||||
return data['repository']['pullRequest'];
|
||||
}
|
||||
|
||||
Widget _buildBadge() {
|
||||
Future queryMore(String cursor) async {
|
||||
var data = await SettingsProvider.of(context).query('''
|
||||
{
|
||||
repository(owner: "$owner", name: "$name") {
|
||||
pullRequest(number: $id) {
|
||||
timeline(first: $pageSize, after: $cursor) {
|
||||
totalCount
|
||||
pageInfo {
|
||||
endCursor
|
||||
}
|
||||
nodes {
|
||||
$commonChunk
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
''');
|
||||
return data['repository']['pullRequest'];
|
||||
}
|
||||
|
||||
Future<List> queryTrailing() async {
|
||||
var data = await SettingsProvider.of(context).query('''
|
||||
{
|
||||
repository(owner: "$owner", name: "$name") {
|
||||
pullRequest(number: $id) {
|
||||
timeline(last: $pageSize) {
|
||||
nodes {
|
||||
$commonChunk
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
''');
|
||||
return data['repository']['pullRequest']['timeline']['nodes'];
|
||||
}
|
||||
|
||||
Widget _buildBadge(payload) {
|
||||
bool merged = payload['merged'];
|
||||
Color bgColor = merged ? Palette.purple : Palette.green;
|
||||
IconData iconData = merged ? Octicons.git_merge : Octicons.git_pull_request;
|
||||
@ -117,47 +166,67 @@ class _PullRequestScreenState extends State<PullRequestScreen> {
|
||||
|
||||
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: Text(_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(context);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
payload = _payload;
|
||||
});
|
||||
}
|
||||
return LongListScaffold(
|
||||
title: Text(_fullName + ' #' + widget.number.toString()),
|
||||
headerBuilder: (payload) {
|
||||
return Column(children: <Widget>[
|
||||
Container(
|
||||
// padding: EdgeInsets.all(10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
_buildBadge(payload),
|
||||
Text(
|
||||
payload['title'],
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
CommentItem(payload),
|
||||
],
|
||||
),
|
||||
)
|
||||
]);
|
||||
},
|
||||
itemBuilder: (itemPayload) => TimelineItem(itemPayload),
|
||||
onRefresh: () async {
|
||||
var res = await queryPullRequest();
|
||||
int totalCount = res['timeline']['totalCount'];
|
||||
String cursor = res['timeline']['pageInfo']['endCursor'];
|
||||
List leadingItems = res['timeline']['nodes'];
|
||||
|
||||
var payload = LongListPayload(
|
||||
header: res,
|
||||
totalCount: totalCount,
|
||||
cursor: cursor,
|
||||
leadingItems: leadingItems,
|
||||
trailingItems: [],
|
||||
);
|
||||
|
||||
if (totalCount > 2 * pageSize) {
|
||||
payload.trailingItems = await queryTrailing();
|
||||
}
|
||||
|
||||
return payload;
|
||||
},
|
||||
onLoadMore: (String _cursor) async {
|
||||
var res = await queryMore(_cursor);
|
||||
int totalCount = res['timeline']['totalCount'];
|
||||
String cursor = res['timeline']['pageInfo']['endCursor'];
|
||||
List leadingItems = res['timeline']['nodes'];
|
||||
|
||||
var payload = LongListPayload(
|
||||
totalCount: totalCount,
|
||||
cursor: cursor,
|
||||
leadingItems: leadingItems,
|
||||
);
|
||||
|
||||
return payload;
|
||||
},
|
||||
// onLoadMore: () => ,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ class Palette {
|
||||
static const gray = Color(0xff959da5);
|
||||
}
|
||||
|
||||
final pageSize = 20;
|
||||
final pageSize = 5;
|
||||
|
||||
final graphqlChunk1 = '''
|
||||
title
|
||||
|
@ -19,17 +19,17 @@ class EventItem extends StatelessWidget {
|
||||
|
||||
TextSpan _buildIssue(BuildContext context) {
|
||||
int id = event.payload['issue']['number'];
|
||||
String name = event.repo.name;
|
||||
var arr = name.split('/');
|
||||
return createLinkSpan(
|
||||
context, '#' + id.toString(), () => IssueScreen(id, arr[0], arr[1]));
|
||||
return createLinkSpan(context, '#' + id.toString(),
|
||||
() => IssueScreen.fromFullName(number: id, fullName: event.repo.name));
|
||||
}
|
||||
|
||||
TextSpan _buildPullRequest(BuildContext context, int id) {
|
||||
String name = event.repo.name;
|
||||
var arr = name.split('/');
|
||||
return createLinkSpan(context, '#' + id.toString(),
|
||||
() => PullRequestScreen(id, arr[0], arr[1]));
|
||||
TextSpan _buildPullRequest(BuildContext context, int number) {
|
||||
return createLinkSpan(
|
||||
context,
|
||||
'#' + number.toString(),
|
||||
() => PullRequestScreen.fromFullName(
|
||||
number: number, fullName: event.repo.name),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItem({
|
||||
|
218
lib/widgets/long_list_scaffold.dart
Normal file
218
lib/widgets/long_list_scaffold.dart
Normal file
@ -0,0 +1,218 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import '../providers/settings.dart';
|
||||
import 'loading.dart';
|
||||
import 'link.dart';
|
||||
|
||||
class LongListPayload<T, K> {
|
||||
T header;
|
||||
int totalCount;
|
||||
String cursor;
|
||||
List<K> leadingItems;
|
||||
List<K> trailingItems;
|
||||
|
||||
LongListPayload({
|
||||
this.header,
|
||||
this.totalCount,
|
||||
this.cursor,
|
||||
this.leadingItems,
|
||||
this.trailingItems,
|
||||
});
|
||||
}
|
||||
|
||||
// This is a scaffold for issue and pull request
|
||||
// Since the list could be very long, and some users may only want to to check trailing items
|
||||
// We should load leading and trailing items at first fetching, and do load more in the middle
|
||||
// e.g. https://github.com/reactjs/rfcs/pull/68
|
||||
class LongListScaffold<T, K> extends StatefulWidget {
|
||||
final Widget title;
|
||||
final List<Widget> actions;
|
||||
final Widget trailing;
|
||||
final Widget Function(T headerPayload) headerBuilder;
|
||||
final Widget Function(K itemPayload) itemBuilder;
|
||||
final Future<LongListPayload<T, K>> Function() onRefresh;
|
||||
final Future<LongListPayload<T, K>> Function(String cursor) onLoadMore;
|
||||
|
||||
LongListScaffold({
|
||||
@required this.title,
|
||||
this.actions,
|
||||
this.trailing,
|
||||
@required this.headerBuilder,
|
||||
@required this.itemBuilder,
|
||||
@required this.onRefresh,
|
||||
@required this.onLoadMore,
|
||||
});
|
||||
|
||||
@override
|
||||
_LongListScaffoldState createState() => _LongListScaffoldState();
|
||||
}
|
||||
|
||||
class _LongListScaffoldState<T, K> extends State<LongListScaffold<T, K>> {
|
||||
bool loading;
|
||||
bool loadingMore = false;
|
||||
LongListPayload<T, K> payload;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_refresh();
|
||||
}
|
||||
|
||||
Future<void> _refresh() async {
|
||||
print('long list scaffold refresh');
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
try {
|
||||
payload = await widget.onRefresh();
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadMore() async {
|
||||
print('long list scaffold load more');
|
||||
setState(() {
|
||||
loadingMore = true;
|
||||
});
|
||||
try {
|
||||
var _payload = await widget.onLoadMore(payload.cursor);
|
||||
payload.totalCount = _payload.totalCount;
|
||||
payload.cursor = _payload.cursor;
|
||||
payload.leadingItems.addAll(_payload.leadingItems);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
loadingMore = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildItem(BuildContext context, int index) {
|
||||
if (index % 2 == 1) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(bottom: BorderSide(color: Colors.black12)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
int realIndex = index ~/ 2;
|
||||
|
||||
if (realIndex < payload.leadingItems.length) {
|
||||
return widget.itemBuilder(payload.leadingItems[realIndex]);
|
||||
} else if (realIndex == payload.leadingItems.length) {
|
||||
var count = payload.totalCount -
|
||||
payload.leadingItems.length +
|
||||
payload.trailingItems.length;
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: ExactAssetImage('images/progressive-disclosure-line.png',
|
||||
scale: 2),
|
||||
repeat: ImageRepeat.repeatX,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Link(
|
||||
beforeRedirect: _loadMore,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.black12),
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Text('$count hidden items',
|
||||
style: TextStyle(color: Colors.black87, fontSize: 15)),
|
||||
Padding(padding: EdgeInsets.only(top: 4)),
|
||||
loadingMore
|
||||
? CupertinoActivityIndicator()
|
||||
: Text(
|
||||
'Load more...',
|
||||
style:
|
||||
TextStyle(color: Colors.blueAccent, fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return widget.itemBuilder(
|
||||
payload.trailingItems[realIndex - payload.leadingItems.length - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
int get _itemCount {
|
||||
int count = payload.leadingItems.length + payload.trailingItems.length;
|
||||
if (payload.totalCount > count) {
|
||||
count++;
|
||||
}
|
||||
return 2 * count; // including bottom border
|
||||
}
|
||||
|
||||
Widget _buildSliver() {
|
||||
if (loading) {
|
||||
return SliverToBoxAdapter(child: Loading(more: false));
|
||||
} else {
|
||||
return SliverList(
|
||||
delegate:
|
||||
SliverChildBuilderDelegate(_buildItem, childCount: _itemCount),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
if (loading) {
|
||||
return Loading(more: false);
|
||||
} else {
|
||||
return ListView.builder(itemCount: _itemCount, itemBuilder: _buildItem);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
switch (SettingsProvider.of(context).theme) {
|
||||
case ThemeMap.cupertino:
|
||||
List<Widget> slivers = [
|
||||
CupertinoSliverRefreshControl(onRefresh: widget.onRefresh)
|
||||
];
|
||||
if (payload != null) {
|
||||
slivers.add(
|
||||
SliverToBoxAdapter(child: widget.headerBuilder(payload.header)),
|
||||
);
|
||||
}
|
||||
slivers.add(_buildSliver());
|
||||
|
||||
return CupertinoPageScaffold(
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
middle: widget.title,
|
||||
trailing: widget.trailing,
|
||||
),
|
||||
child: SafeArea(
|
||||
child: CustomScrollView(slivers: slivers),
|
||||
),
|
||||
);
|
||||
default:
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: widget.title,
|
||||
actions: widget.actions,
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: widget.onRefresh,
|
||||
child: _buildBody(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -55,9 +55,17 @@ class _NotificationItemState extends State<NotificationItem> {
|
||||
Widget _buildRoute(BuildContext context) {
|
||||
switch (payload.type) {
|
||||
case 'Issue':
|
||||
return IssueScreen(payload.number, payload.owner, payload.name);
|
||||
return IssueScreen(
|
||||
number: payload.number,
|
||||
owner: payload.owner,
|
||||
name: payload.name,
|
||||
);
|
||||
case 'PullRequest':
|
||||
return PullRequestScreen(payload.number, payload.owner, payload.name);
|
||||
return PullRequestScreen(
|
||||
number: payload.number,
|
||||
owner: payload.owner,
|
||||
name: payload.name,
|
||||
);
|
||||
case 'Release':
|
||||
// return
|
||||
default:
|
||||
|
@ -7,9 +7,8 @@ import 'user_name.dart';
|
||||
|
||||
class TimelineItem extends StatelessWidget {
|
||||
final Map<String, dynamic> item;
|
||||
final Map<String, dynamic> payload;
|
||||
|
||||
TimelineItem(this.item, this.payload);
|
||||
TimelineItem(this.item);
|
||||
|
||||
TextSpan _buildReviewText(BuildContext context, item) {
|
||||
switch (item['state']) {
|
||||
@ -189,7 +188,9 @@ class TimelineItem extends StatelessWidget {
|
||||
case 'ReviewRequestedEvent':
|
||||
return _buildItem(
|
||||
iconData: Octicons.eye,
|
||||
actor: payload['author']['login'],
|
||||
// actor: payload['author']['login'],
|
||||
// TODO:
|
||||
actor: 'test',
|
||||
textSpan: TextSpan(children: [
|
||||
TextSpan(text: ' requested a review from '),
|
||||
createUserSpan(item['requestedReviewer']['login']),
|
||||
@ -247,10 +248,6 @@ class TimelineItem extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom:
|
||||
BorderSide(color: CupertinoColors.extraLightBackgroundGray))),
|
||||
child: _buildByType(context),
|
||||
);
|
||||
}
|
||||
|
@ -21,7 +21,10 @@ dependencies:
|
||||
github: ^4.1.0
|
||||
intl: ^0.15.7
|
||||
url_launcher: ^4.2.0
|
||||
uni_links: ^0.1.4
|
||||
flutter_markdown: ^0.2.0
|
||||
shared_preferences: ^0.5.0
|
||||
nanoid: ^0.0.6
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
@ -48,6 +51,9 @@ flutter:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
assets:
|
||||
- images/
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.io/assets-and-images/#resolution-aware.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user