diff --git a/lib/main.dart b/lib/main.dart index 79d259b..b16253c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -53,6 +53,10 @@ void main() async { themeModel.router.define(GiteaRouter.prefix + screen.path, handler: Handler(handlerFunc: screen.handler)); }); + BitbucketRouter.routes.forEach((screen) { + themeModel.router.define(BitbucketRouter.prefix + screen.path, + handler: Handler(handlerFunc: screen.handler)); + }); GithubRouter.routes.forEach((screen) { themeModel.router.define(GithubRouter.prefix + screen.path, handler: Handler(handlerFunc: screen.handler)); diff --git a/lib/models/auth.dart b/lib/models/auth.dart index 4bb2ade..d1b86f7 100644 --- a/lib/models/auth.dart +++ b/lib/models/auth.dart @@ -284,6 +284,19 @@ class AuthModel with ChangeNotifier { ); } + Future fetchBbReadme(String p) async { + if (p.startsWith('/') && !p.startsWith('/api')) p = '/api/2.0$p'; + final input = Uri.parse(p); + final uri = Uri.parse(activeAccount.domain).replace( + userInfo: '${activeAccount.login}:${activeAccount.appPassword}', + path: input.path, + query: input.query, + ); + final res = await http.get(uri); + if (res.statusCode >= 400) return null; + return res.body; + } + Future init() async { // Listen scheme _sub = getUriLinksStream().listen(_onSchemeDetected, onError: (err) { diff --git a/lib/models/bitbucket.dart b/lib/models/bitbucket.dart index 0a151da..1379799 100644 --- a/lib/models/bitbucket.dart +++ b/lib/models/bitbucket.dart @@ -14,15 +14,22 @@ class BbPagination { } @JsonSerializable(fieldRename: FieldRename.snake) -class BbUser { - String username; +class BbRepoOwner { String nickname; String displayName; String type; // user, team - bool isStaff; - DateTime createdOn; Map links; String get avatarUrl => links['avatar']['href']; + BbRepoOwner(); + factory BbRepoOwner.fromJson(Map json) => + _$BbRepoOwnerFromJson(json); +} + +@JsonSerializable(fieldRename: FieldRename.snake) +class BbUser extends BbRepoOwner { + String username; + bool isStaff; + DateTime createdOn; BbUser(); factory BbUser.fromJson(Map json) => _$BbUserFromJson(json); } @@ -30,7 +37,7 @@ class BbUser { @JsonSerializable(fieldRename: FieldRename.snake) class BbRepo { String name; - BbUser owner; + BbRepoOwner owner; String website; String language; int size; @@ -40,8 +47,19 @@ class BbRepo { DateTime updatedOn; String description; String fullName; + String slug; + BbRepoMainbranch mainbranch; Map links; String get avatarUrl => links['avatar']['href']; BbRepo(); factory BbRepo.fromJson(Map json) => _$BbRepoFromJson(json); } + +@JsonSerializable(fieldRename: FieldRename.snake) +class BbRepoMainbranch { + String type; + String name; + BbRepoMainbranch(); + factory BbRepoMainbranch.fromJson(Map json) => + _$BbRepoMainbranchFromJson(json); +} diff --git a/lib/models/bitbucket.g.dart b/lib/models/bitbucket.g.dart index 874c0ee..78538cb 100644 --- a/lib/models/bitbucket.g.dart +++ b/lib/models/bitbucket.g.dart @@ -24,27 +24,43 @@ Map _$BbPaginationToJson(BbPagination instance) => 'values': instance.values, }; -BbUser _$BbUserFromJson(Map json) { - return BbUser() - ..username = json['username'] as String +BbRepoOwner _$BbRepoOwnerFromJson(Map json) { + return BbRepoOwner() ..nickname = json['nickname'] as String ..displayName = json['display_name'] as String ..type = json['type'] as String - ..isStaff = json['is_staff'] as bool - ..createdOn = json['created_on'] == null - ? null - : DateTime.parse(json['created_on'] as String) ..links = json['links'] as Map; } -Map _$BbUserToJson(BbUser instance) => { - 'username': instance.username, +Map _$BbRepoOwnerToJson(BbRepoOwner instance) => + { 'nickname': instance.nickname, 'display_name': instance.displayName, 'type': instance.type, + 'links': instance.links, + }; + +BbUser _$BbUserFromJson(Map json) { + return BbUser() + ..nickname = json['nickname'] as String + ..displayName = json['display_name'] as String + ..type = json['type'] as String + ..links = json['links'] as Map + ..username = json['username'] as String + ..isStaff = json['is_staff'] as bool + ..createdOn = json['created_on'] == null + ? null + : DateTime.parse(json['created_on'] as String); +} + +Map _$BbUserToJson(BbUser instance) => { + 'nickname': instance.nickname, + 'display_name': instance.displayName, + 'type': instance.type, + 'links': instance.links, + 'username': instance.username, 'is_staff': instance.isStaff, 'created_on': instance.createdOn?.toIso8601String(), - 'links': instance.links, }; BbRepo _$BbRepoFromJson(Map json) { @@ -52,7 +68,7 @@ BbRepo _$BbRepoFromJson(Map json) { ..name = json['name'] as String ..owner = json['owner'] == null ? null - : BbUser.fromJson(json['owner'] as Map) + : BbRepoOwner.fromJson(json['owner'] as Map) ..website = json['website'] as String ..language = json['language'] as String ..size = json['size'] as int @@ -66,6 +82,10 @@ BbRepo _$BbRepoFromJson(Map json) { : DateTime.parse(json['updated_on'] as String) ..description = json['description'] as String ..fullName = json['full_name'] as String + ..slug = json['slug'] as String + ..mainbranch = json['mainbranch'] == null + ? null + : BbRepoMainbranch.fromJson(json['mainbranch'] as Map) ..links = json['links'] as Map; } @@ -81,5 +101,19 @@ Map _$BbRepoToJson(BbRepo instance) => { 'updated_on': instance.updatedOn?.toIso8601String(), 'description': instance.description, 'full_name': instance.fullName, + 'slug': instance.slug, + 'mainbranch': instance.mainbranch, 'links': instance.links, }; + +BbRepoMainbranch _$BbRepoMainbranchFromJson(Map json) { + return BbRepoMainbranch() + ..type = json['type'] as String + ..name = json['name'] as String; +} + +Map _$BbRepoMainbranchToJson(BbRepoMainbranch instance) => + { + 'type': instance.type, + 'name': instance.name, + }; diff --git a/lib/router.dart b/lib/router.dart index 61b789e..a50f802 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -1,4 +1,6 @@ import 'package:fluro/fluro.dart'; +import 'package:git_touch/screens/bb_repo.dart'; +import 'package:git_touch/screens/bb_user.dart'; import 'package:git_touch/screens/code_theme.dart'; import 'package:git_touch/screens/commits.dart'; import 'package:git_touch/screens/gitea_commits.dart'; @@ -232,3 +234,22 @@ class GiteaRouter { (_, p) => GiteaIssuesScreen(p['owner'].first, p['name'].first, isPr: true)); } + +class BitbucketRouter { + static const prefix = '/bitbucket'; + static final routes = [ + BitbucketRouter.user, + BitbucketRouter.repo, + ]; + static final user = RouterScreen( + '/:login', + (context, params) => params['team'].first == '1' + ? BbUserScreen(params['login'].first) + : BbUserScreen(params['login'].first), + ); + static final repo = RouterScreen( + '/:owner/:name', + (context, params) => + BbRepoScreen(params['owner'].first, params['name'].first), + ); +} diff --git a/lib/screens/bb_repo.dart b/lib/screens/bb_repo.dart new file mode 100644 index 0000000..c52ba50 --- /dev/null +++ b/lib/screens/bb_repo.dart @@ -0,0 +1,76 @@ +import 'package:filesize/filesize.dart'; +import 'package:flutter/material.dart'; +import 'package:git_touch/models/auth.dart'; +import 'package:git_touch/models/bitbucket.dart'; +import 'package:git_touch/models/theme.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/markdown_view.dart'; +import 'package:git_touch/widgets/repo_header.dart'; +import 'package:git_touch/widgets/table_view.dart'; +import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; + +class BbRepoScreen extends StatelessWidget { + final String owner; + final String name; + BbRepoScreen(this.owner, this.name); + + @override + Widget build(BuildContext context) { + return RefreshStatefulScaffold>( + title: AppBarTitle('Repository'), + fetchData: () async { + final auth = Provider.of(context); + final res = await auth.fetchBb('/repositories/$owner/$name'); + final repo = BbRepo.fromJson(res); + final readme = await auth.fetchBbReadme( + '/repositories/$owner/$name/src/${repo.mainbranch.name}/README.md'); + return Tuple2(repo, readme); + }, + bodyBuilder: (t, setState) { + final theme = Provider.of(context); + final p = t.item1; + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RepoHeader( + avatarUrl: p.avatarUrl, + avatarLink: null, + owner: p.owner.displayName, // TODO: + name: p.slug, + description: p.description, + homepageUrl: p.website, + ), + CommonStyle.border, + TableView( + hasIcon: true, + items: [ + TableViewItem( + leftIconData: Octicons.code, + text: Text('Code'), + rightWidget: Text(filesize(p.size)), + url: '/bitbucket/$owner/$name/src/${p.mainbranch.name}', + ), + TableViewItem( + leftIconData: Octicons.history, + text: Text('Commits'), + url: '/bitbucket/$owner/$name/commits', + ), + ], + ), + CommonStyle.verticalGap, + if (t.item2 != null) + Container( + padding: CommonStyle.padding, + color: theme.palette.background, + child: MarkdownView(t.item2), + ), + CommonStyle.verticalGap, + ], + ); + }, + ); + } +}