Initial scaffold: FocusFlow ADHD Task Manager Flutter app
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>
This commit is contained in:
153
lib/core/network/api_client.dart
Normal file
153
lib/core/network/api_client.dart
Normal file
@@ -0,0 +1,153 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user