From 36efeceab0dc24e8a193f8bbaf0a39751266e068 Mon Sep 17 00:00:00 2001 From: Nastya_Kozlova Date: Sun, 27 Oct 2024 19:38:53 +0400 Subject: [PATCH] 4-5 labs --- lib/data/dtos/characters_dto.dart | 32 +++++ lib/data/dtos/characters_dto.g.dart | 33 +++++ lib/data/dtos/cocktailss_dto.dart | 26 ++++ lib/data/dtos/cocktailss_dto.g.dart | 21 +++ lib/data/mappers/characters_mapper.dart | 11 ++ lib/data/mappers/сocktail_mapper.dart | 10 ++ lib/domain/models/card.dart | 10 +- .../details_page/details_page.dart | 26 ++-- lib/presentation/home_page/card.dart | 10 +- lib/presentation/home_page/home_page.dart | 125 +++++++++++------- lib/repositories/CocktailRepository.dart | 39 ++++++ lib/repositories/api_interface.dart | 5 + lib/repositories/mock_repository.dart | 34 +++++ lib/repositories/potter_repository.dart | 36 +++++ pubspec.lock | 24 ++++ pubspec.yaml | 5 +- 16 files changed, 363 insertions(+), 84 deletions(-) create mode 100644 lib/data/dtos/characters_dto.dart create mode 100644 lib/data/dtos/characters_dto.g.dart create mode 100644 lib/data/dtos/cocktailss_dto.dart create mode 100644 lib/data/dtos/cocktailss_dto.g.dart create mode 100644 lib/data/mappers/characters_mapper.dart create mode 100644 lib/data/mappers/сocktail_mapper.dart create mode 100644 lib/repositories/CocktailRepository.dart create mode 100644 lib/repositories/api_interface.dart create mode 100644 lib/repositories/mock_repository.dart create mode 100644 lib/repositories/potter_repository.dart diff --git a/lib/data/dtos/characters_dto.dart b/lib/data/dtos/characters_dto.dart new file mode 100644 index 0000000..a968077 --- /dev/null +++ b/lib/data/dtos/characters_dto.dart @@ -0,0 +1,32 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'characters_dto.g.dart'; + +@JsonSerializable(createToJson: false) +class CharactersDto { + final List? data; + + const CharactersDto({this.data}); + factory CharactersDto.fromJson(Map json) => _$CharactersDtoFromJson(json); +} + +@JsonSerializable(createToJson: false) +class CharacterDataDto { + final String? id; + final String? type; + final CharacterAttributesDataDto? attributes; + + const CharacterDataDto({this.id, this.type, this.attributes}); + factory CharacterDataDto.fromJson(Map json) => _$CharacterDataDtoFromJson(json); +} + +@JsonSerializable(createToJson: false) +class CharacterAttributesDataDto{ + final String? name; + final String? born; + final String? died; + final String? image; + + const CharacterAttributesDataDto({this.name, this.born, this.died, this.image}); + factory CharacterAttributesDataDto.fromJson(Map json) => _$CharacterAttributesDataDtoFromJson(json); +} \ No newline at end of file diff --git a/lib/data/dtos/characters_dto.g.dart b/lib/data/dtos/characters_dto.g.dart new file mode 100644 index 0000000..bd8c5d9 --- /dev/null +++ b/lib/data/dtos/characters_dto.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'characters_dto.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CharactersDto _$CharactersDtoFromJson(Map json) => + CharactersDto( + data: (json['data'] as List?) + ?.map((e) => CharacterDataDto.fromJson(e as Map)) + .toList(), + ); + +CharacterDataDto _$CharacterDataDtoFromJson(Map json) => + CharacterDataDto( + id: json['id'] as String?, + type: json['type'] as String?, + attributes: json['attributes'] == null + ? null + : CharacterAttributesDataDto.fromJson( + json['attributes'] as Map), + ); + +CharacterAttributesDataDto _$CharacterAttributesDataDtoFromJson( + Map json) => + CharacterAttributesDataDto( + name: json['name'] as String?, + born: json['born'] as String?, + died: json['died'] as String?, + image: json['image'] as String?, + ); diff --git a/lib/data/dtos/cocktailss_dto.dart b/lib/data/dtos/cocktailss_dto.dart new file mode 100644 index 0000000..6051a08 --- /dev/null +++ b/lib/data/dtos/cocktailss_dto.dart @@ -0,0 +1,26 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'cocktailss_dto.g.dart'; + +@JsonSerializable(createToJson: false) +class CocktailssDto { + final List? drinks; + + const CocktailssDto({this.drinks}); + factory CocktailssDto.fromJson(Map json) => _$CocktailssDtoFromJson(json); +} + +@JsonSerializable(createToJson: false) +class CocktailDataDto { + final String? idDrink; + final String? strDrink; + final String? strDrinkThumb; + + const CocktailDataDto({ + this.idDrink, + this.strDrink, + this.strDrinkThumb, + }); + + factory CocktailDataDto.fromJson(Map json) => _$CocktailDataDtoFromJson(json); +} \ No newline at end of file diff --git a/lib/data/dtos/cocktailss_dto.g.dart b/lib/data/dtos/cocktailss_dto.g.dart new file mode 100644 index 0000000..2c5d2a7 --- /dev/null +++ b/lib/data/dtos/cocktailss_dto.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'cocktailss_dto.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CocktailssDto _$CocktailssDtoFromJson(Map json) => + CocktailssDto( + drinks: (json['drinks'] as List?) + ?.map((e) => CocktailDataDto.fromJson(e as Map)) + .toList(), + ); + +CocktailDataDto _$CocktailDataDtoFromJson(Map json) => + CocktailDataDto( + idDrink: json['idDrink'] as String?, + strDrink: json['strDrink'] as String?, + strDrinkThumb: json['strDrinkThumb'] as String?, + ); diff --git a/lib/data/mappers/characters_mapper.dart b/lib/data/mappers/characters_mapper.dart new file mode 100644 index 0000000..d870f7d --- /dev/null +++ b/lib/data/mappers/characters_mapper.dart @@ -0,0 +1,11 @@ + +import 'package:flutter_app/data/dtos/characters_dto.dart'; +import 'package:flutter_app/domain/models/card.dart'; + +extension CharacterDataDtoToModel on CharacterDataDto { + CardData toDomain() => CardData( + attributes?.name ?? 'UNKNOWN', + imageUrl: attributes?.image, + article: '${attributes?.born} - ${attributes?.died}', + ); +} \ No newline at end of file diff --git a/lib/data/mappers/сocktail_mapper.dart b/lib/data/mappers/сocktail_mapper.dart new file mode 100644 index 0000000..2bf3dd6 --- /dev/null +++ b/lib/data/mappers/сocktail_mapper.dart @@ -0,0 +1,10 @@ +import 'package:flutter_app/data/dtos/cocktailss_dto.dart'; +import 'package:flutter_app/domain/models/card.dart'; + +extension CocktailDataDtoToModel on CocktailDataDto { + CardData toDomain() => CardData( + strDrink ?? 'UNKNOWN', + article: idDrink ?? 'No ID', + imageUrl: strDrinkThumb, + ); +} \ No newline at end of file diff --git a/lib/domain/models/card.dart b/lib/domain/models/card.dart index 3abc31a..b96ccc8 100644 --- a/lib/domain/models/card.dart +++ b/lib/domain/models/card.dart @@ -3,18 +3,12 @@ import 'package:flutter/cupertino.dart'; class CardData { final String text; final String article; - final IconData icon; final String? imageUrl; - final String color; - final String price; - CardData( this.text, { required this.article, - required this.icon, this.imageUrl, - required this.color, - required this.price, }); -} \ No newline at end of file +} + diff --git a/lib/presentation/details_page/details_page.dart b/lib/presentation/details_page/details_page.dart index 2ca6b47..d037aa7 100644 --- a/lib/presentation/details_page/details_page.dart +++ b/lib/presentation/details_page/details_page.dart @@ -1,11 +1,14 @@ +import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_app/domain/models/card.dart'; class DetailsPage extends StatelessWidget { final CardData data; + final double randomPrice; - const DetailsPage(this.data, {super.key}); + DetailsPage(this.data, {super.key}) + : randomPrice = 10 + Random().nextDouble() * 30; @override Widget build(BuildContext context) { @@ -16,7 +19,9 @@ class DetailsPage extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.only(bottom: 16.0), - child: Image.network(data.imageUrl ?? ' ',), + child: Image.network( + data.imageUrl ?? ' ', + ), ), Padding( padding: const EdgeInsets.only(left: 10.0, bottom: 4.0), @@ -29,23 +34,12 @@ class DetailsPage extends StatelessWidget { ), const SizedBox(height: 4), Text( - 'Артикул: ' + data.article, - style: TextStyle( - fontSize: 18, - ), - ), - Text( - 'Цена: ' + data.price, - style: TextStyle( - fontSize: 18, - ), - ), - Text( - 'Цвет: ' + data.color, + 'Price: \$${randomPrice.toStringAsFixed(2)}', style: TextStyle( fontSize: 18, ), ), + const SizedBox(height: 16), ], ), ), @@ -54,3 +48,5 @@ class DetailsPage extends StatelessWidget { ); } } + + diff --git a/lib/presentation/home_page/card.dart b/lib/presentation/home_page/card.dart index ad10f10..9e21f61 100644 --- a/lib/presentation/home_page/card.dart +++ b/lib/presentation/home_page/card.dart @@ -5,18 +5,14 @@ typedef OnLikeCallback = void Function(String title, bool isLiked)?; class _Card extends StatefulWidget { final String text; final String article; - final IconData icon; final String? imageUrl; - final String price; final OnLikeCallback onLike; final VoidCallback? onTap; const _Card( this.text, { - this.icon = Icons.ac_unit_outlined, required this.article, this.imageUrl, - required this.price, this.onLike, this.onTap, }); @@ -30,9 +26,7 @@ class _Card extends StatefulWidget { _Card( data.text, article: data.article, - icon: data.icon, imageUrl: data.imageUrl, - price: data.price, onLike: onLike, onTap: onTap, ); @@ -83,10 +77,10 @@ class _CardState extends State<_Card> { style: Theme.of(context).textTheme.headlineLarge, ), const SizedBox(height: 4), - Text( + /*Text( widget.price, style: Theme.of(context).textTheme.headlineSmall, - ), + ),*/ ], ), ), diff --git a/lib/presentation/home_page/home_page.dart b/lib/presentation/home_page/home_page.dart index 7c52aa9..ee2933c 100644 --- a/lib/presentation/home_page/home_page.dart +++ b/lib/presentation/home_page/home_page.dart @@ -1,9 +1,16 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_app/repositories/mock_repository.dart'; import '../../domain/models/card.dart'; +import '../../repositories/CocktailRepository.dart'; +import '../../repositories/potter_repository.dart'; import '../details_page/details_page.dart'; + +import 'package:dio/dio.dart'; + + part 'card.dart'; class MyHomePage extends StatefulWidget { @@ -35,78 +42,94 @@ class _MyHomePageState extends State { } } -class Body extends StatelessWidget { +class Body extends StatefulWidget { const Body({super.key}); @override - Widget build(BuildContext context) { - final data = [ - CardData( - 'Свитер', - article: '4359358830', - icon: Icons.hail, - imageUrl: 'https://imgcdn.loverepublic.ru/upload/images/43593/4359358830_55.jpg', - color: ' черный', - price: '6 999 ₽', - ), - CardData( - 'Джинсы', - article: '4450204304', - icon: Icons.hail, - imageUrl: 'https://imgcdn.loverepublic.ru/upload/images/44505/4450515115_63.jpg', - color: 'синий / индиго', - price: '16 999 ₽', - ), - CardData( - 'Брюки', - article: '4450515115', - icon: Icons.hail, - imageUrl: 'https://imgcdn.loverepublic.ru/upload/images/44502/4450204707_57.jpg', - color: 'черный / антрацитовый', - price: '5 999 ₽', - ), + _BodyState createState() => _BodyState(); +} - ]; - return Center( - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: data.map((data) { - return _Card.fromData( - data, - onLike: (String title, bool isLiked) => - _showSnackBar(context, title, isLiked), - onTap: () => _navToDetails(context, data), - ); - }).toList(), - ), +class _BodyState extends State { + late TextEditingController searchController; + late Future?> data; + /*final PotterRepository repo = PotterRepository();*/ + final CocktailRepository repo = CocktailRepository(); + + @override + void initState() { + super.initState(); + searchController = TextEditingController(); + data = repo.loadData(null); + } + + @override + void dispose() { + searchController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(12), + child: CupertinoSearchTextField( + controller: searchController, + onChanged: (search) { + setState(() { + data = repo.loadData(search.isNotEmpty ? search : null); + }); + }, + ), + ), + Expanded( + child: Center( + child: FutureBuilder?>( + future: data, + builder: (context, snapshot) => + SingleChildScrollView( + child: snapshot.hasData + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: snapshot.data?.map((data) { + return _Card.fromData( + data, + onLike: (String title, bool isLiked) => + _showSnackBar(context, title, isLiked), + onTap: () => _navToDetails(context, data), + ); + }).toList() ?? [], + ) + : const CircularProgressIndicator(), + ), + ), + ), + ), + ], ), ); } - void _navToDetails (BuildContext context, CardData data) { + void _navToDetails(BuildContext context, CardData data) { Navigator.push( context, CupertinoPageRoute(builder: (context) => DetailsPage(data)), ); } - void _showSnackBar(BuildContext context, String title, bool isLiked) { WidgetsBinding.instance.addPostFrameCallback((_) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( - '$title ${isLiked ? 'добавлено в избранное' : 'удалено из избранного'}', - style: Theme - .of(context) - .textTheme - .bodyLarge, + 'Racoon $title ${isLiked ? 'liked!' : 'disliked :('}', + style: Theme.of(context).textTheme.bodyLarge, ), backgroundColor: Colors.orangeAccent, duration: const Duration(seconds: 1), )); }); } -} - - +} \ No newline at end of file diff --git a/lib/repositories/CocktailRepository.dart b/lib/repositories/CocktailRepository.dart new file mode 100644 index 0000000..0b4a76c --- /dev/null +++ b/lib/repositories/CocktailRepository.dart @@ -0,0 +1,39 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_app/domain/models/card.dart'; +import 'package:pretty_dio_logger/pretty_dio_logger.dart'; +import 'api_interface.dart'; + +class CocktailRepository extends ApiInterface { + static final Dio _dio = Dio() + ..interceptors.add(PrettyDioLogger( + requestHeader: true, + requestBody: true, + )); + + static const String _baseUrl = 'https://www.thecocktaildb.com/api/json/v1/1'; + + @override + Future?> loadData(String? q) async { + try { + final String url = q != null + ? '$_baseUrl/search.php?s=$q' // Поиск по названию + : '$_baseUrl/filter.php?c=Ordinary_Drink'; // Получение списка коктейлей по категории + + final Response response = await _dio.get>(url); + + if (response.data != null && response.data['drinks'] != null) { + final List data = (response.data['drinks'] as List).map((drink) { + return CardData( + drink['strDrink'], // Название коктейля + article: drink['idDrink'], // ID коктейля + imageUrl: drink['strDrinkThumb'], // Фото коктейля + ); + }).toList(); + return data; + } + return null; + } on DioException catch (e) { + return null; + } + } +} \ No newline at end of file diff --git a/lib/repositories/api_interface.dart b/lib/repositories/api_interface.dart new file mode 100644 index 0000000..6602e11 --- /dev/null +++ b/lib/repositories/api_interface.dart @@ -0,0 +1,5 @@ +import 'package:flutter_app/domain/models/card.dart'; + +abstract class ApiInterface { + Future?> loadData(String? q); +} \ No newline at end of file diff --git a/lib/repositories/mock_repository.dart b/lib/repositories/mock_repository.dart new file mode 100644 index 0000000..546eed4 --- /dev/null +++ b/lib/repositories/mock_repository.dart @@ -0,0 +1,34 @@ +import 'package:flutter_app/domain/models/card.dart'; +import 'package:flutter_app/repositories/api_interface.dart'; + +class MockRepository extends ApiInterface { + @override + Future?> loadData(String? q) async { + return [ + CardData( + 'Свитер', + article: '4359358830', + /*icon: Icons.hail,*/ + imageUrl: 'https://imgcdn.loverepublic.ru/upload/images/43593/4359358830_55.jpg', + /*color: ' черный', + price: '6 999 ₽',*/ + ), + CardData( + 'Джинсы', + article: '4450204304', + /*icon: Icons.hail,*/ + imageUrl: 'https://imgcdn.loverepublic.ru/upload/images/44505/4450515115_63.jpg', + /*color: 'синий / индиго', + price: '16 999 ₽',*/ + ), + CardData( + 'Брюки', + article: '4450515115', + /*icon: Icons.hail,*/ + imageUrl: 'https://imgcdn.loverepublic.ru/upload/images/44502/4450204707_57.jpg', + /*color: 'черный / антрацитовый', + price: '5 999 ₽',*/ + ), + ]; + } +} \ No newline at end of file diff --git a/lib/repositories/potter_repository.dart b/lib/repositories/potter_repository.dart new file mode 100644 index 0000000..178fe88 --- /dev/null +++ b/lib/repositories/potter_repository.dart @@ -0,0 +1,36 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_app/data/mappers/characters_mapper.dart'; +import 'package:pretty_dio_logger/pretty_dio_logger.dart'; + +import '../data/dtos/characters_dto.dart'; +import '../domain/models/card.dart'; +import 'api_interface.dart'; + +class PotterRepository extends ApiInterface { + static final Dio _dio = Dio() + ..interceptors.add(PrettyDioLogger( + requestHeader: true, + requestBody: true, + )); + + static const String _baseUrl = 'https://api.potterdb.com'; + + @override + Future?> loadData(String? q) async { + try { + const String url = '$_baseUrl/v1/characters'; + + final Response response = await _dio.get>( + url, + queryParameters: q != null ? {"filter[name_cont]": q} : null, + ); + + final CharactersDto dto = CharactersDto.fromJson( + response.data as Map); + final List? data = dto.data?.map((e) => e.toDomain()).toList(); + return data; + } on DioException catch (e) { + return null; + } + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 5bb78b2..2d7f953 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -182,6 +182,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.7" + dio: + dependency: "direct main" + description: + name: dio + sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" + url: "https://pub.dev" + source: hosted + version: "5.7.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" + url: "https://pub.dev" + source: hosted + version: "2.0.0" fake_async: dependency: transitive description: @@ -400,6 +416,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + pretty_dio_logger: + dependency: "direct main" + description: + name: pretty_dio_logger + sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407" + url: "https://pub.dev" + source: hosted + version: "1.4.0" pub_semver: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4545414..8540015 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,8 +35,9 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 - - json_annotation: ^4.8.1 + json_annotation: ^4.9.0 + dio: ^5.4.2+1 + pretty_dio_logger: ^1.3.1 dev_dependencies: flutter_test: