From a9f9b957936978e702e8ded8f5ad85197c5f577b Mon Sep 17 00:00:00 2001 From: Rongjian Zhang Date: Sun, 2 Feb 2020 19:50:00 +0800 Subject: [PATCH] feat(bb): object screen --- lib/models/auth.dart | 24 +++++------ lib/models/bitbucket.dart | 9 ++++ lib/models/bitbucket.g.dart | 13 ++++++ lib/router.dart | 11 +++++ lib/screens/bb_object.dart | 70 ++++++++++++++++++++++++++++++++ lib/screens/bb_repo.dart | 7 ++-- lib/screens/bb_user.dart | 2 +- lib/widgets/blob_view.dart | 12 +++--- lib/widgets/object_tree.dart | 2 + lib/widgets/repository_item.dart | 2 +- 10 files changed, 128 insertions(+), 24 deletions(-) create mode 100644 lib/screens/bb_object.dart diff --git a/lib/models/auth.dart b/lib/models/auth.dart index d1b86f7..182c7e7 100644 --- a/lib/models/auth.dart +++ b/lib/models/auth.dart @@ -260,7 +260,7 @@ class AuthModel with ChangeNotifier { } } - Future fetchBb(String p) async { + Future fetchBb(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( @@ -268,14 +268,17 @@ class AuthModel with ChangeNotifier { path: input.path, query: input.query, ); - final res = await http.get(uri); - final info = json.decode(utf8.decode(res.bodyBytes)); - return info; + return http.get(uri); + } + + Future fetchBbJson(String p) async { + final res = await fetchBb(p); + return json.decode(utf8.decode(res.bodyBytes)); } Future> fetchBbWithPage(String p) async { - final res = await fetchBb(p); - final v = BbPagination.fromJson(res); + final data = await fetchBbJson(p); + final v = BbPagination.fromJson(data); return BbPagePayload( cursor: v.next, total: v.size, @@ -285,14 +288,7 @@ 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); + final res = await fetchBb(p); if (res.statusCode >= 400) return null; return res.body; } diff --git a/lib/models/bitbucket.dart b/lib/models/bitbucket.dart index 2811804..06d887d 100644 --- a/lib/models/bitbucket.dart +++ b/lib/models/bitbucket.dart @@ -64,3 +64,12 @@ class BbRepoMainbranch { factory BbRepoMainbranch.fromJson(Map json) => _$BbRepoMainbranchFromJson(json); } + +@JsonSerializable(fieldRename: FieldRename.snake) +class BbTree { + String type; + String path; + int size; + BbTree(); + factory BbTree.fromJson(Map json) => _$BbTreeFromJson(json); +} diff --git a/lib/models/bitbucket.g.dart b/lib/models/bitbucket.g.dart index 78538cb..ee6d662 100644 --- a/lib/models/bitbucket.g.dart +++ b/lib/models/bitbucket.g.dart @@ -117,3 +117,16 @@ Map _$BbRepoMainbranchToJson(BbRepoMainbranch instance) => 'type': instance.type, 'name': instance.name, }; + +BbTree _$BbTreeFromJson(Map json) { + return BbTree() + ..type = json['type'] as String + ..path = json['path'] as String + ..size = json['size'] as int; +} + +Map _$BbTreeToJson(BbTree instance) => { + 'type': instance.type, + 'path': instance.path, + 'size': instance.size, + }; diff --git a/lib/router.dart b/lib/router.dart index a50f802..a23197d 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -1,4 +1,5 @@ import 'package:fluro/fluro.dart'; +import 'package:git_touch/screens/bb_object.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'; @@ -240,6 +241,7 @@ class BitbucketRouter { static final routes = [ BitbucketRouter.user, BitbucketRouter.repo, + BitbucketRouter.object, ]; static final user = RouterScreen( '/:login', @@ -252,4 +254,13 @@ class BitbucketRouter { (context, params) => BbRepoScreen(params['owner'].first, params['name'].first), ); + static final object = RouterScreen( + '/:owner/:name/src/:ref', + (context, params) => BbObjectScreen( + params['owner'].first, + params['name'].first, + params['ref'].first, + path: params['path']?.first, + ), + ); } diff --git a/lib/screens/bb_object.dart b/lib/screens/bb_object.dart new file mode 100644 index 0000000..24bcd6a --- /dev/null +++ b/lib/screens/bb_object.dart @@ -0,0 +1,70 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:git_touch/models/auth.dart'; +import 'package:git_touch/models/bitbucket.dart'; +import 'package:git_touch/scaffolds/refresh_stateful.dart'; +import 'package:git_touch/utils/utils.dart'; +import 'package:git_touch/widgets/action_entry.dart'; +import 'package:git_touch/widgets/app_bar_title.dart'; +import 'package:git_touch/widgets/blob_view.dart'; +import 'package:git_touch/widgets/object_tree.dart'; +import 'package:provider/provider.dart'; +import 'package:path/path.dart' as p; + +class BbObjectScreen extends StatelessWidget { + final String owner; + final String name; + final String ref; + final String path; + BbObjectScreen(this.owner, this.name, this.ref, {this.path}); + + @override + Widget build(BuildContext context) { + return RefreshStatefulScaffold( + title: AppBarTitle(path ?? 'Files'), + fetchData: () async { + final res = await Provider.of(context) + .fetchBb('/repositories/$owner/$name/src/$ref/${path ?? ''}'); + if (res.headers[HttpHeaders.contentTypeHeader] == 'text/plain') { + return res.body; + } else { + return BbPagination.fromJson(json.decode(res.body)).values; + } + }, + actionBuilder: (p, _) { + if (p is String) { + return ActionEntry( + iconData: Icons.settings, + url: '/choose-code-theme', + ); + } else { + return null; + } + }, + bodyBuilder: (pl, _) { + if (pl is String) { + return BlobView(path, text: pl); + } else if (pl is List) { + final items = pl.map((t) => BbTree.fromJson(t)).toList(); + items.sort((a, b) { + return sortByKey('dir', a.type, b.type); + }); + return ObjectTree(items: [ + for (var v in items) + ObjectTreeItem( + name: p.basename(v.path), + type: v.type, + size: v.type == 'commit_file' ? v.size : null, + url: + '/bitbucket/$owner/$name/src/$ref?path=${v.path.urlencode}', + downloadUrl: null, + ), + ]); + } else { + return null; + } + }, + ); + } +} diff --git a/lib/screens/bb_repo.dart b/lib/screens/bb_repo.dart index e92789f..0b72938 100644 --- a/lib/screens/bb_repo.dart +++ b/lib/screens/bb_repo.dart @@ -23,10 +23,11 @@ class BbRepoScreen extends StatelessWidget { 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( + final r = await auth.fetchBbJson('/repositories/$owner/$name'); + final repo = BbRepo.fromJson(r); + final res = await auth.fetchBb( '/repositories/$owner/$name/src/${repo.mainbranch.name}/README.md'); + final readme = res.statusCode >= 400 ? null : res.body; return Tuple2(repo, readme); }, bodyBuilder: (t, setState) { diff --git a/lib/screens/bb_user.dart b/lib/screens/bb_user.dart index 70e0404..ee61ef4 100644 --- a/lib/screens/bb_user.dart +++ b/lib/screens/bb_user.dart @@ -23,7 +23,7 @@ class BbUserScreen extends StatelessWidget { title: Text(isViewer ? 'Me' : 'User'), fetchData: () async { final res = await Future.wait([ - auth.fetchBb('/users/$_login'), + auth.fetchBbJson('/users/$_login'), auth.fetchBbWithPage('/repositories/$_login'), ]); return Tuple2( diff --git a/lib/widgets/blob_view.dart b/lib/widgets/blob_view.dart index d4a4e1e..6c94501 100644 --- a/lib/widgets/blob_view.dart +++ b/lib/widgets/blob_view.dart @@ -13,16 +13,18 @@ import 'package:git_touch/utils/utils.dart'; class BlobView extends StatelessWidget { final String name; - // final String text; + final String text; final String base64Text; final String networkUrl; BlobView( this.name, { - // this.text, - @required this.base64Text, + this.text, + this.base64Text, this.networkUrl, }); + String get _text => text ?? base64Text.base64ToUtf8; + @override Widget build(BuildContext context) { final codeProvider = Provider.of(context); @@ -52,13 +54,13 @@ class BlobView extends StatelessWidget { case 'markdown': return Padding( padding: CommonStyle.padding, - child: MarkdownView(base64Text.base64ToUtf8), // TODO: basePath + child: MarkdownView(_text), // TODO: basePath ); default: return SingleChildScrollView( scrollDirection: Axis.horizontal, child: HighlightView( - base64Text.base64ToUtf8, + _text, language: name.ext ?? 'plaintext', theme: themeMap[theme.brightness == Brightness.dark ? codeProvider.themeDark diff --git a/lib/widgets/object_tree.dart b/lib/widgets/object_tree.dart index 47519a6..032be05 100644 --- a/lib/widgets/object_tree.dart +++ b/lib/widgets/object_tree.dart @@ -28,9 +28,11 @@ class ObjectTree extends StatelessWidget { switch (item.type) { case 'blob': // github gql, gitlab case 'file': // github rest, gitea + case 'commit_file': // bitbucket return SetiIcon(item.name, size: 36); case 'tree': // github gql, gitlab case 'dir': // github rest, gitea + case 'commit_directory': // bitbucket return Icon( Octicons.file_directory, color: PrimerColors.blue300, diff --git a/lib/widgets/repository_item.dart b/lib/widgets/repository_item.dart index e9bfe9a..6b221ee 100644 --- a/lib/widgets/repository_item.dart +++ b/lib/widgets/repository_item.dart @@ -46,7 +46,7 @@ class RepositoryItem extends StatelessWidget { name = payload.name, url = '/bitbucket/${payload.fullName}', avatarUrl = payload.avatarUrl, - avatarLink = '/bitbucket/${payload.owner.displayName}', + avatarLink = null, note = 'Updated ${timeago.format(payload.updatedOn)}', description = payload.description, forkCount = 0,