import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../core/config.dart'; import '../providers/auth_provider.dart'; import 'models/match.dart'; import 'models/recording_item.dart'; import 'models/stream_session.dart'; import 'models/team.dart'; import 'models/user.dart'; final apiClientProvider = Provider((ref) { final auth = ref.watch(authProvider); return ApiClient(accessToken: auth.accessToken); }); class ApiClient { ApiClient({this.accessToken}) { _dio = Dio( BaseOptions( baseUrl: AppConfig.apiV1, connectTimeout: const Duration(seconds: 15), receiveTimeout: const Duration(seconds: 15), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, ), ); _dio.interceptors.add( InterceptorsWrapper( onRequest: (options, handler) { if (accessToken != null && accessToken!.isNotEmpty) { options.headers['Authorization'] = 'Bearer $accessToken'; } handler.next(options); }, ), ); } late final Dio _dio; final String? accessToken; // --- Auth --- Future<({User user, AuthTokens tokens})> login({ required String email, required String password, }) async { final response = await _dio.post>( '/auth/login', data: {'email': email, 'password': password}, ); final data = response.data!; return ( user: User.fromJson(data['user'] as Map), tokens: AuthTokens.fromJson(data), ); } Future refresh(String refreshToken) async { final response = await _dio.post>( '/auth/refresh', data: {'refresh_token': refreshToken}, ); return AuthTokens.fromJson(response.data!); } Future me() async { final response = await _dio.get>('/auth/me'); return User.fromJson(response.data!); } Future logout() async { await _dio.post('/auth/logout'); } // --- Teams --- Future> fetchTeams() async { final response = await _dio.get>('/teams'); return response.data! .map((e) => Team.fromJson(e as Map)) .toList(); } Future> fetchRecordings(String teamId) async { final response = await _dio.get>('/teams/$teamId/recordings'); return response.data! .map((e) => RecordingItem.fromJson(e as Map)) .toList(); } // --- Matches --- Future> fetchMatches(String teamId) async { final response = await _dio.get>('/teams/$teamId/matches'); return response.data! .map((e) => MatchModel.fromJson(e as Map)) .toList(); } Future fetchMatch(String matchId) async { final response = await _dio.get>('/matches/$matchId'); return MatchModel.fromJson(response.data!); } Future updateMatch(String matchId, Map body) async { final response = await _dio.patch>( '/matches/$matchId', data: {'match': body}, ); return MatchModel.fromJson(response.data!); } Future deleteMatch(String matchId) async { await _dio.delete('/matches/$matchId'); } Future createMatch(String teamId, Map body) async { final response = await _dio.post>( '/teams/$teamId/matches', data: {'match': body}, ); return MatchModel.fromJson(response.data!); } // --- Sessions --- Future createSession( String matchId, { String platform = 'matchlivetv', String privacyStatus = 'unlisted', String qualityPreset = '720p_30_2.5mbps', int? targetBitrate, int? targetFps, }) async { final response = await _dio.post>( '/matches/$matchId/sessions', data: { 'platform': platform, 'privacy_status': privacyStatus, 'quality_preset': qualityPreset, if (targetBitrate != null) 'target_bitrate': targetBitrate, if (targetFps != null) 'target_fps': targetFps, }, ); return StreamSession.fromJson(response.data!); } Future fetchSession(String sessionId) async { final response = await _dio.get>('/sessions/$sessionId'); return StreamSession.fromJson(response.data!); } Future startSession(String sessionId) async { final response = await _dio.patch>('/sessions/$sessionId/start'); return StreamSession.fromJson(response.data!); } Future stopSession(String sessionId) async { final response = await _dio.patch>('/sessions/$sessionId/stop'); return StreamSession.fromJson(response.data!); } Future pauseSession(String sessionId) async { final response = await _dio.patch>('/sessions/$sessionId/pause'); return StreamSession.fromJson(response.data!); } Future createRegiaLink(String sessionId) async { final response = await _dio.post>( '/sessions/$sessionId/regia_link', ); return RegiaLinkResponse.fromJson(response.data!); } Future createPairingToken(String sessionId) async { final response = await _dio.post>( '/sessions/$sessionId/pairing_token', ); return PairingTokenResponse.fromJson(response.data!); } Future claimPairing({ required String sessionId, required String pairingToken, required String deviceRole, }) async { final response = await _dio.post>( '/sessions/$sessionId/claim_pairing', data: { 'pairing_token': pairingToken, 'device_role': deviceRole, }, ); return StreamSession.fromJson(response.data!); } Future networkTest({ required String sessionId, required double downloadMbps, required double uploadMbps, required int latencyMs, required String networkType, }) async { final response = await _dio.post>( '/sessions/$sessionId/network_test', data: { 'download_mbps': downloadMbps, 'upload_mbps': uploadMbps, 'latency_ms': latencyMs, 'network_type': networkType, }, ); return NetworkTestResult.fromJson(response.data!); } Future youtubeStats(String sessionId) async { final response = await _dio.get>('/sessions/$sessionId/youtube_stats'); return YoutubeStats.fromJson(response.data!); } Future postTelemetry({ required String sessionId, required String deviceRole, int? batteryLevel, String? networkType, int? signalStrength, int? currentBitrate, int? targetBitrate, int? fps, }) async { await _dio.post( '/sessions/$sessionId/telemetry', data: { 'device_role': deviceRole, if (batteryLevel != null) 'battery_level': batteryLevel, if (networkType != null) 'network_type': networkType, if (signalStrength != null) 'signal_strength': signalStrength, if (currentBitrate != null) 'current_bitrate': currentBitrate, if (targetBitrate != null) 'target_bitrate': targetBitrate, if (fps != null) 'fps': fps, }, ); } }