feat: add organization screen

This commit is contained in:
Rongjian Zhang
2019-03-10 23:34:34 +08:00
parent 83c22bda9a
commit 718226860e
7 changed files with 274 additions and 23 deletions

View File

@@ -9,6 +9,7 @@ import 'screens/me.dart';
import 'screens/login.dart';
import 'screens/issue.dart';
import 'screens/repos.dart';
import 'screens/organization.dart';
import 'screens/trending.dart';
import 'utils/utils.dart';
@@ -69,6 +70,7 @@ class _HomeState extends State<Home> {
_buildScreen(int index) {
// return IssueScreen(number: 29, owner: 'reactjs', name: 'rfcs');
// return ReposScreen('pd4d10');
// return OrganizationScreen('flutter');
// return TrendingScreen();
switch (index) {
case 0:
@@ -110,7 +112,12 @@ class _HomeState extends State<Home> {
case ThemeMap.cupertino:
return CupertinoApp(
home: CupertinoTheme(
data: CupertinoThemeData(),
data: CupertinoThemeData(
textTheme: CupertinoTextThemeData(
// primaryColor: Palette.primary,
textStyle: TextStyle(color: Palette.primary),
),
),
child: CupertinoTabScaffold(
tabBar: CupertinoTabBar(items: _buildNavigationItems()),
tabBuilder: (context, index) {

View File

@@ -307,11 +307,11 @@ class SettingsProviderState extends State<SettingsProvider> {
body: json.encode({'query': query}))
.timeout(_timeoutDuration);
// print(res.body);
final data = json.decode(res.body);
// print(data);
if (data['errors'] != null) {
throw new Exception(data['errors'].toString());
throw new Exception(data['errors'][0]['message']);
}
return data['data'];

View File

@@ -0,0 +1,212 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:share/share.dart';
import '../providers/settings.dart';
import '../scaffolds/refresh.dart';
import '../widgets/avatar.dart';
import '../widgets/entry_item.dart';
import '../widgets/list_group.dart';
import '../widgets/repo_item.dart';
import '../widgets/link.dart';
import '../widgets/action.dart';
import '../screens/repos.dart';
import '../screens/users.dart';
import '../utils/utils.dart';
class OrganizationScreen extends StatefulWidget {
final String login;
OrganizationScreen(this.login);
_OrganizationScreenState createState() => _OrganizationScreenState();
}
class _OrganizationScreenState extends State<OrganizationScreen> {
Future query() async {
var login = widget.login;
var data = await SettingsProvider.of(context).query('''
{
organization(login: "$login") {
name
avatarUrl
websiteUrl
email
location
repositories(first: $pageSize, orderBy: {field: PUSHED_AT, direction: DESC}) {
totalCount
nodes {
$repoChunk
}
}
pinnedRepositories(first: $pageSize) {
nodes {
$repoChunk
}
}
url
membersWithRole {
totalCount
}
}
}
''');
return data['organization'];
}
Widget _buildRepos(payload) {
String title;
List items;
if (payload['pinnedRepositories']['nodes'].length == 0) {
title = 'Popular repositories';
items = payload['repositories']['nodes'];
} else {
title = 'Pinned repositories';
items = payload['pinnedRepositories']['nodes'];
}
return ListGroup(
title: Text(
title,
style: TextStyle(fontSize: 16),
),
items: items,
itemBuilder: (item, _) {
return RepoItem(
item,
showOwner: item['owner']['login'] != widget.login,
);
},
);
}
Widget _buildInfo(payload) {
// TODO: redesign the UI to show all information
String email = payload['email'] ?? '';
if (email.isNotEmpty) {
return Link(
material: false,
child: Row(children: <Widget>[
Icon(
Octicons.mail,
color: Colors.black54,
size: 16,
),
Padding(padding: EdgeInsets.only(left: 4)),
Text(email, style: TextStyle(color: Colors.black54, fontSize: 15))
]),
onTap: () {
launch('mailto:' + email);
},
);
}
String website = payload['websiteUrl'] ?? '';
if (website.isNotEmpty) {
return Row(children: <Widget>[
Icon(
Octicons.link,
color: Colors.black54,
size: 16,
),
Padding(padding: EdgeInsets.only(left: 4)),
Text(website, style: TextStyle(color: Colors.black54, fontSize: 16))
]);
}
String location = payload['location'] ?? '';
if (location.isNotEmpty) {
return Row(children: <Widget>[
Icon(
Octicons.location,
color: Colors.black54,
size: 16,
),
Padding(padding: EdgeInsets.only(left: 4)),
Text(location, style: TextStyle(color: Colors.black54, fontSize: 16))
]);
}
return Container();
}
@override
Widget build(BuildContext context) {
return RefreshScaffold(
onRefresh: query,
title: Text(widget.login),
trailingBuilder: (payload) {
List<Action> actions = [];
actions.addAll([
Action(
text: 'Share',
onPress: () {
Share.share(payload['url']);
},
),
Action(
text: 'Open in Browser',
onPress: () {
launch(payload['url']);
},
),
]);
return ActionButton(title: 'User Actions', actions: actions);
},
bodyBuilder: (payload) {
return Column(
children: <Widget>[
Container(
padding: EdgeInsets.all(10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Avatar(url: payload['avatarUrl'], size: 28),
Padding(padding: EdgeInsets.only(left: 10)),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
payload['name'] ?? widget.login,
style: TextStyle(height: 1.2, fontSize: 16),
),
Padding(padding: EdgeInsets.only(top: 10)),
_buildInfo(payload),
],
),
)
],
),
),
Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Colors.black12),
top: BorderSide(color: Colors.black12),
),
),
child: Row(
children: <Widget>[
EntryItem(
count: payload['repositories']['totalCount'],
text: 'Repositories',
screenBuilder: (_) =>
ReposScreen(login: widget.login, org: true),
),
EntryItem(
count: payload['membersWithRole']['totalCount'],
text: 'Members',
screenBuilder: (_) =>
UsersScreen(login: widget.login, org: true),
),
],
),
),
_buildRepos(payload),
],
);
},
);
}
}

