mirror of
https://github.com/git-touch/git-touch
synced 2025-03-05 11:48:02 +01:00
refactor: scaffolds
This commit is contained in:
parent
c79f7abdba
commit
1226e8ff8d
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:git_touch/models/theme.dart';
|
||||
import 'package:git_touch/scaffolds/utils.dart';
|
||||
import 'package:git_touch/utils/utils.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../widgets/error_reload.dart';
|
||||
@ -17,14 +18,14 @@ class ListPayload<T, K> {
|
||||
}
|
||||
|
||||
// This is a scaffold for infinite scroll screens
|
||||
class ListScaffold<T, K> extends StatefulWidget {
|
||||
class ListStatefulScaffold<T, K> extends StatefulWidget {
|
||||
final Widget title;
|
||||
final Widget Function({Function({bool force}) refresh}) trailingBuiler;
|
||||
final Widget Function() trailingBuiler;
|
||||
final Widget Function(T payload) itemBuilder;
|
||||
final Future<ListPayload<T, K>> Function() onRefresh;
|
||||
final Future<ListPayload<T, K>> Function(K cursor) onLoadMore;
|
||||
|
||||
ListScaffold({
|
||||
ListStatefulScaffold({
|
||||
@required this.title,
|
||||
@required this.itemBuilder,
|
||||
@required this.onRefresh,
|
||||
@ -33,10 +34,12 @@ class ListScaffold<T, K> extends StatefulWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
_ListScaffoldState<T, K> createState() => _ListScaffoldState();
|
||||
_ListStatefulScaffoldState<T, K> createState() =>
|
||||
_ListStatefulScaffoldState();
|
||||
}
|
||||
|
||||
class _ListScaffoldState<T, K> extends State<ListScaffold<T, K>> {
|
||||
class _ListStatefulScaffoldState<T, K>
|
||||
extends State<ListStatefulScaffold<T, K>> {
|
||||
bool loading = false;
|
||||
bool loadingMore = false;
|
||||
String error = '';
|
||||
@ -145,7 +148,7 @@ class _ListScaffoldState<T, K> extends State<ListScaffold<T, K>> {
|
||||
return widget.itemBuilder(items[index ~/ 2]);
|
||||
}
|
||||
|
||||
Widget _buildSliver(BuildContext context) {
|
||||
Widget _buildCupertinoSliver() {
|
||||
if (error.isNotEmpty) {
|
||||
return SliverToBoxAdapter(
|
||||
child: ErrorReload(text: error, onTap: _refresh),
|
||||
@ -164,7 +167,7 @@ class _ListScaffoldState<T, K> extends State<ListScaffold<T, K>> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildBody(BuildContext context) {
|
||||
Widget _buildMaterial() {
|
||||
if (error.isNotEmpty) {
|
||||
return ErrorReload(text: error, onTap: _refresh);
|
||||
} else if (loading && items.isEmpty) {
|
||||
@ -181,45 +184,30 @@ class _ListScaffoldState<T, K> extends State<ListScaffold<T, K>> {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget _buildBody() {
|
||||
switch (Provider.of<ThemeModel>(context).theme) {
|
||||
case AppThemeType.cupertino:
|
||||
List<Widget> slivers = [
|
||||
CupertinoSliverRefreshControl(onRefresh: _refresh)
|
||||
];
|
||||
// if (widget.header != null) {
|
||||
// slivers.add(SliverToBoxAdapter(child: widget.header));
|
||||
// }
|
||||
slivers.add(_buildSliver(context));
|
||||
|
||||
return CupertinoPageScaffold(
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
middle: widget.title,
|
||||
trailing: widget.trailingBuiler == null
|
||||
? null
|
||||
: widget.trailingBuiler(refresh: _refresh),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: CustomScrollView(
|
||||
controller: _controller,
|
||||
slivers: slivers,
|
||||
),
|
||||
),
|
||||
return CustomScrollView(
|
||||
controller: _controller,
|
||||
slivers: [
|
||||
CupertinoSliverRefreshControl(onRefresh: _refresh),
|
||||
_buildCupertinoSliver(),
|
||||
],
|
||||
);
|
||||
default:
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: widget.title,
|
||||
actions: widget.trailingBuiler == null
|
||||
? null
|
||||
: [widget.trailingBuiler(refresh: _refresh)],
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: _refresh,
|
||||
child: _buildBody(context),
|
||||
),
|
||||
return RefreshIndicator(
|
||||
onRefresh: _refresh,
|
||||
child: _buildMaterial(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonScaffold(
|
||||
title: widget.title,
|
||||
body: _buildBody(),
|
||||
trailing: widget.trailingBuiler == null ? null : widget.trailingBuiler(),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:git_touch/models/theme.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../widgets/loading.dart';
|
||||
import '../widgets/error_reload.dart';
|
||||
|
||||
class RefreshScaffold<T> extends StatefulWidget {
|
||||
final Widget title;
|
||||
final Widget Function(T payload) bodyBuilder;
|
||||
final Future<T> Function() onRefresh;
|
||||
final Widget Function(T payload) trailingBuilder;
|
||||
|
||||
RefreshScaffold({
|
||||
@required this.title,
|
||||
@required this.bodyBuilder,
|
||||
@required this.onRefresh,
|
||||
this.trailingBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
_RefreshScaffoldState createState() => _RefreshScaffoldState();
|
||||
}
|
||||
|
||||
class _RefreshScaffoldState<T> extends State<RefreshScaffold<T>> {
|
||||
bool _loading;
|
||||
T _payload;
|
||||
String _error = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_refresh();
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
if (_error.isNotEmpty) {
|
||||
return ErrorReload(text: _error, onTap: _refresh);
|
||||
} else if (_payload == null) {
|
||||
return Loading(more: false);
|
||||
} else {
|
||||
return widget.bodyBuilder(_payload);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _refresh() async {
|
||||
try {
|
||||
setState(() {
|
||||
_error = '';
|
||||
_loading = true;
|
||||
});
|
||||
_payload = await widget.onRefresh();
|
||||
} catch (err) {
|
||||
_error = err.toString();
|
||||
throw err;
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTrailing() {
|
||||
if (_payload == null || widget.trailingBuilder == null) return null;
|
||||
|
||||
return widget.trailingBuilder(_payload);
|
||||
}
|
||||
|
||||
List<Widget> _buildActions() {
|
||||
if (_payload == null || widget.trailingBuilder == null) return null;
|
||||
var w = widget.trailingBuilder(_payload);
|
||||
return [if (w != null) w];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
switch (Provider.of<ThemeModel>(context).theme) {
|
||||
case AppThemeType.cupertino:
|
||||
return CupertinoPageScaffold(
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
middle: widget.title,
|
||||
trailing: _buildTrailing(),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
CupertinoSliverRefreshControl(onRefresh: _refresh),
|
||||
SliverToBoxAdapter(child: _buildBody())
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
default:
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: widget.title,
|
||||
actions: _buildActions(),
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: _refresh,
|
||||
child: SingleChildScrollView(child: _buildBody()),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
79
lib/scaffolds/refresh_stateful.dart
Normal file
79
lib/scaffolds/refresh_stateful.dart
Normal file
@ -0,0 +1,79 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:git_touch/scaffolds/utils.dart';
|
||||
import '../widgets/loading.dart';
|
||||
import '../widgets/error_reload.dart';
|
||||
|
||||
class RefreshStatefulScaffold<T> extends StatefulWidget {
|
||||
final Widget title;
|
||||
final Widget Function(T payload) bodyBuilder;
|
||||
final Future<T> Function() onRefresh;
|
||||
final Widget Function(T payload) trailingBuilder;
|
||||
|
||||
RefreshStatefulScaffold({
|
||||
@required this.title,
|
||||
@required this.bodyBuilder,
|
||||
@required this.onRefresh,
|
||||
this.trailingBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
_RefreshStatefulScaffoldState createState() =>
|
||||
_RefreshStatefulScaffoldState();
|
||||
}
|
||||
|
||||
class _RefreshStatefulScaffoldState<T>
|
||||
extends State<RefreshStatefulScaffold<T>> {
|
||||
bool _loading;
|
||||
T _payload;
|
||||
String _error = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_refresh();
|
||||
}
|
||||
|
||||
Future<void> _refresh() async {
|
||||
try {
|
||||
setState(() {
|
||||
_error = '';
|
||||
_loading = true;
|
||||
});
|
||||
_payload = await widget.onRefresh();
|
||||
} catch (err) {
|
||||
_error = err.toString();
|
||||
throw err;
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget get _trailing {
|
||||
if (_payload == null || widget.trailingBuilder == null) return null;
|
||||
return widget.trailingBuilder(_payload);
|
||||
}
|
||||
|
||||
Widget get _body {
|
||||
if (_error.isNotEmpty) {
|
||||
return ErrorReload(text: _error, onTap: _refresh);
|
||||
} else if (_payload == null) {
|
||||
return Loading(more: false);
|
||||
} else {
|
||||
return widget.bodyBuilder(_payload);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonScaffold(
|
||||
title: widget.title,
|
||||
body: RefreshWrapper(onRefresh: _refresh, body: _body),
|
||||
trailing: _trailing,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:git_touch/models/theme.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class SimpleScaffold extends StatelessWidget {
|
||||
final Widget title;
|
||||
final Widget child;
|
||||
final Widget trailing;
|
||||
final List<Widget> actions;
|
||||
final PreferredSizeWidget bottom;
|
||||
|
||||
SimpleScaffold({
|
||||
@required this.title,
|
||||
@required this.child,
|
||||
this.trailing,
|
||||
this.actions,
|
||||
this.bottom,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
switch (Provider.of<ThemeModel>(context).theme) {
|
||||
case AppThemeType.cupertino:
|
||||
return CupertinoPageScaffold(
|
||||
navigationBar:
|
||||
CupertinoNavigationBar(middle: title, trailing: trailing),
|
||||
child: SafeArea(
|
||||
child: SingleChildScrollView(child: child),
|
||||
),
|
||||
);
|
||||
default:
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: title, actions: actions, bottom: bottom),
|
||||
body: SingleChildScrollView(child: child),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
23
lib/scaffolds/single.dart
Normal file
23
lib/scaffolds/single.dart
Normal file
@ -0,0 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:git_touch/scaffolds/utils.dart';
|
||||
|
||||
class SingleScaffold extends StatelessWidget {
|
||||
final Widget title;
|
||||
final Widget body;
|
||||
final Widget trailing;
|
||||
|
||||
SingleScaffold({
|
||||
@required this.title,
|
||||
@required this.body,
|
||||
this.trailing,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonScaffold(
|
||||
title: title,
|
||||
body: SingleChildScrollView(child: body),
|
||||
trailing: trailing,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,171 +1,77 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:git_touch/models/theme.dart';
|
||||
import 'package:git_touch/scaffolds/utils.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../widgets/loading.dart';
|
||||
import '../widgets/error_reload.dart';
|
||||
|
||||
class TabScaffold<T> extends StatefulWidget {
|
||||
final Widget title;
|
||||
final Widget Function(T payload, int activeTab) bodyBuilder;
|
||||
final Future<T> Function(int activeTab) onRefresh;
|
||||
class CommonTabPayload {
|
||||
final List<String> tabs;
|
||||
final Widget Function(T payload) trailingBuilder;
|
||||
final int activeTab;
|
||||
final Function(int active) onTabSwitch;
|
||||
CommonTabPayload({
|
||||
@required this.tabs,
|
||||
@required this.activeTab,
|
||||
@required this.onTabSwitch,
|
||||
});
|
||||
}
|
||||
|
||||
class TabScaffold extends StatelessWidget {
|
||||
final Widget title;
|
||||
final Widget body;
|
||||
final Widget trailing;
|
||||
final void Function() onRefresh;
|
||||
final CommonTabPayload tabPayload;
|
||||
|
||||
TabScaffold({
|
||||
@required this.title,
|
||||
@required this.bodyBuilder,
|
||||
@required this.body,
|
||||
this.trailing,
|
||||
@required this.onRefresh,
|
||||
@required this.tabs,
|
||||
this.trailingBuilder,
|
||||
@required this.tabPayload,
|
||||
});
|
||||
|
||||
@override
|
||||
_TabScaffoldState createState() => _TabScaffoldState();
|
||||
}
|
||||
|
||||
class _TabScaffoldState<T> extends State<TabScaffold<T>> {
|
||||
bool _loading;
|
||||
T _payload0;
|
||||
T _payload1;
|
||||
T _payload2;
|
||||
String _error = '';
|
||||
int _activeTab = 0;
|
||||
|
||||
T _getPayload(int selected) {
|
||||
switch (selected) {
|
||||
case 0:
|
||||
return _payload0;
|
||||
case 1:
|
||||
return _payload1;
|
||||
case 2:
|
||||
return _payload2;
|
||||
Widget _buildTitle(BuildContext context) {
|
||||
switch (Provider.of<ThemeModel>(context).theme) {
|
||||
case AppThemeType.cupertino:
|
||||
return DefaultTextStyle(
|
||||
style: TextStyle(fontSize: 16),
|
||||
child: CupertinoSegmentedControl(
|
||||
groupValue: tabPayload.activeTab,
|
||||
onValueChanged: tabPayload.onTabSwitch,
|
||||
children: tabPayload.tabs.asMap().map((key, text) => MapEntry(
|
||||
key,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text(text),
|
||||
))),
|
||||
),
|
||||
);
|
||||
default:
|
||||
throw '';
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
||||
T get _payload => _getPayload(_activeTab);
|
||||
|
||||
set _payload(T v) {
|
||||
switch (_activeTab) {
|
||||
case 0:
|
||||
_payload0 = v;
|
||||
break;
|
||||
case 1:
|
||||
_payload1 = v;
|
||||
break;
|
||||
case 2:
|
||||
_payload2 = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_refresh();
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
if (_error.isNotEmpty) {
|
||||
return ErrorReload(text: _error, onTap: _refresh);
|
||||
} else if (_payload == null) {
|
||||
return Loading(more: false);
|
||||
} else {
|
||||
return widget.bodyBuilder(_payload, _activeTab);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _refresh() async {
|
||||
try {
|
||||
setState(() {
|
||||
_error = '';
|
||||
_loading = true;
|
||||
});
|
||||
_payload = await widget.onRefresh(_activeTab);
|
||||
} catch (err) {
|
||||
_error = err.toString();
|
||||
throw err;
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _switch(int selected) async {
|
||||
setState(() {
|
||||
_activeTab = selected;
|
||||
});
|
||||
if (_getPayload(selected) == null) {
|
||||
await _refresh();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTrailing() {
|
||||
if (_payload == null || widget.trailingBuilder == null) return null;
|
||||
|
||||
return widget.trailingBuilder(_payload);
|
||||
}
|
||||
|
||||
List<Widget> _buildActions() {
|
||||
if (_payload == null || widget.trailingBuilder == null) return null;
|
||||
var w = widget.trailingBuilder(_payload);
|
||||
return [if (w != null) w];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scaffold = CommonScaffold(
|
||||
title: _buildTitle(context),
|
||||
body: RefreshWrapper(body: body, onRefresh: onRefresh),
|
||||
trailing: trailing,
|
||||
bottom: TabBar(
|
||||
onTap: tabPayload.onTabSwitch,
|
||||
tabs: tabPayload.tabs
|
||||
.map((text) => Tab(text: text.toUpperCase()))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
|
||||
switch (Provider.of<ThemeModel>(context).theme) {
|
||||
case AppThemeType.cupertino:
|
||||
return CupertinoPageScaffold(
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
middle: DefaultTextStyle(
|
||||
style: TextStyle(fontSize: 16),
|
||||
child: CupertinoSegmentedControl(
|
||||
groupValue: _activeTab,
|
||||
onValueChanged: _switch,
|
||||
children: widget.tabs.asMap().map((key, text) => MapEntry(
|
||||
key,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text(text),
|
||||
))),
|
||||
),
|
||||
),
|
||||
trailing: null, // TODO:
|
||||
),
|
||||
child: SafeArea(
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
CupertinoSliverRefreshControl(onRefresh: _refresh),
|
||||
SliverToBoxAdapter(child: _buildBody())
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
return scaffold;
|
||||
default:
|
||||
return DefaultTabController(
|
||||
length: widget.tabs.length,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: widget.title,
|
||||
actions: _buildActions(),
|
||||
bottom: TabBar(
|
||||
onTap: _switch,
|
||||
tabs: widget.tabs
|
||||
.map((text) => Tab(text: text.toUpperCase()))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: _refresh,
|
||||
child: SingleChildScrollView(child: _buildBody()),
|
||||
),
|
||||
),
|
||||
length: tabPayload.tabs.length,
|
||||
child: scaffold,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
126
lib/scaffolds/tab_stateful.dart
Normal file
126
lib/scaffolds/tab_stateful.dart
Normal file
@ -0,0 +1,126 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:git_touch/scaffolds/tab.dart';
|
||||
import '../widgets/loading.dart';
|
||||
import '../widgets/error_reload.dart';
|
||||
|
||||
class TabStatefulScaffold<T> extends StatefulWidget {
|
||||
final Widget title;
|
||||
final Widget Function(T payload, int activeTab) bodyBuilder;
|
||||
final Future<T> Function(int activeTab) onRefresh;
|
||||
final List<String> tabs;
|
||||
final Widget Function(T payload) trailingBuilder;
|
||||
|
||||
TabStatefulScaffold({
|
||||
@required this.title,
|
||||
@required this.bodyBuilder,
|
||||
@required this.onRefresh,
|
||||
@required this.tabs,
|
||||
this.trailingBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
_TabStatefulScaffoldState createState() => _TabStatefulScaffoldState();
|
||||
}
|
||||
|
||||
class _TabStatefulScaffoldState<T> extends State<TabStatefulScaffold<T>> {
|
||||
bool _loading;
|
||||
T _payload0;
|
||||
T _payload1;
|
||||
T _payload2;
|
||||
String _error = '';
|
||||
int _activeTab = 0;
|
||||
|
||||
T _getPayload(int selected) {
|
||||
switch (selected) {
|
||||
case 0:
|
||||
return _payload0;
|
||||
case 1:
|
||||
return _payload1;
|
||||
case 2:
|
||||
return _payload2;
|
||||
default:
|
||||
throw '';
|
||||
}
|
||||
}
|
||||
|
||||
T get _payload => _getPayload(_activeTab);
|
||||
|
||||
set _payload(T v) {
|
||||
switch (_activeTab) {
|
||||
case 0:
|
||||
_payload0 = v;
|
||||
break;
|
||||
case 1:
|
||||
_payload1 = v;
|
||||
break;
|
||||
case 2:
|
||||
_payload2 = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_refresh();
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
if (_error.isNotEmpty) {
|
||||
return ErrorReload(text: _error, onTap: _refresh);
|
||||
} else if (_payload == null) {
|
||||
return Loading(more: false);
|
||||
} else {
|
||||
return widget.bodyBuilder(_payload, _activeTab);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _refresh() async {
|
||||
try {
|
||||
setState(() {
|
||||
_error = '';
|
||||
_loading = true;
|
||||
});
|
||||
_payload = await widget.onRefresh(_activeTab);
|
||||
} catch (err) {
|
||||
_error = err.toString();
|
||||
throw err;
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _switch(int selected) async {
|
||||
setState(() {
|
||||
_activeTab = selected;
|
||||
});
|
||||
if (_getPayload(selected) == null) {
|
||||
await _refresh();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTrailing() {
|
||||
if (_payload == null || widget.trailingBuilder == null) return null;
|
||||
return widget.trailingBuilder(_payload);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TabScaffold(
|
||||
title: widget.title,
|
||||
trailing: _buildTrailing(),
|
||||
tabPayload: CommonTabPayload(
|
||||
tabs: widget.tabs,
|
||||
activeTab: _activeTab,
|
||||
onTabSwitch: _switch,
|
||||
),
|
||||
onRefresh: _refresh,
|
||||
body: _buildBody(),
|
||||
);
|
||||
}
|
||||
}
|
69
lib/scaffolds/utils.dart
Normal file
69
lib/scaffolds/utils.dart
Normal file
@ -0,0 +1,69 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:git_touch/models/theme.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class CommonScaffold extends StatelessWidget {
|
||||
final Widget title;
|
||||
final Widget body;
|
||||
final Widget trailing;
|
||||
final PreferredSizeWidget bottom;
|
||||
|
||||
CommonScaffold({
|
||||
@required this.title,
|
||||
@required this.body,
|
||||
this.trailing,
|
||||
this.bottom,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
switch (Provider.of<ThemeModel>(context).theme) {
|
||||
case AppThemeType.cupertino:
|
||||
return CupertinoPageScaffold(
|
||||
navigationBar:
|
||||
CupertinoNavigationBar(middle: title, trailing: trailing),
|
||||
child: SafeArea(child: body),
|
||||
);
|
||||
default:
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: title,
|
||||
actions: [
|
||||
if (trailing != null) trailing,
|
||||
],
|
||||
bottom: bottom,
|
||||
),
|
||||
body: body,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RefreshWrapper extends StatelessWidget {
|
||||
final Widget body;
|
||||
final void Function() onRefresh;
|
||||
|
||||
RefreshWrapper({
|
||||
@required this.onRefresh,
|
||||
@required this.body,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
switch (Provider.of<ThemeModel>(context).theme) {
|
||||
case AppThemeType.cupertino:
|
||||
return CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
CupertinoSliverRefreshControl(onRefresh: onRefresh),
|
||||
SliverToBoxAdapter(child: body),
|
||||
],
|
||||
);
|
||||
default:
|
||||
return RefreshIndicator(
|
||||
onRefresh: onRefresh,
|
||||
child: SingleChildScrollView(child: body),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_highlight/flutter_highlight.dart';
|
||||
import 'package:flutter_highlight/theme_map.dart';
|
||||
import 'package:git_touch/models/code.dart';
|
||||
import 'package:git_touch/scaffolds/simple.dart';
|
||||
import 'package:git_touch/scaffolds/single.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import 'package:git_touch/widgets/picker.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -18,9 +18,9 @@ class CodeSettingsScreen extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
var codeProvider = Provider.of<CodeModel>(context);
|
||||
|
||||
return SimpleScaffold(
|
||||
return SingleScaffold(
|
||||
title: AppBarTitle('Code theme'),
|
||||
child: Column(
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
PickerGroup(
|
||||
|
@ -1,11 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:git_touch/models/settings.dart';
|
||||
import 'package:git_touch/scaffolds/list_stateful.dart';
|
||||
import 'package:git_touch/utils/utils.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import 'package:git_touch/widgets/link.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:timeago/timeago.dart' as timeago;
|
||||
import 'package:git_touch/scaffolds/list.dart';
|
||||
import 'package:git_touch/widgets/avatar.dart';
|
||||
import 'package:primer/primer.dart';
|
||||
|
||||
@ -82,7 +82,7 @@ class CommitsScreen extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListScaffold(
|
||||
return ListStatefulScaffold(
|
||||
title: AppBarTitle('Commits'),
|
||||
onRefresh: () => _query(context),
|
||||
onLoadMore: (cursor) => _query(context, cursor),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:git_touch/scaffolds/simple.dart';
|
||||
import 'package:git_touch/scaffolds/single.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
|
||||
class ImageView extends StatelessWidget {
|
||||
@ -9,9 +9,9 @@ class ImageView extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SimpleScaffold(
|
||||
return SingleScaffold(
|
||||
title: AppBarTitle('Image preview'),
|
||||
child: Container(
|
||||
body: Container(
|
||||
child: Image(image: imageProvider),
|
||||
),
|
||||
);
|
||||
|
@ -1,11 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:git_touch/models/settings.dart';
|
||||
import 'package:git_touch/scaffolds/list_stateful.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import 'package:git_touch/widgets/avatar.dart';
|
||||
import 'package:primer/primer.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:timeago/timeago.dart' as timeago;
|
||||
import '../scaffolds/list.dart';
|
||||
import '../utils/utils.dart';
|
||||
import '../widgets/link.dart';
|
||||
import '../screens/issue.dart';
|
||||
@ -191,7 +191,7 @@ class IssuesScreen extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListScaffold(
|
||||
return ListStatefulScaffold(
|
||||
title: AppBarTitle(
|
||||
(isPullRequest ? 'Pull requests' : 'Issues') + ' of $owner/$name'),
|
||||
onRefresh: () => _query(context),
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:git_touch/models/settings.dart';
|
||||
import 'package:git_touch/scaffolds/single.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../scaffolds/simple.dart';
|
||||
import '../widgets/link.dart';
|
||||
import '../widgets/loading.dart';
|
||||
import '../models/account.dart';
|
||||
@ -90,9 +90,9 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
});
|
||||
});
|
||||
|
||||
return SimpleScaffold(
|
||||
return SingleScaffold(
|
||||
title: AppBarTitle('Select account'),
|
||||
child: settings.loading
|
||||
body: settings.loading
|
||||
? Center(child: Loading())
|
||||
: Container(
|
||||
child: Column(
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:git_touch/models/settings.dart';
|
||||
import 'package:git_touch/scaffolds/single.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../scaffolds/simple.dart';
|
||||
|
||||
class LoginGitlabScreen extends StatefulWidget {
|
||||
@override
|
||||
@ -15,9 +15,9 @@ class _LoginGitlabScreenState extends State<LoginGitlabScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SimpleScaffold(
|
||||
return SingleScaffold(
|
||||
title: AppBarTitle('Login to GitLab'),
|
||||
child: Column(
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
TextField(
|
||||
// decoration: InputDecoration(icon: Icon(Icons.more_vert)),
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:git_touch/models/notification.dart';
|
||||
import 'package:git_touch/scaffolds/list_stateful.dart';
|
||||
import 'package:git_touch/utils/utils.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../scaffolds/list.dart';
|
||||
import '../widgets/event_item.dart';
|
||||
import 'package:git_touch/models/settings.dart';
|
||||
|
||||
@ -67,7 +67,7 @@ class NewsScreenState extends State<NewsScreen> {
|
||||
|
||||
@override
|
||||
Widget build(context) {
|
||||
return ListScaffold<EventPayload, int>(
|
||||
return ListStatefulScaffold<EventPayload, int>(
|
||||
title: AppBarTitle('News'),
|
||||
itemBuilder: (payload) => EventItem(payload),
|
||||
onRefresh: fetchEvents,
|
||||
|
@ -1,13 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:git_touch/scaffolds/single.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import '../scaffolds/simple.dart';
|
||||
|
||||
class NotFoundScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SimpleScaffold(
|
||||
return SingleScaffold(
|
||||
title: AppBarTitle('Not Found'),
|
||||
child: Text('Woops, This page is not implemented yet'),
|
||||
body: Text('Woops, This page is not implemented yet'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:git_touch/scaffolds/tab.dart';
|
||||
import 'package:git_touch/scaffolds/tab_stateful.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:git_touch/models/notification.dart';
|
||||
@ -142,7 +142,7 @@ $key: pullRequest(number: ${item.number}) {
|
||||
|
||||
@override
|
||||
Widget build(context) {
|
||||
return TabScaffold(
|
||||
return TabStatefulScaffold(
|
||||
title: AppBarTitle('Notifications'),
|
||||
tabs: ['Unread', 'Paticipating', 'All'],
|
||||
// trailing: GestureDetector(
|
||||
@ -167,19 +167,19 @@ $key: pullRequest(number: ${item.number}) {
|
||||
// _onSwitchTab(value);
|
||||
// },
|
||||
// ),
|
||||
trailingBuilder: (_) => IconButton(
|
||||
icon: Icon(Icons.done_all),
|
||||
onPressed: () async {
|
||||
// TODO:
|
||||
// var value = await Provider.of<ThemeModel>(context)
|
||||
// .showConfirm(context, 'Mark all as read?');
|
||||
// if (value) {
|
||||
// await Provider.of<SettingsModel>(context)
|
||||
// .putWithCredentials('/notifications');
|
||||
// await fetchNotifications(0);
|
||||
// }
|
||||
},
|
||||
),
|
||||
// trailingBuilder: (_) => IconButton(
|
||||
// icon: Icon(Icons.done_all),
|
||||
// onPressed: () async {
|
||||
// // TODO:
|
||||
// // var value = await Provider.of<ThemeModel>(context)
|
||||
// // .showConfirm(context, 'Mark all as read?');
|
||||
// // if (value) {
|
||||
// // await Provider.of<SettingsModel>(context)
|
||||
// // .putWithCredentials('/notifications');
|
||||
// // await fetchNotifications(0);
|
||||
// // }
|
||||
// },
|
||||
// ),
|
||||
onRefresh: fetchNotifications,
|
||||
bodyBuilder: (groupMap, activeTab) {
|
||||
if (groupMap.isEmpty) return EmptyWidget();
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_highlight/theme_map.dart';
|
||||
import 'package:git_touch/models/code.dart';
|
||||
import 'package:git_touch/scaffolds/refresh_stateful.dart';
|
||||
import 'package:git_touch/screens/code_settings.dart';
|
||||
import 'package:git_touch/screens/image_view.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
@ -12,7 +13,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_highlight/flutter_highlight.dart';
|
||||
import 'package:git_touch/models/settings.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:git_touch/scaffolds/refresh.dart';
|
||||
import 'package:git_touch/utils/utils.dart';
|
||||
import 'package:primer/primer.dart';
|
||||
import 'package:seti/seti.dart';
|
||||
@ -143,7 +143,7 @@ class ObjectScreen extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RefreshScaffold(
|
||||
return RefreshStatefulScaffold(
|
||||
title: AppBarTitle(paths.join('/')),
|
||||
onRefresh: () async {
|
||||
var data = await Provider.of<SettingsModel>(context).query('''{
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:git_touch/scaffolds/refresh_stateful.dart';
|
||||
import 'package:git_touch/screens/repositories.dart';
|
||||
import 'package:git_touch/screens/users.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
@ -11,7 +12,6 @@ import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:share/share.dart';
|
||||
import 'package:git_touch/models/settings.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../scaffolds/refresh.dart';
|
||||
import '../widgets/action.dart';
|
||||
import '../utils/utils.dart';
|
||||
|
||||
@ -54,7 +54,7 @@ class OrganizationScreen extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RefreshScaffold(
|
||||
return RefreshStatefulScaffold(
|
||||
onRefresh: () async {
|
||||
// Use pinnableItems instead of organization here due to token permission
|
||||
var data = await Provider.of<SettingsModel>(context).query('''
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:git_touch/scaffolds/list_stateful.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import '../scaffolds/list.dart';
|
||||
import 'package:git_touch/models/settings.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../utils/utils.dart';
|
||||
@ -67,7 +67,7 @@ class RepositoriesScreen extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListScaffold(
|
||||
return ListStatefulScaffold(
|
||||
title: AppBarTitle(title),
|
||||
onRefresh: () => _queryRepos(context),
|
||||
onLoadMore: (cursor) => _queryRepos(context, cursor),
|
||||
|
@ -3,6 +3,7 @@ import 'package:filesize/filesize.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:git_touch/models/settings.dart';
|
||||
import 'package:git_touch/scaffolds/refresh_stateful.dart';
|
||||
import 'package:git_touch/screens/users.dart';
|
||||
import 'package:git_touch/utils/utils.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
@ -14,7 +15,6 @@ import 'package:git_touch/screens/commits.dart';
|
||||
import 'package:git_touch/screens/object.dart';
|
||||
import 'package:share/share.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../scaffolds/refresh.dart';
|
||||
import 'package:git_touch/widgets/repository_item.dart';
|
||||
import '../widgets/entry_item.dart';
|
||||
import '../screens/issues.dart';
|
||||
@ -129,7 +129,7 @@ class RepositoryScreen extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RefreshScaffold(
|
||||
return RefreshStatefulScaffold(
|
||||
title: AppBarTitle('Repository'),
|
||||
onRefresh: () => Future.wait([
|
||||
queryRepo(context),
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:git_touch/models/theme.dart';
|
||||
import 'package:git_touch/scaffolds/single.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:git_touch/models/settings.dart';
|
||||
import '../scaffolds/simple.dart';
|
||||
import '../utils/utils.dart';
|
||||
import 'package:git_touch/widgets/repository_item.dart';
|
||||
import '../widgets/loading.dart';
|
||||
@ -60,9 +60,9 @@ class _SearchScreenState extends State<SearchScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SimpleScaffold(
|
||||
return SingleScaffold(
|
||||
title: AppBarTitle('Search GitHub Repositories'),
|
||||
child: Column(
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Container(padding: EdgeInsets.all(8), child: _buildInput()),
|
||||
loading
|
||||
|
@ -1,13 +1,13 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:git_touch/models/theme.dart';
|
||||
import 'package:git_touch/scaffolds/single.dart';
|
||||
import 'package:git_touch/screens/object.dart';
|
||||
import 'package:git_touch/screens/repository.dart';
|
||||
import 'package:git_touch/utils/utils.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:launch_review/launch_review.dart';
|
||||
import '../scaffolds/simple.dart';
|
||||
import '../widgets/table_view.dart';
|
||||
import '../screens/login.dart';
|
||||
|
||||
@ -21,9 +21,9 @@ class SettingsScreen extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
var themeProvider = Provider.of<ThemeModel>(context);
|
||||
|
||||
return SimpleScaffold(
|
||||
return SingleScaffold(
|
||||
title: AppBarTitle('Settings'),
|
||||
child: Column(
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
borderView1,
|
||||
TableView(headerText: 'ACCOUNTS', items: [
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:git_touch/scaffolds/tab.dart';
|
||||
import 'package:git_touch/scaffolds/tab_stateful.dart';
|
||||
import 'package:git_touch/utils/utils.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import 'package:git_touch/widgets/user_item.dart';
|
||||
@ -9,7 +9,7 @@ import 'package:git_touch/widgets/repository_item.dart';
|
||||
|
||||
class TrendingScreen extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return TabScaffold(
|
||||
return TabStatefulScaffold(
|
||||
title: AppBarTitle('Trending'),
|
||||
tabs: ['Repositories', 'Users'],
|
||||
onRefresh: (tabIndex) async {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:git_touch/scaffolds/refresh_stateful.dart';
|
||||
import 'package:git_touch/screens/repositories.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import 'package:git_touch/widgets/table_view.dart';
|
||||
@ -9,7 +10,6 @@ import 'package:share/share.dart';
|
||||
import 'package:github_contributions/github_contributions.dart';
|
||||
import 'package:git_touch/models/settings.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../scaffolds/refresh.dart';
|
||||
import '../widgets/entry_item.dart';
|
||||
import 'package:git_touch/widgets/repository_item.dart';
|
||||
import '../widgets/link.dart';
|
||||
@ -138,7 +138,7 @@ class UserScreen extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RefreshScaffold(
|
||||
return RefreshStatefulScaffold(
|
||||
onRefresh: () {
|
||||
return Future.wait(
|
||||
[query(context), getContributions(login)],
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:git_touch/scaffolds/list_stateful.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import 'package:git_touch/widgets/user_item.dart';
|
||||
import '../scaffolds/list.dart';
|
||||
import 'package:git_touch/models/settings.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../utils/utils.dart';
|
||||
@ -69,7 +69,7 @@ class UsersScreen extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListScaffold(
|
||||
return ListStatefulScaffold(
|
||||
title: AppBarTitle(title),
|
||||
onRefresh: () => _queryUsers(context),
|
||||
onLoadMore: (cursor) => _queryUsers(context, cursor),
|
||||
|
Loading…
x
Reference in New Issue
Block a user