Files
MatchLiveTv/mobile/lib/shared/api_client.dart
Emiliano Frascaro 566104aff4 Billing upgrade/downgrade, inviti in app e diretta YouTube da mobile.
Upgrade Stripe con addebito immediato; downgrade programmato a fine periodo.
UX billing più sobria con box info; icone piani. API inviti e OAuth YouTube
per staff trasmissione; deep link join; scelta partita programmata o nuova.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 22:15:27 +02:00

276 lines
8.3 KiB
Dart

import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/config.dart';
import '../providers/auth_provider.dart';
import 'models/invite_preview.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<ApiClient>((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<Map<String, dynamic>>(
'/auth/login',
data: {'email': email, 'password': password},
);
final data = response.data!;
return (
user: User.fromJson(data['user'] as Map<String, dynamic>),
tokens: AuthTokens.fromJson(data),
);
}
Future<AuthTokens> refresh(String refreshToken) async {
final response = await _dio.post<Map<String, dynamic>>(
'/auth/refresh',
data: {'refresh_token': refreshToken},
);
return AuthTokens.fromJson(response.data!);
}
Future<User> me() async {
final response = await _dio.get<Map<String, dynamic>>('/auth/me');
return User.fromJson(response.data!);
}
Future<void> logout() async {
await _dio.post('/auth/logout');
}
// --- Teams ---
Future<List<Team>> fetchTeams() async {
final response = await _dio.get<List<dynamic>>('/teams');
return response.data!
.map((e) => Team.fromJson(e as Map<String, dynamic>))
.toList();
}
Future<InvitePreview> fetchInvitePreview(String token) async {
final response = await _dio.get<Map<String, dynamic>>('/invitations/$token');
return InvitePreview.fromJson(response.data!);
}
Future<String> acceptInvitation(String token) async {
final response = await _dio.post<Map<String, dynamic>>('/invitations/$token/accept');
return response.data!['message'] as String? ?? 'Invito accettato';
}
Future<String> youtubeAuthorizeUrl(String teamId, {bool returnApp = true}) async {
final response = await _dio.get<Map<String, dynamic>>(
'/teams/$teamId/youtube/authorize',
queryParameters: returnApp ? {'return_app': '1'} : null,
);
return response.data!['authorization_url'] as String;
}
Future<List<RecordingItem>> fetchRecordings(String teamId) async {
final response = await _dio.get<List<dynamic>>('/teams/$teamId/recordings');
return response.data!
.map((e) => RecordingItem.fromJson(e as Map<String, dynamic>))
.toList();
}
// --- Matches ---
Future<List<MatchModel>> fetchMatches(String teamId) async {
final response =
await _dio.get<List<dynamic>>('/teams/$teamId/matches');
return response.data!
.map((e) => MatchModel.fromJson(e as Map<String, dynamic>))
.toList();
}
Future<MatchModel> fetchMatch(String matchId) async {
final response =
await _dio.get<Map<String, dynamic>>('/matches/$matchId');
return MatchModel.fromJson(response.data!);
}
Future<MatchModel> updateMatch(String matchId, Map<String, dynamic> body) async {
final response = await _dio.patch<Map<String, dynamic>>(
'/matches/$matchId',
data: {'match': body},
);
return MatchModel.fromJson(response.data!);
}
Future<void> deleteMatch(String matchId) async {
await _dio.delete<void>('/matches/$matchId');
}
Future<MatchModel> createMatch(String teamId, Map<String, dynamic> body) async {
final response = await _dio.post<Map<String, dynamic>>(
'/teams/$teamId/matches',
data: {'match': body},
);
return MatchModel.fromJson(response.data!);
}
// --- Sessions ---
Future<StreamSession> 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<Map<String, dynamic>>(
'/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<StreamSession> fetchSession(String sessionId) async {
final response =
await _dio.get<Map<String, dynamic>>('/sessions/$sessionId');
return StreamSession.fromJson(response.data!);
}
Future<StreamSession> startSession(String sessionId) async {
final response =
await _dio.patch<Map<String, dynamic>>('/sessions/$sessionId/start');
return StreamSession.fromJson(response.data!);
}
Future<StreamSession> stopSession(String sessionId) async {
final response =
await _dio.patch<Map<String, dynamic>>('/sessions/$sessionId/stop');
return StreamSession.fromJson(response.data!);
}
Future<StreamSession> pauseSession(String sessionId) async {
final response =
await _dio.patch<Map<String, dynamic>>('/sessions/$sessionId/pause');
return StreamSession.fromJson(response.data!);
}
Future<RegiaLinkResponse> createRegiaLink(String sessionId) async {
final response = await _dio.post<Map<String, dynamic>>(
'/sessions/$sessionId/regia_link',
);
return RegiaLinkResponse.fromJson(response.data!);
}
Future<PairingTokenResponse> createPairingToken(String sessionId) async {
final response = await _dio.post<Map<String, dynamic>>(
'/sessions/$sessionId/pairing_token',
);
return PairingTokenResponse.fromJson(response.data!);
}
Future<StreamSession> claimPairing({
required String sessionId,
required String pairingToken,
required String deviceRole,
}) async {
final response = await _dio.post<Map<String, dynamic>>(
'/sessions/$sessionId/claim_pairing',
data: {
'pairing_token': pairingToken,
'device_role': deviceRole,
},
);
return StreamSession.fromJson(response.data!);
}
Future<NetworkTestResult> networkTest({
required String sessionId,
required double downloadMbps,
required double uploadMbps,
required int latencyMs,
required String networkType,
}) async {
final response = await _dio.post<Map<String, dynamic>>(
'/sessions/$sessionId/network_test',
data: {
'download_mbps': downloadMbps,
'upload_mbps': uploadMbps,
'latency_ms': latencyMs,
'network_type': networkType,
},
);
return NetworkTestResult.fromJson(response.data!);
}
Future<YoutubeStats> youtubeStats(String sessionId) async {
final response =
await _dio.get<Map<String, dynamic>>('/sessions/$sessionId/youtube_stats');
return YoutubeStats.fromJson(response.data!);
}
Future<void> 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,
},
);
}
}