готовая 6 лаба

This commit is contained in:
mayday 2024-12-10 13:41:28 +04:00
parent 03dc457dd8
commit 935f2784bf
6 changed files with 123 additions and 57 deletions

View File

@ -25,7 +25,6 @@ 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;
@ -33,17 +32,24 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
on<HomeLoadDataEvent>(_onLoadData); on<HomeLoadDataEvent>(_onLoadData);
} }
// Внутри 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));
try { try {
final characters = await characterService.getCharacters(search: event.searchQuery); // Получаем список объектов CharacterDTO
print('Characters loaded: $characters'); // Отладочный вывод final charactersDTO = await characterService.getCharacters(search: event.searchQuery);
emit(state.copyWith(status: HomeStatus.loaded, characters: characters));
// Преобразуем список Character в список CharacterDTO
final charactersAsDTO = charactersDTO.map((character) => character.toDTO()).toList();
emit(state.copyWith(status: HomeStatus.loaded, characters: charactersAsDTO)); // Передаем в emit
} catch (e) { } catch (e) {
print('Error: $e'); print('Error: $e');
emit(state.copyWith(status: HomeStatus.error, errorMessage: e.toString())); emit(state.copyWith(status: HomeStatus.error, errorMessage: e.toString()));
} }
} }
} }

View File

@ -3,10 +3,9 @@ import 'package:flutter/material.dart';
import '../models/character.dart'; // Добавим импорт для Character import '../models/character.dart'; // Добавим импорт для Character
enum HomeStatus { initial, loading, loaded, error } enum HomeStatus { initial, loading, loaded, error }
class HomeState extends Equatable { class HomeState extends Equatable {
final HomeStatus status; final HomeStatus status;
final List<Character> characters; // Список персонажей final List<CharacterDTO> characters; // Теперь используем DTO
final String errorMessage; final String errorMessage;
const HomeState({ const HomeState({
@ -15,10 +14,9 @@ class HomeState extends Equatable {
this.errorMessage = '', this.errorMessage = '',
}); });
// Метод для обновления состояния
HomeState copyWith({ HomeState copyWith({
HomeStatus? status, HomeStatus? status,
List<Character>? characters, List<CharacterDTO>? characters,
String? errorMessage, String? errorMessage,
}) { }) {
return HomeState( return HomeState(

View File

@ -41,7 +41,6 @@ class MyHomePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Загружаем данные сразу после инициализации страницы
context.read<HomeBloc>().add(HomeLoadDataEvent()); context.read<HomeBloc>().add(HomeLoadDataEvent());
return Scaffold( return Scaffold(
@ -71,20 +70,20 @@ class MyHomePage extends StatelessWidget {
return ListView.builder( return ListView.builder(
itemCount: characters.length, itemCount: characters.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final character = characters[index]; final characterDTO = characters[index]; // Убедитесь, что это CharacterDTO
return ListTile( return ListTile(
leading: SizedBox( leading: SizedBox(
width: 40, width: 40,
child: Image.network(character.imageUrl, width: 50, height: 50), child: Image.network(characterDTO.imageUrl, width: 50, height: 50),
), ),
title: Text(character.name), title: Text(characterDTO.name),
subtitle: Text(character.typeString), subtitle: Text(characterDTO.typeString), // Используем typeString из CharacterDTO
onTap: () { onTap: () {
// Переход на страницу с деталями персонажа // Переход на страницу с деталями персонажа, передаем CharacterDTO
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => CharacterDetailPage(character: character), builder: (context) => CharacterDetailPage(characterDTO: characterDTO), // Передаем CharacterDTO
), ),
); );
}, },
@ -103,7 +102,6 @@ class MyHomePage extends StatelessWidget {
class CharacterSearchDelegate extends SearchDelegate { class CharacterSearchDelegate extends SearchDelegate {
@override @override
Widget buildSuggestions(BuildContext context) { Widget buildSuggestions(BuildContext context) {
// Показываем предложения на основе текущего запроса
return BlocProvider<HomeBloc>( return BlocProvider<HomeBloc>(
create: (context) => HomeBloc(CharacterService()), create: (context) => HomeBloc(CharacterService()),
child: BlocBuilder<HomeBloc, HomeState>( child: BlocBuilder<HomeBloc, HomeState>(
@ -115,15 +113,16 @@ class CharacterSearchDelegate extends SearchDelegate {
return ListView.builder( return ListView.builder(
itemCount: suggestions.length, itemCount: suggestions.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final character = suggestions[index]; final character = suggestions[index]; // Используем правильный тип
return ListTile( return ListTile(
title: Text(character.name), title: Text(character.name),
subtitle: Text(character.typeString), subtitle: Text(character.typeString),
onTap: () { onTap: () {
// Переход на страницу с деталями персонажа
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => CharacterDetailPage(character: character), builder: (context) => CharacterDetailPage(characterDTO: character), // Передаем правильный параметр
), ),
); );
}, },
@ -137,13 +136,11 @@ class CharacterSearchDelegate extends SearchDelegate {
@override @override
Widget buildResults(BuildContext context) { Widget buildResults(BuildContext context) {
// Отправляем запрос с задержкой
final debouncedSearchCubit = context.read<DebouncedSearchCubit>(); final debouncedSearchCubit = context.read<DebouncedSearchCubit>();
debouncedSearchCubit.search(query); debouncedSearchCubit.search(query);
return BlocBuilder<DebouncedSearchCubit, String>( return BlocBuilder<DebouncedSearchCubit, String>(
builder: (context, searchQuery) { builder: (context, searchQuery) {
// Отправляем запрос на сервер через HomeBloc
if (searchQuery.isNotEmpty) { if (searchQuery.isNotEmpty) {
context.read<HomeBloc>().add(HomeLoadDataEvent(searchQuery: searchQuery)); context.read<HomeBloc>().add(HomeLoadDataEvent(searchQuery: searchQuery));
} }
@ -161,21 +158,26 @@ class CharacterSearchDelegate extends SearchDelegate {
return ListView.builder( return ListView.builder(
itemCount: characters.length, itemCount: characters.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final character = characters[index]; final characterDTO = characters[index]; // Здесь определяем characterDTO
return ListTile( return ListTile(
title: Text(character.name), leading: SizedBox(
subtitle: Text(character.typeString), width: 40,
onTap: () { child: Image.network(characterDTO.imageUrl, width: 50, height: 50),
Navigator.push( ),
context, title: Text(characterDTO.name),
MaterialPageRoute( subtitle: Text(characterDTO.typeString), // Используем typeString
builder: (context) => CharacterDetailPage(character: character), onTap: () {
), // Переход на страницу с деталями персонажа
); Navigator.push(
}, context,
); MaterialPageRoute(
}, builder: (context) => CharacterDetailPage(characterDTO: characterDTO), // Передаем characterDTO
),
);
},
);
}
); );
} else { } else {
return Center(child: Text('Нет результатов')); return Center(child: Text('Нет результатов'));
@ -192,8 +194,8 @@ class CharacterSearchDelegate extends SearchDelegate {
IconButton( IconButton(
icon: Icon(Icons.clear), icon: Icon(Icons.clear),
onPressed: () { onPressed: () {
query = ''; // Очистить запрос query = '';
showSuggestions(context); // Показать предложения showSuggestions(context);
}, },
), ),
]; ];
@ -204,7 +206,7 @@ class CharacterSearchDelegate extends SearchDelegate {
return IconButton( return IconButton(
icon: Icon(Icons.arrow_back), icon: Icon(Icons.arrow_back),
onPressed: () { onPressed: () {
close(context, null); // Закрыть поиск close(context, null);
}, },
); );
} }

View File

@ -54,8 +54,9 @@ class Character {
return '$typeString - $backstory'; return '$typeString - $backstory';
} }
static empty() { // Для пустого объекта
return null; static Character? empty() {
return null; // Возвращаем null для пустого объекта
} }
} }
@ -84,3 +85,68 @@ class Hunter extends Character {
type: CharacterType.Hunter, type: CharacterType.Hunter,
); );
} }
class CharacterDTO {
final String name;
final String characterType;
final String backstory;
final String imageUrl;
CharacterDTO({
required this.name,
required this.characterType,
required this.backstory,
required this.imageUrl,
});
CharacterDTO toDTO() {
return CharacterDTO(
name: name,
characterType: typeString,
backstory: backstory,
imageUrl: imageUrl,
);
}
// Добавим метод fromJson
factory CharacterDTO.fromJson(Map<String, dynamic> json) {
return CharacterDTO(
name: json['name'],
characterType: json['type'], // 'type' - это то, что мы ожидаем от API
backstory: json['backstory'],
imageUrl: json['image_url'], // Используем корректное имя поля для URL
);
}
// Геттер для typeString
String get typeString {
switch (characterType) {
case 'Выживший':
return 'Выживший';
case 'Охотник':
return 'Охотник';
default:
return 'Неизвестно';
}
}
// Преобразуем DTO обратно в объект Character
Character toCharacter() {
return Character(
name: name,
type: characterType == 'Выживший' ? CharacterType.Survivor : CharacterType.Hunter,
backstory: backstory,
imageUrl: imageUrl,
);
}
// Метод для преобразования в Map
Map<String, dynamic> toMap() {
return {
'name': name,
'type': characterType,
'backstory': backstory,
'image_url': imageUrl,
};
}
}

View File

@ -1,54 +1,47 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../models/character.dart'; // Убедитесь, что путь правильный import '../models/character.dart'; // Убедитесь, что путь правильный
class CharacterDetailPage extends StatelessWidget { class CharacterDetailPage extends StatelessWidget {
final Character character; final CharacterDTO characterDTO;
// Конструктор с обязательным параметром const CharacterDetailPage({Key? key, required this.characterDTO}) : super(key: key);
const CharacterDetailPage({Key? key, required this.character}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(character.name), title: Text(characterDTO.name),
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Изображение персонажа
Center( Center(
child: Image.network( child: Image.network(
character.imageUrl, characterDTO.imageUrl,
height: 200, height: 200,
width: 200, width: 200,
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
), ),
SizedBox(height: 20), SizedBox(height: 20),
Text( Text(
character.name, // Имя персонажа characterDTO.name,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
), ),
SizedBox(height: 10), SizedBox(height: 10),
Text( Text(
character.typeString, // Тип персонажа (Survivor или Hunter) characterDTO.typeString,
style: TextStyle(fontSize: 18, color: Colors.grey[600]), style: TextStyle(fontSize: 18, color: Colors.grey[600]),
), ),
SizedBox(height: 20), SizedBox(height: 20),
// Предыстория персонажа
Text( Text(
'Полное описание:', 'Полное описание:',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
), ),
SizedBox(height: 10), SizedBox(height: 10),
// Текст предыстории
Text( Text(
character.backstory, characterDTO.backstory,
style: TextStyle(fontSize: 14), style: TextStyle(fontSize: 14),
maxLines: null, maxLines: null,
softWrap: true, softWrap: true,

View File

@ -5,7 +5,8 @@ import '../models/character.dart';
const String baseUrl = 'http://192.168.1.83:5000'; // IP-адрес вместо localhost const String baseUrl = 'http://192.168.1.83:5000'; // IP-адрес вместо localhost
class CharacterService { class CharacterService {
Future<List<Character>> getCharacters({String search = ''}) async { // Метод теперь возвращает список CharacterDTO
Future<List<CharacterDTO>> getCharacters({String search = ''}) async {
try { try {
// Формируем URL с параметром поиска // Формируем URL с параметром поиска
final uri = Uri.parse('$baseUrl/characters?search=$search'); final uri = Uri.parse('$baseUrl/characters?search=$search');
@ -22,8 +23,8 @@ class CharacterService {
final List<dynamic> data = json.decode(response.body); final List<dynamic> data = json.decode(response.body);
print('Characters received: $data'); // Печать данных print('Characters received: $data'); // Печать данных
// Возвращаем список объектов Character // Возвращаем список объектов CharacterDTO
return data.map((item) => Character.fromJson(item)).toList(); return data.map((item) => CharacterDTO.fromJson(item)).toList();
} else { } else {
// Ошибка, если сервер вернул не 200 статус // Ошибка, если сервер вернул не 200 статус
print('Error: Server responded with status ${response.statusCode}'); print('Error: Server responded with status ${response.statusCode}');