готовая 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();
}
}
class HomeBloc extends Bloc<HomeEvent, HomeState> {
final CharacterService characterService;
@ -33,17 +32,24 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
on<HomeLoadDataEvent>(_onLoadData);
}
// Внутри HomeBloc, когда получаем данные
Future<void> _onLoadData(HomeLoadDataEvent event, Emitter<HomeState> emit) async {
emit(state.copyWith(status: HomeStatus.loading));
try {
final characters = await characterService.getCharacters(search: event.searchQuery);
print('Characters loaded: $characters'); // Отладочный вывод
emit(state.copyWith(status: HomeStatus.loaded, characters: characters));
// Получаем список объектов CharacterDTO
final charactersDTO = await characterService.getCharacters(search: event.searchQuery);
// Преобразуем список Character в список CharacterDTO
final charactersAsDTO = charactersDTO.map((character) => character.toDTO()).toList();
emit(state.copyWith(status: HomeStatus.loaded, characters: charactersAsDTO)); // Передаем в emit
} catch (e) {
print('Error: $e');
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
enum HomeStatus { initial, loading, loaded, error }
class HomeState extends Equatable {
final HomeStatus status;
final List<Character> characters; // Список персонажей
final List<CharacterDTO> characters; // Теперь используем DTO
final String errorMessage;
const HomeState({
@ -15,10 +14,9 @@ class HomeState extends Equatable {
this.errorMessage = '',
});
// Метод для обновления состояния
HomeState copyWith({
HomeStatus? status,
List<Character>? characters,
List<CharacterDTO>? characters,
String? errorMessage,
}) {
return HomeState(

View File

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

View File

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

View File

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