diff --git a/lib/models/gitlab.dart b/lib/models/gitlab.dart index e0edeb1..4a5b7e7 100644 --- a/lib/models/gitlab.dart +++ b/lib/models/gitlab.dart @@ -2,6 +2,22 @@ import 'package:json_annotation/json_annotation.dart'; part 'gitlab.g.dart'; +@JsonSerializable(fieldRename: FieldRename.snake) +class GitlabUserProject { + int id; + GitlabUser owner; + String name; + String description; + int starCount; + int forksCount; + String visibility; + + GitlabUserProject(); + + factory GitlabUserProject.fromJson(Map json) => + _$GitlabUserProjectFromJson(json); +} + @JsonSerializable(fieldRename: FieldRename.snake) class GitlabUser { int id; @@ -15,22 +31,6 @@ class GitlabUser { _$GitlabUserFromJson(json); } -@JsonSerializable(fieldRename: FieldRename.snake) -class GitlabProject { - int id; - GitlabUser owner; - String name; - String description; - int starCount; - int forksCount; - String visibility; - - GitlabProject(); - - factory GitlabProject.fromJson(Map json) => - _$GitlabProjectFromJson(json); -} - @JsonSerializable(fieldRename: FieldRename.snake) class GitlabTodoProject { String pathWithNamespace; @@ -80,3 +80,44 @@ class GitlabIssueNote { factory GitlabIssueNote.fromJson(Map json) => _$GitlabIssueNoteFromJson(json); } + +@JsonSerializable(fieldRename: FieldRename.snake) +class GitlabProject { + int id; + String name; + String avatarUrl; + String description; + int starCount; + int forksCount; + String visibility; + String readmeUrl; + String webUrl; + GitlabProjectNamespace namespace; + bool issuesEnabled; + int openIssuesCount; + bool mergeRequestsEnabled; + + @JsonKey(ignore: true) + String readme; + + @JsonKey(ignore: true) + Map languages; + + @JsonKey(ignore: true) + String primaryLanguage; + + GitlabProject(); + + factory GitlabProject.fromJson(Map json) => + _$GitlabProjectFromJson(json); +} + +@JsonSerializable(fieldRename: FieldRename.snake) +class GitlabProjectNamespace { + int id; + String name; + GitlabProjectNamespace(); + + factory GitlabProjectNamespace.fromJson(Map json) => + _$GitlabProjectNamespaceFromJson(json); +} diff --git a/lib/models/gitlab.g.dart b/lib/models/gitlab.g.dart index cd66e70..1145c74 100644 --- a/lib/models/gitlab.g.dart +++ b/lib/models/gitlab.g.dart @@ -6,6 +6,30 @@ part of 'gitlab.dart'; // JsonSerializableGenerator // ************************************************************************** +GitlabUserProject _$GitlabUserProjectFromJson(Map json) { + return GitlabUserProject() + ..id = json['id'] as int + ..owner = json['owner'] == null + ? null + : GitlabUser.fromJson(json['owner'] as Map) + ..name = json['name'] as String + ..description = json['description'] as String + ..starCount = json['star_count'] as int + ..forksCount = json['forks_count'] as int + ..visibility = json['visibility'] as String; +} + +Map _$GitlabUserProjectToJson(GitlabUserProject instance) => + { + 'id': instance.id, + 'owner': instance.owner, + 'name': instance.name, + 'description': instance.description, + 'star_count': instance.starCount, + 'forks_count': instance.forksCount, + 'visibility': instance.visibility, + }; + GitlabUser _$GitlabUserFromJson(Map json) { return GitlabUser() ..id = json['id'] as int @@ -22,30 +46,6 @@ Map _$GitlabUserToJson(GitlabUser instance) => 'avatar_url': instance.avatarUrl, }; -GitlabProject _$GitlabProjectFromJson(Map json) { - return GitlabProject() - ..id = json['id'] as int - ..owner = json['owner'] == null - ? null - : GitlabUser.fromJson(json['owner'] as Map) - ..name = json['name'] as String - ..description = json['description'] as String - ..starCount = json['star_count'] as int - ..forksCount = json['forks_count'] as int - ..visibility = json['visibility'] as String; -} - -Map _$GitlabProjectToJson(GitlabProject instance) => - { - 'id': instance.id, - 'owner': instance.owner, - 'name': instance.name, - 'description': instance.description, - 'star_count': instance.starCount, - 'forks_count': instance.forksCount, - 'visibility': instance.visibility, - }; - GitlabTodoProject _$GitlabTodoProjectFromJson(Map json) { return GitlabTodoProject() ..pathWithNamespace = json['path_with_namespace'] as String; @@ -115,3 +115,54 @@ Map _$GitlabIssueNoteToJson(GitlabIssueNote instance) => 'author': instance.author, 'body': instance.body, }; + +GitlabProject _$GitlabProjectFromJson(Map json) { + return GitlabProject() + ..id = json['id'] as int + ..name = json['name'] as String + ..avatarUrl = json['avatar_url'] as String + ..description = json['description'] as String + ..starCount = json['star_count'] as int + ..forksCount = json['forks_count'] as int + ..visibility = json['visibility'] as String + ..readmeUrl = json['readme_url'] as String + ..webUrl = json['web_url'] as String + ..namespace = json['namespace'] == null + ? null + : GitlabProjectNamespace.fromJson( + json['namespace'] as Map) + ..issuesEnabled = json['issues_enabled'] as bool + ..openIssuesCount = json['open_issues_count'] as int + ..mergeRequestsEnabled = json['merge_requests_enabled'] as bool; +} + +Map _$GitlabProjectToJson(GitlabProject instance) => + { + 'id': instance.id, + 'name': instance.name, + 'avatar_url': instance.avatarUrl, + 'description': instance.description, + 'star_count': instance.starCount, + 'forks_count': instance.forksCount, + 'visibility': instance.visibility, + 'readme_url': instance.readmeUrl, + 'web_url': instance.webUrl, + 'namespace': instance.namespace, + 'issues_enabled': instance.issuesEnabled, + 'open_issues_count': instance.openIssuesCount, + 'merge_requests_enabled': instance.mergeRequestsEnabled, + }; + +GitlabProjectNamespace _$GitlabProjectNamespaceFromJson( + Map json) { + return GitlabProjectNamespace() + ..id = json['id'] as int + ..name = json['name'] as String; +} + +Map _$GitlabProjectNamespaceToJson( + GitlabProjectNamespace instance) => + { + 'id': instance.id, + 'name': instance.name, + }; diff --git a/lib/screens/gitlab_project.dart b/lib/screens/gitlab_project.dart new file mode 100644 index 0000000..581e169 --- /dev/null +++ b/lib/screens/gitlab_project.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:git_touch/models/auth.dart'; +import 'package:git_touch/models/gitlab.dart'; +import 'package:git_touch/scaffolds/refresh_stateful.dart'; +import 'package:git_touch/utils/utils.dart'; +import 'package:git_touch/widgets/app_bar_title.dart'; +import 'package:git_touch/widgets/entry_item.dart'; +import 'package:git_touch/widgets/markdown_view.dart'; +import 'package:git_touch/widgets/table_view.dart'; +import 'package:provider/provider.dart'; +import 'package:git_touch/models/theme.dart'; +import 'package:git_touch/widgets/repository_item.dart'; +import 'package:git_touch/widgets/action_button.dart'; + +class GitlabProjectScreen extends StatelessWidget { + final int id; + final String branch; + + GitlabProjectScreen(this.id, {this.branch}); + + @override + Widget build(BuildContext context) { + return RefreshStatefulScaffold( + title: AppBarTitle('Project'), + fetchData: () async { + final auth = Provider.of(context); + final json = await auth.fetchGitlab('/projects/$id'); + final project = GitlabProject.fromJson(json); + if (project.readmeUrl != null) { + project.readme = await auth.fetchWithGitlabToken( + project.readmeUrl.replaceFirst(r'/blob/', '/raw/')); + } + final l = await auth.fetchGitlab('/projects/$id/languages'); + project.languages = Map.from(l); + return project; + }, + actionBuilder: (data, setState) { + if (data == null) + return ActionButton(title: 'Project Actions', items: []); + + return ActionButton( + title: 'Project Actions', + items: [ + ActionItem.share(data.webUrl), + ActionItem.launch(data.webUrl), + ], + ); + }, + bodyBuilder: (data, _) { + final langWidth = MediaQuery.of(context).size.width - + CommonStyle.padding.left - + CommonStyle.padding.right - + data.languages.length + + 1; + + final theme = Provider.of(context); + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RepositoryItem.raw( + data.namespace.name, + data.avatarUrl, + data.name, + data.description, + Octicons.repo, // TODO: + data.starCount, + data.forksCount, + data.languages.keys.first, + null, + null, + [], + inRepoScreen: true), + CommonStyle.border, + Row( + children: [ + EntryItem( + count: data.starCount, + text: 'Stars', + ), + EntryItem( + count: data.forksCount, + text: 'Forks', // TODO: + ), + ], + ), + CommonStyle.verticalGap, + if (data.languages.isNotEmpty) + Container( + color: theme.palette.background, + padding: CommonStyle.padding.copyWith(top: 8, bottom: 8), + child: ClipRRect( + borderRadius: BorderRadius.circular(2), + child: SizedBox( + height: 10, + child: Row( + children: join( + SizedBox(width: 1), + data.languages.entries + .map((e) => Container( + color: convertColor('#ff0'), + width: langWidth * + e.value / + data.languages.length)) + .toList(), + ), + ), + ), + ), + ), + TableView( + hasIcon: true, + items: [ + TableViewItem( + leftIconData: Octicons.code, + text: Text('Code'), + // screenBuilder: (_) => GitlabObjectScreen(), + ), + if (data.issuesEnabled) + TableViewItem( + leftIconData: Octicons.issue_opened, + text: Text('Issues'), + rightWidget: + Text(numberFormat.format(data.openIssuesCount)), + ), + if (data.mergeRequestsEnabled) + TableViewItem( + leftIconData: Octicons.git_pull_request, + text: Text('Merge requests'), + ), + ], + ), + CommonStyle.verticalGap, + if (data.readme != null) + Container( + padding: CommonStyle.padding, + color: theme.palette.background, + child: MarkdownView(data.readme), + ), + CommonStyle.verticalGap, + ], + ); + }, + ); + } +} diff --git a/lib/screens/gitlab_repository.dart b/lib/screens/gitlab_repository.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/screens/gitlab_user.dart b/lib/screens/gitlab_user.dart index c22a72c..a113510 100644 --- a/lib/screens/gitlab_user.dart +++ b/lib/screens/gitlab_user.dart @@ -15,7 +15,8 @@ class GitlabUserScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return RefreshStatefulScaffold>>( + return RefreshStatefulScaffold< + Tuple2>>( title: Text('User'), fetchData: () async { final auth = Provider.of(context); @@ -25,7 +26,7 @@ class GitlabUserScreen extends StatelessWidget { final v1 = await auth.fetchGitlab('/users/${user.id}/projects'); final projects = - (v1 as List).map((v) => GitlabProject.fromJson(v)).toList(); + (v1 as List).map((v) => GitlabUserProject.fromJson(v)).toList(); return Tuple2(user, projects); }, diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index a11123c..6ebbc30 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -29,7 +29,7 @@ class Avatar extends StatelessWidget { borderRadius: BorderRadius.circular(4), child: FadeInImage.assetNetwork( placeholder: 'images/octoface.png', - image: url, + image: url ?? 'images/octoface.png', width: size, height: size, fadeInDuration: Duration(milliseconds: 200), diff --git a/lib/widgets/repository_item.dart b/lib/widgets/repository_item.dart index 3d6e1b5..6b617fc 100644 --- a/lib/widgets/repository_item.dart +++ b/lib/widgets/repository_item.dart @@ -5,6 +5,7 @@ import 'package:git_touch/graphql/github_user.dart'; import 'package:git_touch/models/gitea.dart'; import 'package:git_touch/models/gitlab.dart'; import 'package:git_touch/models/theme.dart'; +import 'package:git_touch/screens/gitlab_project.dart'; import 'package:git_touch/screens/repository.dart'; import 'package:git_touch/widgets/action_button.dart'; import 'package:git_touch/widgets/avatar.dart'; @@ -112,7 +113,7 @@ class RepositoryItem extends StatelessWidget { } } - RepositoryItem.gitlab(GitlabProject payload, {this.inRepoScreen = false}) + RepositoryItem.gitlab(GitlabUserProject payload, {this.inRepoScreen = false}) : this.owner = payload.owner.name, this.avatarUrl = payload.owner.avatarUrl, this.name = payload.name, @@ -122,8 +123,7 @@ class RepositoryItem extends StatelessWidget { this.forkCount = payload.forksCount, this.primaryLanguageName = null, this.primaryLanguageColor = null, - this.screenBuilder = - ((_) => RepositoryScreen(payload.owner.username, payload.name)), + this.screenBuilder = ((_) => GitlabProjectScreen(payload.id)), this.topics = []; RepositoryItem.gitea(GiteaRepository payload, {this.inRepoScreen = false})