161 lines
4.3 KiB
Dart
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),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|