Skip to content

Conversation

@mjnhmd
Copy link
Contributor

@mjnhmd mjnhmd commented Dec 17, 2025

增加了列表页下拉刷新和详情页种子信息模块
image

@gemini-code-assist
Copy link
Contributor

Summary of Changes

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

  • 列表页下拉刷新: 在列表页增加了下拉刷新功能,用户现在可以通过下拉手势刷新种子列表数据,提升了数据获取的便捷性。
  • 种子详情概览模块: 在种子详情页新增了一个全面的概览模块,集中展示了种子的封面、副标题、优惠标签、自定义标签、做种/下载数、文件大小、豆瓣/IMDb评分以及发布时间等关键信息,使得详情页内容更加丰富和直观。
  • 评论加载调试增强: 为详情页的评论加载逻辑添加了详细的调试打印信息,包括加载开始、成功及异常情况,并打印了部分评论的简要信息,便于问题排查和监控。
  • API适配器优化: 优化了API适配器中 _parseTorrentSearchResult 方法的返回值处理方式,将其先赋值给一个局部变量再返回,提高了代码可读性。
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a 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.

Comment on lines 682 to 685
? FutureBuilder<List<int>>(
future: ImageHttpClient.instance
.fetchImage(coverUrl)
.then((response) => response.data!),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

build 方法中直接创建 Future 会导致性能问题。每次Widget重建时(例如调用 setState),都会重新创建一个新的 Future,从而触发不必要的网络请求来重新获取图片。

为了优化性能,应该在 initState 中初始化 Future 并将其存储在状态变量中,然后在 FutureBuilder 中使用该变量。

Comment on lines +580 to +586
// 格式化文件大小
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';
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这部分代码与 lib/utils/format.dart 文件中的 FormatUtil.formatFileSize 功能重复。在Widget状态类中创建可复用的辅助方法不是一个好习惯,因为它会导致代码重复和维护困难。

建议将此方法移至 lib/utils/format.dart 中,作为一个新的静态方法或通过为现有方法添加精度参数来增强它。这样可以促进代码复用并提高可维护性。

final now = DateTime.now();
final hoursLeft = endDateTime.difference(now).inHours;
if (hoursLeft > 0) return '$baseText ${hoursLeft}h';
} catch (e) { /* 解析失败 */ }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

try-catch 块中的 catch 为空,这会静默地忽略异常,使得在日期格式不正确时难以调试。建议至少在调试模式下记录错误信息。

Suggested change
} catch (e) { /* 解析失败 */ }
} catch (e, s) { if (kDebugMode) { print('Failed to parse discount end time: $e\n$s'); } /* 解析失败 */ }

Comment on lines 638 to 957
/// 构建种子信息概览卡片
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,
),
),
],
),
],
),
],
),
],
),
),
],
),
),
),
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这个 _buildInfoOverviewCard 方法过于庞大和复杂,降低了代码的可读性和可维护性。将UI构建逻辑拆分成更小的、独立的Widget是一种更好的做法。

建议将此方法重构为多个独立的 StatelessWidget,例如 _CoverImage, _TorrentInfoSection, _TorrentStatsBar 等。这样做不仅能让代码更清晰,还能让Flutter更精细地控制Widget的重建,从而可能提升性能。

Comment on lines +811 to +918
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,
),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

_buildInfoOverviewCard 中存在多处硬编码的颜色(例如 Colors.green.shade600, Color(0xFF007711) 等)。这使得UI与应用整体主题不一致,并且难以适配深色/浅色模式。

建议使用当前主题的颜色,通过 Theme.of(context).colorScheme 获取。对于品牌相关的颜色(如豆瓣、IMDb),建议将它们定义为常量,而不是在UI代码中内联硬编码。

Comment on lines +303 to +308
final result = _parseTorrentSearchResult(
searchData,
historyMap: historyMap,
peerMap: peerMap,
);
return result;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这里引入了一个不必要的局部变量 result,这通常是为了调试方便。为了代码的简洁性,建议直接返回函数调用的结果。

Suggested change
final result = _parseTorrentSearchResult(
searchData,
historyMap: historyMap,
peerMap: peerMap,
);
return result;
return _parseTorrentSearchResult(
searchData,
historyMap: historyMap,
peerMap: peerMap,
);

