From 7a97bc22abdef9afaf6d27bfb6fabf5408d0afec Mon Sep 17 00:00:00 2001
From: Shreyas Thirumalai <shreyasthirumalai@gmail.com>
Date: Sun, 17 May 2020 09:50:11 +0530
Subject: [PATCH] feat: bitbucket issues, prs screens (#91)

---
 lib/models/bitbucket.dart   | 28 ++++++++++++++++++++
 lib/models/bitbucket.g.dart | 48 ++++++++++++++++++++++++++++++++++
 lib/router.dart             | 12 +++++++++
 lib/screens/bb_issues.dart  | 51 +++++++++++++++++++++++++++++++++++++
 lib/screens/bb_pulls.dart   | 51 +++++++++++++++++++++++++++++++++++++
 lib/screens/bb_repo.dart    | 10 ++++++++
 6 files changed, 200 insertions(+)
 create mode 100644 lib/screens/bb_issues.dart
 create mode 100644 lib/screens/bb_pulls.dart

diff --git a/lib/models/bitbucket.dart b/lib/models/bitbucket.dart
index 1f6072b..9f1cdd4 100644
--- a/lib/models/bitbucket.dart
+++ b/lib/models/bitbucket.dart
@@ -95,3 +95,31 @@ class BbCommitAuthor {
   factory BbCommitAuthor.fromJson(Map<String, dynamic> json) =>
       _$BbCommitAuthorFromJson(json);
 }
+
+@JsonSerializable(fieldRename: FieldRename.snake)
+class BbIssues {
+  String priority;
+  String state;
+  BbRepo repository;
+  String title;
+  BbRepoOwner reporter;
+  DateTime createdOn;
+  Map<String, dynamic> links;
+  String get issueLink => links['self']['href'];
+  BbIssues();
+  factory BbIssues.fromJson(Map<String, dynamic> json) =>
+      _$BbIssuesFromJson(json);
+}
+
+@JsonSerializable(fieldRename: FieldRename.snake)
+class BbPulls {
+  String description;
+  BbRepoOwner author;
+  String title;
+  Map<String, dynamic> links;
+  String get pullRequestLink => links['self']['href'];
+  DateTime createdOn;
+  BbPulls();
+  factory BbPulls.fromJson(Map<String, dynamic> json) =>
+      _$BbPullsFromJson(json);
+}
diff --git a/lib/models/bitbucket.g.dart b/lib/models/bitbucket.g.dart
index 286c24b..1f34ae3 100644
--- a/lib/models/bitbucket.g.dart
+++ b/lib/models/bitbucket.g.dart
@@ -166,3 +166,51 @@ Map<String, dynamic> _$BbCommitAuthorToJson(BbCommitAuthor instance) =>
       'raw': instance.raw,
       'user': instance.user,
     };
