Files
AI-Health/health_app/lib/widgets/common_widgets.dart
MingNian 4f1ad82345 fix: 欢迎卡片UI优化 + 蓝牙录入按钮 + 多处修复
- 欢迎卡片:去掉白框圆角错位、图片满宽、按钮去箭头改圆角居中
- 渐变actionOutlineGradient改紫蓝双色循环
- 新增健康Agent"蓝牙录入"按钮→蓝牙设备页
- "查看健康情况"改名"健康概览"
- 饮食编辑框删除bug修复(控制器缓存)
- 启动脚本加adb reverse自动转发
- 登录闪屏修复
2026-06-17 10:58:59 +08:00

225 lines
6.0 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 '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),
);
}
}