216 lines
6.2 KiB
Dart
216 lines
6.2 KiB
Dart
import 'package:dio/dio.dart';
|
|
import '../../../../core/constants/api_constants.dart';
|
|
import '../../../../core/errors/exceptions.dart';
|
|
import '../../../../core/network/dio_client.dart';
|
|
import '../models/category_model.dart';
|
|
|
|
/// Remote data source for categories API operations
|
|
abstract class CategoryRemoteDataSource {
|
|
/// Fetch all categories from the API
|
|
Future<List<CategoryModel>> fetchCategories();
|
|
|
|
/// Fetch a single category by ID
|
|
Future<CategoryModel> fetchCategoryById(String id);
|
|
|
|
/// Sync categories (bulk update/create)
|
|
Future<void> syncCategories(List<CategoryModel> categories);
|
|
}
|
|
|
|
/// Implementation of CategoryRemoteDataSource using Dio
|
|
class CategoryRemoteDataSourceImpl implements CategoryRemoteDataSource {
|
|
final DioClient _dioClient;
|
|
|
|
CategoryRemoteDataSourceImpl(this._dioClient);
|
|
|
|
@override
|
|
Future<List<CategoryModel>> fetchCategories() async {
|
|
try {
|
|
final response = await _dioClient.get(ApiConstants.categories);
|
|
|
|
if (response.statusCode == ApiConstants.statusOk) {
|
|
final data = response.data;
|
|
|
|
// Handle different response structures
|
|
List<dynamic> categoriesJson;
|
|
if (data is Map<String, dynamic>) {
|
|
// Response wrapped in object: { "categories": [...] }
|
|
categoriesJson = data['categories'] as List<dynamic>? ??
|
|
data['data'] as List<dynamic>? ??
|
|
[];
|
|
} else if (data is List) {
|
|
// Direct array response
|
|
categoriesJson = data;
|
|
} else {
|
|
throw DataParsingException('Unexpected response format');
|
|
}
|
|
|
|
return categoriesJson
|
|
.map((json) => CategoryModel.fromJson(json as Map<String, dynamic>))
|
|
.toList();
|
|
} else {
|
|
throw ServerException(
|
|
'Failed to fetch categories',
|
|
response.statusCode ?? 500,
|
|
);
|
|
}
|
|
} on AppException {
|
|
rethrow;
|
|
} catch (e, stackTrace) {
|
|
throw NetworkException(
|
|
'Failed to fetch categories: ${e.toString()}',
|
|
null,
|
|
e,
|
|
stackTrace,
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<CategoryModel> fetchCategoryById(String id) async {
|
|
try {
|
|
final response = await _dioClient.get(ApiConstants.categoryById(id));
|
|
|
|
if (response.statusCode == ApiConstants.statusOk) {
|
|
final data = response.data;
|
|
|
|
// Handle different response structures
|
|
Map<String, dynamic> categoryJson;
|
|
if (data is Map<String, dynamic>) {
|
|
// Check if category is wrapped in a key
|
|
categoryJson = data['category'] as Map<String, dynamic>? ??
|
|
data['data'] as Map<String, dynamic>? ??
|
|
data;
|
|
} else {
|
|
throw DataParsingException('Unexpected response format');
|
|
}
|
|
|
|
return CategoryModel.fromJson(categoryJson);
|
|
} else if (response.statusCode == ApiConstants.statusNotFound) {
|
|
throw NotFoundException('Category with ID $id not found');
|
|
} else {
|
|
throw ServerException(
|
|
'Failed to fetch category',
|
|
response.statusCode ?? 500,
|
|
);
|
|
}
|
|
} on AppException {
|
|
rethrow;
|
|
} catch (e, stackTrace) {
|
|
throw NetworkException(
|
|
'Failed to fetch category: ${e.toString()}',
|
|
null,
|
|
e,
|
|
stackTrace,
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> syncCategories(List<CategoryModel> categories) async {
|
|
try {
|
|
final categoriesJson = categories.map((c) => c.toJson()).toList();
|
|
|
|
final response = await _dioClient.post(
|
|
ApiConstants.syncCategories,
|
|
data: {
|
|
'categories': categoriesJson,
|
|
},
|
|
);
|
|
|
|
if (response.statusCode != ApiConstants.statusOk &&
|
|
response.statusCode != ApiConstants.statusCreated) {
|
|
throw ServerException(
|
|
'Failed to sync categories',
|
|
response.statusCode ?? 500,
|
|
);
|
|
}
|
|
} on AppException {
|
|
rethrow;
|
|
} catch (e, stackTrace) {
|
|
throw NetworkException(
|
|
'Failed to sync categories: ${e.toString()}',
|
|
null,
|
|
e,
|
|
stackTrace,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Mock implementation for testing and development
|
|
class CategoryRemoteDataSourceMock implements CategoryRemoteDataSource {
|
|
/// Simulate network delay
|
|
Future<void> _delay() async {
|
|
await Future.delayed(
|
|
Duration(milliseconds: ApiConstants.mockApiDelay),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<List<CategoryModel>> fetchCategories() async {
|
|
await _delay();
|
|
|
|
// Return mock categories
|
|
return [
|
|
CategoryModel(
|
|
id: '1',
|
|
name: 'Electronics',
|
|
description: 'Electronic devices and accessories',
|
|
iconPath: 'assets/icons/electronics.png',
|
|
color: '#2196F3',
|
|
productCount: 25,
|
|
createdAt: DateTime.now().subtract(const Duration(days: 60)),
|
|
),
|
|
CategoryModel(
|
|
id: '2',
|
|
name: 'Clothing',
|
|
description: 'Fashion and apparel',
|
|
iconPath: 'assets/icons/clothing.png',
|
|
color: '#E91E63',
|
|
productCount: 50,
|
|
createdAt: DateTime.now().subtract(const Duration(days: 50)),
|
|
),
|
|
CategoryModel(
|
|
id: '3',
|
|
name: 'Food & Beverages',
|
|
description: 'Food, drinks, and snacks',
|
|
iconPath: 'assets/icons/food.png',
|
|
color: '#FF9800',
|
|
productCount: 100,
|
|
createdAt: DateTime.now().subtract(const Duration(days: 40)),
|
|
),
|
|
CategoryModel(
|
|
id: '4',
|
|
name: 'Home & Garden',
|
|
description: 'Home improvement and garden supplies',
|
|
iconPath: 'assets/icons/home.png',
|
|
color: '#4CAF50',
|
|
productCount: 30,
|
|
createdAt: DateTime.now().subtract(const Duration(days: 30)),
|
|
),
|
|
];
|
|
}
|
|
|
|
@override
|
|
Future<CategoryModel> fetchCategoryById(String id) async {
|
|
await _delay();
|
|
|
|
// Return mock category
|
|
return CategoryModel(
|
|
id: id,
|
|
name: 'Sample Category $id',
|
|
description: 'This is sample category $id',
|
|
iconPath: 'assets/icons/category.png',
|
|
color: '#2196F3',
|
|
productCount: 10,
|
|
createdAt: DateTime.now().subtract(const Duration(days: 30)),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> syncCategories(List<CategoryModel> categories) async {
|
|
await _delay();
|
|
// Mock sync - do nothing
|
|
}
|
|
}
|