155 lines
4.2 KiB
Dart
155 lines
4.2 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;
|
||
|
||
// --- 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<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;
|
||
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),
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
}
|