осталось добавить кнопку обновления

This commit is contained in:
mirain 2024-12-17 13:28:23 +03:00
parent 5171697023
commit 7760545cb5
8 changed files with 143 additions and 151 deletions

View File

@ -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));
}
}

View File

@ -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];
}

View File

@ -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;
}
}

View File

@ -1,7 +0,0 @@
{
"appTitle": "My Application",
"likeButton": "Like",
"changeLanguage": "Change Language",
"errorMessage": "{message}",
"noData": "No data"
}

View File

@ -1,7 +0,0 @@
{
"appTitle": "Мое Приложение",
"likeButton": "Лайк",
"changeLanguage": "Сменить язык",
"errorMessage": "{message}",
"noData": "Нет данных"
}

View File

@ -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'));
}
},
),

View File

@ -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,
),

View 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();
}
}