+
+BbIssues _$BbIssuesFromJson(Map<String, dynamic> json) {
+  return BbIssues()
+    ..priority = json['priority'] as String
+    ..state = json['state'] as String
+    ..repository = json['repository'] == null
+        ? null
+        : BbRepo.fromJson(json['repository'] as Map<String, dynamic>)
+    ..title = json['title'] as String
+    ..reporter = json['reporter'] == null
+        ? null
+        : BbRepoOwner.fromJson(json['reporter'] as Map<String, dynamic>)
+    ..createdOn = json['created_on'] == null
+        ? null
+        : DateTime.parse(json['created_on'] as String)
+    ..links = json['links'] as Map<String, dynamic>;
+}
+
+Map<String, dynamic> _$BbIssuesToJson(BbIssues instance) => <String, dynamic>{
+      'priority': instance.priority,
+      'state': instance.state,
+      'repository': instance.repository,
+      'title': instance.title,
+      'reporter': instance.reporter,
+      'created_on': instance.createdOn?.toIso8601String(),
+      'links': instance.links,
+    };
+
+BbPulls _$BbPullsFromJson(Map<String, dynamic> json) {
+  return BbPulls()
+    ..description = json['description'] as String
+    ..author = json['author'] == null
+        ? null
+        : BbRepoOwner.fromJson(json['author'] as Map<String, dynamic>)
+    ..title = json['title'] as String
+    ..links = json['links'] as Map<String, dynamic>
+    ..createdOn = json['created_on'] == null
+        ? null
+        : DateTime.parse(json['created_on'] as String);
+}
+
+Map<String, dynamic> _$BbPullsToJson(BbPulls instance) => <String, dynamic>{
+      'description': instance.description,
+      'author': instance.author,
+      'title': instance.title,
+      'links': instance.links,
+      'created_on': instance.createdOn?.toIso8601String(),
+    };
diff --git a/lib/router.dart b/lib/router.dart
index 65431ec..091cca9 100644
--- a/lib/router.dart
+++ b/lib/router.dart
@@ -2,6 +2,8 @@ import 'package:fluro/fluro.dart';
 import 'package:git_touch/screens/bb_commits.dart';
 import 'package:git_touch/screens/bb_object.dart';
 import 'package:git_touch/screens/bb_repo.dart';
+import 'package:git_touch/screens/bb_issues.dart';
+import 'package:git_touch/screens/bb_pulls.dart';
 import 'package:git_touch/screens/bb_user.dart';
 import 'package:git_touch/screens/code_theme.dart';
 import 'package:git_touch/screens/gh_commits.dart';
@@ -298,6 +300,8 @@ class BitbucketRouter {
     BitbucketRouter.repo,
     BitbucketRouter.object,
     BitbucketRouter.commits,
+    BitbucketRouter.issues,
+    BitbucketRouter.pulls,
   ];
   static final user = RouterScreen(
       '/:login',
@@ -317,8 +321,16 @@ class BitbucketRouter {
       path: params['path']?.first,
     ),
   );
+  static final issues = RouterScreen(
+      '/:owner/:name/issues/:ref',
+      (_, p) =>
+          BbIssuesScreen(p['owner'].first, p['name'].first, p['ref'].first));
   static final commits = RouterScreen(
       '/:owner/:name/commits/:ref',
       (_, p) =>
           BbCommitsScreen(p['owner'].first, p['name'].first, p['ref'].first));
+  static final pulls = RouterScreen(
+      '/:owner/:name/pulls/:ref',
+      (_, p) =>
+          BbPullsScreen(p['owner'].first, p['name'].first, p['ref'].first));
 }
