mirror of
https://github.com/pd4d10/git-touch.git
synced 2026-04-28 18:39:26 -05:00
refactor: extract long list scaffold for issue and pull request
This commit is contained in:
@@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user