Files
PlayMaker/SYNC_CHANGES_SUMMARY.md
2026-05-15 12:43:30 +01:00

4.7 KiB

Resumo das Mudanças - Sincronização de Jogo em Tempo Real

1. lib/controllers/placar_controller.dart

Adicionado ao constructor:

final void Function(String actionType, Map<String, dynamic> actionData)? onSyncAction;

PlacarController({
  required this.gameId,
  required this.myTeam,
  required this.opponentTeam,
  this.onSyncAction,  // ← NOVO
});

Adicionado método _dispatchSyncAction:

void _dispatchSyncAction(String actionType, Map<String, dynamic> actionData) {
  if (onSyncAction != null) {
    final enrichedActionData = Map<String, dynamic>.from(actionData)
      ..['remaining_seconds'] = durationNotifier.value.inSeconds
      ..['is_running'] = isRunning;
    onSyncAction!(actionType, enrichedActionData);
  }
}

Adicionado em 5 métodos (chamada _dispatchSyncAction):

  • useTimeout() → dispatch 'use_timeout'
  • handleSubbing() → dispatch 'subbing'
  • swapCourtPlayers() → dispatch 'swap_players'
  • registerFoul() → dispatch 'register_foul'
  • commitStat() → dispatch 'commit_stat'

Exemplo em commitStat:

_dispatchSyncAction('commit_stat', {
  'action': action,
  'player_data': playerData,
});

2. lib/pages/PlacarPage.dart

Adicionado ao state:

String? _lastAppliedSyncEventId;  // ← NOVO - deduplicação de eventos

Constructor do controller:

_controller = PlacarController(
  gameId: widget.gameId,
  myTeam: widget.myTeam,
  opponentTeam: widget.opponentTeam,
  onSyncAction: _onLocalControllerSync,  // ← CONECTADO
);

Adicionado novo método _onLocalControllerSync:

void _onLocalControllerSync(String actionType, Map<String, dynamic> actionData) {
  if (_sessionId == null || _isApplyingRemoteSync) return;
  print("📤 Enviando sync action local: $actionType -> $actionData");
  _sharingController.sendSyncEvent(_sessionId!, actionType, actionData);
}

Atualizado _setupSyncListener (deduplicação):

_syncSubscription = _sharingController.listenToGameSyncOthers(_sessionId!).listen(
  (dynamic event) {
    Map<String, dynamic>? record;
    if (event is List && event.isNotEmpty) {
      for (final item in event) {
        final row = item as Map<String, dynamic>?;
        if (row == null) continue;
        final rowId = row['id']?.toString();
        if (rowId != null && rowId != _lastAppliedSyncEventId) {
          record = row;
          break;  // ← para no primeiro evento novo
        }
      }
    } else if (event is Map<String, dynamic>) {
      record = Map<String, dynamic>.from(event);
    }

    if (record != null) {
      final recordId = record['id']?.toString();
      if (recordId != null && recordId == _lastAppliedSyncEventId) return;
      if (recordId != null) _lastAppliedSyncEventId = recordId;
      _applyRemoteSyncEvent(record);
    }
  },
);

Atualizado _applyRemoteSyncEvent (aplicar estado remoto):

void _applyRemoteSyncEvent(Map<String, dynamic> record) {
  final actionType = record['action_type']?.toString();
  final actionData = Map<String, dynamic>.from(record['action_data'] ?? {});

  // ← NOVO: aplicar timer remotamente em TODAS as ações
  final remoteSeconds = int.tryParse(actionData['remaining_seconds']?.toString() ?? '');
  final remoteIsRunning = actionData['is_running'] == true;
  if (remoteSeconds != null) {
    _controller.durationNotifier.value = Duration(seconds: remoteSeconds);
  }
  if (remoteIsRunning != _controller.isRunning) {
    _isApplyingRemoteSync = true;
    _controller.toggleTimer(context);
    _isApplyingRemoteSync = false;
  }

  // Aplicar ações específicas
  if (actionType == 'toggle_timer') {
    setState(() {});
  } else if (actionType == 'commit_stat') {
    // aplicar pontos/faltas
  } else if (actionType == 'register_foul') {
    // aplicar falta
  } else if (actionType == 'subbing') {
    // aplicar substituição
  } else if (actionType == 'swap_players') {
    // trocar posição
  } else if (actionType == 'use_timeout') {
    // usar timeout
  }
}

Fluxo Completo

  1. Ação LocalcommitStat() no controller
  2. Controller emite_dispatchSyncAction('commit_stat', {action, player_data, remaining_seconds, is_running})
  3. PlacarPage escuta_onLocalControllerSync() recebe o evento
  4. Envia ao SupabasesendSyncEvent() armazena em game_sync_events
  5. Parceiro recebelistenToGameSyncOthers() retorna o evento
  6. Aplica remotamente_applyRemoteSyncEvent() executa a ação no parceiro
  7. Estado sincronizado → Ambos têm timer, pontos, faltas idênticos

Resultado

Timer não reseta ao marcar ponto Pontos sincronizam entre os dois lados Faltas sincronizam Timeouts sincronizam Substituições sincronizam Posições de jogadores sincronizam