готовая 6 лаба
This commit is contained in:
parent
03dc457dd8
commit
935f2784bf
@ -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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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}');
|
||||||
|
Loading…
Reference in New Issue
Block a user