From 242dd1200de034b4bf2bfbf30ea94c1a3dcd6153 Mon Sep 17 00:00:00 2001 From: Rongjian Zhang Date: Mon, 14 Jun 2021 11:28:12 +0800 Subject: [PATCH] fix: bitbucket and gitlab files length closes #224 --- lib/models/auth.dart | 11 ++- lib/models/bitbucket.dart | 6 +- lib/models/bitbucket.g.dart | 7 +- lib/models/gitea.dart | 9 +- lib/models/gitea.g.dart | 14 +-- lib/models/gitee.dart | 6 +- lib/models/gitee.g.dart | 7 +- lib/models/gitlab.dart | 8 +- lib/models/gitlab.g.dart | 9 +- lib/models/gogs.dart | 9 +- lib/models/gogs.g.dart | 14 +-- lib/scaffolds/list_stateful.dart | 2 +- lib/screens/bb_object.dart | 78 ++++++++-------- lib/screens/bb_repo.dart | 1 - lib/screens/code_theme.dart | 1 + lib/screens/ge_repo.dart | 1 - lib/screens/ge_tree.dart | 7 +- lib/screens/gh_events.dart | 4 +- lib/screens/gh_gists_files.dart | 5 +- lib/screens/gh_news.dart | 4 +- lib/screens/gh_object.dart | 7 +- lib/screens/gh_repo.dart | 1 - lib/screens/gh_search.dart | 6 +- lib/screens/gh_user.dart | 2 - lib/screens/gl_project.dart | 1 - lib/screens/gl_tree.dart | 52 +++++------ lib/screens/go_object.dart | 3 +- lib/screens/go_repo.dart | 1 - lib/screens/go_user.dart | 1 - lib/screens/gt_object.dart | 3 +- lib/screens/gt_repo.dart | 1 - lib/screens/gt_user.dart | 1 - lib/screens/settings.dart | 147 ++++++++++++++++--------------- lib/utils/utils.dart | 6 +- lib/widgets/object_tree.dart | 72 +++++++-------- lib/widgets/release_item.dart | 39 ++++---- lib/widgets/table_view.dart | 118 ++++++++++++------------- 37 files changed, 337 insertions(+), 327 deletions(-) diff --git a/lib/models/auth.dart b/lib/models/auth.dart index 5e5d1ef..54dc676 100644 --- a/lib/models/auth.dart +++ b/lib/models/auth.dart @@ -289,7 +289,7 @@ class AuthModel with ChangeNotifier { Future fetchGiteaWithPage(String path, {int? page, int? limit}) async { page = page ?? 1; - limit = limit ?? pageSize; + limit = limit ?? PAGE_SIZE; var uri = Uri.parse('${activeAccount!.domain}/api/v1$path'); uri = uri.replace( @@ -393,7 +393,7 @@ class AuthModel with ChangeNotifier { Future fetchGogsWithPage(String path, {int? page, int? limit}) async { page = page ?? 1; - limit = limit ?? pageSize; + limit = limit ?? PAGE_SIZE; var uri = Uri.parse('${activeAccount!.domain}/api/v1$path'); uri = uri.replace( @@ -480,7 +480,7 @@ class AuthModel with ChangeNotifier { Future fetchGiteeWithPage(String path, {int? page, int? limit}) async { page = page ?? 1; - limit = limit ?? pageSize; + limit = limit ?? PAGE_SIZE; var uri = Uri.parse('${activeAccount!.domain}/api/v5$path'); uri = uri.replace( @@ -545,7 +545,10 @@ class AuthModel with ChangeNotifier { final uri = Uri.parse(activeAccount!.domain).replace( userInfo: '${activeAccount!.login}:${activeAccount!.appPassword}', path: input.path, - query: input.query, + queryParameters: { + 'pagelen': PAGE_SIZE.toString(), + ...input.queryParameters + }, ); if (isPost) { return http.post( diff --git a/lib/models/bitbucket.dart b/lib/models/bitbucket.dart index 740293b..093e448 100644 --- a/lib/models/bitbucket.dart +++ b/lib/models/bitbucket.dart @@ -65,11 +65,11 @@ class BbRepoMainbranch { @JsonSerializable(fieldRename: FieldRename.snake) class BbTree { - String? type; - String? path; + String type; + String path; int? size; Map? links; - BbTree(); + BbTree({required this.type, required this.path}); factory BbTree.fromJson(Map json) => _$BbTreeFromJson(json); } diff --git a/lib/models/bitbucket.g.dart b/lib/models/bitbucket.g.dart index cf010f8..a42619c 100644 --- a/lib/models/bitbucket.g.dart +++ b/lib/models/bitbucket.g.dart @@ -115,9 +115,10 @@ Map _$BbRepoMainbranchToJson(BbRepoMainbranch instance) => }; BbTree _$BbTreeFromJson(Map json) { - return BbTree() - ..type = json['type'] as String? - ..path = json['path'] as String? + return BbTree( + type: json['type'] as String, + path: json['path'] as String, + ) ..size = json['size'] as int? ..links = json['links'] as Map?; } diff --git a/lib/models/gitea.dart b/lib/models/gitea.dart index 2e3dcf5..331a3e7 100644 --- a/lib/models/gitea.dart +++ b/lib/models/gitea.dart @@ -48,12 +48,12 @@ class GiteaRepository { @JsonSerializable(fieldRename: FieldRename.snake) class GiteaTree { - String? type; - String? name; + String type; + String name; String? path; int? size; String? downloadUrl; - GiteaTree(); + GiteaTree({required this.type, required this.name}); factory GiteaTree.fromJson(Map json) => _$GiteaTreeFromJson(json); } @@ -61,7 +61,8 @@ class GiteaTree { @JsonSerializable(fieldRename: FieldRename.snake) class GiteaBlob extends GiteaTree { String? content; - GiteaBlob(); + GiteaBlob({required String type, required String name}) + : super(name: name, type: type); factory GiteaBlob.fromJson(Map json) => _$GiteaBlobFromJson(json); } diff --git a/lib/models/gitea.g.dart b/lib/models/gitea.g.dart index d760a04..f76934a 100644 --- a/lib/models/gitea.g.dart +++ b/lib/models/gitea.g.dart @@ -81,9 +81,10 @@ Map _$GiteaRepositoryToJson(GiteaRepository instance) => }; GiteaTree _$GiteaTreeFromJson(Map json) { - return GiteaTree() - ..type = json['type'] as String? - ..name = json['name'] as String? + return GiteaTree( + type: json['type'] as String, + name: json['name'] as String, + ) ..path = json['path'] as String? ..size = json['size'] as int? ..downloadUrl = json['download_url'] as String?; @@ -98,9 +99,10 @@ Map _$GiteaTreeToJson(GiteaTree instance) => { }; GiteaBlob _$GiteaBlobFromJson(Map json) { - return GiteaBlob() - ..type = json['type'] as String? - ..name = json['name'] as String? + return GiteaBlob( + type: json['type'] as String, + name: json['name'] as String, + ) ..path = json['path'] as String? ..size = json['size'] as int? ..downloadUrl = json['download_url'] as String? diff --git a/lib/models/gitee.dart b/lib/models/gitee.dart index dfcc61e..2b7bd8e 100644 --- a/lib/models/gitee.dart +++ b/lib/models/gitee.dart @@ -107,11 +107,11 @@ class GiteeCommitAuthor { @JsonSerializable(fieldRename: FieldRename.snake) class GiteeTreeItem { - String? path; - String? type; + String path; + String type; String? sha; int? size; - GiteeTreeItem(); + GiteeTreeItem({required this.path, required this.type}); factory GiteeTreeItem.fromJson(Map json) => _$GiteeTreeItemFromJson(json); } diff --git a/lib/models/gitee.g.dart b/lib/models/gitee.g.dart index 3af380f..1839e26 100644 --- a/lib/models/gitee.g.dart +++ b/lib/models/gitee.g.dart @@ -181,9 +181,10 @@ Map _$GiteeCommitAuthorToJson(GiteeCommitAuthor instance) => }; GiteeTreeItem _$GiteeTreeItemFromJson(Map json) { - return GiteeTreeItem() - ..path = json['path'] as String? - ..type = json['type'] as String? + return GiteeTreeItem( + path: json['path'] as String, + type: json['type'] as String, + ) ..sha = json['sha'] as String? ..size = json['size'] as int?; } diff --git a/lib/models/gitlab.dart b/lib/models/gitlab.dart index 57a628a..a42428c 100644 --- a/lib/models/gitlab.dart +++ b/lib/models/gitlab.dart @@ -128,10 +128,10 @@ class GitlabProjectNamespace { @JsonSerializable(fieldRename: FieldRename.snake) class GitlabTreeItem { - String? type; - String? path; - String? name; - GitlabTreeItem(); + String type; + String path; + String name; + GitlabTreeItem({required this.type, required this.path, required this.name}); factory GitlabTreeItem.fromJson(Map json) => _$GitlabTreeItemFromJson(json); } diff --git a/lib/models/gitlab.g.dart b/lib/models/gitlab.g.dart index a7baba1..edd2094 100644 --- a/lib/models/gitlab.g.dart +++ b/lib/models/gitlab.g.dart @@ -229,10 +229,11 @@ Map _$GitlabProjectNamespaceToJson( }; GitlabTreeItem _$GitlabTreeItemFromJson(Map json) { - return GitlabTreeItem() - ..type = json['type'] as String? - ..path = json['path'] as String? - ..name = json['name'] as String?; + return GitlabTreeItem( + type: json['type'] as String, + path: json['path'] as String, + name: json['name'] as String, + ); } Map _$GitlabTreeItemToJson(GitlabTreeItem instance) => diff --git a/lib/models/gogs.dart b/lib/models/gogs.dart index 9d554b4..ec0877e 100644 --- a/lib/models/gogs.dart +++ b/lib/models/gogs.dart @@ -50,12 +50,12 @@ class GogsOrg { @JsonSerializable(fieldRename: FieldRename.snake) class GogsTree { - String? type; - String? name; + String type; + String name; String? path; int? size; String? downloadUrl; - GogsTree(); + GogsTree({required this.type, required this.name}); factory GogsTree.fromJson(Map json) => _$GogsTreeFromJson(json); } @@ -63,7 +63,8 @@ class GogsTree { @JsonSerializable(fieldRename: FieldRename.snake) class GogsBlob extends GogsTree { String? content; - GogsBlob(); + GogsBlob({required String type, required String name}) + : super(name: name, type: type); factory GogsBlob.fromJson(Map json) => _$GogsBlobFromJson(json); } diff --git a/lib/models/gogs.g.dart b/lib/models/gogs.g.dart index cc3f4a8..035d788 100644 --- a/lib/models/gogs.g.dart +++ b/lib/models/gogs.g.dart @@ -85,9 +85,10 @@ Map _$GogsOrgToJson(GogsOrg instance) => { }; GogsTree _$GogsTreeFromJson(Map json) { - return GogsTree() - ..type = json['type'] as String? - ..name = json['name'] as String? + return GogsTree( + type: json['type'] as String, + name: json['name'] as String, + ) ..path = json['path'] as String? ..size = json['size'] as int? ..downloadUrl = json['download_url'] as String?; @@ -102,9 +103,10 @@ Map _$GogsTreeToJson(GogsTree instance) => { }; GogsBlob _$GogsBlobFromJson(Map json) { - return GogsBlob() - ..type = json['type'] as String? - ..name = json['name'] as String? + return GogsBlob( + type: json['type'] as String, + name: json['name'] as String, + ) ..path = json['path'] as String? ..size = json['size'] as int? ..downloadUrl = json['download_url'] as String? diff --git a/lib/scaffolds/list_stateful.dart b/lib/scaffolds/list_stateful.dart index ab064af..19178f4 100644 --- a/lib/scaffolds/list_stateful.dart +++ b/lib/scaffolds/list_stateful.dart @@ -19,8 +19,8 @@ class ListStatefulScaffold extends StatefulWidget { ListStatefulScaffold({ required this.title, - required this.itemBuilder, required this.fetch, + required this.itemBuilder, this.actionBuilder, }); diff --git a/lib/screens/bb_object.dart b/lib/screens/bb_object.dart index cea9d5f..4bf6c38 100644 --- a/lib/screens/bb_object.dart +++ b/lib/screens/bb_object.dart @@ -1,9 +1,9 @@ import 'dart:convert'; +import 'package:git_touch/scaffolds/list_stateful.dart'; import 'package:universal_io/io.dart'; 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'; @@ -22,51 +22,55 @@ class BbObjectScreen extends StatelessWidget { @override Widget build(BuildContext context) { final auth = Provider.of(context); - return RefreshStatefulScaffold( + + return ListStatefulScaffold( title: AppBarTitle(path ?? 'Files'), - fetch: () async { - final res = await auth - .fetchBb('/repositories/$owner/$name/src/$ref/${path ?? ''}'); + fetch: (next) async { + final res = await auth.fetchBb( + next ?? '/repositories/$owner/$name/src/$ref/${path ?? ''}'); if (res.headers[HttpHeaders.contentTypeHeader] == 'text/plain') { - return utf8.decode(res.bodyBytes); - } else { - return BbPagination.fromJson(json.decode(utf8.decode(res.bodyBytes))) - .values; - } - }, - actionBuilder: (dynamic p, _) { - if (p is String) { - return ActionEntry( - iconData: Ionicons.cog, - url: '/choose-code-theme', + return ListPayload( + cursor: '', + hasMore: false, + items: [utf8.decode(res.bodyBytes)], ); } else { - return null; - } - }, - bodyBuilder: (dynamic pl, _) { - if (pl is String) { - return BlobView(path, text: pl); - } else if (pl is List) { - final items = pl.map((t) => BbTree.fromJson(t)).toList(); + final v = + BbPagination.fromJson(json.decode(utf8.decode(res.bodyBytes))); + final items = [for (var t in v.values) BbTree.fromJson(t)]; 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: v.links!['self']['href'] as String?, - ), - ]); - } else { - return null; + + return ListPayload( + cursor: v.next, + hasMore: v.next != null, + items: items, + ); } }, + itemBuilder: (pl) { + if (pl is String) { + return BlobView(path, text: pl); + } else if (pl is BbTree) { + return ObjectTreeItem( + name: p.basename(pl.path), + type: pl.type, + // size: v.type == 'commit_file' ? v.size : null, + size: pl.size, + url: '/bitbucket/$owner/$name/src/$ref?path=${pl.path.urlencode}', + downloadUrl: pl.links!['self']['href'] as String?, + ); + } else { + return Container(); + } + }, + actionBuilder: () { + return ActionEntry( + iconData: Ionicons.cog, + url: '/choose-code-theme', + ); + }, ); } } diff --git a/lib/screens/bb_repo.dart b/lib/screens/bb_repo.dart index 190db64..2b02ae0 100644 --- a/lib/screens/bb_repo.dart +++ b/lib/screens/bb_repo.dart @@ -57,7 +57,6 @@ class BbRepoScreen extends StatelessWidget { ), CommonStyle.border, TableView( - hasIcon: true, items: [ TableViewItem( leftIconData: Octicons.code, diff --git a/lib/screens/code_theme.dart b/lib/screens/code_theme.dart index c4579dd..10215a9 100644 --- a/lib/screens/code_theme.dart +++ b/lib/screens/code_theme.dart @@ -46,6 +46,7 @@ class MyApp extends StatelessWidget { CommonStyle.verticalGap, TableView( headerText: AppLocalizations.of(context)!.fontStyle, + hasIcon: false, items: [ TableViewItem( text: Text(AppLocalizations.of(context)!.fontSize), diff --git a/lib/screens/ge_repo.dart b/lib/screens/ge_repo.dart index a532ed4..fac8527 100644 --- a/lib/screens/ge_repo.dart +++ b/lib/screens/ge_repo.dart @@ -133,7 +133,6 @@ class GeRepoScreen extends StatelessWidget { ), CommonStyle.border, TableView( - hasIcon: true, items: [ TableViewItem( leftIconData: Octicons.code, diff --git a/lib/screens/ge_tree.dart b/lib/screens/ge_tree.dart index 88f6423..32d2256 100644 --- a/lib/screens/ge_tree.dart +++ b/lib/screens/ge_tree.dart @@ -6,6 +6,7 @@ import 'package:git_touch/widgets/app_bar_title.dart'; import 'package:git_touch/widgets/object_tree.dart'; import 'package:flutter/material.dart'; import 'package:git_touch/models/auth.dart'; +import 'package:git_touch/widgets/table_view.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/S.dart'; @@ -30,7 +31,7 @@ class GeTreeScreen extends StatelessWidget { return items; }, bodyBuilder: (data, _) { - return ObjectTree( + return TableView( items: [ for (var item in data) ObjectTreeItem( @@ -41,9 +42,9 @@ class GeTreeScreen extends StatelessWidget { url: (() { switch (item.type) { case 'tree': - return '/gitee/$owner/$name/tree/${item.sha}?path=${item.path!.urlencode}'; + return '/gitee/$owner/$name/tree/${item.sha}?path=${item.path.urlencode}'; case 'blob': - return '/gitee/$owner/$name/blob/${item.sha}?path=${item.path!.urlencode}'; + return '/gitee/$owner/$name/blob/${item.sha}?path=${item.path.urlencode}'; default: return null; } diff --git a/lib/screens/gh_events.dart b/lib/screens/gh_events.dart index 46f1ab6..a40a51a 100644 --- a/lib/screens/gh_events.dart +++ b/lib/screens/gh_events.dart @@ -21,12 +21,12 @@ class GhEventsScreen extends StatelessWidget { fetch: (page) async { page = page ?? 1; final events = await context.read().ghClient!.getJSON( - '/users/$login/events?page=$page&per_page=$pageSize', + '/users/$login/events?page=$page&per_page=$PAGE_SIZE', convert: (dynamic vs) => [for (var v in vs) GithubEvent.fromJson(v)]); return ListPayload( cursor: page + 1, - hasMore: events.length == pageSize, + hasMore: events.length == PAGE_SIZE, items: events, ); }, diff --git a/lib/screens/gh_gists_files.dart b/lib/screens/gh_gists_files.dart index 2003734..cc88c9c 100644 --- a/lib/screens/gh_gists_files.dart +++ b/lib/screens/gh_gists_files.dart @@ -7,6 +7,7 @@ import 'package:git_touch/graphql/github.var.gql.dart'; import 'package:git_touch/scaffolds/refresh_stateful.dart'; import 'package:git_touch/widgets/app_bar_title.dart'; import 'package:git_touch/widgets/object_tree.dart'; +import 'package:git_touch/widgets/table_view.dart'; import 'package:provider/provider.dart'; import 'package:git_touch/models/auth.dart'; import 'package:flutter_gen/gen_l10n/S.dart'; @@ -30,7 +31,7 @@ class GhGistsFilesScreen extends StatelessWidget { return gist; }, bodyBuilder: (payload, _) { - return ObjectTree( + return TableView( items: payload!.files!.map((v) { final uri = Uri( path: '/github/$login/gists/$id/${v.name}', @@ -41,7 +42,7 @@ class GhGistsFilesScreen extends StatelessWidget { return ObjectTreeItem( url: uri, type: 'file', - name: v.name, + name: v.name ?? '', downloadUrl: null, size: v.size, ); diff --git a/lib/screens/gh_news.dart b/lib/screens/gh_news.dart index c63fa16..b2a7230 100644 --- a/lib/screens/gh_news.dart +++ b/lib/screens/gh_news.dart @@ -44,12 +44,12 @@ class GhNewsScreenState extends State { final login = auth.activeAccount!.login; final events = await auth.ghClient!.getJSON( - '/users/$login/received_events?page=$page&per_page=$pageSize', + '/users/$login/received_events?page=$page&per_page=$PAGE_SIZE', convert: (dynamic vs) => [for (var v in vs) GithubEvent.fromJson(v)], ); return ListPayload( cursor: page + 1, - hasMore: events.length == pageSize, + hasMore: events.length == PAGE_SIZE, items: events, ); }, diff --git a/lib/screens/gh_object.dart b/lib/screens/gh_object.dart index 798c8d7..98f7f48 100644 --- a/lib/screens/gh_object.dart +++ b/lib/screens/gh_object.dart @@ -7,6 +7,7 @@ import 'package:git_touch/widgets/blob_view.dart'; import 'package:git_touch/widgets/object_tree.dart'; import 'package:flutter/material.dart'; import 'package:git_touch/models/auth.dart'; +import 'package:git_touch/widgets/table_view.dart'; import 'package:github/github.dart'; import 'package:provider/provider.dart'; @@ -58,7 +59,7 @@ class GhObjectScreen extends StatelessWidget { }, bodyBuilder: (data, _) { if (data.isDirectory) { - return ObjectTree( + return TableView( items: data.tree!.map((v) { // if (item.type == 'commit') return null; final uri = Uri( @@ -69,8 +70,8 @@ class GhObjectScreen extends StatelessWidget { }, ).toString(); return ObjectTreeItem( - name: v.name, - type: v.type, + name: v.name ?? '', + type: v.type ?? '', url: uri.toString(), downloadUrl: v.downloadUrl, size: v.type == 'file' ? v.size : null, diff --git a/lib/screens/gh_repo.dart b/lib/screens/gh_repo.dart index 90fe92e..11d86db 100644 --- a/lib/screens/gh_repo.dart +++ b/lib/screens/gh_repo.dart @@ -238,7 +238,6 @@ class GhRepoScreen extends StatelessWidget { ]), ], TableView( - hasIcon: true, items: [ if (ref != null) TableViewItem( diff --git a/lib/screens/gh_search.dart b/lib/screens/gh_search.dart index bd48733..8cc0323 100644 --- a/lib/screens/gh_search.dart +++ b/lib/screens/gh_search.dart @@ -50,7 +50,7 @@ class _GhSearchScreenState extends State { final auth = context.read(); final data = await auth.query(''' { - repository: search(first: $pageSize, type: REPOSITORY, query: "$keyword") { + repository: search(first: $PAGE_SIZE, type: REPOSITORY, query: "$keyword") { nodes { ... on Repository { owner { @@ -76,7 +76,7 @@ class _GhSearchScreenState extends State { } } } - user: search(first: $pageSize, type: USER, query: "$keyword") { + user: search(first: $PAGE_SIZE, type: USER, query: "$keyword") { nodes { ... on Organization { __typename @@ -90,7 +90,7 @@ class _GhSearchScreenState extends State { } } } - issue: search(first: $pageSize, type: ISSUE, query: "$keyword") { + issue: search(first: $PAGE_SIZE, type: ISSUE, query: "$keyword") { nodes { ... on PullRequest { __typename diff --git a/lib/screens/gh_user.dart b/lib/screens/gh_user.dart index 0d762da..df54ba5 100644 --- a/lib/screens/gh_user.dart +++ b/lib/screens/gh_user.dart @@ -118,7 +118,6 @@ class _User extends StatelessWidget { ), CommonStyle.border, TableView( - hasIcon: true, items: [ TableViewItem( leftIconData: Octicons.rss, @@ -217,7 +216,6 @@ class _Org extends StatelessWidget { ), ]), TableView( - hasIcon: true, items: [ TableViewItem( leftIconData: Octicons.rss, diff --git a/lib/screens/gl_project.dart b/lib/screens/gl_project.dart index 989c92a..676b59a 100644 --- a/lib/screens/gl_project.dart +++ b/lib/screens/gl_project.dart @@ -156,7 +156,6 @@ class GlProjectScreen extends StatelessWidget { ), CommonStyle.border, TableView( - hasIcon: true, items: [ TableViewItem( leftIconData: Octicons.code, diff --git a/lib/screens/gl_tree.dart b/lib/screens/gl_tree.dart index f417387..04868d7 100644 --- a/lib/screens/gl_tree.dart +++ b/lib/screens/gl_tree.dart @@ -1,6 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:git_touch/models/gitlab.dart'; -import 'package:git_touch/scaffolds/refresh_stateful.dart'; +import 'package:git_touch/scaffolds/list_stateful.dart'; import 'package:git_touch/widgets/app_bar_title.dart'; import 'package:git_touch/widgets/object_tree.dart'; import 'package:flutter/material.dart'; @@ -18,39 +18,41 @@ class GlTreeScreen extends StatelessWidget { @override Widget build(BuildContext context) { final auth = Provider.of(context); - return RefreshStatefulScaffold>( + + return ListStatefulScaffold( title: AppBarTitle(path ?? AppLocalizations.of(context)!.files), - fetch: () async { + fetch: (page) async { final uri = Uri( path: '/projects/$id/repository/tree', queryParameters: { 'ref': ref, + 'page': page?.toString(), ...(path == null ? {} : {'path': path}) }, ); - final res = await auth.fetchGitlab(uri.toString()); - return (res as List).map((v) => GitlabTreeItem.fromJson(v)); + final res = await auth.fetchGitlabWithPage(uri.toString()); + return ListPayload( + cursor: res.cursor, + hasMore: res.hasMore, + items: [for (var v in res.data) GitlabTreeItem.fromJson(v)], + ); }, - bodyBuilder: (data, _) { - return ObjectTree( - items: data.map((item) { - return ObjectTreeItem( - type: item.type, - name: item.name, - downloadUrl: - '${auth.activeAccount!.domain}/api/v4/projects/$id/repository/files/${item.path!.urlencode}/raw?ref=master', // TODO: - url: (() { - switch (item.type) { - case 'tree': - return '/gitlab/projects/$id/tree/$ref?path=${item.path!.urlencode}'; - case 'blob': - return '/gitlab/projects/$id/blob/$ref?path=${item.path!.urlencode}'; - default: - return null; - } - })(), - ); - }), + itemBuilder: (item) { + return ObjectTreeItem( + type: item.type, + name: item.name, + downloadUrl: + '${auth.activeAccount!.domain}/api/v4/projects/$id/repository/files/${item.path.urlencode}/raw?ref=master', // TODO: + url: (() { + switch (item.type) { + case 'tree': + return '/gitlab/projects/$id/tree/$ref?path=${item.path.urlencode}'; + case 'blob': + return '/gitlab/projects/$id/blob/$ref?path=${item.path.urlencode}'; + default: + return null; + } + })(), ); }, ); diff --git a/lib/screens/go_object.dart b/lib/screens/go_object.dart index 349414a..0f30605 100644 --- a/lib/screens/go_object.dart +++ b/lib/screens/go_object.dart @@ -8,6 +8,7 @@ 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:git_touch/widgets/table_view.dart'; import 'package:provider/provider.dart'; class GoObjectScreen extends StatelessWidget { @@ -44,7 +45,7 @@ class GoObjectScreen extends StatelessWidget { items.sort((a, b) { return sortByKey('dir', a.type, b.type); }); - return ObjectTree(items: [ + return TableView(items: [ for (var v in items) ObjectTreeItem( name: v.name, diff --git a/lib/screens/go_repo.dart b/lib/screens/go_repo.dart index 5e5bc71..1146d48 100644 --- a/lib/screens/go_repo.dart +++ b/lib/screens/go_repo.dart @@ -89,7 +89,6 @@ class GoRepoScreen extends StatelessWidget { ), CommonStyle.border, TableView( - hasIcon: true, items: [ TableViewItem( leftIconData: Octicons.code, diff --git a/lib/screens/go_user.dart b/lib/screens/go_user.dart index 473f494..ceffc58 100644 --- a/lib/screens/go_user.dart +++ b/lib/screens/go_user.dart @@ -71,7 +71,6 @@ class GoUserScreen extends StatelessWidget { ]), CommonStyle.border, TableView( - hasIcon: true, items: [ TableViewItem( leftIconData: Octicons.home, diff --git a/lib/screens/gt_object.dart b/lib/screens/gt_object.dart index 724493a..ee410c9 100644 --- a/lib/screens/gt_object.dart +++ b/lib/screens/gt_object.dart @@ -8,6 +8,7 @@ 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:git_touch/widgets/table_view.dart'; import 'package:provider/provider.dart'; class GtObjectScreen extends StatelessWidget { @@ -43,7 +44,7 @@ class GtObjectScreen extends StatelessWidget { items.sort((a, b) { return sortByKey('dir', a.type, b.type); }); - return ObjectTree(items: [ + return TableView(items: [ for (var v in items) ObjectTreeItem( name: v.name, diff --git a/lib/screens/gt_repo.dart b/lib/screens/gt_repo.dart index 9b234d6..5168889 100644 --- a/lib/screens/gt_repo.dart +++ b/lib/screens/gt_repo.dart @@ -81,7 +81,6 @@ class GtRepoScreen extends StatelessWidget { ), CommonStyle.border, TableView( - hasIcon: true, items: [ TableViewItem( leftIconData: Octicons.code, diff --git a/lib/screens/gt_user.dart b/lib/screens/gt_user.dart index f8666fb..c506e3d 100644 --- a/lib/screens/gt_user.dart +++ b/lib/screens/gt_user.dart @@ -131,7 +131,6 @@ class GtUserScreen extends StatelessWidget { ContributionWidget(weeks: p.userHeatmap), CommonStyle.border, TableView( - hasIcon: true, items: [ TableViewItem( leftIconData: Octicons.home, diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index e2a6ca6..189f1d8 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -26,81 +26,86 @@ class SettingsScreen extends StatelessWidget { body: Column( children: [ CommonStyle.verticalGap, - TableView(headerText: AppLocalizations.of(context)!.system, items: [ - if (auth.activeAccount!.platform == PlatformType.github) ...[ + TableView( + hasIcon: false, + headerText: AppLocalizations.of(context)!.system, + items: [ + if (auth.activeAccount!.platform == PlatformType.github) ...[ + TableViewItem( + text: Text(AppLocalizations.of(context)!.githubStatus), + url: 'https://www.githubstatus.com/', + ), + TableViewItem( + text: Text(AppLocalizations.of(context)!.reviewPermissions), + url: + 'https://github.com/settings/connections/applications/$clientId', + rightWidget: Text(auth.activeAccount!.login), + ), + ], + if (auth.activeAccount!.platform == PlatformType.gitlab) + TableViewItem( + text: Text(AppLocalizations.of(context)!.gitlabStatus), + url: '${auth.activeAccount!.domain}/help', + rightWidget: FutureBuilder( + future: + auth.fetchGitlab('/version').then((v) => v['version']), + builder: (context, snapshot) { + return Text(snapshot.data ?? ''); + }, + ), + ), + if (auth.activeAccount!.platform == PlatformType.gitea) + TableViewItem( + leftIconData: Octicons.info, + text: Text(AppLocalizations.of(context)!.giteaStatus), + url: '/gitea/status', + rightWidget: FutureBuilder( + future: + auth.fetchGitea('/version').then((v) => v['version']), + builder: (context, snapshot) { + return Text(snapshot.data ?? ''); + }, + ), + ), TableViewItem( - text: Text(AppLocalizations.of(context)!.githubStatus), - url: 'https://www.githubstatus.com/', - ), - TableViewItem( - text: Text(AppLocalizations.of(context)!.reviewPermissions), - url: - 'https://github.com/settings/connections/applications/$clientId', + text: Text(AppLocalizations.of(context)!.switchAccounts), + url: '/login', rightWidget: Text(auth.activeAccount!.login), ), + TableViewItem( + text: Text('App Language'), + rightWidget: Text(theme.locale == null + ? AppLocalizations.of(context)!.followSystem + : localeNameMap[theme.locale!] ?? theme.locale!), + onTap: () { + theme.showActions(context, [ + for (final key in [ + null, + ...AppLocalizations.supportedLocales + .map((l) => l.toString()) + .where((key) => localeNameMap[key] != null) + ]) + ActionItem( + text: key == null + ? AppLocalizations.of(context)!.followSystem + : localeNameMap[key], + onTap: (_) async { + final res = await theme.showConfirm( + context, + Text( + 'The app will reload to make the language setting take effect'), + ); + if (res == true && theme.locale != key) { + await theme.setLocale(key); + auth.reloadApp(); + } + }, + ) + ]); + }, + ) ], - if (auth.activeAccount!.platform == PlatformType.gitlab) - TableViewItem( - text: Text(AppLocalizations.of(context)!.gitlabStatus), - url: '${auth.activeAccount!.domain}/help', - rightWidget: FutureBuilder( - future: - auth.fetchGitlab('/version').then((v) => v['version']), - builder: (context, snapshot) { - return Text(snapshot.data ?? ''); - }, - ), - ), - if (auth.activeAccount!.platform == PlatformType.gitea) - TableViewItem( - leftIconData: Octicons.info, - text: Text(AppLocalizations.of(context)!.giteaStatus), - url: '/gitea/status', - rightWidget: FutureBuilder( - future: auth.fetchGitea('/version').then((v) => v['version']), - builder: (context, snapshot) { - return Text(snapshot.data ?? ''); - }, - ), - ), - TableViewItem( - text: Text(AppLocalizations.of(context)!.switchAccounts), - url: '/login', - rightWidget: Text(auth.activeAccount!.login), - ), - TableViewItem( - text: Text('App Language'), - rightWidget: Text(theme.locale == null - ? AppLocalizations.of(context)!.followSystem - : localeNameMap[theme.locale!] ?? theme.locale!), - onTap: () { - theme.showActions(context, [ - for (final key in [ - null, - ...AppLocalizations.supportedLocales - .map((l) => l.toString()) - .where((key) => localeNameMap[key] != null) - ]) - ActionItem( - text: key == null - ? AppLocalizations.of(context)!.followSystem - : localeNameMap[key], - onTap: (_) async { - final res = await theme.showConfirm( - context, - Text( - 'The app will reload to make the language setting take effect'), - ); - if (res == true && theme.locale != key) { - await theme.setLocale(key); - auth.reloadApp(); - } - }, - ) - ]); - }, - ) - ]), + ), CommonStyle.verticalGap, TableView(headerText: 'theme', items: [ TableViewItem( diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index f28e031..9b2c326 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -95,7 +95,7 @@ class GithubPalette { } // final pageSize = 5; -final pageSize = 30; +final PAGE_SIZE = 30; var createWarning = (String text) => Text(text, style: TextStyle(color: Colors.redAccent)); @@ -189,12 +189,12 @@ const TOTAL_COUNT_FALLBACK = 999; // TODO: class ListPayload { K cursor; - Iterable items; bool hasMore; + Iterable items; ListPayload({ - required this.items, required this.cursor, required this.hasMore, + required this.items, }); } diff --git a/lib/widgets/object_tree.dart b/lib/widgets/object_tree.dart index 5e125bd..5aec2f7 100644 --- a/lib/widgets/object_tree.dart +++ b/lib/widgets/object_tree.dart @@ -5,31 +5,27 @@ import 'package:git_touch/widgets/table_view.dart'; import 'package:primer/primer.dart'; import 'package:file_icon/file_icon.dart'; -class ObjectTreeItem { +class ObjectTreeItem extends StatelessWidget { + final String type; + final String name; + final int? size; final String? url; final String? downloadUrl; - final String? name; - final String? type; - final int? size; - ObjectTreeItem({ - required this.name, - required this.url, - required this.downloadUrl, + + const ObjectTreeItem({ required this.type, + required this.name, this.size, + this.url, + this.downloadUrl, }); -} -class ObjectTree extends StatelessWidget { - final Iterable items; - ObjectTree({required this.items}); - - Widget _buildIcon(ObjectTreeItem item) { - switch (item.type) { + Widget _buildIcon() { + switch (type) { case 'blob': // github gql, gitlab case 'file': // github rest, gitea case 'commit_file': // bitbucket - return FileIcon(item.name!, size: 36); + return FileIcon(name, size: 36); case 'tree': // github gql, gitlab case 'dir': // github rest, gitea case 'commit_directory': // bitbucket @@ -51,32 +47,26 @@ class ObjectTree extends StatelessWidget { @override Widget build(BuildContext context) { - return TableView( - hasIcon: true, - items: [ - for (var item in items) - TableViewItem( - leftWidget: _buildIcon(item), - text: Text(item.name!), - rightWidget: item.size == null ? null : Text(filesize(item.size)), - url: [ - // Let system browser handle these files - // - // TODO: - // Unhandled Exception: PlatformException(Error, Error while launching - // https://github.com/flutter/flutter/issues/49162 + return TableViewItem( + leftWidget: _buildIcon(), + text: Text(name), + rightWidget: size == null ? null : Text(filesize(size)), + url: [ + // Let system browser handle these files + // + // TODO: + // Unhandled Exception: PlatformException(Error, Error while launching + // https://github.com/flutter/flutter/issues/49162 - // Docs - 'pdf', 'docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls', - // Fonts - 'ttf', 'otf', 'eot', 'woff', 'woff2', - 'svg', - ].contains(item.name!.ext) - ? item.downloadUrl - : item.url, - hideRightChevron: item.size != null, - ) - ], + // Docs + 'pdf', 'docx', 'doc', 'pptx', 'ppt', 'xlsx', 'xls', + // Fonts + 'ttf', 'otf', 'eot', 'woff', 'woff2', + 'svg', + ].contains(name.ext) + ? downloadUrl + : url, + hideRightChevron: size != null, ); } } diff --git a/lib/widgets/release_item.dart b/lib/widgets/release_item.dart index e8a8bae..2cbe7fb 100644 --- a/lib/widgets/release_item.dart +++ b/lib/widgets/release_item.dart @@ -87,26 +87,29 @@ class ReleaseItem extends StatelessWidget { ), ), children: [ - TableView(items: [ - if (releaseAssets != null) - for (var asset in releaseAssets!.nodes!) - TableViewItem( - text: Text( - asset.name, - style: TextStyle( - color: theme.palette.primary, - fontSize: 14, - fontWeight: FontWeight.w400, + TableView( + hasIcon: false, + items: [ + if (releaseAssets != null) + for (var asset in releaseAssets!.nodes!) + TableViewItem( + text: Text( + asset.name, + style: TextStyle( + color: theme.palette.primary, + fontSize: 14, + fontWeight: FontWeight.w400, + ), ), + rightWidget: IconButton( + onPressed: () { + theme.push(context, asset.downloadUrl); + }, + icon: Icon(Ionicons.download_outline)), + hideRightChevron: true, ), - rightWidget: IconButton( - onPressed: () { - theme.push(context, asset.downloadUrl); - }, - icon: Icon(Ionicons.download_outline)), - hideRightChevron: true, - ), - ]) + ], + ) ], ), ) diff --git a/lib/widgets/table_view.dart b/lib/widgets/table_view.dart index ff7eb06..0bc4627 100644 --- a/lib/widgets/table_view.dart +++ b/lib/widgets/table_view.dart @@ -24,7 +24,7 @@ class TableViewHeader extends StatelessWidget { } } -class TableViewItem { +class TableViewItem extends StatelessWidget { final Widget text; final IconData? leftIconData; final Widget? leftWidget; @@ -42,76 +42,74 @@ class TableViewItem { this.url, this.hideRightChevron = false, }) : assert(leftIconData == null || leftWidget == null); -} - -class TableView extends StatelessWidget { - final String? headerText; - final Iterable items; - final bool hasIcon; - - double get _leftPadding => hasIcon ? 44 : 12; - - TableView({this.headerText, required this.items, this.hasIcon = false}); @override Widget build(BuildContext context) { final theme = Provider.of(context); + return LinkWidget( + onTap: onTap, + url: url, + child: DefaultTextStyle( + style: TextStyle(fontSize: 17, color: theme.palette.text), + overflow: TextOverflow.ellipsis, + child: Container( + height: 44, + child: Row( + children: [ + SizedBox( + width: (leftWidget == null && leftIconData == null) ? 12 : 44, + child: Center( + child: leftWidget ?? + Icon( + leftIconData, + color: theme.palette.primary, + size: 20, + )), + ), + Expanded(child: text), + if (rightWidget != null) ...[ + DefaultTextStyle( + style: TextStyle( + fontSize: 17, + color: theme.palette.tertiaryText, + ), + child: rightWidget!, + ), + SizedBox(width: 6) + ], + if ((onTap != null || url != null) && !hideRightChevron) + Icon(Ionicons.chevron_forward, + size: 20, color: theme.palette.tertiaryText) + else + SizedBox(width: 2), + SizedBox(width: 8), + ], + ), + ), + ), + ); + } +} + +class TableView extends StatelessWidget { + final String? headerText; + final Iterable items; + final bool? hasIcon; + + TableView({this.headerText, required this.items, this.hasIcon = true}); + + double get _leftPadding => hasIcon == true ? 44 : 12; + + @override + Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (headerText != null) TableViewHeader(headerText), - CommonStyle.border, ...join( BorderView(leftPadding: _leftPadding), - items.map((item) { - var leftWidget = item.leftWidget; - if (leftWidget == null && hasIcon) { - leftWidget = Icon( - item.leftIconData, - color: theme.palette.primary, - size: 20, - ); - } - - return LinkWidget( - onTap: item.onTap, - url: item.url, - child: DefaultTextStyle( - style: TextStyle(fontSize: 17, color: theme.palette.text), - overflow: TextOverflow.ellipsis, - child: Container( - height: 44, - child: Row( - children: [ - SizedBox( - width: _leftPadding, - child: Center(child: leftWidget), - ), - Expanded(child: item.text), - if (item.rightWidget != null) ...[ - DefaultTextStyle( - style: TextStyle( - fontSize: 17, - color: theme.palette.tertiaryText, - ), - child: item.rightWidget!, - ), - SizedBox(width: 6) - ], - if ((item.onTap != null || item.url != null) && - !item.hideRightChevron) - Icon(Ionicons.chevron_forward, - size: 20, color: theme.palette.tertiaryText) - else - SizedBox(width: 2), - SizedBox(width: 8), - ], - ), - ), - ), - ); - }).toList(), + items.toList(), ), CommonStyle.border, ],