aves_mio22/lib/widgets/collection/remote_status_button.dart
2026-04-18 20:05:02 +02:00

161 lines
4.3 KiB
Dart

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<RemoteStatusButton> createState() => _RemoteStatusButtonState();
}
class _RemoteStatusButtonState extends State<RemoteStatusButton>
with SingleTickerProviderStateMixin {
late final AnimationController _blink = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 700),
lowerBound: 0.25,
upperBound: 1.0,
);
bool _busy = false;
Timer? _lpTimer;
bool _longPressFired = false;
Offset? _downPos;
static const _longPressDelay = Duration(milliseconds: 600);
static const double _moveSlop = 10.0;
@override
void dispose() {
_lpTimer?.cancel();
_blink.dispose();
super.dispose();
}
Future<void> _toggle() async {
if (_busy) return;
setState(() => _busy = true);
try {
await RemoteController.instance.toggleRemote(source: widget.source);
} finally {
if (mounted) setState(() => _busy = false);
}
}
Future<void> _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<RemoteSyncState>(
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; // 🔥 arancione lampeggiante
break;
case RemoteSyncState.upToDate:
color = Colors.greenAccent;
blinking = false; // verde fisso
break;
// 🔥 TUTTI GLI ERRORI → ROSSO FISSO
case RemoteSyncState.serverDown:
case RemoteSyncState.timeout:
case RemoteSyncState.networkError:
case RemoteSyncState.badGateway:
case RemoteSyncState.serverError:
color = Colors.redAccent;
blinking = false; // 🔥 rosso NON lampeggiante
break;
}
// Gestione blinking
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),
);
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();
if (_longPressFired) {
_longPressFired = false;
return;
}
await _toggle();
},
onPointerCancel: (e) {
_cancelLongPressTimer();
},
behavior: HitTestBehavior.opaque,
child: Center(child: icon),
),
);
},
);
}
}