From d1c005db181d58411bbcaf8c378356211f3d399a Mon Sep 17 00:00:00 2001 From: sofia7ya Date: Wed, 11 Dec 2024 12:04:34 +0400 Subject: [PATCH] lab5 done --- lib/data/dtos/potions_dto.g.dart | 32 ++++++ lib/data/mappers/potions_mapper.dart | 17 ++- lib/data/repositories/api_interface.dart | 7 ++ lib/data/repositories/mock_repository.dart | 30 +++++ lib/data/repositories/potter_repository.dart | 35 ++++++ lib/main.dart | 2 +- lib/presentation/dialogs/error_dialog.dart | 33 ++++++ lib/presentation/dialogs/show_dialog.dart | 12 ++ lib/presentation/home_page/card.dart | 41 +++---- lib/presentation/home_page/home_page.dart | 109 +++++++++++-------- pubspec.lock | 24 ++++ pubspec.yaml | 3 +- 12 files changed, 275 insertions(+), 70 deletions(-) create mode 100644 lib/data/dtos/potions_dto.g.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/potions_dto.g.dart b/lib/data/dtos/potions_dto.g.dart new file mode 100644 index 0000000..e72ffe0 --- /dev/null +++ b/lib/data/dtos/potions_dto.g.dart @@ -0,0 +1,32 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'potions_dto.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PotionsDto _$PotionsDtoFromJson(Map json) => PotionsDto( + data: (json['data'] as List?) + ?.map((e) => PotionDataDto.fromJson(e as Map)) + .toList(), + ); + +PotionDataDto _$PotionDataDtoFromJson(Map json) => + PotionDataDto( + id: json['id'] as String?, + type: json['type'] as String?, + attributes: json['attributes'] == null + ? null + : PotionAttributesDataDto.fromJson( + json['attributes'] as Map), + ); + +PotionAttributesDataDto _$PotionAttributesDataDtoFromJson( + Map json) => + PotionAttributesDataDto( + name: json['name'] as String?, + characteristics: json['characteristics'] as String?, + effect: json['effect'] as String?, + image: json['image'] as String?, + ); diff --git a/lib/data/mappers/potions_mapper.dart b/lib/data/mappers/potions_mapper.dart index 61e3ad1..2225251 100644 --- a/lib/data/mappers/potions_mapper.dart +++ b/lib/data/mappers/potions_mapper.dart @@ -1,10 +1,23 @@ import 'package:flutter_labs/data/dtos/potions_dto.dart'; import 'package:flutter_labs/domain/models/card.dart'; +const _imagePlaceholder = + 'https://cdn-icons-png.flaticon.com/512/4036/4036418.png'; + extension PotionDataDtoToModel on PotionDataDto { CardData toDomain() => CardData( attributes?.name ?? 'UNKNOWN', - imageUrl: attributes?.image, - descriptionText: '${attributes?.characteristics} - ${attributes?.effect}', + imageUrl: attributes?.image ?? _imagePlaceholder, + descriptionText: _makeDescriptionText(attributes?.characteristics, attributes?.effect), ); + + String _makeDescriptionText(String? characteristics, String? effect) { + return characteristics != null && effect != null + ? '$characteristics - $effect' + : characteristics != null + ? 'characteristics: $characteristics' + : effect != null + ? 'effect: $effect' + : ''; + } } \ No newline at end of file diff --git a/lib/data/repositories/api_interface.dart b/lib/data/repositories/api_interface.dart new file mode 100644 index 0000000..3d0ec7d --- /dev/null +++ b/lib/data/repositories/api_interface.dart @@ -0,0 +1,7 @@ +import 'package:flutter_labs/domain/models/card.dart'; + +typedef OnErrorCallback = void Function(String? error); + +abstract class ApiInterface { + Future?> loadData({OnErrorCallback? onError}); +} \ No newline at end of file diff --git a/lib/data/repositories/mock_repository.dart b/lib/data/repositories/mock_repository.dart new file mode 100644 index 0000000..93263e3 --- /dev/null +++ b/lib/data/repositories/mock_repository.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_labs/data/repositories/api_interface.dart'; +import 'package:flutter_labs/domain/models/card.dart'; + +class MockRepository extends ApiInterface { + @override + Future?> loadData({OnErrorCallback? onError}) async { + return [ + CardData( + 'Mouse', + descriptionText: 'hehehe', + imageUrl: + 'https://sun9-26.userapi.com/impg/sB_tLPWPCIqxQWmlbcmlRYiw1ibFb70_QMtNwg/56qpyc_C8Go.jpg?size=736x711&quality=95&sign=8f7163b54538a2e7bad5f36a857485d4&type=album', + ), + CardData( + 'Mouse2', + descriptionText: 'pretty face', + icon: Icons.hail, + imageUrl: + 'https://sun165-1.userapi.com/impg/EVLbaLilqr8xw5tsqZLQQb6DSYrdKo7Q9sYSsw/H4FRwyMR6Ec.jpg?size=1280x960&quality=96&sign=f606e4ae3d1ccd27917cd1ffa6d91e58&type=album', + ), + CardData( + 'Mouse3', + descriptionText: 'I like hamsters', + icon: Icons.warning_amber, + imageUrl: 'https://sun34-1.userapi.com/impg/_DLT-op0LbBdgh5h-ILvC7IMDY5kbLR349v7vA/tX7vtk6mNlA.jpg?size=736x736&quality=96&sign=47f2b0f63bf249c62f4498fb637695d5&type=album', + ), + ]; + } +} \ No newline at end of file diff --git a/lib/data/repositories/potter_repository.dart b/lib/data/repositories/potter_repository.dart new file mode 100644 index 0000000..404fed5 --- /dev/null +++ b/lib/data/repositories/potter_repository.dart @@ -0,0 +1,35 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_labs/data/dtos/potions_dto.dart'; +import 'package:flutter_labs/data/mappers/potions_mapper.dart'; +import 'package:flutter_labs/data/repositories/api_interface.dart'; +import 'package:flutter_labs/domain/models/card.dart'; +import 'package:pretty_dio_logger/pretty_dio_logger.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/potions'; + + final Response response = await _dio.get>( + url, + queryParameters: q != null ? {'filter[name_cont]': q} : null, + ); + + final PotionsDto dto = PotionsDto.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/main.dart b/lib/main.dart index f6def57..1ad6ee7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,7 +16,7 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), - home: const HomePage(title: 'The coolest hamsters on earth ^^'), + home: const HomePage(title: 'The most unusual liquids O_o'), ); } } diff --git a/lib/presentation/dialogs/error_dialog.dart b/lib/presentation/dialogs/error_dialog.dart new file mode 100644 index 0000000..d8c6525 --- /dev/null +++ b/lib/presentation/dialogs/error_dialog.dart @@ -0,0 +1,33 @@ +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..aaaa881 --- /dev/null +++ b/lib/presentation/dialogs/show_dialog.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_labs/presentation/dialogs/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/card.dart b/lib/presentation/home_page/card.dart index 3221934..db12761 100644 --- a/lib/presentation/home_page/card.dart +++ b/lib/presentation/home_page/card.dart @@ -80,25 +80,26 @@ class _CardState extends State<_Card> { errorBuilder: (_, __, ___) => const Placeholder(), ), ), - Align( - alignment: Alignment.bottomLeft, - child: Container( - decoration: const BoxDecoration( - color: Colors.orangeAccent, - borderRadius: BorderRadius.only( - topRight: Radius.circular(20), - // - )), - padding: const EdgeInsets.fromLTRB(8, 2, 8, 2), - child: Text( - 'скидок нет', - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith(color: Colors.black), - ), - ), - ), + // Пусть будет для скидки + //Align( + // alignment: Alignment.bottomLeft, + // child: Container( + // decoration: const BoxDecoration( + // color: Colors.orangeAccent, + // borderRadius: BorderRadius.only( + // topRight: Radius.circular(20), + // // + // )), + // padding: const EdgeInsets.fromLTRB(8, 2, 8, 2), + // child: Text( + // 'скидок нет', + // style: Theme.of(context) + // .textTheme + // .bodyMedium + // ?.copyWith(color: Colors.black), + // ), + // ), + //), ], ), ), @@ -111,7 +112,7 @@ class _CardState extends State<_Card> { children: [ Text( widget.text, - style: Theme.of(context).textTheme.headlineLarge, + style: Theme.of(context).textTheme.headlineSmall, ), Text( widget.descriptionText, diff --git a/lib/presentation/home_page/home_page.dart b/lib/presentation/home_page/home_page.dart index 8f42688..79096d9 100644 --- a/lib/presentation/home_page/home_page.dart +++ b/lib/presentation/home_page/home_page.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_labs/data/repositories/potter_repository.dart'; import 'package:flutter_labs/presentation/details_page/details_page.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_labs/domain/models/card.dart'; +import 'package:flutter_labs/presentation/dialogs/show_dialog.dart'; part 'card.dart'; @@ -15,74 +17,89 @@ class HomePage extends StatefulWidget { } class _HomePageState extends State { - final Color _color = Colors.orangeAccent; @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: _color, - title: Text(widget.title), - ), - body: const Body(), - ); + return const Scaffold(body: Body()); } } -class Body extends StatelessWidget { +class Body extends StatefulWidget { const Body({super.key}); // ключи @override - Widget build(BuildContext context) { - final data = [ - CardData('mouse', - descriptionText: 'hahaha', - icon: Icons.abc, - imageUrl: - 'https://sun9-26.userapi.com/impg/sB_tLPWPCIqxQWmlbcmlRYiw1ibFb70_QMtNwg/56qpyc_C8Go.jpg?size=736x711&quality=95&sign=8f7163b54538a2e7bad5f36a857485d4&type=album'), - CardData('mouse2', - descriptionText: 'ahahaha', - icon: Icons.access_alarm_outlined, - imageUrl: - 'https://sun165-1.userapi.com/impg/EVLbaLilqr8xw5tsqZLQQb6DSYrdKo7Q9sYSsw/H4FRwyMR6Ec.jpg?size=1280x960&quality=96&sign=f606e4ae3d1ccd27917cd1ffa6d91e58&type=album'), - CardData('mouse3', - descriptionText: 'eeee', - icon: Icons.access_alarm_rounded, - imageUrl: - 'https://sun34-1.userapi.com/impg/_DLT-op0LbBdgh5h-ILvC7IMDY5kbLR349v7vA/tX7vtk6mNlA.jpg?size=736x736&quality=96&sign=47f2b0f63bf249c62f4498fb637695d5&type=album'), - ]; + State 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 { + final searchController = TextEditingController(); + late Future?> data; + + final repo = PotterRepository(); + + @override + void initState() { + data = repo.loadData(onError: (e) => showErrorDialog(context, error: e)); + super.initState(); + } + + @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(q: search); + }); + }, + ), + ), + 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) { 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( - 'Cute hamster $title ${isLiked ? 'liked!' : 'disliked :('}', - style: Theme.of(context).textTheme.bodyLarge, - ), + content: Text( + '$title ${isLiked ? 'liked!' : 'disliked :('}', + style: Theme.of(context).textTheme.bodyLarge, + ), backgroundColor: Colors.orangeAccent, duration: const Duration(seconds: 1), )); diff --git a/pubspec.lock b/pubspec.lock index 54bfaa6..68cb92e 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 044bddc..191da4d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,8 +11,9 @@ dependencies: sdk: flutter cupertino_icons: ^1.0.8 - json_annotation: ^4.8.1 + dio: ^5.4.2+1 + pretty_dio_logger: ^1.3.1 dev_dependencies: flutter_test: