From fe516de6e25e1f15410b0d10dbb780203b5f186b Mon Sep 17 00:00:00 2001 From: Timourka Date: Tue, 1 Oct 2024 18:21:37 +0400 Subject: [PATCH] =?UTF-8?q?=D0=BF=D0=BE=D1=87=D1=82=D0=B8=20=D1=81=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D0=B0=D0=BB=205=20=D0=BB=D0=B0=D0=B1=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/dtos/spells_dto.dart | 52 +++++++++++--- lib/data/dtos/spells_dto.g.dart | 37 ++++++++++ lib/data/mappers/spells_mapper.dart | 12 ++++ lib/data/repositories/api_interface.dart | 7 ++ lib/data/repositories/mock_repository.dart | 25 +++++++ lib/data/repositories/potter_repository.dart | 38 ++++++++++ lib/presentation/dialogs/error_dialog.dart | 32 +++++++++ lib/presentation/dialogs/show_dialog.dart | 13 ++++ lib/presentation/home_page/home_page.dart | 73 ++++++++++++-------- pubspec.lock | 24 +++++++ pubspec.yaml | 2 + 11 files changed, 278 insertions(+), 37 deletions(-) create mode 100644 lib/data/dtos/spells_dto.g.dart create mode 100644 lib/data/mappers/spells_mapper.dart create mode 100644 lib/data/repositories/api_interface.dart create mode 100644 lib/data/repositories/mock_repository.dart create mode 100644 lib/data/repositories/potter_repository.dart create mode 100644 lib/presentation/dialogs/error_dialog.dart create mode 100644 lib/presentation/dialogs/show_dialog.dart diff --git a/lib/data/dtos/spells_dto.dart b/lib/data/dtos/spells_dto.dart index 9460a68..97728a0 100644 --- a/lib/data/dtos/spells_dto.dart +++ b/lib/data/dtos/spells_dto.dart @@ -3,27 +3,59 @@ import 'package:json_annotation/json_annotation.dart'; part 'spells_dto.g.dart'; @JsonSerializable(createToJson: false) -class SpellDto{ +class SpellDto { final List? data; + const SpellDto({ + this.data, + }); + factory SpellDto.fromJson(Map json) => + _$SpellDtoFromJson(json); } @JsonSerializable(createToJson: false) -class SpellDto{ +class SpellDataDto { final String? id; - final String? type?; - final CharacterAttributesDataDto? attributes; + final String? type; + final SpellAttributesDataDto? attributes; + const SpellDataDto({ + this.id, + this.type, + this.attributes, + }); + factory SpellDataDto.fromJson(Map json) => + _$SpellDataDtoFromJson(json); } @JsonSerializable(createToJson: false) -class CharacterAttributesDataDto{ - final String? category; - final String? incantation; - final String? name; - final String? image; +class SpellAttributesDataDto { + final String? slug; // хз + final String? category; // категория + final String? creator; // создатель + final String? effect; // воздействие + final String? hand; // взмах + final String? image; // картинка + final String? incantation; // произношение + final String? light; // цвет + final String? name; // название + final String? wiki; // ссылка на вики + const SpellAttributesDataDto({ + this.slug, + this.category, + this.creator, + this.effect, + this.hand, + this.image, + this.incantation, + this.light, + this.name, + this.wiki, + }); -} \ No newline at end of file + factory SpellAttributesDataDto.fromJson(Map json) => + _$SpellAttributesDataDtoFromJson(json); +} diff --git a/lib/data/dtos/spells_dto.g.dart b/lib/data/dtos/spells_dto.g.dart new file mode 100644 index 0000000..c2f4cea --- /dev/null +++ b/lib/data/dtos/spells_dto.g.dart @@ -0,0 +1,37 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'spells_dto.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SpellDto _$SpellDtoFromJson(Map json) => SpellDto( + data: (json['data'] as List?) + ?.map((e) => SpellDataDto.fromJson(e as Map)) + .toList(), + ); + +SpellDataDto _$SpellDataDtoFromJson(Map json) => SpellDataDto( + id: json['id'] as String?, + type: json['type'] as String?, + attributes: json['attributes'] == null + ? null + : SpellAttributesDataDto.fromJson( + json['attributes'] as Map), + ); + +SpellAttributesDataDto _$SpellAttributesDataDtoFromJson( + Map json) => + SpellAttributesDataDto( + slug: json['slug'] as String?, + category: json['category'] as String?, + creator: json['creator'] as String?, + effect: json['effect'] as String?, + hand: json['hand'] as String?, + image: json['image'] as String?, + incantation: json['incantation'] as String?, + light: json['light'] as String?, + name: json['name'] as String?, + wiki: json['wiki'] as String?, + ); diff --git a/lib/data/mappers/spells_mapper.dart b/lib/data/mappers/spells_mapper.dart new file mode 100644 index 0000000..b041323 --- /dev/null +++ b/lib/data/mappers/spells_mapper.dart @@ -0,0 +1,12 @@ +import 'package:laba1/domain/models/card.dart'; + +import '../dtos/spells_dto.dart'; + +const _imagePlaceHolder ='https://cdn-icons-png.flaticon.com/512/1277/1277244.png'; + +extension SpellsDataDtoToModel on SpellDataDto { + CardData toDomain() => CardData( + attributes?.name ?? 'UNKNOWN', + imageUrl: attributes?.image ?? _imagePlaceHolder, + ); +} diff --git a/lib/data/repositories/api_interface.dart b/lib/data/repositories/api_interface.dart new file mode 100644 index 0000000..92eb3a8 --- /dev/null +++ b/lib/data/repositories/api_interface.dart @@ -0,0 +1,7 @@ +import '../../domain/models/card.dart'; + +typedef OnErrorCallback = void Function(String? error); + +abstract class ApiInterface { + Future?> loadData({OnErrorCallback? onError}); +} diff --git a/lib/data/repositories/mock_repository.dart b/lib/data/repositories/mock_repository.dart new file mode 100644 index 0000000..8607703 --- /dev/null +++ b/lib/data/repositories/mock_repository.dart @@ -0,0 +1,25 @@ +import '../../domain/models/card.dart'; +import 'api_interface.dart'; + +class MockRepository extends ApiInterface { + @override + Future?> loadData({OnErrorCallback? onError}) async { + return [ + CardData('orange', + imageUrl: + 'https://kuban24.tv/wp-content/uploads/2023/10/photo_2023-10-02_16-08-02.jpg'), + CardData("aboba", + imageUrl: + 'https://masterpiecer-images.s3.yandex.net/5fa453a2d4c51a7:upscaled'), + CardData("Hello world!!!", + imageUrl: + 'https://m.media-amazon.com/images/I/81YqUbAZ0GL._AC_UF1000,1000_QL80_.jpg'), + CardData('(=^・^=)', + imageUrl: + 'https://i.pinimg.com/236x/c8/cc/24/c8cc24bba37a25c009647b8875aae0e3.jpg'), + CardData('плохо быть старым ' + 'трезвым и больным, ' * 5, + imageUrl: + 'https://images.genius.com/c754c6f1755acee741881d55985a6c34.865x865x1.jpg'), + ]; + } +} diff --git a/lib/data/repositories/potter_repository.dart b/lib/data/repositories/potter_repository.dart new file mode 100644 index 0000000..1e2de1f --- /dev/null +++ b/lib/data/repositories/potter_repository.dart @@ -0,0 +1,38 @@ +import 'package:dio/dio.dart'; +import 'package:laba1/data/mappers/spells_mapper.dart'; +import 'package:pretty_dio_logger/pretty_dio_logger.dart'; + +import '../../domain/models/card.dart'; +import '../dtos/spells_dto.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, OnErrorCallback? onError}) async { + try { + const String url = '$_baseUrl/v1/spells'; + + final Response response = await _dio.get>( + url, + queryParameters: q != null ? {'filter[name_cont]': q} : null, + ); + + final SpellDto dto = SpellDto.fromJson( + response.data as Map); + final List? data = dto.data?.map((e) => e.toDomain()).toList(); + return data; + } on DioException catch (e) { + onError?.call(e.response?.statusMessage); + return null; + } + } +} \ No newline at end of file diff --git a/lib/presentation/dialogs/error_dialog.dart b/lib/presentation/dialogs/error_dialog.dart new file mode 100644 index 0000000..6e22dc7 --- /dev/null +++ b/lib/presentation/dialogs/error_dialog.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +class ErrorDialog extends StatelessWidget { + final String? error; + const ErrorDialog(this.error, {super.key}); + @override + Widget build(BuildContext context) { + return Center( + child: Material( + color: Colors.transparent, + child: Container( + margin: const EdgeInsets.all(36), + padding: const EdgeInsets.all(20), + decoration: const BoxDecoration( + color: Colors.grey, + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.error, color: Colors.white), + const SizedBox(height: 12), + Text( + error ?? 'UNKNOWN', + style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: Colors.white), + ), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/dialogs/show_dialog.dart b/lib/presentation/dialogs/show_dialog.dart new file mode 100644 index 0000000..4072397 --- /dev/null +++ b/lib/presentation/dialogs/show_dialog.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +import 'error_dialog.dart'; + +void showErrorDialog( + BuildContext context, { + required String? error, + }) { + showDialog( + context: context, + builder: (_) => ErrorDialog(error), + ); +} \ No newline at end of file diff --git a/lib/presentation/home_page/home_page.dart b/lib/presentation/home_page/home_page.dart index 13dc012..630e3d5 100644 --- a/lib/presentation/home_page/home_page.dart +++ b/lib/presentation/home_page/home_page.dart @@ -1,8 +1,11 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:laba1/data/repositories/potter_repository.dart'; import 'package:laba1/presentation/details_page/details_page.dart'; +import '../../data/repositories/mock_repository.dart'; import '../../domain/models/card.dart'; +import '../dialogs/show_dialog.dart'; part 'card.dart'; @@ -23,42 +26,58 @@ class MyHomePage extends StatelessWidget { } } -class Body extends StatelessWidget { +class Body extends StatefulWidget { const Body({Key? key}) : super(key: key); + @override + State createState() => _BodyState(); +} + +class _BodyState extends State { + final searchController = TextEditingController(); + late Future?> data; + final repo = PotterRepository(); + @override Widget build(BuildContext context) { - final List data = [ - CardData('orange', - imageUrl: - 'https://kuban24.tv/wp-content/uploads/2023/10/photo_2023-10-02_16-08-02.jpg'), - CardData("aboba", - imageUrl: - 'https://masterpiecer-images.s3.yandex.net/5fa453a2d4c51a7:upscaled'), - CardData("Hello world!!!", - imageUrl: - 'https://m.media-amazon.com/images/I/81YqUbAZ0GL._AC_UF1000,1000_QL80_.jpg'), - CardData('(=^・^=)', - imageUrl: - 'https://i.pinimg.com/236x/c8/cc/24/c8cc24bba37a25c009647b8875aae0e3.jpg'), - CardData('плохо быть старым ' + 'трезвым и больным, ' * 5, - imageUrl: - 'https://images.genius.com/c754c6f1755acee741881d55985a6c34.865x865x1.jpg'), - ]; + data = repo.loadData( + onError: (e) => showErrorDialog(context, error: e), + ); return Center( child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.center, - children: data - .map((e) => _Card.fromData( - e, - onLike: (bool isLiked) { - _showSnackBar(context, isLiked); - }, - onTap: () => _navToDetails(context, e), - )) - .toList(), + children: [ + CupertinoSearchTextField( + controller: searchController, + onChanged: (search) { + setState(() {}); + data = repo.loadData( + q: search, + onError: (e) => showErrorDialog(context, error: e), + ); + }, + ), + FutureBuilder?>( + future: data, + builder: (context, snapshot) => snapshot.hasData + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: snapshot.data + ?.map((e) => _Card.fromData( + e, + onLike: (bool isLiked) { + _showSnackBar(context, isLiked); + }, + onTap: () => _navToDetails(context, e), + )) + .toList() ?? + [], + ) + : const CircularProgressIndicator(), + ), + ], ), ), ); diff --git a/pubspec.lock b/pubspec.lock index d96279d..3bee088 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 9837cd1..551764d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,8 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 json_annotation: ^4.8.1 + dio: ^5.4.2+1 + pretty_dio_logger: ^1.3.1 dev_dependencies: flutter_test: sdk: flutter