Files
gopeed/ui/flutter_v2/lib/modules/task/widgets/task_item.dart
T
2025-09-29 20:13:30 +08:00

492 lines
18 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:ui';
import 'package:flutter/material.dart';
/// TaskItem - 任务项组件
///
/// 一个带有毛玻璃效果和渐变边框的任务项容器,宽度自适应父组件
class TaskItem extends StatelessWidget {
/// 左侧图标
final dynamic icon;
/// 任务名称
final String? taskName;
/// 进度值 (0.0 - 1.0)
final double? progress;
/// 右上角操作按钮列表
final List<Widget>? actionButtons;
/// 容器高度,默认106px
final double height;
/// 圆角大小,默认8px
final double borderRadius;
/// 点击回调
final VoidCallback? onTap;
/// 文件大小文本(如2.1MB/2.4MB)
final String? fileSizeText;
/// 右下角图标
final IconData? bottomRightIcon;
/// 右下角渐变文本
final String? bottomRightGradientText;
/// 右下角状态文本
final String? bottomRightStatusText;
const TaskItem({
super.key,
this.icon,
this.taskName,
this.progress,
this.actionButtons,
this.height = 106.0,
this.borderRadius = 8.0,
this.onTap,
this.fileSizeText,
this.bottomRightIcon,
this.bottomRightGradientText,
this.bottomRightStatusText,
});
@override
Widget build(BuildContext context) {
return MouseRegion(
cursor:
onTap != null ? SystemMouseCursors.click : SystemMouseCursors.basic,
child: GestureDetector(
onTap: onTap,
child: Container(
height: height,
child: Stack(
children: [
// Rectangle 82: 主容器 (最底层)
Positioned.fill(
child: ClipRRect(
borderRadius: BorderRadius.circular(borderRadius),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 24.0, sigmaY: 24.0),
child: Container(
decoration: BoxDecoration(
// 背景色:rgba(178, 231, 254, 0.08)
color: const Color(0xFFB2E7FE).withOpacity(0.08),
borderRadius: BorderRadius.circular(borderRadius),
border: Border.all(
width: 0.5,
color: Colors.transparent,
),
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius),
// 渐变边框效果
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
stops: [0.17, 0.92],
colors: [
Colors.transparent,
Color(0x29FFFFFF), // rgba(255, 255, 255, 0.16)
],
),
),
),
),
),
),
),
// 蒙版组 (中间层) - 简化测试
Positioned.fill(
child: ClipRRect(
borderRadius: BorderRadius.circular(borderRadius),
child: Stack(
children: [
// Rectangle 80: 渐变模糊背景 - 只保留这个
Positioned(
left: 0,
right: 0,
top: 7.23,
child: ImageFiltered(
imageFilter: ImageFilter.blur(
sigmaX: 40.0,
sigmaY: 40.0,
),
child: Container(
height: 90.34,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius),
gradient: const LinearGradient(
begin: Alignment.centerLeft, // 90deg = 从左到右
end: Alignment.centerRight,
stops: [0.0, 1.0],
colors: [
Color(0xFF2D3975), // #2D3975 at 0%
Color(0xFF266682), // #266682 at 100%
],
),
),
),
),
),
// Rectangle 81: 纯色背景蒙版 - 图层样式穿透,所以注释掉
// 设计稿中标明"图层样式穿透",意味着这层不影响最终视觉效果
// Positioned(
// left: 0,
// right: 0,
// top: 0,
// child: Container(
// height: height,
// decoration: BoxDecoration(
// color: const Color(0xFFB2E7FE),
// borderRadius: BorderRadius.circular(borderRadius),
// ),
// ),
// ),
],
),
),
),
// 内容层 (最上层)
Positioned.fill(
child: Container(
padding: const EdgeInsets.all(16.0), // 统一的16px内边距
child: Row(
children: [
// 左侧图标区域
if (icon != null)
Container(
margin: const EdgeInsets.only(
right: 2.0,
), // 图标右边间隔2px
child: _buildIcon(),
),
// 右侧内容容器
Expanded(
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 任务名称区域 (与操作按钮在同一行)
Row(
children: [
// 任务名称
if (taskName != null)
Expanded(
child: Container(
height: 22.0, // 设计稿高度22px
child: Align(
alignment:
Alignment
.bottomLeft, // align-items: flex-end
child: Text(
taskName!,
style: const TextStyle(
fontSize: 16.0, // 16px
fontWeight:
FontWeight.bold, // bold
height:
22.0 /
16.0, // line-height: 22px
color: Color(
0xFFFFFFFE,
), // #FFFFFE
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
),
// 右侧操作按钮列表
if (actionButtons != null &&
actionButtons!.isNotEmpty)
Row(
mainAxisSize: MainAxisSize.min,
children: _buildActionButtons(),
),
],
),
// 进度条
if (progress != null)
Container(
margin: const EdgeInsets.only(
top: 20.0,
), // 距离名称下面20px
child: _buildProgressBar(),
),
// 文件大小文本和右下角元素行
Container(
margin: const EdgeInsets.only(
top: 8.0,
), // 距离进度条下面8px
height: 16.0, // 直接指定高度为16px
child: Row(
crossAxisAlignment:
CrossAxisAlignment.center, // 垂直居中对齐
children: [
// 左侧文件大小文本
if (fileSizeText != null &&
fileSizeText!.isNotEmpty)
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: Text(
fileSizeText!,
style: const TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500,
color: Color(
0xFFCCCCCC,
), // #CCCCCC
letterSpacing: 0.0,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
// 右下角元素组
_buildBottomRightElements(),
],
),
),
],
),
),
),
],
),
),
),
],
),
),
),
);
}
/// 构建图标组件
Widget _buildIcon() {
if (icon == null) return const SizedBox.shrink();
// 如果是字符串,认为是资源路径
if (icon is String) {
return Image.asset(
icon as String,
width: 32.0,
height: 32.0,
fit: BoxFit.contain,
);
}
// 如果是IconData,使用Icon组件
if (icon is IconData) {
return Icon(icon as IconData, size: 32.0, color: Colors.white);
}
// 如果是Widget,直接使用
if (icon is Widget) {
return SizedBox(width: 32.0, height: 32.0, child: icon as Widget);
}
return const SizedBox.shrink();
}
/// 构建操作按钮列表
List<Widget> _buildActionButtons() {
if (actionButtons == null || actionButtons!.isEmpty) {
return [];
}
List<Widget> buttons = [];
for (int i = 0; i < actionButtons!.length; i++) {
// 添加按钮
buttons.add(
SizedBox(width: 20.0, height: 20.0, child: actionButtons![i]),
);
// 添加间隔 (除了最后一个按钮)
if (i < actionButtons!.length - 1) {
buttons.add(const SizedBox(width: 16.0));
}
}
return buttons;
}
/// 构建进度条
Widget _buildProgressBar() {
if (progress == null) return const SizedBox.shrink();
return LayoutBuilder(
builder: (context, constraints) {
// 使用所有可用宽度,因为已经有统一的16px内边距
final availableWidth = constraints.maxWidth;
final progressWidth = availableWidth * progress!;
return SizedBox(
height: 8.0,
child: Stack(
children: [
// 底部背景条
Container(
width: availableWidth,
height: 8.0,
decoration: BoxDecoration(
color: const Color(0xFFEBEDF3), // #EBEDF3
borderRadius: BorderRadius.circular(548.0), // 548px圆角
),
),
// 进度条
Container(
width: progressWidth,
height: 8.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(143.0), // 143px圆角
gradient: const LinearGradient(
begin: Alignment.centerLeft, // 90deg
end: Alignment.centerRight,
stops: [0.0, 0.44, 0.99],
colors: [
Color(0xC93ACBBE), // rgba(58, 203, 190, 0.79)
Color(0xFF3ACBBE), // #3ACBBE at 44%
Color(0xFF2CDB90), // #2CDB90 at 99%
],
),
border: Border.all(width: 1.0, color: Colors.transparent),
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(143.0),
// 渐变边框效果
gradient: const LinearGradient(
begin: Alignment.topCenter, // 180deg
end: Alignment.bottomCenter,
colors: [
Color(0xFF39CBBC), // #39CBBC at 0%
Color(0xFF2CDA92), // #2CDA92 at 100%
],
),
),
child: Container(
margin: const EdgeInsets.all(1.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(142.0),
gradient: const LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
stops: [0.0, 0.44, 0.99],
colors: [
Color(0xC93ACBBE), // rgba(58, 203, 190, 0.79)
Color(0xFF3ACBBE), // #3ACBBE at 44%
Color(0xFF2CDB90), // #2CDB90 at 99%
],
),
),
),
),
),
],
),
);
},
);
}
/// 构建右下角元素组
Widget _buildBottomRightElements() {
// 如果没有任何右下角元素,返回空
if (bottomRightIcon == null &&
bottomRightGradientText == null &&
bottomRightStatusText == null) {
return const SizedBox.shrink();
}
List<Widget> children = [];
// 添加图标
if (bottomRightIcon != null) {
children.add(Icon(bottomRightIcon!, size: 16.0, color: Colors.white));
}
// 添加渐变文本
if (bottomRightGradientText != null &&
bottomRightGradientText!.isNotEmpty) {
if (children.isNotEmpty) {
children.add(const SizedBox(width: 4.0)); // 间隔4px
}
children.add(
ShaderMask(
shaderCallback:
(bounds) => const LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
transform: GradientRotation(272 * 3.14159 / 180), // 272度转弧度
stops: [0.73, 1.22],
colors: [
Color(0xFF39CDB9), // #39CDB9 at 73%
Color(0xFF2EDA94), // #2EDA94 at 122%
],
).createShader(bounds),
child: Text(
bottomRightGradientText!,
style: const TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500,
color: Colors.white, // 这个颜色会被shader覆盖
letterSpacing: 0.0,
height: 1.0, // 设置行高为1,减少额外空间
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
);
}
// 添加状态文本
if (bottomRightStatusText != null && bottomRightStatusText!.isNotEmpty) {
if (children.isNotEmpty) {
children.add(const SizedBox(width: 12.0)); // 间隔12px
}
children.add(
Text(
bottomRightStatusText!,
style: const TextStyle(
fontFamily: 'Source Han Sans',
fontSize: 14.0,
fontWeight: FontWeight.w500,
color: Color(0xFFCCCCCC), // #CCCCCC
letterSpacing: 0.0,
height: 1.0, // 设置行高为1,减少额外空间
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
);
}
return Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, // 垂直居中对齐
textBaseline: TextBaseline.alphabetic, // 统一文本基线
children: children,
);
}
}