34 Commits

Author SHA1 Message Date
77bc29c3d5 feat(diary) Added network methods and routes 2024-02-21 20:19:15 +03:00
05e04c444f fix(diary) Fix 2024-02-21 15:29:37 +03:00
75f7d52d39 feat(diary) Default Meal time values and Portions dynamic display for each meal 2024-02-21 15:29:10 +03:00
becf515acc feat(diary) Dynamic current meal display 2024-02-21 14:34:04 +03:00
65c465ab9d refactor(diary) Prepared front-end methods for API intergration 2024-02-21 12:18:25 +03:00
6d04d1a688 fix(diary) Fix meal list 2024-02-20 19:53:07 +03:00
3e4b683c33 feat(diary) Dynamic Meal list and Meal List object 2024-02-20 15:57:15 +03:00
f52c929b20 feat(diary) New time convert function, comment display and more 2024-02-20 15:27:07 +03:00
c46f612e1d refactor(diary) Podfile for iOS Cocoapods 2024-02-20 10:27:42 +03:00
bd61687a96 fix(diary) fix 2024-02-20 10:23:10 +03:00
1903896cf6 fix(diary) fix 2024-02-20 10:22:15 +03:00
d2371ef8ce fix(diary) No comment on this.. 2024-02-20 10:18:23 +03:00
c7e19718a3 feat(diary) Added a new event for meal time change 2024-02-20 10:14:00 +03:00
ba6c0f06f6 Meal time chhange front 2024-02-19 16:47:01 +03:00
7d0de42488 feat(diary) API methods added 2024-02-19 14:27:39 +03:00
0091e00060 feat(diary) dynnapic expandlists and mood/satiety scale 2024-02-19 09:28:25 +03:00
faf0439349 feat(diary) Image upload package and logic 2024-02-16 15:15:26 +03:00
139f57e1b5 feat(diary) More events for api methods. Stump for photo upload 2024-02-16 13:27:21 +03:00
a6ab577d20 feat(diary) Made an event expanded list display for mood 2024-02-15 16:02:13 +03:00
8e49d056c5 feat(diary) Made an event for satieety and mood change 2024-02-15 15:11:38 +03:00
397b579e56 feat(diary) Addeed mood and satiety picker scale to the screen 2024-02-15 13:08:50 +03:00
57a7f9bd91 feat(diary) Added controllers and improved form build logic 2024-02-14 14:56:16 +03:00
2fdf3979da feat(diary) Finished static layouts 2024-02-14 13:44:50 +03:00
2c2508840d feat(diary) New layoutsa and styles, build method edits 2024-02-14 13:06:01 +03:00
f25bbfecbf feat(diary) Comment layouts and controllers 2024-02-14 10:59:15 +03:00
5118697a02 feat(diary) Mood list layouts 2024-02-14 10:02:48 +03:00
7da4cb8014 refactor(diary) Added more layouts and constrollers 2024-02-13 18:24:35 +03:00
82eca66ae4 feat(diary) Improved layouts and TimePicker 2024-02-12 15:02:43 +03:00
7b989076cc fix(diary) Fix 2024-02-09 19:54:59 +03:00
d3c00cc99f Removed excess controllers 2024-02-09 19:44:58 +03:00
e8ff33b4d2 feat(diary) Added logic and BLOC manager for diary. Added a stump for diary layouts 2024-02-09 19:40:51 +03:00
e036f4c318 revert 53d6ea56a0
revert feat(diary) Initial layouts for Diary edit
2024-02-09 16:21:18 +00:00
53d6ea56a0 feat(diary) Initial layouts for Diary edit 2024-02-09 11:23:05 +03:00
4ec2834a42 Merge pull request 'profile' (#3) from profile into main
Reviewed-on: maxxkim/eatvio_app#3
2024-02-07 10:42:23 +00:00
19 changed files with 2197 additions and 18 deletions

View File

@@ -28,7 +28,7 @@
### Как запускать?
Для запуска на эмуляторе в программе VS Code скомпилируйте и запустите файл main.dart (предварительно необходимо установить эмулятор в Android Studio)
Для запуска на эмуляторе в программе VS Code скомпилируйте и запустите файл main.dart (предварительно необходимо установить эмулятор в Android Studio или подключите устройство в режиме разработчика через USB)
Для тестирования на устройствах Android в корневой папке проекта выполните команду

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

44
ios/Podfile Normal file
View File

@@ -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

View File

@@ -41,5 +41,9 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View File

@@ -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<AuthBloc, AuthState>(builder: (context, state) {
return NextStepButton(
text: "authentificate".tr.toUpperCase(),

View File

@@ -0,0 +1,493 @@
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:flutter_dotenv/flutter_dotenv.dart';
part 'diary_edit_event.dart';
part 'diary_edit_state.dart';
/// A bloc that manages the state of a DiaryEdit according to the event that is dispatched to it.
class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
DiaryEditBloc(DiaryEditState initialState) : super(initialState) {
on<DiaryEditInitialEvent>(_onInitialize);
on<ChangeSatiety>(_changeSatiety); // пользователь нажимает на шкалу сытости
on<ChangeMood>(_changeMood); // пользователь нажимает на шкалу настроения
on<ChangeMealTime>(
_changeMealTime); // пользователь изменяет время приема пиши
on<ChangeMeal>(_changeMeal);
on<WakeupEvent>(_changeWakeup); // пользователь выбирает время пробуждения
on<RetireEvent>(_changeRetire); // пользователь выбирает время отхода ко сну
on<ToggleMoodExpandedEvent>(_toggleMood);
on<ToggleWakeupExpandedEvent>(_toggleWakeup);
on<AddPortionEvent>(_addPortion); //пользователь нажимает "Добавить продукт"
on<RemovePortionEvent>(
_removePortion); // пользователь нажимает на крестик около продукта
on<UploadImages>(_uploadImages); //пользователь нажимает "Добавить фото"
on<AddComment>(_addComment); //пользователь нажимает "Добавить комментарий"
}
Future<void> _onInitialize(
DiaryEditInitialEvent event,
Emitter<DiaryEditState> emit,
) async {
try {
final token =
"laravel_sanctum_Q22zOtJhQUcJnh0kseZFblFv5XAPaKbG3gTM5hga71ecbb49";
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/diary/index'),
headers: {
'Authorization': 'Bearer $token',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (data is Map<String, dynamic> && data.containsKey('id')) {
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<String, dynamic>) {
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,
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,
diaryEditModelObj: state.diaryEditModelObj
?.copyWith(mealList: fillMealList())));
}
} else {
// Ошибка при декодировании JSON
emit(DiaryEditErrorState(
'Ошибка при декодировании данных дневника'));
}
} catch (e) {
// Ошибка при получении данных дневника
emit(DiaryEditErrorState(
'Ошибка при получении данных дневника: $e'));
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
emit(
DiaryEditErrorState('Некоторые необходимые данные отсутствуют.'));
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(DiaryEditErrorState('Ошибка сервера: ${response.statusCode}'));
}
} catch (e) {
// Ошибка при получении данных дневника
emit(DiaryEditErrorState('Ошибка при получении данных дневника: $e'));
}
}
Future<void> _changeSatiety(
ChangeSatiety event, Emitter<DiaryEditState> 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<http.Response> 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<void> _changeMood(
ChangeMood event, Emitter<DiaryEditState> emit) async {
if (state is DiaryEditLoadedState) {
var currentState = state as DiaryEditLoadedState;
var updatedState =
currentState.copyWith(selectedMood: event.selectedElement);
emit(updatedState); // Передаем обновленное состояние в emit
// Отправляем данные по HTTP маршруту и ждем ответ
var response = await sendMoodData(
currentState.token,
event.selectedElement,
currentState.currentMeal?.id,
currentState.diaryId);
if (response.statusCode == 200) {
currentState.selectedMood = event.selectedElement;
emit(currentState); // Передаем обновленное состояние в emit
}
}
}
Future<http.Response> 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<void> _changeWakeup(
WakeupEvent event, Emitter<DiaryEditState> emit) async {
if (state is DiaryEditLoadedState) {
var currentState = state as DiaryEditLoadedState;
var updatedState =
currentState.copyWith(wakeupController: event.selectedTime);
emit(updatedState); // Передаем обновленное состояние в emit
// Отправляем данные по HTTP маршруту и ждем ответ
var response = await sendWakeupData(
currentState.token, event.selectedTime, currentState.diaryId);
if (response.statusCode == 200) {
currentState.wakeupController = event.selectedTime;
emit(currentState); // Передаем обновленное состояние в emit
}
}
}
Future<http.Response> 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<void> _changeRetire(
RetireEvent event, Emitter<DiaryEditState> emit) async {
if (state is DiaryEditLoadedState) {
var currentState = state as DiaryEditLoadedState;
var updatedState =
currentState.copyWith(retireController: event.selectedTime);
emit(updatedState); // Передаем обновленное состояние в emit
// Отправляем данные по HTTP маршруту и ждем ответ
var response = await sendRetireData(
currentState.token, event.selectedTime, currentState.diaryId);
if (response.statusCode == 200) {
currentState.retireController = event.selectedTime;
emit(currentState); // Передаем обновленное состояние в emit
}
}
}
Future<http.Response> sendRetireData(
String? token,
TimeOfDay retire,
int diaryID,
) async {
String url = dotenv.env['SERVER_ROOT']! + '/diary/???';
final credentials = {
'wakeup': retire,
'diary_id': diaryID,
};
var response = await http.post(Uri.parse(url),
headers: {
'Authorization': 'Bearer $token',
},
body: json.encode(credentials),
encoding: utf8);
return response;
}
FutureOr<void> _changeMealTime(
ChangeMealTime event, Emitter<DiaryEditState> 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<http.Response> 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<void> _changeMeal(ChangeMeal event, Emitter<DiaryEditState> 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<void> _toggleMood(
ToggleMoodExpandedEvent event, Emitter<DiaryEditState> emit) {
if (state is DiaryEditLoadedState) {
var currentState = state as DiaryEditLoadedState;
emit(currentState.copyWith(moodExpanded: !currentState.moodExpanded));
currentState.moodExpanded = !currentState.moodExpanded;
}
}
FutureOr<void> _toggleWakeup(
ToggleWakeupExpandedEvent event, Emitter<DiaryEditState> emit) {
if (state is DiaryEditLoadedState) {
var currentState = state as DiaryEditLoadedState;
emit(currentState.copyWith(wakeupExpanded: !currentState.wakeupExpanded));
currentState.wakeupExpanded = !currentState.wakeupExpanded;
}
}
FutureOr<void> _addPortion(
AddPortionEvent event, Emitter<DiaryEditState> 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<http.Response> 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<void> _removePortion(
RemovePortionEvent event, Emitter<DiaryEditState> emit) {}
Future<FutureOr<void>> _uploadImages(
UploadImages event, Emitter<DiaryEditState> emit) async {
String imageUrl = await uploadImageToServer(event.image.path);
print(imageUrl);
}
FutureOr<void> _addComment(AddComment event, Emitter<DiaryEditState> emit) {}
Future<String> 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<String> 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<SelectionPopupModel> 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"),
];
}

View File

@@ -0,0 +1,111 @@
// ignore_for_file: must_be_immutable
part of 'diary_edit_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///DiaryEdit widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class DiaryEditEvent extends Equatable {}
/// Event that is dispatched when the DiaryEdit widget is first created.
class DiaryEditInitialEvent extends DiaryEditEvent {
@override
List<Object?> get props => [];
}
class ChangeSatiety extends DiaryEditEvent {
ChangeSatiety(
this.selectedElement,
);
final int selectedElement;
@override
List<Object> get props => [selectedElement];
}
class ChangeMood extends DiaryEditEvent {
ChangeMood(
this.selectedElement,
);
final int selectedElement;
@override
List<Object> get props => [selectedElement];
}
class ChangeMealTime extends DiaryEditEvent {
ChangeMealTime(this.time);
final TimeOfDay time;
@override
List<Object> get props => [time];
}
class AddComment extends DiaryEditEvent {
AddComment(this.diaryId, this.comment);
final String comment;
final int diaryId;
@override
List<Object> get props => [comment, diaryId];
}
class WakeupEvent extends DiaryEditEvent {
WakeupEvent(this.selectedTime);
final TimeOfDay selectedTime;
@override
List<Object> get props => [selectedTime];
}
class RetireEvent extends DiaryEditEvent {
RetireEvent(this.selectedTime);
final TimeOfDay selectedTime;
@override
List<Object> get props => [selectedTime];
}
class ToggleMoodExpandedEvent extends DiaryEditEvent {
@override
List<Object?> get props => [];
}
class ToggleWakeupExpandedEvent extends DiaryEditEvent {
@override
List<Object?> get props => [];
}
class AddPortionEvent extends DiaryEditEvent {
AddPortionEvent(this.name, this.quantity, this.portion);
final String name;
final int quantity;
final String portion;
@override
List<Object> get props => [name, quantity, portion];
}
class RemovePortionEvent extends DiaryEditEvent {
RemovePortionEvent(this.portionId);
final int portionId;
@override
List<Object> get props => [portionId];
}
class UploadImages extends DiaryEditEvent {
UploadImages(this.image);
final XFile image;
@override
List<Object> get props => [image];
}
class ChangeMeal extends DiaryEditEvent {
ChangeMeal({required this.value, required this.meals});
SelectionPopupModel value;
List<dynamic>? meals;
@override
List<Object?> get props => [value, meals];
}

View File

@@ -0,0 +1,126 @@
// ignore_for_file: must_be_immutable
part of 'diary_edit_bloc.dart';
class DiaryEditState extends Equatable {
const DiaryEditState({this.diaryEditModelObj});
final DiaryEditModel? diaryEditModelObj;
@override
List<Object?> get props => [diaryEditModelObj];
}
class DiaryEditInitialState extends DiaryEditState {
DiaryEditInitialState({required DiaryEditModel diaryEditModelObj})
: super(diaryEditModelObj: diaryEditModelObj);
}
/// Represents the state of DiaryEdit in the application.
class DiaryEditLoadedState extends DiaryEditState {
DiaryEditLoadedState(
{this.diaryEditModelObj,
this.diaryDate,
this.token,
this.meals,
this.portions,
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<dynamic>? meals;
List<dynamic>? portions;
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<Object?> get props => [
diaryEditModelObj,
diaryDate,
token,
meals,
wakeupController,
retireController,
mealTimeController,
commentController,
productNameController,
productQuantityController,
selectedMood,
selectedSatiety,
diaryId,
wakeupExpanded,
moodExpanded,
currentMeal
];
DiaryEditLoadedState copyWith({
DiaryEditModel? diaryEditModelObj,
SelectionPopupModel? currentMeal,
String? diaryDate,
String? token,
List<dynamic>? meals,
List<dynamic>? portions,
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,
diaryDate: diaryDate ?? this.diaryDate,
token: token ?? this.token,
meals: meals ?? this.meals,
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);
}
}
class DiaryEditErrorState extends DiaryEditState {
final String errorMessage;
const DiaryEditErrorState(this.errorMessage);
@override
List<Object> get props => [errorMessage];
}

View File

@@ -0,0 +1,965 @@
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_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_scale_element.dart';
// ignore: must_be_immutable
class DiaryEditScreen extends StatelessWidget {
DiaryEditScreen({Key? key})
: super(
key: key,
);
static Widget builder(BuildContext context) {
return BlocProvider<DiaryEditBloc>(
create: (context) =>
DiaryEditBloc(DiaryEditState(diaryEditModelObj: DiaryEditModel()))
..add(DiaryEditInitialEvent()),
child: DiaryEditScreen());
}
Widget build(BuildContext context) {
return BlocBuilder<DiaryEditBloc, DiaryEditState>(
builder: (context, state) {
if (state is DiaryEditInitialState) {
// Это начальное состояние, данные ещё не загружены.
return CircularProgressIndicator(
color: Colors.white,
backgroundColor: Colors.white,
);
} else if (state is DiaryEditLoadedState) {
// Данные загружены, можно строить экран.
return _buildDiaryEditScreen(context, state);
} else if (state is DiaryEditErrorState) {
// Произошла ошибка при загрузке данных.
return Center(child: Text(state.errorMessage));
} else {
// Другие состояния, если они есть.
return Container(); // Пустой контейнер для необработанных состояний.
}
},
);
}
Widget _buildDiaryEditScreen(
BuildContext context, DiaryEditLoadedState state) {
mediaQueryData = MediaQuery.of(context);
return FormBuilder(
child: SafeArea(
child: Scaffold(
backgroundColor: Color.fromARGB(255, 240, 242, 245),
resizeToAvoidBottomInset: true,
body: SizedBox(
width: double.maxFinite,
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.only(bottom: 5.v),
child: Column(
children: [
_buildHeaderRow(context, state),
SizedBox(height: 30.v),
_buildWakupRetire(context, state),
SizedBox(height: 30.v),
//_buildImageStack(context),
_buildMeal(context, state),
SizedBox(height: 20.v),
_buildMood(context, state),
SizedBox(height: 30.v),
_buildNutritionChart(context),
SizedBox(height: 30.v),
_buildComment(context, state),
],
),
),
),
),
],
),
),
bottomNavigationBar: _buildEndDay(context),
),
));
}
/// Section Widget
Widget _buildHeaderRow(BuildContext context, DiaryEditLoadedState state) {
final day = state.diaryId;
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.h, vertical: 16.h),
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: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CustomImageView(
imagePath: ImageConstant.imgArrowLeft,
onTap: () {
NavigatorService.goBack();
}),
Padding(
padding: EdgeInsets.only(top: 1.v),
child: Column(
children: [
Align(
alignment: Alignment.centerRight,
child: Text(
"$day день",
style: CustomTextStyles.titleMediumBlack,
),
),
SizedBox(height: 2.v),
Text(
state.diaryDate.toString(),
style: CustomTextStyles.bodyMediumBluegray800,
),
],
),
),
],
),
);
}
/// Section Widget
Widget _buildAddProduct(BuildContext context) {
return CustomOutlinedButton(
text: "Добавить продукт",
margin: EdgeInsets.symmetric(horizontal: 10.h),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
);
}
/// Section Widget
Widget _buildAddPhoto(BuildContext context) {
return GestureDetector(
onTap: () async {
final picker = ImagePicker();
List<XFile>? images = await picker.pickMultiImage();
if (images.isNotEmpty) {
for (var image in images) {
context.read<DiaryEditBloc>().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,
)
],
),
),
);
}
_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),
),
],
),
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,
),
),
),
),
),
],
),
),
SizedBox(height: 20.v),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10.h),
child: BlocSelector<DiaryEditBloc, DiaryEditState,
DiaryEditModel?>(
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<DiaryEditBloc>().add(
ChangeMeal(meals: state.meals, value: value));
},
);
})),
SizedBox(height: 20.v),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10.h),
child: CustomTextFormField(
hintText: "Введите названия продукта",
controller: state.productNameController,
),
),
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: <TextInputFormatter>[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<DiaryEditBloc, DiaryEditState>(
builder: (context, state) {
if (state is DiaryEditLoadedState) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("😭"),
ScaleElement(
color: Colors.red,
isSelected: state.selectedSatiety == 0,
onTap: () {
context.read<DiaryEditBloc>().add(ChangeSatiety(0));
},
),
ScaleElement(
color: Colors.orange,
isSelected: state.selectedSatiety == 1,
onTap: () {
context.read<DiaryEditBloc>().add(ChangeSatiety(1));
},
),
ScaleElement(
color: Colors.yellow,
isSelected: state.selectedSatiety == 2,
onTap: () {
context.read<DiaryEditBloc>().add(ChangeSatiety(2));
},
),
ScaleElement(
color: Colors.lime,
isSelected: state.selectedSatiety == 3,
onTap: () {
context.read<DiaryEditBloc>().add(ChangeSatiety(3));
},
),
ScaleElement(
color: Colors.lightGreen,
isSelected: state.selectedSatiety == 4,
onTap: () {
context.read<DiaryEditBloc>().add(ChangeSatiety(4));
},
),
ScaleElement(
color: Colors.green,
isSelected: state.selectedSatiety == 5,
onTap: () {
context.read<DiaryEditBloc>().add(ChangeSatiety(5));
},
),
Text("😊"),
// Остальные элементы шкалы
],
);
} else {
return Container(); // Возвращаем пустой контейнер, если состояние не является DiaryEditLoadedState
}
},
)
],
),
));
}
_buildProduct(BuildContext context, Map<String, dynamic> 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,
),
),
Padding(
padding: EdgeInsets.only(
left: 20.h,
top: 3.v,
bottom: 3.v,
),
child: Text(
portion['quantity'] + portion['portion'],
),
),
Spacer(),
GestureDetector(
onTap: () {
context
.read<DiaryEditBloc>()
.add(RemovePortionEvent(portion['id']));
},
child: Padding(
padding: const EdgeInsets.only(right: 10),
child: Icon(
Icons.close,
color: appTheme.blueGray300,
),
)),
],
),
);
}
Widget _buildNutritionChart(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.h),
padding: EdgeInsets.symmetric(
horizontal: 10.h,
vertical: 16.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: [
SizedBox(height: 3.v),
Text(
"Ваш КБЖУ",
style: theme.textTheme.titleMedium,
),
SizedBox(height: 8.v),
Text(
"От дневной суточной нормы",
style: CustomTextStyles.bodyMediumBluegray800,
),
SizedBox(height: 19.v),
Align(
alignment: Alignment.centerRight,
child: Padding(
padding: EdgeInsets.only(
left: 30.h,
right: 12.h,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
margin: EdgeInsets.only(top: 7.v),
decoration: AppDecoration.outlinePrimary.copyWith(
borderRadius: BorderRadiusStyle.roundedBorder1,
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(height: 101.v),
Container(
height: 47.v,
width: 12.h,
decoration: BoxDecoration(
color: theme.colorScheme.errorContainer,
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(1.h),
),
),
),
],
),
),
Spacer(
flex: 33,
),
Container(
margin: EdgeInsets.only(top: 7.v),
decoration: AppDecoration.outlinePrimary.copyWith(
borderRadius: BorderRadiusStyle.roundedBorder1,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 66.v),
Container(
height: 82.v,
width: 12.h,
decoration: BoxDecoration(
color: appTheme.lightGreenA700,
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(1.h),
),
),
),
],
),
),
Spacer(
flex: 33,
),
Container(
margin: EdgeInsets.only(top: 7.v),
decoration: AppDecoration.outlinePrimary.copyWith(
borderRadius: BorderRadiusStyle.roundedBorder1,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 14.v),
Container(
height: 134.v,
width: 12.h,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(1.h),
),
),
),
],
),
),
Spacer(
flex: 33,
),
Container(
margin: EdgeInsets.only(top: 7.v),
decoration: AppDecoration.outlinePrimary.copyWith(
borderRadius: BorderRadiusStyle.roundedBorder1,
),
child: Container(
height: 148.v,
width: 12.h,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(
1.h,
),
),
),
),
Padding(
padding: EdgeInsets.only(
left: 6.h,
bottom: 128.v,
),
child: Text(
"*",
style: theme.textTheme.titleLarge,
),
),
],
),
),
),
SizedBox(height: 8.v),
Padding(
padding: EdgeInsets.only(left: 4.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Калории",
style: theme.textTheme.titleSmall,
),
Padding(
padding: EdgeInsets.only(left: 28.h),
child: Text(
"Белки",
style: theme.textTheme.titleSmall,
),
),
Spacer(),
Text(
"Жиры",
style: theme.textTheme.titleSmall,
),
Padding(
padding: EdgeInsets.only(left: 25.h),
child: Text(
"Углеводы",
style: theme.textTheme.titleSmall,
),
),
],
),
),
SizedBox(height: 2.v),
Padding(
padding: EdgeInsets.only(
left: 10.h,
right: 2.h,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"255 из 250Ккал",
style: theme.textTheme.bodySmall,
),
Padding(
padding: EdgeInsets.only(
left: 25.h,
bottom: 11.v,
),
child: Text(
"50 из 250 гр",
style: theme.textTheme.bodySmall,
),
),
Padding(
padding: EdgeInsets.only(
left: 19.h,
bottom: 11.v,
),
child: Text(
"270 из 250 гр",
style: theme.textTheme.bodySmall,
),
),
Padding(
padding: EdgeInsets.only(
left: 14.h,
bottom: 11.v,
),
child: Text(
"400 из 250 гр",
style: theme.textTheme.bodySmall,
),
),
],
),
),
SizedBox(height: 19.v),
Text(
"*Превышенная суточная норма углеводов",
style: theme.textTheme.labelLarge,
),
],
),
);
}
/// Section Widget
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,
),
),
],
),
),
],
),
));
}
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,
),
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<DiaryEditBloc>(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),
],
),
),
],
)),
);
}
/// Section Widget
Widget _buildEndDay(BuildContext context) {
return CustomOutlinedButton(
text: "Закончить день",
margin: EdgeInsets.only(
left: 16.h,
right: 16.h,
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<DiaryEditBloc>(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<DiaryEditBloc>().add(ChangeMood(0));
},
),
ScaleElement(
color: Colors.orange,
isSelected: state.selectedMood == 1,
onTap: () {
context.read<DiaryEditBloc>().add(ChangeMood(1));
},
),
ScaleElement(
color: Colors.yellow,
isSelected: state.selectedMood == 2,
onTap: () {
context.read<DiaryEditBloc>().add(ChangeMood(2));
},
),
ScaleElement(
color: Colors.lime,
isSelected: state.selectedMood == 3,
onTap: () {
context.read<DiaryEditBloc>().add(ChangeMood(3));
},
),
ScaleElement(
color: Colors.lightGreen,
isSelected: state.selectedMood == 4,
onTap: () {
context.read<DiaryEditBloc>().add(ChangeMood(4));
},
),
ScaleElement(
color: Colors.green,
isSelected: state.selectedMood == 5,
onTap: () {
context.read<DiaryEditBloc>().add(ChangeMood(5));
},
),
Text("😊"),
// Остальные элементы шкалы
],
),
])));
}
Future<void> _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<DiaryEditBloc>().add(WakeupEvent(
pickedTime,
));
}
}
Future<void> _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<DiaryEditBloc>().add(RetireEvent(pickedTime));
}
}
Future<void> _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<DiaryEditBloc>().add(ChangeMealTime(pickedTime));
}
}
}

