import 'dart:async'; import 'package:flutter/material.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/remote/remote_sync_bus.dart'; import 'package:aves/remote/remote_controller.dart'; import 'package:aves/remote/remote_settings_dialog.dart'; class RemoteStatusButton extends StatefulWidget { final CollectionSource source; const RemoteStatusButton({super.key, required this.source}); @override State createState() => _RemoteStatusButtonState(); } class _RemoteStatusButtonState extends State with SingleTickerProviderStateMixin { late final AnimationController _blink = AnimationController( vsync: this, duration: const Duration(milliseconds: 700), lowerBound: 0.25, upperBound: 1.0, ); bool _busy = false; // --- long press manuale --- Timer? _lpTimer; bool _longPressFired = false; Offset? _downPos; static const _longPressDelay = Duration(milliseconds: 600); static const double _moveSlop = 10.0; // px: tolleranza movimento prima di cancellare @override void dispose() { _lpTimer?.cancel(); _blink.dispose(); super.dispose(); } Future _toggle() async { if (_busy) return; setState(() => _busy = true); try { await RemoteController.instance.toggleRemote(source: widget.source); } finally { if (mounted) setState(() => _busy = false); } } Future _openSettings() async { if (_busy) return; await RemoteSettingsDialog.show(context); } void _startLongPressTimer(Offset globalPos) { _lpTimer?.cancel(); _longPressFired = false; _downPos = globalPos; _lpTimer = Timer(_longPressDelay, () async { if (!mounted || _busy) return; _longPressFired = true; await _openSettings(); }); } void _cancelLongPressTimer() { _lpTimer?.cancel(); _lpTimer = null; } @override Widget build(BuildContext context) { final bus = RemoteSyncBus.instance; return ValueListenableBuilder( valueListenable: bus.stateNotifier, builder: (context, st, _) { Color color; bool blinking; switch (st) { case RemoteSyncState.disabled: color = Colors.grey; blinking = false; break; case RemoteSyncState.syncing: color = Colors.orangeAccent; blinking = true; break; case RemoteSyncState.upToDate: color = Colors.greenAccent; blinking = false; break; case RemoteSyncState.serverDown: color = Colors.redAccent; blinking = true; break; } if (blinking) { if (!_blink.isAnimating) _blink.repeat(reverse: true); } else { if (_blink.isAnimating) _blink.stop(); _blink.value = 1.0; } final icon = FadeTransition( opacity: _blink, child: Icon(Icons.satellite_alt_rounded, color: color), ); // ✅ area touch standard AppBar 48x48: non prende tutto l’header return SizedBox.square( dimension: kMinInteractiveDimension, child: Listener( onPointerDown: (e) { if (_busy) return; _startLongPressTimer(e.position); }, onPointerMove: (e) { final start = _downPos; if (start != null) { final dx = (e.position.dx - start.dx).abs(); final dy = (e.position.dy - start.dy).abs(); if (dx > _moveSlop || dy > _moveSlop) { _cancelLongPressTimer(); } } }, onPointerUp: (e) async { if (_busy) return; _cancelLongPressTimer(); // se il long press è già scattato, NON fare toggle if (_longPressFired) { _longPressFired = false; return; } await _toggle(); }, onPointerCancel: (e) { _cancelLongPressTimer(); }, behavior: HitTestBehavior.opaque, child: Center(child: icon), ), ); }, ); } }