-
Notifications
You must be signed in to change notification settings - Fork 22
增加种子详情概览模块 #64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
增加种子详情概览模块 #64
Conversation
Summary of ChangesHello @mjnhmd, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 本次拉取请求主要致力于提升应用的用户体验,通过在列表页引入下拉刷新机制,使得用户能够更便捷地获取最新数据。同时,在种子详情页新增了一个全面的概览模块,将关键的种子信息(如封面、评分、统计数据等)以直观友好的方式呈现,极大地丰富了详情页的内容展示,并方便用户快速了解种子核心信息。 Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request primarily enhances the HomePage by adding a RefreshIndicator to the ListView.builder for pull-to-refresh functionality. The TorrentDetailPage receives significant updates, including the addition of a comprehensive _buildInfoOverviewCard that displays torrent details like cover image, description, discount tags, statistics (seeders, leechers, size), and ratings (Douban, IMDb), supported by new helper methods for formatting size, handling discount visuals, and validating ratings. Debugging print statements were also added to the _loadComments method. A minor change in mteam_adapter.dart introduced an intermediate variable before returning a function call. Review comments suggest optimizing the FutureBuilder in the new overview card to prevent unnecessary network requests on rebuild, refactoring the large _buildInfoOverviewCard into smaller widgets for better readability and maintainability, moving the _formatSize method to a utility file to avoid code duplication, replacing hardcoded colors with theme-aware options, improving error logging in a try-catch block, and removing an unnecessary local variable in mteam_adapter.dart for conciseness.
lib/pages/torrent_detail_page.dart
Outdated
| ? FutureBuilder<List<int>>( | ||
| future: ImageHttpClient.instance | ||
| .fetchImage(coverUrl) | ||
| .then((response) => response.data!), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // 格式化文件大小 | ||
| String _formatSize(int bytes) { | ||
| if (bytes < 1024) return '$bytes B'; | ||
| if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(2)} KB'; | ||
| if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB'; | ||
| return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB'; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| final now = DateTime.now(); | ||
| final hoursLeft = endDateTime.difference(now).inHours; | ||
| if (hoursLeft > 0) return '$baseText ${hoursLeft}h'; | ||
| } catch (e) { /* 解析失败 */ } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// 构建种子信息概览卡片 | ||
| Widget _buildInfoOverviewCard() { | ||
| final torrent = widget.torrentItem; | ||
| final hasDouban = _hasRatingValue(torrent.doubanRating); | ||
| final hasImdb = _hasRatingValue(torrent.imdbRating); | ||
| final tags = torrent.tags; | ||
| final colorScheme = Theme.of(context).colorScheme; | ||
|
|
||
| // 封面图片回退逻辑:优先使用 cover,否则使用 imageList 的第一张图 | ||
| final coverUrl = torrent.cover.isNotEmpty | ||
| ? torrent.cover | ||
| : (torrent.imageList.isNotEmpty ? torrent.imageList.first : ''); | ||
| return Card( | ||
| margin: const EdgeInsets.only(bottom: 16), | ||
| elevation: 2, | ||
| shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), | ||
| child: Padding( | ||
| padding: const EdgeInsets.all(12), | ||
| child: IntrinsicHeight( | ||
| child: Row( | ||
| crossAxisAlignment: CrossAxisAlignment.stretch, | ||
| children: [ | ||
| // 左侧封面图 - 固定宽度,高度自适应 | ||
| Container( | ||
| width: 80, | ||
| constraints: const BoxConstraints(minHeight: 110), | ||
| decoration: BoxDecoration( | ||
| color: colorScheme.surfaceContainerHighest, | ||
| borderRadius: BorderRadius.circular(8), | ||
| border: Border.all( | ||
| color: colorScheme.outline.withValues(alpha: 0.2), | ||
| width: 1, | ||
| ), | ||
| boxShadow: [ | ||
| BoxShadow( | ||
| color: Colors.black.withValues(alpha: 0.08), | ||
| blurRadius: 4, | ||
| offset: const Offset(0, 2), | ||
| ), | ||
| ], | ||
| ), | ||
| child: ClipRRect( | ||
| borderRadius: BorderRadius.circular(7), | ||
| child: coverUrl.isNotEmpty | ||
| ? FutureBuilder<List<int>>( | ||
| future: ImageHttpClient.instance | ||
| .fetchImage(coverUrl) | ||
| .then((response) => response.data!), | ||
| builder: (context, snapshot) { | ||
| if (snapshot.connectionState == ConnectionState.waiting) { | ||
| return Center( | ||
| child: SizedBox( | ||
| width: 20, | ||
| height: 20, | ||
| child: CircularProgressIndicator( | ||
| strokeWidth: 2, | ||
| color: colorScheme.primary, | ||
| ), | ||
| ), | ||
| ); | ||
| } | ||
| if (snapshot.hasError || !snapshot.hasData) { | ||
| return _buildCoverPlaceholder(); | ||
| } | ||
| final imageData = Uint8List.fromList(snapshot.data!); | ||
| return GestureDetector( | ||
| onTap: () => Navigator.of(context).push( | ||
| MaterialPageRoute( | ||
| builder: (_) => FullScreenImageViewer(imageData: imageData), | ||
| fullscreenDialog: true, | ||
| ), | ||
| ), | ||
| child: Image.memory( | ||
| imageData, | ||
| fit: BoxFit.cover, | ||
| errorBuilder: (_, __, ___) => _buildCoverPlaceholder(), | ||
| ), | ||
| ); | ||
| }, | ||
| ) | ||
| : _buildCoverPlaceholder(), | ||
| ), | ||
| ), | ||
| const SizedBox(width: 12), | ||
| // 右侧信息区域 - 自适应高度 | ||
| Expanded( | ||
| child: Column( | ||
| crossAxisAlignment: CrossAxisAlignment.start, | ||
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||
| children: [ | ||
| // 上部:副标题和标签 | ||
| Column( | ||
| crossAxisAlignment: CrossAxisAlignment.start, | ||
| children: [ | ||
| // 副标题 | ||
| if (torrent.smallDescr.isNotEmpty) | ||
| Padding( | ||
| padding: const EdgeInsets.only(bottom: 8), | ||
| child: Text( | ||
| torrent.smallDescr, | ||
| maxLines: 2, | ||
| overflow: TextOverflow.ellipsis, | ||
| style: Theme.of(context).textTheme.bodyMedium?.copyWith( | ||
| color: colorScheme.onSurfaceVariant, | ||
| height: 1.3, | ||
| ), | ||
| ), | ||
| ), | ||
| // 标签行(包含优惠标签和自定义标签) | ||
| Wrap( | ||
| spacing: 6, | ||
| runSpacing: 6, | ||
| children: [ | ||
| // 优惠标签 - 放在最前面 | ||
| if (torrent.discount != DiscountType.normal) | ||
| Container( | ||
| padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), | ||
| decoration: BoxDecoration( | ||
| color: _discountColor(torrent.discount), | ||
| borderRadius: BorderRadius.circular(4), | ||
| boxShadow: [ | ||
| BoxShadow( | ||
| color: _discountColor(torrent.discount).withValues(alpha: 0.3), | ||
| blurRadius: 4, | ||
| offset: const Offset(0, 1), | ||
| ), | ||
| ], | ||
| ), | ||
| child: Text( | ||
| _discountText(torrent.discount, torrent.discountEndTime), | ||
| style: const TextStyle( | ||
| color: Colors.white, | ||
| fontSize: 11, | ||
| fontWeight: FontWeight.w600, | ||
| ), | ||
| ), | ||
| ), | ||
| // 自定义标签 | ||
| ...tags.map((tag) => Container( | ||
| padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), | ||
| decoration: BoxDecoration( | ||
| color: tag.color, | ||
| borderRadius: BorderRadius.circular(4), | ||
| ), | ||
| child: Text( | ||
| tag.content, | ||
| style: const TextStyle( | ||
| color: Colors.white, | ||
| fontSize: 10, | ||
| fontWeight: FontWeight.w500, | ||
| ), | ||
| ), | ||
| )), | ||
| ], | ||
| ), | ||
| ], | ||
| ), | ||
| const SizedBox(height: 10), | ||
| // 下部:统计信息和时间 | ||
| Column( | ||
| crossAxisAlignment: CrossAxisAlignment.start, | ||
| children: [ | ||
| // 统计信息行 | ||
| Container( | ||
| padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), | ||
| decoration: BoxDecoration( | ||
| color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5), | ||
| borderRadius: BorderRadius.circular(6), | ||
| ), | ||
| child: Row( | ||
| mainAxisSize: MainAxisSize.min, | ||
| children: [ | ||
| // 做种数 | ||
| Icon(Icons.upload_rounded, color: Colors.green.shade600, size: 14), | ||
| const SizedBox(width: 2), | ||
| Text( | ||
| '${torrent.seeders}', | ||
| style: TextStyle( | ||
| fontSize: 12, | ||
| fontWeight: FontWeight.w600, | ||
| color: Colors.green.shade700, | ||
| ), | ||
| ), | ||
| const SizedBox(width: 8), | ||
| // 下载数 | ||
| Icon(Icons.download_rounded, color: Colors.orange.shade600, size: 14), | ||
| const SizedBox(width: 2), | ||
| Text( | ||
| '${torrent.leechers}', | ||
| style: TextStyle( | ||
| fontSize: 12, | ||
| fontWeight: FontWeight.w600, | ||
| color: Colors.orange.shade700, | ||
| ), | ||
| ), | ||
| const SizedBox(width: 10), | ||
| // 分隔线 | ||
| Container( | ||
| width: 1, | ||
| height: 12, | ||
| color: colorScheme.outline.withValues(alpha: 0.3), | ||
| ), | ||
| const SizedBox(width: 10), | ||
| // 文件大小 | ||
| Icon(Icons.storage_rounded, color: colorScheme.primary, size: 14), | ||
| const SizedBox(width: 4), | ||
| Text( | ||
| _formatSize(torrent.sizeBytes), | ||
| style: TextStyle( | ||
| fontSize: 12, | ||
| fontWeight: FontWeight.w600, | ||
| color: colorScheme.onSurface, | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| const SizedBox(height: 8), | ||
| // 评分和时间行 | ||
| Row( | ||
| children: [ | ||
| // 豆瓣评分 | ||
| if (hasDouban) ...[ | ||
| Container( | ||
| padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), | ||
| decoration: BoxDecoration( | ||
| color: const Color(0xFF007711), | ||
| borderRadius: BorderRadius.circular(4), | ||
| ), | ||
| child: Row( | ||
| mainAxisSize: MainAxisSize.min, | ||
| children: [ | ||
| const Text( | ||
| '豆', | ||
| style: TextStyle( | ||
| fontSize: 10, | ||
| color: Colors.white, | ||
| fontWeight: FontWeight.bold, | ||
| ), | ||
| ), | ||
| const SizedBox(width: 3), | ||
| Text( | ||
| '${torrent.doubanRating}', | ||
| style: const TextStyle( | ||
| fontSize: 11, | ||
| color: Colors.white, | ||
| fontWeight: FontWeight.w600, | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| const SizedBox(width: 6), | ||
| ], | ||
| // IMDB评分 | ||
| if (hasImdb) ...[ | ||
| Container( | ||
| padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), | ||
| decoration: BoxDecoration( | ||
| color: const Color(0xFFF5C518), | ||
| borderRadius: BorderRadius.circular(4), | ||
| ), | ||
| child: Row( | ||
| mainAxisSize: MainAxisSize.min, | ||
| children: [ | ||
| const Text( | ||
| 'IMDb', | ||
| style: TextStyle( | ||
| fontSize: 9, | ||
| color: Colors.black, | ||
| fontWeight: FontWeight.bold, | ||
| ), | ||
| ), | ||
| const SizedBox(width: 3), | ||
| Text( | ||
| '${torrent.imdbRating}', | ||
| style: const TextStyle( | ||
| fontSize: 11, | ||
| color: Colors.black, | ||
| fontWeight: FontWeight.w600, | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| const SizedBox(width: 6), | ||
| ], | ||
| const Spacer(), | ||
| // 发布时间 | ||
| Row( | ||
| mainAxisSize: MainAxisSize.min, | ||
| children: [ | ||
| Icon( | ||
| Icons.schedule_rounded, | ||
| size: 12, | ||
| color: colorScheme.outline, | ||
| ), | ||
| const SizedBox(width: 4), | ||
| Text( | ||
| Formatters.formatTorrentCreatedDate(torrent.createdDate), | ||
| style: TextStyle( | ||
| fontSize: 11, | ||
| color: colorScheme.outline, | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ], | ||
| ), | ||
| ], | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| ), | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Icon(Icons.upload_rounded, color: Colors.green.shade600, size: 14), | ||
| const SizedBox(width: 2), | ||
| Text( | ||
| '${torrent.seeders}', | ||
| style: TextStyle( | ||
| fontSize: 12, | ||
| fontWeight: FontWeight.w600, | ||
| color: Colors.green.shade700, | ||
| ), | ||
| ), | ||
| const SizedBox(width: 8), | ||
| // 下载数 | ||
| Icon(Icons.download_rounded, color: Colors.orange.shade600, size: 14), | ||
| const SizedBox(width: 2), | ||
| Text( | ||
| '${torrent.leechers}', | ||
| style: TextStyle( | ||
| fontSize: 12, | ||
| fontWeight: FontWeight.w600, | ||
| color: Colors.orange.shade700, | ||
| ), | ||
| ), | ||
| const SizedBox(width: 10), | ||
| // 分隔线 | ||
| Container( | ||
| width: 1, | ||
| height: 12, | ||
| color: colorScheme.outline.withValues(alpha: 0.3), | ||
| ), | ||
| const SizedBox(width: 10), | ||
| // 文件大小 | ||
| Icon(Icons.storage_rounded, color: colorScheme.primary, size: 14), | ||
| const SizedBox(width: 4), | ||
| Text( | ||
| _formatSize(torrent.sizeBytes), | ||
| style: TextStyle( | ||
| fontSize: 12, | ||
| fontWeight: FontWeight.w600, | ||
| color: colorScheme.onSurface, | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| const SizedBox(height: 8), | ||
| // 评分和时间行 | ||
| Row( | ||
| children: [ | ||
| // 豆瓣评分 | ||
| if (hasDouban) ...[ | ||
| Container( | ||
| padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), | ||
| decoration: BoxDecoration( | ||
| color: const Color(0xFF007711), | ||
| borderRadius: BorderRadius.circular(4), | ||
| ), | ||
| child: Row( | ||
| mainAxisSize: MainAxisSize.min, | ||
| children: [ | ||
| const Text( | ||
| '豆', | ||
| style: TextStyle( | ||
| fontSize: 10, | ||
| color: Colors.white, | ||
| fontWeight: FontWeight.bold, | ||
| ), | ||
| ), | ||
| const SizedBox(width: 3), | ||
| Text( | ||
| '${torrent.doubanRating}', | ||
| style: const TextStyle( | ||
| fontSize: 11, | ||
| color: Colors.white, | ||
| fontWeight: FontWeight.w600, | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| const SizedBox(width: 6), | ||
| ], | ||
| // IMDB评分 | ||
| if (hasImdb) ...[ | ||
| Container( | ||
| padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), | ||
| decoration: BoxDecoration( | ||
| color: const Color(0xFFF5C518), | ||
| borderRadius: BorderRadius.circular(4), | ||
| ), | ||
| child: Row( | ||
| mainAxisSize: MainAxisSize.min, | ||
| children: [ | ||
| const Text( | ||
| 'IMDb', | ||
| style: TextStyle( | ||
| fontSize: 9, | ||
| color: Colors.black, | ||
| fontWeight: FontWeight.bold, | ||
| ), | ||
| ), | ||
| const SizedBox(width: 3), | ||
| Text( | ||
| '${torrent.imdbRating}', | ||
| style: const TextStyle( | ||
| fontSize: 11, | ||
| color: Colors.black, | ||
| fontWeight: FontWeight.w600, | ||
| ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| final result = _parseTorrentSearchResult( | ||
| searchData, | ||
| historyMap: historyMap, | ||
| peerMap: peerMap, | ||
| ); | ||
| return result; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1. 添加封面图片 Future 缓存机制,避免重复加载相同图片 2. 实现 `_initCoverImageFuture` 方法,仅在 URL 变化时才重新创建加载任务 3. 修改图片渲染逻辑,使用缓存的 Future 替代每次渲染时创建新的请求
|
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
本次 PR 增加了列表页的下拉刷新功能和详情页的种子信息概览模块,整体实现良好。我对详情页的代码实现提出了一些改进建议,主要涉及提升代码复用性、增强空值处理的健壮性以及移除冗余和调试代码,以提高代码质量和可维护性。具体建议请见评论。
lib/pages/torrent_detail_page.dart
Outdated
| _coverImageFuture = ImageHttpClient.instance | ||
| .fetchImage(coverUrl) | ||
| .then((response) => response.data!); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
在 _initCoverImageFuture 方法中,使用了 response.data! 来强制解包。如果 response.data 为 null,这会抛出一个运行时错误。虽然 FutureBuilder 会捕获这个错误,但更好的做法是显式地处理 null 情况,抛出一个更有意义的异常,这样能让错误处理更清晰。
| _coverImageFuture = ImageHttpClient.instance | |
| .fetchImage(coverUrl) | |
| .then((response) => response.data!); | |
| _coverImageFuture = ImageHttpClient.instance | |
| .fetchImage(coverUrl) | |
| .then((response) { | |
| if (response.data == null) { | |
| throw Exception('Image data is null for url: $coverUrl'); | |
| } | |
| return response.data!; | |
| }); |
| String _formatSize(int bytes) { | ||
| if (bytes < 1024) return '$bytes B'; | ||
| if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(2)} KB'; | ||
| if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB'; | ||
| return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB'; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_formatSize 方法是一个通用的文件大小格式化函数。为了提高代码的复用性并保持代码整洁,建议将此方法移至 lib/utils/format.dart 文件中的 Formatters 类,并作为一个公共的静态方法。这样其他组件也可以复用它。
例如,在 Formatters 类中添加:
static String formatSizeWithUnit(int bytes) {
if (bytes < 1024) return '$bytes B';
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(2)} KB';
if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB';
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB';
}然后在此处删除 _formatSize 方法,并在调用处(如 862 行)使用 Formatters.formatSizeWithUnit()。
| try { | ||
| final endDateTime = DateTime.parse(endTime); | ||
| final now = DateTime.now(); | ||
| final hoursLeft = endDateTime.difference(now).inHours; | ||
| if (hoursLeft > 0) return '$baseText ${hoursLeft}h'; | ||
| } catch (e) { /* 解析失败 */ } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
在 _discountText 方法中,catch (e) { /* 解析失败 */ } 捕获了 DateTime.parse 可能抛出的异常,但没有做任何处理。这会导致在 endTime 格式不正确时静默失败,难以调试。建议在 kDebugMode 下打印错误日志,以便于发现和定位问题。
try {
final endDateTime = DateTime.parse(endTime);
final now = DateTime.now();
final hoursLeft = endDateTime.difference(now).inHours;
if (hoursLeft > 0) return '$baseText ${hoursLeft}h';
} catch (e) {
if (kDebugMode) {
print('Failed to parse discount end time: $endTime, error: $e');
}
}| final coverUrl = torrent.cover.isNotEmpty | ||
| ? torrent.cover | ||
| : (torrent.imageList.isNotEmpty ? torrent.imageList.first : ''); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // 记录前3条评论用于调试 | ||
| for (var i = 0; i < comments.comments.length && i < 3; i++) { | ||
| final comment = comments.comments[i]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
本次PR在列表页增加了下拉刷新功能,并在详情页增加了种子信息概览模块,功能实现完整。代码整体质量不错,特别是在详情页UI构建方面。我发现了一些可以改进的地方,主要集中在 lib/pages/torrent_detail_page.dart 文件中,包括代码重复、硬编码颜色和残留的调试代码等。具体的建议请见我的评论。
| String _formatSize(int bytes) { | ||
| if (bytes < 1024) return '$bytes B'; | ||
| if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(2)} KB'; | ||
| if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB'; | ||
| return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB'; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| try { | ||
| final endDateTime = DateTime.parse(endTime); | ||
| final now = DateTime.now(); | ||
| final hoursLeft = endDateTime.difference(now).inHours; | ||
| if (hoursLeft > 0) return '$baseText ${hoursLeft}h'; | ||
| } catch (e) { /* 解析失败 */ } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
此处的 catch 块静默地捕获了异常,这可能会隐藏 endTime 字符串格式错误等潜在问题,使调试变得困难。建议至少在开发环境中记录异常信息,以便于问题的排查。
try {
final endDateTime = DateTime.parse(endTime);
final now = DateTime.now();
final hoursLeft = endDateTime.difference(now).inHours;
if (hoursLeft > 0) return '$baseText ${hoursLeft}h';
} catch (e, s) {
debugPrint('Failed to parse discount end time: $endTime, error: $e, stack: $s');
}| final coverUrl = torrent.cover.isNotEmpty | ||
| ? torrent.cover | ||
| : (torrent.imageList.isNotEmpty ? torrent.imageList.first : ''); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Icon(Icons.upload_rounded, color: Colors.green.shade600, size: 14), | ||
| const SizedBox(width: 2), | ||
| Text( | ||
| '${torrent.seeders}', | ||
| style: TextStyle( | ||
| fontSize: 12, | ||
| fontWeight: FontWeight.w600, | ||
| color: Colors.green.shade700, | ||
| ), | ||
| ), | ||
| const SizedBox(width: 8), | ||
| // 下载数 | ||
| Icon(Icons.download_rounded, color: Colors.orange.shade600, size: 14), | ||
| const SizedBox(width: 2), | ||
| Text( | ||
| '${torrent.leechers}', | ||
| style: TextStyle( | ||
| fontSize: 12, | ||
| fontWeight: FontWeight.w600, | ||
| color: Colors.orange.shade700, | ||
| ), | ||
| ), | ||
| const SizedBox(width: 10), | ||
| // 分隔线 | ||
| Container( | ||
| width: 1, | ||
| height: 12, | ||
| color: colorScheme.outline.withValues(alpha: 0.3), | ||
| ), | ||
| const SizedBox(width: 10), | ||
| // 文件大小 | ||
| Icon(Icons.storage_rounded, color: colorScheme.primary, size: 14), | ||
| const SizedBox(width: 4), | ||
| Text( | ||
| _formatSize(torrent.sizeBytes), | ||
| style: TextStyle( | ||
| fontSize: 12, | ||
| fontWeight: FontWeight.w600, | ||
| color: colorScheme.onSurface, | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| const SizedBox(height: 8), | ||
| // 评分和时间行 | ||
| Row( | ||
| children: [ | ||
| // 豆瓣评分 | ||
| if (hasDouban) ...[ | ||
| Container( | ||
| padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), | ||
| decoration: BoxDecoration( | ||
| color: const Color(0xFF007711), | ||
| borderRadius: BorderRadius.circular(4), | ||
| ), | ||
| child: Row( | ||
| mainAxisSize: MainAxisSize.min, | ||
| children: [ | ||
| const Text( | ||
| '豆', | ||
| style: TextStyle( | ||
| fontSize: 10, | ||
| color: Colors.white, | ||
| fontWeight: FontWeight.bold, | ||
| ), | ||
| ), | ||
| const SizedBox(width: 3), | ||
| Text( | ||
| '${torrent.doubanRating}', | ||
| style: const TextStyle( | ||
| fontSize: 11, | ||
| color: Colors.white, | ||
| fontWeight: FontWeight.w600, | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| const SizedBox(width: 6), | ||
| ], | ||
| // IMDB评分 | ||
| if (hasImdb) ...[ | ||
| Container( | ||
| padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), | ||
| decoration: BoxDecoration( | ||
| color: const Color(0xFFF5C518), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // 记录前3条评论用于调试 | ||
| for (var i = 0; i < comments.comments.length && i < 3; i++) { | ||
| final comment = comments.comments[i]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
原则上一个pr只针对一个功能,另外比较大的功能变化请先发issue讨论。 |
# Conflicts: # lib/app.dart
增加了列表页下拉刷新和详情页种子信息模块
