import 'dart:async'; import 'package:flutter/material.dart'; import '../../../../core/infra/player_engine/player_engine.dart'; enum _DragMode { none, brightness, volume, seek } class AndroidGestureLayer extends StatefulWidget { const AndroidGestureLayer({ super.key, required this.enabled, required this.player, required this.onToggleControls, required this.onDoubleTapCenter, required this.onDoubleTapLeft, required this.onDoubleTapRight, required this.onBrightnessChange, required this.onVolumeChange, required this.onVerticalDragEnd, required this.onHorizontalSeekUpdate, required this.onHorizontalSeekStart, required this.onHorizontalSeekEnd, required this.onLongPressStart, required this.onLongPressEnd, }); final bool enabled; final PlayerEngine player; final VoidCallback onToggleControls; final VoidCallback onDoubleTapCenter; final VoidCallback onDoubleTapLeft; final VoidCallback onDoubleTapRight; final ValueChanged onBrightnessChange; final ValueChanged onVolumeChange; final VoidCallback onVerticalDragEnd; final ValueChanged onHorizontalSeekUpdate; final VoidCallback onHorizontalSeekStart; final VoidCallback onHorizontalSeekEnd; final ValueChanged onLongPressStart; final VoidCallback onLongPressEnd; @override State createState() => _AndroidGestureLayerState(); } class _AndroidGestureLayerState extends State { _DragMode _dragMode = _DragMode.none; Offset _panStartPos = Offset.zero; double _cumulativeDx = 0; double _cumulativeDy = 0; Timer? _doubleTapTimer; Offset _lastTapDownPos = Offset.zero; void _handleTapDown(TapDownDetails details) { _lastTapDownPos = details.localPosition; } void _handleTapUp(TapUpDetails details) { final pos = _lastTapDownPos; if (_doubleTapTimer != null) { _doubleTapTimer!.cancel(); _doubleTapTimer = null; widget.onToggleControls(); _executeDoubleTap(pos); } else { widget.onToggleControls(); _doubleTapTimer = Timer(const Duration(milliseconds: 300), () { _doubleTapTimer = null; }); } } void _executeDoubleTap(Offset pos) { final width = context.size?.width ?? 0; final third = width / 3; if (pos.dx < third) { widget.onDoubleTapLeft(); } else if (pos.dx > third * 2) { widget.onDoubleTapRight(); } else { widget.onDoubleTapCenter(); } } void _handlePanStart(DragStartDetails details) { _panStartPos = details.localPosition; _dragMode = _DragMode.none; _cumulativeDx = 0; _cumulativeDy = 0; } void _handlePanUpdate(DragUpdateDetails details) { _cumulativeDx += details.delta.dx; _cumulativeDy += details.delta.dy; if (_dragMode == _DragMode.none) { final absDx = _cumulativeDx.abs(); final absDy = _cumulativeDy.abs(); if (absDx < 20 && absDy < 20) return; if (absDx > absDy * 1.5) { _dragMode = _DragMode.seek; widget.onHorizontalSeekStart(); } else if (absDy > absDx * 1.5) { final screenWidth = context.size?.width ?? 1; _dragMode = _panStartPos.dx < screenWidth / 2 ? _DragMode.brightness : _DragMode.volume; } else { return; } } if (_dragMode == _DragMode.seek) { final screenWidth = context.size?.width ?? 1; final totalDuration = widget.player.state.duration; final seekDelta = totalDuration * (_cumulativeDx / screenWidth) * 0.5; widget.onHorizontalSeekUpdate(seekDelta); } else if (_dragMode == _DragMode.brightness) { final screenHeight = context.size?.height ?? 1; final delta = -details.delta.dy / (screenHeight * 0.7); widget.onBrightnessChange(delta); } else if (_dragMode == _DragMode.volume) { final screenHeight = context.size?.height ?? 1; final delta = -details.delta.dy / (screenHeight * 1.5); widget.onVolumeChange(delta); } } void _handlePanEnd(DragEndDetails details) { if (_dragMode == _DragMode.seek) { widget.onHorizontalSeekEnd(); } else if (_dragMode == _DragMode.brightness || _dragMode == _DragMode.volume) { widget.onVerticalDragEnd(); } _dragMode = _DragMode.none; } @override void dispose() { _doubleTapTimer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { if (!widget.enabled) return const SizedBox.expand(); return GestureDetector( behavior: HitTestBehavior.translucent, onTapDown: _handleTapDown, onTapUp: _handleTapUp, onPanStart: _handlePanStart, onPanUpdate: _handlePanUpdate, onPanEnd: _handlePanEnd, onLongPressStart: (details) { final screenWidth = context.size?.width ?? 1; final isLeftSide = details.localPosition.dx < screenWidth / 2; widget.onLongPressStart(isLeftSide); }, onLongPressEnd: (_) => widget.onLongPressEnd(), child: const SizedBox.expand(), ); } }