From 86e49d75cf643ca0a1e472ccf9bc9d47a0061216 Mon Sep 17 00:00:00 2001 From: Rongjian Zhang Date: Tue, 24 Sep 2019 01:46:50 +0800 Subject: [PATCH] refactor: refresh scaffold supports tabs --- lib/scaffolds/refresh.dart | 77 +++++++++++------ lib/scaffolds/refresh_stateless.dart | 74 ----------------- lib/screens/notifications.dart | 119 ++++++--------------------- lib/screens/object.dart | 2 +- lib/screens/organization.dart | 2 +- lib/screens/repository.dart | 2 +- lib/screens/trending.dart | 54 ++++++------ lib/screens/user.dart | 2 +- lib/widgets/table_view.dart | 2 +- 9 files changed, 108 insertions(+), 226 deletions(-) delete mode 100644 lib/scaffolds/refresh_stateless.dart diff --git a/lib/scaffolds/refresh.dart b/lib/scaffolds/refresh.dart index b04325f..eb38946 100644 --- a/lib/scaffolds/refresh.dart +++ b/lib/scaffolds/refresh.dart @@ -5,20 +5,19 @@ import 'package:provider/provider.dart'; import '../widgets/loading.dart'; import '../widgets/error_reload.dart'; -// This is a scaffold for pull to refresh class RefreshScaffold extends StatefulWidget { final Widget title; final Widget Function(T payload) bodyBuilder; - final Future Function() onRefresh; + final Future Function(int activeTab) onRefresh; final Widget Function(T payload) trailingBuilder; - // final List Function(T payload) actionsBuilder; + final List tabs; RefreshScaffold({ @required this.title, @required this.bodyBuilder, @required this.onRefresh, this.trailingBuilder, - // this.actionsBuilder, + this.tabs, }); @override @@ -26,9 +25,10 @@ class RefreshScaffold extends StatefulWidget { } class _RefreshScaffoldState extends State> { - bool loading; - T payload; - String error = ''; + bool _loading; + T _payload; + String _error = ''; + int _activeTab = 0; @override void initState() { @@ -37,43 +37,46 @@ class _RefreshScaffoldState extends State> { } Widget _buildBody() { - if (error.isNotEmpty) { - return ErrorReload(text: error, onTap: _refresh); - } else if (payload == null) { + if (_error.isNotEmpty) { + return ErrorReload(text: _error, onTap: _refresh); + } else if (_payload == null) { return Loading(more: false); } else { - return widget.bodyBuilder(payload); + return widget.bodyBuilder(_payload); } } - Future _refresh() async { + Future _refresh([int activeTab]) async { try { setState(() { - error = ''; - loading = true; + _error = ''; + _loading = true; + if (activeTab != null) { + _activeTab = activeTab; + } }); - payload = await widget.onRefresh(); + _payload = await widget.onRefresh(activeTab); } catch (err) { - error = err.toString(); + _error = err.toString(); throw err; } finally { if (mounted) { setState(() { - loading = false; + _loading = false; }); } } } Widget _buildTrailing() { - if (payload == null || widget.trailingBuilder == null) return null; + if (_payload == null || widget.trailingBuilder == null) return null; - return widget.trailingBuilder(payload); + return widget.trailingBuilder(_payload); } List _buildActions() { - if (payload == null || widget.trailingBuilder == null) return null; - var w = widget.trailingBuilder(payload); + if (_payload == null || widget.trailingBuilder == null) return null; + var w = widget.trailingBuilder(_payload); return [if (w != null) w]; } @@ -83,8 +86,22 @@ class _RefreshScaffoldState extends State> { case AppThemeType.cupertino: return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( - middle: widget.title, - trailing: _buildTrailing(), + 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, ), child: SafeArea( child: CustomScrollView( @@ -96,16 +113,28 @@ class _RefreshScaffoldState extends State> { ), ); default: - return Scaffold( + var w = 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/refresh_stateless.dart b/lib/scaffolds/refresh_stateless.dart deleted file mode 100644 index 8e47431..0000000 --- a/lib/scaffolds/refresh_stateless.dart +++ /dev/null @@ -1,74 +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'; -import '../widgets/loading.dart'; -import '../widgets/error_reload.dart'; - -// This is a scaffold for pull to refresh -class RefreshStatelessScaffold extends StatelessWidget { - final Widget title; - final Widget Function() bodyBuilder; - final Future Function() onRefresh; - final bool loading; - final String error; - final Widget trailing; - final List actions; - final PreferredSizeWidget bottom; - - RefreshStatelessScaffold({ - @required this.title, - @required this.bodyBuilder, - @required this.onRefresh, - @required this.loading, - @required this.error, - this.trailing, - this.actions, - this.bottom, - }); - - Widget _buildBody() { - if (error.isNotEmpty) { - return ErrorReload(text: error, onTap: onRefresh); - } else if (loading) { - return Loading(more: false); - } else { - return bodyBuilder(); - } - } - - @override - Widget build(BuildContext context) { - switch (Provider.of(context).theme) { - case AppThemeType.cupertino: - return CupertinoPageScaffold( - navigationBar: - CupertinoNavigationBar(middle: title, trailing: trailing), - child: SafeArea( - child: CustomScrollView( - slivers: [ - CupertinoSliverRefreshControl(onRefresh: onRefresh), - SliverToBoxAdapter(child: _buildBody()) - ], - ), - ), - ); - default: - return DefaultTabController( - length: 3, - child: Scaffold( - appBar: AppBar( - title: title, - actions: actions, - bottom: bottom, - ), - body: RefreshIndicator( - onRefresh: onRefresh, - child: SingleChildScrollView(child: _buildBody()), - ), - ), - ); - } - } -} diff --git a/lib/screens/notifications.dart b/lib/screens/notifications.dart index c3c386d..fdec1ec 100644 --- a/lib/screens/notifications.dart +++ b/lib/screens/notifications.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; +import 'package:git_touch/scaffolds/refresh.dart'; import 'package:git_touch/widgets/app_bar_title.dart'; import 'package:provider/provider.dart'; -import '../scaffolds/refresh_stateless.dart'; import 'package:git_touch/models/notification.dart'; -import 'package:git_touch/models/theme.dart'; import 'package:git_touch/models/settings.dart'; import '../widgets/notification_item.dart'; import '../widgets/list_group.dart'; @@ -18,17 +17,6 @@ class NotificationScreen extends StatefulWidget { } class NotificationScreenState extends State { - String error = ''; - int active = 0; - bool loading = false; - Map groupMap = {}; - - @override - void initState() { - super.initState(); - nextTick(_onSwitchTab); - } - Future> fetchNotifications(int index) async { List items = await Provider.of(context).getWithCredentials( '/notifications?all=${index == 2}&participating=${index == 1}'); @@ -108,9 +96,9 @@ $key: pullRequest(number: ${item.number}) { } Widget _buildGroupItem( - BuildContext context, - MapEntry entry, - ) { + BuildContext context, + MapEntry entry, + Map groupMap) { var group = entry.value; var repo = group.repo; return ListGroup( @@ -126,7 +114,7 @@ $key: pullRequest(number: ${item.number}) { onTap: () async { await Provider.of(context) .putWithCredentials('/repos/$repo/notifications'); - await _onSwitchTab(); + // await _onSwitchTab(); // TODO: }, child: Icon( Octicons.check, @@ -152,75 +140,11 @@ $key: pullRequest(number: ${item.number}) { ); } - Future _onSwitchTab([int index]) async { - if (loading) return; - - setState(() { - error = ''; - if (index != null) { - active = index; - } - loading = true; - }); - try { - groupMap = await fetchNotifications(active); - } catch (err) { - error = err.toString(); - throw err; - } finally { - if (mounted) { - setState(() { - loading = false; - }); - } - } - } - - var textMap = { - 0: 'Unread', - 1: 'Paticipating', - 2: 'All', - }; - - Widget _buildTitle() { - switch (Provider.of(context).theme) { - case AppThemeType.cupertino: - // var textStyle = DefaultTextStyle.of(context).style; - return DefaultTextStyle( - style: TextStyle(fontSize: 16), - child: SizedBox.expand( - child: CupertinoSegmentedControl( - groupValue: active, - onValueChanged: _onSwitchTab, - children: textMap.map((key, text) => MapEntry(key, Text(text))), - ), - ), - ); - default: - return AppBarTitle('Notifications'); - } - } - - void _confirm() async { - var value = await Provider.of(context) - .showConfirm(context, 'Mark all as read?'); - if (value) { - await Provider.of(context) - .putWithCredentials('/notifications'); - await _onSwitchTab(); - } - } - @override Widget build(context) { - return RefreshStatelessScaffold( - title: _buildTitle(), - bottom: TabBar( - onTap: _onSwitchTab, - tabs: textMap.entries - .map((entry) => Tab(text: entry.value.toUpperCase())) - .toList(), - ), + return RefreshScaffold( + title: AppBarTitle('Notifications'), + tabs: ['Unread', 'Paticipating', 'All'], // trailing: GestureDetector( // child: Icon(Icons.more_vert, size: 20), // onTap: () async { @@ -243,22 +167,27 @@ $key: pullRequest(number: ${item.number}) { // _onSwitchTab(value); // }, // ), - actions: [ - IconButton( - icon: Icon(Icons.done_all), - onPressed: _confirm, - ) - ], - onRefresh: _onSwitchTab, - loading: loading, - error: error, - bodyBuilder: () { + trailingBuilder: (_) => IconButton( + icon: Icon(Icons.done_all), + onPressed: () async { + // TODO: + // var value = await Provider.of(context) + // .showConfirm(context, 'Mark all as read?'); + // if (value) { + // await Provider.of(context) + // .putWithCredentials('/notifications'); + // await fetchNotifications(0); + // } + }, + ), + onRefresh: fetchNotifications, + bodyBuilder: (groupMap) { return groupMap.isEmpty ? EmptyWidget() : Column(children: [ Padding(padding: EdgeInsets.only(top: 10)), ...groupMap.entries - .map((entry) => _buildGroupItem(context, entry)) + .map((entry) => _buildGroupItem(context, entry, groupMap)) .toList() ]); }, diff --git a/lib/screens/object.dart b/lib/screens/object.dart index fe1cdbd..b44d4d5 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 80d6c79..75c0ba9 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 a959506..50f8723 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/trending.dart b/lib/screens/trending.dart index 845c750..cfb17d1 100644 --- a/lib/screens/trending.dart +++ b/lib/screens/trending.dart @@ -12,38 +12,36 @@ class TrendingScreen extends StatefulWidget { } class _TrendingScreenState extends State { - Future> _fetchTrendingRepos() async { - var res = await http.get('https://github-trending-api.now.sh'); - var items = json.decode(res.body); - - return items.map((item) { - return { - 'owner': {'login': item['author'], 'avatarUrl': item['avatar']}, - 'name': item['name'], - 'description': item['description'], - 'stargazers': { - 'totalCount': item['stars'], - }, - 'forks': { - 'totalCount': item['forks'], - }, - 'primaryLanguage': item['language'] == null - ? null - : { - 'name': item['language'], - 'color': item['languageColor'], - }, - 'isPrivate': false, - 'isFork': false // TODO: - }; - }).toList(); - } - @override Widget build(BuildContext context) { return RefreshScaffold( title: AppBarTitle('Trending'), - onRefresh: _fetchTrendingRepos, + onRefresh: (_) async { + var res = await http.get('https://github-trending-api.now.sh'); + var items = json.decode(res.body); + + return items.map((item) { + return { + 'owner': {'login': item['author'], 'avatarUrl': item['avatar']}, + 'name': item['name'], + 'description': item['description'], + 'stargazers': { + 'totalCount': item['stars'], + }, + 'forks': { + 'totalCount': item['forks'], + }, + 'primaryLanguage': item['language'] == null + ? null + : { + 'name': item['language'], + 'color': item['languageColor'], + }, + 'isPrivate': false, + 'isFork': false // TODO: + }; + }).toList(); + }, bodyBuilder: (payload) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, diff --git a/lib/screens/user.dart b/lib/screens/user.dart index 4adcf12..e78b3b4 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/widgets/table_view.dart b/lib/widgets/table_view.dart index fe859ad..7faea85 100644 --- a/lib/widgets/table_view.dart +++ b/lib/widgets/table_view.dart @@ -103,7 +103,7 @@ class TableView extends StatelessWidget { ); if (item.onTap == null && item.screenBuilder == null && item.url == null) { - return widget; + return Ink(color: PrimerColors.white, child: widget); } return Link( onTap: item.onTap,