mirror of
https://github.com/pd4d10/git-touch.git
synced 2026-04-27 11:07:49 -05:00
feat: add organization screen
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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'];
|
||||
|
||||
212
lib/screens/organization.dart
Normal file
212
lib/screens/organization.dart
Normal 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),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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"],
|
||||
|
||||
Reference in New Issue
Block a user