From f420330f68ef3c473a0a0041c1b98be721c34811 Mon Sep 17 00:00:00 2001 From: allllen4a Date: Thu, 14 Nov 2024 16:44:44 +0300 Subject: [PATCH] lab7 --- assets/icon.jpg | Bin 8176 -> 0 bytes lib/data/dtos/movies_dto.dart | 46 ++++++-- lib/data/dtos/movies_dto.g.dart | 15 +++ lib/data/mappers/movies_mapper.dart | 16 ++- lib/data/repositories/mock_repository.dart | 18 --- lib/data/repositories/movie_repository.dart | 6 +- lib/domain/models/carddata.dart | 17 ++- lib/main.dart | 12 +- .../details_page/details_page.dart | 109 ++++++++++++------ lib/presentation/home_page/card.dart | 109 ++++++++++-------- lib/presentation/home_page/home_page.dart | 106 +++++++++-------- pubspec.yaml | 2 +- 12 files changed, 278 insertions(+), 178 deletions(-) delete mode 100644 assets/icon.jpg delete mode 100644 lib/data/repositories/mock_repository.dart diff --git a/assets/icon.jpg b/assets/icon.jpg deleted file mode 100644 index 9d71906c264d5a730f93fbe0f1151f968cde28d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8176 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 diff --git a/lib/data/dtos/movies_dto.dart b/lib/data/dtos/movies_dto.dart index 25ae0e8..3aec70f 100644 --- a/lib/data/dtos/movies_dto.dart +++ b/lib/data/dtos/movies_dto.dart @@ -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? genres; + final List? 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 json) => _$MovieDataDtoFromJson(json); } +@JsonSerializable(createToJson: false) +class GenreDto { + final String? name; + + const GenreDto({this.name}); + + factory GenreDto.fromJson(Map json) => + _$GenreDtoFromJson(json); +} + +@JsonSerializable(createToJson: false) +class CountryDto { + final String? name; + + const CountryDto({this.name}); + + factory CountryDto.fromJson(Map 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}); diff --git a/lib/data/dtos/movies_dto.g.dart b/lib/data/dtos/movies_dto.g.dart index b328948..6208385 100644 --- a/lib/data/dtos/movies_dto.g.dart +++ b/lib/data/dtos/movies_dto.g.dart @@ -30,6 +30,21 @@ MovieDataDto _$MovieDataDtoFromJson(Map json) => MovieDataDto( ? null : PosterDto.fromJson(json['poster'] as Map), id: (json['id'] as num?)?.toInt(), + year: (json['year'] as num?)?.toInt() ?? 0, + genres: (json['genres'] as List?) + ?.map((e) => GenreDto.fromJson(e as Map)) + .toList(), + countries: (json['countries'] as List?) + ?.map((e) => CountryDto.fromJson(e as Map)) + .toList(), + ); + +GenreDto _$GenreDtoFromJson(Map json) => GenreDto( + name: json['name'] as String?, + ); + +CountryDto _$CountryDtoFromJson(Map json) => CountryDto( + name: json['name'] as String?, ); PosterDto _$PosterDtoFromJson(Map json) => PosterDto( diff --git a/lib/data/mappers/movies_mapper.dart b/lib/data/mappers/movies_mapper.dart index 366ef38..21000c6 100644 --- a/lib/data/mappers/movies_mapper.dart +++ b/lib/data/mappers/movies_mapper.dart @@ -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 diff --git a/lib/data/repositories/mock_repository.dart b/lib/data/repositories/mock_repository.dart deleted file mode 100644 index ad0f969..0000000 --- a/lib/data/repositories/mock_repository.dart +++ /dev/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 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'), - ], - ); - } - -} \ No newline at end of file diff --git a/lib/data/repositories/movie_repository.dart b/lib/data/repositories/movie_repository.dart index def30c1..978998b 100644 --- a/lib/data/repositories/movie_repository.dart +++ b/lib/data/repositories/movie_repository.dart @@ -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 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( diff --git a/lib/domain/models/carddata.dart b/lib/domain/models/carddata.dart index c6d0595..285ff9b 100644 --- a/lib/domain/models/carddata.dart +++ b/lib/domain/models/carddata.dart @@ -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? genres; + final List? countries; + CardData(this.text, { + required this.descriptionText, + this.imageUrl, + this.id, + this.year, + this.genres, + this.countries, + }); } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 13f3106..f7a3dca 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,16 +21,16 @@ class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { - return BlocProvider( + return BlocProvider( // lazy: false, create: (context) => LocaleBloc(Locale(Platform.localeName)), - child: BlocBuilder( + child: BlocBuilder( // 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( lazy: true, create: (_) => MovieRepository(), - child: BlocProvider( + child: BlocProvider( // добавили BlocProvider lazy: false, create: (context) => LikeBloc(), child: BlocProvider( diff --git a/lib/presentation/details_page/details_page.dart b/lib/presentation/details_page/details_page.dart index b59b376..625fbe9 100644 --- a/lib/presentation/details_page/details_page.dart +++ b/lib/presentation/details_page/details_page.dart @@ -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, // Курсив для выделения стран + ), + ), + ), + ], + ), ), - ), - ], + ], + ), ), ), ), diff --git a/lib/presentation/home_page/card.dart b/lib/presentation/home_page/card.dart index c1407f2..01f0d22 100644 --- a/lib/presentation/home_page/card.dart +++ b/lib/presentation/home_page/card.dart @@ -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(0), - ) - : const Icon( - Icons.favorite_border, - key: ValueKey(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(0), + ) + : const Icon( + Icons.favorite_border, + color: Colors.purple, + key: ValueKey(1), + ), + ), ), ), ), diff --git a/lib/presentation/home_page/home_page.dart b/lib/presentation/home_page/home_page.dart index 934d07e..0eaef0d 100644 --- a/lib/presentation/home_page/home_page.dart +++ b/lib/presentation/home_page/home_page.dart @@ -32,7 +32,7 @@ class _HomePageState extends State { 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 { WidgetsBinding.instance.addPostFrameCallback((_) { context.read().add(const HomeLoadDataEvent()); - context.read().add(const LoadLikesEvent()); + context.read().add(const LoadLikesEvent()); // событие на изменение лайка }); scrollController.addListener(_onNextPageListener); @@ -84,7 +84,7 @@ class _BodyState extends State { 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().add(ChangeLikeEvent(id)); @@ -96,7 +96,7 @@ class _BodyState extends State { 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 { child: Column( children: [ Padding( - padding: const EdgeInsets.all(12), - child: CupertinoSearchTextField( - controller: searchController, - onChanged: (search) { - Debounce.run(() => context - .read() - .add(HomeLoadDataEvent(search: search))); - })), - GestureDetector( - onTap: () => - context.read().add(const ChangeLocaleEvent()), - child: SizedBox.square( - dimension: 50, - child: Padding( - padding: const EdgeInsets.only(right: 12), - child: BlocBuilder( - 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() + .add(HomeLoadDataEvent(search: search))); + }, + ), ), - ), + const SizedBox(width: 12), // Отступ между полем поиска и иконкой + GestureDetector( + onTap: () => + context.read().add(const ChangeLocaleEvent()), // смена иконки локализации + child: SizedBox.square( + dimension: 50, + child: BlocBuilder( + builder: (context, state) { + return state.currentLocale.languageCode == 'ru' + ? const SvgRu() + : const SvgUk(); + }, + ), + ), + ), + ], ), ), - BlocBuilder( + BlocBuilder( // обертка списка с карточками builder: (context, state) => state.isLoading ? CircularProgressIndicator() : BlocBuilder( - 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( builder: (context, state) => state.isPaginationLoading ? const CircularProgressIndicator() diff --git a/pubspec.yaml b/pubspec.yaml index c57d1a8..8d9fa38 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: