From 935f2784bfb14413628552f6cc32222b2508e9d0 Mon Sep 17 00:00:00 2001 From: mayday Date: Tue, 10 Dec 2024 13:41:28 +0400 Subject: [PATCH] =?UTF-8?q?=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D0=B0=D1=8F=206?= =?UTF-8?q?=20=D0=BB=D0=B0=D0=B1=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bloc/bloc.dart | 14 ++++-- lib/bloc/state.dart | 6 +-- lib/main.dart | 62 ++++++++++++------------ lib/models/character.dart | 70 +++++++++++++++++++++++++++- lib/pages/character_detail_page.dart | 21 +++------ lib/utils/character_service.dart | 7 +-- 6 files changed, 123 insertions(+), 57 deletions(-) diff --git a/lib/bloc/bloc.dart b/lib/bloc/bloc.dart index 7a72813..56d26ee 100644 --- a/lib/bloc/bloc.dart +++ b/lib/bloc/bloc.dart @@ -25,7 +25,6 @@ class DebouncedSearchCubit extends Cubit { return super.close(); } } - class HomeBloc extends Bloc { final CharacterService characterService; @@ -33,17 +32,24 @@ class HomeBloc extends Bloc { on(_onLoadData); } + // Внутри HomeBloc, когда получаем данные Future _onLoadData(HomeLoadDataEvent event, Emitter 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())); } } + + } diff --git a/lib/bloc/state.dart b/lib/bloc/state.dart index 01dac59..96b3544 100644 --- a/lib/bloc/state.dart +++ b/lib/bloc/state.dart @@ -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 characters; // Список персонажей + final List characters; // Теперь используем DTO final String errorMessage; const HomeState({ @@ -15,10 +14,9 @@ class HomeState extends Equatable { this.errorMessage = '', }); - // Метод для обновления состояния HomeState copyWith({ HomeStatus? status, - List? characters, + List? characters, String? errorMessage, }) { return HomeState( diff --git a/lib/main.dart b/lib/main.dart index 3a692b8..e628094 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -41,7 +41,6 @@ class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { - // Загружаем данные сразу после инициализации страницы context.read().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( create: (context) => HomeBloc(CharacterService()), child: BlocBuilder( @@ -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.search(query); return BlocBuilder( builder: (context, searchQuery) { - // Отправляем запрос на сервер через HomeBloc if (searchQuery.isNotEmpty) { context.read().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); }, ); } diff --git a/lib/models/character.dart b/lib/models/character.dart index e1d3ab7..10de82f 100644 --- a/lib/models/character.dart +++ b/lib/models/character.dart @@ -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 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 toMap() { + return { + 'name': name, + 'type': characterType, + 'backstory': backstory, + 'image_url': imageUrl, + }; + } +} diff --git a/lib/pages/character_detail_page.dart b/lib/pages/character_detail_page.dart index 47cb339..cf019eb 100644 --- a/lib/pages/character_detail_page.dart +++ b/lib/pages/character_detail_page.dart @@ -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, diff --git a/lib/utils/character_service.dart b/lib/utils/character_service.dart index 75e5279..4d87801 100644 --- a/lib/utils/character_service.dart +++ b/lib/utils/character_service.dart @@ -5,7 +5,8 @@ import '../models/character.dart'; const String baseUrl = 'http://192.168.1.83:5000'; // IP-адрес вместо localhost class CharacterService { - Future> getCharacters({String search = ''}) async { + // Метод теперь возвращает список CharacterDTO + Future> getCharacters({String search = ''}) async { try { // Формируем URL с параметром поиска final uri = Uri.parse('$baseUrl/characters?search=$search'); @@ -22,8 +23,8 @@ class CharacterService { final List 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}');