diff --git a/lib/screens/bb_issues.dart b/lib/screens/bb_issues.dart
new file mode 100644
index 0000000..286f3d4
--- /dev/null
+++ b/lib/screens/bb_issues.dart
@@ -0,0 +1,51 @@
+import 'package:flutter/material.dart';
+import 'package:git_touch/models/auth.dart';
+import 'package:git_touch/models/bitbucket.dart';
+import 'package:git_touch/scaffolds/list_stateful.dart';
+import 'package:git_touch/widgets/app_bar_title.dart';
+import 'package:git_touch/widgets/issue_item.dart';
+import 'package:provider/provider.dart';
+
+class BbIssuesScreen extends StatelessWidget {
+  final String owner;
+  final String name;
+  final String ref;
+  BbIssuesScreen(this.owner, this.name, this.ref);
+
+  Future<ListPayload<BbIssues, String>> _query(BuildContext context,
+      [String nextUrl]) async {
+    final auth = Provider.of<AuthModel>(context);
+    final res = await auth
+        .fetchBbWithPage(nextUrl ?? '/repositories/$owner/$name/issues');
+    return ListPayload(
+      cursor: res.cursor,
+      hasMore: res.hasMore,
+      items: <BbIssues>[
+        for (var v in res.data) BbIssues.fromJson(v),
+      ],
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final auth = Provider.of<AuthModel>(context);
+    return ListStatefulScaffold<BbIssues, String>(
+      title: AppBarTitle('Issues'),
+      onRefresh: () => _query(context),
+      onLoadMore: (page) => _query(context, page),
+      itemBuilder: (v) {
+        int issueNumber =
+            int.parse(v.issueLink.replaceFirst(RegExp(r'.*\/'), ''));
+        return IssueItem(
+          avatarUrl: v.reporter.avatarUrl,
+          author: v.reporter.displayName,
+          title: v.title,
+          number: issueNumber,
+          commentCount: 0,
+          updatedAt: v.createdOn,
+          url: '${auth.activeAccount.domain}/$owner/$name/issues/$issueNumber',
+        );
+      },
+    );
+  }
+}
diff --git a/lib/screens/bb_pulls.dart b/lib/screens/bb_pulls.dart
new file mode 100644
index 0000000..c2246dd
--- /dev/null
+++ b/lib/screens/bb_pulls.dart
@@ -0,0 +1,51 @@
+import 'package:flutter/material.dart';
+import 'package:git_touch/models/auth.dart';
+import 'package:git_touch/models/bitbucket.dart';
+import 'package:git_touch/scaffolds/list_stateful.dart';
+import 'package:git_touch/widgets/app_bar_title.dart';
+import 'package:git_touch/widgets/issue_item.dart';
+import 'package:provider/provider.dart';
+
+class BbPullsScreen extends StatelessWidget {
+  final String owner;
+  final String name;
+  final String ref;
+  BbPullsScreen(this.owner, this.name, this.ref);
+
+  Future<ListPayload<BbPulls, String>> _query(BuildContext context,
+      [String nextUrl]) async {
+    final auth = Provider.of<AuthModel>(context);
+    final res = await auth
+        .fetchBbWithPage(nextUrl ?? '/repositories/$owner/$name/pullrequests');
+    return ListPayload(
+      cursor: res.cursor,
+      hasMore: res.hasMore,
+      items: <BbPulls>[
+        for (var v in res.data) BbPulls.fromJson(v),
+      ],
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final auth = Provider.of<AuthModel>(context);
+    return ListStatefulScaffold<BbPulls, String>(
+      title: AppBarTitle('Pull requests'),
+      onRefresh: () => _query(context),
+      onLoadMore: (page) => _query(context, page),
+      itemBuilder: (v) {
+        int pullNumber =
+            int.parse(v.pullRequestLink.replaceFirst(RegExp(r'.*\/'), ''));
+        return IssueItem(
+          avatarUrl: v.author.avatarUrl,
+          author: v.author.displayName,
+          title: v.title,
+          number: pullNumber,
+          commentCount: 0,
+          updatedAt: v.createdOn,
+          url: '${auth.activeAccount.domain}/$owner/$name/issues/$pullNumber',
+        );
+      },
+    );
+  }
+}
diff --git a/lib/screens/bb_repo.dart b/lib/screens/bb_repo.dart
index 813d439..51003d3 100644
--- a/lib/screens/bb_repo.dart
+++ b/lib/screens/bb_repo.dart
@@ -57,6 +57,16 @@ class BbRepoScreen extends StatelessWidget {
                   rightWidget: Text(filesize(p.size)),
                   url: '/bitbucket/$owner/$name/src/${p.mainbranch.name}',
                 ),
+                TableViewItem(
+                  leftIconData: Octicons.issue_opened,
+                  text: Text('Issues'),
+                  url: '/bitbucket/$owner/$name/issues/${p.mainbranch.name}',
+                ),
+                TableViewItem(
+                  leftIconData: Octicons.git_pull_request,
+                  text: Text('Pull requests'),
+                  url: '/bitbucket/$owner/$name/pulls/${p.mainbranch.name}',
+                ),
                 TableViewItem(
                   leftIconData: Octicons.history,
                   text: Text('Commits'),