feat: add graphql types for user screen

This commit is contained in:
Rongjian Zhang 2019-12-06 21:51:33 +08:00
parent 405cc03a30
commit b11d6be74b
12 changed files with 101250 additions and 247 deletions

View File

@ -27,9 +27,7 @@ targets:
- schema: lib/github.schema.json
output: lib/graphql/gh_user.dart
queries_glob: lib/graphql/user.graphql
- schema: lib/github.schema.json
output: lib/graphql/gh_repository.dart
queries_glob: lib/graphql/repository.graphql
resolve_type_field: __typename
scalar_mapping:
- graphql_type: URI
dart_type: String

99007
lib/github.schema.json Normal file

File diff suppressed because it is too large Load Diff

1400
lib/graphql/gh_user.dart Normal file

File diff suppressed because it is too large Load Diff

431
lib/graphql/gh_user.g.dart Normal file
View File

@ -0,0 +1,431 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'gh_user.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
GhUser _$GhUserFromJson(Map<String, dynamic> json) {
return GhUser()
..repositoryOwner = json['repositoryOwner'] == null
? null
: RepositoryOwner.fromJson(
json['repositoryOwner'] as Map<String, dynamic>);
}
Map<String, dynamic> _$GhUserToJson(GhUser instance) => <String, dynamic>{
'repositoryOwner': instance.repositoryOwner?.toJson(),
};
RepositoryOwner _$RepositoryOwnerFromJson(Map<String, dynamic> json) {
return RepositoryOwner()..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$RepositoryOwnerToJson(RepositoryOwner instance) =>
<String, dynamic>{
'__typename': instance.resolveType,
};
User _$UserFromJson(Map<String, dynamic> json) {
return User()
..login = json['login'] as String
..name = json['name'] as String
..avatarUrl = json['avatarUrl'] as String
..bio = json['bio'] as String
..company = json['company'] as String
..location = json['location'] as String
..email = json['email'] as String
..websiteUrl = json['websiteUrl'] as String
..starredRepositories = json['starredRepositories'] == null
? null
: StarredRepositoryConnection.fromJson(
json['starredRepositories'] as Map<String, dynamic>)
..followers = json['followers'] == null
? null
: FollowerConnection.fromJson(json['followers'] as Map<String, dynamic>)
..following = json['following'] == null
? null
: FollowingConnection.fromJson(
json['following'] as Map<String, dynamic>)
..repositories = json['repositories'] == null
? null
: RepositoryConnection.fromJson(
json['repositories'] as Map<String, dynamic>)
..pinnedItems = json['pinnedItems'] == null
? null
: PinnableItemConnection.fromJson(
json['pinnedItems'] as Map<String, dynamic>)
..viewerCanFollow = json['viewerCanFollow'] as bool
..viewerIsFollowing = json['viewerIsFollowing'] as bool
..url = json['url'] as String
..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'login': instance.login,
'name': instance.name,
'avatarUrl': instance.avatarUrl,
'bio': instance.bio,
'company': instance.company,
'location': instance.location,
'email': instance.email,
'websiteUrl': instance.websiteUrl,
'starredRepositories': instance.starredRepositories?.toJson(),
'followers': instance.followers?.toJson(),
'following': instance.following?.toJson(),
'repositories': instance.repositories?.toJson(),
'pinnedItems': instance.pinnedItems?.toJson(),
'viewerCanFollow': instance.viewerCanFollow,
'viewerIsFollowing': instance.viewerIsFollowing,
'url': instance.url,
'__typename': instance.resolveType,
};
StarredRepositoryConnection _$StarredRepositoryConnectionFromJson(
Map<String, dynamic> json) {
return StarredRepositoryConnection()..totalCount = json['totalCount'] as int;
}
Map<String, dynamic> _$StarredRepositoryConnectionToJson(
StarredRepositoryConnection instance) =>
<String, dynamic>{
'totalCount': instance.totalCount,
};
FollowerConnection _$FollowerConnectionFromJson(Map<String, dynamic> json) {
return FollowerConnection()..totalCount = json['totalCount'] as int;
}
Map<String, dynamic> _$FollowerConnectionToJson(FollowerConnection instance) =>
<String, dynamic>{
'totalCount': instance.totalCount,
};
FollowingConnection _$FollowingConnectionFromJson(Map<String, dynamic> json) {
return FollowingConnection()..totalCount = json['totalCount'] as int;
}
Map<String, dynamic> _$FollowingConnectionToJson(
FollowingConnection instance) =>
<String, dynamic>{
'totalCount': instance.totalCount,
};
RepositoryConnection _$RepositoryConnectionFromJson(Map<String, dynamic> json) {
return RepositoryConnection()
..totalCount = json['totalCount'] as int
..nodes = (json['nodes'] as List)
?.map((e) =>
e == null ? null : Repository.fromJson(e as Map<String, dynamic>))
?.toList();
}
Map<String, dynamic> _$RepositoryConnectionToJson(
RepositoryConnection instance) =>
<String, dynamic>{
'totalCount': instance.totalCount,
'nodes': instance.nodes?.map((e) => e?.toJson())?.toList(),
};
Repository _$RepositoryFromJson(Map<String, dynamic> json) {
return Repository()
..owner = json['owner'] == null
? null
: RepositoryOwner.fromJson(json['owner'] as Map<String, dynamic>)
..name = json['name'] as String
..description = json['description'] as String
..isPrivate = json['isPrivate'] as bool
..isFork = json['isFork'] as bool
..stargazers = json['stargazers'] == null
? null
: StargazerConnection.fromJson(
json['stargazers'] as Map<String, dynamic>)
..forks = json['forks'] == null
? null
: RepositoryConnection.fromJson(json['forks'] as Map<String, dynamic>)
..primaryLanguage = json['primaryLanguage'] == null
? null
: Language.fromJson(json['primaryLanguage'] as Map<String, dynamic>)
..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$RepositoryToJson(Repository instance) =>
<String, dynamic>{
'owner': instance.owner?.toJson(),
'name': instance.name,
'description': instance.description,
'isPrivate': instance.isPrivate,
'isFork': instance.isFork,
'stargazers': instance.stargazers?.toJson(),
'forks': instance.forks?.toJson(),
'primaryLanguage': instance.primaryLanguage?.toJson(),
'__typename': instance.resolveType,
};
StargazerConnection _$StargazerConnectionFromJson(Map<String, dynamic> json) {
return StargazerConnection()..totalCount = json['totalCount'] as int;
}
Map<String, dynamic> _$StargazerConnectionToJson(
StargazerConnection instance) =>
<String, dynamic>{
'totalCount': instance.totalCount,
};
Language _$LanguageFromJson(Map<String, dynamic> json) {
return Language()
..color = json['color'] as String
..name = json['name'] as String
..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$LanguageToJson(Language instance) => <String, dynamic>{
'color': instance.color,
'name': instance.name,
'__typename': instance.resolveType,
};
Node _$NodeFromJson(Map<String, dynamic> json) {
return Node()..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$NodeToJson(Node instance) => <String, dynamic>{
'__typename': instance.resolveType,
};
PinnableItem _$PinnableItemFromJson(Map<String, dynamic> json) {
return PinnableItem();
}
Map<String, dynamic> _$PinnableItemToJson(PinnableItem instance) =>
<String, dynamic>{};
ProjectOwner _$ProjectOwnerFromJson(Map<String, dynamic> json) {
return ProjectOwner()..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$ProjectOwnerToJson(ProjectOwner instance) =>
<String, dynamic>{
'__typename': instance.resolveType,
};
RegistryPackageOwner _$RegistryPackageOwnerFromJson(Map<String, dynamic> json) {
return RegistryPackageOwner()..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$RegistryPackageOwnerToJson(
RegistryPackageOwner instance) =>
<String, dynamic>{
'__typename': instance.resolveType,
};
RegistryPackageSearch _$RegistryPackageSearchFromJson(
Map<String, dynamic> json) {
return RegistryPackageSearch()..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$RegistryPackageSearchToJson(
RegistryPackageSearch instance) =>
<String, dynamic>{
'__typename': instance.resolveType,
};
Subscribable _$SubscribableFromJson(Map<String, dynamic> json) {
return Subscribable()..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$SubscribableToJson(Subscribable instance) =>
<String, dynamic>{
'__typename': instance.resolveType,
};
Starrable _$StarrableFromJson(Map<String, dynamic> json) {
return Starrable()
..stargazers = json['stargazers'] == null
? null
: StargazerConnection.fromJson(
json['stargazers'] as Map<String, dynamic>)
..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$StarrableToJson(Starrable instance) => <String, dynamic>{
'stargazers': instance.stargazers?.toJson(),
'__typename': instance.resolveType,
};
UniformResourceLocatable _$UniformResourceLocatableFromJson(
Map<String, dynamic> json) {
return UniformResourceLocatable()..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$UniformResourceLocatableToJson(
UniformResourceLocatable instance) =>
<String, dynamic>{
'__typename': instance.resolveType,
};
RepositoryInfo _$RepositoryInfoFromJson(Map<String, dynamic> json) {
return RepositoryInfo()
..owner = json['owner'] == null
? null
: RepositoryOwner.fromJson(json['owner'] as Map<String, dynamic>)
..name = json['name'] as String
..description = json['description'] as String
..isPrivate = json['isPrivate'] as bool
..isFork = json['isFork'] as bool
..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$RepositoryInfoToJson(RepositoryInfo instance) =>
<String, dynamic>{
'owner': instance.owner?.toJson(),
'name': instance.name,
'description': instance.description,
'isPrivate': instance.isPrivate,
'isFork': instance.isFork,
'__typename': instance.resolveType,
};
PinnableItemConnection _$PinnableItemConnectionFromJson(
Map<String, dynamic> json) {
return PinnableItemConnection()
..nodes = (json['nodes'] as List)
?.map((e) =>
e == null ? null : PinnableItem.fromJson(e as Map<String, dynamic>))
?.toList();
}
Map<String, dynamic> _$PinnableItemConnectionToJson(
PinnableItemConnection instance) =>
<String, dynamic>{
'nodes': instance.nodes?.map((e) => e?.toJson())?.toList(),
};
AuditEntryActor _$AuditEntryActorFromJson(Map<String, dynamic> json) {
return AuditEntryActor();
}
Map<String, dynamic> _$AuditEntryActorToJson(AuditEntryActor instance) =>
<String, dynamic>{};
Actor _$ActorFromJson(Map<String, dynamic> json) {
return Actor()
..login = json['login'] as String
..avatarUrl = json['avatarUrl'] as String
..url = json['url'] as String
..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$ActorToJson(Actor instance) => <String, dynamic>{
'login': instance.login,
'avatarUrl': instance.avatarUrl,
'url': instance.url,
'__typename': instance.resolveType,
};
ProfileOwner _$ProfileOwnerFromJson(Map<String, dynamic> json) {
return ProfileOwner()
..login = json['login'] as String
..name = json['name'] as String
..location = json['location'] as String
..email = json['email'] as String
..websiteUrl = json['websiteUrl'] as String
..pinnedItems = json['pinnedItems'] == null
? null
: PinnableItemConnection.fromJson(
json['pinnedItems'] as Map<String, dynamic>)
..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$ProfileOwnerToJson(ProfileOwner instance) =>
<String, dynamic>{
'login': instance.login,
'name': instance.name,
'location': instance.location,
'email': instance.email,
'websiteUrl': instance.websiteUrl,
'pinnedItems': instance.pinnedItems?.toJson(),
'__typename': instance.resolveType,
};
Sponsorable _$SponsorableFromJson(Map<String, dynamic> json) {
return Sponsorable()..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$SponsorableToJson(Sponsorable instance) =>
<String, dynamic>{
'__typename': instance.resolveType,
};
Organization _$OrganizationFromJson(Map<String, dynamic> json) {
return Organization()
..login = json['login'] as String
..name = json['name'] as String
..avatarUrl = json['avatarUrl'] as String
..description = json['description'] as String
..location = json['location'] as String
..email = json['email'] as String
..websiteUrl = json['websiteUrl'] as String
..url = json['url'] as String
..pinnedItems = json['pinnedItems'] == null
? null
: PinnableItemConnection.fromJson(
json['pinnedItems'] as Map<String, dynamic>)
..pinnableItems = json['pinnableItems'] == null
? null
: PinnableItemConnection.fromJson(
json['pinnableItems'] as Map<String, dynamic>)
..membersWithRole = json['membersWithRole'] == null
? null
: OrganizationMemberConnection.fromJson(
json['membersWithRole'] as Map<String, dynamic>)
..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$OrganizationToJson(Organization instance) =>
<String, dynamic>{
'login': instance.login,
'name': instance.name,
'avatarUrl': instance.avatarUrl,
'description': instance.description,
'location': instance.location,
'email': instance.email,
'websiteUrl': instance.websiteUrl,
'url': instance.url,
'pinnedItems': instance.pinnedItems?.toJson(),
'pinnableItems': instance.pinnableItems?.toJson(),
'membersWithRole': instance.membersWithRole?.toJson(),
'__typename': instance.resolveType,
};
OrganizationMemberConnection _$OrganizationMemberConnectionFromJson(
Map<String, dynamic> json) {
return OrganizationMemberConnection()..totalCount = json['totalCount'] as int;
}
Map<String, dynamic> _$OrganizationMemberConnectionToJson(
OrganizationMemberConnection instance) =>
<String, dynamic>{
'totalCount': instance.totalCount,
};
MemberStatusable _$MemberStatusableFromJson(Map<String, dynamic> json) {
return MemberStatusable()..resolveType = json['__typename'] as String;
}
Map<String, dynamic> _$MemberStatusableToJson(MemberStatusable instance) =>
<String, dynamic>{
'__typename': instance.resolveType,
};
GhUserArguments _$GhUserArgumentsFromJson(Map<String, dynamic> json) {
return GhUserArguments(
login: json['login'] as String,
);
}
Map<String, dynamic> _$GhUserArgumentsToJson(GhUserArguments instance) =>
<String, dynamic>{
'login': instance.login,
};

144
lib/graphql/user.graphql Normal file
View File

@ -0,0 +1,144 @@
query($login: String!) {
repositoryOwner(login: $login) {
__typename
... on User {
login
name
avatarUrl
bio
company
location
email
websiteUrl
starredRepositories {
totalCount
}
followers {
totalCount
}
following {
totalCount
}
repositories(
first: 6
ownerAffiliations: OWNER
orderBy: { field: STARGAZERS, direction: DESC }
) {
totalCount
nodes {
owner {
__typename
login
avatarUrl
}
name
description
isPrivate
isFork
stargazers {
totalCount
}
forks {
totalCount
}
primaryLanguage {
color
name
}
}
}
pinnedItems(first: 6) {
nodes {
... on Repository {
owner {
__typename
login
avatarUrl
}
name
description
isPrivate
isFork
stargazers {
totalCount
}
forks {
totalCount
}
primaryLanguage {
color
name
}
}
}
}
viewerCanFollow
viewerIsFollowing
url
}
... on Organization {
login
name
avatarUrl
description
location
email
websiteUrl
url
pinnedItems(first: 6) {
nodes {
... on Repository {
owner {
__typename
login
avatarUrl
}
name
description
isPrivate
isFork
stargazers {
totalCount
}
forks {
totalCount
}
primaryLanguage {
color
name
}
}
}
}
pinnableItems(first: 6, types: [REPOSITORY]) {
totalCount
nodes {
... on Repository {
owner {
__typename
login
avatarUrl
}
name
description
isPrivate
isFork
stargazers {
totalCount
}
forks {
totalCount
}
primaryLanguage {
color
name
}
}
}
}
membersWithRole {
totalCount
}
}
}
}

View File

@ -1,18 +1,16 @@
import 'dart:io';
import 'dart:convert';
import 'dart:async';
// import 'package:artemis/client.dart';
// import 'package:gql_http_link/gql_http_link.dart';
import 'package:gql_http_link/gql_http_link.dart';
import 'package:artemis/artemis.dart';
import 'package:fimber/fimber.dart';
import 'package:http/http.dart' as http;
import 'package:uni_links/uni_links.dart';
import 'package:nanoid/nanoid.dart';
import 'package:url_launcher/url_launcher.dart';
// import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:shared_preferences/shared_preferences.dart';
// import '../utils/utils.dart';
import '../utils/constants.dart';
import '../utils/utils.dart';
import 'account.dart';
@ -188,19 +186,19 @@ class AuthModel with ChangeNotifier {
var _timeoutDuration = Duration(seconds: 10);
// var _timeoutDuration = Duration(seconds: 1);
// ArtemisClient _gqlClient;
// ArtemisClient get gqlClient {
// if (token == null) return null;
ArtemisClient _gqlClient;
ArtemisClient get gqlClient {
if (token == null) return null;
// if (_gqlClient == null) {
// _gqlClient = ArtemisClient.fromLink(
// HttpLink(_apiPrefix + '/graphql',
// defaultHeaders: {HttpHeaders.authorizationHeader: 'token $token'}),
// );
// }
if (_gqlClient == null) {
_gqlClient = ArtemisClient.fromLink(
HttpLink(_apiPrefix + '/graphql',
defaultHeaders: {HttpHeaders.authorizationHeader: 'token $token'}),
);
}
// return _gqlClient;
// }
return _gqlClient;
}
Future<dynamic> query(String query, [String _token]) async {
if (_token == null) {

View File

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:git_touch/graphql/gh_user.dart';
import 'package:git_touch/models/theme.dart';
import 'package:git_touch/scaffolds/refresh_stateful.dart';
import 'package:git_touch/screens/settings.dart';
@ -9,6 +10,7 @@ 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/entry_item.dart';
import 'package:git_touch/widgets/repository_item.dart';
import 'package:git_touch/widgets/table_view.dart';
import 'package:git_touch/widgets/text_contains_organization.dart';
import 'package:git_touch/widgets/user_contributions.dart';
@ -16,85 +18,19 @@ import 'package:git_touch/widgets/user_item.dart';
import 'package:github_contributions/github_contributions.dart';
import 'package:git_touch/models/auth.dart';
import 'package:provider/provider.dart';
import 'package:git_touch/widgets/repository_item.dart';
import 'package:git_touch/widgets/action_button.dart';
import 'package:tuple/tuple.dart';
class UserScreen extends StatelessWidget {
final String login;
UserScreen(this.login);
Future _query(BuildContext context) async {
var _login = login ?? Provider.of<AuthModel>(context).activeAccount.login;
var data = await Provider.of<AuthModel>(context).query('''
{
repositoryOwner(login: "$_login") {
__typename
... on User {
$userGqlChunk
company
location
email
websiteUrl
starredRepositories {
totalCount
}
followers {
totalCount
}
following {
totalCount
}
repositories(first: 6, ownerAffiliations: OWNER, orderBy: {field: STARGAZERS, direction: DESC}) {
totalCount
nodes {
$repoChunk
}
}
pinnedItems(first: 6) {
nodes {
... on Repository {
$repoChunk
}
}
}
viewerCanFollow
viewerIsFollowing
url
}
... on Organization {
login
name
avatarUrl
description
location
email
websiteUrl
url
pinnedItems(first: 6) {
nodes {
... on Repository {
$repoChunk
}
}
}
pinnableItems(first: 6, types: [REPOSITORY]) {
totalCount
nodes {
... on Repository {
$repoChunk
}
}
}
membersWithRole {
totalCount
}
}
}
}
'''); // Use pinnableItems instead of organization here due to token permission
return data['repositoryOwner'];
Future<RepositoryOwner> _query(BuildContext context) async {
final data = await Provider.of<AuthModel>(context)
.gqlClient
.execute(GhUserQuery(variables: GhUserArguments(login: 'pd4d10')));
return data.data.repositoryOwner;
}
Future<List<ContributionsInfo>> _fetchContributions(
@ -112,14 +48,209 @@ class UserScreen extends StatelessWidget {
}
}
Iterable<Widget> _buildPinnedItems(
Iterable<Repository> pinnedItems, Iterable<Repository> repositories) {
String title;
Iterable<Repository> items = [];
if (pinnedItems.isNotEmpty) {
title = 'pinned repositories';
items = pinnedItems;
} else if (repositories.isNotEmpty) {
title = 'popular repositories';
items = repositories;
}
if (items.isEmpty) return [];
return [
CommonStyle.verticalGap,
if (title != null) TableViewHeader(title),
...join(
CommonStyle.border,
items.map((item) {
return RepositoryItem.github(item);
}).toList(),
),
];
}
Widget _buildUser(
BuildContext context, User user, List<ContributionsInfo> contributions) {
final theme = Provider.of<ThemeModel>(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
UserItem(
login: user.login,
name: user.name,
avatarUrl: user.avatarUrl,
bio: user.bio,
inUserScreen: true,
),
CommonStyle.border,
Row(children: [
EntryItem(
count: user.repositories.totalCount,
text: 'Repositories',
screenBuilder: (context) => RepositoriesScreen(user.login),
),
EntryItem(
count: user.starredRepositories.totalCount,
text: 'Stars',
screenBuilder: (context) => RepositoriesScreen.stars(user.login),
),
EntryItem(
count: user.followers.totalCount,
text: 'Followers',
screenBuilder: (context) => UsersScreen.followers(user.login),
),
EntryItem(
count: user.following.totalCount,
text: 'Following',
screenBuilder: (context) => UsersScreen.following(user.login),
),
]),
CommonStyle.verticalGap,
UserContributions(contributions),
CommonStyle.verticalGap,
TableView(
hasIcon: true,
items: [
if (isNotNullOrEmpty(user.company))
TableViewItem(
leftIconData: Octicons.organization,
text: TextContainsOrganization(
user.company,
style: TextStyle(fontSize: 16, color: theme.palette.text),
overflow: TextOverflow.ellipsis,
),
),
if (isNotNullOrEmpty(user.location))
TableViewItem(
leftIconData: Octicons.location,
text: Text(user.location),
onTap: () {
launchUrl('https://www.google.com/maps/place/' +
user.location.replaceAll(RegExp(r'\s+'), ''));
},
),
if (isNotNullOrEmpty(user.email))
TableViewItem(
leftIconData: Octicons.mail,
text: Text(user.email),
onTap: () {
launchUrl('mailto:' + user.email);
},
),
if (isNotNullOrEmpty(user.websiteUrl))
TableViewItem(
leftIconData: Octicons.link,
text: Text(user.websiteUrl),
onTap: () {
var url = user.websiteUrl;
if (!url.startsWith('http')) {
url = 'http://$url';
}
launchUrl(url);
},
),
],
),
..._buildPinnedItems(
user.pinnedItems.nodes
.where((n) => n is Repository)
.cast<Repository>(),
user.repositories.nodes),
CommonStyle.verticalGap,
],
);
}
Widget _buildOrganization(BuildContext context, Organization payload) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
UserItem(
login: payload.login,
name: payload.name,
avatarUrl: payload.avatarUrl,
bio: payload.description,
inUserScreen: true,
),
CommonStyle.border,
Row(children: [
EntryItem(
// count: payload.pinnableItems.totalCount,
count: 0, // TODO:
text: 'Repositories',
screenBuilder: (context) =>
RepositoriesScreen.ofOrganization(payload.login),
),
EntryItem(
count: payload.membersWithRole.totalCount,
text: 'Members',
screenBuilder: (context) => UsersScreen.members(payload.login),
),
]),
CommonStyle.verticalGap,
TableView(
hasIcon: true,
items: [
if (isNotNullOrEmpty(payload.location))
TableViewItem(
leftIconData: Octicons.location,
text: Text(payload.location),
onTap: () {
launchUrl('https://www.google.com/maps/place/' +
payload.location.replaceAll(RegExp(r'\s+'), ''));
},
),
if (isNotNullOrEmpty(payload.email))
TableViewItem(
leftIconData: Octicons.mail,
text: Text(payload.email),
onTap: () {
launchUrl('mailto:' + payload.email);
},
),
if (isNotNullOrEmpty(payload.websiteUrl))
TableViewItem(
leftIconData: Octicons.link,
text: Text(payload.websiteUrl),
onTap: () {
var url = payload.websiteUrl;
if (!url.startsWith('http')) {
url = 'http://$url';
}
launchUrl(url);
},
),
],
),
..._buildPinnedItems(
payload.pinnedItems.nodes
.where((n) => n is Repository)
.cast<Repository>(),
payload.pinnableItems.nodes
.where((n) => n is Repository)
.cast<Repository>(),
),
CommonStyle.verticalGap,
],
);
}
@override
Widget build(BuildContext context) {
return RefreshStatefulScaffold(
fetchData: () {
return Future.wait([
return RefreshStatefulScaffold<
Tuple2<RepositoryOwner, List<ContributionsInfo>>>(
fetchData: () async {
final vs = await Future.wait([
_query(context),
_fetchContributions(context),
]);
return Tuple2(
vs[0] as RepositoryOwner, vs[1] as List<ContributionsInfo>);
},
title: AppBarTitle('User'), // TODO:
actionBuilder: (data, _) {
@ -129,8 +260,10 @@ class UserScreen extends StatelessWidget {
items: [],
);
switch (data[0]['__typename']) {
final payload = data.item1;
switch (payload.resolveType) {
case 'User':
final user = payload as User;
if (login == null) {
return ActionEntry(
iconData: Icons.settings,
@ -144,37 +277,37 @@ class UserScreen extends StatelessWidget {
return ActionButton(
title: 'User Actions',
items: [
if (data != null && data[0]['viewerCanFollow'])
if (user.viewerCanFollow)
ActionItem(
text:
data[0]['viewerIsFollowing'] ? 'Unfollow' : 'Follow',
text: user.viewerIsFollowing ? 'Unfollow' : 'Follow',
onPress: (_) async {
if (data[0]['viewerIsFollowing']) {
if (user.viewerIsFollowing) {
await Provider.of<AuthModel>(context)
.deleteWithCredentials('/user/following/$login');
data[0]['viewerIsFollowing'] = false;
user.viewerIsFollowing = false;
} else {
Provider.of<AuthModel>(context)
.putWithCredentials('/user/following/$login');
data[0]['viewerIsFollowing'] = true;
user.viewerIsFollowing = true;
}
},
),
if (data != null) ...[
ActionItem.share(data[0]['url']),
ActionItem.launch(data[0]['url']),
ActionItem.share(user.url),
ActionItem.launch(user.url),
],
],
);
}
break;
case 'Organization':
final organization = payload as Organization;
return ActionButton(
title: 'Organization Actions',
items: [
if (data != null) ...[
ActionItem.share(data[0]['url']),
ActionItem.launch(data[0]['url']),
ActionItem.share(organization.url),
ActionItem.launch(organization.url),
],
],
);
@ -183,118 +316,15 @@ class UserScreen extends StatelessWidget {
}
},
bodyBuilder: (data, _) {
var user = data[0];
var contributions = data[1];
final isOrganization = user['__typename'] == 'Organization';
final theme = Provider.of<ThemeModel>(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
UserItem(
login: user['login'],
name: user['name'],
avatarUrl: user['avatarUrl'],
bio: isOrganization ? user['description'] : user['bio'],
inUserScreen: true,
),
CommonStyle.border,
Row(children: [
if (isOrganization) ...[
EntryItem(
count: user['pinnableItems']['totalCount'],
text: 'Repositories',
screenBuilder: (context) =>
RepositoriesScreen.ofOrganization(user['login']),
),
EntryItem(
count: user['membersWithRole']['totalCount'],
text: 'Members',
screenBuilder: (context) =>
UsersScreen.members(user['login']),
),
] else ...[
EntryItem(
count: user['repositories']['totalCount'],
text: 'Repositories',
screenBuilder: (context) => RepositoriesScreen(user['login']),
),
EntryItem(
count: user['starredRepositories']['totalCount'],
text: 'Stars',
screenBuilder: (context) =>
RepositoriesScreen.stars(user['login']),
),
EntryItem(
count: user['followers']['totalCount'],
text: 'Followers',
screenBuilder: (context) =>
UsersScreen.followers(user['login']),
),
EntryItem(
count: user['following']['totalCount'],
text: 'Following',
screenBuilder: (context) =>
UsersScreen.following(user['login']),
),
]
]),
CommonStyle.verticalGap,
if (contributions.isNotEmpty) ...[
UserContributions(contributions),
CommonStyle.verticalGap,
],
TableView(
hasIcon: true,
items: [
if (!isOrganization && isNotNullOrEmpty(user['company']))
TableViewItem(
leftIconData: Octicons.organization,
text: TextContainsOrganization(
user['company'],
style: TextStyle(fontSize: 16, color: theme.palette.text),
overflow: TextOverflow.ellipsis,
),
),
if (isNotNullOrEmpty(user['location']))
TableViewItem(
leftIconData: Octicons.location,
text: Text(user['location']),
onTap: () {
launchUrl('https://www.google.com/maps/place/' +
(user['location'] as String)
.replaceAll(RegExp(r'\s+'), ''));
},
),
if (isNotNullOrEmpty(user['email']))
TableViewItem(
leftIconData: Octicons.mail,
text: Text(user['email']),
onTap: () {
launchUrl('mailto:' + user['email']);
},
),
if (isNotNullOrEmpty(user['websiteUrl']))
TableViewItem(
leftIconData: Octicons.link,
text: Text(user['websiteUrl']),
onTap: () {
var url = user['websiteUrl'] as String;
if (!url.startsWith('http')) {
url = 'http://$url';
}
launchUrl(url);
},
),
],
),
...buildPinnedItems(
user['pinnedItems']['nodes'],
user[isOrganization ? 'pinnableItems' : 'repositories']
['nodes']),
CommonStyle.verticalGap,
],
);
final payload = data.item1;
switch (payload.resolveType) {
case 'User':
return _buildUser(context, payload as User, data.item2);
case 'Organization':
return _buildOrganization(context, payload as Organization);
default:
return null;
}
},
);
}

View File

@ -178,32 +178,3 @@ launchUrl(String url) async {
// TODO: fallback
}
}
Iterable<Widget> buildPinnedItems(List pinnedItems, List repositories) {
String title;
List items = [];
if (pinnedItems.isNotEmpty) {
title = 'pinned repositories';
items = pinnedItems;
} else if (repositories.isNotEmpty) {
title = 'popular repositories';
items = repositories;
}
items = items
.where((x) => x.isNotEmpty)
.toList(); // TODO: Pinned items may include Gist
if (items.isEmpty) return [];
return [
CommonStyle.verticalGap,
if (title != null) TableViewHeader(title),
...join(
CommonStyle.border,
items.map((item) {
return RepositoryItem(item);
}).toList(),
),
];
}

View File

@ -10,7 +10,12 @@ class EntryItem extends StatelessWidget {
final WidgetBuilder screenBuilder;
final String url;
EntryItem({this.count, this.text, this.screenBuilder, this.url});
EntryItem({
@required this.count,
@required this.text,
this.screenBuilder,
this.url,
});
@override
Widget build(BuildContext context) {

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:git_touch/graphql/gh_user.dart';
import 'package:git_touch/models/gitea.dart';
import 'package:git_touch/models/gitlab.dart';
import 'package:git_touch/models/theme.dart';
@ -67,6 +68,23 @@ class RepositoryItem extends StatelessWidget {
? []
: payload['repositoryTopics']['nodes'];
RepositoryItem.github(Repository payload, {this.inRepoScreen = false})
: this.owner = (payload.owner as User).login,
this.avatarUrl = (payload.owner as User).avatarUrl,
this.name = payload.name,
this.description = payload.description,
this.iconData = Octicons.repo, // TODO:
this.starCount = payload.stargazers.totalCount,
this.forkCount = payload.forks.totalCount,
this.primaryLanguageName = payload.primaryLanguage?.name,
this.primaryLanguageColor = payload.primaryLanguage?.color,
this.screenBuilder = ((_) =>
RepositoryScreen((payload.owner as User).login, payload.name)),
this.topics = []; // TODO:
// this.topics = payload['repositoryTopics'] == null
// ? []
// : payload['repositoryTopics']['nodes'];
RepositoryItem.gitlab(GitlabRepository payload, {this.inRepoScreen = false})
: this.owner = payload.owner.name,
this.avatarUrl = payload.owner.avatarUrl,

View File

@ -38,7 +38,8 @@ dependencies:
fimber: ^0.3.2
photo_view: ^0.7.0
gql: ^0.11.1
artemis: ^2.0.7
artemis: ^2.1.2
gql_link: ^0.2.0
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.