This commit is contained in:
Nastya_Kozlova 2024-10-27 19:38:53 +04:00
parent 42c2570fff
commit 36efeceab0
16 changed files with 363 additions and 84 deletions

View File

@ -0,0 +1,32 @@
import 'package:json_annotation/json_annotation.dart';
part 'characters_dto.g.dart';
@JsonSerializable(createToJson: false)
class CharactersDto {
final List<CharacterDataDto>? data;
const CharactersDto({this.data});
factory CharactersDto.fromJson(Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> json) => _$CharacterAttributesDataDtoFromJson(json);
}

View File

@ -0,0 +1,33 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'characters_dto.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
CharactersDto _$CharactersDtoFromJson(Map<String, dynamic> json) =>
CharactersDto(
data: (json['data'] as List<dynamic>?)
?.map((e) => CharacterDataDto.fromJson(e as Map<String, dynamic>))
.toList(),
);
CharacterDataDto _$CharacterDataDtoFromJson(Map<String, dynamic> json) =>
CharacterDataDto(
id: json['id'] as String?,
type: json['type'] as String?,
attributes: json['attributes'] == null
? null
: CharacterAttributesDataDto.fromJson(
json['attributes'] as Map<String, dynamic>),
);
CharacterAttributesDataDto _$CharacterAttributesDataDtoFromJson(
Map<String, dynamic> json) =>
CharacterAttributesDataDto(
name: json['name'] as String?,
born: json['born'] as String?,
died: json['died'] as String?,
image: json['image'] as String?,
);

View File

@ -0,0 +1,26 @@
import 'package:json_annotation/json_annotation.dart';
part 'cocktailss_dto.g.dart';
@JsonSerializable(createToJson: false)
class CocktailssDto {
final List<CocktailDataDto>? drinks;
const CocktailssDto({this.drinks});
factory CocktailssDto.fromJson(Map<String, dynamic> 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<String, dynamic> json) => _$CocktailDataDtoFromJson(json);
}

View File

@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'cocktailss_dto.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
CocktailssDto _$CocktailssDtoFromJson(Map<String, dynamic> json) =>
CocktailssDto(
drinks: (json['drinks'] as List<dynamic>?)
?.map((e) => CocktailDataDto.fromJson(e as Map<String, dynamic>))
.toList(),
);
CocktailDataDto _$CocktailDataDtoFromJson(Map<String, dynamic> json) =>
CocktailDataDto(
idDrink: json['idDrink'] as String?,
strDrink: json['strDrink'] as String?,
strDrinkThumb: json['strDrinkThumb'] as String?,
);

View File

@ -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}',
);
}

View File

@ -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,
);
}

View File

@ -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,
});
}
}

View File

@ -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 {
);
}
}

View File

@ -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,
),
),*/
],
),
),

View File

@ -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<MyHomePage> {
}
}
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<Body> {
late TextEditingController searchController;
late Future<List<CardData>?> 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<List<CardData>?>(
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),
));
});
}
}
}

View File

@ -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<List<CardData>?> loadData(String? q) async {
try {
final String url = q != null
? '$_baseUrl/search.php?s=$q' // Поиск по названию
: '$_baseUrl/filter.php?c=Ordinary_Drink'; // Получение списка коктейлей по категории
final Response<dynamic> response = await _dio.get<Map<dynamic, dynamic>>(url);
if (response.data != null && response.data['drinks'] != null) {
final List<CardData> 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;
}
}
}

View File

@ -0,0 +1,5 @@
import 'package:flutter_app/domain/models/card.dart';
abstract class ApiInterface {
Future<List<CardData>?> loadData(String? q);
}

View File

@ -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<List<CardData>?> 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 ₽',*/
),
];
}
}

View File

@ -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<List<CardData>?> loadData(String? q) async {
try {
const String url = '$_baseUrl/v1/characters';
final Response<dynamic> response = await _dio.get<Map<dynamic, dynamic>>(
url,
queryParameters: q != null ? {"filter[name_cont]": q} : null,
);
final CharactersDto dto = CharactersDto.fromJson(
response.data as Map<String, dynamic>);
final List<CardData>? data = dto.data?.map((e) => e.toDomain()).toList();
return data;
} on DioException catch (e) {
return null;
}
}
}

View File

@ -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:

View File

@ -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: