import 'dart:async'; import 'package:flutter/material.dart'; import '../guid/guid_glyph_shape.dart'; import '../theme/app_theme.dart'; /// Swipeable question card: right submits, left defers (does not resolve). /// /// The center band supports vertical drag as a slider from -10 (down) to 10 (up). class SwipeQuestionTile extends StatefulWidget { const SwipeQuestionTile({ super.key, required this.questionId, required this.onSwipeRight, required this.onSwipeLeft, this.busy = false, }); final String questionId; final Future Function(num answer) onSwipeRight; final Future Function() onSwipeLeft; final bool busy; @override State createState() => _SwipeQuestionTileState(); } class _SwipeQuestionTileState extends State { double _dragOffset = 0; double _verticalOffset = 0; bool _acting = false; static const double _swipeThreshold = 96; static const double _maxVerticalDrag = 120; static const double _sliderMin = -10; static const double _sliderMax = 10; /// Swipe up → +10, swipe down → -10 (linear between). double get _sliderValue => (_verticalOffset / _maxVerticalDrag * _sliderMax).clamp(_sliderMin, _sliderMax); Future _releaseDrag() async { if (_acting || widget.busy) { setState(() => _dragOffset = 0); return; } if (_dragOffset > _swipeThreshold) { setState(() { _acting = true; _dragOffset = MediaQuery.sizeOf(context).width; }); await widget.onSwipeRight(_sliderValue); } else if (_dragOffset < -_swipeThreshold) { setState(() { _acting = true; _dragOffset = -MediaQuery.sizeOf(context).width; }); await widget.onSwipeLeft(); } else if (mounted) { setState(() => _dragOffset = 0); } if (mounted) { setState(() { _acting = false; _dragOffset = 0; }); } } @override Widget build(BuildContext context) { final double width = MediaQuery.sizeOf(context).width; final double progress = (_dragOffset / _swipeThreshold).clamp(-1.0, 1.0); return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return Stack( alignment: Alignment.center, children: [ Positioned.fill( child: DecoratedBox( decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), gradient: LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [ Colors.redAccent.withValues( alpha: 0.15 + 0.35 * (-progress).clamp(0.0, 1.0), ), AppColors.surfaceElevated, AppColors.success.withValues( alpha: 0.15 + 0.35 * progress.clamp(0.0, 1.0), ), ], ), ), ), ), Transform.translate( offset: Offset(_dragOffset, 0), child: GestureDetector( onHorizontalDragUpdate: widget.busy || _acting ? null : (DragUpdateDetails details) { setState(() { _dragOffset += details.delta.dx; _dragOffset = _dragOffset.clamp(-width * 0.55, width * 0.55); }); }, onHorizontalDragEnd: widget.busy || _acting ? null : (_) => unawaited(_releaseDrag()), child: Material( color: AppColors.surfaceElevated, elevation: 4, shadowColor: Colors.black45, borderRadius: BorderRadius.circular(16), child: Container( width: constraints.maxWidth, constraints: const BoxConstraints(minHeight: 220), padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 24, ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), ), child: Stack( alignment: Alignment.center, children: [ Positioned( top: 20, bottom: 20, left: constraints.maxWidth * 0.22, right: constraints.maxWidth * 0.22, child: DecoratedBox( decoration: BoxDecoration( borderRadius: BorderRadius.circular(14), color: Color.lerp( AppColors.surfaceElevated, AppColors.surface, 0.45, ), ), ), ), Positioned( top: 24, bottom: 24, left: constraints.maxWidth * 0.22, right: constraints.maxWidth * 0.22, child: GestureDetector( behavior: HitTestBehavior.opaque, onVerticalDragUpdate: widget.busy || _acting ? null : (DragUpdateDetails details) { setState(() { _verticalOffset -= details.delta.dy; _verticalOffset = _verticalOffset.clamp( -_maxVerticalDrag, _maxVerticalDrag, ); }); }, child: Center( child: Transform.translate( offset: Offset(0, -_verticalOffset), child: QuestionGuidGlyph( guid: widget.questionId, displayValue: _sliderValue, ), ), ), ), ), ], ), ), ), ), ), if (widget.busy) const Positioned.fill( child: ColoredBox( color: Color(0x66000000), child: Center(child: CircularProgressIndicator()), ), ), ], ); }, ); } }