From 612a7a417a6b02e65ba9f5fdf62f9de7dc02590b Mon Sep 17 00:00:00 2001 From: Rongjian Zhang Date: Mon, 27 Jan 2020 14:43:10 +0800 Subject: [PATCH] improvement: settings screen --- lib/graphql/gh.dart | 294 ++++++++++++++++++++++++++-- lib/graphql/gh_user.graphql | 61 +++++- lib/scaffolds/refresh_stateful.dart | 11 +- lib/screens/object.dart | 7 +- lib/screens/settings.dart | 118 ++++++++--- lib/screens/user.dart | 96 +++++---- 6 files changed, 485 insertions(+), 102 deletions(-) diff --git a/lib/graphql/gh.dart b/lib/graphql/gh.dart index c00596a..0a90bc5 100644 --- a/lib/graphql/gh.dart +++ b/lib/graphql/gh.dart @@ -6309,6 +6309,12 @@ class GhUserQuery extends GraphQLQuery { ]) ], selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'id'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), FieldNode( name: NameNode(value: 'login'), alias: null, @@ -6358,13 +6364,13 @@ class GhUserQuery extends GraphQLQuery { directives: [], selectionSet: null), FieldNode( - name: NameNode(value: 'websiteUrl'), + name: NameNode(value: 'createdAt'), alias: null, arguments: [], directives: [], selectionSet: null), FieldNode( - name: NameNode(value: 'createdAt'), + name: NameNode(value: 'websiteUrl'), alias: null, arguments: [], directives: [], @@ -6408,19 +6414,6 @@ class GhUserQuery extends GraphQLQuery { directives: [], selectionSet: null) ])), - FieldNode( - name: NameNode(value: 'repositories'), - alias: null, - arguments: [], - directives: [], - selectionSet: SelectionSetNode(selections: [ - FieldNode( - name: NameNode(value: 'totalCount'), - alias: null, - arguments: [], - directives: [], - selectionSet: null) - ])), FieldNode( name: NameNode(value: 'contributionsCollection'), alias: null, @@ -6455,7 +6448,276 @@ class GhUserQuery extends GraphQLQuery { ])) ])) ])) - ])) + ])), + FieldNode( + name: NameNode(value: 'repositories'), + alias: null, + arguments: [ + ArgumentNode( + name: NameNode(value: 'first'), + value: IntValueNode(value: '6')), + ArgumentNode( + name: NameNode(value: 'ownerAffiliations'), + value: EnumValueNode(name: NameNode(value: 'OWNER'))), + ArgumentNode( + name: NameNode(value: 'orderBy'), + value: ObjectValueNode(fields: [ + ObjectFieldNode( + name: NameNode(value: 'field'), + value: EnumValueNode( + name: NameNode(value: 'STARGAZERS'))), + ObjectFieldNode( + name: NameNode(value: 'direction'), + value: EnumValueNode( + name: NameNode(value: 'DESC'))) + ])) + ], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'totalCount'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'nodes'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'owner'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'login'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'avatarUrl'), + alias: null, + arguments: [], + directives: [], + selectionSet: null) + ])), + FieldNode( + name: NameNode(value: 'name'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'description'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'isPrivate'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'isFork'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'stargazers'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'totalCount'), + alias: null, + arguments: [], + directives: [], + selectionSet: null) + ])), + FieldNode( + name: NameNode(value: 'forks'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'totalCount'), + alias: null, + arguments: [], + directives: [], + selectionSet: null) + ])), + FieldNode( + name: NameNode(value: 'primaryLanguage'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'color'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'name'), + alias: null, + arguments: [], + directives: [], + selectionSet: null) + ])) + ])) + ])), + FieldNode( + name: NameNode(value: 'pinnedItems'), + alias: null, + arguments: [ + ArgumentNode( + name: NameNode(value: 'first'), + value: IntValueNode(value: '6')) + ], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'totalCount'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'nodes'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + InlineFragmentNode( + typeCondition: TypeConditionNode( + on: NamedTypeNode( + name: NameNode(value: 'Repository'), + isNonNull: false)), + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'owner'), + alias: null, + arguments: [], + directives: [], + selectionSet: + SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'login'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'avatarUrl'), + alias: null, + arguments: [], + directives: [], + selectionSet: null) + ])), + FieldNode( + name: NameNode(value: 'name'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'description'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'isPrivate'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'isFork'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'stargazers'), + alias: null, + arguments: [], + directives: [], + selectionSet: + SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'totalCount'), + alias: null, + arguments: [], + directives: [], + selectionSet: null) + ])), + FieldNode( + name: NameNode(value: 'forks'), + alias: null, + arguments: [], + directives: [], + selectionSet: + SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'totalCount'), + alias: null, + arguments: [], + directives: [], + selectionSet: null) + ])), + FieldNode( + name: NameNode(value: 'primaryLanguage'), + alias: null, + arguments: [], + directives: [], + selectionSet: + SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'color'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'name'), + alias: null, + arguments: [], + directives: [], + selectionSet: null) + ])) + ])) + ])) + ])), + FieldNode( + name: NameNode(value: 'viewerCanFollow'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'viewerIsFollowing'), + alias: null, + arguments: [], + directives: [], + selectionSet: null) ])) ])) ]); diff --git a/lib/graphql/gh_user.graphql b/lib/graphql/gh_user.graphql index f2a6f0e..69d23cf 100644 --- a/lib/graphql/gh_user.graphql +++ b/lib/graphql/gh_user.graphql @@ -151,6 +151,7 @@ query GhUser($login: String!, $isViewer: Boolean!) { } } viewer @include(if: $isViewer) { + id login avatarUrl url @@ -159,8 +160,8 @@ query GhUser($login: String!, $isViewer: Boolean!) { company location email - websiteUrl createdAt + websiteUrl starredRepositories { totalCount } @@ -170,9 +171,6 @@ query GhUser($login: String!, $isViewer: Boolean!) { following { totalCount } - repositories { - totalCount - } contributionsCollection { contributionCalendar { weeks { @@ -182,5 +180,60 @@ query GhUser($login: String!, $isViewer: Boolean!) { } } } + repositories( + first: 6 + ownerAffiliations: OWNER + orderBy: { field: STARGAZERS, direction: DESC } + ) { + totalCount + nodes { + owner { + login + avatarUrl + } + name + description + isPrivate + isFork + stargazers { + totalCount + } + forks { + totalCount + } + primaryLanguage { + color + name + } + } + } + pinnedItems(first: 6) { + totalCount # TODO: Add this for correct generated code + nodes { + __typename + ... on Repository { + owner { + login + avatarUrl + } + name + description + isPrivate + isFork + stargazers { + totalCount + } + forks { + totalCount + } + primaryLanguage { + color + name + } + } + } + } + viewerCanFollow + viewerIsFollowing } } diff --git a/lib/scaffolds/refresh_stateful.dart b/lib/scaffolds/refresh_stateful.dart index 56877bd..55792ef 100644 --- a/lib/scaffolds/refresh_stateful.dart +++ b/lib/scaffolds/refresh_stateful.dart @@ -10,6 +10,7 @@ class RefreshStatefulScaffold extends StatefulWidget { final Future Function() fetchData; final Widget Function(T data, void Function(VoidCallback fn) setState) actionBuilder; + final Widget action; final canRefresh; RefreshStatefulScaffold({ @@ -17,8 +18,9 @@ class RefreshStatefulScaffold extends StatefulWidget { @required this.bodyBuilder, @required this.fetchData, this.actionBuilder, + this.action, this.canRefresh = true, - }); + }) : assert(actionBuilder == null || action == null); @override _RefreshStatefulScaffoldState createState() => @@ -45,9 +47,9 @@ class _RefreshStatefulScaffoldState _loading = true; }); _data = await widget.fetchData(); - } catch (err) { - _error = err.toString(); - throw err; + // } catch (err) { + // _error = err.toString(); + // throw err; } finally { if (mounted) { setState(() { @@ -58,6 +60,7 @@ class _RefreshStatefulScaffoldState } Widget get _action { + if (widget.action != null) return widget.action; if (widget.actionBuilder == null || _data == null) return null; return widget.actionBuilder(_data, setState); } diff --git a/lib/screens/object.dart b/lib/screens/object.dart index 736d675..501658a 100644 --- a/lib/screens/object.dart +++ b/lib/screens/object.dart @@ -169,10 +169,9 @@ class ObjectScreen extends StatelessWidget { child: HighlightView( text, language: _language, - theme: themeMap[ - theme.brightness == Brightness.dark - ? codeProvider.themeDark - : codeProvider.theme], + theme: themeMap[theme.brightness == Brightness.dark + ? codeProvider.themeDark + : codeProvider.theme], padding: CommonStyle.padding, textStyle: TextStyle( fontSize: codeProvider.fontSize.toDouble(), diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index 0514c6c..500e0f1 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -1,11 +1,15 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:git_touch/models/auth.dart'; +import 'package:git_touch/models/code.dart'; import 'package:git_touch/models/theme.dart'; import 'package:git_touch/scaffolds/single.dart'; import 'package:git_touch/utils/utils.dart'; import 'package:git_touch/widgets/action_button.dart'; import 'package:git_touch/widgets/app_bar_title.dart'; import 'package:git_touch/widgets/table_view.dart'; +import 'package:launch_review/launch_review.dart'; +import 'package:package_info/package_info.dart'; import 'package:provider/provider.dart'; import 'package:tuple/tuple.dart'; @@ -14,7 +18,24 @@ final settingsRouter = RouterScreen( (context, parameters) => SettingsScreen(), ); -class SettingsScreen extends StatelessWidget { +class SettingsScreen extends StatefulWidget { + @override + _SettingsScreenState createState() => _SettingsScreenState(); +} + +class _SettingsScreenState extends State { + var _version = ''; + + @override + void initState() { + super.initState(); + PackageInfo.fromPlatform().then((info) { + setState(() { + _version = info.version; + }); + }); + } + Widget _buildRightWidget(BuildContext context, bool checked) { final theme = Provider.of(context); if (!checked) return null; @@ -24,44 +45,22 @@ class SettingsScreen extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Provider.of(context); + final auth = Provider.of(context); + final code = Provider.of(context); return SingleScaffold( title: AppBarTitle('Settings'), body: Column( children: [ CommonStyle.verticalGap, - TableView( - headerText: 'ACCOUNTS', - items: [ - TableViewItem( - text: Text('Switch to another account'), - url: '/login', - ), - ], - ), - CommonStyle.verticalGap, - TableView(headerText: 'THEME', items: [ + TableView(headerText: 'accounts', items: [ TableViewItem( - text: Text('Scaffold'), - rightWidget: Text(theme.theme == AppThemeType.cupertino - ? 'Cupertino' - : 'Material'), - onTap: () { - theme.showActions(context, [ - for (var t in [ - Tuple2('Material', AppThemeType.material), - Tuple2('Cupertino', AppThemeType.cupertino), - ]) - ActionItem( - text: t.item1, - onTap: (_) { - if (theme.theme != t.item2) { - theme.setTheme(t.item2); - } - }, - ) - ]); - }, + text: Text('Switch Accounts'), + url: '/login', + rightWidget: Text(auth.activeAccount.login), ), + ]), + CommonStyle.verticalGap, + TableView(headerText: 'theme', items: [ TableViewItem( text: Text('Brightness'), rightWidget: Text(theme.brighnessValue == AppBrightnessType.light @@ -86,11 +85,66 @@ class SettingsScreen extends StatelessWidget { ]); }, ), + TableViewItem( + text: Text('Scaffold Theme'), + rightWidget: Text(theme.theme == AppThemeType.cupertino + ? 'Cupertino' + : 'Material'), + onTap: () { + theme.showActions(context, [ + for (var t in [ + Tuple2('Material', AppThemeType.material), + Tuple2('Cupertino', AppThemeType.cupertino), + ]) + ActionItem( + text: t.item1, + onTap: (_) { + if (theme.theme != t.item2) { + theme.setTheme(t.item2); + } + }, + ) + ]); + }, + ), TableViewItem( text: Text('Code Theme'), url: '/choose-code-theme', + rightWidget: Text('${code.fontFamily}, ${code.fontSize}pt'), ), ]), + CommonStyle.verticalGap, + TableView(headerText: 'feedback', items: [ + TableViewItem( + text: Text('Submit an issue'), + rightWidget: Text('pd4d10/git-touch'), + url: '/pd4d10/git-touch/issues/new', + ), + TableViewItem( + text: Text('Rate This App'), + onTap: () { + LaunchReview.launch( + androidAppId: 'io.github.pd4d10.gittouch', + iOSAppId: '1452042346', + ); + }, + ), + TableViewItem( + text: Text('Email'), + rightWidget: Text('pd4d10@gmail.com'), + hideRightChevron: true, + url: 'mailto:pd4d10@gmail.com', + ), + ]), + CommonStyle.verticalGap, + TableView(headerText: 'about', items: [ + TableViewItem(text: Text('Version'), rightWidget: Text(_version)), + TableViewItem( + text: Text('Source Code'), + rightWidget: Text('pd4d10/git-touch'), + url: '/pd4d10/git-touch', + ), + ]) ], ), ); diff --git a/lib/screens/user.dart b/lib/screens/user.dart index e686f1b..90cc474 100644 --- a/lib/screens/user.dart +++ b/lib/screens/user.dart @@ -5,6 +5,7 @@ import 'package:git_touch/models/theme.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/action_entry.dart'; import 'package:git_touch/widgets/app_bar_title.dart'; import 'package:git_touch/screens/repositories.dart'; import 'package:git_touch/widgets/avatar.dart'; @@ -292,28 +293,28 @@ class UserScreen extends StatelessWidget { ], ), CommonStyle.verticalGap, - if (isViewer) - TableView( - hasIcon: true, - items: [ - TableViewItem( - leftIconData: Icons.settings, - text: Text('Settings'), - url: '/settings', - ), - TableViewItem( - leftIconData: Icons.info_outline, - text: Text('About'), - url: '/about', - ), - ], - ) - else - ..._buildPinnedItems( - p.pinnedItems.nodes - .where((n) => n is GhUserRepository) - .cast(), - p.repositories.nodes), + // if (isViewer) + // TableView( + // hasIcon: true, + // items: [ + // TableViewItem( + // leftIconData: Icons.settings, + // text: Text('Settings'), + // url: '/settings', + // ), + // TableViewItem( + // leftIconData: Icons.info_outline, + // text: Text('About'), + // url: '/about', + // ), + // ], + // ) + // else + ..._buildPinnedItems( + p.pinnedItems.nodes + .where((n) => n is GhUserRepository) + .cast(), + p.repositories.nodes), CommonStyle.verticalGap, ], ); @@ -389,6 +390,7 @@ class UserScreen extends StatelessWidget { @override Widget build(BuildContext context) { final auth = Provider.of(context); + final theme = Provider.of(context); return RefreshStatefulScaffold( fetchData: () async { final data = await auth.gqlClient.execute(GhUserQuery( @@ -396,26 +398,36 @@ class UserScreen extends StatelessWidget { return isViewer ? data.data.viewer : data.data.repositoryOwner; }, title: AppBarTitle(isViewer ? 'Me' : login), - actionBuilder: (payload, setState) { - switch (payload.resolveType) { - case 'User': - final user = payload as GhUserUser; - return ActionButton( - title: 'User Actions', - items: [...ActionItem.getUrlActions(user.url)], - ); - case 'Organization': - final organization = payload as GhUserOrganization; - return ActionButton( - title: 'Organization Actions', - items: [ - ...ActionItem.getUrlActions(organization.url), - ], - ); - default: - return null; - } - }, + action: isViewer + ? ActionEntry( + iconData: Icons.settings, + onTap: () { + theme.push(context, '/settings'); + }, + ) + : null, + actionBuilder: isViewer + ? null + : (payload, setState) { + switch (payload.resolveType) { + case 'User': + final user = payload as GhUserUser; + return ActionButton( + title: 'User Actions', + items: [...ActionItem.getUrlActions(user.url)], + ); + case 'Organization': + final organization = payload as GhUserOrganization; + return ActionButton( + title: 'Organization Actions', + items: [ + ...ActionItem.getUrlActions(organization.url), + ], + ); + default: + return null; + } + }, bodyBuilder: (payload, setState) { if (isViewer) { return _buildUser(context, payload as GhUserUser, setState);