осталось добавить кнопку обновления
This commit is contained in:
parent
5171697023
commit
7760545cb5
@ -4,7 +4,6 @@ import 'state.dart';
|
|||||||
import '../utils/character_service.dart';
|
import '../utils/character_service.dart';
|
||||||
import '../models/character.dart';
|
import '../models/character.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
class DebouncedSearchCubit extends Cubit<String> {
|
class DebouncedSearchCubit extends Cubit<String> {
|
||||||
DebouncedSearchCubit() : super('');
|
DebouncedSearchCubit() : super('');
|
||||||
@ -25,14 +24,16 @@ class DebouncedSearchCubit extends Cubit<String> {
|
|||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||||
final CharacterService characterService;
|
final CharacterService characterService;
|
||||||
|
|
||||||
HomeBloc(this.characterService) : super(const HomeState()) {
|
HomeBloc(this.characterService) : super(const HomeState()) {
|
||||||
on<HomeLoadDataEvent>(_onLoadData);
|
on<HomeLoadDataEvent>(_onLoadData);
|
||||||
|
on<HomeSearchEvent>(_onSearchCharacters); // Добавлен новый обработчик для поиска
|
||||||
}
|
}
|
||||||
|
|
||||||
// Внутри HomeBloc, когда получаем данные
|
// Загрузка данных
|
||||||
Future<void> _onLoadData(HomeLoadDataEvent event, Emitter<HomeState> emit) async {
|
Future<void> _onLoadData(HomeLoadDataEvent event, Emitter<HomeState> emit) async {
|
||||||
emit(state.copyWith(status: HomeStatus.loading));
|
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
|
@override
|
||||||
List<Object?> get props => [searchQuery];
|
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/events.dart';
|
||||||
import 'bloc/state.dart';
|
import 'bloc/state.dart';
|
||||||
import 'utils/character_service.dart';
|
import 'utils/character_service.dart';
|
||||||
import 'models/character.dart';
|
import 'components/locale/l10n/app_locale.dart';
|
||||||
import 'pages/character_detail_page.dart';
|
import 'pages/character_detail_page.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'utils/character_search_delegate.dart';
|
||||||
import 'l10n/messages_all.dart'; // Обновите на путь к сгенерированным локализациям
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@ -62,22 +61,14 @@ class _MyAppState extends State<MyApp> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
localizationsDelegates: [
|
debugShowCheckedModeBanner: false,
|
||||||
S.delegate, // Используйте сгенерированные локализации
|
localizationsDelegates: AppLocale.localizationsDelegates,
|
||||||
GlobalMaterialLocalizations.delegate,
|
supportedLocales: AppLocale.supportedLocales,
|
||||||
GlobalWidgetsLocalizations.delegate,
|
|
||||||
GlobalCupertinoLocalizations.delegate,
|
|
||||||
],
|
|
||||||
supportedLocales: [
|
|
||||||
const Locale('en', ''),
|
|
||||||
const Locale('ru', ''),
|
|
||||||
],
|
|
||||||
locale: _locale,
|
locale: _locale,
|
||||||
localeResolutionCallback: (locale, supportedLocales) {
|
localeResolutionCallback: (locale, supportedLocales) {
|
||||||
return supportedLocales.contains(locale) ? locale : const Locale('en');
|
return supportedLocales.contains(locale) ? locale : const Locale('en');
|
||||||
},
|
},
|
||||||
home: MyHomePage(
|
home: MyHomePage(
|
||||||
title: S.of(context).appTitle, // Локализованная строка
|
|
||||||
onLanguageChanged: _changeLanguage,
|
onLanguageChanged: _changeLanguage,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -85,11 +76,9 @@ class _MyAppState extends State<MyApp> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class MyHomePage extends StatelessWidget {
|
class MyHomePage extends StatelessWidget {
|
||||||
final String title;
|
|
||||||
final Function(String) onLanguageChanged;
|
final Function(String) onLanguageChanged;
|
||||||
|
|
||||||
const MyHomePage({
|
const MyHomePage({
|
||||||
required this.title,
|
|
||||||
required this.onLanguageChanged,
|
required this.onLanguageChanged,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
@ -98,25 +87,20 @@ class MyHomePage extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
context.read<HomeBloc>().add(HomeLoadDataEvent());
|
context.read<HomeBloc>().add(HomeLoadDataEvent());
|
||||||
|
|
||||||
|
final appTitle = AppLocale.of(context)?.appTitle ?? 'Default Title';
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(title),
|
title: Text(appTitle),
|
||||||
actions: [
|
actions: [
|
||||||
PopupMenuButton<String>(
|
IconButton(
|
||||||
onSelected: (languageCode) {
|
|
||||||
onLanguageChanged(languageCode);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.language),
|
icon: const Icon(Icons.language),
|
||||||
itemBuilder: (context) => [
|
onPressed: () {
|
||||||
PopupMenuItem(
|
// Переключение между языками
|
||||||
value: 'en',
|
final currentLocale = Localizations.localeOf(context).languageCode;
|
||||||
child: Text(S.of(context).languageEnglish),
|
final newLocale = currentLocale == 'en' ? 'ru' : 'en';
|
||||||
),
|
onLanguageChanged(newLocale);
|
||||||
PopupMenuItem(
|
},
|
||||||
value: 'ru',
|
|
||||||
child: Text(S.of(context).languageRussian),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.search),
|
icon: const Icon(Icons.search),
|
||||||
@ -134,7 +118,8 @@ class MyHomePage extends StatelessWidget {
|
|||||||
if (state.status == HomeStatus.loading) {
|
if (state.status == HomeStatus.loading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
} else if (state.status == HomeStatus.error) {
|
} 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) {
|
} else if (state.status == HomeStatus.loaded) {
|
||||||
final characters = state.characters;
|
final characters = state.characters;
|
||||||
|
|
||||||
@ -161,7 +146,7 @@ class MyHomePage extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} 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 'package:flutter/material.dart';
|
||||||
import '../models/character.dart';
|
import '../models/character.dart';
|
||||||
|
import '../components/locale/l10n/app_locale.dart';
|
||||||
|
|
||||||
class CharacterDetailPage extends StatelessWidget {
|
class CharacterDetailPage extends StatelessWidget {
|
||||||
final CharacterDTO characterDTO;
|
final CharacterDTO characterDTO;
|
||||||
|
|
||||||
@ -7,9 +9,12 @@ class CharacterDetailPage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final appLocale = AppLocale.of(context);
|
||||||
|
final characterTitle = appLocale?.characterTitle(characterDTO.name) ?? characterDTO.name;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(characterDTO.name),
|
title: Text(characterTitle),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
@ -24,25 +29,25 @@ class CharacterDetailPage extends StatelessWidget {
|
|||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Text(
|
Text(
|
||||||
characterDTO.name,
|
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(
|
Text(
|
||||||
characterDTO.typeString,
|
characterDTO.typeString,
|
||||||
style: TextStyle(fontSize: 18, color: Colors.grey[600]),
|
style: TextStyle(fontSize: 18, color: Colors.grey[600]),
|
||||||
),
|
),
|
||||||
SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Text(
|
Text(
|
||||||
'Полное описание:',
|
appLocale?.likeButton?.toString() ?? 'Like',
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
characterDTO.backstory,
|
characterDTO.backstory,
|
||||||
style: TextStyle(fontSize: 14),
|
style: const TextStyle(fontSize: 14),
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
softWrap: true,
|
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