View File

@@ -0,0 +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({this.mealList = const []});
List<SelectionPopupModel> mealList;
DiaryEditModel copyWith({
List<SelectionPopupModel>? mealList,
}) {
return DiaryEditModel(
mealList: mealList ?? this.mealList,
);
}
@override
List<Object?> get props => [mealList];
}

View File

@@ -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';

View File

@@ -19,6 +19,7 @@ import 'package:eatvio_app/presentation/registration/app_navigation_screen/app_n
import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart';
import 'package:eatvio_app/presentation/profile/my_profile_screen/my_profile_screen.dart';
import 'package:eatvio_app/presentation/profile/my_profile_screen/my_settings_screen.dart';
import 'package:eatvio_app/presentation/diary/diary_edit_screen/diary_edit_screen.dart';
class AppRoutes {
// REGISTRATION
@@ -69,6 +70,8 @@ class AppRoutes {
static const String mySettings = '/mySettings';
static const String diaryEdit = '/diaryEdit';
static Map<String, WidgetBuilder> get routes => {
dateOfBirthScreen: DateOfBirthScreen.builder,
cityOfLivingScreen: CityOfLivingScreen.builder,
@@ -91,5 +94,6 @@ class AppRoutes {
initialRoute: SplashScreen.builder,
myProfile: MyProfileScreen.builder,
mySettings: MySettingsScreen.builder,
diaryEdit: DiaryEditScreen.builder,
};
}

View File

@@ -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),

View File

@@ -0,0 +1,147 @@
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
class CustomDropDown extends StatelessWidget {
CustomDropDown({
Key? key,
this.alignment,
this.width,
this.focusNode,
this.icon,
this.autofocus = true,
this.textStyle,
this.items,
this.hintText,
this.hintStyle,
this.prefix,
this.prefixConstraints,
this.suffix,
this.suffixConstraints,
this.contentPadding,
this.borderDecoration,
this.fillColor,
this.filled = true,
this.validator,
this.onChanged,
this.defaultValue,
}) : super(
key: key,
);
final Alignment? alignment;
final double? width;
final FocusNode? focusNode;
final Widget? icon;
final bool? autofocus;
final TextStyle? textStyle;
final List<SelectionPopupModel>? items;
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 SelectionPopupModel? defaultValue;
final FormFieldValidator<SelectionPopupModel>? validator;
final Function(SelectionPopupModel)? onChanged;
@override
Widget build(BuildContext context) {
return alignment != null
? Align(
alignment: alignment ?? Alignment.center,
child: dropDownWidget,
)
: dropDownWidget;
}
Widget get dropDownWidget => SizedBox(
width: width ?? double.maxFinite,
child: DropdownButtonFormField<SelectionPopupModel>(
focusNode: focusNode ?? FocusNode(),
icon: icon,
autofocus: autofocus!,
style: textStyle ?? theme.textTheme.titleMedium,
items: items?.map((SelectionPopupModel item) {
return DropdownMenuItem<SelectionPopupModel>(
value: item,
child: Text(
item.title,
overflow: TextOverflow.ellipsis,
style: hintStyle ?? theme.textTheme.titleMedium,
),
);
}).toList(),
decoration: decoration,
validator: validator,
value: defaultValue,
onChanged: (value) {
onChanged!(value!);
},
),
);
InputDecoration get decoration => InputDecoration(
hintText: hintText ?? "",
hintStyle: hintStyle ?? theme.textTheme.titleMedium,
prefixIcon: prefix,
prefixIconConstraints: prefixConstraints,
suffixIcon: suffix,
suffixIconConstraints: suffixConstraints,
isDense: true,
contentPadding: contentPadding ??
EdgeInsets.only(
left: 6.h,
top: 6.v,
bottom: 6.v,
),
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,
),
),
);
}

