Files
PlayMaker/lib/controllers/game_sharing_controller.dart
2026-05-15 12:43:30 +01:00

282 lines
8.2 KiB
Dart
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ...existing code...
import 'package:supabase_flutter/supabase_flutter.dart';
import 'dart:math';
class GameSharingController {
final _supabase = Supabase.instance.client;
String get myUserId => _supabase.auth.currentUser?.id ?? '';
String get myUserEmail => _supabase.auth.currentUser?.email ?? '';
// ====================================
// 1⃣ GERAR CÓDIGO E CRIAR SESSÃO
// ====================================
Future<String?> createShareSession(String gameId) async {
try {
final shareCode = _generateShareCode();
final response = await _supabase.from('game_sessions').insert({
'game_id': gameId,
'created_by': myUserId,
'share_code': shareCode,
'status': 'active',
}).select().single();
return shareCode;
} catch (e) {
print("❌ Erro ao criar sessão de compartilhamento: $e");
return null;
}
}
// ====================================
// 2⃣ ENTRAR EM JOGO COMPARTILHADO
// ====================================
Future<Map<String, dynamic>?> joinGameByCode(String shareCode) async {
try {
print("🔍 Procurando sessão com código: $shareCode");
// Procura a sessão pelo código
final sessions = await _supabase
.from('game_sessions')
.select()
.eq('share_code', shareCode.toUpperCase())
.eq('status', 'active');
print("📋 Sessões encontradas: ${sessions.length}");
if (sessions.isEmpty) {
print("❌ Código inválido ou expirado");
return null;
}
final session = sessions.first;
final gameId = session['game_id'] as String;
final createdBy = session['created_by'] as String;
print("🎮 Game ID: $gameId, Criado por: $createdBy");
// Garante que o utilizador atual tem perfil
print("👤 Verificando perfil do utilizador: $myUserId");
await _ensureUserProfile();
// Atualiza a sessão para adicionar o utilizador que está a entrar
await _supabase.from('game_sessions').update({
'shared_with_user_id': myUserId,
'updated_at': DateTime.now().toIso8601String(),
}).eq('id', session['id']);
print("✅ Sessão atualizada com novo utilizador");
// Busca informações do jogo
final gameData = await _supabase
.from('games')
.select()
.eq('id', gameId)
.single();
print("📊 Dados do jogo: $gameData");
// Busca o nome do utilizador que criou
final creatorData = await _supabase
.from('profiles')
.select('username, full_name')
.eq('id', createdBy)
.maybeSingle();
final creatorName = creatorData != null
? (creatorData['full_name'] ?? creatorData['username'] ?? 'Utilizador')
: 'Utilizador';
print("👤 Criador: $creatorName");
return {
'session_id': session['id'],
'game_id': gameId,
'creator_name': creatorName,
'game': gameData,
};
} catch (e) {
print("❌ Erro ao entrar no jogo: $e");
return null;
}
}
// ====================================
// GARANTIR QUE UTILIZADOR TEM PERFIL
// ====================================
Future<void> _ensureUserProfile() async {
try {
final user = _supabase.auth.currentUser;
if (user == null) return;
// Verifica se o perfil existe
final existing = await _supabase
.from('profiles')
.select()
.eq('id', user.id)
.maybeSingle();
if (existing == null) {
// Cria o perfil se não existir - usa apenas colunas básicas
print("📝 Criando perfil para novo utilizador");
await _supabase.from('profiles').upsert({
'id': user.id,
'username': user.email?.split('@').first ?? 'user',
}, onConflict: 'id');
print("✅ Perfil criado com sucesso");
} else {
print("✅ Perfil já existe");
}
} catch (e) {
print("⚠️ Aviso ao verificar/criar perfil: $e");
// Não falha o join se o perfil já existe
}
}
// ====================================
// 3⃣ OBTER INFORMAÇÕES DA SESSÃO
// ====================================
Future<Map<String, dynamic>?> getSessionInfo(String sessionId) async {
try {
final response = await _supabase
.from('game_sessions')
.select()
.eq('id', sessionId)
.single();
return response;
} catch (e) {
print("❌ Erro ao buscar informações da sessão: $e");
return null;
}
}
// ====================================
// 4⃣ ENVIAR EVENTO DE SINCRONIZAÇÃO
// ====================================
Future<bool> sendSyncEvent(
String sessionId,
String actionType,
Map<String, dynamic> actionData, {
String? playerId, // opcional: identifica jogador/entidade alvo
}) async {
try {
await _supabase.from('game_sync_events').insert({
'session_id': sessionId,
'action_type': actionType,
'action_data': actionData,
'triggered_by': myUserId,
if (playerId != null) 'player_id': playerId,
});
print("✅ Evento sincronizado: $actionType");
return true;
} catch (e) {
print("❌ Erro ao enviar evento de sincronização: $e");
return false;
}
}
// ====================================
// 5⃣ OUVIR EVENTOS EM TEMPO REAL
// ====================================
Stream<dynamic> listenToGameSync(String sessionId) {
return _supabase
.from('game_sync_events')
.stream(primaryKey: ['id'])
.eq('session_id', sessionId)
.order('created_at', ascending: false);
}
/// Retorna apenas os eventos que NÃO foram disparados pelo utilizador atual.
/// Emite uma lista de eventos (List<Map<String, dynamic>>) por cada atualização.
Stream<List<Map<String, dynamic>>> listenToGameSyncOthers(String sessionId) {
return listenToGameSync(sessionId).map((data) {
List<Map<String, dynamic>> rows = [];
try {
if (data is List) {
rows = List<Map<String, dynamic>>.from(data);
} else if (data is Map) {
rows = [Map<String, dynamic>.from(data)];
}
} catch (_) {
return <Map<String, dynamic>>[];
}
return rows.where((r) => (r['triggered_by'] as String?) != myUserId).toList();
});
}
// ====================================
// 6⃣ OBTER ÚLTIMOS EVENTOS
// ====================================
Future<List<Map<String, dynamic>>> getRecentSyncEvents(String sessionId, {int limit = 10}) async {
try {
final response = await _supabase
.from('game_sync_events')
.select()
.eq('session_id', sessionId)
.order('created_at', ascending: false)
.limit(limit);
return List<Map<String, dynamic>>.from(response);
} catch (e) {
print("❌ Erro ao buscar eventos: $e");
return [];
}
}
// ====================================
// 7⃣ TERMINAR SESSÃO COMPARTILHADA
// ====================================
Future<bool> endShareSession(String sessionId) async {
try {
await _supabase
.from('game_sessions')
.update({'status': 'ended'})
.eq('id', sessionId);
print("✅ Sessão terminada");
return true;
} catch (e) {
print("❌ Erro ao terminar sessão: $e");
return false;
}
}
// ====================================
// 8⃣ OBTER SESSÃO ATIVA DO JOGO
// ====================================
Future<Map<String, dynamic>?> getActiveSessionForGame(String gameId) async {
try {
final response = await _supabase
.from('game_sessions')
.select()
.eq('game_id', gameId)
.eq('status', 'active')
.single();
return response;
} catch (e) {
return null; // Sem sessão ativa
}
}
// ====================================
// FUNÇÕES PRIVADAS
// ====================================
String _generateShareCode() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
final random = Random();
return List.generate(6, (index) => chars[random.nextInt(chars.length)]).join();
}
}