This commit is contained in:
Владимир Данилов 2024-12-21 07:05:58 +04:00
parent 87a0be61f6
commit 697e7d1da5
27 changed files with 648 additions and 287 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View File

@ -0,0 +1,10 @@
/// Generate by [asset_generator](https://github.com/fluttercandies/flutter_asset_generator) library.
/// PLEASE DO NOT EDIT MANUALLY.
// ignore_for_file: constant_identifier_names
class R {
const R._();
static const String ASSETS_SVG_RU_SVG = 'assets/svg/ru.svg';
static const String ASSETS_SVG_US_SVG = 'assets/svg/us.svg';
}

View File

@ -4,16 +4,19 @@ part 'characters_dto.g.dart';
@JsonSerializable(createToJson: false) @JsonSerializable(createToJson: false)
class CharactersDto { class CharactersDto {
final List<CharacterDataDto>? data; final List<CharacterDataDto>? results;
final PaginationDto? pagination; final PaginationDto? info;
const CharactersDto({this.data, this.pagination,}); const CharactersDto({
this.results,
this.info,
});
factory CharactersDto.fromJson(Map<String, dynamic> json) => _$CharactersDtoFromJson(json); factory CharactersDto.fromJson(Map<String, dynamic> json) => _$CharactersDtoFromJson(json);
} }
@JsonSerializable(createToJson: false) @JsonSerializable(createToJson: false)
class CharacterDataDto{ class CharacterDataDto {
final int? id; final int? id;
final String? name; final String? name;
final String? status; final String? status;

View File

@ -3,19 +3,20 @@ import 'package:flutter_labs_app/domain/models/card.dart';
import '../../domain/models/home.dart'; import '../../domain/models/home.dart';
extension CharacterDataDtoModel on CharacterDataDto{ extension CharacterDataDtoModel on CharacterDataDto {
CardDate toDomain() => CardDate( CardDate toDomain() => CardDate(
name ?? 'UNKNOW', name ?? 'UNKNOW',
imageUrl: image, imageUrl: image,
descriptionText: _makeDescriptionText(species, status), descriptionText: _makeDescriptionText(species, status),
id: id.toString(),
); );
String _makeDescriptionText(String? species, String? status){ String _makeDescriptionText(String? species, String? status) {
return species != null && status != null return species != null && status != null
? 'species: $species \n status: $status' ? 'species: $species \n status: $status'
: species != null : species != null
? 'species: $species' ? 'species: $species'
:status != null : status != null
? 'status: $status' ? 'status: $status'
: ''; : '';
} }
@ -23,7 +24,7 @@ extension CharacterDataDtoModel on CharacterDataDto{
extension CharactersDataDtoModel on CharactersDto { extension CharactersDataDtoModel on CharactersDto {
HomeData toDomain() => HomeData( HomeData toDomain() => HomeData(
data: data?.map((e) => e.toDomain()).toList(), data: results?.map((e) => e.toDomain()).toList(),
nextPage: pagination?.next, nextPage: info?.next,
); );
} }

View File

@ -3,6 +3,6 @@ import 'package:flutter_labs_app/domain/models/home.dart';
typedef OnErrorCallback = void Function(String? error); typedef OnErrorCallback = void Function(String? error);
abstract class ApiInterface{ abstract class ApiInterface {
Future<HomeData?> loadData({OnErrorCallback? onError}); Future<HomeData?> loadData({OnErrorCallback? onError});
} }

View File

@ -3,28 +3,31 @@ import 'package:flutter_labs_app/data/repositories/api_interface.dart';
import 'package:flutter_labs_app/domain/models/card.dart'; import 'package:flutter_labs_app/domain/models/card.dart';
import 'package:flutter_labs_app/domain/models/home.dart'; import 'package:flutter_labs_app/domain/models/home.dart';
class MockRepository extends ApiInterface{ class MockRepository extends ApiInterface {
@override @override
Future<HomeData?> loadData({OnErrorCallback? onError}) async{ Future<HomeData?> loadData({OnErrorCallback? onError}) async {
return HomeData( return HomeData(
data: [ data: [
CardDate( CardDate(
'Hi', 'Hi',
descriptionText: 'hello', descriptionText: 'hello',
icon: Icons.h_mobiledata_sharp, icon: Icons.h_mobiledata_sharp,
imageUrl: 'https://avatars.mds.yandex.net/i?id=7cb577fccf8b7354b5248cb8101dd09433fa521f-4253662-images-thumbs&n=13', imageUrl:
'https://avatars.mds.yandex.net/i?id=7cb577fccf8b7354b5248cb8101dd09433fa521f-4253662-images-thumbs&n=13',
), ),
CardDate( CardDate(
'Privet', 'Privet',
descriptionText: 'hello', descriptionText: 'hello',
icon: Icons.pages, icon: Icons.pages,
imageUrl: 'https://avatars.mds.yandex.net/i?id=34f57633c955c47b56c68537076e5bfabb4a397b-4577841-images-thumbs&n=13', imageUrl:
'https://avatars.mds.yandex.net/i?id=34f57633c955c47b56c68537076e5bfabb4a397b-4577841-images-thumbs&n=13',
), ),
CardDate( CardDate(
'Arigato', 'Arigato',
descriptionText: 'hello', descriptionText: 'hello',
icon: Icons.account_tree_sharp, icon: Icons.account_tree_sharp,
imageUrl: 'https://avatars.mds.yandex.net/i?id=d3e8f5e5c373aec40e18fdb5bc28f98367e0f0d9-4271037-images-thumbs&n=13', imageUrl:
'https://avatars.mds.yandex.net/i?id=d3e8f5e5c373aec40e18fdb5bc28f98367e0f0d9-4271037-images-thumbs&n=13',
), ),
CardDate( CardDate(
'Last', 'Last',

View File

@ -6,7 +6,7 @@ import 'package:flutter_labs_app/domain/models/card.dart';
import 'package:flutter_labs_app/domain/models/home.dart'; import 'package:flutter_labs_app/domain/models/home.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart'; import 'package:pretty_dio_logger/pretty_dio_logger.dart';
class RickRepository extends ApiInterface{ class RickRepository extends ApiInterface {
static final Dio _dio = Dio() static final Dio _dio = Dio()
..interceptors.add(PrettyDioLogger( ..interceptors.add(PrettyDioLogger(
requestHeader: true, requestHeader: true,
@ -22,7 +22,7 @@ class RickRepository extends ApiInterface{
int page = 1, int page = 1,
int pageSize = 20, int pageSize = 20,
}) async { }) async {
try{ try {
const String url = '$_baseUrl/api/character'; const String url = '$_baseUrl/api/character';
final Response<dynamic> response = await _dio.get<Map<dynamic, dynamic>>( final Response<dynamic> response = await _dio.get<Map<dynamic, dynamic>>(
@ -36,7 +36,7 @@ class RickRepository extends ApiInterface{
final CharactersDto dto = CharactersDto.fromJson(response.data as Map<String, dynamic>); final CharactersDto dto = CharactersDto.fromJson(response.data as Map<String, dynamic>);
final HomeData? data = dto.toDomain(); final HomeData? data = dto.toDomain();
return data; return data;
} on DioException catch (e){ } on DioException catch (e) {
onError?.call(e.error?.toString()); onError?.call(e.error?.toString());
return null; return null;
} }

View File

@ -1,16 +1,17 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class CardDate{ class CardDate {
final String text; final String text;
final String descriptionText; final String descriptionText;
final IconData icon; final IconData icon;
final String? imageUrl; final String? imageUrl;
final String? id;
CardDate( CardDate(
this.text, this.text, {
{
required this.descriptionText, required this.descriptionText,
this.icon = Icons.ac_unit_outlined, this.icon = Icons.ac_unit_outlined,
this.imageUrl = 'https://via.placeholder.com/150', this.imageUrl = 'https://via.placeholder.com/150',
this.id,
}); });
} }

View File

@ -1,8 +1,15 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_labs_app/data/repositories/rick_repository.dart'; import 'package:flutter_labs_app/data/repositories/rick_repository.dart';
import 'package:flutter_labs_app/presentation/home_page/bloc/bloc.dart'; import 'package:flutter_labs_app/presentation/home_page/bloc/bloc.dart';
import 'package:flutter_labs_app/presentation/home_page/home_page.dart'; import 'package:flutter_labs_app/presentation/home_page/home_page.dart';
import 'package:flutter_labs_app/presentation/like_bloc/like_bloc.dart';
import 'package:flutter_labs_app/presentation/locale_bloc/locale_bloc.dart';
import 'package:flutter_labs_app/presentation/locale_bloc/locale_state.dart';
import 'components/locale/l10n/app_locale.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
@ -13,8 +20,16 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider<LocaleBloc>(
lazy: false,
create: (context) => LocaleBloc(Locale(Platform.localeName)),
child: BlocBuilder<LocaleBloc, LocaleState>(
builder: (context, state) {
return MaterialApp( return MaterialApp(
title: 'Flutter Demo', title: 'Flutter Demo',
locale: state.currentLocale,
localizationsDelegates: AppLocale.localizationsDelegates,
supportedLocales: AppLocale.supportedLocales,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: ThemeData( theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.limeAccent), colorScheme: ColorScheme.fromSeed(seedColor: Colors.limeAccent),
@ -23,12 +38,19 @@ class MyApp extends StatelessWidget {
home: RepositoryProvider<RickRepository>( home: RepositoryProvider<RickRepository>(
lazy: true, lazy: true,
create: (_) => RickRepository(), create: (_) => RickRepository(),
child: BlocProvider<LikeBloc>(
lazy: false,
create: (context) => LikeBloc(),
child: BlocProvider<HomeBloc>( child: BlocProvider<HomeBloc>(
lazy: false, lazy: false,
create: (context) => HomeBloc(context.read<RickRepository>()), create: (context) => HomeBloc(context.read<RickRepository>()),
child: const MyHomePage(title: 'Danilov V.V.'), child: const MyHomePage(title: 'Danilov V.V.'),
), ),
), ),
),
);
},
),
); );
} }
} }

View File

@ -0,0 +1,35 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../components/resources.g.dart';
abstract class SvgObjects {
static void init() {
final pics = <String>[
R.ASSETS_SVG_RU_SVG,
R.ASSETS_SVG_US_SVG,
];
for (final String p in pics) {
final loader = SvgAssetLoader(p);
svg.cache.putIfAbsent(loader.cacheKey(null), () => loader.loadBytes(null));
}
}
}
class SvgRu extends StatelessWidget {
const SvgRu({super.key});
@override
Widget build(BuildContext context) {
return SvgPicture.asset(R.ASSETS_SVG_RU_SVG);
}
}
class SvgUk extends StatelessWidget {
const SvgUk({super.key});
@override
Widget build(BuildContext context) {
return SvgPicture.asset(R.ASSETS_SVG_US_SVG);
}
}

View File

@ -1,13 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_labs_app/domain/models/card.dart'; import 'package:flutter_labs_app/domain/models/card.dart';
class DetailsPage extends StatelessWidget{ class DetailsPage extends StatelessWidget {
final CardDate data; final CardDate data;
const DetailsPage(this.data, {super.key}); const DetailsPage(this.data, {super.key});
@override @override
Widget build(BuildContext context){ Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(), appBar: AppBar(),
body: Column( body: Column(

View File

@ -25,7 +25,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
onError: (e) => error = e, onError: (e) => error = e,
); );
if (event.nextPage != null){ if (event.nextPage != null) {
data?.data?.insertAll(0, state.data?.data ?? []); data?.data?.insertAll(0, state.data?.data ?? []);
} }

View File

@ -1,8 +1,8 @@
abstract class HomeEvent{ abstract class HomeEvent {
const HomeEvent(); const HomeEvent();
} }
class HomeLoadDataEvent extends HomeEvent{ class HomeLoadDataEvent extends HomeEvent {
final String? search; final String? search;
final int? nextPage; final int? nextPage;

View File

@ -1,50 +1,48 @@
part of 'home_page.dart'; part of 'home_page.dart';
typedef OnLikeCallback = void Function(String title, bool isLiked)?; typedef OnLikeCallback = void Function(String? id, String title, bool isLiked)?;
class _Card extends StatefulWidget { class _Card extends StatelessWidget {
final String text; final String text;
final String descriptionText; final String descriptionText;
final IconData icon; final IconData icon;
final String? imageUrl; final String? imageUrl;
final OnLikeCallback onLike; final OnLikeCallback onLike;
final VoidCallback? onTap; final VoidCallback? onTap;
final String? id;
final bool isLiked;
const _Card( const _Card(this.text,
this.text, {required this.descriptionText,
{
required this.descriptionText,
this.icon = Icons.ac_unit_outlined, this.icon = Icons.ac_unit_outlined,
this.imageUrl, this.imageUrl,
this.onLike, this.onLike,
this.onTap this.onTap,
} this.id,
); this.isLiked = false,
});
factory _Card.fromData( factory _Card.fromData(
CardDate data, { CardDate data, {
OnLikeCallback onLike, OnLikeCallback onLike,
VoidCallback? onTap, VoidCallback? onTap,
}) => _Card( bool isLiked = false,
}) =>
_Card(
data.text, data.text,
descriptionText: data.descriptionText, descriptionText: data.descriptionText,
icon: data.icon, icon: data.icon,
imageUrl: data.imageUrl, imageUrl: data.imageUrl,
onLike: onLike, onLike: onLike,
onTap: onTap, onTap: onTap,
id: data.id,
isLiked: isLiked,
); );
@override
State<_Card> createState() => _CardState();
}
class _CardState extends State<_Card> {
bool isLiked = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: widget.onTap, onTap: onTap,
child: Container( child: Container(
margin: const EdgeInsets.all(16), margin: const EdgeInsets.all(16),
constraints: const BoxConstraints(minHeight: 140), constraints: const BoxConstraints(minHeight: 140),
@ -61,10 +59,8 @@ class _CardState extends State<_Card> {
spreadRadius: 4, spreadRadius: 4,
offset: const Offset(0, 5), offset: const Offset(0, 5),
blurRadius: 8, blurRadius: 8,
blurStyle: BlurStyle.normal blurStyle: BlurStyle.normal)
) ]),
]
),
child: IntrinsicHeight( child: IntrinsicHeight(
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -78,7 +74,7 @@ class _CardState extends State<_Card> {
height: double.infinity, height: double.infinity,
width: 120, width: 120,
child: Image.network( child: Image.network(
widget.imageUrl ?? '', imageUrl ?? '',
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
), ),
@ -90,43 +86,34 @@ class _CardState extends State<_Card> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
widget.text, text,
style: Theme.of(context).textTheme.headlineLarge, style: Theme.of(context).textTheme.headlineLarge,
), ),
Text( Text(descriptionText, style: Theme.of(context).textTheme.bodyLarge),
widget.descriptionText,
style: Theme.of(context).textTheme.bodyLarge
),
], ],
), ),
), ),
), ),
Column( Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0), padding: const EdgeInsets.only(left: 8.0, right: 8.0),
child: Icon(widget.icon), child: Icon(icon),
), ),
Padding( Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0), padding: const EdgeInsets.only(left: 8.0, right: 8.0),
child: GestureDetector( child: GestureDetector(
onTap: (){ onTap: () => onLike?.call(id, text, isLiked),
setState(() {
isLiked = !isLiked;
});
widget.onLike?.call(widget.text, isLiked);
},
child: AnimatedSwitcher( child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: isLiked child: isLiked
?const Icon( ? const Icon(
Icons.favorite, Icons.favorite,
color: Colors.redAccent, color: Colors.redAccent,
key: ValueKey<int>(0), key: ValueKey<int>(0),
) )
:const Icon( : const Icon(
Icons.favorite_border, Icons.favorite_border,
key: ValueKey<int>(1), key: ValueKey<int>(1),
), ),
@ -137,8 +124,7 @@ class _CardState extends State<_Card> {
), ),
], ],
), ),
) )),
),
); );
} }
} }

View File

@ -1,12 +1,20 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_labs_app/components/extensions/context_x.dart';
import 'package:flutter_labs_app/data/repositories/mock_repository.dart'; import 'package:flutter_labs_app/data/repositories/mock_repository.dart';
import 'package:flutter_labs_app/data/repositories/rick_repository.dart'; import 'package:flutter_labs_app/data/repositories/rick_repository.dart';
import 'package:flutter_labs_app/presentation/details_page/details_page.dart'; import 'package:flutter_labs_app/presentation/details_page/details_page.dart';
import '../../components/utils/debounce.dart'; import '../../components/utils/debounce.dart';
import '../../domain/models/card.dart'; import '../../domain/models/card.dart';
import '../common/svg_objects.dart';
import '../like_bloc/like_bloc.dart';
import '../like_bloc/like_event.dart';
import '../like_bloc/like_state.dart';
import '../locale_bloc/locale_bloc.dart';
import '../locale_bloc/locale_events.dart';
import '../locale_bloc/locale_state.dart';
import 'bloc/bloc.dart'; import 'bloc/bloc.dart';
import 'bloc/events.dart'; import 'bloc/events.dart';
import 'bloc/state.dart'; import 'bloc/state.dart';
@ -48,8 +56,11 @@ class _BodyState extends State<Body> {
@override @override
void initState() { void initState() {
SvgObjects.init();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<HomeBloc>().add(const HomeLoadDataEvent()); context.read<HomeBloc>().add(const HomeLoadDataEvent());
context.read<LikeBloc>().add(const LoadLikesEvent());
}); });
scrollController.addListener(_onNextPageListener); scrollController.addListener(_onNextPageListener);
@ -60,9 +71,8 @@ class _BodyState extends State<Body> {
if (scrollController.position.atEdge) { if (scrollController.position.atEdge) {
final bloc = context.read<HomeBloc>(); final bloc = context.read<HomeBloc>();
if (!bloc.state.isPaginationLoading) { if (!bloc.state.isPaginationLoading) {
bloc.add(HomeLoadDataEvent( bloc.add(
search: searchController.text, HomeLoadDataEvent(search: searchController.text, nextPage: bloc.state.data?.nextPage));
nextPage: bloc.state.data?.nextPage));
} }
} }
} }
@ -81,7 +91,7 @@ class _BodyState extends State<Body> {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar( ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text( content: Text(
'Лайк на $title ${isLiked ? 'поставлен' : 'убран'}', '$title ${isLiked ? context.locale.liked : context.locale.disliked}',
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
), ),
backgroundColor: Colors.orangeAccent, backgroundColor: Colors.orangeAccent,
@ -91,14 +101,11 @@ class _BodyState extends State<Body> {
} }
void _navToDetails(BuildContext context, CardDate data) { void _navToDetails(BuildContext context, CardDate data) {
Navigator.push( Navigator.push(context, CupertinoPageRoute(builder: (context) => DetailsPage(data)));
context, CupertinoPageRoute(builder: (context) => DetailsPage(data)));
} }
Future<void> _onRefresh() { Future<void> _onRefresh() {
context context.read<HomeBloc>().add(HomeLoadDataEvent(search: searchController.text));
.read<HomeBloc>()
.add(HomeLoadDataEvent(search: searchController.text));
return Future.value(null); return Future.value(null);
} }
@ -108,24 +115,50 @@ class _BodyState extends State<Body> {
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: Column( child: Column(
children: [ children: [
Padding( Row(
children: [
Expanded(
flex: 4,
child: Padding(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
child: CupertinoSearchTextField( child: CupertinoSearchTextField(
controller: searchController, controller: searchController,
placeholder: context.locale.search,
onChanged: (search) { onChanged: (search) {
Debounce.run(() => context Debounce.run(
.read<HomeBloc>() () => context.read<HomeBloc>().add(HomeLoadDataEvent(search: search)));
.add(HomeLoadDataEvent(search: search)));
})), })),
),
GestureDetector(
onTap: () =>
context.read<LocaleBloc>().add(const ChangeLocaleEvent()),
child: SizedBox.square(
dimension: 50,
child: Padding(
padding: const EdgeInsets.only(right: 12),
child: BlocBuilder<LocaleBloc, LocaleState>(
builder: (context, state) {
return state.currentLocale.languageCode == 'ru'
? const SvgRu()
: const SvgUk();
},
),
),
),
),
],
),
BlocBuilder<HomeBloc, HomeState>( BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) => state.error != null builder: (context, state) => state.error != null
? Text( ? Text(
state.error ?? '', state.error ?? '',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(color: Colors.red), style:
Theme.of(context).textTheme.headlineSmall?.copyWith(color: Colors.red),
) )
: state.isLoading : state.isLoading
? CircularProgressIndicator() ? CircularProgressIndicator()
: Expanded( : BlocBuilder<LikeBloc, LikeState>(
builder: (context, likeState) => Expanded(
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: _onRefresh, onRefresh: _onRefresh,
child: ListView.builder( child: ListView.builder(
@ -137,16 +170,17 @@ class _BodyState extends State<Body> {
return data != null return data != null
? _Card.fromData( ? _Card.fromData(
data, data,
onLike: (title, isLiked) => onLike: _onLike,
_showSnackbar( isLiked: likeState.likedIds?.contains(data.id) == true,
context, title, isLiked), onTap: () => _navToDetails(context, data),
onTap: () =>
_navToDetails(context, data),
) )
: const SizedBox.shrink(); : const SizedBox.shrink();
}), }
),
),
)
)
), ),
)),
BlocBuilder<HomeBloc, HomeState>( BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) => state.isPaginationLoading builder: (context, state) => state.isPaginationLoading
? const CircularProgressIndicator() ? const CircularProgressIndicator()
@ -154,4 +188,12 @@ class _BodyState extends State<Body> {
], ],
)); ));
} }
void _onLike(String? id, String title, bool isLiked) {
print("$id $title, $isLiked");
if (id != null) {
context.read<LikeBloc>().add(ChangeLikeEvent(id));
_showSnackbar(context, title, !isLiked);
}
}
} }

