вроде всё кроме иконки

This commit is contained in:
MaD 2024-12-17 20:15:30 +04:00
parent 753419dbf3
commit aececde5c2
7 changed files with 94 additions and 25 deletions

View File

@ -2,9 +2,12 @@
"@@locale" : "en", "@@locale" : "en",
"search": "Search", "search": "Search",
"liked": "liked!", "liked": "liked",
"disliked": "disliked :<", "disliked": "disliked",
"heroDetailsTitle": "Hero Details", "heroDetailsTitle": "Hero Details",
"heroListTitle": "Hero List",
"noHeroesAvailable": "no heroes available",
"noHeroesFound": "no heroes found",
"heroNoImage": "No image available", "heroNoImage": "No image available",
"heroNoDescription": "No description available", "heroNoDescription": "No description available",

View File

@ -2,10 +2,13 @@
"@@locale" : "ru", "@@locale" : "ru",
"search": "Поиск", "search": "Поиск",
"liked": "нравится!", "liked": "нравится",
"disliked": "не нравится :<", "disliked": "больше не нравится",
"heroListTitle": "Список героев",
"heroDetailsTitle": "Детали героя", "heroDetailsTitle": "Детали героя",
"heroNoImage": "Изображение недоступно", "heroNoImage": "Изображение недоступно",
"noHeroesAvailable": "Нет доступных героев",
"noHeroesFound": "Герои не найдены",
"heroNoDescription": "Описание отсутствует", "heroNoDescription": "Описание отсутствует",
"arbEnding": "ЗАПЯТАЯ" "arbEnding": "ЗАПЯТАЯ"

View File

