diff --git a/flutter_app/lib/data/dtos/characters_dto.dart b/flutter_app/lib/data/dtos/characters_dto.dart deleted file mode 100644 index 28113d2..0000000 --- a/flutter_app/lib/data/dtos/characters_dto.dart +++ /dev/null @@ -1,41 +0,0 @@ -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) { - print('CharacterDataDto.fromJson: $json'); - return _$CharacterDataDtoFromJson(json); - } -} - -@JsonSerializable(createToJson: false) -class CharacterAttributesDataDto { - final String? fullName; - final String? title; - final String? family; - final String? imageUrl; - - const CharacterAttributesDataDto({this.fullName, this.title, this.family, this.imageUrl}); - - factory CharacterAttributesDataDto.fromJson(Map json) { - print('CharacterAttributesDataDto.fromJson: $json'); - return _$CharacterAttributesDataDtoFromJson(json); - } -} \ No newline at end of file diff --git a/flutter_app/lib/data/dtos/characters_dto.g.dart b/flutter_app/lib/data/dtos/characters_dto.g.dart deleted file mode 100644 index 925527f..0000000 --- a/flutter_app/lib/data/dtos/characters_dto.g.dart +++ /dev/null @@ -1,33 +0,0 @@ -// 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( - fullName: json['fullName'] as String?, - title: json['title'] as String?, - family: json['family'] as String?, - imageUrl: json['imageUrl'] as String?, - ); diff --git a/flutter_app/lib/data/dtos/thrones_character_dto.dart b/flutter_app/lib/data/dtos/thrones_character_dto.dart index 29ca4bc..277f44a 100644 --- a/flutter_app/lib/data/dtos/thrones_character_dto.dart +++ b/flutter_app/lib/data/dtos/thrones_character_dto.dart @@ -4,7 +4,7 @@ part 'thrones_character_dto.g.dart'; @JsonSerializable(createToJson: false) class ThronesCharacterDto { - final String id; + final int id; final String firstName; final String lastName; final String fullName; diff --git a/flutter_app/lib/data/dtos/thrones_character_dto.g.dart b/flutter_app/lib/data/dtos/thrones_character_dto.g.dart index fdaa407..c0e5cfb 100644 --- a/flutter_app/lib/data/dtos/thrones_character_dto.g.dart +++ b/flutter_app/lib/data/dtos/thrones_character_dto.g.dart @@ -8,7 +8,7 @@ part of 'thrones_character_dto.dart'; ThronesCharacterDto _$ThronesCharacterDtoFromJson(Map json) => ThronesCharacterDto( - id: json['id'] as String, + id: (json['id'] as num).toInt(), firstName: json['firstName'] as String, lastName: json['lastName'] as String, fullName: json['fullName'] as String, diff --git a/flutter_app/lib/data/mappers/characters_mapper.dart b/flutter_app/lib/data/mappers/characters_mapper.dart index 33e8a23..5e50b6f 100644 --- a/flutter_app/lib/data/mappers/characters_mapper.dart +++ b/flutter_app/lib/data/mappers/characters_mapper.dart @@ -6,9 +6,14 @@ const _imagePlaceholder = extension ThronesCharacterDtoToModel on ThronesCharacterDto { CardData toDomain() => CardData( - fullName, - imageUrl: imageUrl, descriptionText: _makeDescriptionText(title, family), + imageUrl: imageUrl, + firstName: firstName, + lastName: lastName, + title: title, + family: family, + fullName: fullName, + text: '', ); String _makeDescriptionText(String? title, String? family) { diff --git a/flutter_app/lib/data/repositories/got_repository.dart b/flutter_app/lib/data/repositories/got_repository.dart index 549ef38..d81c92b 100644 --- a/flutter_app/lib/data/repositories/got_repository.dart +++ b/flutter_app/lib/data/repositories/got_repository.dart @@ -3,14 +3,9 @@ import 'package:flutter_app/data/dtos/thrones_character_dto.dart'; import 'package:flutter_app/data/mappers/characters_mapper.dart'; import 'package:flutter_app/data/repositories/api_interface.dart'; import 'package:flutter_app/domain/models/card.dart'; -import 'package:pretty_dio_logger/pretty_dio_logger.dart'; class ThronesRepository extends ApiInterface { - static final Dio _dio = Dio() - ..interceptors.add(PrettyDioLogger( - requestHeader: true, - requestBody: true, - )); + static final Dio _dio = Dio(); static const String _baseUrl = 'https://thronesapi.com'; @@ -29,8 +24,10 @@ class ThronesRepository extends ApiInterface { .toList(); final List data = characters + .where((character) => q == null || character.fullName.toLowerCase().contains(q.toLowerCase())) .map((e) => e.toDomain()) .toList(); + return data; } on DioException catch (e) { onError?.call(e.response?.statusMessage); diff --git a/flutter_app/lib/data/repositories/mock_repository.dart b/flutter_app/lib/data/repositories/mock_repository.dart index 839055f..7a4e594 100644 --- a/flutter_app/lib/data/repositories/mock_repository.dart +++ b/flutter_app/lib/data/repositories/mock_repository.dart @@ -7,60 +7,68 @@ class MockRepository extends ApiInterface { Future?> loadData({OnErrorCallback? onError}) async { return [ CardData( - 'Daenerys Targaryen', + fullName: 'Daenerys Targaryen', descriptionText: 'Mother of Dragons', icon: Icons.favorite, imageUrl: - 'https://thronesapi.com/assets/images/daenerys.jpg', + 'https://thronesapi.com/assets/images/daenerys.jpg', + text: '', firstName: '', lastName: '', title: '', family: '', ), CardData( - 'Samwell Tarly', + fullName: 'Samwell Tarly', descriptionText: 'Maester', icon: Icons.favorite, imageUrl: 'https://thronesapi.com/assets/images/sam.jpg', + text: '', firstName: '', lastName: '', title: '', family: '', ), CardData( - 'Jon Snow', + fullName: 'Jon Snow', descriptionText: 'King of the North', icon: Icons.favorite, imageUrl: 'https://thronesapi.com/assets/images/jon-snow.jpg', + text: '', firstName: '', lastName: '', title: '', family: '', ), CardData( - 'Arya Stark', + fullName: 'Arya Stark', descriptionText: 'No One', icon: Icons.favorite, imageUrl: 'https://thronesapi.com/assets/images/arya-stark.jpg', + text: '', firstName: '', lastName: '', title: '', family: '', ), CardData( - 'Sansa Stark', + fullName: 'Sansa Stark', descriptionText: 'Lady of Winterfell', icon: Icons.favorite, imageUrl: 'https://thronesapi.com/assets/images/sansa-stark.jpeg', + text: '', firstName: '', lastName: '', title: '', family: '', ), CardData( - 'Brandon Stark', + fullName: 'Brandon Stark', descriptionText: 'Lord of Winterfell', icon: Icons.favorite, imageUrl: 'https://thronesapi.com/assets/images/bran-stark.jpg', + text: '', firstName: '', lastName: '', title: '', family: '', ), CardData( - 'Ned Stark', + fullName: 'Ned Stark', descriptionText: 'Lord of Winterfell', icon: Icons.favorite, imageUrl: 'https://thronesapi.com/assets/images/ned-stark.jpg', + text: '', firstName: '', lastName: '', title: '', family: '', ), CardData( - 'Robert Baratheon', + fullName: 'Robert Baratheon', descriptionText: 'Lord of the Seven Kingdoms', icon: Icons.favorite, imageUrl: 'https://thronesapi.com/assets/images/robert-baratheon.jpeg', + text: '', firstName: '', lastName: '', title: '', family: '', ), ]; } diff --git a/flutter_app/lib/domain/models/card.dart b/flutter_app/lib/domain/models/card.dart index 12438db..25179be 100644 --- a/flutter_app/lib/domain/models/card.dart +++ b/flutter_app/lib/domain/models/card.dart @@ -5,11 +5,21 @@ class CardData { final String descriptionText; final IconData icon; final String? imageUrl; + final String firstName; + final String lastName; + final String fullName; + final String title; + final String family; - CardData( - this.text, { - required this.descriptionText, - this.icon = Icons.catching_pokemon, - this.imageUrl, - }); + CardData({ + required this.text, + required this.descriptionText, + this.icon = Icons.people, + this.imageUrl, + required this.firstName, + required this.lastName, + required this.fullName, + required this.title, + required this.family, + }); } \ No newline at end of file diff --git a/flutter_app/lib/presentation/details_page/details_page.dart b/flutter_app/lib/presentation/details_page/details_page.dart index bff8255..d54037e 100644 --- a/flutter_app/lib/presentation/details_page/details_page.dart +++ b/flutter_app/lib/presentation/details_page/details_page.dart @@ -11,9 +11,10 @@ class DetailsPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(), - body: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + appBar: AppBar(), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -32,11 +33,11 @@ class DetailsPage extends StatelessWidget { Padding( padding: const EdgeInsets.only(bottom: 4.0), child: Text( - data.text, + data.fullName, style: Theme.of(context).textTheme.bodyLarge?.copyWith( - fontSize: 27.0, - fontFamily: 'Times New Roman', - ), + fontSize: 27.0, + fontFamily: 'Times New Roman', + ), ), ), Text( @@ -45,10 +46,32 @@ class DetailsPage extends StatelessWidget { fontSize: 20.0, fontFamily: 'Times New Roman', ), - ) + ), + const SizedBox(height: 16.0), + Text( + 'First Name: ${data.firstName}', + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: 8.0), + Text( + 'Last Name: ${data.lastName}', + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: 8.0), + Text( + 'Title: ${data.title}', + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: 8.0), + Text( + 'Family: ${data.family}', + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: 8.0), ], ), - ) + ), + ), ); } } \ No newline at end of file diff --git a/flutter_app/lib/presentation/home_page/card.dart b/flutter_app/lib/presentation/home_page/card.dart index 644dc45..eba3893 100644 --- a/flutter_app/lib/presentation/home_page/card.dart +++ b/flutter_app/lib/presentation/home_page/card.dart @@ -5,6 +5,7 @@ typedef OnLikeCallback = void Function(String title, bool isLiked)?; class _Card extends StatefulWidget { final String text; final String descriptionText; + final String fullName; final IconData icon; final String? imageUrl; final OnLikeCallback onLike; @@ -14,6 +15,7 @@ class _Card extends StatefulWidget { this.text, { this.icon = Icons.catching_pokemon, required this.descriptionText, + required this.fullName, this.imageUrl, this.onLike, this.onTap, @@ -27,6 +29,7 @@ class _Card extends StatefulWidget { _Card( data.text, descriptionText: data.descriptionText, + fullName: data.fullName, icon: data.icon, imageUrl: data.imageUrl, onLike: onLike, @@ -44,92 +47,74 @@ class _CardState extends State<_Card> { Widget build(BuildContext context) { return GestureDetector( onTap: widget.onTap, - child: Container( - margin: const EdgeInsets.all(16), - constraints: const BoxConstraints(minHeight: 140), - decoration: BoxDecoration( - color: Colors.white70, - borderRadius: BorderRadius.circular(20), - border: Border.all(color: Colors.grey.shade200), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(.5), - spreadRadius: 4, - offset: const Offset(0, 5), - blurRadius: 8, + child: Card( + margin: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(15), + child: SizedBox( + height: 250, + width: double.infinity, + child: Image.network( + widget.imageUrl ?? '', + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => const Placeholder(), + ), + ), ), - ], - ), - child: IntrinsicHeight( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ClipRRect( - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(20), - topLeft: Radius.circular(20), - ), - child: SizedBox( - height: double.infinity, - width: 160, - child: Image.network( - widget.imageUrl ?? '', - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => const Placeholder(), - ), - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.text, - style: Theme.of(context).textTheme.headlineLarge, - ), - Text( - widget.descriptionText, - style: Theme.of(context).textTheme.bodyLarge, - ), - ], - ), - ), - ), - Align( - alignment: Alignment.bottomRight, - child: Padding( - padding: const EdgeInsets.only( - left: 8.0, - right: 16.0, - bottom: 16.0, - ), - child: GestureDetector( - onTap: () { - setState(() { - isLiked = !isLiked; - }); - widget.onLike?.call(widget.text, isLiked); - }, - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: isLiked - ? const Icon( - Icons.favorite, - color: Colors.redAccent, - key: ValueKey(0), - ) - : const Icon( - Icons.favorite_border, - key: ValueKey(1), - ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.fullName, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + fontSize: 20.0, + fontFamily: 'Times New Roman', ), ), + Text( + widget.descriptionText, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + ), + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: const EdgeInsets.only( + left: 8.0, + right: 16.0, + bottom: 16.0, + ), + child: GestureDetector( + onTap: () { + setState(() { + isLiked = !isLiked; + }); + widget.onLike?.call(widget.fullName, isLiked); + }, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: isLiked + ? const Icon( + Icons.favorite, + color: Colors.redAccent, + key: ValueKey(0), + ) + : const Icon( + Icons.favorite_border, + key: ValueKey(1), + ), + ), ), ), - ], - ), + ), + ], ), ), ); diff --git a/flutter_app/lib/presentation/home_page/home_page.dart b/flutter_app/lib/presentation/home_page/home_page.dart index 917e6a5..cb1e1ca 100644 --- a/flutter_app/lib/presentation/home_page/home_page.dart +++ b/flutter_app/lib/presentation/home_page/home_page.dart @@ -30,7 +30,7 @@ class Body extends StatefulWidget { } class _BodyState extends State { - final searchController = TextEditingController(); + late TextEditingController searchController; late Future?> data; final repo = ThronesRepository(); @@ -38,7 +38,14 @@ class _BodyState extends State { @override void initState() { data = repo.loadData(); - super.initState(); + searchController = TextEditingController(); + data = repo.loadData(); + } + + @override + void dispose() { + searchController.dispose(); + super.dispose(); } @override @@ -75,6 +82,7 @@ class _BodyState extends State { ); } else { return ListView.builder( + shrinkWrap: true, itemCount: snapshot.data!.length, itemBuilder: (context, index) { final cardData = snapshot.data![index]; @@ -90,7 +98,7 @@ class _BodyState extends State { }, ), ), - ), + ) ], ), );