BLoC/Cubit state management, ADHD-friendly theme (calming teal, no red), GetIt DI, GoRouter navigation. Screens: task dashboard, focus mode, task create/detail, streaks, time perception, settings, onboarding, auth. Custom widgets: TaskCard, RewardPopup, StreakRing, GentleNudgeCard. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
154 lines
5.1 KiB
Dart
154 lines
5.1 KiB
Dart
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<Response<T>> get<T>(
|
|
String path, {
|
|
Map<String, dynamic>? queryParameters,
|
|
Options? options,
|
|
}) =>
|
|
_dio.get<T>(path, queryParameters: queryParameters, options: options);
|
|
|
|
Future<Response<T>> post<T>(
|
|
String path, {
|
|
Object? data,
|
|
Options? options,
|
|
}) =>
|
|
_dio.post<T>(path, data: data, options: options);
|
|
|
|
Future<Response<T>> put<T>(
|
|
String path, {
|
|
Object? data,
|
|
Options? options,
|
|
}) =>
|
|
_dio.put<T>(path, data: data, options: options);
|
|
|
|
Future<Response<T>> patch<T>(
|
|
String path, {
|
|
Object? data,
|
|
Options? options,
|
|
}) =>
|
|
_dio.patch<T>(path, data: data, options: options);
|
|
|
|
Future<Response<T>> delete<T>(
|
|
String path, {
|
|
Object? data,
|
|
Options? options,
|
|
}) =>
|
|
_dio.delete<T>(path, data: data, options: options);
|
|
|
|
// ── Auth helpers ─────────────────────────────────────────────────
|
|
|
|
Future<ApiResponse<Map<String, dynamic>>> login(String email, String password) async {
|
|
final response = await post<Map<String, dynamic>>(
|
|
ApiConstants.authLogin,
|
|
data: {'email': email, 'password': password},
|
|
);
|
|
return ApiResponse.fromJson(
|
|
response.data!,
|
|
(json) => json as Map<String, dynamic>,
|
|
);
|
|
}
|
|
|
|
Future<ApiResponse<Map<String, dynamic>>> register(
|
|
String displayName,
|
|
String email,
|
|
String password,
|
|
) async {
|
|
final response = await post<Map<String, dynamic>>(
|
|
ApiConstants.authRegister,
|
|
data: {
|
|
'displayName': displayName,
|
|
'email': email,
|
|
'password': password,
|
|
},
|
|
);
|
|
return ApiResponse.fromJson(
|
|
response.data!,
|
|
(json) => json as Map<String, dynamic>,
|
|
);
|
|
}
|
|
|
|
Future<void> logout() => post(ApiConstants.authLogout);
|
|
|
|
// ── Task helpers ─────────────────────────────────────────────────
|
|
|
|
Future<Response<Map<String, dynamic>>> fetchTasks({
|
|
int page = 1,
|
|
int limit = 20,
|
|
}) =>
|
|
get<Map<String, dynamic>>(
|
|
ApiConstants.tasks,
|
|
queryParameters: {'page': page, 'limit': limit},
|
|
);
|
|
|
|
Future<Response<Map<String, dynamic>>> fetchNextTask() =>
|
|
get<Map<String, dynamic>>(ApiConstants.nextTask);
|
|
|
|
Future<Response<Map<String, dynamic>>> completeTask(String id) =>
|
|
post<Map<String, dynamic>>(ApiConstants.completeTask(id));
|
|
|
|
Future<Response<Map<String, dynamic>>> skipTask(String id) =>
|
|
post<Map<String, dynamic>>(ApiConstants.skipTask(id));
|
|
|
|
Future<Response<Map<String, dynamic>>> createTask(Map<String, dynamic> data) =>
|
|
post<Map<String, dynamic>>(ApiConstants.tasks, data: data);
|
|
|
|
Future<Response<Map<String, dynamic>>> deleteTask(String id) =>
|
|
delete<Map<String, dynamic>>(ApiConstants.task(id));
|
|
|
|
// ── Streak helpers ───────────────────────────────────────────────
|
|
|
|
Future<Response<Map<String, dynamic>>> fetchStreaks() =>
|
|
get<Map<String, dynamic>>(ApiConstants.streaks);
|
|
|
|
Future<Response<Map<String, dynamic>>> completeStreak(String id) =>
|
|
post<Map<String, dynamic>>(ApiConstants.streak(id));
|
|
|
|
Future<Response<Map<String, dynamic>>> forgiveStreak(String id) =>
|
|
post<Map<String, dynamic>>(ApiConstants.forgiveStreak(id));
|
|
|
|
// ── Reward helpers ───────────────────────────────────────────────
|
|
|
|
Future<Response<Map<String, dynamic>>> generateReward(String taskId) =>
|
|
post<Map<String, dynamic>>(
|
|
ApiConstants.generateReward,
|
|
data: {'taskId': taskId},
|
|
);
|
|
|
|
// ── Time helpers ─────────────────────────────────────────────────
|
|
|
|
Future<Response<Map<String, dynamic>>> fetchTimeAccuracy() =>
|
|
get<Map<String, dynamic>>(ApiConstants.timeAccuracy);
|
|
}
|