aves_mio1/lib/widgets/collection/remote_status_button.dart
FabioMich66 deb7b4c6dd
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
super
2026-04-10 10:07:04 +02:00

155 lines
4.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 lheader
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),
),
);
},
);
}
}