View File

@ -0,0 +1,56 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'like_state.dart';
// **************************************************************************
// CopyWithGenerator
// **************************************************************************
abstract class _$LikeStateCWProxy {
LikeState likedIds(List<String>? likedIds);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `LikeState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// LikeState(...).copyWith(id: 12, name: "My name")
/// ````
LikeState call({
List<String>? likedIds,
});
}
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfLikeState.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfLikeState.copyWith.fieldName(...)`
class _$LikeStateCWProxyImpl implements _$LikeStateCWProxy {
final LikeState _value;
const _$LikeStateCWProxyImpl(this._value);
@override
LikeState likedIds(List<String>? likedIds) => this(likedIds: likedIds);
@override
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `LikeState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// LikeState(...).copyWith(id: 12, name: "My name")
/// ````
LikeState call({
Object? likedIds = const $CopyWithPlaceholder(),
}) {
return LikeState(
likedIds: likedIds == const $CopyWithPlaceholder()
? _value.likedIds
// ignore: cast_nullable_to_non_nullable
: likedIds as List<String>?,
);
}
}
extension $LikeStateCopyWith on LikeState {
/// Returns a callable class that can be used as follows: `instanceOfLikeState.copyWith(...)` or like so:`instanceOfLikeState.copyWith.fieldName(...)`.
// ignore: library_private_types_in_public_api
_$LikeStateCWProxy get copyWith => _$LikeStateCWProxyImpl(this);
}

View File

@ -5,23 +5,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "72.0.0" version: "61.0.0"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.2"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.7.0" version: "5.13.0"
archive:
dependency: transitive
description:
name: archive
sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -134,6 +137,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.3" version: "2.0.3"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev"
source: hosted
version: "0.4.2"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -166,6 +177,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
copy_with_extension:
dependency: transitive
description:
name: copy_with_extension
sha256: "9c17d2b9a041ad19c97aa544c46a60f192cd409529bb9cfb98f48f336bb84d31"
url: "https://pub.dev"
source: hosted
version: "4.0.4"
copy_with_extension_gen:
dependency: "direct main"
description:
name: copy_with_extension_gen
sha256: "19ab5e5095a9047315b42a6c2bb7ddf59a85cd23a75278500661ced4299165e9"
url: "https://pub.dev"
source: hosted
version: "4.0.4"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
@ -186,10 +213,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.7" version: "2.3.2"
dio: dio:
dependency: "direct main" dependency: "direct main"
description: description:
@ -222,6 +249,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -251,6 +286,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.1.6" version: "8.1.6"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
url: "https://pub.dev"
source: hosted
version: "0.13.1"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -259,11 +302,37 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "4.0.0"
flutter_localization:
dependency: "direct main"
description:
name: flutter_localization
sha256: "9b3b8825146a3850297a0ec3686b326d618328dca4cd5178b764af9303a26379"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
flutter_localizations:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
sha256: "8c5d68a82add3ca76d792f058b186a0599414f279f00ece4830b9b231b570338"
url: "https://pub.dev"
source: hosted
version: "2.0.7"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:
@ -288,6 +357,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.3.2"
http:
dependency: transitive
description:
name: http
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev"
source: hosted
version: "1.2.2"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -304,6 +381,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.0.2"
image:
dependency: transitive
description:
name: image
sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6"
url: "https://pub.dev"
source: hosted
version: "4.5.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev"
source: hosted
version: "0.19.0"
io: io:
dependency: transitive dependency: transitive
description: description:
@ -332,10 +425,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: json_serializable name: json_serializable
sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.9.0" version: "6.8.0"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@ -376,14 +469,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
macros:
dependency: transitive
description:
name: macros
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
url: "https://pub.dev"
source: hosted
version: "0.1.2-main.4"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -440,6 +525,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" version: "1.9.0"
path_parsing:
dependency: transitive
description:
name: path_parsing
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.2"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -448,6 +589,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.1" version: "1.5.1"
posix:
dependency: transitive
description:
name: posix
sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a
url: "https://pub.dev"
source: hosted
version: "6.0.1"
pretty_dio_logger: pretty_dio_logger:
dependency: "direct main" dependency: "direct main"
description: description:
@ -480,6 +629,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
url: "https://pub.dev"
source: hosted
version: "2.2.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.dev"
source: hosted
version: "2.4.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@ -589,6 +794,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7"
url: "https://pub.dev"
source: hosted
version: "1.1.15"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb"
url: "https://pub.dev"
source: hosted
version: "1.1.12"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad"
url: "https://pub.dev"
source: hosted
version: "1.1.16"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -637,6 +866,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "3.0.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
@ -647,4 +892,4 @@ packages:
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.5.3 <4.0.0" dart: ">=3.5.3 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54" flutter: ">=3.24.0"

View File

@ -1,101 +1,54 @@
name: flutter_labs_app name: flutter_labs_app
description: "A new Flutter project." description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: ^3.5.3 sdk: ^3.5.3
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
json_annotation: ^4.9.0 json_annotation: ^4.9.0
dio: ^5.4.2+1 dio: ^5.4.2+1
pretty_dio_logger: ^1.3.1 pretty_dio_logger: ^1.3.1
copy_with_extension_gen: ^4.0.0
#BLoC #BLoC
equatable: ^2.0.5 equatable: ^2.0.5
flutter_bloc: ^8.1.5 flutter_bloc: ^8.1.5
flutter_localization:
intl: 0.19.0
shared_preferences: 2.2.3
flutter_svg: 2.0.7
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
#Иконки
flutter_launcher_icons: 0.13.1
build_runner: ^2.4.9 build_runner: ^2.4.9
json_serializable: ^6.7.1 json_serializable: ^6.7.1
# 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: ^4.0.0
# For information on the generic Dart part of this file, see the flutter_icons:
# following page: https://dart.dev/tools/pub/pubspec android: "ic_launcher"
ios: true
image_path: "assets/launcher.jpg"
min_sdk_android: 21
# The following section is specific to Flutter packages.
flutter: flutter:
generate: true
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true uses-material-design: true
# To add assets to your application, add an assets section, like this: assets:
# assets: - assets/svg/
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package

View File

@ -6,6 +6,9 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <flutter_localization/flutter_localization_plugin_c_api.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
FlutterLocalizationPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi"));
} }

View File

@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
flutter_localization
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST