158 lines
4.7 KiB
Markdown
158 lines
4.7 KiB
Markdown
# Resumo das Mudanças - Sincronização de Jogo em Tempo Real
|
|
|
|
## 1. lib/controllers/placar_controller.dart
|
|
|
|
### Adicionado ao constructor:
|
|
```dart
|
|
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:
|
|
```dart
|
|
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:**
|
|
```dart
|
|
_dispatchSyncAction('commit_stat', {
|
|
'action': action,
|
|
'player_data': playerData,
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 2. lib/pages/PlacarPage.dart
|
|
|
|
### Adicionado ao state:
|
|
```dart
|
|
String? _lastAppliedSyncEventId; // ← NOVO - deduplicação de eventos
|
|
```
|
|
|
|
### Constructor do controller:
|
|
```dart
|
|
_controller = PlacarController(
|
|
gameId: widget.gameId,
|
|
myTeam: widget.myTeam,
|
|
opponentTeam: widget.opponentTeam,
|
|
onSyncAction: _onLocalControllerSync, // ← CONECTADO
|
|
);
|
|
```
|
|
|
|
### Adicionado novo método _onLocalControllerSync:
|
|
```dart
|
|
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):
|
|
```dart
|
|
_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):
|
|
```dart
|
|
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 Local** → `commitStat()` 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 Supabase** → `sendSyncEvent()` armazena em `game_sync_events`
|
|
5. **Parceiro recebe** → `listenToGameSyncOthers()` 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
|