View File

@@ -0,0 +1,148 @@
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.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<TextInputFormatter>? 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<String>? validator;
final void Function(String?)? onSaved;
@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,
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,
),
),
);
}

View File

@@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
class CustomIconButton extends StatelessWidget {
CustomIconButton({
Key? key,
this.alignment,
this.height,
this.width,
this.padding,
this.decoration,
this.child,
this.onTap,
}) : super(
key: key,
);
final Alignment? alignment;
final double? height;
final double? width;
final EdgeInsetsGeometry? padding;
final BoxDecoration? decoration;
final Widget? child;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return alignment != null
? Align(
alignment: alignment ?? Alignment.center,
child: iconButtonWidget,
)
: iconButtonWidget;
}
Widget get iconButtonWidget => SizedBox(
height: height ?? 0,
width: width ?? 0,
child: IconButton(
padding: EdgeInsets.zero,
icon: Container(
height: height ?? 0,
width: width ?? 0,
padding: padding ?? EdgeInsets.zero,
decoration: decoration ??
BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(9),
border: Border.all(
color: Colors.blueGrey,
width: 1,
),
),
child: child,
),
onPressed: onTap,
),
);
}
/// Extension on [CustomIconButton] to facilitate inclusion of all types of border style etc
extension IconButtonStyleHelper on CustomIconButton {
static BoxDecoration get fillBlueA => BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(14),
);
}

View File

@@ -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,
),
),
);
}
}

View File

@@ -23,6 +23,8 @@ 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
dev_dependencies:
flutter_test: