diff --git a/lib/bloc/bloc.dart b/lib/bloc/bloc.dart index 56d26ee..a4564a8 100644 --- a/lib/bloc/bloc.dart +++ b/lib/bloc/bloc.dart @@ -4,7 +4,6 @@ import 'state.dart'; import '../utils/character_service.dart'; import '../models/character.dart'; import 'dart:async'; -import 'package:flutter_bloc/flutter_bloc.dart'; class DebouncedSearchCubit extends Cubit { DebouncedSearchCubit() : super(''); @@ -25,14 +24,16 @@ class DebouncedSearchCubit extends Cubit { return super.close(); } } + class HomeBloc extends Bloc { final CharacterService characterService; HomeBloc(this.characterService) : super(const HomeState()) { on(_onLoadData); + on(_onSearchCharacters); // Добавлен новый обработчик для поиска } - // Внутри HomeBloc, когда получаем данные + // Загрузка данных Future _onLoadData(HomeLoadDataEvent event, Emitter emit) async { emit(state.copyWith(status: HomeStatus.loading)); @@ -50,6 +51,16 @@ class HomeBloc extends Bloc { } } + // Логика поиска + void _onSearchCharacters(HomeSearchEvent event, Emitter emit) { + final query = event.query.toLowerCase(); + // Фильтруем данные по запросу + final filteredCharacters = state.characters + .where((character) => character.name.toLowerCase().contains(query)) + .toList(); + + // Обновляем состояние с отфильтрованными данными + emit(state.copyWith(status: HomeStatus.loaded, characters: filteredCharacters)); + } } - diff --git a/lib/bloc/events.dart b/lib/bloc/events.dart index 2abe254..06f784a 100644 --- a/lib/bloc/events.dart +++ b/lib/bloc/events.dart @@ -15,3 +15,12 @@ class HomeLoadDataEvent extends HomeEvent { @override List get props => [searchQuery]; } + +class HomeSearchEvent extends HomeEvent { + final String query; + + const HomeSearchEvent(this.query); + + @override + List get props => [query]; +} diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart deleted file mode 100644 index ed66a36..0000000 --- a/lib/generated/l10n.dart +++ /dev/null @@ -1,92 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'dart:async'; -import 'package:identity/l10n/messages_all.dart'; - -class S { - S(); - - static S? _current; - - static S get current { - assert(_current != null, - 'No instance of S was loaded. Try to initialize the S delegate before accessing S.current.'); - return _current!; - } - - static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); - - static Future load(Locale locale) { - final name = locale.countryCode?.isEmpty ?? false - ? locale.languageCode - : locale.toString(); - final localeName = Intl.canonicalizedLocale(name); - Intl.defaultLocale = localeName; - return initializeMessages(localeName).then((_) { - final instance = S(); - S._current = instance; - - return instance; - }); - } - - static S of(BuildContext context) { - final instance = S.maybeOf(context); - assert(instance != null, - 'No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates?'); - return instance!; - } - - static S? maybeOf(BuildContext context) { - return Localizations.of(context, S); - } - - // Translations - String get appTitle { - return Intl.message( - 'My Application', - name: 'appTitle', - desc: '', - args: [], - ); - } - - String get likeButton { - return Intl.message( - 'Like', - name: 'likeButton', - desc: '', - args: [], - ); - } - - String get changeLanguage { - return Intl.message( - 'Change Language', - name: 'changeLanguage', - desc: '', - args: [], - ); - } -} - -class AppLocalizationDelegate extends LocalizationsDelegate { - const AppLocalizationDelegate(); - - @override - bool isSupported(Locale locale) { - return ['en', 'ru'].contains(locale.languageCode); - } - - @override - Future load(Locale locale) { - return S.load(locale); - } - - @override - bool shouldReload(covariant LocalizationsDelegate old) { - return false; - } -} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb deleted file mode 100644 index b27318c..0000000 --- a/lib/l10n/intl_en.arb +++ /dev/null @@ -1,7 +0,0 @@ -{ - "appTitle": "My Application", - "likeButton": "Like", - "changeLanguage": "Change Language", - "errorMessage": "{message}", - "noData": "No data" -} diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb deleted file mode 100644 index 2822a34..0000000 --- a/lib/l10n/intl_ru.arb +++ /dev/null @@ -1,7 +0,0 @@ -{ - "appTitle": "Мое Приложение", - "likeButton": "Лайк", - "changeLanguage": "Сменить язык", - "errorMessage": "{message}", - "noData": "Нет данных" -} diff --git a/lib/main.dart b/lib/main.dart index 3847cd1..5dd2ebc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,10 +5,9 @@ import 'bloc/bloc.dart'; import 'bloc/events.dart'; import 'bloc/state.dart'; import 'utils/character_service.dart'; -import 'models/character.dart'; +import 'components/locale/l10n/app_locale.dart'; import 'pages/character_detail_page.dart'; -import 'package:flutter_localizations/flutter_localizations.dart'; -import 'l10n/messages_all.dart'; // Обновите на путь к сгенерированным локализациям +import 'utils/character_search_delegate.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -62,22 +61,14 @@ class _MyAppState extends State { @override Widget build(BuildContext context) { return MaterialApp( - localizationsDelegates: [ - S.delegate, // Используйте сгенерированные локализации - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: [ - const Locale('en', ''), - const Locale('ru', ''), - ], + debugShowCheckedModeBanner: false, + localizationsDelegates: AppLocale.localizationsDelegates, + supportedLocales: AppLocale.supportedLocales, locale: _locale, localeResolutionCallback: (locale, supportedLocales) { return supportedLocales.contains(locale) ? locale : const Locale('en'); }, home: MyHomePage( - title: S.of(context).appTitle, // Локализованная строка onLanguageChanged: _changeLanguage, ), ); @@ -85,11 +76,9 @@ class _MyAppState extends State { } class MyHomePage extends StatelessWidget { - final String title; final Function(String) onLanguageChanged; const MyHomePage({ - required this.title, required this.onLanguageChanged, super.key, }); @@ -98,25 +87,20 @@ class MyHomePage extends StatelessWidget { Widget build(BuildContext context) { context.read().add(HomeLoadDataEvent()); + final appTitle = AppLocale.of(context)?.appTitle ?? 'Default Title'; + return Scaffold( appBar: AppBar( - title: Text(title), + title: Text(appTitle), actions: [ - PopupMenuButton( - onSelected: (languageCode) { - onLanguageChanged(languageCode); - }, + IconButton( icon: const Icon(Icons.language), - itemBuilder: (context) => [ - PopupMenuItem( - value: 'en', - child: Text(S.of(context).languageEnglish), - ), - PopupMenuItem( - value: 'ru', - child: Text(S.of(context).languageRussian), - ), - ], + onPressed: () { + // Переключение между языками + final currentLocale = Localizations.localeOf(context).languageCode; + final newLocale = currentLocale == 'en' ? 'ru' : 'en'; + onLanguageChanged(newLocale); + }, ), IconButton( icon: const Icon(Icons.search), @@ -134,7 +118,8 @@ class MyHomePage extends StatelessWidget { if (state.status == HomeStatus.loading) { return const Center(child: CircularProgressIndicator()); } else if (state.status == HomeStatus.error) { - return Center(child: Text(S.of(context).errorMessage(state.errorMessage))); + return Center( + child: Text(AppLocale.of(context)?.errorMessage(state.errorMessage) ?? 'Error')); } else if (state.status == HomeStatus.loaded) { final characters = state.characters; @@ -161,7 +146,7 @@ class MyHomePage extends StatelessWidget { }, ); } else { - return Center(child: Text(S.of(context).noData)); + return Center(child: Text(AppLocale.of(context)?.noData ?? 'No Data')); } }, ), diff --git a/lib/pages/character_detail_page.dart b/lib/pages/character_detail_page.dart index e130299..1216865 100644 --- a/lib/pages/character_detail_page.dart +++ b/lib/pages/character_detail_page.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import '../models/character.dart'; +import '../components/locale/l10n/app_locale.dart'; + class CharacterDetailPage extends StatelessWidget { final CharacterDTO characterDTO; @@ -7,9 +9,12 @@ class CharacterDetailPage extends StatelessWidget { @override Widget build(BuildContext context) { + final appLocale = AppLocale.of(context); + final characterTitle = appLocale?.characterTitle(characterDTO.name) ?? characterDTO.name; + return Scaffold( appBar: AppBar( - title: Text(characterDTO.name), + title: Text(characterTitle), ), body: Padding( padding: const EdgeInsets.all(16.0), @@ -24,25 +29,25 @@ class CharacterDetailPage extends StatelessWidget { fit: BoxFit.cover, ), ), - SizedBox(height: 20), + const SizedBox(height: 20), Text( characterDTO.name, - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), - SizedBox(height: 10), + const SizedBox(height: 10), Text( characterDTO.typeString, style: TextStyle(fontSize: 18, color: Colors.grey[600]), ), - SizedBox(height: 20), + const SizedBox(height: 20), Text( - 'Полное описание:', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + appLocale?.likeButton?.toString() ?? 'Like', + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), - SizedBox(height: 10), + const SizedBox(height: 10), Text( characterDTO.backstory, - style: TextStyle(fontSize: 14), + style: const TextStyle(fontSize: 14), maxLines: null, softWrap: true, ), diff --git a/lib/utils/character_search_delegate.dart b/lib/utils/character_search_delegate.dart new file mode 100644 index 0000000..e06465b --- /dev/null +++ b/lib/utils/character_search_delegate.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../bloc/bloc.dart'; +import '../bloc/events.dart'; +import '../bloc/state.dart'; +import '../components/locale/l10n/app_locale.dart'; +import '../pages/character_detail_page.dart'; + +class CharacterSearchDelegate extends SearchDelegate { + @override + List? buildActions(BuildContext context) { + return [ + IconButton( + icon: const Icon(Icons.clear), + onPressed: () { + query = ''; + }, + ), + ]; + } + + @override + Widget? buildLeading(BuildContext context) { + return IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + close(context, null); + }, + ); + } + + @override + Widget buildResults(BuildContext context) { + context.read().add(HomeSearchEvent(query)); + + return BlocBuilder( + builder: (context, state) { + if (state.status == HomeStatus.loading) { + return const Center(child: CircularProgressIndicator()); + } else if (state.status == HomeStatus.error) { + return Center( + child: Text( + AppLocale.of(context)?.errorMessage(state.errorMessage) ?? 'Error', + ), + ); + } else if (state.status == HomeStatus.loaded) { + final characters = state.characters; + + if (characters.isEmpty) { + return Center( + child: Text(AppLocale.of(context)?.noData ?? 'No characters found.'), + ); + } + + return ListView.builder( + itemCount: characters.length, + itemBuilder: (context, index) { + final characterDTO = characters[index]; + return ListTile( + leading: SizedBox( + width: 40, + child: Image.network(characterDTO.imageUrl, width: 50, height: 50), + ), + title: Text(characterDTO.name), + subtitle: Text(characterDTO.typeString), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CharacterDetailPage(characterDTO: characterDTO), + ), + ); + }, + ); + }, + ); + } else { + return const SizedBox.shrink(); + } + }, + ); + } + + @override + Widget buildSuggestions(BuildContext context) { + return const SizedBox.shrink(); + } +}