mirror of
https://github.com/git-touch/git-touch
synced 2025-01-26 13:54:53 +01:00
refactor: extract state to list scaffold, keep business logic clean
This commit is contained in:
parent
84596d3fcf
commit
8d50862b52
@ -72,6 +72,7 @@ class _SettingsProviderState extends State<SettingsProvider> {
|
||||
|
||||
theme = _theme;
|
||||
await prefs.setInt('theme', theme);
|
||||
print('write theme: $theme');
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
@ -161,12 +162,12 @@ class _SettingsProviderState extends State<SettingsProvider> {
|
||||
}
|
||||
|
||||
int _theme = prefs.getInt('theme');
|
||||
print('read theme: $_theme');
|
||||
if (ThemeMap.all.contains(_theme)) {
|
||||
theme = _theme;
|
||||
} else if (Platform.isIOS) {
|
||||
theme = ThemeMap.cupertino;
|
||||
}
|
||||
theme = ThemeMap.material;
|
||||
|
||||
setState(() {
|
||||
ready = true;
|
||||
|
@ -5,39 +5,45 @@ import '../providers/settings.dart';
|
||||
import '../widgets/error_reload.dart';
|
||||
import '../widgets/loading.dart';
|
||||
|
||||
typedef RefreshCallback = Future<void> Function();
|
||||
class ListPayload<T, K> {
|
||||
K cursor;
|
||||
List<T> items;
|
||||
bool end;
|
||||
|
||||
ListPayload({this.items, this.cursor, this.end});
|
||||
}
|
||||
|
||||
// This is a scaffold for infinite scroll screens
|
||||
class ListScaffold extends StatefulWidget {
|
||||
class ListScaffold<T, K> extends StatefulWidget {
|
||||
final Widget title;
|
||||
final IconData trailingIconData;
|
||||
final Function trailingOnTap;
|
||||
final Widget header;
|
||||
final int itemCount;
|
||||
final IndexedWidgetBuilder itemBuilder;
|
||||
final RefreshCallback onRefresh;
|
||||
final RefreshCallback onLoadMore;
|
||||
// final IconData trailingIconData;
|
||||
// final Function trailingOnTap;
|
||||
final Widget Function(T payload) itemBuilder;
|
||||
final Future<ListPayload<T, K>> Function() onRefresh;
|
||||
final Future<ListPayload<T, K>> Function(K cursor) onLoadMore;
|
||||
|
||||
ListScaffold({
|
||||
@required this.title,
|
||||
this.trailingIconData,
|
||||
this.trailingOnTap,
|
||||
this.header,
|
||||
@required this.itemCount,
|
||||
// this.trailingIconData,
|
||||
// this.trailingOnTap,
|
||||
@required this.itemBuilder,
|
||||
@required this.onRefresh,
|
||||
this.onLoadMore,
|
||||
@required this.onLoadMore,
|
||||
});
|
||||
|
||||
@override
|
||||
_ListScaffoldState createState() => _ListScaffoldState();
|
||||
}
|
||||
|
||||
class _ListScaffoldState extends State<ListScaffold> {
|
||||
class _ListScaffoldState<T, K> extends State<ListScaffold<T, K>> {
|
||||
bool loading = false;
|
||||
bool loadingMore = false;
|
||||
String error = '';
|
||||
|
||||
List<T> items = [];
|
||||
K cursor;
|
||||
bool end;
|
||||
|
||||
ScrollController _controller = ScrollController();
|
||||
|
||||
@override
|
||||
@ -61,7 +67,10 @@ class _ListScaffoldState extends State<ListScaffold> {
|
||||
loading = true;
|
||||
});
|
||||
try {
|
||||
await widget.onRefresh();
|
||||
var _payload = await widget.onRefresh();
|
||||
items = _payload.items;
|
||||
cursor = _payload.cursor;
|
||||
end = _payload.end;
|
||||
} catch (err) {
|
||||
// print(err);
|
||||
error = err.toString();
|
||||
@ -75,12 +84,15 @@ class _ListScaffoldState extends State<ListScaffold> {
|
||||
}
|
||||
|
||||
Future<void> _loadMore() async {
|
||||
// print('list scaffold load more');
|
||||
print('list scaffold load more');
|
||||
setState(() {
|
||||
loadingMore = true;
|
||||
});
|
||||
try {
|
||||
await widget.onLoadMore();
|
||||
var _payload = await widget.onLoadMore(cursor);
|
||||
items.addAll(_payload.items);
|
||||
cursor = _payload.cursor;
|
||||
end = _payload.end;
|
||||
} catch (err) {
|
||||
print(err);
|
||||
} finally {
|
||||
@ -93,7 +105,7 @@ class _ListScaffoldState extends State<ListScaffold> {
|
||||
}
|
||||
|
||||
Widget _buildItem(BuildContext context, int index) {
|
||||
if (index == 2 * widget.itemCount) {
|
||||
if (index == 2 * items.length) {
|
||||
return Loading(more: true);
|
||||
}
|
||||
|
||||
@ -105,19 +117,21 @@ class _ListScaffoldState extends State<ListScaffold> {
|
||||
);
|
||||
}
|
||||
|
||||
return widget.itemBuilder(context, index ~/ 2);
|
||||
return widget.itemBuilder(items[index ~/ 2]);
|
||||
}
|
||||
|
||||
Widget _buildSliver(BuildContext context) {
|
||||
if (error.isNotEmpty) {
|
||||
return ErrorReload(text: error, reload: _refresh);
|
||||
return SliverToBoxAdapter(
|
||||
child: ErrorReload(text: error, reload: _refresh),
|
||||
);
|
||||
} else if (loading) {
|
||||
return SliverToBoxAdapter(child: Loading(more: false));
|
||||
} else {
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
_buildItem,
|
||||
childCount: 2 * widget.itemCount + 1,
|
||||
childCount: 2 * items.length + 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -131,7 +145,7 @@ class _ListScaffoldState extends State<ListScaffold> {
|
||||
} else {
|
||||
return ListView.builder(
|
||||
controller: _controller,
|
||||
itemCount: 2 * widget.itemCount + 1,
|
||||
itemCount: 2 * items.length + 1,
|
||||
itemBuilder: _buildItem,
|
||||
);
|
||||
}
|
||||
@ -144,9 +158,9 @@ class _ListScaffoldState extends State<ListScaffold> {
|
||||
List<Widget> slivers = [
|
||||
CupertinoSliverRefreshControl(onRefresh: widget.onRefresh)
|
||||
];
|
||||
if (widget.header != null) {
|
||||
slivers.add(SliverToBoxAdapter(child: widget.header));
|
||||
}
|
||||
// if (widget.header != null) {
|
||||
// slivers.add(SliverToBoxAdapter(child: widget.header));
|
||||
// }
|
||||
slivers.add(_buildSliver(context));
|
||||
|
||||
return CupertinoPageScaffold(
|
||||
@ -173,14 +187,14 @@ class _ListScaffoldState extends State<ListScaffold> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: widget.title,
|
||||
actions: widget.trailingIconData == null
|
||||
? []
|
||||
: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(widget.trailingIconData),
|
||||
onPressed: widget.trailingOnTap,
|
||||
)
|
||||
],
|
||||
// actions: widget.trailingIconData == null
|
||||
// ? []
|
||||
// : <Widget>[
|
||||
// IconButton(
|
||||
// icon: Icon(widget.trailingIconData),
|
||||
// onPressed: widget.trailingOnTap,
|
||||
// )
|
||||
// ],
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: widget.onRefresh,
|
||||
|
@ -169,7 +169,8 @@ class _LongListScaffoldState<T, K> extends State<LongListScaffold<T, K>> {
|
||||
|
||||
Widget _buildSliver() {
|
||||
if (error.isNotEmpty) {
|
||||
return ErrorReload(text: error, reload: _refresh);
|
||||
return SliverToBoxAdapter(
|
||||
child: ErrorReload(text: error, reload: _refresh));
|
||||
} else if (loading) {
|
||||
return SliverToBoxAdapter(child: Loading(more: false));
|
||||
} else {
|
||||
|
@ -42,8 +42,8 @@ class _RefreshScaffoldState<T> extends State<RefreshScaffold<T>> {
|
||||
Widget _buildBody() {
|
||||
if (error.isNotEmpty) {
|
||||
return ErrorReload(text: error, reload: _refresh);
|
||||
} else if (loading) {
|
||||
return Loading(more: true);
|
||||
} else if (payload == null) {
|
||||
return Loading(more: false);
|
||||
} else {
|
||||
return widget.bodyBuilder(payload);
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ class RefreshStatelessScaffold extends StatelessWidget {
|
||||
if (error.isNotEmpty) {
|
||||
return ErrorReload(text: error, reload: onRefresh);
|
||||
} else if (loading) {
|
||||
return Loading(more: true);
|
||||
return Loading(more: false);
|
||||
} else {
|
||||
return bodyBuilder();
|
||||
}
|
||||
|
@ -11,40 +11,32 @@ class NewsScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class NewsScreenState extends State<NewsScreen> {
|
||||
int page = 1;
|
||||
List<Event> _events = [];
|
||||
|
||||
Future<List<Event>> fetchEvents(int page) async {
|
||||
var settings = SettingsProvider.of(context);
|
||||
var login = settings.activeLogin;
|
||||
List data = await settings
|
||||
.getWithCredentials('/users/$login/received_events?page=$page');
|
||||
// print(data);
|
||||
return data.map<Event>((item) => Event.fromJSON(item)).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(context) {
|
||||
// FIXME: can't add generic type here. Don't know why
|
||||
// type '(Event) => EventItem' is not a subtype of type '(dynamic) => Widget'
|
||||
return ListScaffold(
|
||||
title: Text('News'),
|
||||
itemCount: _events.length,
|
||||
itemBuilder: (context, index) => EventItem(_events[index]),
|
||||
itemBuilder: (payload) => EventItem(payload),
|
||||
onRefresh: () async {
|
||||
page = 1;
|
||||
var page = 1;
|
||||
var items = await fetchEvents(page);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_events = items;
|
||||
});
|
||||
}
|
||||
return ListPayload(
|
||||
cursor: page + 1, end: items.length < 30, items: items);
|
||||
},
|
||||
onLoadMore: () async {
|
||||
page = page + 1;
|
||||
onLoadMore: (page) async {
|
||||
var items = await fetchEvents(page);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_events.addAll(items);
|
||||
});
|
||||
}
|
||||
return ListPayload(
|
||||
cursor: page + 1, end: items.length < 30, items: items);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -3,12 +3,13 @@ import 'package:flutter/cupertino.dart';
|
||||
import '../screens/issue.dart';
|
||||
import '../screens/pull_request.dart';
|
||||
import '../screens/user.dart';
|
||||
import 'link.dart';
|
||||
// import 'link.dart';
|
||||
import 'avatar.dart';
|
||||
import '../utils/utils.dart';
|
||||
|
||||
class EventItem extends StatelessWidget {
|
||||
final Event event;
|
||||
|
||||
EventItem(this.event);
|
||||
|
||||
TextSpan _buildRepo(BuildContext context) {
|
||||
|
@ -37,7 +37,7 @@ class RepoItem extends StatelessWidget {
|
||||
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 15),
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(top: 6)),
|
||||
Text(item['description']),
|
||||
Text(item['description'] ?? 'No description provided yet'),
|
||||
Padding(padding: EdgeInsets.only(top: 6)),
|
||||
DefaultTextStyle(
|
||||
style: TextStyle(color: Colors.black54, fontSize: 13),
|
||||
@ -50,17 +50,21 @@ class RepoItem extends StatelessWidget {
|
||||
size: 14, color: Colors.black54),
|
||||
Text(item['forks']['totalCount'].toString()),
|
||||
Padding(padding: EdgeInsets.only(left: 16)),
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: new BoxDecoration(
|
||||
color:
|
||||
convertColor(item['primaryLanguage']['color']),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(left: 4)),
|
||||
Text(item['primaryLanguage']['name']),
|
||||
item['primaryLanguage'] == null
|
||||
? Container()
|
||||
: Row(children: <Widget>[
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: new BoxDecoration(
|
||||
color: convertColor(
|
||||
item['primaryLanguage']['color']),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(left: 4)),
|
||||
Text(item['primaryLanguage']['name']),
|
||||
]),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user