1. 添加封面图片 Future 缓存机制,避免重复加载相同图片
2. 实现 `_initCoverImageFuture` 方法,仅在 URL 变化时才重新创建加载任务
3. 修改图片渲染逻辑,使用缓存的 Future 替代每次渲染时创建新的请求
@mjnhmd
Copy link
Contributor Author

mjnhmd commented Dec 17, 2025

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

本次 PR 增加了列表页的下拉刷新功能和详情页的种子信息概览模块,整体实现良好。我对详情页的代码实现提出了一些改进建议,主要涉及提升代码复用性、增强空值处理的健壮性以及移除冗余和调试代码,以提高代码质量和可维护性。具体建议请见评论。

Comment on lines 594 to 596
_coverImageFuture = ImageHttpClient.instance
.fetchImage(coverUrl)
.then((response) => response.data!);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

_initCoverImageFuture 方法中,使用了 response.data! 来强制解包。如果 response.datanull,这会抛出一个运行时错误。虽然 FutureBuilder 会捕获这个错误,但更好的做法是显式地处理 null 情况,抛出一个更有意义的异常,这样能让错误处理更清晰。

Suggested change
_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!;
});

Comment on lines +600 to +605
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';
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

_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()

Comment on lines +624 to +629
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) { /* 解析失败 */ }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

_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');
        }
      }

Comment on lines +666 to +668
final coverUrl = torrent.cover.isNotEmpty
? torrent.cover
: (torrent.imageList.isNotEmpty ? torrent.imageList.first : '');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

_buildInfoOverviewCard 方法中,coverUrl 变量被定义但从未被使用。这部分逻辑与 initState 中初始化封面图片的逻辑重复了。建议移除这个未使用的变量以保持代码简洁。

Comment on lines +1021 to +1024
// 记录前3条评论用于调试
for (var i = 0; i < comments.comments.length && i < 3; i++) {
final comment = comments.comments[i];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这段 for 循环(// 记录前3条评论用于调试)似乎是用于调试的临时代码,但在循环体中并未执行任何有效操作。建议在提交前移除这类调试代码,以保持代码库的整洁。

@mjnhmd
Copy link
Contributor Author

mjnhmd commented Dec 18, 2025

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a 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 文件中,包括代码重复、硬编码颜色和残留的调试代码等。具体的建议请见我的评论。

Comment on lines +605 to +610
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';
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

_formatSize 方法的功能与 lib/utils/format.dart 文件中的 FormatUtil.formatFileSize 方法几乎完全相同,造成了代码重复。建议移除此方法,并直接使用 FormatUtil.formatFileSize。如果需要不同的格式化精度,可以考虑扩展现有的工具函数。

为了保持代码的统一性和可维护性,请尽量使用公共的工具函数。

Comment on lines +629 to +634
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) { /* 解析失败 */ }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

此处的 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');
      }

Comment on lines +671 to +673
final coverUrl = torrent.cover.isNotEmpty
? torrent.cover
: (torrent.imageList.isNotEmpty ? torrent.imageList.first : '');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

局部变量 coverUrl 在此被赋值但从未被使用。获取封面图片的逻辑已在 initState 方法中处理,并赋值给了 _coverImageFuture。建议移除这几行冗余代码。

Comment on lines +833 to +919
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),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

此组件中多处使用了硬编码的颜色值,例如 Colors.green.shade600const Color(0xFF007711) 等。这使得组件在不同主题(尤其是暗色模式)下的表现可能不佳,也降低了代码的可维护性。

建议:

  1. 对于与主题相关的颜色(如做种/下载数),尽量使用 Theme.of(context).colorScheme 中的语义化颜色,例如 colorScheme.primary
  2. 对于品牌颜色(如豆瓣、IMDb),建议将它们定义为常量,并在一个统一的地方管理。

Comment on lines +1026 to +1029
// 记录前3条评论用于调试
for (var i = 0; i < comments.comments.length && i < 3; i++) {
final comment = comments.comments[i];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这部分代码似乎是用于调试的,循环体为空,并且局部变量 comment 未被使用。建议在合并前移除这些残留的调试代码。

@JustLookAtNow
Copy link
Owner

原则上一个pr只针对一个功能,另外比较大的功能变化请先发issue讨论。
1.辛苦不同功能拆分下pr
2.能否解释下详情页这个种子详情有什么用么?不都是从外面列表点进来的,如果能展示比列表更多的信息那就罢了,现在看起来跟列表中的信息一样呀!如果能把媒体详情提取到这里就好了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants