diff --git a/lib/data/dtos/candies_dto.dart b/lib/data/dtos/candies_dto.dart new file mode 100644 index 0000000..71866ff --- /dev/null +++ b/lib/data/dtos/candies_dto.dart @@ -0,0 +1,66 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'candies_dto.g.dart'; + +@JsonSerializable(createToJson: false) +class CandiesResponseDto { + // Для пагинации + final int? totalCount; + final int? pageSize; + final int? currentPage; + final int? totalPages; + + // Сами данные сущности + final List? items; + + const CandiesResponseDto( + {this.totalCount, this.pageSize, this.currentPage, this.totalPages, this.items}); + + factory CandiesResponseDto.fromJson(Map json) => + _$CandiesResponseDtoFromJson(json); +} + +@JsonSerializable(createToJson: false) +class CandyItemDto { + final int? beanId; + final List? groupName; + final List? ingredients; + final String? flavorName; + final String? description; + final String? colorGroup; + final String? backgroundColor; + final String? imageUrl; + final bool? glutenFree; + final bool? sugarFree; + final bool? seasonal; + final bool? kosher; + + const CandyItemDto( + {this.beanId, + this.groupName, + this.ingredients, + this.flavorName, + this.description, + this.colorGroup, + this.backgroundColor, + this.imageUrl, + this.glutenFree, + this.sugarFree, + this.seasonal, + this.kosher}); + + factory CandyItemDto.fromJson(Map json) => _$CandyItemDtoFromJson(json); +} + +extension CandiesResponseDtoExtensions on CandiesResponseDto { + CandiesResponseDto copyWithFilteredItems(List? items) { + return CandiesResponseDto( + totalCount: items?.length, + pageSize: pageSize, + currentPage: currentPage, + totalPages: totalPages, + items: items, + ); + } +} + diff --git a/lib/data/mapper/candies_mapper.dart b/lib/data/mapper/candies_mapper.dart new file mode 100644 index 0000000..33e729a --- /dev/null +++ b/lib/data/mapper/candies_mapper.dart @@ -0,0 +1,24 @@ +import 'package:candystore/data/dtos/candies_dto.dart'; +import 'package:candystore/models/card_data.dart'; +import 'package:candystore/models/candy_data.dart'; + +extension CandyItemDtoToModel on CandyItemDto { + CardData toDomain() => CardData( + flavorName: flavorName ?? "UNKNOWN", + imageUrl: imageUrl ?? + "https://upload.wikimedia.org/wikipedia/commons/a/a2/Person_Image_Placeholder.png", + description: description ?? "UNKNOWN", + groupName: groupName ?? ["UNKNOWN"], + beanId: beanId); +} + +extension CandiesDtoToModel on CandiesResponseDto { + CandyData toDomain() => CandyData( + data: items?.map((e) => e.toDomain()).toList(), + nextPage: currentPage != null && totalPages != null && currentPage! < totalPages! + ? currentPage! + 1 + : null, + currentPage: currentPage, + totalPages: totalPages, + ); +} diff --git a/lib/data/repositories/api_interface.dart b/lib/data/repositories/api_interface.dart new file mode 100644 index 0000000..01ef601 --- /dev/null +++ b/lib/data/repositories/api_interface.dart @@ -0,0 +1,7 @@ +import 'package:candystore/models/candy_data.dart'; + +typedef OnErrorCallback = void Function(String? error); + +abstract class ApiInterface { + Future loadData({OnErrorCallback? onError}); +} diff --git a/lib/data/repositories/candy_repository.dart b/lib/data/repositories/candy_repository.dart new file mode 100644 index 0000000..d68a7d7 --- /dev/null +++ b/lib/data/repositories/candy_repository.dart @@ -0,0 +1,55 @@ +import 'dart:developer'; +import 'package:dio/dio.dart'; +import 'package:pretty_dio_logger/pretty_dio_logger.dart'; + +import 'package:candystore/data/dtos/candies_dto.dart'; +import 'package:candystore/data/mapper/candies_mapper.dart'; +import 'package:candystore/data/repositories/api_interface.dart'; +import 'package:candystore/models/candy_data.dart'; + +class CandyRepository extends ApiInterface { + static final Dio _dio = Dio(BaseOptions(connectTimeout: const Duration(seconds: 10))) + ..interceptors.add(PrettyDioLogger(request: true, requestHeader: true, requestBody: true)); + + static const String _baseUrl = "https://jellybellywikiapi.onrender.com/api"; + + @override + Future loadData( + {OnErrorCallback? onError, String? q, int page = 1, int pageSize = 10}) async { + try { + const String url = '$_baseUrl/Beans?'; + + final Response response = await _dio.get>( + url, + queryParameters: { + 'pageIndex': page, + 'pageSize': pageSize}, + ); + + final CandiesResponseDto dto = + CandiesResponseDto.fromJson(response.data as Map); + + // Если `q` указан, фильтруем данные + List? filteredItems; + if (q != null && q.isNotEmpty) { + final query = q.toLowerCase(); + filteredItems = dto.items?.where((item) { + final flavorName = item.flavorName?.toLowerCase() ?? ''; + return flavorName.contains(query); // Динамическое совпадение + }).toList(); + } + + // Создаём новый объект с отфильтрованными элементами (или возвращаем полный список) + final filteredDto = dto.copyWithFilteredItems(filteredItems ?? dto.items); + + return filteredDto.toDomain(); + } on DioException catch (e) { + log("DioException: $e"); + onError?.call(e.error?.toString()); + return null; + } catch (e) { + log('Unknown error: $e'); + return null; + } + } +}