Aggiunge overlay server-side burn-in e stabilizza diretta live.
Tabellone, badge e brand sono ricodificati nel flusso _air via OverlayRelay; sync punteggio HTTP, volume condiviso rails/sidekiq, qualità 720p migliorata e badge CONNECTING in ripresa da pausa. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
77
mobile/lib/providers/score_sync_provider.dart
Normal file
77
mobile/lib/providers/score_sync_provider.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../shared/api_client.dart';
|
||||
import '../shared/models/score_state.dart';
|
||||
import 'score_provider.dart';
|
||||
import 'session_provider.dart';
|
||||
|
||||
/// Coda sync HTTP: evita richieste fuori ordine e allinea l'app al DB (pagina live).
|
||||
class ScoreSyncService {
|
||||
ScoreSyncService(this._ref);
|
||||
|
||||
final Ref _ref;
|
||||
bool _busy = false;
|
||||
bool _dirty = false;
|
||||
DateTime? _suppressStaleRemoteUntil;
|
||||
|
||||
/// Ignora broadcast WebSocket obsoleti subito dopo un push HTTP.
|
||||
bool acceptCableScore(ScoreState remote) {
|
||||
final until = _suppressStaleRemoteUntil;
|
||||
if (until == null || DateTime.now().isAfter(until)) return true;
|
||||
final local = _ref.read(scoreProvider);
|
||||
return !_isClearlyBehind(remote, local);
|
||||
}
|
||||
|
||||
bool _isClearlyBehind(ScoreState remote, ScoreState local) {
|
||||
final r = remote.homeSets * 1000 +
|
||||
remote.awaySets * 1000 +
|
||||
remote.homePoints +
|
||||
remote.awayPoints;
|
||||
final l = local.homeSets * 1000 +
|
||||
local.awaySets * 1000 +
|
||||
local.homePoints +
|
||||
local.awayPoints;
|
||||
return r < l;
|
||||
}
|
||||
|
||||
Future<void> push() async {
|
||||
_dirty = true;
|
||||
if (_busy) return;
|
||||
_busy = true;
|
||||
while (_dirty) {
|
||||
_dirty = false;
|
||||
final sessionId = _ref.read(sessionProvider).session?.id;
|
||||
if (sessionId == null) continue;
|
||||
|
||||
final payload = _ref.read(scoreProvider).toCablePayload();
|
||||
try {
|
||||
final remote = await _ref.read(apiClientProvider).syncScore(sessionId, payload);
|
||||
_suppressStaleRemoteUntil = DateTime.now().add(const Duration(milliseconds: 500));
|
||||
if (remote != null) {
|
||||
_ref.read(scoreProvider.notifier).applyRemote(remote);
|
||||
}
|
||||
} catch (_) {
|
||||
// Mantieni stato locale; il prossimo pull periodico riallinea se possibile.
|
||||
}
|
||||
}
|
||||
_busy = false;
|
||||
}
|
||||
|
||||
Future<void> pullFromServer() async {
|
||||
if (_busy) return;
|
||||
final sessionId = _ref.read(sessionProvider).session?.id;
|
||||
if (sessionId == null) return;
|
||||
try {
|
||||
final session = await _ref.read(apiClientProvider).fetchSession(sessionId);
|
||||
if (session.score != null) {
|
||||
_ref.read(scoreProvider.notifier).applyRemote(session.score!);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
final scoreSyncProvider = Provider<ScoreSyncService>((ref) {
|
||||
return ScoreSyncService(ref);
|
||||
});
|
||||
Reference in New Issue
Block a user