【开源鸿蒙跨平台Flutter开发】AtomGit Pocket Tool:仓库组件化
一、阶段目标与平台背景
1.1 项目迭代背景
本项目基于昨天开发的 AtomGit 口袋工具,在第一阶段成功实现了基础的搜索功能和仓库列表展示,并打通了与 AtomGit OpenAPI 的数据交互。随着核心功能的初步完成,第二阶段我们将聚焦于用户体验优化与代码组件化设计,旨在构建一个更稳定、可维护且用户友好的应用程序。
在初步版本中,虽然功能已基本可用,但存在以下可优化点:
- 界面交互较为基础,缺乏流畅的刷新体验
- 重复的布局代码增加了维护成本
- 数据加载和错误处理机制有待完善
本阶段将围绕这些问题展开改进,为后续的功能扩展打下坚实基础。
1.2 AtomGit API
AtomGit 作为国内知名的代码托管平台,其 API 提供了丰富的接口能力。
二、核心资料与技术栈
2.1 第三方库与依赖
在本次迭代中,我们在 pubspec.yaml 中新增刷新库依赖:
dependencies:
http: ^1.2.2
pull_to_refresh_flutter3: ^2.0.2
pull_to_refresh_flutter3 提供 SmartRefresher 组件以及 RefreshController,可快速实现下拉刷新、上拉加载与多种状态提示。还是挺方便挺好用的。
2.3 数据模型与封装
- Repository:仓库数据模型,封装 id、名称、描述、语言、Star/Fork 数、更新时间等字段,并提供
formattedUpdatedAt、displayName等派生属性。
class Repository {
final int id;
final String name;
final String fullName;
final String? description;
final String? htmlUrl;
final String? language;
final int stargazersCount;
final int forksCount;
final String? ownerName;
final String? ownerAvatarUrl;
final DateTime? updatedAt;
final String? namespacePath; // 新增:namespace的path,用于API调用
Repository({
required this.id,
required this.name,
required this.fullName,
required this.description,
required this.htmlUrl,
required this.language,
required this.stargazersCount,
required this.forksCount,
required this.ownerName,
required this.ownerAvatarUrl,
required this.updatedAt,
this.namespacePath,
});
factory Repository.fromJson(Map<String, dynamic> json) {
final owner = json['owner'];
final namespace = json['namespace'];
String? ownerName;
String? ownerAvatarUrl;
String? namespacePath;
// 优先从namespace获取真实的仓库所有者信息
if (namespace is Map<String, dynamic>) {
ownerName = namespace['path']?.toString() ?? namespace['name']?.toString();
ownerAvatarUrl = namespace['avatar']?.toString();
namespacePath = namespace['path']?.toString();
}
// 如果namespace没有,则从owner获取
if (ownerName == null && owner is Map<String, dynamic>) {
ownerName = owner['login']?.toString() ?? owner['name']?.toString();
ownerAvatarUrl = owner['avatar_url']?.toString();
} else if (ownerName == null && owner is String) {
ownerName = owner;
ownerAvatarUrl = json['avatar_url']?.toString();
}
// 使用 path 字段作为仓库名(这是 API 调用需要的),而不是 name(可能是中文显示名)
final fullName = json['full_name']?.toString() ?? json['path']?.toString() ?? json['name']?.toString() ?? '';
// path 是仓库的实际路径名,name 可能是显示名(如中文名)
final name = json['path']?.toString() ?? json['name']?.toString() ?? '';
return Repository(
id: _parseInt(json['id']),
name: name,
fullName: fullName,
description: json['description']?.toString(),
htmlUrl: json['html_url']?.toString(),
language: json['language']?.toString(),
stargazersCount: _parseInt(json['stargazers_count']),
forksCount: _parseInt(json['forks_count']),
ownerName: ownerName,
ownerAvatarUrl: ownerAvatarUrl,
updatedAt: _parseDateTime(json['updated_at']),
namespacePath: namespacePath,
);
}
static int _parseInt(dynamic value) {
if (value is int) return value;
if (value is String) {
return int.tryParse(value) ?? 0;
}
return 0;
}
static DateTime? _parseDateTime(dynamic value) {
if (value == null) return null;
final text = value.toString();
try {
return DateTime.parse(text).toLocal();
} catch (_) {
return null;
}
}
String get displayName => name.isNotEmpty ? name : fullName;
String get formattedUpdatedAt {
if (updatedAt == null) return '未知';
final date = updatedAt!;
return '${date.year}-${_twoDigits(date.month)}-${_twoDigits(date.day)} ${_twoDigits(date.hour)}:${_twoDigits(date.minute)}';
}
static String _twoDigits(int value) => value.toString().padLeft(2, '0');
}

- UserSummary:搜索用户列表项结构,包含 id、login、avatar、profile 链接等。
const UserSummary({
required this.id,
required this.login,
required this.htmlUrl,
required this.avatarUrl,
required this.type,
required this.score,
});

三、实现步骤详解
3.1 API 服务层升级
在 lib/services/api_service.dart 中统一封装 GET 请求,添加公共 Header,提升代码复用性:
static Map<String, String> get _headers => <String, String>{
'Authorization': 'Bearer $token',
'Accept': 'application/json',
};
static Future<http.Response> _get(Uri url) {
return http.get(url, headers: _headers);
}
针对不同接口分别返回模型:
searchUsers:支持page/per_page,根据返回结构构造UserSearchResult。getUserRepositories:返回List<Repository>,用于“我的仓库”分页。
通过 Future + try/catch 引导上层处理异常,错误信息携带状态码与响应体便于定位问题。
3.2 自定义仓库卡片组件
lib/widgets/repository_card.dart 负责统一仓库信息的展示:
- 头部采用
CircleAvatar展示仓库所有者头像,若缺失则显示首字母; - 主体呈现仓库名称、作者、描述;
- 尾部通过
Chip集合展示 Star 数、Fork 数、语言和更新时间。
该组件可在搜索结果和“我的仓库”页面复用,避免重复布局代码,可以为后续的开发提供便利。
四、运行结果

以上是运行的结果,仓库显示出来的卡片已经组件化。也包含了仓库名称,编程语言等
这次组件化之后,会使得后续的开发更加的方便。明天继续加油
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)