diff --git a/lib/scaffolds/refresh.dart b/lib/scaffolds/refresh.dart index eb38946..53cbbd1 100644 --- a/lib/scaffolds/refresh.dart +++ b/lib/scaffolds/refresh.dart @@ -8,16 +8,14 @@ import '../widgets/error_reload.dart'; class RefreshScaffold extends StatefulWidget { final Widget title; final Widget Function(T payload) bodyBuilder; - final Future Function(int activeTab) onRefresh; + final Future Function() onRefresh; final Widget Function(T payload) trailingBuilder; - final List tabs; RefreshScaffold({ @required this.title, @required this.bodyBuilder, @required this.onRefresh, this.trailingBuilder, - this.tabs, }); @override @@ -28,7 +26,6 @@ class _RefreshScaffoldState extends State> { bool _loading; T _payload; String _error = ''; - int _activeTab = 0; @override void initState() { @@ -46,16 +43,13 @@ class _RefreshScaffoldState extends State> { } } - Future _refresh([int activeTab]) async { + Future _refresh() async { try { setState(() { _error = ''; _loading = true; - if (activeTab != null) { - _activeTab = activeTab; - } }); - _payload = await widget.onRefresh(activeTab); + _payload = await widget.onRefresh(); } catch (err) { _error = err.toString(); throw err; @@ -86,22 +80,8 @@ class _RefreshScaffoldState extends State> { case AppThemeType.cupertino: return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( - middle: widget.tabs == null - ? widget.title - : DefaultTextStyle( - style: TextStyle(), - child: CupertinoSegmentedControl( - groupValue: _activeTab, - onValueChanged: _refresh, - children: widget.tabs.asMap().map((key, text) => MapEntry( - key, - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Text(text), - ))), - ), - ), - trailing: widget.tabs == null ? _buildTrailing() : null, + middle: widget.title, + trailing: _buildTrailing(), ), child: SafeArea( child: CustomScrollView( @@ -113,28 +93,16 @@ class _RefreshScaffoldState extends State> { ), ); default: - var w = Scaffold( + return Scaffold( appBar: AppBar( title: widget.title, actions: _buildActions(), - bottom: widget.tabs == null - ? null - : TabBar( - onTap: _refresh, - tabs: widget.tabs - .map((text) => Tab(text: text.toUpperCase())) - .toList(), - ), ), body: RefreshIndicator( onRefresh: _refresh, child: SingleChildScrollView(child: _buildBody()), ), ); - if (widget.tabs == null) { - return w; - } - return DefaultTabController(length: widget.tabs.length, child: w); } } } diff --git a/lib/scaffolds/tab.dart b/lib/scaffolds/tab.dart new file mode 100644 index 0000000..fbf36e6 --- /dev/null +++ b/lib/scaffolds/tab.dart @@ -0,0 +1,135 @@ +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 TabScaffold extends StatefulWidget { + final Widget title; + final Widget Function(T payload) bodyBuilder; + final Future Function(int activeTab) onRefresh; + final Widget Function(T payload) trailingBuilder; + final List tabs; + + TabScaffold({ + @required this.title, + @required this.bodyBuilder, + @required this.onRefresh, + this.trailingBuilder, + this.tabs, + }); + + @override + _TabScaffoldState createState() => _TabScaffoldState(); +} + +class _TabScaffoldState extends State> { + bool _loading; + T _payload; + String _error = ''; + int _activeTab = 0; + + @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 _refresh([int activeTab]) async { + try { + setState(() { + _error = ''; + _loading = true; + if (activeTab != null) { + _activeTab = activeTab; + } + }); + _payload = await widget.onRefresh(activeTab); + } 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 _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(context).theme) { + case AppThemeType.cupertino: + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: DefaultTextStyle( + style: TextStyle(), + child: CupertinoSegmentedControl( + groupValue: _activeTab, + onValueChanged: _refresh, + children: widget.tabs.asMap().map((key, text) => MapEntry( + key, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Text(text), + ))), + ), + ), + trailing: null, // TODO: + ), + child: SafeArea( + child: CustomScrollView( + slivers: [ + CupertinoSliverRefreshControl(onRefresh: _refresh), + SliverToBoxAdapter(child: _buildBody()) + ], + ), + ), + ); + default: + return DefaultTabController( + length: widget.tabs.length, + child: Scaffold( + appBar: AppBar( + title: widget.title, + actions: _buildActions(), + bottom: TabBar( + onTap: _refresh, + tabs: widget.tabs + .map((text) => Tab(text: text.toUpperCase())) + .toList(), + ), + ), + body: RefreshIndicator( + onRefresh: _refresh, + child: SingleChildScrollView(child: _buildBody()), + ), + ), + ); + } + } +} diff --git a/lib/screens/notifications.dart b/lib/screens/notifications.dart index fdec1ec..75b7026 100644 --- a/lib/screens/notifications.dart +++ b/lib/screens/notifications.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; -import 'package:git_touch/scaffolds/refresh.dart'; +import 'package:git_touch/scaffolds/tab.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 RefreshScaffold( + return TabScaffold( title: AppBarTitle('Notifications'), tabs: ['Unread', 'Paticipating', 'All'], // trailing: GestureDetector( diff --git a/lib/screens/object.dart b/lib/screens/object.dart index b44d4d5..fe1cdbd 100644 --- a/lib/screens/object.dart +++ b/lib/screens/object.dart @@ -145,7 +145,7 @@ class ObjectScreen extends StatelessWidget { Widget build(BuildContext context) { return RefreshScaffold( title: AppBarTitle(paths.join('/')), - onRefresh: (_) async { + onRefresh: () async { var data = await Provider.of(context).query('''{ repository(owner: "$owner", name: "$name") { object(expression: "$_expression") { diff --git a/lib/screens/organization.dart b/lib/screens/organization.dart index 75c0ba9..80d6c79 100644 --- a/lib/screens/organization.dart +++ b/lib/screens/organization.dart @@ -55,7 +55,7 @@ class OrganizationScreen extends StatelessWidget { @override Widget build(BuildContext context) { return RefreshScaffold( - onRefresh: (_) async { + onRefresh: () async { // Use pinnableItems instead of organization here due to token permission var data = await Provider.of(context).query(''' { diff --git a/lib/screens/repository.dart b/lib/screens/repository.dart index 50f8723..a959506 100644 --- a/lib/screens/repository.dart +++ b/lib/screens/repository.dart @@ -131,7 +131,7 @@ class RepositoryScreen extends StatelessWidget { Widget build(BuildContext context) { return RefreshScaffold( title: AppBarTitle('Repository'), - onRefresh: (_) => Future.wait([ + onRefresh: () => Future.wait([ queryRepo(context), fetchReadme(context), ]), diff --git a/lib/screens/user.dart b/lib/screens/user.dart index e78b3b4..4adcf12 100644 --- a/lib/screens/user.dart +++ b/lib/screens/user.dart @@ -139,7 +139,7 @@ class UserScreen extends StatelessWidget { @override Widget build(BuildContext context) { return RefreshScaffold( - onRefresh: (_) { + onRefresh: () { return Future.wait( [query(context), getContributions(login)], ); diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 9631d42..909bb46 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -8,7 +8,8 @@ import 'package:git_touch/screens/user.dart'; import 'package:intl/intl.dart'; import 'package:primer/primer.dart'; import 'package:provider/provider.dart'; -export 'package:flutter_vector_icons/flutter_vector_icons.dart'; +export 'package:flutter_vector_icons/flutter_vector_icons.dart' hide Octicons; +export 'package:primer/primer.dart' show Octicons; final monospaceFont = Platform.isIOS ? 'Menlo' : 'monospace'; // FIXME: @@ -55,7 +56,10 @@ TextSpan createLinkSpan( ) { return TextSpan( text: text, - style: TextStyle(color: PrimerColors.blue500, fontWeight: FontWeight.w600), + style: TextStyle( + color: PrimerColors.blue500, + fontWeight: FontWeight.w600, + ), recognizer: TapGestureRecognizer() ..onTap = () { Provider.of(context).pushRoute(context, builder);