This commit is contained in:
Levi committed 2025-09-29 20:13:30 +08:00
1 parent 2ab5a4bb53
commit 8835c2855a
11 files changed
+851 -47

No files matched your search

@@ -1,60 +0,0 @@
import 'package:flutter/material.dart';
class GradientButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
final IconData? icon; // 添加可选图标参数
const GradientButton({
super.key,
required this.text,
required this.onPressed,
this.icon,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 120,
height: 40,
child: ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(143),
),
padding: EdgeInsets.zero,
backgroundColor: Colors.transparent,
),
child: Ink(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF3ACBBE), Color(0xFF2CDB90)],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
borderRadius: BorderRadius.all(Radius.circular(143)),
),
child: Container(
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (icon != null) Icon(icon, size: 16.0, color: Colors.white),
if (icon != null) const SizedBox(width: 4.0), // 图标和文本之间的间距
Text(
text,
style: const TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
color: Color(0xFFFFFFFE),
),
),
],
),
),
),
),
);
}
}
@@ -1,5 +1,8 @@
import 'package:flutter/material.dart';
import 'gradient_button.dart';
import '../../../components/gp_gradient_button.dart';
import '../../../components/gp_outline_button.dart';
import '../../../components/gp_icon_button.dart';
import '../widgets/task_item.dart';
import 'task_tabs.dart';
class TaskScreen extends StatefulWidget {
@@ -32,7 +35,7 @@ class _TaskScreenState extends State<TaskScreen> {
padding: const EdgeInsets.only(left: 16.0, top: 16.0),
child: Row(
children: [
GradientButton(
GpGradientButton(
text: '创建任务',
icon: Icons.add, // 添加加号图标
onPressed: () {
@@ -49,7 +52,37 @@ class _TaskScreenState extends State<TaskScreen> {
],
),
),
const SizedBox(height: 20),
const SizedBox(height: 16),
// 操作按钮列表
Padding(
padding: const EdgeInsets.only(right: 24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
GpOutlineButton(
icon: 'assets/icons/pause.png',
onTap: () {
// 处理开始按钮点击事件
},
),
const SizedBox(width: 12),
GpOutlineButton(
icon: 'assets/icons/pause.png',
onTap: () {
// 处理暂停按钮点击事件
},
),
const SizedBox(width: 12),
GpOutlineButton(
icon: 'assets/icons/pause.png',
onTap: () {
// 处理删除按钮点击事件
},
),
],
),
),
const SizedBox(height: 16),
Expanded(child: _buildTaskList(_selectedTabIndex)),
],
),
@@ -60,16 +93,126 @@ class _TaskScreenState extends State<TaskScreen> {
// 根据选中的标签页显示不同的任务列表
switch (tabIndex) {
case 0:
return const Center(
child: Text('下载中的任务列表', style: TextStyle(color: Colors.white)),
return ListView.separated(
itemCount: 3, // 示例显示3个任务项
separatorBuilder: (context, index) => const SizedBox(height: 12),
itemBuilder:
(context, index) => TaskItem(
icon: Icons.download, // 添加下载图标
taskName: '下载任务 ${index + 1}', // 添加任务名称
progress: (index + 1) * 0.25, // 添加进度值 (0.25, 0.5, 0.75)
fileSizeText:
'${(2.1 + index * 0.1).toStringAsFixed(1)}MB/2.4MB', // 示例文件大小
bottomRightIcon: Icons.speed, // 添加速度图标
bottomRightGradientText:
'${(1.5 + index * 0.3).toStringAsFixed(1)}MB/s', // 下载速度
bottomRightStatusText: '下载中', // 状态文本
actionButtons: [
GpIconButton(
icon: Icons.pause,
onTap: () => print('暂停任务 $index'),
),
GpIconButton(
icon: Icons.delete,
onTap: () => print('删除任务 $index'),
),
GpIconButton(
icon: Icons.folder_open,
onTap: () => print('打开所在位置 $index'),
),
GpIconButton(
icon: Icons.info,
onTap: () => print('查看信息 $index'),
),
GpIconButton(
icon: Icons.more_vert,
onTap: () => print('更多操作 $index'),
),
],
onTap: () {
print('任务项 $index 被点击');
},
),
);
case 1:
return const Center(
child: Text('已完成的任务列表', style: TextStyle(color: Colors.white)),
return ListView.separated(
itemCount: 2, // 示例显示2个任务项
separatorBuilder: (context, index) => const SizedBox(height: 12),
itemBuilder:
(context, index) => TaskItem(
icon: Icons.check_circle, // 添加完成图标
taskName: '已完成任务 ${index + 1}', // 添加任务名称
progress: 1.0, // 已完成任务进度为100%
fileSizeText: '2.4MB/2.4MB - 已完成', // 添加完成信息
bottomRightIcon: Icons.check, // 添加完成图标
bottomRightGradientText: '完成时间', // 完成时间文本
bottomRightStatusText: '已完成', // 状态文本
actionButtons: [
GpIconButton(
icon: Icons.pause,
onTap: () => print('暂停任务 $index'),
),
GpIconButton(
icon: Icons.delete,
onTap: () => print('删除任务 $index'),
),
GpIconButton(
icon: Icons.folder_open,
onTap: () => print('打开所在位置 $index'),
),
GpIconButton(
icon: Icons.info,
onTap: () => print('查看信息 $index'),
),
GpIconButton(
icon: Icons.more_vert,
onTap: () => print('更多操作 $index'),
),
],
onTap: () {
print('已完成任务项 $index 被点击');
},
),
);
case 2:
return const Center(
child: Text('失败的任务列表', style: TextStyle(color: Colors.white)),
return ListView.separated(
itemCount: 1, // 示例显示1个任务项
separatorBuilder: (context, index) => const SizedBox(height: 12),
itemBuilder:
(context, index) => TaskItem(
icon: Icons.error, // 添加错误图标
taskName: '失败任务 ${index + 1}', // 添加任务名称
progress: 0.3, // 失败任务的部分进度
fileSizeText: '1.2MB/4.0MB - 任务失败', // 添加失败信息
bottomRightIcon: Icons.error_outline, // 添加错误图标
bottomRightGradientText: '重试', // 重试文本
bottomRightStatusText: '失败', // 状态文本
actionButtons: [
GpIconButton(
icon: Icons.pause,
onTap: () => print('暂停任务 $index'),
),
GpIconButton(
icon: Icons.delete,
onTap: () => print('删除任务 $index'),
),
GpIconButton(
icon: Icons.folder_open,
onTap: () => print('打开所在位置 $index'),
),
GpIconButton(
icon: Icons.info,
onTap: () => print('查看信息 $index'),
),
GpIconButton(
icon: Icons.more_vert,
onTap: () => print('更多操作 $index'),
),
],
onTap: () {
print('失败任务项 $index 被点击');
},
),
);
default:
return Container();
@@ -0,0 +1,491 @@
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,
);
}
}