import 'package:dio/dio.dart'; import 'package:focusflow_shared/focusflow_shared.dart'; import 'interceptors/auth_interceptor.dart'; /// Central Dio wrapper for all API communication. /// /// Registered as a lazy singleton in GetIt so every feature shares /// the same HTTP client (and therefore the same auth interceptor, /// retry logic, and error normalisation). class ApiClient { late final Dio _dio; ApiClient() { _dio = Dio( BaseOptions( // TODO: read from env / config baseUrl: 'https://api.focusflow.app', connectTimeout: const Duration(seconds: 15), receiveTimeout: const Duration(seconds: 15), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, ), ); _dio.interceptors.addAll([ AuthInterceptor(), LogInterceptor(requestBody: true, responseBody: true), ]); } // ── Convenience HTTP verbs ─────────────────────────────────────── Future> get( String path, { Map? queryParameters, Options? options, }) => _dio.get(path, queryParameters: queryParameters, options: options); Future> post( String path, { Object? data, Options? options, }) => _dio.post(path, data: data, options: options); Future> put( String path, { Object? data, Options? options, }) => _dio.put(path, data: data, options: options); Future> patch( String path, { Object? data, Options? options, }) => _dio.patch(path, data: data, options: options); Future> delete( String path, { Object? data, Options? options, }) => _dio.delete(path, data: data, options: options); // ── Auth helpers ───────────────────────────────────────────────── Future>> login(String email, String password) async { final response = await post>( ApiConstants.authLogin, data: {'email': email, 'password': password}, ); return ApiResponse.fromJson( response.data!, (json) => json as Map, ); } Future>> register( String displayName, String email, String password, ) async { final response = await post>( ApiConstants.authRegister, data: { 'displayName': displayName, 'email': email, 'password': password, }, ); return ApiResponse.fromJson( response.data!, (json) => json as Map, ); } Future logout() => post(ApiConstants.authLogout); // ── Task helpers ───────────────────────────────────────────────── Future>> fetchTasks({ int page = 1, int limit = 20, }) => get>( ApiConstants.tasks, queryParameters: {'page': page, 'limit': limit}, ); Future>> fetchNextTask() => get>(ApiConstants.nextTask); Future>> completeTask(String id) => post>(ApiConstants.completeTask(id)); Future>> skipTask(String id) => post>(ApiConstants.skipTask(id)); Future>> createTask(Map data) => post>(ApiConstants.tasks, data: data); Future>> deleteTask(String id) => delete>(ApiConstants.task(id)); // ── Streak helpers ─────────────────────────────────────────────── Future>> fetchStreaks() => get>(ApiConstants.streaks); Future>> completeStreak(String id) => post>(ApiConstants.streak(id)); Future>> forgiveStreak(String id) => post>(ApiConstants.forgiveStreak(id)); // ── Reward helpers ─────────────────────────────────────────────── Future>> generateReward(String taskId) => post>( ApiConstants.generateReward, data: {'taskId': taskId}, ); // ── Time helpers ───────────────────────────────────────────────── Future>> fetchTimeAccuracy() => get>(ApiConstants.timeAccuracy); }