diff --git a/lib/home.dart b/lib/home.dart index 71b3547..40f235a 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -3,6 +3,7 @@ import 'package:flutter/cupertino.dart'; import 'package:git_touch/models/auth.dart'; import 'package:git_touch/models/notification.dart'; import 'package:git_touch/models/theme.dart'; +import 'package:git_touch/screens/bb_user.dart'; import 'package:git_touch/screens/gitea_orgs.dart'; import 'package:git_touch/screens/gitea_user.dart'; import 'package:git_touch/screens/gitlab_explore.dart'; @@ -60,6 +61,14 @@ class _HomeState extends State { return GitlabUserScreen(null); } break; + case PlatformType.bitbucket: + switch (index) { + case 0: + return BbUserScreen(null); + case 1: + return BbUserScreen(null); + } + break; case PlatformType.gitea: switch (index) { case 0: @@ -134,6 +143,17 @@ class _HomeState extends State { title: Text('Me'), ), ]; + case PlatformType.bitbucket: + return [ + BottomNavigationBarItem( + icon: Icon(Icons.explore), + title: Text('Explore'), + ), + BottomNavigationBarItem( + icon: Icon(Icons.person), + title: Text('Me'), + ), + ]; case PlatformType.gitea: return [ BottomNavigationBarItem( diff --git a/lib/models/auth.dart b/lib/models/auth.dart index ccaddd0..463386c 100644 --- a/lib/models/auth.dart +++ b/lib/models/auth.dart @@ -32,7 +32,12 @@ class DataWithPage { int cursor; bool hasMore; int total; - DataWithPage({this.data, this.cursor, this.hasMore, this.total}); + DataWithPage({ + @required this.data, + @required this.cursor, + @required this.hasMore, + this.total, + }); } class AuthModel with ChangeNotifier { @@ -242,6 +247,30 @@ class AuthModel with ChangeNotifier { } } + Future fetchBb(String p) async { + if (!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 info = json.decode(utf8.decode(res.bodyBytes)); + return info; + } + + Future> fetchBbWithPage(String p) async { + final res = await fetchBb(p); + final v = BbPagination.fromJson(res); + return DataWithPage( + cursor: v.page, + total: v.size, + data: v.values, + hasMore: v.next != null, + ); + } + Future init() async { // Listen scheme _sub = getUriLinksStream().listen(_onSchemeDetected, onError: (err) { diff --git a/lib/models/bitbucket.dart b/lib/models/bitbucket.dart index 2e436fc..0a151da 100644 --- a/lib/models/bitbucket.dart +++ b/lib/models/bitbucket.dart @@ -1,10 +1,24 @@ import 'package:json_annotation/json_annotation.dart'; part 'bitbucket.g.dart'; +@JsonSerializable(fieldRename: FieldRename.snake) +class BbPagination { + int pagelen; + int size; + int page; + String next; + List values; + BbPagination(); + factory BbPagination.fromJson(Map json) => + _$BbPaginationFromJson(json); +} + @JsonSerializable(fieldRename: FieldRename.snake) class BbUser { String username; + String nickname; String displayName; + String type; // user, team bool isStaff; DateTime createdOn; Map links; @@ -12,3 +26,22 @@ class BbUser { BbUser(); factory BbUser.fromJson(Map json) => _$BbUserFromJson(json); } + +@JsonSerializable(fieldRename: FieldRename.snake) +class BbRepo { + String name; + BbUser owner; + String website; + String language; + int size; + String type; // repository + bool isPrivate; + DateTime createdOn; + DateTime updatedOn; + String description; + String fullName; + Map links; + String get avatarUrl => links['avatar']['href']; + BbRepo(); + factory BbRepo.fromJson(Map json) => _$BbRepoFromJson(json); +} diff --git a/lib/models/bitbucket.g.dart b/lib/models/bitbucket.g.dart index f91b24e..874c0ee 100644 --- a/lib/models/bitbucket.g.dart +++ b/lib/models/bitbucket.g.dart @@ -6,10 +6,30 @@ part of 'bitbucket.dart'; // JsonSerializableGenerator // ************************************************************************** +BbPagination _$BbPaginationFromJson(Map json) { + return BbPagination() + ..pagelen = json['pagelen'] as int + ..size = json['size'] as int + ..page = json['page'] as int + ..next = json['next'] as String + ..values = json['values'] as List; +} + +Map _$BbPaginationToJson(BbPagination instance) => + { + 'pagelen': instance.pagelen, + 'size': instance.size, + 'page': instance.page, + 'next': instance.next, + 'values': instance.values, + }; + BbUser _$BbUserFromJson(Map json) { return BbUser() ..username = json['username'] as String + ..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 @@ -19,8 +39,47 @@ BbUser _$BbUserFromJson(Map json) { Map _$BbUserToJson(BbUser instance) => { 'username': instance.username, + 'nickname': instance.nickname, 'display_name': instance.displayName, + 'type': instance.type, 'is_staff': instance.isStaff, 'created_on': instance.createdOn?.toIso8601String(), 'links': instance.links, }; + +BbRepo _$BbRepoFromJson(Map json) { + return BbRepo() + ..name = json['name'] as String + ..owner = json['owner'] == null + ? null + : BbUser.fromJson(json['owner'] as Map) + ..website = json['website'] as String + ..language = json['language'] as String + ..size = json['size'] as int + ..type = json['type'] as String + ..isPrivate = json['is_private'] as bool + ..createdOn = json['created_on'] == null + ? null + : DateTime.parse(json['created_on'] as String) + ..updatedOn = json['updated_on'] == null + ? null + : DateTime.parse(json['updated_on'] as String) + ..description = json['description'] as String + ..fullName = json['full_name'] as String + ..links = json['links'] as Map; +} + +Map _$BbRepoToJson(BbRepo instance) => { + 'name': instance.name, + 'owner': instance.owner, + 'website': instance.website, + 'language': instance.language, + 'size': instance.size, + 'type': instance.type, + 'is_private': instance.isPrivate, + 'created_on': instance.createdOn?.toIso8601String(), + 'updated_on': instance.updatedOn?.toIso8601String(), + 'description': instance.description, + 'full_name': instance.fullName, + 'links': instance.links, + }; diff --git a/lib/screens/bb_user.dart b/lib/screens/bb_user.dart new file mode 100644 index 0000000..90a44f4 --- /dev/null +++ b/lib/screens/bb_user.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.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/widgets/action_entry.dart'; +import 'package:git_touch/widgets/repository_item.dart'; +import 'package:git_touch/widgets/user_header.dart'; +import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; +import 'package:git_touch/utils/utils.dart'; +import 'package:timeago/timeago.dart' as timeago; + +class BbUserScreen extends StatelessWidget { + final String login; + BbUserScreen(this.login); + bool get isViewer => login == null; + + @override + Widget build(BuildContext context) { + return RefreshStatefulScaffold>>( + title: Text(isViewer ? 'Me' : 'User'), + fetchData: () async { + final auth = Provider.of(context); + final _login = login ?? auth.activeAccount.login; + final res = await Future.wait([ + auth.fetchBb('/users/$_login'), + auth.fetchBbWithPage('/repositories/$_login'), + ]); + return Tuple2( + BbUser.fromJson(res[0]), + [ + for (var v in (res[1] as DataWithPage).data) + BbRepo.fromJson(v) + ], + ); + }, + action: isViewer + ? ActionEntry( + iconData: Icons.settings, + url: '/settings', + ) + : null, + bodyBuilder: (data, _) { + final user = data.item1; + final repos = data.item2; + return Column( + children: [ + UserHeader( + login: user.displayName, + avatarUrl: user.avatarUrl, + name: user.nickname, + createdAt: user.createdOn, + bio: null, + ), + CommonStyle.border, + Column( + children: [ + for (var v in repos) + RepositoryItem( + owner: v.owner.displayName, + name: v.name, + url: '/bitbucket/${v.fullName}', + avatarUrl: v.avatarUrl, + avatarLink: '/bitbucket/${v.owner.displayName}', + note: 'Updated ${timeago.format(v.updatedOn)}', + description: v.description, + forkCount: 0, + starCount: 0, + ) + ], + ) + ], + ); + }, + ); + } +}