View File

@@ -10,7 +10,9 @@ import '../widgets/repo_item.dart';
import '../widgets/entry_item.dart';
import '../screens/issues.dart';
import '../screens/user.dart';
import '../screens/organization.dart';
import '../widgets/action.dart';
import '../utils/utils.dart';
class RepoScreen extends StatefulWidget {
final String owner;
@@ -34,6 +36,7 @@ class _RepoScreenState extends State<RepoScreen> {
owner {
__typename
login
url
}
name
isPrivate
@@ -92,14 +95,19 @@ class _RepoScreenState extends State<RepoScreen> {
return ActionButton(title: 'Repository Actions', actions: [
Action(
text: 'View @$owner',
text: '@$owner',
onPress: () {
WidgetBuilder builder;
switch (payload['owner']['__typename']) {
case 'Organization':
// TODO:
break;
// builder = (_) => OrganizationScreen(owner);
// break;
// Seems organization permission is a little complicated
// So we just launch browser currently
launch(payload['owner']['url']);
return;
case 'User':
builder = (_) => UserScreen(owner);
break;

View File

@@ -8,25 +8,34 @@ import '../widgets/repo_item.dart';
class ReposScreen extends StatefulWidget {
final String login;
final bool star;
final bool org;
ReposScreen({this.login, this.star = false});
ReposScreen({this.login, this.star = false, this.org = false});
@override
_ReposScreenState createState() => _ReposScreenState();
}
class _ReposScreenState extends State<ReposScreen> {
get login => widget.login;
String get login => widget.login;
String get scope => widget.org ? 'organization' : 'user';
String get resource => widget.star ? 'starredRepositories' : 'repositories';
String get fieldOrderBy {
if (widget.star) {
return 'STARRED_AT';
}
if (widget.org) {
return 'PUSHED_AT';
}
return 'UPDATED_AT';
}
Future<ListPayload> _queryRepos([String cursor]) async {
var cursorChunk = cursor == null ? '' : ', after: "$cursor"';
var resouce = widget.star ? 'starredRepositories' : 'repositories';
var fieldOrderBy = widget.star ? 'STARRED_AT' : 'UPDATED_AT';
var data = await SettingsProvider.of(context).query('''
{
user(login: "$login") {
$resouce(first: $pageSize$cursorChunk, orderBy: {field: $fieldOrderBy, direction: DESC}) {
$scope(login: "$login") {
$resource(first: $pageSize$cursorChunk, orderBy: {field: $fieldOrderBy, direction: DESC}) {
pageInfo {
hasNextPage
endCursor
@@ -38,7 +47,7 @@ class _ReposScreenState extends State<ReposScreen> {
}
}
''');
var repo = data["user"][resouce];
var repo = data[scope][resource];
return ListPayload(
cursor: repo["pageInfo"]["endCursor"],

View File

@@ -27,7 +27,7 @@ class UserScreen extends StatefulWidget {
}
class _UserScreenState extends State<UserScreen> {
Future queryUser(BuildContext context) async {
Future query() async {
var login = widget.login;
var data = await SettingsProvider.of(context).query('''
{
@@ -35,6 +35,7 @@ class _UserScreenState extends State<UserScreen> {
name
avatarUrl
bio
websiteUrl
email
company
location
@@ -93,7 +94,7 @@ class _UserScreenState extends State<UserScreen> {
);
}
Widget _buildEmail(payload) {
Widget _buildInfo(payload) {
// TODO: redesign the UI to show all information
String email = payload['email'] ?? '';
if (email.isNotEmpty) {
@@ -148,7 +149,7 @@ class _UserScreenState extends State<UserScreen> {
return RefreshScaffold(
onRefresh: () {
return Future.wait(
[queryUser(context), getContributionsSvg(widget.login)],
[query(), getContributionsSvg(widget.login)],
);
},
title: Text(widget.login),
@@ -221,7 +222,7 @@ class _UserScreenState extends State<UserScreen> {
style: TextStyle(height: 1.2, fontSize: 16),
),
Padding(padding: EdgeInsets.only(top: 10)),
_buildEmail(payload),
_buildInfo(payload),
],
),
)

View File

@@ -9,23 +9,37 @@ import '../widgets/avatar.dart';
class UsersScreen extends StatefulWidget {
final String login;
final bool following;
final bool org;
UsersScreen({this.login, this.following = false});
UsersScreen({
@required this.login,
this.following = false,
this.org = false,
});
@override
_UsersScreenState createState() => _UsersScreenState();
}
class _UsersScreenState extends State<UsersScreen> {
get login => widget.login;
get resource => widget.following ? 'following' : 'followers';
String get login => widget.login;
String get scope => widget.org ? 'organization' : 'user';
String get resource {
if (widget.org) {
return 'members';
}
if (widget.following) {
return 'following';
}
return 'followers';
}
Future<ListPayload> _queryUsers([String cursor]) async {
var cursorChunk = cursor == null ? '' : ', after: "$cursor"';
var data = await SettingsProvider.of(context).query('''
{
user(login: "$login") {
$scope(login: "$login") {
$resource(first: $pageSize$cursorChunk) {
pageInfo {
hasNextPage
@@ -39,7 +53,7 @@ class _UsersScreenState extends State<UsersScreen> {
}
}
''');
var repo = data["user"][resource];
var repo = data[scope][resource];
return ListPayload(
cursor: repo["pageInfo"]["endCursor"],