282 lines
8.2 KiB
Dart
282 lines
8.2 KiB
Dart
// ...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();
|
||
}
|
||
}
|