feat: gists screen (#80)

closes: #66

Co-authored-by: Rongjian Zhang <pd4d10@gmail.com>
This commit is contained in:
Shreyas Thirumalai
2020-05-14 10:58:12 +05:30
committed by GitHub
parent 7a11dca621
commit 094ad4b1bb
8 changed files with 359 additions and 13 deletions

View File

@@ -326,13 +326,47 @@ class GithubUserOrganizationItem {
_$GithubUserOrganizationItemFromJson(json);
}
@JsonSerializable(fieldRename: FieldRename.snake)
class GistFiles {
GistFiles({
this.filename,
this.size,
this.rawUrl,
this.type,
this.language,
this.truncated,
this.content,
});
String filename;
int size;
String rawUrl;
String type;
String language;
bool truncated;
String content;
factory GistFiles.fromJson(Map<String, dynamic> json) =>
_$GistFilesFromJson(json);
}
@JsonSerializable(fieldRename: FieldRename.snake)
class GithubGistsItem {
int id;
bool isFork;
bool isPublic;
String name;
String id;
String description;
bool public;
Map<String, GistFiles> files;
GithubEventUser owner;
List<GistFiles> get fileNames {
List<GistFiles> filenames = [];
files.forEach((String key, GistFiles value) {
filenames.add(value);
});
return filenames;
}
DateTime createdAt;
DateTime updatedAt;
GithubGistsItem();
factory GithubGistsItem.fromJson(Map<String, dynamic> json) =>
_$GithubGistsItemFromJson(json);

View File

@@ -461,12 +461,43 @@ Map<String, dynamic> _$GithubUserOrganizationItemToJson(
'url': instance.url,
};
GistFiles _$GistFilesFromJson(Map<String, dynamic> json) {
return GistFiles(
filename: json['filename'] as String,
size: json['size'] as int,
rawUrl: json['raw_url'] as String,
type: json['type'] as String,
language: json['language'] as String,
truncated: json['truncated'] as bool,
content: json['content'] as String,
);
}
Map<String, dynamic> _$GistFilesToJson(GistFiles instance) => <String, dynamic>{
'filename': instance.filename,
'size': instance.size,
'raw_url': instance.rawUrl,
'type': instance.type,
'language': instance.language,
'truncated': instance.truncated,
'content': instance.content,
};
GithubGistsItem _$GithubGistsItemFromJson(Map<String, dynamic> json) {
return GithubGistsItem()
..id = json['id'] as int
..isFork = json['is_fork'] as bool
..isPublic = json['is_public'] as bool
..name = json['name'] as String
..id = json['id'] as String
..description = json['description'] as String
..public = json['public'] as bool
..files = (json['files'] as Map<String, dynamic>)?.map(
(k, e) => MapEntry(
k, e == null ? null : GistFiles.fromJson(e as Map<String, dynamic>)),
)
..owner = json['owner'] == null
? null
: GithubEventUser.fromJson(json['owner'] as Map<String, dynamic>)
..createdAt = json['created_at'] == null
? null
: DateTime.parse(json['created_at'] as String)
..updatedAt = json['updated_at'] == null
? null
: DateTime.parse(json['updated_at'] as String);
@@ -475,9 +506,11 @@ GithubGistsItem _$GithubGistsItemFromJson(Map<String, dynamic> json) {
Map<String, dynamic> _$GithubGistsItemToJson(GithubGistsItem instance) =>
<String, dynamic>{
'id': instance.id,
'is_fork': instance.isFork,
'is_public': instance.isPublic,
'name': instance.name,
'description': instance.description,
'public': instance.public,
'files': instance.files,
'owner': instance.owner,
'created_at': instance.createdAt?.toIso8601String(),
'updated_at': instance.updatedAt?.toIso8601String(),
};

View File

@@ -7,6 +7,7 @@ import 'package:git_touch/screens/code_theme.dart';
import 'package:git_touch/screens/gh_commits.dart';
import 'package:git_touch/screens/gh_contributors.dart';
import 'package:git_touch/screens/gh_files.dart';
import 'package:git_touch/screens/gh_gists_files.dart';
import 'package:git_touch/screens/gh_org_repos.dart';
import 'package:git_touch/screens/gl_commit.dart';
import 'package:git_touch/screens/gl_starrers.dart';
@@ -38,8 +39,9 @@ import 'package:git_touch/screens/settings.dart';
import 'package:git_touch/screens/gh_user.dart';
import 'package:git_touch/screens/gh_users.dart';
import 'package:git_touch/screens/gh_user_organization.dart';
import 'package:git_touch/screens/gh_gists.dart';
import 'package:git_touch/screens/gh_gist_object.dart';
import 'package:git_touch/screens/gh_compare.dart';
// import 'package:git_touch/screens/gh_gists.dart';
class RouterScreen {
String path;
@@ -78,6 +80,8 @@ class GithubRouter {
GithubRouter.watchers,
GithubRouter.contributors,
GithubRouter.files,
GithubRouter.gistFiles,
GithubRouter.gistObject,
GithubRouter.compare,
];
static final user = RouterScreen('/:login', (_, p) {
@@ -99,7 +103,7 @@ class GithubRouter {
case 'organizations':
return GhUserOrganizationScreen(login);
case 'gists':
// return GhGistsScreen(login);
return GhGistsScreen(login);
default:
return GhUserScreen(login);
}
@@ -112,6 +116,18 @@ class GithubRouter {
branch: p['ref'].first);
}
});
static final gistObject = RouterScreen('/:login/gists/:id/:file', (_, p) {
return GistObjectScreen(
p['login'].first,
p['id'].first,
p['file'].first,
raw: p['raw']?.first,
content: p['content'].first,
);
});
static final gistFiles = RouterScreen('/:login/gists/:id', (_, p) {
return GhGistsFilesScreen(p['login'].first, p['id'].first);
});
static final issueAdd = RouterScreen('/:owner/:name/issues/new', (_, p) {
return GhIssueFormScreen(p['owner'].first, p['name'].first);
});

View File

@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:git_touch/scaffolds/common.dart';
import 'package:git_touch/widgets/app_bar_title.dart';
import 'package:git_touch/widgets/blob_view.dart';
import 'package:git_touch/widgets/action_entry.dart';
class GistObjectScreen extends StatelessWidget {
final String login;
final String id;
final String file;
final String raw;
final String content;
GistObjectScreen(this.login, this.id, this.file, {this.raw, this.content});
@override
Widget build(BuildContext context) {
return CommonScaffold(
title: AppBarTitle(file),
action: ActionEntry(
iconData: Icons.settings,
url: '/choose-code-theme',
),
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: BlobView(
file,
text: content,
)));
}
}

48
lib/screens/gh_gists.dart Normal file
View File

@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:git_touch/models/github.dart';
import 'package:git_touch/scaffolds/list_stateful.dart';
import 'package:git_touch/widgets/app_bar_title.dart';
import 'package:git_touch/widgets/gists_item.dart';
import 'package:provider/provider.dart';
import 'package:git_touch/models/auth.dart';
class GhGistsScreen extends StatelessWidget {
final String login;
GhGistsScreen(this.login);
Future<ListPayload<GithubGistsItem, int>> _query(BuildContext context,
[int page = 1]) async {
final auth = Provider.of<AuthModel>(context);
final res = await auth.ghClient.getJSON<List, List<GithubGistsItem>>(
'/users/$login/gists?page=$page',
convert: (vs) => [for (var v in vs) GithubGistsItem.fromJson(v)],
);
return ListPayload(
cursor: page + 1,
items: res,
hasMore: res.isNotEmpty,
);
}
@override
Widget build(BuildContext context) {
return ListStatefulScaffold<GithubGistsItem, int>(
title: AppBarTitle('Gists'),
onRefresh: () => _query(context),
onLoadMore: (cursor) => _query(context, cursor),
itemBuilder: (v) {
return GistsItem(
description: v.description,
login: login,
files: v.files,
filenames: v.fileNames,
language: v.fileNames[0].language,
avatarUrl: v.owner.avatarUrl,
updatedAt: v.updatedAt,
id: v.id,
);
},
);
}
}

View File

@@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:git_touch/models/github.dart';
import 'package:git_touch/scaffolds/refresh_stateful.dart';
import 'package:git_touch/widgets/app_bar_title.dart';
import 'package:git_touch/widgets/object_tree.dart';
import 'package:provider/provider.dart';
import 'package:git_touch/models/auth.dart';
class GhGistsFilesScreen extends StatelessWidget {
final String id;
final String login;
GhGistsFilesScreen(this.login, this.id);
@override
Widget build(BuildContext context) {
final auth = Provider.of<AuthModel>(context);
return RefreshStatefulScaffold<GithubGistsItem>(
title: AppBarTitle('Files'),
fetchData: () async {
final data = await auth.ghClient.getJSON(
'/gists/$id',
convert: (vs) => GithubGistsItem.fromJson(vs),
);
return data;
},
bodyBuilder: (payload, _) {
return ObjectTree(
items: payload.fileNames.map((v) {
final uri = Uri(
path: '/github/$login/gists/$id/${v.filename}',
queryParameters: {
'content': v.content,
...(v.rawUrl == null ? {} : {'raw': v.rawUrl}),
},
).toString();
return ObjectTreeItem(
url: uri,
type: 'file',
name: v.filename,
downloadUrl: v.rawUrl,
size: v.size,
);
}),
);
},
);
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:git_touch/graphql/gh.dart';
import 'package:git_touch/models/github.dart';
import 'package:git_touch/models/theme.dart';
import 'package:git_touch/scaffolds/refresh_stateful.dart';
import 'package:git_touch/utils/utils.dart';
@@ -152,6 +153,10 @@ class GhUserScreen extends StatelessWidget {
TableView(
hasIcon: true,
items: [
TableViewItem(
leftIconData: Octicons.book,
text: Text('Gists'),
url: '/github/$login?tab=gists'),
TableViewItem(
leftIconData: Octicons.home,
text: Text('Organizations'),

130
lib/widgets/gists_item.dart Normal file
View File

@@ -0,0 +1,130 @@
import 'package:flutter/material.dart';
import 'package:git_touch/models/github.dart';
import 'package:git_touch/models/theme.dart';
import 'package:git_touch/utils/utils.dart';
import 'package:git_touch/widgets/avatar.dart';
import 'package:git_touch/widgets/link.dart';
import 'package:provider/provider.dart';
import 'package:github/src/const/language_color.dart';
import 'package:timeago/timeago.dart' as timeago;
class GistsItem extends StatelessWidget {
final String description;
final String login;
final Map<String, GistFiles> files;
final List<GistFiles> filenames;
final String language;
final String avatarUrl;
final DateTime updatedAt;
final String id;
GistsItem({
@required this.description,
@required this.login,
@required this.files,
@required this.filenames,
@required this.language,
@required this.avatarUrl,
@required this.updatedAt,
@required this.id,
});
@override
Widget build(BuildContext context) {
final theme = Provider.of<ThemeModel>(context);
return Link(
url: '/github/$login/gists/$id',
child: Container(
padding: CommonStyle.padding,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Avatar(
url: avatarUrl,
size: AvatarSize.small,
linkUrl: '/github/$login',
),
SizedBox(width: 8),
Expanded(
child: Text.rich(
TextSpan(children: [
TextSpan(
text: '$login / ',
style: TextStyle(
fontSize: 18,
color: theme.palette.primary,
),
),
TextSpan(
text: filenames[0].filename,
style: TextStyle(
fontSize: 18,
color: theme.palette.primary,
fontWeight: FontWeight.w600,
),
),
]),
overflow: TextOverflow.ellipsis,
),
),
],
),
SizedBox(height: 8),
if (description != null && description.isNotEmpty) ...[
Text(
description,
style: TextStyle(
color: theme.palette.secondaryText,
fontSize: 16,
),
),
SizedBox(height: 10),
],
if (updatedAt != null) ...[
Text(
'Updated ${timeago.format(updatedAt)}',
style: TextStyle(
fontSize: 14,
color: theme.palette.tertiaryText,
),
),
SizedBox(height: 10),
],
DefaultTextStyle(
style: TextStyle(color: theme.palette.text, fontSize: 14),
child: Row(
children: <Widget>[
if (language != null)
Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: convertColor(languagesColor[language]),
shape: BoxShape.circle,
),
),
SizedBox(width: 4),
if (language != null)
Text(
language,
overflow: TextOverflow.ellipsis,
),
SizedBox(width: 24),
],
),
),
],
),
),
],
),
),
);
}
}