осталось добавить кнопку обновления
This commit is contained in:
parent
5171697023
commit
7760545cb5
@ -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<String> {
|
||||
DebouncedSearchCubit() : super('');
|
||||
@ -25,14 +24,16 @@ class DebouncedSearchCubit extends Cubit<String> {
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
final CharacterService characterService;
|
||||
|
||||
HomeBloc(this.characterService) : super(const HomeState()) {
|
||||
on<HomeLoadDataEvent>(_onLoadData);
|
||||
on<HomeSearchEvent>(_onSearchCharacters); // Добавлен новый обработчик для поиска
|
||||
}
|
||||
|
||||
// Внутри HomeBloc, когда получаем данные
|
||||
// Загрузка данных
|
||||
Future<void> _onLoadData(HomeLoadDataEvent event, Emitter<HomeState> emit) async {
|
||||
emit(state.copyWith(status: HomeStatus.loading));
|
||||
|
||||
@ -50,6 +51,16 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
}
|
||||
}
|
||||
|
||||
// Логика поиска
|
||||
void _onSearchCharacters(HomeSearchEvent event, Emitter<HomeState> 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,3 +15,12 @@ class HomeLoadDataEvent extends HomeEvent {
|
||||
@override
|
||||
List<Object?> get props => [searchQuery];
|
||||
}
|
||||
|
||||
class HomeSearchEvent extends HomeEvent {
|
||||
final String query;
|
||||
|
||||
const HomeSearchEvent(this.query);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [query];
|
||||
}
|
||||
|
@ -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<S> 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<S>(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<S> {
|
||||
const AppLocalizationDelegate();
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) {
|
||||
return <String>['en', 'ru'].contains(locale.languageCode);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<S> load(Locale locale) {
|
||||
return S.load(locale);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReload(covariant LocalizationsDelegate<S> old) {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"appTitle": "My Application",
|
||||
"likeButton": "Like",
|
||||
"changeLanguage": "Change Language",
|
||||
"errorMessage": "{message}",
|
||||
"noData": "No data"
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"appTitle": "Мое Приложение",
|
||||
"likeButton": "Лайк",
|
||||
"changeLanguage": "Сменить язык",
|
||||
"errorMessage": "{message}",
|
||||
"noData": "Нет данных"
|
||||
}
|
@ -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<MyApp> {
|
||||
@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<MyApp> {
|
||||
}
|
||||
|
||||
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<HomeBloc>().add(HomeLoadDataEvent());
|
||||
|
||||
final appTitle = AppLocale.of(context)?.appTitle ?? 'Default Title';
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(title),
|
||||
title: Text(appTitle),
|
||||
actions: [
|
||||
PopupMenuButton<String>(
|
||||
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'));
|
||||
}
|
||||
},
|
||||
),
|
||||
|
@ -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,
|
||||
),
|
||||
|
88
lib/utils/character_search_delegate.dart
Normal file
88
lib/utils/character_search_delegate.dart
Normal file
@ -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<Widget>? 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<HomeBloc>().add(HomeSearchEvent(query));
|
||||
|
||||
return BlocBuilder<HomeBloc, HomeState>(
|
||||
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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user