From 224097153a80ef94f6efe5481faa644284c33988 Mon Sep 17 00:00:00 2001 From: gg12 darfren Date: Mon, 7 Oct 2024 21:54:13 +0400 Subject: [PATCH] lab6 --- lib/components/utils/debounce.dart | 15 ++ lib/data/dtos/character_dto.dart | 59 ++++++ lib/data/dtos/codeforcer_dto.dart | 27 --- lib/data/dtos/codeforcer_dto.g.dart | 23 --- lib/data/mappers/character_mapper.dart | 29 +++ lib/data/mappers/codeforcer_mapper.dart | 12 -- lib/domain/models/card.dart | 20 +- lib/domain/models/home.dart | 7 + lib/main.dart | 15 +- .../details_page/details_page.dart | 49 ++--- lib/presentation/dialogs/error_dialog.dart | 35 ++++ lib/presentation/dialogs/show_dialog.dart | 13 ++ lib/presentation/home_page/bloc/bloc.dart | 39 ++++ lib/presentation/home_page/bloc/events.dart | 9 + lib/presentation/home_page/bloc/state.dart | 29 +++ lib/presentation/home_page/card.dart | 43 ++--- lib/presentation/home_page/home_page.dart | 174 +++++++++++++----- lib/repositories/api_interface.dart | 6 +- lib/repositories/codeforcers_repository.dart | 32 ---- lib/repositories/mock_repository.dart | 26 --- lib/repositories/naruto.dart | 45 +++++ pubspec.lock | 80 ++++++-- pubspec.yaml | 5 +- 23 files changed, 536 insertions(+), 256 deletions(-) create mode 100644 lib/components/utils/debounce.dart create mode 100644 lib/data/dtos/character_dto.dart delete mode 100644 lib/data/dtos/codeforcer_dto.dart delete mode 100644 lib/data/dtos/codeforcer_dto.g.dart create mode 100644 lib/data/mappers/character_mapper.dart delete mode 100644 lib/data/mappers/codeforcer_mapper.dart create mode 100644 lib/domain/models/home.dart create mode 100644 lib/presentation/dialogs/error_dialog.dart create mode 100644 lib/presentation/dialogs/show_dialog.dart create mode 100644 lib/presentation/home_page/bloc/bloc.dart create mode 100644 lib/presentation/home_page/bloc/events.dart create mode 100644 lib/presentation/home_page/bloc/state.dart delete mode 100644 lib/repositories/codeforcers_repository.dart delete mode 100644 lib/repositories/mock_repository.dart create mode 100644 lib/repositories/naruto.dart diff --git a/lib/components/utils/debounce.dart b/lib/components/utils/debounce.dart new file mode 100644 index 0000000..fd9593b --- /dev/null +++ b/lib/components/utils/debounce.dart @@ -0,0 +1,15 @@ +import 'dart:async'; +import 'dart:ui'; +class Debounce { + factory Debounce() => _instance; + Debounce._(); + static final Debounce _instance = Debounce._(); + static Timer? _timer; + static void run( + VoidCallback action, { + Duration delay = const Duration(milliseconds: 500), + }) { + _timer?.cancel(); + _timer = Timer(delay, action); + } +} \ No newline at end of file diff --git a/lib/data/dtos/character_dto.dart b/lib/data/dtos/character_dto.dart new file mode 100644 index 0000000..57abdd7 --- /dev/null +++ b/lib/data/dtos/character_dto.dart @@ -0,0 +1,59 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'character_dto.g.dart'; + +@JsonSerializable(createToJson: false) +class CharactersDto { + @JsonKey(name: 'characters') + final List? data; + final MetaDto? meta; + + const CharactersDto({ + this.data, + this.meta, + }); + + factory CharactersDto.fromJson(Map json) => + _$CharactersDtoFromJson(json); +} + +@JsonSerializable(createToJson: false) +class CharacterDataDto { + final int? id; + final String? name; + final List? images; + @JsonKey(name: 'personal') + final CharacterDataAttributesDto? attributes; + + const CharacterDataDto({this.id, this.name, this.images, this.attributes}); + + factory CharacterDataDto.fromJson(Map json) => + _$CharacterDataDtoFromJson(json); +} + +@JsonSerializable(createToJson: false) +class CharacterDataAttributesDto { + final String? birthdate; + final String? sex; + final String? clan; + + const CharacterDataAttributesDto({this.birthdate, this.sex, this.clan}); + + factory CharacterDataAttributesDto.fromJson(Map json) => + _$CharacterDataAttributesDtoFromJson(json); +} + +@JsonSerializable(createToJson: false) +class MetaDto { + final PaginationDto? pagination; + const MetaDto({this.pagination}); + factory MetaDto.fromJson(Map json) => _$MetaDtoFromJson(json); +} +@JsonSerializable(createToJson: false) +class PaginationDto { + final int? current; + final int? next; + final int? last; + const PaginationDto({this.current, this.next, this.last}); + factory PaginationDto.fromJson(Map json) => _$PaginationDtoFromJson(json); +} diff --git a/lib/data/dtos/codeforcer_dto.dart b/lib/data/dtos/codeforcer_dto.dart deleted file mode 100644 index 80c2e12..0000000 --- a/lib/data/dtos/codeforcer_dto.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -part 'codeforcer_dto.g.dart'; - -@JsonSerializable(createToJson: false) -class CodeforcersDto { - @JsonKey(name: 'result') - final List? data; - - const CodeforcersDto({this.data}); - - factory CodeforcersDto.fromJson(Map json) => _$CodeforcersDtoFromJson(json); -} - - -@JsonSerializable(createToJson: false) -class CodeforcerDto { - final String? handle; - final String? rank; - final String? maxRank; - final String? titlePhoto; - final int? rating; - - const CodeforcerDto({this.handle, this.rank, this.titlePhoto, this.rating, this.maxRank}); - - factory CodeforcerDto.fromJson(Map json) => _$CodeforcerDtoFromJson(json); -} diff --git a/lib/data/dtos/codeforcer_dto.g.dart b/lib/data/dtos/codeforcer_dto.g.dart deleted file mode 100644 index e3794ee..0000000 --- a/lib/data/dtos/codeforcer_dto.g.dart +++ /dev/null @@ -1,23 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'codeforcer_dto.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -CodeforcersDto _$CodeforcersDtoFromJson(Map json) => - CodeforcersDto( - data: (json['result'] as List?) - ?.map((e) => CodeforcerDto.fromJson(e as Map)) - .toList(), - ); - -CodeforcerDto _$CodeforcerDtoFromJson(Map json) => - CodeforcerDto( - handle: json['handle'] as String?, - rank: json['rank'] as String?, - titlePhoto: json['titlePhoto'] as String?, - rating: (json['rating'] as num?)?.toInt(), - maxRank: json['maxRank'] as String?, - ); diff --git a/lib/data/mappers/character_mapper.dart b/lib/data/mappers/character_mapper.dart new file mode 100644 index 0000000..51b4f30 --- /dev/null +++ b/lib/data/mappers/character_mapper.dart @@ -0,0 +1,29 @@ +import 'package:pmu_project/data/dtos/character_dto.dart'; +import 'package:pmu_project/domain/models/card.dart'; + +import '../../domain/models/home.dart'; + +const _imagePlaceHolder = + 'https://avatars.mds.yandex.net/i?id=754d78a3c15c744d93908f05c5ae9e85_l-4140023-images-thumbs&n=13'; + +extension CharactersDtoToModel on CharactersDto { + HomeData toDomain() => HomeData( + data: data?.map((e) => e.toDomain()).toList(), + nextPage: meta?.pagination?.next, + ); +} + +extension CharacterDataDtoToModel on CharacterDataDto { + CardData toDomain() => CardData( + name ?? 'Unknown', + _makeDescriptionText(), + clan: 'clan: ${attributes != null ? (attributes?.clan ?? 'unknown') : 'unknown'}', + imageUrl: '${(images != null && images!.length > 0) ? (images?[0] ?? _imagePlaceHolder) : _imagePlaceHolder}' + ); + + String _makeDescriptionText() { + return 'birthdate: ${attributes != null ? (attributes?.birthdate ?? 'unknown') : 'unknown'}\n' + 'sex: ${attributes != null ? (attributes?.sex ?? 'unknown') : 'unknown'}\n' + 'clan: ${attributes != null ? (attributes?.clan ?? 'unknown') : 'unknown'}'; + } +} diff --git a/lib/data/mappers/codeforcer_mapper.dart b/lib/data/mappers/codeforcer_mapper.dart deleted file mode 100644 index 3300d7b..0000000 --- a/lib/data/mappers/codeforcer_mapper.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:pmu_project/data/dtos/codeforcer_dto.dart'; -import 'package:pmu_project/domain/models/card.dart'; - -extension CodeforcerDtoToModel on CodeforcerDto { - CardData toDomain() => CardData( - this?.handle ?? 'BEBRA', - rank: this?.rank ?? 'Не в рейтинге', - maxRank: this?.maxRank ?? 'Не в рейтинге', - rating: this?.rating ?? 0, - imageUrl: this?.titlePhoto, - ); -} diff --git a/lib/domain/models/card.dart b/lib/domain/models/card.dart index 247122b..033fd76 100644 --- a/lib/domain/models/card.dart +++ b/lib/domain/models/card.dart @@ -1,19 +1,15 @@ import 'package:flutter/material.dart'; class CardData { - final String handle; - final String? rank; - final String? maxRank; - final int? rating; - final IconData icon; - final String? imageUrl; + final String name; + final String descriptionText; + final String clan; + final String imageUrl; CardData( - this.handle, { - required this.rank, - required this.rating, - this.icon = Icons.hail, - this.imageUrl, - this.maxRank, + this.name, + this.descriptionText,{ + required this.imageUrl, + required this.clan, }); } diff --git a/lib/domain/models/home.dart b/lib/domain/models/home.dart new file mode 100644 index 0000000..1e14215 --- /dev/null +++ b/lib/domain/models/home.dart @@ -0,0 +1,7 @@ +import 'card.dart'; + +class HomeData { + final List? data; + final int? nextPage; + HomeData({this.data, this.nextPage}); +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 9b42762..aa03ac9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:pmu_project/presentation/home_page/bloc/bloc.dart'; import 'package:pmu_project/presentation/home_page/home_page.dart'; +import 'package:pmu_project/repositories/naruto.dart'; void main() { runApp(const MyApp()); @@ -16,9 +19,15 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: mainColor), useMaterial3: true, ), - home: const MyHomePage(title: 'Кодфорсеры'), + home: RepositoryProvider( + lazy: true, + create: (_) => NarutoRepository(), + child: BlocProvider( + lazy: false, + create: (context) => HomeBloc(context.read()), + child: const MyHomePage(), + ), + ), ); } } - - diff --git a/lib/presentation/details_page/details_page.dart b/lib/presentation/details_page/details_page.dart index 5ec8622..6a15b50 100644 --- a/lib/presentation/details_page/details_page.dart +++ b/lib/presentation/details_page/details_page.dart @@ -17,52 +17,27 @@ class DetailsPage extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.only(bottom: 16.0), - child: Image.network(data.imageUrl ?? '',), - ), - Padding( - padding: const EdgeInsets.only(bottom: 4.0, left: 4.0, right: 4.0), - child: Text( - data.handle, - style: Theme - .of(context) - .textTheme - .headlineLarge, + child: Image.network( + data.imageUrl ?? '', ), ), Padding( - padding: const EdgeInsets.only(bottom: 4.0, left: 4.0, right: 4.0), + padding: + const EdgeInsets.only(bottom: 4.0, left: 4.0, right: 4.0), child: Text( - 'Звание: ' + (data.rank ?? 'Не в рейтинге'), - style: Theme - .of(context) - .textTheme - .bodyMedium, + data.name, + style: Theme.of(context).textTheme.headlineLarge, ), ), Padding( - padding: const EdgeInsets.only(bottom: 4.0, left: 4.0, right: 4.0), + padding: + const EdgeInsets.only(bottom: 4.0, left: 4.0, right: 4.0), child: Text( - 'Максимальное звание: ' + (data.maxRank ?? 'Не в рейтинге'), - style: Theme - .of(context) - .textTheme - .bodyMedium, - ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 4.0, left: 4.0, right: 4.0), - child: Text( - 'Рейтинг: ' + (data.rating ?? 0).toString(), - style: Theme - .of(context) - .textTheme - .bodyMedium, + data.descriptionText, + style: Theme.of(context).textTheme.bodyMedium, ), ), ], - ) - - ); + )); } - -} \ 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..cea54d7 --- /dev/null +++ b/lib/presentation/dialogs/error_dialog.dart @@ -0,0 +1,35 @@ +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..6b28b53 --- /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/bloc/bloc.dart b/lib/presentation/home_page/bloc/bloc.dart new file mode 100644 index 0000000..a87516e --- /dev/null +++ b/lib/presentation/home_page/bloc/bloc.dart @@ -0,0 +1,39 @@ +import 'package:bloc/bloc.dart'; +import 'package:pmu_project/presentation/home_page/bloc/events.dart'; +import 'package:pmu_project/presentation/home_page/bloc/state.dart'; + +import '../../../repositories/naruto.dart'; + +class HomeBloc extends Bloc { + final NarutoRepository repo; + + HomeBloc(this.repo) : super(const HomeState()) { + on(_onLoadData); + } + + Future _onLoadData(HomeLoadDataEvent event, Emitter emit) async { + if (event.nextPage == null) { + emit(state.copyWith(isLoading: true)); + } else { + emit(state.copyWith(isPaginationLoading: true)); + } + + String? error; + + final data = await repo.loadData( + q: event.search, + page: event.nextPage ?? 1, + onError: (e) => error = e, + ); + if (event.nextPage != null) { + data?.data?.insertAll(0, state.data?.data ?? []); + } + + emit(state.copyWith( + isLoading: false, + isPaginationLoading: false, + data: data, + error: error, + )); + } +} \ No newline at end of file diff --git a/lib/presentation/home_page/bloc/events.dart b/lib/presentation/home_page/bloc/events.dart new file mode 100644 index 0000000..9e814f8 --- /dev/null +++ b/lib/presentation/home_page/bloc/events.dart @@ -0,0 +1,9 @@ +abstract class HomeEvent { + const HomeEvent(); +} + +class HomeLoadDataEvent extends HomeEvent { + final String? search; + final int? nextPage; + const HomeLoadDataEvent({this.search, this.nextPage}); +} \ No newline at end of file diff --git a/lib/presentation/home_page/bloc/state.dart b/lib/presentation/home_page/bloc/state.dart new file mode 100644 index 0000000..6423ab5 --- /dev/null +++ b/lib/presentation/home_page/bloc/state.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; +import 'package:copy_with_extension/copy_with_extension.dart'; + +import '../../../domain/models/card.dart'; +import '../../../domain/models/home.dart'; + +part 'state.g.dart'; + +@CopyWith() +class HomeState extends Equatable { + final HomeData? data; + final bool isLoading; + final bool isPaginationLoading; + final String? error; + + const HomeState({ + this.data, + this.isLoading = false, + this.isPaginationLoading = false, + this.error, + }); + + List get props => [ + data, + isLoading, + isPaginationLoading, + error, + ]; +} \ No newline at end of file diff --git a/lib/presentation/home_page/card.dart b/lib/presentation/home_page/card.dart index 4d2f4b7..3ce7aa8 100644 --- a/lib/presentation/home_page/card.dart +++ b/lib/presentation/home_page/card.dart @@ -3,23 +3,23 @@ part of 'home_page.dart'; typedef OnLikeCall = void Function(String Title, bool isLiked); class _Card extends StatefulWidget { - final String handle; - final IconData icon; - final String? rank; - final String? imageUrl; - final int? rating; + final String name; + final String descriptionText; + final String clan; + final String imageUrl; final Color color; final OnLikeCall? onLike; final VoidCallback? onTap; - const _Card(this.handle, - {this.icon = Icons.ac_unit_outlined, - this.rank, - this.imageUrl, - this.color = Colors.white70, - this.onLike, - this.onTap, - this.rating}); + const _Card( + this.name, + this.descriptionText, { + required this.clan, + required this.imageUrl, + this.color = Colors.white70, + this.onLike, + this.onTap, + }); factory _Card.fromData( CardData data, { @@ -27,10 +27,9 @@ class _Card extends StatefulWidget { VoidCallback? onTap, }) => _Card( - data.handle, - rank: data.rank, - rating: data.rating, - icon: data.icon, + data.name, + data.descriptionText, + clan: data.clan, imageUrl: data.imageUrl, color: Colors.white70, onLike: onLike, @@ -104,15 +103,11 @@ class _CardState extends State<_Card> { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - widget.handle, + widget.name, style: Theme.of(context).textTheme.headlineLarge, ), Text( - widget.rank ?? 'Не в рейтинге', - style: Theme.of(context).textTheme.bodyLarge, - ), - Text( - 'Рейтинг:' + (widget.rating != null ? widget.rating.toString() : 'Нет'), + widget.clan, style: Theme.of(context).textTheme.bodyLarge, ), ], @@ -129,7 +124,7 @@ class _CardState extends State<_Card> { setState(() { isLiked = !isLiked; }); - widget.onLike?.call(widget.handle, isLiked); + widget.onLike?.call(widget.name, isLiked); }, child: AnimatedSwitcher( duration: const Duration(milliseconds: 150), diff --git a/lib/presentation/home_page/home_page.dart b/lib/presentation/home_page/home_page.dart index ed75ab3..2cf1fc6 100644 --- a/lib/presentation/home_page/home_page.dart +++ b/lib/presentation/home_page/home_page.dart @@ -1,30 +1,38 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pmu_project/presentation/details_page/details_page.dart'; -import 'package:pmu_project/repositories/codeforcers_repository.dart'; -import 'package:pmu_project/repositories/mock_repository.dart'; +import 'package:pmu_project/presentation/home_page/bloc/state.dart'; +import 'package:pmu_project/repositories/naruto.dart'; + +import '../../components/utils/debounce.dart'; import '../../domain/models/card.dart'; +import '../../repositories/api_interface.dart'; +import 'bloc/bloc.dart'; +import 'bloc/events.dart'; part 'card.dart'; const Color mainColor = Colors.deepOrangeAccent; class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - final String title; + const MyHomePage({super.key}); @override State createState() => _MyHomePageState(); } + // void _showSnackBar(BuildContext context, String title, bool isLiked) { WidgetsBinding.instance.addPostFrameCallback((_) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( isLiked ? "Нравится" : "Не нравится", - style: Theme.of(context).textTheme.bodyLarge, + style: Theme + .of(context) + .textTheme + .bodyLarge, ), backgroundColor: Colors.orangeAccent, duration: const Duration(seconds: 1), @@ -33,57 +41,131 @@ void _showSnackBar(BuildContext context, String title, bool isLiked) { } class _MyHomePageState extends State { - final Color _color = Colors.orangeAccent; + @override + Widget build(BuildContext context) { + return const Scaffold(body: _Body()); + } +} + +class _Body extends StatefulWidget { + const _Body({super.key}); + + @override + State<_Body> createState() => _BodyState(); + + +} + +class _BodyState extends State<_Body> { + final searchController = TextEditingController(); + final scrollController = ScrollController(); @override void initState() { + WidgetsBinding.instance.addPostFrameCallback((_) { + context.read().add(const HomeLoadDataEvent()); + }); + + scrollController.addListener(_onNextPageListener); + super.initState(); } + void _onNextPageListener() { + if (scrollController.offset > scrollController.position.maxScrollExtent) { + // preventing multiple pagination request on multiple swipes + final bloc = context.read(); + if (!bloc.state.isPaginationLoading) { + bloc.add(HomeLoadDataEvent( + search: searchController.text, + nextPage: bloc.state.data?.nextPage, + )); + } + } + } + + @override + void dispose() { + searchController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - leading: Icon(Icons.abc_rounded), - backgroundColor: mainColor, - title: Text(widget.title), + 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) { + Debounce.run(() => + context.read().add( + HomeLoadDataEvent(search: search))); + }, + ), + ), + BlocBuilder( + builder: (context, state) => + state.error != null + ? Text( + state.error ?? '', + style: Theme + .of(context) + .textTheme + .headlineSmall + ?.copyWith(color: Colors.red), + ) + : state.isLoading + ? const CircularProgressIndicator() + : Expanded( + child: RefreshIndicator( + onRefresh: _onRefresh, + child: ListView.builder( + controller: scrollController, + padding: EdgeInsets.zero, + itemCount: state.data?.data?.length ?? 0, + itemBuilder: (context, index) { + final data = state.data?.data?[index]; + return data != null + ? _Card.fromData( + data, + onLike: (title, isLiked) => + _showSnackBar(context, title, isLiked), + onTap: () => _navToDetails(context, data), + ) + : const SizedBox.shrink(); + }, + ), + ), + ), + ), + BlocBuilder( + builder: (context, state) => + state.isPaginationLoading + ? const CircularProgressIndicator() + : const SizedBox.shrink(), + ), + ], ), - body: const Body(), ); } -} -class Body extends StatelessWidget { - const Body({super.key}); - - @override - Widget build(BuildContext context) { - final data = CodeforcersRepository().loadData(); - - return 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(), - ) - ), - ); + Future _onRefresh() { + context.read().add( + HomeLoadDataEvent(search: searchController.text)); + return Future.value(null); } -} -void _navToDetails(BuildContext context, CardData data) { - Navigator.push( - context, - CupertinoPageRoute(builder: (context) => DetailsPage(data)), - ); -} + void _navToDetails(BuildContext context, CardData data) { + Navigator.push( + context, + CupertinoPageRoute(builder: (context) => DetailsPage(data)), + ); + } +} \ No newline at end of file diff --git a/lib/repositories/api_interface.dart b/lib/repositories/api_interface.dart index abad151..0792b04 100644 --- a/lib/repositories/api_interface.dart +++ b/lib/repositories/api_interface.dart @@ -1,5 +1,9 @@ import 'package:pmu_project/domain/models/card.dart'; +import '../domain/models/home.dart'; + +typedef OnErrorCallback = void Function(String? error); + abstract class ApiInterface { - Future?> loadData(); + Future loadData({OnErrorCallback? onError}); } \ No newline at end of file diff --git a/lib/repositories/codeforcers_repository.dart b/lib/repositories/codeforcers_repository.dart deleted file mode 100644 index a3fef97..0000000 --- a/lib/repositories/codeforcers_repository.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:dio/dio.dart'; -import 'package:pmu_project/data/dtos/codeforcer_dto.dart'; -import 'package:pmu_project/data/mappers/codeforcer_mapper.dart'; -import 'package:pmu_project/domain/models/card.dart'; -import 'package:pmu_project/repositories/api_interface.dart'; -import 'package:pretty_dio_logger/pretty_dio_logger.dart'; - -class CodeforcersRepository extends ApiInterface{ - static final Dio _dio = Dio() - ..interceptors.add(PrettyDioLogger( - requestHeader: true, - requestBody: true, - )); - - static const String _baseUrl = 'https://codeforces.com/api'; - - @override - Future?> loadData() async { - try{ - const String url = '$_baseUrl/user.info?handles=Oleja123;' - 'maxK99;bekodeg;MecTb_3a_PrideBlack;Spiritum;Serxiolog&checkHistoricHandles=false'; - final Response response = await _dio.get>(url); - - final CodeforcersDto dto = CodeforcersDto.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/lib/repositories/mock_repository.dart b/lib/repositories/mock_repository.dart deleted file mode 100644 index fa3f179..0000000 --- a/lib/repositories/mock_repository.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:pmu_project/repositories/api_interface.dart'; - -import '../domain/models/card.dart'; - -class MockRepository extends ApiInterface { - @override - Future ?> loadData() async { - return [ - CardData( - 'Рулет из баклажанов', - rank: 'рулет из баклажанов, чеснока и сыра', - rating: 300, - imageUrl: - 'https://kolyda.ru/d/baklazhanchiki_farshirovannye_orehami.jpg', - ), - CardData( - 'Рулет из баклажанов', - rank: 'рулет из баклажанов, чеснока и сыра', - rating: 300, - imageUrl: - 'https://kolyda.ru/d/baklazhanchiki_farshirovannye_orehami.jpg', - ), - ]; - } -} \ No newline at end of file diff --git a/lib/repositories/naruto.dart b/lib/repositories/naruto.dart new file mode 100644 index 0000000..fc44875 --- /dev/null +++ b/lib/repositories/naruto.dart @@ -0,0 +1,45 @@ +import 'package:dio/dio.dart'; +import 'package:pmu_project/data/dtos/character_dto.dart'; +import 'package:pmu_project/data/mappers/character_mapper.dart'; +import 'package:pmu_project/domain/models/card.dart'; +import 'package:pmu_project/repositories/api_interface.dart'; +import 'package:pretty_dio_logger/pretty_dio_logger.dart'; + +import '../domain/models/home.dart'; + +class NarutoRepository extends ApiInterface { + static final Dio _dio = Dio() + ..interceptors.add(PrettyDioLogger( + requestHeader: true, + requestBody: true, + )); + + static const String _baseUrl = 'https://dattebayo-api.onrender.com'; + + @override + Future loadData({ + OnErrorCallback? onError, + String? q, + int page = 1, + int pageSize = 8, + }) async { + const String url = '$_baseUrl/characters'; + try { + final Response response = await _dio.get>( + url, + queryParameters: { + 'name': q, + 'page': page, + 'limit': pageSize, + }, + ); + final CharactersDto dto = + CharactersDto.fromJson(response.data as Map); + final HomeData data = dto.toDomain(); + return data; + } on DioException catch (e) { + onError?.call(e.error?.toString()); + return null; + } + } +} diff --git a/pubspec.lock b/pubspec.lock index e5255b0..09531db 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -38,6 +38,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + url: "https://pub.dev" + source: hosted + version: "8.1.4" boolean_selector: dependency: transitive description: @@ -158,6 +166,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + copy_with_extension: + dependency: transitive + description: + name: copy_with_extension + sha256: fbcf890b0c34aedf0894f91a11a579994b61b4e04080204656b582708b5b1125 + url: "https://pub.dev" + source: hosted + version: "5.0.4" + copy_with_extension_gen: + dependency: "direct dev" + description: + name: copy_with_extension_gen + sha256: "51cd11094096d40824c8da629ca7f16f3b7cea5fc44132b679617483d43346b0" + url: "https://pub.dev" + source: hosted + version: "5.0.4" crypto: dependency: transitive description: @@ -198,6 +222,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -227,14 +259,22 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a + url: "https://pub.dev" + source: hosted + version: "8.1.6" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -340,10 +380,10 @@ packages: dependency: transitive description: name: lints - sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.0" logging: dependency: transitive description: @@ -392,6 +432,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" package_config: dependency: transitive description: @@ -424,6 +472,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + provider: + dependency: transitive + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" pub_semver: dependency: transitive description: @@ -489,10 +545,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" stream_channel: dependency: transitive description: @@ -513,10 +569,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" term_glyph: dependency: transitive description: @@ -529,10 +585,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.3" timing: dependency: transitive description: @@ -561,10 +617,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.0" watcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8aa66be..b20b8fd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,8 @@ dependencies: dio: ^5.4.2+1 pretty_dio_logger: ^1.4.0 + equatable: ^2.0.5 + flutter_bloc: ^8.1.5 dev_dependencies: flutter_test: @@ -48,13 +50,14 @@ dev_dependencies: build_runner: ^2.4.13 json_serializable: ^6.8.0 + copy_with_extension_gen: ^5.0.4 # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^4.0.0 + flutter_lints: ^5.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec