import 'package:flutter/material.dart'; import '../core/app_colors.dart'; import '../core/app_design_tokens.dart'; class AppGradientOutlineButton extends StatelessWidget { final String label; final VoidCallback? onPressed; final bool loading; const AppGradientOutlineButton({ super.key, required this.label, required this.onPressed, this.loading = false, }); @override Widget build(BuildContext context) { final enabled = onPressed != null && !loading; return DecoratedBox( decoration: BoxDecoration( gradient: enabled ? AppColors.actionOutlineGradient : null, color: enabled ? null : AppColors.borderLight, borderRadius: AppRadius.mdBorder, ), child: Padding( padding: const EdgeInsets.all(1.4), child: Material( color: Colors.white, borderRadius: BorderRadius.circular(AppRadius.md - 1.4), clipBehavior: Clip.antiAlias, child: InkWell( onTap: enabled ? onPressed : null, child: SizedBox( height: 50, child: Center( child: loading ? const SizedBox.square( dimension: 19, child: CircularProgressIndicator(strokeWidth: 2), ) : Text( label, style: AppTextStyles.button.copyWith( color: enabled ? AppColors.primaryDark : AppColors.textHint, ), ), ), ), ), ), ), ); } } class AppCreateFab extends StatelessWidget { final VoidCallback onPressed; final String tooltip; final IconData icon; const AppCreateFab({ super.key, required this.onPressed, required this.tooltip, this.icon = Icons.add_rounded, }); @override Widget build(BuildContext context) { return Tooltip( message: tooltip, child: DecoratedBox( key: const ValueKey('app-create-fab-border'), decoration: BoxDecoration( gradient: AppColors.actionOutlineGradient, borderRadius: AppRadius.pillBorder, boxShadow: [ BoxShadow( color: const Color(0xFF101828).withValues(alpha: 0.08), blurRadius: 12, offset: const Offset(0, 5), ), ], ), child: Padding( padding: const EdgeInsets.all(1.5), child: Material( color: Colors.white, borderRadius: AppRadius.pillBorder, clipBehavior: Clip.antiAlias, child: InkWell( onTap: onPressed, child: SizedBox.square( dimension: 53, child: Icon(icon, color: AppColors.primaryDark, size: 27), ), ), ), ), ), ); } } class SwipeDeleteTile extends StatefulWidget { final Widget child; final VoidCallback onDelete; final VoidCallback? onTap; final EdgeInsetsGeometry margin; final BorderRadiusGeometry borderRadius; final bool enabled; const SwipeDeleteTile({ super.key, required this.child, required this.onDelete, this.onTap, this.margin = const EdgeInsets.symmetric(horizontal: 16, vertical: 4), this.borderRadius = const BorderRadius.all(Radius.circular(16)), this.enabled = true, }); @override State createState() => _SwipeDeleteTileState(); } class _SwipeDeleteTileState extends State with SingleTickerProviderStateMixin { double _dx = 0; static const _maxSlide = 80.0; static const _threshold = 24.0; void _onDragUpdate(DragUpdateDetails d) { if (!widget.enabled) return; setState(() => _dx = (_dx + d.delta.dx).clamp(-_maxSlide, 0.0)); } void _onDragEnd(DragEndDetails d) { if (!widget.enabled) return; setState(() => _dx = _dx < -_threshold ? -_maxSlide : 0); } void _close() { if (_dx == 0) return; setState(() => _dx = 0); } void _delete() { _close(); widget.onDelete(); } @override Widget build(BuildContext context) { final isSwiped = _dx < 0; return Padding( padding: widget.margin, child: ClipRRect( borderRadius: widget.borderRadius, child: Stack( children: [ Positioned.fill(child: ColoredBox(color: AppColors.error)), GestureDetector( behavior: HitTestBehavior.opaque, onTap: isSwiped ? _close : widget.onTap, onHorizontalDragUpdate: widget.enabled ? _onDragUpdate : null, onHorizontalDragEnd: widget.enabled ? _onDragEnd : null, child: Transform.translate( offset: Offset(_dx, 0), child: ColoredBox( key: const ValueKey('swipe-delete-content-surface'), color: Colors.white, child: isSwiped ? AbsorbPointer(child: widget.child) : widget.child, ), ), ), if (isSwiped) Positioned.fill( child: Align( alignment: Alignment.centerRight, child: Material( color: Colors.transparent, child: InkWell( key: const ValueKey('swipe-delete-action'), onTap: widget.enabled ? _delete : null, child: const SizedBox( width: _maxSlide, height: double.infinity, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.delete_outline, color: AppColors.textOnGradient, size: 24, ), SizedBox(height: 2), Text( '删除', style: TextStyle( color: AppColors.textOnGradient, fontSize: 12, fontWeight: FontWeight.w600, ), ), ], ), ), ), ), ), ), ], ), ), ); } }