- 欢迎卡片:去掉白框圆角错位、图片满宽、按钮去箭头改圆角居中 - 渐变actionOutlineGradient改紫蓝双色循环 - 新增健康Agent"蓝牙录入"按钮→蓝牙设备页 - "查看健康情况"改名"健康概览" - 饮食编辑框删除bug修复(控制器缓存) - 启动脚本加adb reverse自动转发 - 登录闪屏修复
225 lines
6.0 KiB
Dart
225 lines
6.0 KiB
Dart
import 'package:flutter/material.dart';
|
||
import '../core/app_colors.dart';
|
||
|
||
/// 渐变边框按钮(白色底+彩色渐变边框)
|
||
class GradientBorderButton extends StatelessWidget {
|
||
final String text;
|
||
final IconData? icon;
|
||
final VoidCallback? onTap;
|
||
final double height;
|
||
final bool isSuccess;
|
||
|
||
const GradientBorderButton({
|
||
super.key,
|
||
required this.text,
|
||
this.icon,
|
||
this.onTap,
|
||
this.height = 52,
|
||
this.isSuccess = false,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Container(
|
||
height: height,
|
||
decoration: BoxDecoration(
|
||
gradient: isSuccess
|
||
? AppColors.calmHealthGradient
|
||
: AppColors.primaryGradient,
|
||
borderRadius: BorderRadius.circular(14),
|
||
boxShadow: AppColors.buttonShadow,
|
||
),
|
||
child: Material(
|
||
color: Colors.transparent,
|
||
child: InkWell(
|
||
onTap: onTap,
|
||
borderRadius: BorderRadius.circular(14),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
if (icon != null) ...[
|
||
Icon(icon, size: 22, color: AppColors.textOnGradient),
|
||
const SizedBox(width: 8),
|
||
],
|
||
Text(
|
||
text,
|
||
style: const TextStyle(
|
||
fontSize: 17,
|
||
fontWeight: FontWeight.w600,
|
||
color: AppColors.textOnGradient,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 卡片内的小操作按钮
|
||
class CardActionButton extends StatelessWidget {
|
||
final String label;
|
||
final IconData icon;
|
||
final VoidCallback? onTap;
|
||
final Color? iconColor;
|
||
|
||
const CardActionButton({
|
||
super.key,
|
||
required this.label,
|
||
required this.icon,
|
||
this.onTap,
|
||
this.iconColor,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return InkWell(
|
||
onTap: onTap,
|
||
borderRadius: BorderRadius.circular(12),
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||
decoration: BoxDecoration(
|
||
gradient: AppColors.surfaceGradient,
|
||
borderRadius: BorderRadius.circular(12),
|
||
border: Border.all(color: AppColors.borderLight),
|
||
boxShadow: AppColors.cardShadowLight,
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Icon(icon, size: 18, color: iconColor ?? AppColors.primary),
|
||
const SizedBox(width: 6),
|
||
Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.w500,
|
||
color: AppColors.textPrimary,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 滑动删除组件:左滑露出红色删除背景,达30%阈值锁定,点击确认删除
|
||
/// [margin] 控制卡片之间的间距,red背景与child完全对齐
|
||
class SwipeDeleteTile extends StatefulWidget {
|
||
final Widget child;
|
||
final VoidCallback onDelete;
|
||
final VoidCallback? onTap;
|
||
final EdgeInsetsGeometry margin;
|
||
const SwipeDeleteTile({
|
||
super.key,
|
||
required this.child,
|
||
required this.onDelete,
|
||
this.onTap,
|
||
this.margin = const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||
});
|
||
@override
|
||
State<SwipeDeleteTile> createState() => _SwipeDeleteTileState();
|
||
}
|
||
|
||
class _SwipeDeleteTileState extends State<SwipeDeleteTile>
|
||
with SingleTickerProviderStateMixin {
|
||
double _dx = 0;
|
||
static const _maxSlide = 80.0;
|
||
static const _threshold = 24.0; // 30% of 80
|
||
|
||
void _onDragUpdate(DragUpdateDetails d) {
|
||
setState(() => _dx = (_dx + d.delta.dx).clamp(-_maxSlide, 0.0));
|
||
}
|
||
|
||
void _onDragEnd(DragEndDetails d) {
|
||
if (_dx < -_threshold) {
|
||
setState(() => _dx = -_maxSlide);
|
||
} else {
|
||
setState(() => _dx = 0);
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final isSwiped = _dx < 0;
|
||
return Padding(
|
||
padding: widget.margin,
|
||
child: Stack(
|
||
children: [
|
||
Positioned.fill(
|
||
child: Container(
|
||
decoration: BoxDecoration(
|
||
color: AppColors.error,
|
||
borderRadius: BorderRadius.circular(16),
|
||
),
|
||
child: const Align(
|
||
alignment: Alignment.centerRight,
|
||
child: Padding(
|
||
padding: EdgeInsets.only(right: 20),
|
||
child: Icon(
|
||
Icons.delete_outline,
|
||
color: AppColors.textOnGradient,
|
||
size: 28,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
GestureDetector(
|
||
behavior: isSwiped
|
||
? HitTestBehavior.opaque
|
||
: HitTestBehavior.deferToChild,
|
||
onTap: isSwiped
|
||
? () {
|
||
widget.onDelete();
|
||
setState(() => _dx = 0);
|
||
}
|
||
: widget.onTap,
|
||
onHorizontalDragUpdate: _onDragUpdate,
|
||
onHorizontalDragEnd: _onDragEnd,
|
||
child: Transform.translate(
|
||
offset: Offset(_dx, 0),
|
||
child: isSwiped
|
||
? AbsorbPointer(absorbing: true, child: widget.child)
|
||
: widget.child,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 功能区小图标容器
|
||
class IconBox extends StatelessWidget {
|
||
final IconData icon;
|
||
final double size;
|
||
final Color? backgroundColor;
|
||
final Color? iconColor;
|
||
|
||
const IconBox({
|
||
super.key,
|
||
required this.icon,
|
||
this.size = 44,
|
||
this.backgroundColor,
|
||
this.iconColor,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Container(
|
||
width: size,
|
||
height: size,
|
||
decoration: BoxDecoration(
|
||
gradient: backgroundColor == null ? AppColors.calmHealthGradient : null,
|
||
color: backgroundColor,
|
||
borderRadius: BorderRadius.circular(size / 3),
|
||
boxShadow: AppColors.cardShadowLight,
|
||
),
|
||
child: Icon(icon, size: size * 0.55, color: iconColor ?? Colors.white),
|
||
);
|
||
}
|
||
}
|