feat: organization screen
This commit is contained in:
parent
3354ac4e6b
commit
45687a48a6
|
@ -303,8 +303,9 @@ class SettingsModel with ChangeNotifier {
|
|||
String _oauthState;
|
||||
void redirectToGithubOauth() {
|
||||
_oauthState = nanoid();
|
||||
var scope = Uri.encodeComponent('user,repo,read:org');
|
||||
launch(
|
||||
'https://github.com/login/oauth/authorize?client_id=$clientId&redirect_uri=gittouch://login&scope=user%20repo&state=$_oauthState',
|
||||
'https://github.com/login/oauth/authorize?client_id=$clientId&redirect_uri=gittouch://login&scope=$scope&state=$_oauthState',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,44 +1,63 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import 'package:git_touch/widgets/table_view.dart';
|
||||
import 'package:git_touch/widgets/user_item.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:share/share.dart';
|
||||
import 'package:git_touch/models/settings.dart';
|
||||
import 'package:provider/provider.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 {
|
||||
class OrganizationScreen extends StatelessWidget {
|
||||
final String login;
|
||||
OrganizationScreen(this.login);
|
||||
_OrganizationScreenState createState() => _OrganizationScreenState();
|
||||
}
|
||||
|
||||
class _OrganizationScreenState extends State<OrganizationScreen> {
|
||||
Future query() async {
|
||||
var login = widget.login;
|
||||
var data = await Provider.of<SettingsModel>(context).query('''
|
||||
OrganizationScreen(this.login);
|
||||
|
||||
Iterable<Widget> _buildRepos(payload) {
|
||||
String title;
|
||||
List items;
|
||||
|
||||
if ((payload['pinnedItems']['nodes'] as List).isNotEmpty) {
|
||||
title = 'pinned repositories';
|
||||
items = payload['pinnedItems']['nodes'] as List;
|
||||
} else {
|
||||
items = [];
|
||||
}
|
||||
|
||||
items = items
|
||||
.where((x) => x != null)
|
||||
.toList(); // TODO: Pinned items may include somethings other than repo
|
||||
if (items.isEmpty) return [];
|
||||
|
||||
return [
|
||||
borderView1,
|
||||
if (title != null) TableViewHeader(title),
|
||||
...join(
|
||||
borderView,
|
||||
items.map((item) {
|
||||
return RepoItem(item);
|
||||
}).toList(),
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RefreshScaffold(
|
||||
onRefresh: () async {
|
||||
var data = await Provider.of<SettingsModel>(context).query('''
|
||||
{
|
||||
organization(login: "$login") {
|
||||
name
|
||||
avatarUrl
|
||||
websiteUrl
|
||||
email
|
||||
description
|
||||
location
|
||||
repositories(first: $pageSize, orderBy: {field: PUSHED_AT, direction: DESC}) {
|
||||
totalCount
|
||||
nodes {
|
||||
$repoChunk
|
||||
}
|
||||
}
|
||||
email
|
||||
websiteUrl
|
||||
pinnedItems(first: $pageSize) {
|
||||
nodes {
|
||||
... on Repository {
|
||||
|
@ -47,95 +66,14 @@ class _OrganizationScreenState extends State<OrganizationScreen> {
|
|||
}
|
||||
}
|
||||
url
|
||||
membersWithRole {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
}
|
||||
''');
|
||||
return data['organization'];
|
||||
}
|
||||
|
||||
Widget _buildRepos(payload) {
|
||||
String title;
|
||||
List items;
|
||||
if (payload['pinnedItems']['nodes'].length == 0) {
|
||||
title = 'Popular repositories';
|
||||
items = payload['repositories']['nodes'];
|
||||
} else {
|
||||
title = 'Pinned repositories';
|
||||
items = payload['pinnedItems']['nodes'];
|
||||
}
|
||||
|
||||
return ListGroup(
|
||||
title: Text(
|
||||
title,
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
items: items,
|
||||
itemBuilder: (item, _) {
|
||||
return RepoItem(item);
|
||||
return data['organization'];
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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: AppBarTitle(widget.login),
|
||||
title: AppBarTitle('Organization'),
|
||||
trailingBuilder: (payload) {
|
||||
return ActionButton(title: 'User Actions', actions: [
|
||||
return ActionButton(title: 'Organization Actions', actions: [
|
||||
MyAction(
|
||||
text: 'Share',
|
||||
onPress: () {
|
||||
|
@ -152,55 +90,51 @@ class _OrganizationScreenState extends State<OrganizationScreen> {
|
|||
},
|
||||
bodyBuilder: (payload) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
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),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
UserItem(
|
||||
login,
|
||||
name: payload['name'],
|
||||
avatarUrl: payload['avatarUrl'],
|
||||
bio: payload['description'],
|
||||
),
|
||||
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),
|
||||
borderView1,
|
||||
TableView(
|
||||
hasIcon: true,
|
||||
items: [
|
||||
if (isNotNullOrEmpty(payload['location']))
|
||||
TableViewItem(
|
||||
leftIconData: Octicons.location,
|
||||
text: Text(payload['location']),
|
||||
onTap: () {
|
||||
launch('https://www.google.com/maps/place/' +
|
||||
(payload['location'] as String)
|
||||
.replaceAll(RegExp(r'\s+'), ''));
|
||||
},
|
||||
),
|
||||
EntryItem(
|
||||
count: payload['membersWithRole']['totalCount'],
|
||||
text: 'Members',
|
||||
screenBuilder: (_) => UsersScreen(
|
||||
login: widget.login, type: UsersScreenType.orgs),
|
||||
if (isNotNullOrEmpty(payload['email']))
|
||||
TableViewItem(
|
||||
leftIconData: Octicons.mail,
|
||||
text: Text(payload['email']),
|
||||
onTap: () {
|
||||
launch('mailto:' + payload['email']);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
if (isNotNullOrEmpty(payload['websiteUrl']))
|
||||
TableViewItem(
|
||||
leftIconData: Octicons.link,
|
||||
text: Text(payload['websiteUrl']),
|
||||
onTap: () {
|
||||
var url = payload['websiteUrl'] as String;
|
||||
if (!url.startsWith('http')) {
|
||||
url = 'http://$url';
|
||||
}
|
||||
launch(url);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
_buildRepos(payload),
|
||||
..._buildRepos(payload),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'dart:convert';
|
||||
import 'package:filesize/filesize.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
@ -8,7 +7,6 @@ import 'package:git_touch/utils/utils.dart';
|
|||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import 'package:git_touch/widgets/markdown_view.dart';
|
||||
import 'package:git_touch/widgets/table_view.dart';
|
||||
import 'package:primer/primer.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:git_touch/models/theme.dart';
|
||||
import 'package:git_touch/screens/commits.dart';
|
||||
|
@ -42,7 +40,6 @@ class RepoScreen extends StatelessWidget {
|
|||
owner {
|
||||
__typename
|
||||
login
|
||||
url
|
||||
avatarUrl
|
||||
}
|
||||
name
|
||||
|
@ -120,13 +117,8 @@ class RepoScreen extends StatelessWidget {
|
|||
|
||||
switch (payload['owner']['__typename']) {
|
||||
case 'Organization':
|
||||
// builder = (_) => OrganizationScreen(owner);
|
||||
// break;
|
||||
|
||||
// Seems organization permission is a little complicated
|
||||
// So we just launch browser currently
|
||||
launch(payload['owner']['url']);
|
||||
return;
|
||||
builder = (_) => OrganizationScreen(owner);
|
||||
break;
|
||||
case 'User':
|
||||
builder = (_) => UserScreen(owner);
|
||||
break;
|
||||
|
|
|
@ -7,33 +7,28 @@ import '../utils/utils.dart';
|
|||
import '../widgets/repo_item.dart';
|
||||
|
||||
/// Repos of user
|
||||
class ReposScreen extends StatefulWidget {
|
||||
class ReposScreen extends StatelessWidget {
|
||||
final String login;
|
||||
final bool star;
|
||||
final bool org;
|
||||
|
||||
ReposScreen({this.login, this.star = false, this.org = false});
|
||||
ReposScreen(this.login, {this.star = false, this.org = false});
|
||||
|
||||
@override
|
||||
_ReposScreenState createState() => _ReposScreenState();
|
||||
}
|
||||
|
||||
class _ReposScreenState extends State<ReposScreen> {
|
||||
String get login => widget.login;
|
||||
String get scope => widget.org ? 'organization' : 'user';
|
||||
String get resource => widget.star ? 'starredRepositories' : 'repositories';
|
||||
String get scope => org ? 'organization' : 'user';
|
||||
String get resource => star ? 'starredRepositories' : 'repositories';
|
||||
String get fieldOrderBy {
|
||||
if (widget.star) {
|
||||
if (star) {
|
||||
return 'STARRED_AT';
|
||||
}
|
||||
if (widget.org) {
|
||||
if (org) {
|
||||
return 'PUSHED_AT';
|
||||
}
|
||||
return 'UPDATED_AT';
|
||||
}
|
||||
|
||||
Future<ListPayload> _queryRepos([String cursor]) async {
|
||||
Future<ListPayload> _queryRepos(BuildContext context, [String cursor]) async {
|
||||
var cursorChunk = cursor == null ? '' : ', after: "$cursor"';
|
||||
// FIXME: organization scope not work due to permissions
|
||||
var data = await Provider.of<SettingsModel>(context).query('''
|
||||
{
|
||||
$scope(login: "$login") {
|
||||
|
@ -61,9 +56,9 @@ class _ReposScreenState extends State<ReposScreen> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListScaffold(
|
||||
title: AppBarTitle(widget.star ? 'Stars' : 'Repositories'),
|
||||
onRefresh: () => _queryRepos(),
|
||||
onLoadMore: (cursor) => _queryRepos(cursor),
|
||||
title: AppBarTitle(star ? 'Stars' : 'Repositories'),
|
||||
onRefresh: () => _queryRepos(context),
|
||||
onLoadMore: (cursor) => _queryRepos(context, cursor),
|
||||
itemBuilder: (payload) => RepoItem(payload),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import 'package:flutter/cupertino.dart';
|
|||
import 'package:git_touch/widgets/app_bar_title.dart';
|
||||
import 'package:git_touch/widgets/table_view.dart';
|
||||
import 'package:git_touch/widgets/user_item.dart';
|
||||
import 'package:primer/primer.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:share/share.dart';
|
||||
import 'package:github_contributions/github_contributions.dart';
|
||||
|
@ -11,7 +10,6 @@ import 'package:git_touch/models/settings.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import '../scaffolds/refresh.dart';
|
||||
import '../widgets/entry_item.dart';
|
||||
import '../widgets/list_group.dart';
|
||||
import '../widgets/repo_item.dart';
|
||||
import '../widgets/link.dart';
|
||||
import '../widgets/action.dart';
|
||||
|
@ -156,26 +154,22 @@ class UserScreen extends StatelessWidget {
|
|||
fullscreenDialog: true,
|
||||
);
|
||||
} else {
|
||||
List<MyAction> actions = [];
|
||||
|
||||
if (payload['viewerCanFollow']) {
|
||||
actions.add(MyAction(
|
||||
text: payload['viewerIsFollowing'] ? 'Unfollow' : 'Follow',
|
||||
onPress: () async {
|
||||
if (payload['viewerIsFollowing']) {
|
||||
await Provider.of<SettingsModel>(context)
|
||||
.deleteWithCredentials('/user/following/$login');
|
||||
payload['viewerIsFollowing'] = false;
|
||||
} else {
|
||||
Provider.of<SettingsModel>(context)
|
||||
.putWithCredentials('/user/following/$login');
|
||||
payload['viewerIsFollowing'] = true;
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
actions.addAll([
|
||||
return ActionButton(title: 'User Actions', actions: [
|
||||
if (payload['viewerCanFollow'])
|
||||
MyAction(
|
||||
text: payload['viewerIsFollowing'] ? 'Unfollow' : 'Follow',
|
||||
onPress: () async {
|
||||
if (payload['viewerIsFollowing']) {
|
||||
await Provider.of<SettingsModel>(context)
|
||||
.deleteWithCredentials('/user/following/$login');
|
||||
payload['viewerIsFollowing'] = false;
|
||||
} else {
|
||||
Provider.of<SettingsModel>(context)
|
||||
.putWithCredentials('/user/following/$login');
|
||||
payload['viewerIsFollowing'] = true;
|
||||
}
|
||||
},
|
||||
),
|
||||
MyAction(
|
||||
text: 'Share',
|
||||
onPress: () {
|
||||
|
@ -189,8 +183,6 @@ class UserScreen extends StatelessWidget {
|
|||
},
|
||||
),
|
||||
]);
|
||||
|
||||
return ActionButton(title: 'User Actions', actions: actions);
|
||||
}
|
||||
},
|
||||
bodyBuilder: (data) {
|
||||
|
@ -211,13 +203,12 @@ class UserScreen extends StatelessWidget {
|
|||
EntryItem(
|
||||
count: payload['repositories']['totalCount'],
|
||||
text: 'Repositories',
|
||||
screenBuilder: (context) => ReposScreen(login: login),
|
||||
screenBuilder: (context) => ReposScreen(login),
|
||||
),
|
||||
EntryItem(
|
||||
count: payload['starredRepositories']['totalCount'],
|
||||
text: 'Stars',
|
||||
screenBuilder: (context) =>
|
||||
ReposScreen(login: login, star: true),
|
||||
screenBuilder: (context) => ReposScreen(login, star: true),
|
||||
),
|
||||
EntryItem(
|
||||
count: payload['followers']['totalCount'],
|
||||
|
|
Loading…
Reference in New Issue