diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..98d26074 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +SERVER_ROOT='https://ohngeeri3ien-dev3.eatvio.ru/api' +STORAGE_URL='https://ohngeeri3ien-dev3.eatvio.ru/storage/' \ No newline at end of file diff --git a/assets/images/img_2456927096976.svg b/assets/images/img_2456927096976.svg index 17ee6769..f869ff41 100644 --- a/assets/images/img_2456927096976.svg +++ b/assets/images/img_2456927096976.svg @@ -1,5 +1,12 @@ - - - - + + + + + + + + + + + diff --git a/assets/images/img_logo_eatvio.svg b/assets/images/img_logo_eatvio.svg index 701b5d1d..bc575bc9 100644 --- a/assets/images/img_logo_eatvio.svg +++ b/assets/images/img_logo_eatvio.svg @@ -1,12 +1,12 @@ - - - - - + + + + + + + + + + - - - - - diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 00000000..d97f17e2 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 08477d7a..8397f756 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -41,5 +41,9 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/lib/core/utils/size_utils.dart b/lib/core/utils/size_utils.dart index ddc30061..8911620b 100644 --- a/lib/core/utils/size_utils.dart +++ b/lib/core/utils/size_utils.dart @@ -3,7 +3,8 @@ import 'package:flutter/material.dart'; // This functions are responsible to make UI responsive across all the mobile devices. -MediaQueryData mediaQueryData = MediaQueryData.fromWindow(ui.window); +// ignore: deprecated_member_use +MediaQueryData mediaQueryData = MediaQueryData.fromView(ui.window); // These are the Viewport values of your Figma Design. // These are used in the code as a reference to create your UI Responsively. diff --git a/lib/presentation/auth/auth_screen/auth_screen.dart b/lib/presentation/auth/auth_screen/auth_screen.dart index 19bcac26..4cbef203 100644 --- a/lib/presentation/auth/auth_screen/auth_screen.dart +++ b/lib/presentation/auth/auth_screen/auth_screen.dart @@ -68,6 +68,14 @@ class AuthScreen extends StatelessWidget { Navigator.pushNamed( context, AppRoutes.languageSelectScreen); }), + NextStepButton( + text: "Тест дневника".toUpperCase(), + margin: + EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + onPressed: () { + Navigator.pushNamed(context, AppRoutes.diaryEdit); + }), BlocBuilder(builder: (context, state) { return NextStepButton( text: "authentificate".tr.toUpperCase(), @@ -95,7 +103,7 @@ class AuthScreen extends StatelessWidget { box.put('token', token); Navigator.pushNamed( context, - AppRoutes.diaryEdit, + AppRoutes.myProfile, ); } else { showDialog( diff --git a/lib/presentation/diary/diary_edit_screen/bloc/diary_edit_bloc.dart b/lib/presentation/diary/diary_edit_screen/bloc/diary_edit_bloc.dart index 7254d633..1796418f 100644 --- a/lib/presentation/diary/diary_edit_screen/bloc/diary_edit_bloc.dart +++ b/lib/presentation/diary/diary_edit_screen/bloc/diary_edit_bloc.dart @@ -1,10 +1,12 @@ +import 'dart:async'; + import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import '/core/app_export.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:eatvio_app/presentation/diary/diary_edit_screen/models/diary_edit_model.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; -import 'package:hive/hive.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; part 'diary_edit_event.dart'; @@ -14,6 +16,22 @@ part 'diary_edit_state.dart'; class DiaryEditBloc extends Bloc { DiaryEditBloc(DiaryEditState initialState) : super(initialState) { on(_onInitialize); + on(_changeSatiety); // пользователь нажимает на шкалу сытости + on(_changeMood); // пользователь нажимает на шкалу настроения + on( + _changeMealTime); // пользователь изменяет время приема пиши + on(_changeMeal); + on(_changeWakeup); // пользователь выбирает время пробуждения + on(_changeRetire); // пользователь выбирает время отхода ко сну + on(_toggleMood); + on(_toggleWakeup); + on(_addPortion); //пользователь нажимает "Добавить продукт" + on( + _removePortion); // пользователь нажимает на крестик около продукта + on(_uploadImages); //пользователь нажимает "Добавить фото" + on(_addComment); //пользователь нажимает "Добавить комментарий" + on( + _inputProduct); //пользователь нажимает "Добавить комментарий" } Future _onInitialize( @@ -21,8 +39,8 @@ class DiaryEditBloc extends Bloc { Emitter emit, ) async { try { - final box = await Hive.openBox('token'); - final token = box.get('token'); + final token = + "laravel_sanctum_Q22zOtJhQUcJnh0kseZFblFv5XAPaKbG3gTM5hga71ecbb49"; final response = await http.get( Uri.parse(dotenv.env['SERVER_ROOT']! + '/diary/index'), headers: { @@ -32,18 +50,78 @@ class DiaryEditBloc extends Bloc { if (response.statusCode == 200) { final data = jsonDecode(response.body); - // Проверка наличия ключевых полей в JSON if (data is Map && data.containsKey('id')) { - // Все ключевые поля присутствуют, продолжаем обработку - if (state is DiaryEditLoadedState) { - // Если текущее состояние уже загружено, обновляем его - var currentState = state as DiaryEditLoadedState; - // Обновляем значения контроллеров только если они уже были созданы - - emit((currentState).copyWith()); - } else { - // Если текущее состояние - начальное, создаем новое загруженное состояние - emit(DiaryEditLoadedState()); + final int diaryId = data['id']; + try { + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/diary/show/$diaryId'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + final diary = jsonDecode(response.body)['diary']; + if (diary is Map) { + print(diary['meals'][1].runtimeType); + final String diaryDate = + diary['date'] ?? DateTime.now().toString(); + // Продолжайте обработку данных дневника + if (state is DiaryEditLoadedState) { + // Если текущее состояние уже загружено, обновляем его + var currentState = state as DiaryEditLoadedState; + emit((currentState).copyWith( + currentMeal: currentState.currentMeal, + wakeupExpanded: currentState.wakeupExpanded, + moodExpanded: currentState.moodExpanded, + token: currentState.token, + meals: currentState.meals, + wakeupController: currentState.wakeupController, + retireController: currentState.retireController, + commentController: currentState.commentController, + mealTimeController: currentState.mealTimeController, + productNameController: currentState.productNameController, + selectedSatiety: currentState.selectedSatiety, + selectedMood: currentState.selectedMood, + diaryDate: currentState.diaryDate, + diaryId: currentState.diaryId, + productQuantityController: + currentState.productQuantityController, + autocompleteList: currentState.autocompleteList, + diaryEditModelObj: currentState.diaryEditModelObj)); + } else { + // Если текущее состояние - начальное, создаем новое загруженное состояние + emit(DiaryEditLoadedState( + currentMeal: SelectionPopupModel( + id: 0, title: "Прием пищи 1", isSelected: true), + wakeupExpanded: true, + moodExpanded: true, + token: token, + meals: diary['meals'], + portions: diary['meals'][0]['portions'], + wakeupController: toTime(diary['wakeup'] ?? "09:00"), + retireController: toTime(diary['retire'] ?? "23:00"), + mealTimeController: + toTime(diary['meals'][0]['time'] ?? "10:00"), + selectedSatiety: diary['meals'][0]['satiety'], + selectedMood: diary['meals'][0]['mood'], + commentController: + TextEditingController(text: diary['comment']), + productNameController: TextEditingController(), + productQuantityController: TextEditingController(), + diaryId: diaryId, + diaryDate: diaryDate, + autocompleteList: [], + diaryEditModelObj: state.diaryEditModelObj + ?.copyWith(mealList: fillMealList()))); + } + } else { + // Ошибка при декодировании JSON + emit(DiaryEditErrorState( + 'Ошибка при декодировании данных дневника')); + } + } catch (e) { + // Ошибка при получении данных дневника + emit(DiaryEditErrorState( + 'Ошибка при получении данных дневника: $e')); } } else { // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку @@ -55,8 +133,386 @@ class DiaryEditBloc extends Bloc { emit(DiaryEditErrorState('Ошибка сервера: ${response.statusCode}')); } } catch (e) { - // Ошибка при декодировании JSON или другая ошибка выполнения - emit(DiaryEditErrorState('Ошибка при получении данных профиля: $e')); + // Ошибка при получении данных дневника + emit(DiaryEditErrorState('Ошибка при получении данных дневника: $e')); } } + + Future _changeSatiety( + ChangeSatiety event, Emitter emit) async { + if (state is DiaryEditLoadedState) { + var currentState = state as DiaryEditLoadedState; + var updatedState = + currentState.copyWith(selectedSatiety: event.selectedElement); + + emit(updatedState); // Передаем обновленное состояние в emit + + // Отправляем данные по HTTP маршруту и ждем ответ + var response = await sendSatietyData( + currentState.token, + event.selectedElement, + currentState.currentMeal?.id, + currentState.diaryId); + + if (response.statusCode == 200) { + currentState.selectedSatiety = event.selectedElement; + emit(currentState); // Передаем обновленное состояние в emit + } + } + } + + Future sendSatietyData( + String? token, + int selectedSatiety, + int? mealID, + int diaryID, + ) async { + String url = + dotenv.env['SERVER_ROOT']! + '$diaryID/meal/$mealID/satiety/fill'; + final credentials = { + 'satiety': selectedSatiety, + 'meal_id': mealID, + 'diary_id': diaryID, + }; + + var response = await http.post(Uri.parse(url), + headers: { + 'Authorization': 'Bearer $token', + }, + body: json.encode(credentials), + encoding: utf8); + + return response; + } + + Future _changeMood( + ChangeMood event, Emitter emit) async { + if (state is DiaryEditLoadedState) { + var currentState = state as DiaryEditLoadedState; + var updatedState = + currentState.copyWith(selectedMood: event.selectedElement); + + // Отправляем данные по HTTP маршруту и ждем ответ + var response = await sendMoodData( + currentState.token, + event.selectedElement, + currentState.currentMeal?.id, + currentState.diaryId); + + if (response.statusCode == 200) { + updatedState.selectedMood = event.selectedElement; + emit(updatedState); // Передаем обновленное состояние в emit + } + } + } + + Future sendMoodData( + String? token, + int selectedMood, + int? mealID, + int diaryID, + ) async { + String url = + dotenv.env['SERVER_ROOT']! + '/$diaryID/meal/$mealID/mood/fill'; + final credentials = { + 'mood': selectedMood, + 'meal_id': mealID, + 'diary_id': diaryID, + }; + + var response = await http.post(Uri.parse(url), + headers: { + 'Authorization': 'Bearer $token', + }, + body: json.encode(credentials), + encoding: utf8); + + return response; + } + + FutureOr _changeWakeup( + WakeupEvent event, Emitter emit) async { + if (state is DiaryEditLoadedState) { + var currentState = state as DiaryEditLoadedState; + var updatedState = + currentState.copyWith(wakeupController: event.selectedTime); + + // Отправляем данные по HTTP маршруту и ждем ответ + var response = await sendWakeupData( + currentState.token, event.selectedTime, currentState.diaryId); + + if (response.statusCode == 200) { + updatedState.wakeupController = event.selectedTime; + emit(updatedState); // Передаем обновленное состояние в emit + } + } + } + + Future sendWakeupData( + String? token, + TimeOfDay wakeup, + int diaryID, + ) async { + String url = dotenv.env['SERVER_ROOT']! + '/diary/???'; + final credentials = { + 'wakeup': wakeup, + 'diary_id': diaryID, + }; + + var response = await http.post(Uri.parse(url), + headers: { + 'Authorization': 'Bearer $token', + }, + body: json.encode(credentials), + encoding: utf8); + + return response; + } + + FutureOr _changeRetire( + RetireEvent event, Emitter emit) async { + if (state is DiaryEditLoadedState) { + var currentState = state as DiaryEditLoadedState; + var updatedState = + currentState.copyWith(retireController: event.selectedTime); + + // Отправляем данные по HTTP маршруту и ждем ответ + var response = await sendRetireData( + currentState.token, event.selectedTime, currentState.diaryId); + + if (response.statusCode == 200) { + updatedState.retireController = event.selectedTime; + emit(updatedState); // Передаем обновленное состояние в emit + } + } + } + + Future sendRetireData( + String? token, TimeOfDay retire, int diaryID) async { + String url = dotenv.env['SERVER_ROOT']! + '/diary/$diaryID/retire/fill'; + final credentials = { + 'retire': retire, + }; + + var response = await http.post(Uri.parse(url), + headers: { + 'Authorization': 'Bearer $token', + }, + body: json.encode(credentials), + encoding: utf8); + + return response; + } + + FutureOr _changeMealTime( + ChangeMealTime event, Emitter emit) async { + if (state is DiaryEditLoadedState) { + var currentState = state as DiaryEditLoadedState; + var updatedState = currentState.copyWith(mealTimeController: event.time); + + emit(updatedState); // Передаем обновленное состояние в emit + + // Отправляем данные по HTTP маршруту и ждем ответ + var response = await sendMealTimeData(currentState.token, event.time, + currentState.currentMeal?.id, currentState.diaryId); + + if (response.statusCode == 200) { + currentState.retireController = event.time; + emit(currentState); // Передаем обновленное состояние в emit + } + } + } + + Future sendMealTimeData( + String? token, + TimeOfDay mealTime, + int? mealID, + int diaryID, + ) async { + String url = dotenv.env['SERVER_ROOT']! + '/diary/???'; + final credentials = { + 'time': mealTime, + 'meal_id': mealID, + 'diary_id': diaryID, + }; + + var response = await http.post(Uri.parse(url), + headers: { + 'Authorization': 'Bearer $token', + }, + body: json.encode(credentials), + encoding: utf8); + + return response; + } + + FutureOr _changeMeal(ChangeMeal event, Emitter emit) { + if (state is DiaryEditLoadedState) { + var currentState = state as DiaryEditLoadedState; + int? id = event.value.id; + emit(currentState.copyWith( + currentMeal: event.value, + portions: event.meals?[event.value.id ?? 0]['portions'], + mealTimeController: + toTime(event.meals?[event.value.id ?? 0]['time'] ?? "1$id:00"), + selectedMood: event.meals?[event.value.id ?? 0]['mood'], + selectedSatiety: event.meals?[event.value.id ?? 0]['satiety'])); + currentState.currentMeal = event.value; + } + } + + FutureOr _toggleMood( + ToggleMoodExpandedEvent event, Emitter emit) { + if (state is DiaryEditLoadedState) { + var currentState = state as DiaryEditLoadedState; + + emit(currentState.copyWith(moodExpanded: !currentState.moodExpanded)); + currentState.moodExpanded = !currentState.moodExpanded; + } + } + + FutureOr _toggleWakeup( + ToggleWakeupExpandedEvent event, Emitter emit) { + if (state is DiaryEditLoadedState) { + var currentState = state as DiaryEditLoadedState; + + emit(currentState.copyWith(wakeupExpanded: !currentState.wakeupExpanded)); + currentState.wakeupExpanded = !currentState.wakeupExpanded; + } + } + + FutureOr _addPortion( + AddPortionEvent event, Emitter emit) async { + if (state is DiaryEditLoadedState) { + var currentState = state as DiaryEditLoadedState; + int? id = currentState.currentMeal?.id; + var updatedState = currentState.copyWith( + currentMeal: currentState.diaryEditModelObj?.mealList[id ?? 0], + portions: currentState.meals?[id ?? 0]['portions'], + mealTimeController: + toTime(currentState.meals?[id ?? 0]['time'] ?? "1$id:00"), + selectedMood: currentState.meals?[id ?? 0]['mood'], + selectedSatiety: currentState.meals?[id ?? 0]['satiety']); + emit(updatedState); // Передаем обновленное состояние в emit + + // Отправляем данные по HTTP маршруту и ждем ответ + var response = await sendPortionData( + currentState.token, + event.name, + event.portion, + event.quantity, + currentState.currentMeal!.id, + currentState.diaryId); + + if (response.statusCode == 200) { + emit(currentState); // Передаем обновленное состояние в emit + } + } + } + + Future sendPortionData( + String? token, + String name, + String portion, + int quantity, + int? mealID, + int diaryID, + ) async { + String url = + dotenv.env['SERVER_ROOT']! + '/$diaryID/meal/$mealID/portions/create'; + final credentials = { + 'name': name, + 'portion': portion, + 'quantity': quantity, + 'meal_id': mealID, + 'diary_id': diaryID, + }; + + var response = await http.post(Uri.parse(url), + headers: { + 'Authorization': 'Bearer $token', + }, + body: json.encode(credentials), + encoding: utf8); + + return response; + } + + FutureOr _inputProduct( + ProductInput event, Emitter emit) async { + if (state is DiaryEditLoadedState) { + var currentState = state as DiaryEditLoadedState; + String? token = currentState.token; + final credentials = { + 'name': event.input, + }; + final uri = Uri.parse(dotenv.env['SERVER_ROOT']! + + '/diary/product_nutritional_values/filter?' + + Uri(queryParameters: credentials).query); + final response = await http.get( + uri, + headers: { + 'Authorization': 'Bearer $token', + }, + ); + final productNutritionalValues = + jsonDecode(response.body)['productNutritionalValue']; + if (productNutritionalValues is Map) { + var updatedState = currentState.copyWith( + autocompleteList: productNutritionalValues.values + .map((value) => value['name']) + .toList()); + + emit(updatedState); + } + } + } + + FutureOr _removePortion( + RemovePortionEvent event, Emitter emit) {} + + Future> _uploadImages( + UploadImages event, Emitter emit) async { + String imageUrl = await uploadImageToServer(event.image.path); + print(imageUrl); + } + + FutureOr _addComment(AddComment event, Emitter emit) {} + Future uploadImageToServer(String imagePath) async { + var request = http.MultipartRequest( + 'POST', + Uri.parse( + 'http://your-api-url.com/upload'), // Замените на ваш маршрут API + ); + + request.files.add(await http.MultipartFile.fromPath('image', imagePath)); + + var response = await request.send(); + if (response.statusCode == 200) { + var responseData = await response.stream.bytesToString(); + return responseData; + } else { + throw Exception('Ошибка при загрузке изображения'); + } + } + + TimeOfDay toTime(String timeStr) { + List timeParts = timeStr.split(":"); + int hour = int.parse(timeParts[0]); + int minute = int.parse(timeParts[1]); + + TimeOfDay timeOfDay = TimeOfDay(hour: hour, minute: minute); + return timeOfDay; + } + + List fillMealList() { + return [ + SelectionPopupModel(id: 0, title: "Прием пищи 1", isSelected: true), + SelectionPopupModel(id: 1, title: "Прием пищи 2"), + SelectionPopupModel(id: 2, title: "Прием пищи 3"), + SelectionPopupModel(id: 3, title: "Прием пищи 4"), + SelectionPopupModel(id: 4, title: "Прием пищи 5"), + SelectionPopupModel(id: 5, title: "Прием пищи 6"), + ]; + } } diff --git a/lib/presentation/diary/diary_edit_screen/bloc/diary_edit_event.dart b/lib/presentation/diary/diary_edit_screen/bloc/diary_edit_event.dart index ea52a649..206e5957 100644 --- a/lib/presentation/diary/diary_edit_screen/bloc/diary_edit_event.dart +++ b/lib/presentation/diary/diary_edit_screen/bloc/diary_edit_event.dart @@ -14,3 +14,105 @@ class DiaryEditInitialEvent extends DiaryEditEvent { @override List get props => []; } + +class ChangeSatiety extends DiaryEditEvent { + ChangeSatiety( + this.selectedElement, + ); + final int selectedElement; + + @override + List get props => [selectedElement]; +} + +class ChangeMood extends DiaryEditEvent { + ChangeMood( + this.selectedElement, + ); + final int selectedElement; + + @override + List get props => [selectedElement]; +} + +class ChangeMealTime extends DiaryEditEvent { + ChangeMealTime(this.time); + final TimeOfDay time; + + @override + List get props => [time]; +} + +class AddComment extends DiaryEditEvent { + AddComment(this.diaryId, this.comment); + final String comment; + final int diaryId; + + @override + List get props => [comment, diaryId]; +} + +class WakeupEvent extends DiaryEditEvent { + WakeupEvent(this.selectedTime); + final TimeOfDay selectedTime; + + @override + List get props => [selectedTime]; +} + +class RetireEvent extends DiaryEditEvent { + RetireEvent(this.selectedTime); + final TimeOfDay selectedTime; + + @override + List get props => [selectedTime]; +} + +class ToggleMoodExpandedEvent extends DiaryEditEvent { + @override + List get props => []; +} + +class ToggleWakeupExpandedEvent extends DiaryEditEvent { + @override + List get props => []; +} + +class AddPortionEvent extends DiaryEditEvent { + AddPortionEvent(this.name, this.quantity, this.portion); + final String name; + final int quantity; + final String portion; + @override + List get props => [name, quantity, portion]; +} + +class RemovePortionEvent extends DiaryEditEvent { + RemovePortionEvent(this.portionId); + final int portionId; + @override + List get props => [portionId]; +} + +class UploadImages extends DiaryEditEvent { + UploadImages(this.image); + final XFile image; + @override + List get props => [image]; +} + +class ProductInput extends DiaryEditEvent { + ProductInput(this.input); + final String input; + @override + List get props => [input]; +} + +class ChangeMeal extends DiaryEditEvent { + ChangeMeal({required this.value, required this.meals}); + + SelectionPopupModel value; + List? meals; + @override + List get props => [value, meals]; +} diff --git a/lib/presentation/diary/diary_edit_screen/bloc/diary_edit_state.dart b/lib/presentation/diary/diary_edit_screen/bloc/diary_edit_state.dart index 7b9da4ab..c518e9af 100644 --- a/lib/presentation/diary/diary_edit_screen/bloc/diary_edit_state.dart +++ b/lib/presentation/diary/diary_edit_screen/bloc/diary_edit_state.dart @@ -18,20 +18,106 @@ class DiaryEditInitialState extends DiaryEditState { /// Represents the state of DiaryEdit in the application. class DiaryEditLoadedState extends DiaryEditState { - DiaryEditLoadedState({this.diaryEditModelObj}) + DiaryEditLoadedState( + {this.diaryEditModelObj, + this.diaryDate, + this.token, + this.meals, + this.portions, + this.autocompleteList, + this.wakeupController, + this.retireController, + this.commentController, + this.mealTimeController, + this.productNameController, + this.productQuantityController, + this.selectedMood, + this.currentMeal, + required this.diaryId, + required this.selectedSatiety, + required this.wakeupExpanded, + required this.moodExpanded}) : super(diaryEditModelObj: null); DiaryEditModel? diaryEditModelObj; + String? diaryDate; + String? token; + List? meals; + List? portions; + List? autocompleteList; + SelectionPopupModel? currentMeal; + TimeOfDay? wakeupController; + TimeOfDay? retireController; + TimeOfDay? mealTimeController; + TextEditingController? commentController; + TextEditingController? productNameController; + TextEditingController? productQuantityController; + int? selectedMood; + int selectedSatiety; + int diaryId; + bool wakeupExpanded; + bool moodExpanded; @override List get props => [ diaryEditModelObj, + diaryDate, + token, + meals, + autocompleteList, + wakeupController, + retireController, + mealTimeController, + commentController, + productNameController, + productQuantityController, + selectedMood, + selectedSatiety, + diaryId, + wakeupExpanded, + moodExpanded, + currentMeal ]; DiaryEditLoadedState copyWith({ DiaryEditModel? diaryEditModelObj, + SelectionPopupModel? currentMeal, + String? diaryDate, + String? token, + List? meals, + List? portions, + List? autocompleteList, + TimeOfDay? wakeupController, + TimeOfDay? retireController, + TimeOfDay? mealTimeController, + TextEditingController? commentController, + TextEditingController? productNameController, + TextEditingController? productQuantityController, + int? selectedMood, + int? selectedSatiety, + int? diaryId, + bool? wakeupExpanded, + bool? moodExpanded, }) { return DiaryEditLoadedState( - diaryEditModelObj: diaryEditModelObj ?? this.diaryEditModelObj, - ); + diaryEditModelObj: diaryEditModelObj ?? this.diaryEditModelObj, + diaryDate: diaryDate ?? this.diaryDate, + token: token ?? this.token, + meals: meals ?? this.meals, + autocompleteList: autocompleteList ?? this.autocompleteList, + portions: portions ?? this.portions, + wakeupController: wakeupController ?? this.wakeupController, + retireController: retireController ?? this.retireController, + mealTimeController: mealTimeController ?? this.mealTimeController, + commentController: commentController ?? this.commentController, + productNameController: + productNameController ?? this.productNameController, + productQuantityController: + productQuantityController ?? this.productQuantityController, + selectedMood: selectedMood ?? this.selectedMood, + selectedSatiety: selectedSatiety ?? this.selectedSatiety, + diaryId: diaryId ?? this.diaryId, + currentMeal: currentMeal ?? this.currentMeal, + wakeupExpanded: wakeupExpanded ?? this.wakeupExpanded, + moodExpanded: moodExpanded ?? this.moodExpanded); } } diff --git a/lib/presentation/diary/diary_edit_screen/diary_edit_screen.dart b/lib/presentation/diary/diary_edit_screen/diary_edit_screen.dart index 7d19300e..2bc07d97 100644 --- a/lib/presentation/diary/diary_edit_screen/diary_edit_screen.dart +++ b/lib/presentation/diary/diary_edit_screen/diary_edit_screen.dart @@ -1,11 +1,14 @@ import 'bloc/diary_edit_bloc.dart'; import 'models/diary_edit_model.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:eatvio_app/core/app_export.dart'; -import 'package:eatvio_app/widgets/custom_text_form_field.dart'; +import 'package:eatvio_app/widgets/custom_edit_text_form_field.dart'; import 'package:eatvio_app/widgets/custom_edit_drop_down.dart'; import 'package:eatvio_app/widgets/custom_outlined_button.dart'; -import 'package:eatvio_app/widgets/custom_icon_button.dart'; +import 'package:eatvio_app/widgets/custom_scale_element.dart'; // ignore: must_be_immutable class DiaryEditScreen extends StatelessWidget { @@ -14,34 +17,6 @@ class DiaryEditScreen extends StatelessWidget { key: key, ); - List dropdownItemList = [ - "Item One", - "Item Two", - "Item Three", - ]; - - List dropdownItemList1 = [ - "Item One", - "Item Two", - "Item Three", - ]; - - TextEditingController enterProductNameController = TextEditingController(); - - TextEditingController quantityController = TextEditingController(); - - List dropdownItemList2 = [ - "Item One", - "Item Two", - "Item Three", - ]; - - List dropdownItemList3 = [ - "Item One", - "Item Two", - "Item Three", - ]; - static Widget builder(BuildContext context) { return BlocProvider( create: (context) => @@ -76,9 +51,11 @@ class DiaryEditScreen extends StatelessWidget { Widget _buildDiaryEditScreen( BuildContext context, DiaryEditLoadedState state) { mediaQueryData = MediaQuery.of(context); - return SafeArea( + return FormBuilder( + child: SafeArea( child: Scaffold( - resizeToAvoidBottomInset: false, + backgroundColor: Color.fromARGB(255, 240, 242, 245), + resizeToAvoidBottomInset: true, body: SizedBox( width: double.maxFinite, child: Column( @@ -89,49 +66,17 @@ class DiaryEditScreen extends StatelessWidget { padding: EdgeInsets.only(bottom: 5.v), child: Column( children: [ - _buildHeaderRow(context), + _buildHeaderRow(context, state), SizedBox(height: 30.v), - Padding( - padding: EdgeInsets.symmetric(horizontal: 16.h), - child: CustomDropDown( - icon: Container( - margin: - EdgeInsets.fromLTRB(30.h, 24.v, 20.h, 22.v), - child: CustomImageView( - imagePath: ImageConstant.imgArrowLeft, - height: 10.v, - width: 18.h, - ), - ), - hintText: "Подъём \\ уход ко сну", - items: dropdownItemList, - onChanged: (value) {}, - ), - ), + _buildWakupRetire(context, state), SizedBox(height: 30.v), - _buildImageStack(context), + _buildMeal(context, state), SizedBox(height: 20.v), - Padding( - padding: EdgeInsets.symmetric(horizontal: 16.h), - child: CustomDropDown( - icon: Container( - margin: - EdgeInsets.fromLTRB(30.h, 30.v, 20.h, 30.v), - child: CustomImageView( - imagePath: ImageConstant.imgArrowLeft, - height: 10.v, - width: 18.h, - ), - ), - hintText: "Ваше настроение после приема пищи", - items: dropdownItemList3, - onChanged: (value) {}, - ), - ), + _buildMood(context, state), SizedBox(height: 30.v), - _buildHeaderColumn(context), + _buildNutritionChart(context), SizedBox(height: 30.v), - _buildHeaderStack(context), + _buildComment(context, state), ], ), ), @@ -142,32 +87,38 @@ class DiaryEditScreen extends StatelessWidget { ), bottomNavigationBar: _buildEndDay(context), ), - ); + )); } /// Section Widget - Widget _buildHeaderRow(BuildContext context) { + Widget _buildHeaderRow(BuildContext context, DiaryEditLoadedState state) { + final day = state.diaryId; return Container( - margin: EdgeInsets.symmetric(horizontal: 16.h), + margin: EdgeInsets.symmetric(horizontal: 16.h, vertical: 16.h), padding: EdgeInsets.symmetric( horizontal: 24.h, vertical: 12.v, ), - decoration: AppDecoration.outlinePrimary.copyWith( - borderRadius: BorderRadiusStyle.roundedBorder12, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ CustomImageView( - imagePath: ImageConstant.imgArrowLeft, - height: 2.v, - width: 20.h, - margin: EdgeInsets.only( - top: 19.v, - bottom: 17.v, - ), - ), + imagePath: ImageConstant.imgArrowLeft, + onTap: () { + NavigatorService.goBack(); + }), Padding( padding: EdgeInsets.only(top: 1.v), child: Column( @@ -175,13 +126,13 @@ class DiaryEditScreen extends StatelessWidget { Align( alignment: Alignment.centerRight, child: Text( - "10 день", + "$day день", style: CustomTextStyles.titleMediumBlack, ), ), SizedBox(height: 2.v), Text( - "03-08-2023", + state.diaryDate.toString(), style: CustomTextStyles.bodyMediumBluegray800, ), ], @@ -192,35 +143,6 @@ class DiaryEditScreen extends StatelessWidget { ); } - /// Section Widget - Widget _buildHeaderButton1(BuildContext context) { - return CustomOutlinedButton( - width: 73.h, - text: "14:52", - ); - } - - /// Section Widget - Widget _buildEnterProductName(BuildContext context) { - return Padding( - padding: EdgeInsets.symmetric(horizontal: 10.h), - child: CustomTextFormField( - controller: enterProductNameController, - hintText: "Введите названия продукта", - ), - ); - } - - /// Section Widget - Widget _buildQuantity(BuildContext context) { - return CustomTextFormField( - width: 146.h, - controller: quantityController, - hintText: "Кол-во", - textInputAction: TextInputAction.done, - ); - } - /// Section Widget Widget _buildAddProduct(BuildContext context) { return CustomOutlinedButton( @@ -232,270 +154,310 @@ class DiaryEditScreen extends StatelessWidget { /// Section Widget Widget _buildAddPhoto(BuildContext context) { - return CustomOutlinedButton( - width: 195.h, - text: "Добавить фото", - rightIcon: Container( - margin: EdgeInsets.only(left: 14.h), - child: CustomImageView( - imagePath: ImageConstant.imgVector, - height: 30.v, - width: 34.h, + return GestureDetector( + onTap: () async { + final picker = ImagePicker(); + List? images = await picker.pickMultiImage(); + if (images.isNotEmpty) { + for (var image in images) { + context.read().add(UploadImages(image)); + } + } + }, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 10.h), + padding: EdgeInsets.symmetric( + horizontal: 40.h, + vertical: 80.v, + ), + decoration: AppDecoration.fillGray.copyWith( + borderRadius: BorderRadiusStyle.roundedBorder5, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only( + left: 7.h, + top: 5.v, + ), + child: Text( + "Добавить фото", + selectionColor: appTheme.blueA400, + ), + ), + Spacer(), + Icon( + Icons.photo_library, + color: appTheme.blueGray300, + ) + ], ), ), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - buttonTextStyle: theme.textTheme.bodyMedium!, ); } - /// Section Widget - Widget _buildImageStack(BuildContext context) { - return SizedBox( - height: 790.v, - width: 374.h, - child: Stack( - alignment: Alignment.bottomCenter, - children: [ - Align( - alignment: Alignment.center, - child: Container( - margin: EdgeInsets.symmetric(horizontal: 15.h), - padding: EdgeInsets.symmetric( - horizontal: 10.h, - vertical: 13.v, + _buildMeal(BuildContext context, DiaryEditLoadedState state) { + final digitsOnly = FilteringTextInputFormatter.digitsOnly; + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16.h), + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 24.h, + vertical: 12.v, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), ), - decoration: AppDecoration.outlinePrimary.copyWith( - borderRadius: BorderRadiusStyle.roundedBorder12, + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 10.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Время приема пищи", + style: CustomTextStyles.bodyMediumBlack900, + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 10.h), + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width - 260, + ), + child: GestureDetector( + onTap: () => _selectMealTime(context, state), + child: AbsorbPointer( + child: CustomTextFormField( + hintText: state.mealTimeController + ?.format(context) + .toString(), + autofocus: false, + ), + ), + ), + ), + ), + ], + ), ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 10.h), - child: Row( + SizedBox(height: 20.v), + Padding( + padding: EdgeInsets.symmetric(horizontal: 10.h), + child: BlocSelector( + selector: (state) => state.diaryEditModelObj, + builder: (context, diaryEditModelObj) { + return CustomDropDown( + hintText: "Прием пищи", + items: diaryEditModelObj?.mealList, + contentPadding: EdgeInsets.symmetric( + horizontal: 10.h, + vertical: 9.v, + ), + defaultValue: diaryEditModelObj?.mealList[0], + onChanged: (value) { + context.read().add( + ChangeMeal(meals: state.meals, value: value)); + }, + ); + })), + SizedBox(height: 20.v), + Padding( + padding: EdgeInsets.symmetric(horizontal: 10.h), + child: CustomTextFormField( + hintText: "Введите названия продукта", + controller: state.productNameController, + onChanged: (value) { + if (value.length > 3) { + context.read().add(ProductInput(value)); + } + }, + ), + ), + SizedBox(height: 20.v), + Padding( + padding: EdgeInsets.symmetric(horizontal: 10.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomTextFormField( + width: 146.h, + hintText: "Кол-во", + textInputAction: TextInputAction.done, + controller: state.productQuantityController, + keyboardType: TextInputType.number, + inputFormatters: [digitsOnly], + ), + Padding( + padding: EdgeInsets.only(right: 30.h), + child: Text( + "порций", + style: CustomTextStyles.bodyMediumBlack900, + )), + ], + ), + ), + SizedBox(height: 30.v), + _buildAddProduct(context), + SizedBox(height: 30.v), + ...state.portions! + .map((portion) => _buildProduct(context, portion)) + .toList(), + SizedBox(height: 30.v), + _buildAddPhoto(context), + SizedBox(height: 30.v), + Text( + "Ваша сытость после приёма пищи", + style: CustomTextStyles.bodyMediumBlack900, + ), + SizedBox(height: 15.v), + BlocBuilder( + builder: (context, state) { + if (state is DiaryEditLoadedState) { + return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Padding( - padding: EdgeInsets.only( - top: 11.v, - bottom: 7.v, - ), - child: Text( - "Время приема пищи", - style: CustomTextStyles.bodyMediumBlack900, - ), + Text("😭"), + ScaleElement( + color: Colors.red, + isSelected: state.selectedSatiety == 0, + onTap: () { + context.read().add(ChangeSatiety(0)); + }, ), - _buildHeaderButton1(context), + ScaleElement( + color: Colors.orange, + isSelected: state.selectedSatiety == 1, + onTap: () { + context.read().add(ChangeSatiety(1)); + }, + ), + ScaleElement( + color: Colors.yellow, + isSelected: state.selectedSatiety == 2, + onTap: () { + context.read().add(ChangeSatiety(2)); + }, + ), + ScaleElement( + color: Colors.lime, + isSelected: state.selectedSatiety == 3, + onTap: () { + context.read().add(ChangeSatiety(3)); + }, + ), + ScaleElement( + color: Colors.lightGreen, + isSelected: state.selectedSatiety == 4, + onTap: () { + context.read().add(ChangeSatiety(4)); + }, + ), + ScaleElement( + color: Colors.green, + isSelected: state.selectedSatiety == 5, + onTap: () { + context.read().add(ChangeSatiety(5)); + }, + ), + Text("😊"), + // Остальные элементы шкалы ], - ), - ), - SizedBox(height: 20.v), - Padding( - padding: EdgeInsets.symmetric(horizontal: 10.h), - child: CustomDropDown( - hintText: "Прием пищи", - items: dropdownItemList1, - contentPadding: EdgeInsets.symmetric( - horizontal: 15.h, - vertical: 9.v, - ), - onChanged: (value) {}, - ), - ), - SizedBox(height: 20.v), - _buildEnterProductName(context), - SizedBox(height: 20.v), - Padding( - padding: EdgeInsets.symmetric(horizontal: 10.h), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _buildQuantity(context), - Padding( - padding: EdgeInsets.only(left: 10.h), - child: CustomDropDown( - width: 146.h, - hintText: "Порция", - items: dropdownItemList2, - contentPadding: EdgeInsets.symmetric( - horizontal: 15.h, - vertical: 9.v, - ), - onChanged: (value) {}, - ), - ), - ], - ), - ), - SizedBox(height: 30.v), - Container( - margin: EdgeInsets.symmetric(horizontal: 10.h), - padding: EdgeInsets.symmetric( - horizontal: 8.h, - vertical: 6.v, - ), - decoration: AppDecoration.fillGray.copyWith( - borderRadius: BorderRadiusStyle.roundedBorder5, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.only( - left: 7.h, - top: 5.v, - ), - child: Text( - "Сырный суп", - style: CustomTextStyles.titleMedium16, - ), - ), - Padding( - padding: EdgeInsets.only( - left: 20.h, - top: 3.v, - bottom: 3.v, - ), - child: Text( - "400мл", - style: CustomTextStyles.titleMedium16, - ), - ), - Spacer(), - CustomImageView( - imagePath: ImageConstant.imgSearch, - height: 24.adaptSize, - width: 24.adaptSize, - ), - CustomImageView( - imagePath: ImageConstant.imgClose, - height: 28.adaptSize, - width: 28.adaptSize, - margin: EdgeInsets.only(left: 10.h), - ), - ], - ), - ), - SizedBox(height: 30.v), - _buildAddProduct(context), - SizedBox(height: 249.v), - _buildAddPhoto(context), - SizedBox(height: 29.v), - Text( - "Ваша сытость после приёма пищи", - style: CustomTextStyles.bodyMediumBlack900, - ), - SizedBox(height: 15.v), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CustomImageView( - imagePath: ImageConstant.imgEdit, - height: 30.adaptSize, - width: 30.adaptSize, - ), - CustomImageView( - height: 13.v, - width: 243.h, - margin: EdgeInsets.only( - left: 10.h, - top: 9.v, - bottom: 8.v, - ), - ), - CustomImageView( - height: 30.adaptSize, - width: 30.adaptSize, - margin: EdgeInsets.only(left: 10.h), - ), - ], - ), - SizedBox(height: 7.v), - ], - ), + ); + } else { + return Container(); // Возвращаем пустой контейнер, если состояние не является DiaryEditLoadedState + } + }, + ) + ], + ), + )); + } + + _buildProduct(BuildContext context, Map portion) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 10.h), + padding: EdgeInsets.symmetric( + horizontal: 8.h, + vertical: 6.v, + ), + decoration: AppDecoration.fillGray.copyWith( + borderRadius: BorderRadiusStyle.roundedBorder5, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only( + left: 7.h, + top: 5.v, + ), + child: Text( + portion['product_name'], + style: CustomTextStyles.titleMedium16, ), ), - Align( - alignment: Alignment.bottomCenter, - child: Padding( - padding: EdgeInsets.only(bottom: 342.v), - child: SizedBox( - width: 374.h, - child: Divider(), - ), - ), - ), - Align( - alignment: Alignment.bottomCenter, - child: Container( - height: 203.v, - width: 303.h, - margin: EdgeInsets.only(bottom: 178.v), - child: Stack( - alignment: Alignment.topRight, - children: [ - CustomImageView( - height: 203.v, - width: 303.h, - radius: BorderRadius.circular( - 5.h, - ), - alignment: Alignment.center, - ), - Align( - alignment: Alignment.topRight, - child: Padding( - padding: EdgeInsets.only( - left: 265.h, - top: 10.v, - right: 10.h, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - CustomIconButton( - height: 18.adaptSize, - width: 18.adaptSize, - padding: EdgeInsets.all(4.h), - alignment: Alignment.centerRight, - child: CustomImageView( - imagePath: ImageConstant.imgCloseBlueGray900, - ), - ), - SizedBox(height: 60.v), - CustomIconButton( - height: 28.adaptSize, - width: 28.adaptSize, - padding: EdgeInsets.all(9.h), - decoration: IconButtonStyleHelper.fillBlueA, - child: CustomImageView( - imagePath: ImageConstant.imageNotFound, - ), - ), - ], - ), - ), - ), - ], - ), + Padding( + padding: EdgeInsets.only( + left: 20.h, + top: 3.v, + bottom: 3.v, + ), + child: Text( + portion['quantity'] + portion['portion'], ), ), + Spacer(), + GestureDetector( + onTap: () { + context + .read() + .add(RemovePortionEvent(portion['id'])); + }, + child: Padding( + padding: const EdgeInsets.only(right: 10), + child: Icon( + Icons.close, + color: appTheme.blueGray300, + ), + )), ], ), ); } - /// Section Widget - Widget _buildHeaderColumn(BuildContext context) { + Widget _buildNutritionChart(BuildContext context) { return Container( margin: EdgeInsets.symmetric(horizontal: 16.h), padding: EdgeInsets.symmetric( horizontal: 10.h, vertical: 16.v, ), - decoration: AppDecoration.outlinePrimary.copyWith( - borderRadius: BorderRadiusStyle.roundedBorder12, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], ), child: Column( mainAxisSize: MainAxisSize.min, @@ -696,7 +658,7 @@ class DiaryEditScreen extends StatelessWidget { ), Padding( padding: EdgeInsets.only( - left: 16.h, + left: 14.h, bottom: 11.v, ), child: Text( @@ -718,87 +680,143 @@ class DiaryEditScreen extends StatelessWidget { } /// Section Widget - Widget _buildHeaderColumn1(BuildContext context) { - return Align( - alignment: Alignment.center, - child: Container( - margin: EdgeInsets.symmetric(horizontal: 15.h), - padding: EdgeInsets.symmetric( - horizontal: 50.h, - vertical: 28.v, - ), - decoration: AppDecoration.outlinePrimary.copyWith( - borderRadius: BorderRadiusStyle.roundedBorder12, - ), - child: SizedBox( - width: 240.h, - child: Text( - "Дополнительная информация для консультанта", - maxLines: 2, - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - style: CustomTextStyles.bodyMediumBlack900, + Widget _buildComment(BuildContext context, DiaryEditLoadedState state) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16.h), + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 24.h, + vertical: 12.v, ), - ), - ), - ); + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Дополнительная информация для консультанта", + style: theme.textTheme.titleMedium, + textAlign: TextAlign.center, + ), + Align( + alignment: Alignment.bottomCenter, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 20.v), + Container( + width: 320.h, + child: CustomTextFormField( + autofocus: false, + hintText: "Введите информацию для консультанта", + maxLines: 7, + borderDecoration: InputBorder.none, + controller: state.commentController, + ), + ), + ], + ), + ), + ], + ), + )); } - /// Section Widget - Widget _buildHeaderStack(BuildContext context) { - return SizedBox( - height: 235.v, - width: 374.h, - child: Stack( - alignment: Alignment.bottomCenter, - children: [ - _buildHeaderColumn1(context), - Align( - alignment: Alignment.bottomCenter, - child: Padding( - padding: EdgeInsets.only(bottom: 103.v), - child: SizedBox( - width: 374.h, - child: Divider(), - ), - ), + Widget _buildWakupRetire(BuildContext context, DiaryEditLoadedState state) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16.h), + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 24.h, + vertical: 12.v, ), - Align( - alignment: Alignment.bottomCenter, - child: Container( - margin: EdgeInsets.only( - left: 35.h, - right: 35.h, - bottom: 20.v, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), ), - padding: EdgeInsets.symmetric( - horizontal: 14.h, - vertical: 9.v, - ), - decoration: AppDecoration.outlineBlueGray.copyWith( - borderRadius: BorderRadiusStyle.roundedBorder5, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 63.v), - Container( - width: 245.h, - margin: EdgeInsets.only(right: 27.h), - child: Text( - "Введите дополнитльеную информацию для консультанта", - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: theme.textTheme.bodyLarge, + ], + ), + child: Column( + children: [ + GestureDetector( + onTap: () { + BlocProvider.of(context) + .add(ToggleWakeupExpandedEvent()); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + r"Подъем \ отход ко сну", + style: CustomTextStyles.bodyMediumBlack900, ), - ), - ], + Icon(state.wakeupExpanded + ? Icons.arrow_drop_up + : Icons.arrow_drop_down), + ], + ), ), - ), - ), - ], - ), + if (state.wakeupExpanded) + Padding( + padding: const EdgeInsets.only(left: 16.0), + child: Column( + children: [ + SizedBox(height: 20.v), + GestureDetector( + onTap: () => _selectWakeupTime(context, state), + child: AbsorbPointer( + child: CustomTextFormField( + hintText: state.wakeupController + ?.format(context) + .toString(), + prefix: Icon(Icons.sunny), + autofocus: false, + /*initialValue: state.wakeupController + ?.format(context) + .toString()*/ + ), + ), + ), + SizedBox(height: 20.v), + GestureDetector( + onTap: () => _selectRetireTime(context, state), + child: AbsorbPointer( + child: CustomTextFormField( + hintText: state.retireController + ?.format(context) + .toString(), + prefix: Icon(Icons.bed), + autofocus: false, + /*initialValue: state.retireController + ?.format(context) + .toString(),*/ + ), + ), + ), + SizedBox(height: 20.v), + ], + ), + ), + ], + )), ); } @@ -812,6 +830,140 @@ class DiaryEditScreen extends StatelessWidget { bottom: 41.v, ), buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + decoration: BoxDecoration( + color: Colors.transparent, + ), // Установите прозрачный фон ); } + + Widget _buildMood(BuildContext context, DiaryEditLoadedState state) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16.h), + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 24.h, + vertical: 12.v, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column(children: [ + GestureDetector( + onTap: () { + BlocProvider.of(context) + .add(ToggleMoodExpandedEvent()); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Ваше настроение после приема пищи", + style: CustomTextStyles.bodyMediumBlack900, + ), + Icon(state.moodExpanded + ? Icons.arrow_drop_up + : Icons.arrow_drop_down), + ], + ), + ), + if (state.moodExpanded) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("😭"), + ScaleElement( + color: Colors.red, + isSelected: state.selectedMood == 0, + onTap: () { + context.read().add(ChangeMood(0)); + }, + ), + ScaleElement( + color: Colors.orange, + isSelected: state.selectedMood == 1, + onTap: () { + context.read().add(ChangeMood(1)); + }, + ), + ScaleElement( + color: Colors.yellow, + isSelected: state.selectedMood == 2, + onTap: () { + context.read().add(ChangeMood(2)); + }, + ), + ScaleElement( + color: Colors.lime, + isSelected: state.selectedMood == 3, + onTap: () { + context.read().add(ChangeMood(3)); + }, + ), + ScaleElement( + color: Colors.lightGreen, + isSelected: state.selectedMood == 4, + onTap: () { + context.read().add(ChangeMood(4)); + }, + ), + ScaleElement( + color: Colors.green, + isSelected: state.selectedMood == 5, + onTap: () { + context.read().add(ChangeMood(5)); + }, + ), + Text("😊"), + // Остальные элементы шкалы + ], + ), + ]))); + } + + Future _selectWakeupTime( + BuildContext context, DiaryEditLoadedState state) async { + final TimeOfDay? pickedTime = await showTimePicker( + context: context, + initialTime: state.wakeupController ?? TimeOfDay.now(), + ); + if (pickedTime != null && pickedTime != state.wakeupController) { + state.wakeupController = pickedTime; + context.read().add(WakeupEvent( + pickedTime, + )); + } + } + + Future _selectRetireTime( + BuildContext context, DiaryEditLoadedState state) async { + final TimeOfDay? pickedTime = await showTimePicker( + context: context, + initialTime: state.retireController ?? TimeOfDay.now(), + ); + if (pickedTime != null && pickedTime != state.retireController) { + state.retireController = pickedTime; + context.read().add(RetireEvent(pickedTime)); + } + } + + Future _selectMealTime( + BuildContext context, DiaryEditLoadedState state) async { + final TimeOfDay? pickedTime = await showTimePicker( + context: context, + initialTime: state.mealTimeController ?? TimeOfDay.now(), + ); + if (pickedTime != null && pickedTime != state.mealTimeController) { + state.mealTimeController = pickedTime; + context.read().add(ChangeMealTime(pickedTime)); + } + } } diff --git a/lib/presentation/diary/diary_edit_screen/models/diary_edit_model.dart b/lib/presentation/diary/diary_edit_screen/models/diary_edit_model.dart index b2aaf181..a175b889 100644 --- a/lib/presentation/diary/diary_edit_screen/models/diary_edit_model.dart +++ b/lib/presentation/diary/diary_edit_screen/models/diary_edit_model.dart @@ -1,16 +1,22 @@ // ignore_for_file: must_be_immutable import 'package:equatable/equatable.dart'; +import 'package:eatvio_app/data/models/selectionPopupModel/selection_popup_model.dart'; /// This class defines the variables used in the [k0_page], /// and is typically used to hold data that is passed between different parts of the application. class DiaryEditModel extends Equatable { - DiaryEditModel(); + DiaryEditModel({this.mealList = const []}); - DiaryEditModel copyWith() { - return DiaryEditModel(); + List mealList; + DiaryEditModel copyWith({ + List? mealList, + }) { + return DiaryEditModel( + mealList: mealList ?? this.mealList, + ); } @override - List get props => []; + List get props => [mealList]; } diff --git a/lib/presentation/profile/my_profile_screen/my_profile_screen.dart b/lib/presentation/profile/my_profile_screen/my_profile_screen.dart index 3b65469e..3fe87cf2 100644 --- a/lib/presentation/profile/my_profile_screen/my_profile_screen.dart +++ b/lib/presentation/profile/my_profile_screen/my_profile_screen.dart @@ -1,5 +1,3 @@ -import 'dart:ffi'; - import 'bloc/my_profile_bloc.dart'; import 'models/my_profile_model.dart'; import 'package:eatvio_app/core/app_export.dart'; diff --git a/lib/theme/custom_button_style.dart b/lib/theme/custom_button_style.dart index 95a5581e..bdd84d93 100644 --- a/lib/theme/custom_button_style.dart +++ b/lib/theme/custom_button_style.dart @@ -8,7 +8,7 @@ class CustomButtonStyles { backgroundColor: appTheme.blueA400, side: BorderSide( color: theme.colorScheme.primary, - width: 1, + width: 0, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15.h), diff --git a/lib/widgets/custom_bottom_bar.dart b/lib/widgets/custom_bottom_bar.dart index 67ae4e3f..c32781a2 100644 --- a/lib/widgets/custom_bottom_bar.dart +++ b/lib/widgets/custom_bottom_bar.dart @@ -1,6 +1,7 @@ import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; +// ignore: must_be_immutable class CustomBottomBar extends StatefulWidget { CustomBottomBar({this.onChanged}); diff --git a/lib/widgets/custom_edit_drop_down.dart b/lib/widgets/custom_edit_drop_down.dart index 12b98cf3..59c58593 100644 --- a/lib/widgets/custom_edit_drop_down.dart +++ b/lib/widgets/custom_edit_drop_down.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; class CustomDropDown extends StatelessWidget { @@ -22,6 +23,7 @@ class CustomDropDown extends StatelessWidget { this.filled = true, this.validator, this.onChanged, + this.defaultValue, }) : super( key: key, ); @@ -38,7 +40,7 @@ class CustomDropDown extends StatelessWidget { final TextStyle? textStyle; - final List? items; + final List? items; final String? hintText; @@ -60,9 +62,11 @@ class CustomDropDown extends StatelessWidget { final bool? filled; - final FormFieldValidator? validator; + final SelectionPopupModel? defaultValue; - final Function(String)? onChanged; + final FormFieldValidator? validator; + + final Function(SelectionPopupModel)? onChanged; @override Widget build(BuildContext context) { @@ -76,29 +80,32 @@ class CustomDropDown extends StatelessWidget { Widget get dropDownWidget => SizedBox( width: width ?? double.maxFinite, - child: DropdownButtonFormField( + child: DropdownButtonFormField( focusNode: focusNode ?? FocusNode(), icon: icon, autofocus: autofocus!, - items: items?.map>((String value) { - return DropdownMenuItem( - value: value, + style: textStyle ?? theme.textTheme.titleMedium, + items: items?.map((SelectionPopupModel item) { + return DropdownMenuItem( + value: item, child: Text( - value, + item.title, overflow: TextOverflow.ellipsis, + style: hintStyle ?? theme.textTheme.titleMedium, ), ); }).toList(), decoration: decoration, validator: validator, + value: defaultValue, onChanged: (value) { - onChanged!(value.toString()); + onChanged!(value!); }, ), ); InputDecoration get decoration => InputDecoration( hintText: hintText ?? "", - hintStyle: hintStyle, + hintStyle: hintStyle ?? theme.textTheme.titleMedium, prefixIcon: prefix, prefixIconConstraints: prefixConstraints, suffixIcon: suffix, @@ -106,27 +113,33 @@ class CustomDropDown extends StatelessWidget { isDense: true, contentPadding: contentPadding ?? EdgeInsets.only( - left: 17, - top: 17, - bottom: 17, + left: 6.h, + top: 6.v, + bottom: 6.v, ), - fillColor: fillColor, + fillColor: fillColor ?? theme.colorScheme.onPrimary, filled: filled, border: borderDecoration ?? OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(8.h), + borderSide: BorderSide( + color: Colors.grey, + width: 1, + ), ), enabledBorder: borderDecoration ?? OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(8.h), + borderSide: BorderSide( + color: Colors.grey, + width: 1, + ), ), focusedBorder: borderDecoration ?? OutlineInputBorder( - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(8.h), borderSide: BorderSide( - color: Colors.blue, + color: Colors.grey, width: 1, ), ), diff --git a/lib/widgets/custom_edit_text_form_field.dart b/lib/widgets/custom_edit_text_form_field.dart new file mode 100644 index 00000000..5b27a43f --- /dev/null +++ b/lib/widgets/custom_edit_text_form_field.dart @@ -0,0 +1,152 @@ +import 'package:eatvio_app/core/app_export.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class CustomTextFormField extends StatelessWidget { + CustomTextFormField({ + Key? key, + this.alignment, + this.width, + this.controller, + this.focusNode, + this.autofocus = true, + this.textStyle, + this.obscureText = false, + this.textInputAction = TextInputAction.next, + this.textInputType = TextInputType.text, + this.maxLines, + this.hintText, + this.hintStyle, + this.prefix, + this.prefixConstraints, + this.suffix, + this.suffixConstraints, + this.initialValue, + this.contentPadding, + this.inputFormatters, + this.borderDecoration, + this.fillColor, + this.filled = true, + this.validator, + this.keyboardType, + this.onChanged, + this.onSaved, + }) : super( + key: key, + ); + + final Alignment? alignment; + + final double? width; + + final TextEditingController? controller; + + final FocusNode? focusNode; + + final bool? autofocus; + + final TextStyle? textStyle; + + final bool? obscureText; + + final List? inputFormatters; + + final TextInputAction? textInputAction; + + final TextInputType? textInputType; + + final int? maxLines; + + final String? hintText; + + final TextStyle? hintStyle; + + final Widget? prefix; + + final BoxConstraints? prefixConstraints; + + final Widget? suffix; + + final BoxConstraints? suffixConstraints; + + final EdgeInsets? contentPadding; + + final InputBorder? borderDecoration; + + final Color? fillColor; + + final bool? filled; + + final TextInputType? keyboardType; + + final String? initialValue; + + final FormFieldValidator? validator; + + final void Function(String?)? onSaved; + + final ValueChanged? onChanged; + + @override + Widget build(BuildContext context) { + return alignment != null + ? Align( + alignment: alignment ?? Alignment.center, + child: textFormFieldWidget, + ) + : textFormFieldWidget; + } + + Widget get textFormFieldWidget => SizedBox( + width: width ?? double.maxFinite, + child: TextFormField( + controller: controller, + focusNode: focusNode ?? FocusNode(), + autofocus: autofocus!, + style: textStyle ?? theme.textTheme.titleMedium, + obscureText: obscureText!, + textInputAction: textInputAction, + maxLines: maxLines ?? 1, + decoration: decoration, + validator: validator, + keyboardType: keyboardType, + onSaved: onSaved, + onChanged: onChanged, + inputFormatters: inputFormatters, + initialValue: initialValue, + ), + ); + InputDecoration get decoration => InputDecoration( + hintText: hintText ?? "", + hintStyle: hintStyle ?? theme.textTheme.bodyLarge, + prefixIcon: prefix, + prefixIconConstraints: prefixConstraints, + suffixIcon: suffix, + suffixIconConstraints: suffixConstraints, + isDense: true, + contentPadding: contentPadding ?? EdgeInsets.all(6.h), + fillColor: fillColor ?? theme.colorScheme.onPrimary, + filled: filled, + border: borderDecoration ?? + OutlineInputBorder( + borderRadius: BorderRadius.circular(8.h), + borderSide: BorderSide(color: Colors.grey, width: 1), + ), + enabledBorder: borderDecoration ?? + OutlineInputBorder( + borderRadius: BorderRadius.circular(8.h), + borderSide: BorderSide( + color: Colors.grey, + width: 1, + ), + ), + focusedBorder: borderDecoration ?? + OutlineInputBorder( + borderRadius: BorderRadius.circular(8.h), + borderSide: BorderSide( + color: Colors.grey, + width: 1, + ), + ), + ); +} diff --git a/lib/widgets/custom_image_view.dart b/lib/widgets/custom_image_view.dart index 5bed7a28..03bd0cbf 100644 --- a/lib/widgets/custom_image_view.dart +++ b/lib/widgets/custom_image_view.dart @@ -96,7 +96,6 @@ class CustomImageView extends StatelessWidget { height: height, width: width, fit: fit ?? BoxFit.contain, - color: color, ), ); case ImageType.file: diff --git a/lib/widgets/custom_scale_element.dart b/lib/widgets/custom_scale_element.dart new file mode 100644 index 00000000..531c95ef --- /dev/null +++ b/lib/widgets/custom_scale_element.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +class ScaleElement extends StatelessWidget { + final Color color; + final bool isSelected; + final VoidCallback onTap; + + const ScaleElement({ + required this.color, + required this.isSelected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + width: isSelected ? 40 : 20, + height: isSelected ? 40 : 20, + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 40b6b2f2..97271c60 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,9 @@ dependencies: flutter_spinkit: ^5.0.0 flutter_dotenv: ^5.1.0 carousel_slider: ^4.2.1 + flutter_form_builder: ^9.2.1 + image_picker: ^1.0.7 + flutter_typeahead: ^5.2.0 dev_dependencies: dart_code_metrics: ^5.7.6