fix: bitbucket and gitlab files length

closes #224
This commit is contained in:
Rongjian Zhang 2021-06-14 11:28:12 +08:00
parent 70e19e9b72
commit 242dd1200d
37 changed files with 337 additions and 327 deletions

View File

@ -289,7 +289,7 @@ class AuthModel with ChangeNotifier {
Future<DataWithPage> 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<DataWithPage> 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<DataWithPage> 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(

View File

@ -65,11 +65,11 @@ class BbRepoMainbranch {
@JsonSerializable(fieldRename: FieldRename.snake)
class BbTree {
String? type;
String? path;
String type;
String path;
int? size;
Map<String, dynamic>? links;
BbTree();
BbTree({required this.type, required this.path});
factory BbTree.fromJson(Map<String, dynamic> json) => _$BbTreeFromJson(json);
}

View File

@ -115,9 +115,10 @@ Map<String, dynamic> _$BbRepoMainbranchToJson(BbRepoMainbranch instance) =>
};
BbTree _$BbTreeFromJson(Map<String, dynamic> 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<String, dynamic>?;
}

View File

@ -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<String, dynamic> 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<String, dynamic> json) =>
_$GiteaBlobFromJson(json);
}

View File

@ -81,9 +81,10 @@ Map<String, dynamic> _$GiteaRepositoryToJson(GiteaRepository instance) =>
};
GiteaTree _$GiteaTreeFromJson(Map<String, dynamic> 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<String, dynamic> _$GiteaTreeToJson(GiteaTree instance) => <String, dynamic>{
};
GiteaBlob _$GiteaBlobFromJson(Map<String, dynamic> 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?

View File

@ -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<String, dynamic> json) =>
_$GiteeTreeItemFromJson(json);
}

View File

@ -181,9 +181,10 @@ Map<String, dynamic> _$GiteeCommitAuthorToJson(GiteeCommitAuthor instance) =>
};
GiteeTreeItem _$GiteeTreeItemFromJson(Map<String, dynamic> 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?;
}

View File

@ -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<String, dynamic> json) =>
_$GitlabTreeItemFromJson(json);
}

View File

@ -229,10 +229,11 @@ Map<String, dynamic> _$GitlabProjectNamespaceToJson(
};
GitlabTreeItem _$GitlabTreeItemFromJson(Map<String, dynamic> 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<String, dynamic> _$GitlabTreeItemToJson(GitlabTreeItem instance) =>

View File

@ -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<String, dynamic> 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<String, dynamic> json) =>
_$GogsBlobFromJson(json);
}

View File

@ -85,9 +85,10 @@ Map<String, dynamic> _$GogsOrgToJson(GogsOrg instance) => <String, dynamic>{
};
GogsTree _$GogsTreeFromJson(Map<String, dynamic> 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<String, dynamic> _$GogsTreeToJson(GogsTree instance) => <String, dynamic>{
};
GogsBlob _$GogsBlobFromJson(Map<String, dynamic> 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?

View File

@ -19,8 +19,8 @@ class ListStatefulScaffold<T, K> extends StatefulWidget {
ListStatefulScaffold({
required this.title,
required this.itemBuilder,
required this.fetch,
required this.itemBuilder,
this.actionBuilder,
});

View File

@ -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<AuthModel>(context);
return RefreshStatefulScaffold(
return ListStatefulScaffold<dynamic, String?>(
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',
);
},
);
}
}

View File

@ -57,7 +57,6 @@ class BbRepoScreen extends StatelessWidget {
),
CommonStyle.border,
TableView(
hasIcon: true,
items: [
TableViewItem(
leftIconData: Octicons.code,

View File

@ -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),

View File

@ -133,7 +133,6 @@ class GeRepoScreen extends StatelessWidget {
),
CommonStyle.border,
TableView(
hasIcon: true,
items: [
TableViewItem(
leftIconData: Octicons.code,

View File

@ -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;
}

View File

@ -21,12 +21,12 @@ class GhEventsScreen extends StatelessWidget {
fetch: (page) async {
page = page ?? 1;
final events = await context.read<AuthModel>().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,
);
},

View File

@ -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,
);

View File

@ -44,12 +44,12 @@ class GhNewsScreenState extends State<GhNewsScreen> {
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,
);
},

View File

@ -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,

View File

@ -238,7 +238,6 @@ class GhRepoScreen extends StatelessWidget {
]),
],
TableView(
hasIcon: true,
items: [
if (ref != null)
TableViewItem(

View File

@ -50,7 +50,7 @@ class _GhSearchScreenState extends State<GhSearchScreen> {
final auth = context.read<AuthModel>();
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<GhSearchScreen> {
}
}
}
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<GhSearchScreen> {
}
}
}
issue: search(first: $pageSize, type: ISSUE, query: "$keyword") {
issue: search(first: $PAGE_SIZE, type: ISSUE, query: "$keyword") {
nodes {
... on PullRequest {
__typename

View File

@ -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,

View File

@ -156,7 +156,6 @@ class GlProjectScreen extends StatelessWidget {
),
CommonStyle.border,
TableView(
hasIcon: true,
items: [
TableViewItem(
leftIconData: Octicons.code,

View File

@ -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<AuthModel>(context);
return RefreshStatefulScaffold<Iterable<GitlabTreeItem>>(
return ListStatefulScaffold<GitlabTreeItem, int>(
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;
}
})(),
);
},
);

View File

@ -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,

View File

@ -89,7 +89,6 @@ class GoRepoScreen extends StatelessWidget {
),
CommonStyle.border,
TableView(
hasIcon: true,
items: [
TableViewItem(
leftIconData: Octicons.code,

View File

@ -71,7 +71,6 @@ class GoUserScreen extends StatelessWidget {
]),
CommonStyle.border,
TableView(
hasIcon: true,
items: [
TableViewItem(
leftIconData: Octicons.home,

View File

@ -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,

View File

@ -81,7 +81,6 @@ class GtRepoScreen extends StatelessWidget {
),
CommonStyle.border,
TableView(
hasIcon: true,
items: [
TableViewItem(
leftIconData: Octicons.code,

View File

@ -131,7 +131,6 @@ class GtUserScreen extends StatelessWidget {
ContributionWidget(weeks: p.userHeatmap),
CommonStyle.border,
TableView(
hasIcon: true,
items: [
TableViewItem(
leftIconData: Octicons.home,

View File

@ -26,81 +26,86 @@ class SettingsScreen extends StatelessWidget {
body: Column(
children: <Widget>[
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<String>(
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<String>(
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<String>(
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<String>(
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(

View File

@ -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<T, K> {
K cursor;
Iterable<T> items;
bool hasMore;
Iterable<T> items;
ListPayload({
required this.items,
required this.cursor,
required this.hasMore,
required this.items,
});
}

View File

@ -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<ObjectTreeItem> 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,
);
}
}

View File

@ -87,26 +87,29 @@ class ReleaseItem extends StatelessWidget {
),
),
children: <Widget>[
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,
),
])
],
)
],
),
)

View File

@ -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<TableViewItem> 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<ThemeModel>(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<Widget> 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: <Widget>[
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,
],