lab7
This commit is contained in:
parent
5db40e6364
commit
f420330f68
BIN
assets/icon.jpg
BIN
assets/icon.jpg
Binary file not shown.
Before Width: | Height: | Size: 8.0 KiB |
@ -31,21 +31,53 @@ class MoviePaginationDto {
|
||||
@JsonSerializable(createToJson: false)
|
||||
class MovieDataDto {
|
||||
@JsonKey(name: 'id')
|
||||
final int? id; // Это поле может использоваться для идентификации фильма
|
||||
final String? name; // Название фильма
|
||||
final String? description; // Описание фильма
|
||||
final PosterDto? poster; // Постер фильма
|
||||
final int? id;
|
||||
final String? name;
|
||||
final String? description;
|
||||
final PosterDto? poster;
|
||||
@JsonKey(name: 'year', defaultValue: 0)
|
||||
final int year;
|
||||
final List<GenreDto>? genres;
|
||||
final List<CountryDto>? countries;
|
||||
|
||||
const MovieDataDto(this.name, this.description, this.poster, {this.id});
|
||||
const MovieDataDto(
|
||||
this.name,
|
||||
this.description,
|
||||
this.poster, {
|
||||
this.id,
|
||||
this.year = 0,
|
||||
this.genres,
|
||||
this.countries,
|
||||
});
|
||||
|
||||
factory MovieDataDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$MovieDataDtoFromJson(json);
|
||||
}
|
||||
|
||||
@JsonSerializable(createToJson: false)
|
||||
class GenreDto {
|
||||
final String? name;
|
||||
|
||||
const GenreDto({this.name});
|
||||
|
||||
factory GenreDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$GenreDtoFromJson(json);
|
||||
}
|
||||
|
||||
@JsonSerializable(createToJson: false)
|
||||
class CountryDto {
|
||||
final String? name;
|
||||
|
||||
const CountryDto({this.name});
|
||||
|
||||
factory CountryDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$CountryDtoFromJson(json);
|
||||
}
|
||||
|
||||
@JsonSerializable(createToJson: false)
|
||||
class PosterDto {
|
||||
final String? url; // URL постера
|
||||
final String? previewUrl; // URL миниатюры постера
|
||||
final String? url;
|
||||
final String? previewUrl;
|
||||
|
||||
const PosterDto({this.url, this.previewUrl});
|
||||
|
||||
|
@ -30,6 +30,21 @@ MovieDataDto _$MovieDataDtoFromJson(Map<String, dynamic> json) => MovieDataDto(
|
||||
? null
|
||||
: PosterDto.fromJson(json['poster'] as Map<String, dynamic>),
|
||||
id: (json['id'] as num?)?.toInt(),
|
||||
year: (json['year'] as num?)?.toInt() ?? 0,
|
||||
genres: (json['genres'] as List<dynamic>?)
|
||||
?.map((e) => GenreDto.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
countries: (json['countries'] as List<dynamic>?)
|
||||
?.map((e) => CountryDto.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
GenreDto _$GenreDtoFromJson(Map<String, dynamic> json) => GenreDto(
|
||||
name: json['name'] as String?,
|
||||
);
|
||||
|
||||
CountryDto _$CountryDtoFromJson(Map<String, dynamic> json) => CountryDto(
|
||||
name: json['name'] as String?,
|
||||
);
|
||||
|
||||
PosterDto _$PosterDtoFromJson(Map<String, dynamic> json) => PosterDto(
|
||||
|
@ -2,18 +2,24 @@ import 'package:pmd_labs/data/dtos/movies_dto.dart';
|
||||
import 'package:pmd_labs/domain/models/carddata.dart';
|
||||
import 'package:pmd_labs/presentation/home_page/home_page.dart';
|
||||
|
||||
const _imagePlaceholder =
|
||||
'https://upload.wikimedia.org/wikipedia/en/archive/b/b1/20210811082420%21Portrait_placeholder.png';
|
||||
|
||||
extension MovieDataDtoMapper on MovieDataDto {
|
||||
CardData toDomain() => CardData(
|
||||
name ?? 'UNKNOWN', // Исправлено с title на name
|
||||
imageUrl: poster?.url, // Обратите внимание, что используем правильно поле
|
||||
id: id?.toString() ?? '0', // Защита от null, если id нет
|
||||
descriptionText: description ?? 'Нет описания', // Используем реальное описание
|
||||
name ?? 'UNKNOWN',
|
||||
imageUrl: poster?.url ?? _imagePlaceholder,
|
||||
id: id?.toString() ?? '0',
|
||||
descriptionText: description ?? 'Нет описания',
|
||||
year: year,
|
||||
genres: genres?.map((genre) => genre.name ?? 'UNKNOWN').toList() ?? [],
|
||||
countries: countries?.map((country) => country.name ?? 'UNKNOWN').toList() ?? [],
|
||||
);
|
||||
}
|
||||
|
||||
extension MoviesDtoToModel on MoviesDto {
|
||||
HomeData toDomain() => HomeData(
|
||||
data: docs?.map((e) => e.toDomain()).toList(), // Изменено с data на docs
|
||||
data: docs?.map((e) => e.toDomain()).toList(),
|
||||
nextPage: (pagination?.hasNextPage ?? false)
|
||||
? ((pagination?.currentPage ?? 0) + 1)
|
||||
: null
|
||||
|
@ -1,18 +0,0 @@
|
||||
import 'package:pmd_labs/components/utils/error_callback.dart';
|
||||
import 'package:pmd_labs/data/repositories/api_interface.dart';
|
||||
import 'package:pmd_labs/domain/models/carddata.dart';
|
||||
import 'package:pmd_labs/presentation/home_page/home_page.dart';
|
||||
|
||||
class MockRepository extends ApiInterface {
|
||||
@override
|
||||
Future<HomeData?> loadData({OnErrorCallback? onError}) async {
|
||||
return HomeData(
|
||||
data: [
|
||||
CardData('JoJo’s Bizarre Adventure', descriptionText: 'kono dio da', imageUrl: 'https://i1.sndcdn.com/avatars-253MmMf9QZzxVBJi-rvlyeg-t1080x1080.jpg'),
|
||||
CardData('Example', descriptionText: 'what is this?', imageUrl: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQvaBQ6nAedlqvXsh-dLXZi2Gexy1RkDbTUKQ&s'),
|
||||
CardData('Mock data', descriptionText: 'Mock data description', imageUrl: 'https://cdn-user30887.skyeng.ru/uploads/6692a339c6989979804399.png'),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -14,14 +14,14 @@ class MovieRepository extends ApiInterface {
|
||||
));
|
||||
|
||||
static const String _baseUrl = 'https://api.kinopoisk.dev';
|
||||
static const String _apiKey = 'HQTFY5N-8D34FT0-HXQQQ1S-KPREHDX'; // Ваш API-ключ
|
||||
static const String _apiKey = 'HQTFY5N-8D34FT0-HXQQQ1S-KPREHDX';
|
||||
|
||||
@override
|
||||
Future<HomeData?> loadData({
|
||||
OnErrorCallback? onError,
|
||||
String? q,
|
||||
int page = 1,
|
||||
int pageSize = 15,
|
||||
int pageSize = 10,
|
||||
}) async {
|
||||
try {
|
||||
const String url = '$_baseUrl/v1.4/movie/search';
|
||||
@ -30,7 +30,7 @@ class MovieRepository extends ApiInterface {
|
||||
url,
|
||||
queryParameters: {
|
||||
'page': page,
|
||||
'limit': pageSize,
|
||||
'pageSize': pageSize,
|
||||
'query': q,
|
||||
},
|
||||
options: Options(
|
||||
|
@ -1,12 +1,17 @@
|
||||
|
||||
class CardData {
|
||||
final String text;
|
||||
final String descriptionText;
|
||||
final String? imageUrl;
|
||||
final String? id;
|
||||
|
||||
CardData(this.text,
|
||||
{required this.descriptionText,
|
||||
this.imageUrl,
|
||||
this.id});
|
||||
final int? year;
|
||||
final List<String>? genres;
|
||||
final List<String>? countries;
|
||||
CardData(this.text, {
|
||||
required this.descriptionText,
|
||||
this.imageUrl,
|
||||
this.id,
|
||||
this.year,
|
||||
this.genres,
|
||||
this.countries,
|
||||
});
|
||||
}
|
@ -21,16 +21,16 @@ class MyApp extends StatelessWidget {
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<LocaleBloc>(
|
||||
return BlocProvider<LocaleBloc>( //
|
||||
lazy: false,
|
||||
create: (context) => LocaleBloc(Locale(Platform.localeName)),
|
||||
child: BlocBuilder<LocaleBloc, LocaleState>(
|
||||
child: BlocBuilder<LocaleBloc, LocaleState>( //
|
||||
builder: (context, state) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
locale: state.currentLocale,
|
||||
localizationsDelegates: AppLocale.localizationsDelegates,
|
||||
supportedLocales: AppLocale.supportedLocales,
|
||||
locale: state.currentLocale, // передаем текущую локаль
|
||||
localizationsDelegates: AppLocale.localizationsDelegates, // делегат (подключение локали)
|
||||
supportedLocales: AppLocale.supportedLocales, // список доступных локалей (подключение локали)
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(
|
||||
colorScheme:
|
||||
@ -40,7 +40,7 @@ class MyApp extends StatelessWidget {
|
||||
home: RepositoryProvider<MovieRepository>(
|
||||
lazy: true,
|
||||
create: (_) => MovieRepository(),
|
||||
child: BlocProvider<LikeBloc>(
|
||||
child: BlocProvider<LikeBloc>( // добавили BlocProvider
|
||||
lazy: false,
|
||||
create: (context) => LikeBloc(),
|
||||
child: BlocProvider<HomeBloc>(
|
||||
|
@ -11,45 +11,86 @@ class DetailsPage extends StatelessWidget {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("Детали"),
|
||||
backgroundColor: Colors.purpleAccent, // Фиолетовый цвет для AppBar
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Изображение слева
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
child: Image.network(
|
||||
data.imageUrl ?? '',
|
||||
height: 600, // Задайте фиксированную высоту для изображения
|
||||
width: 600, // Задайте фиксированную ширину для изображения
|
||||
fit: BoxFit.cover, // Обеспечьте хороший аспект изображения
|
||||
body: Container(
|
||||
color: Colors.purple[100], // Светло-сиреневый фон для всей страницы
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Изображение слева
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
child: Image.network(
|
||||
data.imageUrl ?? '',
|
||||
height: 600, // Задайте фиксированную высоту для изображения
|
||||
width: 600, // Задайте фиксированную ширину для изображения
|
||||
fit: BoxFit.cover, // Обеспечьте хороший аспект изображения
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16), // Промежуток между изображением и текстом
|
||||
SizedBox(width: 16), // Промежуток между изображением и текстом
|
||||
|
||||
// Текст справа
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: Text(
|
||||
data.text,
|
||||
style: Theme.of(context).textTheme.headlineLarge,
|
||||
// Текст справа
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: Text(
|
||||
data.text,
|
||||
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
|
||||
color: Colors.purple, // Простой сиреневый цвет текста заголовка
|
||||
fontWeight: FontWeight.bold, // Жирный шрифт
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
data.descriptionText,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
],
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: Text(
|
||||
'Год: ${data.year}', // Отображение года
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: Colors.purple[700], // Темный сиреневый цвет текста года
|
||||
fontStyle: FontStyle.italic, // Курсив для выделения года
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
data.descriptionText ?? '', // Обработаем случай, если описания нет
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: Colors.purple[600], // Темный сиреневый цвет текста описания
|
||||
),
|
||||
),
|
||||
// Отображение жанров
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
'Жанры: ${data.genres?.join(', ') ?? 'Нет жанров'}', // Проверка на null
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: Colors.purple[600], // Темный сиреневый цвет текста жанров
|
||||
fontStyle: FontStyle.italic, // Курсив для выделения жанров
|
||||
),
|
||||
),
|
||||
),
|
||||
// Отображение стран
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
'Страны: ${data.countries?.join(', ') ?? 'Нет стран'}', // Проверка на null
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: Colors.purple[600], // Темный сиреневый цвет текста стран
|
||||
fontStyle: FontStyle.italic, // Курсив для выделения стран
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -2,7 +2,7 @@ part of 'home_page.dart';
|
||||
|
||||
typedef OnLikeCallback = void Function(String? id, String title, bool isLiked)?;
|
||||
|
||||
class _Card extends StatelessWidget {
|
||||
class _Card extends StatelessWidget { // состояние карточки регулируется извне; виджеты с точкой убираем
|
||||
final String text;
|
||||
final String descriptionText;
|
||||
final String? imageUrl;
|
||||
@ -11,22 +11,20 @@ class _Card extends StatelessWidget {
|
||||
final String? id;
|
||||
final bool isLiked;
|
||||
|
||||
const _Card(
|
||||
this.text, {
|
||||
required this.descriptionText,
|
||||
this.imageUrl,
|
||||
this.onLike,
|
||||
this.onTap,
|
||||
this.id,
|
||||
this.isLiked = false,
|
||||
});
|
||||
const _Card(this.text, {
|
||||
required this.descriptionText,
|
||||
this.imageUrl,
|
||||
this.onLike,
|
||||
this.onTap,
|
||||
this.id,
|
||||
this.isLiked = false,
|
||||
});
|
||||
|
||||
factory _Card.fromData(
|
||||
CardData data, {
|
||||
OnLikeCallback onLike,
|
||||
VoidCallback? onTap,
|
||||
bool isLiked = false,
|
||||
}) =>
|
||||
factory _Card.fromData(CardData data, {
|
||||
OnLikeCallback onLike,
|
||||
VoidCallback? onTap,
|
||||
bool isLiked = false,
|
||||
}) =>
|
||||
_Card(
|
||||
data.text,
|
||||
descriptionText: data.descriptionText,
|
||||
@ -43,13 +41,14 @@ class _Card extends StatelessWidget {
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
constraints: const BoxConstraints(minHeight: 160),
|
||||
constraints: const BoxConstraints(minHeight: 130),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white70,
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: Colors.purpleAccent, width: 2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(.5),
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
spreadRadius: 4,
|
||||
offset: const Offset(0, 5),
|
||||
blurRadius: 8,
|
||||
@ -68,33 +67,32 @@ class _Card extends StatelessWidget {
|
||||
child: SizedBox(
|
||||
height: double.infinity,
|
||||
width: 120,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Image.network(
|
||||
imageUrl ?? '',
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) => const Placeholder(),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: Image.network(
|
||||
imageUrl ?? '',
|
||||
fit: BoxFit.cover,
|
||||
|
||||
errorBuilder: (_, __, ___) => const Placeholder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
text,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
Center(
|
||||
child: Text(
|
||||
text,
|
||||
style: Theme
|
||||
.of(context)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.apply(color: Colors.purple),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
descriptionText,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
)
|
||||
SizedBox(height: 4),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -102,20 +100,35 @@ class _Card extends StatelessWidget {
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 8, right: 16, bottom: 16),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8, right: 16, bottom: 16),
|
||||
child: GestureDetector(
|
||||
onTap: () => onLike?.call(id, text, isLiked),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: isLiked
|
||||
? const Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.redAccent,
|
||||
key: ValueKey<int>(0),
|
||||
)
|
||||
: const Icon(
|
||||
Icons.favorite_border,
|
||||
key: ValueKey<int>(1),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: isLiked ? Colors.transparent : Colors.purple,
|
||||
width: 2,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: isLiked
|
||||
? const Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.purple,
|
||||
key: ValueKey<int>(0),
|
||||
)
|
||||
: const Icon(
|
||||
Icons.favorite_border,
|
||||
color: Colors.purple,
|
||||
key: ValueKey<int>(1),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -32,7 +32,7 @@ class _HomePageState extends State<HomePage> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
backgroundColor: Colors.purpleAccent,
|
||||
),
|
||||
body: const Body(),
|
||||
);
|
||||
@ -56,7 +56,7 @@ class _BodyState extends State<Body> {
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
context.read<HomeBloc>().add(const HomeLoadDataEvent());
|
||||
context.read<LikeBloc>().add(const LoadLikesEvent());
|
||||
context.read<LikeBloc>().add(const LoadLikesEvent()); // событие на изменение лайка
|
||||
});
|
||||
scrollController.addListener(_onNextPageListener);
|
||||
|
||||
@ -84,7 +84,7 @@ class _BodyState extends State<Body> {
|
||||
final MovieRepository repo = MovieRepository();
|
||||
var data = MovieRepository().loadData();
|
||||
|
||||
void _onLike(String? id, String title, bool isLiked) {
|
||||
void _onLike(String? id, String title, bool isLiked) { // обработчик лайков
|
||||
print("$id $title, $isLiked");
|
||||
if (id != null) {
|
||||
context.read<LikeBloc>().add(ChangeLikeEvent(id));
|
||||
@ -96,7 +96,7 @@ class _BodyState extends State<Body> {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(
|
||||
' ${isLiked ? context.locale.liked : context.locale.disliked} $title',
|
||||
' ${isLiked ? context.locale.liked : context.locale.disliked} $title', //переписали константные строки под локаль
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
backgroundColor: Colors.orangeAccent,
|
||||
@ -124,58 +124,64 @@ class _BodyState extends State<Body> {
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: CupertinoSearchTextField(
|
||||
controller: searchController,
|
||||
onChanged: (search) {
|
||||
Debounce.run(() => context
|
||||
.read<HomeBloc>()
|
||||
.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();
|
||||
},
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CupertinoSearchTextField(
|
||||
controller: searchController,
|
||||
onChanged: (search) {
|
||||
Debounce.run(() => context
|
||||
.read<HomeBloc>()
|
||||
.add(HomeLoadDataEvent(search: search)));
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12), // Отступ между полем поиска и иконкой
|
||||
GestureDetector(
|
||||
onTap: () =>
|
||||
context.read<LocaleBloc>().add(const ChangeLocaleEvent()), // смена иконки локализации
|
||||
child: SizedBox.square(
|
||||
dimension: 50,
|
||||
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.isLoading
|
||||
? CircularProgressIndicator()
|
||||
: BlocBuilder<LikeBloc, LikeState>(
|
||||
builder: (context, likeState) => 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,
|
||||
isLiked: likeState.likedIds
|
||||
?.contains(data.id) ==
|
||||
true,
|
||||
onLike: _onLike,
|
||||
onTap: () =>
|
||||
_navToDetails(context, data),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
}),
|
||||
),
|
||||
))),
|
||||
builder: (context, likeState) => 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,
|
||||
isLiked: likeState.likedIds
|
||||
?.contains(data.id) ==
|
||||
true,
|
||||
onLike: _onLike,
|
||||
onTap: () =>
|
||||
_navToDetails(context, data),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
}),
|
||||
),
|
||||
))),
|
||||
BlocBuilder<HomeBloc, HomeState>(
|
||||
builder: (context, state) => state.isPaginationLoading
|
||||
? const CircularProgressIndicator()
|
||||
|
@ -43,7 +43,7 @@ dev_dependencies:
|
||||
flutter_icons:
|
||||
android: "ic_launcher"
|
||||
ios: true
|
||||
image_path: "assets/launcher.jpg"
|
||||
image_path: "assets/icon1.jpg"
|
||||
min_sdk_android: 21
|
||||
|
||||
flutter:
|
||||
|
Loading…
x
Reference in New Issue
Block a user