@ -104,15 +104,21 @@ abstract class AppLocale {
/// No description provided for @liked. /// No description provided for @liked.
/// ///
/// In ru, this message translates to: /// In ru, this message translates to:
/// **'нравится!'** /// **'нравится'**
String get liked; String get liked;
/// No description provided for @disliked. /// No description provided for @disliked.
/// ///
/// In ru, this message translates to: /// In ru, this message translates to:
/// **'не нравится :<'** /// **'больше не нравится'**
String get disliked; String get disliked;
/// No description provided for @heroListTitle.
///
/// In ru, this message translates to:
/// **'Список героев'**
String get heroListTitle;
/// No description provided for @heroDetailsTitle. /// No description provided for @heroDetailsTitle.
/// ///
/// In ru, this message translates to: /// In ru, this message translates to:
@ -125,6 +131,18 @@ abstract class AppLocale {
/// **'Изображение недоступно'** /// **'Изображение недоступно'**
String get heroNoImage; String get heroNoImage;
/// No description provided for @noHeroesAvailable.
///
/// In ru, this message translates to:
/// **'Нет доступных героев'**
String get noHeroesAvailable;
/// No description provided for @noHeroesFound.
///
/// In ru, this message translates to:
/// **'Герои не найдены'**
String get noHeroesFound;
/// No description provided for @heroNoDescription. /// No description provided for @heroNoDescription.
/// ///
/// In ru, this message translates to: /// In ru, this message translates to:

View File

@ -10,10 +10,13 @@ class AppLocaleEn extends AppLocale {
String get search => 'Search'; String get search => 'Search';
@override @override
String get liked => 'liked!'; String get liked => 'liked';
@override @override
String get disliked => 'disliked :<'; String get disliked => 'disliked';
@override
String get heroListTitle => 'Hero List';
@override @override
String get heroDetailsTitle => 'Hero Details'; String get heroDetailsTitle => 'Hero Details';
@ -21,6 +24,12 @@ class AppLocaleEn extends AppLocale {
@override @override
String get heroNoImage => 'No image available'; String get heroNoImage => 'No image available';
@override
String get noHeroesAvailable => 'no heroes available';
@override
String get noHeroesFound => 'no heroes found';
@override @override
String get heroNoDescription => 'No description available'; String get heroNoDescription => 'No description available';

View File

@ -10,10 +10,13 @@ class AppLocaleRu extends AppLocale {
String get search => 'Поиск'; String get search => 'Поиск';
@override @override
String get liked => 'нравится!'; String get liked => 'нравится';
@override @override
String get disliked => 'не нравится :<'; String get disliked => 'больше не нравится';
@override
String get heroListTitle => 'Список героев';
@override @override
String get heroDetailsTitle => 'Детали героя'; String get heroDetailsTitle => 'Детали героя';
@ -21,6 +24,12 @@ class AppLocaleRu extends AppLocale {
@override @override
String get heroNoImage => 'Изображение недоступно'; String get heroNoImage => 'Изображение недоступно';
@override
String get noHeroesAvailable => 'Нет доступных героев';
@override
String get noHeroesFound => 'Герои не найдены';
@override @override
String get heroNoDescription => 'Описание отсутствует'; String get heroNoDescription => 'Описание отсутствует';

View File

@ -13,9 +13,10 @@ class HeroListScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final locale = AppLocale.of(context)!; // Получаем текущую локализацию final locale = AppLocale.of(context)!; // Получаем текущую локализацию
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Heroes'), title: Text(locale.heroListTitle),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.language), icon: const Icon(Icons.language),
@ -50,35 +51,46 @@ class HeroListScreen extends StatelessWidget {
if (searchState is HeroSearchLoading) { if (searchState is HeroSearchLoading) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} else if (searchState is HeroSearchLoaded) { } else if (searchState is HeroSearchLoaded) {
return ListView.builder( return RefreshIndicator(
onRefresh: () async {
// Обновляем список героев
context.read<HeroListBloc>().add(FetchHeroes());
},
child: ListView.builder(
itemCount: searchState.heroes.length, itemCount: searchState.heroes.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return HeroCard(hero: searchState.heroes[index]); return HeroCard(hero: searchState.heroes[index]);
}, },
),
); );
} else if (searchState is HeroSearchError) { } else if (searchState is HeroSearchError) {
return Center(child: Text('Error: ${searchState.message}')); return Center(child: Text('Error: ${searchState.message}'));
} else if (searchState is HeroSearchInitial) { } else if (searchState is HeroSearchInitial) {
// Если поисковый запрос пустой, показываем полный список героев
return BlocBuilder<HeroListBloc, HeroListState>( return BlocBuilder<HeroListBloc, HeroListState>(
builder: (context, listState) { builder: (context, listState) {
if (listState is HeroListLoading) { if (listState is HeroListLoading) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} else if (listState is HeroListLoaded) { } else if (listState is HeroListLoaded) {
return ListView.builder( return RefreshIndicator(
onRefresh: () async {
// Выполняем новый запрос к API
context.read<HeroListBloc>().add(FetchHeroes());
},
child: ListView.builder(
itemCount: listState.heroes.length, itemCount: listState.heroes.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return HeroCard(hero: listState.heroes[index]); return HeroCard(hero: listState.heroes[index]);
}, },
),
); );
} else if (listState is HeroListError) { } else if (listState is HeroListError) {
return Center(child: Text('Error: ${listState.message}')); return Center(child: Text('Error: ${listState.message}'));
} }
return const Center(child: Text('No heroes available.')); return Center(child: Text(locale.noHeroesAvailable));
}, },
); );
} }
return const Center(child: Text('No heroes found.')); return Center(child: Text(locale.noHeroesFound));
}, },
), ),
); );

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import '../../data/dtos/hero_dto.dart'; import '../../data/dtos/hero_dto.dart';
import '../components/screens/hero_detail_screen.dart'; import '../components/screens/hero_detail_screen.dart';
import '../services/like_service.dart'; import '../services/like_service.dart';
import '../../Components/locale/l10n/app_locale.dart';
class HeroCard extends StatefulWidget { class HeroCard extends StatefulWidget {
final HeroDto hero; final HeroDto hero;
@ -30,14 +31,28 @@ class _HeroCardState extends State<HeroCard> {
} }
Future<void> _toggleLike() async { Future<void> _toggleLike() async {
final locale = AppLocale.of(context)!; // Получаем текущую локализацию
String message;
if (_isLiked) { if (_isLiked) {
await _likeService.unlikeHero(widget.hero.id); await _likeService.unlikeHero(widget.hero.id);
message = '${locale.disliked} ${widget.hero.name}';
} else { } else {
await _likeService.likeHero(widget.hero.id); await _likeService.likeHero(widget.hero.id);
message = '${locale.liked} ${widget.hero.name}';
} }
setState(() { setState(() {
_isLiked = !_isLiked; _isLiked = !_isLiked;
}); });
// Отображение Snackbar с локализованным сообщением
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: const Duration(seconds: 2),
),
);
} }
@override @override
@ -46,14 +61,14 @@ class _HeroCardState extends State<HeroCard> {
child: ListTile( child: ListTile(
leading: widget.hero.portraitUrl != null leading: widget.hero.portraitUrl != null
? Image.network(widget.hero.portraitUrl!) ? Image.network(widget.hero.portraitUrl!)
: Icon(Icons.image), : const Icon(Icons.image),
title: Text(widget.hero.name), title: Text(widget.hero.name),
trailing: IconButton( trailing: IconButton(
icon: Icon( icon: Icon(
_isLiked ? Icons.favorite : Icons.favorite_border, _isLiked ? Icons.favorite : Icons.favorite_border,
color: _isLiked ? Colors.red : null, color: _isLiked ? Colors.red : null,
), ),
onPressed: _toggleLike, onPressed: _toggleLike, // Вызываем метод с Snackbar
), ),
onTap: () { onTap: () {
Navigator.push( Navigator.push(