Merge pull request 'Рефакторинг и сохранение авторизованного пользователя' (#31) from maxxkim/eatvio_app:fix_metrics into main

Reviewed-on: eatvio/eatvio_app#31
Reviewed-by: apoloz <anatoliy.poloz@gmail.com>

resolve #30
This commit is contained in:
apoloz
2024-04-15 14:05:05 +00:00
23 changed files with 666 additions and 328 deletions

View File

@@ -86,6 +86,7 @@ class UserRegistrationSheet {
'favorite_foods': favoriteFoods,
'unfavorite_foods': unfavoriteFoods,
'additional_info': additionalInfo,
'activity_coefficient': '1.2',
};
}
}

View File

@@ -65,25 +65,25 @@ class AuthScreen extends StatelessWidget {
EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapNoAccount(context)),
NextStepButton(
/*NextStepButton(
text: "Тест дневника".toUpperCase(),
margin:
EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapDiaryTest(context)),
NextStepButton(
text: "Тест профиля".toUpperCase(),
text: "Тест чата".toUpperCase(),
margin:
EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: onTapProfileTest(context)),
onPressed: onTapProfileTest(context)),*/
BlocBuilder<AuthBloc, AuthState>(builder: (context, state) {
return NextStepButton(
text: "authentificate".tr.toUpperCase(),
margin:
EdgeInsets.only(left: 16.h, right: 16.h, bottom: 50.v),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
onPressed: () => onTapAuth(state, context));
onPressed: onTapAuth(state, context));
}),
])));
}
@@ -102,6 +102,7 @@ class AuthScreen extends StatelessWidget {
headers: {'Content-Type': 'application/json'},
body: json.encode(credentials),
encoding: utf8);
print(response.statusCode);
if (response.statusCode == 200) {
var res = await jsonDecode(response.body);
final box = await Hive.openBox('token');

View File

@@ -0,0 +1,20 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/chat/chat_screen/models/chat_model.dart';
part 'chat_event.dart';
part 'chat_state.dart';
/// A bloc that manages the state of a Chat according to the event that is dispatched to it.
class ChatBloc extends Bloc<ChatEvent, ChatState> {
ChatBloc(ChatState initialState) : super(initialState) {
on<ChatInitialEvent>(_onInitialize);
}
_onInitialize(
ChatInitialEvent event,
Emitter<ChatState> emit,
) async {
emit(state.copyWith(chatModelObj: state.chatModelObj?.copyWith()));
}
}

View File

@@ -0,0 +1,28 @@
// ignore_for_file: must_be_immutable
part of 'chat_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///Chat widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class ChatEvent extends Equatable {}
/// Event that is dispatched when the Chat widget is first created.
class ChatInitialEvent extends ChatEvent {
@override
List<Object?> get props => [];
}
///event for dropdown selection
class ChangeDropDownEvent extends ChatEvent {
ChangeDropDownEvent({required this.value});
SelectionPopupModel value;
@override
List<Object?> get props => [
value,
];
}

View File

@@ -0,0 +1,31 @@
// ignore_for_file: must_be_immutable
part of 'chat_bloc.dart';
/// Represents the state of Chat in the application.
class ChatState extends Equatable {
ChatState({
this.selectedDropDownValue,
this.chatModelObj,
});
SelectionPopupModel? selectedDropDownValue;
ChatModel? chatModelObj;
@override
List<Object?> get props => [
selectedDropDownValue,
chatModelObj,
];
ChatState copyWith({
SelectionPopupModel? selectedDropDownValue,
ChatModel? chatModelObj,
}) {
return ChatState(
selectedDropDownValue:
selectedDropDownValue ?? this.selectedDropDownValue,
chatModelObj: chatModelObj ?? this.chatModelObj,
);
}
}

View File

@@ -0,0 +1,20 @@
import 'bloc/chat_bloc.dart';
import 'models/chat_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
class ChatScreen extends StatelessWidget {
const ChatScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
return BlocProvider<ChatBloc>(
create: (context) => ChatBloc(ChatState(chatModelObj: ChatModel()))
..add(ChatInitialEvent()),
child: ChatScreen());
}
@override
Widget build(BuildContext context) {
mediaQueryData = MediaQuery.of(context);
return Container();
}
}

View File

@@ -0,0 +1,16 @@
// ignore_for_file: must_be_immutable
import 'package:equatable/equatable.dart';
/// This class defines the variables used in the [chat_screen],
/// and is typically used to hold data that is passed between different parts of the application.
class ChatModel extends Equatable {
ChatModel();
ChatModel copyWith() {
return ChatModel();
}
@override
List<Object?> get props => [];
}

View File

@@ -0,0 +1,21 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/chat/chat_select_screen/models/chat_select_model.dart';
part 'chat_select_event.dart';
part 'chat_select_state.dart';
/// A bloc that manages the state of a ChatSelect according to the event that is dispatched to it.
class ChatSelectBloc extends Bloc<ChatSelectEvent, ChatSelectState> {
ChatSelectBloc(ChatSelectState initialState) : super(initialState) {
on<ChatSelectInitialEvent>(_onInitialize);
}
_onInitialize(
ChatSelectInitialEvent event,
Emitter<ChatSelectState> emit,
) async {
emit(state.copyWith(
chatSelectModelObj: state.chatSelectModelObj?.copyWith()));
}
}

View File

@@ -0,0 +1,28 @@
// ignore_for_file: must_be_immutable
part of 'chat_select_bloc.dart';
/// Abstract class for all events that can be dispatched from the
///ChatSelect widget.
///
/// Events must be immutable and implement the [Equatable] interface.
@immutable
abstract class ChatSelectEvent extends Equatable {}
/// Event that is dispatched when the ChatSelect widget is first created.
class ChatSelectInitialEvent extends ChatSelectEvent {
@override
List<Object?> get props => [];
}
///event for dropdown selection
class ChangeDropDownEvent extends ChatSelectEvent {
ChangeDropDownEvent({required this.value});
SelectionPopupModel value;
@override
List<Object?> get props => [
value,
];
}

View File

@@ -0,0 +1,31 @@
// ignore_for_file: must_be_immutable
part of 'chat_select_bloc.dart';
/// Represents the state of ChatSelect in the application.
class ChatSelectState extends Equatable {
ChatSelectState({
this.selectedDropDownValue,
this.chatSelectModelObj,
});
SelectionPopupModel? selectedDropDownValue;
ChatSelectModel? chatSelectModelObj;
@override
List<Object?> get props => [
selectedDropDownValue,
chatSelectModelObj,
];
ChatSelectState copyWith({
SelectionPopupModel? selectedDropDownValue,
ChatSelectModel? chatSelectModelObj,
}) {
return ChatSelectState(
selectedDropDownValue:
selectedDropDownValue ?? this.selectedDropDownValue,
chatSelectModelObj: chatSelectModelObj ?? this.chatSelectModelObj,
);
}
}

View File

@@ -0,0 +1,21 @@
import 'bloc/chat_select_bloc.dart';
import 'models/chat_select_model.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:flutter/material.dart';
class ChatSelectScreen extends StatelessWidget {
const ChatSelectScreen({Key? key}) : super(key: key);
static Widget builder(BuildContext context) {
return BlocProvider<ChatSelectBloc>(
create: (context) => ChatSelectBloc(
ChatSelectState(chatSelectModelObj: ChatSelectModel()))
..add(ChatSelectInitialEvent()),
child: ChatSelectScreen());
}
@override
Widget build(BuildContext context) {
mediaQueryData = MediaQuery.of(context);
return Container();
}
}

View File

@@ -0,0 +1,16 @@
// ignore_for_file: must_be_immutable
import 'package:equatable/equatable.dart';
/// This class defines the variables used in the [chat_select_screen],
/// and is typically used to hold data that is passed between different parts of the application.
class ChatSelectModel extends Equatable {
ChatSelectModel();
ChatSelectModel copyWith() {
return ChatSelectModel();
}
@override
List<Object?> get props => [];
}

View File

@@ -7,6 +7,7 @@ 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';
@@ -44,142 +45,91 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
Emitter<DiaryEditState> emit,
) async {
try {
final token =
"9|laravel_sanctum_KFXbccDcWEvTPfJmV99EQv1neAggmpR2TidVGDPN34fa201a";
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/diary/index'),
headers: {
'Content-Type': 'application/json',
'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/$diaryId/show/'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
);
final diary = jsonDecode(response.body)['diary'];
print("Screen load success");
if (diary is Map<String, dynamic>) {
final String diaryDate =
diary['date'] ?? DateTime.now().toString();
final responseChart = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! +
'/diary/$diaryId/dailyNutritionData/'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
);
if (responseChart.statusCode == 200) {
final recommendedChartValues = jsonDecode(
responseChart.body)['dailyNutritionRecommendation'];
final consumedChartValues =
jsonDecode(responseChart.body)['dailyNutritionIntake'];
if (state is DiaryEditLoadedState) {
// Если текущее состояние уже загружено, обновляем его
var currentState = state as DiaryEditLoadedState;
emit((currentState).copyWith(
currentMeal: currentState.currentMeal,
wakeupExpanded: currentState.wakeupExpanded,
moodExpanded: currentState.moodExpanded,
moderationExpanded: currentState.moderationExpanded,
token: currentState.token,
meals: currentState.meals,
portions: currentState.portions,
photos: currentState.photos,
wakeupController: currentState.wakeupController,
retireController: currentState.retireController,
commentController: currentState.commentController,
mealTimeController: currentState.mealTimeController,
productNameController: currentState.productNameController,
caloriesController: currentState.caloriesController,
proteinsController: currentState.proteinsController,
fatsController: currentState.fatsController,
carbsController: currentState.carbsController,
selectedSatiety: currentState.selectedSatiety,
selectedMood: currentState.selectedMood,
diaryDate: currentState.diaryDate,
diaryId: currentState.diaryId,
productQuantityController:
currentState.productQuantityController,
recommendedChartValues:
currentState.recommendedChartValues,
consumedChartValues: currentState.consumedChartValues,
autocompleteList: currentState.autocompleteList,
diaryEditModelObj: currentState.diaryEditModelObj));
} else {
// Если текущее состояние - начальное, создаем новое загруженное состояние
emit(DiaryEditLoadedState(
currentMeal: SelectionPopupModel(
id: 1, title: "Прием пищи 1", isSelected: true),
portionController: SelectionPopupModel(
id: 0, title: "порций", isSelected: true),
wakeupExpanded: true,
moodExpanded: true,
moderationExpanded: false,
token: token,
recommendedChartValues: recommendedChartValues.values
.map<int>((value) => value as int)
.toList(),
consumedChartValues: consumedChartValues.values
.map<int>((value) => value as int)
.toList() ??
[0, 0, 0, 0],
meals: diary['meals'],
portions: diary['meals'][0]['portions'],
photos: diary['meals'][0]['photos'],
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(),
caloriesController: TextEditingController(),
proteinsController: TextEditingController(),
fatsController: TextEditingController(),
carbsController: TextEditingController(),
diaryId: diaryId,
diaryDate: diaryDate,
autocompleteList: [],
diaryEditModelObj: state.diaryEditModelObj?.copyWith(
mealList: fillMealList(),
portionList: fillPortions())));
}
} else {
emit(DiaryEditErrorState(
'Ошибка при декодировании данных КБЖУ'));
}
} else {
// Ошибка при декодировании JSON
emit(DiaryEditErrorState(
'Ошибка при декодировании данных дневника'));
}
} catch (e) {
// Ошибка при получении данных дневника
emit(DiaryEditErrorState(
'Ошибка при получении данных дневника: $e'));
// final token =
// "9|laravel_sanctum_KFXbccDcWEvTPfJmV99EQv1neAggmpR2TidVGDPN34fa201a";
final box = await Hive.openBox('token');
final token = box.get('token');
//print(await fetchDiaryId(token));
final int diaryId = await fetchDiaryId(token, emit);
try {
print("Diary ID fetched");
final diary = await fetchDiaryData(token, diaryId);
print("Screen load success");
if (diary is Map<String, dynamic>) {
final String diaryDate = diary['date'] ?? DateTime.now().toString();
final chartValues = await fetchChartValues(token, diaryId, emit);
if (state is DiaryEditLoadedState) {
// Если текущее состояние уже загружено, обновляем его
var currentState = state as DiaryEditLoadedState;
emit((currentState).copyWith(
currentMeal: currentState.currentMeal,
wakeupExpanded: currentState.wakeupExpanded,
moodExpanded: currentState.moodExpanded,
moderationExpanded: currentState.moderationExpanded,
token: currentState.token,
meals: currentState.meals,
portions: currentState.portions,
photos: currentState.photos,
wakeupController: currentState.wakeupController,
retireController: currentState.retireController,
commentController: currentState.commentController,
mealTimeController: currentState.mealTimeController,
productNameController: currentState.productNameController,
caloriesController: currentState.caloriesController,
proteinsController: currentState.proteinsController,
fatsController: currentState.fatsController,
carbsController: currentState.carbsController,
selectedSatiety: currentState.selectedSatiety,
selectedMood: currentState.selectedMood,
diaryDate: currentState.diaryDate,
diaryId: currentState.diaryId,
productQuantityController:
currentState.productQuantityController,
recommendedChartValues: currentState.recommendedChartValues,
consumedChartValues: currentState.consumedChartValues,
autocompleteList: currentState.autocompleteList,
diaryEditModelObj: currentState.diaryEditModelObj));
} else {
// Если текущее состояние - начальное, создаем новое загруженное состояние
final List<SelectionPopupModel> meals =
fillMealList(diary['meals']);
emit(DiaryEditLoadedState(
currentMeal: meals[0],
portionController: SelectionPopupModel(
id: 0, title: "порций", isSelected: true),
wakeupExpanded: true,
moodExpanded: true,
moderationExpanded: false,
token: token,
recommendedChartValues: chartValues[0],
consumedChartValues: chartValues[1],
meals: diary['meals'],
portions: diary['meals'][0]['portions'],
photos: diary['meals'][0]['photos'],
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(),
caloriesController: TextEditingController(),
proteinsController: TextEditingController(),
fatsController: TextEditingController(),
carbsController: TextEditingController(),
diaryId: diaryId,
diaryDate: diaryDate,
autocompleteList: [],
diaryEditModelObj: state.diaryEditModelObj
?.copyWith(mealList: meals, portionList: fillPortions())));
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
emit(
DiaryEditErrorState('Некоторые необходимые данные отсутствуют.'));
}
} else {
// Статус ответа не равен 200, обрабатываем ошибку
emit(DiaryEditErrorState('Ошибка сервера: ${response.statusCode}'));
} catch (e) {
// Ошибка при получении данных дневника
emit(DiaryEditErrorState('Ошибка при получении данных дневника: $e'));
}
} catch (e) {
// Ошибка при получении данных дневника
@@ -187,12 +137,91 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
}
}
reloadScreen(DiaryEditEvent event, Emitter<DiaryEditState> emit) async {
fetchDiaryId(String token, Emitter<DiaryEditState> emit) async {
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/diary/index'),
headers: {
'Content-Type': 'application/json',
'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'];
return diaryId;
} else {
emit(DiaryEditErrorState('Некоторые необходимые данные отсутствуют.'));
print('fail');
return -1;
}
} else {
emit(DiaryEditErrorState('Ошибка сервера: ${response.statusCode}'));
print('fail');
return -1;
}
}
fetchDiaryData(String token, int diaryId) async {
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/diary/$diaryId/show/'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
);
final diary = jsonDecode(response.body)['diary'];
return diary;
}
fetchChartValues(
String token, int diaryID, Emitter<DiaryEditState> emit) async {
final responseChart = await http.get(
Uri.parse(
dotenv.env['SERVER_ROOT']! + '/diary/$diaryID/dailyNutritionData/'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
);
if (responseChart.statusCode == 200) {
final recommendedChartValues =
jsonDecode(responseChart.body)['dailyNutritionRecommendation']
.values
.map<int>((value) => value as int)
.toList();
final consumedChartValues =
jsonDecode(responseChart.body)['dailyNutritionIntake']
.values
.map<int>((value) => value as int)
.toList();
return [recommendedChartValues, consumedChartValues];
} else {
emit(DiaryEditErrorState('Ошибка при декодировании данных КБЖУ'));
print('fail');
return -1;
}
}
refreshScreen(
int mealID,
DiaryEditEvent event,
Emitter<DiaryEditState> emit,
) async {
await reloadScreen(event, emit);
}
reloadScreen(
DiaryEditEvent event,
Emitter<DiaryEditState> emit,
) async {
print("Reloading...");
try {
final token =
"9|laravel_sanctum_KFXbccDcWEvTPfJmV99EQv1neAggmpR2TidVGDPN34fa201a";
//final token =
// "9|laravel_sanctum_KFXbccDcWEvTPfJmV99EQv1neAggmpR2TidVGDPN34fa201a"; final box = await Hive.openBox('token');
final box = await Hive.openBox('token');
final token = box.get('token');
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/diary/index'),
headers: {
@@ -231,45 +260,46 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
responseChart.body)['dailyNutritionRecommendation'];
final consumedChartValues =
jsonDecode(responseChart.body)['dailyNutritionIntake'];
final List<SelectionPopupModel> meals =
fillMealList(diary['meals']);
try {
await Future(() => emit(DiaryEditLoadedState(
currentMeal: SelectionPopupModel(
id: 1, title: "Прием пищи 1", isSelected: true),
portionController: SelectionPopupModel(
id: 0, title: "порций", isSelected: true),
wakeupExpanded: true,
moodExpanded: true,
moderationExpanded: false,
token: token,
recommendedChartValues: recommendedChartValues.values
.map<int>((value) => value as int)
.toList(),
consumedChartValues: consumedChartValues.values
.map<int>((value) => value as int)
.toList(),
meals: diary['meals'],
portions: diary['meals'][0]['portions'],
photos: diary['meals'][0]['photos'],
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(),
caloriesController: TextEditingController(),
proteinsController: TextEditingController(),
fatsController: TextEditingController(),
carbsController: TextEditingController(),
diaryId: diaryId,
diaryDate: diaryDate,
autocompleteList: [],
diaryEditModelObj: state.diaryEditModelObj?.copyWith(
mealList: fillMealList(),
portionList: fillPortions()))));
diaryEditModelObj: state.diaryEditModelObj?.copyWith(
mealList: meals, portionList: fillPortions()),
currentMeal: meals[0],
portionController: SelectionPopupModel(
id: 0, title: "порций", isSelected: true),
wakeupExpanded: true,
moodExpanded: true,
moderationExpanded: false,
token: token,
recommendedChartValues: recommendedChartValues.values
.map<int>((value) => value as int)
.toList(),
consumedChartValues: consumedChartValues.values
.map<int>((value) => value as int)
.toList(),
meals: diary['meals'],
portions: diary['meals'][0]['portions'],
photos: diary['meals'][0]['photos'],
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(),
caloriesController: TextEditingController(),
proteinsController: TextEditingController(),
fatsController: TextEditingController(),
carbsController: TextEditingController(),
diaryId: diaryId,
diaryDate: diaryDate,
autocompleteList: [],
)));
print("Updated");
} catch (e) {
@@ -351,6 +381,8 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
currentState.diaryId);
if (response.statusCode == 200) {
print(
'Mood change success! meal_id: ${currentState.currentMeal?.id}, mood: ${event.selectedElement}');
updatedState.selectedMood = event.selectedElement;
emit(updatedState); // Передаем обновленное состояние в emit
}
@@ -387,6 +419,7 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
var response = await sendWakeupData(
currentState.token, event.selectedTime, currentState.diaryId);
if (response.statusCode == 200) {
await reloadScreen(event, emit);
var updatedState =
currentState.copyWith(wakeupController: event.selectedTime);
print("Wakeup time changed to ${event.selectedTime}");
@@ -403,6 +436,7 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
var response = await sendRetireData(
currentState.token, event.selectedTime, currentState.diaryId);
if (response.statusCode == 200) {
await reloadScreen(event, emit);
var updatedState =
currentState.copyWith(retireController: event.selectedTime);
print("Retire time changed to ${event.selectedTime}");
@@ -496,11 +530,10 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
if (state is DiaryEditLoadedState) {
var currentState = state as DiaryEditLoadedState;
final meal = currentState.meals?.firstWhere(
(meal) => meal['type'] == event.value.id,
(meal) => meal['id'] == event.value.id,
orElse: () => null);
print(meal);
int? id = event.value.id;
print(meal['photos']);
DiaryEditLoadedState updatedState = currentState.copyWith(
currentMeal: event.value,
portions: meal['portions'],
@@ -508,6 +541,7 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
mealTimeController: toTime(meal['time'] ?? "1$id:00"),
selectedMood: meal['mood'],
selectedSatiety: meal['satiety']);
print(updatedState.photos);
emit(updatedState);
}
}
@@ -727,7 +761,7 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
);
if (response.statusCode == 200) {
print("finished");
await reloadScreen(event, emit);
}
}
}
@@ -901,15 +935,16 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
return timeOfDay;
}
List<SelectionPopupModel> fillMealList() {
return [
SelectionPopupModel(id: 1, title: "Прием пищи 1", isSelected: true),
SelectionPopupModel(id: 2, title: "Прием пищи 2"),
SelectionPopupModel(id: 3, title: "Прием пищи 3"),
SelectionPopupModel(id: 4, title: "Прием пищи 4"),
SelectionPopupModel(id: 5, title: "Прием пищи 5"),
SelectionPopupModel(id: 6, title: "Прием пищи 6"),
];
List<SelectionPopupModel> fillMealList(List<dynamic> meals) {
meals.sort(compareTypes);
return meals.map((meal) {
String title = "Прием пищи ${meal['type']}";
return SelectionPopupModel(id: meal['id'], title: title);
}).toList();
}
int compareTypes(a, b) {
return a['type'].compareTo(b['type']);
}
List<SelectionPopupModel> fillPortions() {

View File

@@ -139,6 +139,7 @@ class DiaryEditLoadedState extends DiaryEditState {
diaryDate: diaryDate ?? this.diaryDate,
token: token ?? this.token,
meals: meals ?? this.meals,
photos: photos ?? this.photos,
autocompleteList: autocompleteList ?? this.autocompleteList,
portions: portions ?? this.portions,
recommendedChartValues:

View File

@@ -13,6 +13,8 @@ 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';
//import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
import 'package:carousel_slider/carousel_slider.dart';
// ignore: must_be_immutable
class DiaryEditScreen extends StatelessWidget {
@@ -229,7 +231,6 @@ class DiaryEditScreen extends StatelessWidget {
state.autocompleteList!.map((dynamic item) => item as String).toList();
//print(_options);
FocusNode focusNodeName = FocusNode();
FocusNode focusNodeQuantity = FocusNode();
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.h),
padding: EdgeInsets.symmetric(
@@ -318,10 +319,7 @@ class DiaryEditScreen extends StatelessWidget {
hintText: "Введите название продукта",
controller: productEditingController,
focusNode: focusNodeName,
onChanged: getAutocomplete(context, focusNodeQuantity),
/*onFieldSubmitted: (String value) {
onFieldSubmitted();
},*/
onChanged: getAutocomplete(context, focusNodeName),
);
},
optionsViewBuilder: (
@@ -341,7 +339,7 @@ class DiaryEditScreen extends StatelessWidget {
itemBuilder: (BuildContext context, int index) {
final String option = options.elementAt(index);
return GestureDetector(
onTap: selectProduct(onSelected, option),
onTap: selectProduct(onSelected, option, state),
child: ListTile(
title: Text(option),
),
@@ -374,7 +372,7 @@ class DiaryEditScreen extends StatelessWidget {
width: 150.h,
hintText: "Кол-во",
textInputAction: TextInputAction.done,
focusNode: focusNodeQuantity,
// focusNode: focusNodeQuantity,
controller: state.productQuantityController,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[digitsOnly],
@@ -404,11 +402,14 @@ class DiaryEditScreen extends StatelessWidget {
.map((portion) => _buildProduct(context, portion))
.toList(),
SizedBox(height: 30.v),
...state.photos != null
? state.photos!
.map((photo) => _buildPhoto(context, photo))
.toList()
: [],
/*...state.photos != null
? state.photos!.isEmpty
? [Text('Фото пока нет')]
: state.photos!
.map((photo) => _buildPhoto(context, photo))
.toList()
: [Text('Фото нет')],*/
_buildCarousel(context, state),
SizedBox(height: 20.v),
_buildAddPhoto(context),
SizedBox(height: 30.v),
@@ -561,7 +562,9 @@ class DiaryEditScreen extends StatelessWidget {
};
}
selectProduct(AutocompleteOnSelected<String> onSelected, String option) {
selectProduct(AutocompleteOnSelected<String> onSelected, String option,
DiaryEditLoadedState state) {
state.productNameController?.text = option;
return () {
onSelected(option);
};
@@ -600,6 +603,20 @@ class DiaryEditScreen extends StatelessWidget {
};
}
_buildCarousel(BuildContext context, DiaryEditLoadedState state) {
if (state.photos!.isEmpty) {
return Text("Фото нет");
} else {
return CarouselSlider(
options: CarouselOptions(height: 340.0),
items: state.photos!.map<Widget>((photo) {
return _buildPhoto(
context, photo); // Assuming photos are stored as strings
}).toList(),
);
}
}
_buildProduct(BuildContext context, Map<String, dynamic> portion) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 10.h),
@@ -764,25 +781,37 @@ class DiaryEditScreen extends StatelessWidget {
BarChartGroupData(x: 0, barRods: [
BarChartRodData(
toY: consumedValues[0] / recommendedValues[0] * 100,
color: Colors.blue,
color: checkConsumedValues(
[recommendedValues[0]], [consumedValues[1]])
? Colors.red
: Colors.blue,
)
]),
BarChartGroupData(x: 1, barRods: [
BarChartRodData(
toY: consumedValues[1] / recommendedValues[1] * 100,
color: Colors.red,
color: checkConsumedValues(
[recommendedValues[1]], [consumedValues[1]])
? Colors.red
: Colors.blue,
)
]),
BarChartGroupData(x: 2, barRods: [
BarChartRodData(
toY: consumedValues[2] / recommendedValues[2] * 100,
color: Colors.green,
color: checkConsumedValues(
[recommendedValues[2]], [consumedValues[2]])
? Colors.red
: Colors.blue,
)
]),
BarChartGroupData(x: 3, barRods: [
BarChartRodData(
toY: consumedValues[3] / recommendedValues[3] * 100,
color: Colors.orange,
color: checkConsumedValues(
[recommendedValues[3]], [consumedValues[3]])
? Colors.red
: Colors.blue,
)
]),
],
@@ -889,7 +918,7 @@ class DiaryEditScreen extends StatelessWidget {
SizedBox(height: 19.v),
if (checkConsumedValues(recommendedValues, consumedValues))
Text(
"*Превышенная суточная норма углеводов",
"*Превышенная суточная норма",
style: theme.textTheme.labelLarge,
)
],
@@ -1042,7 +1071,6 @@ class DiaryEditScreen extends StatelessWidget {
Function() onFinishDayPressed(BuildContext context) {
return () {
print("sad");
showDialog(
context: context,
builder: (BuildContext dialogContext) {

View File

@@ -1,10 +1,11 @@
//import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/profile/my_profile_screen/models/my_profile_model.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
//import 'package:hive/hive.dart';
import 'package:hive/hive.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
part 'my_profile_event.dart';
@@ -21,28 +22,29 @@ class MyProfileBloc extends Bloc<MyProfileEvent, MyProfileState> {
Emitter<MyProfileState> emit,
) async {
try {
/*final box = await Hive.openBox('token');
final token = box.get('token');*/
final token =
"9|laravel_sanctum_KFXbccDcWEvTPfJmV99EQv1neAggmpR2TidVGDPN34fa201a";
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
); // Если токен есть, запускаем приложение
}
//final token =
// "9|laravel_sanctum_KFXbccDcWEvTPfJmV99EQv1neAggmpR2TidVGDPN34fa201a";
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/profile/index'),
headers: {
'Authorization': 'Bearer $token',
},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
// Проверка наличия ключевых полей в JSON
if (data is Map<String, dynamic> &&
data.containsKey('user') &&
data.containsKey('userDetails') &&
data.containsKey('chartPoints') &&
data.containsKey('circumferences')) {
// Все ключевые поля присутствуют, продолжаем обработку
if (state is MyProfileLoadedState) {
// Если текущее состояние уже загружено, обновляем его
var currentState = state as MyProfileLoadedState;
var plans = []
..addAll(data["finishedPlans"])
@@ -51,9 +53,6 @@ class MyProfileBloc extends Bloc<MyProfileEvent, MyProfileState> {
..addAll(data["paidPlan"])
..addAll(data["requestedPlan"])
..addAll(data["initedPlan"]);
// Обновляем значения контроллеров только если они уже были созданы
emit((currentState).copyWith(
chartPoints: currentState.chartPoints,
chartDates: currentState.chartDates,
@@ -123,18 +122,6 @@ class MyProfileBloc extends Bloc<MyProfileEvent, MyProfileState> {
})
: data["chartPoints"];
/*List<double> weights = [0.0, 0.0, 0.0, 0.0];
List<String> dates = [
"0000-00-00 00:00:00",
"0000-00-00 00:00:00",
"0000-00-00 00:00:00",
"0000-00-00 00:00:00"
];
for (int i = 0; i < data["chartPoints"].length; i++) {
weights[i] = data["chartPoints"][i]["weight"].toDouble();
dates[i] = data["chartPoints"][i]["created_at"].toString();
}
print(data);*/
List<String> dates = [
"0000-00-00 00:00:00",
"0000-00-00 00:00:00",
@@ -169,14 +156,7 @@ class MyProfileBloc extends Bloc<MyProfileEvent, MyProfileState> {
};
}
}
print(data['userDetails']);
/*print(chartPoints);
print(circumferences);*/
/*print(weights);
print(weights.runtimeType);
print(circumferences);
print(circumferences.runtimeType);*/
// Если текущее состояние - начальное, создаем новое загруженное состояние
emit(MyProfileLoadedState(
chartPoints: chartPoints,
chartDates: dates,
@@ -231,7 +211,7 @@ class MyProfileBloc extends Bloc<MyProfileEvent, MyProfileState> {
selectedCity: data['userDetails']['city'],
selectedCountry: data['userDetails']['country'],
selectedActivity: determineActivityLevel(
double.parse(data['userDetails']['activity_coefficient'])),
data['userDetails']['activity_coefficient']),
));
}
} else {

View File

@@ -182,7 +182,7 @@ class MyProfileScreen extends StatelessWidget {
switch (chartType) {
case 1: // График веса
return GestureDetector(
onTap: _editWeight(context, state),
onTap: () => _editWeight(context, state),
child: Column(children: [
Text(
"weight_chart".tr,
@@ -225,7 +225,7 @@ class MyProfileScreen extends StatelessWidget {
]);
case 3: //График обхватов
return GestureDetector(
onTap: _editCircumference(context, state),
onTap: () => _editCircumference(context, state),
child: Column(children: [
Text(
"circumferences_chart".tr,
@@ -279,10 +279,8 @@ class MyProfileScreen extends StatelessWidget {
);
}
/// Section Widget
_buildChart(
BuildContext context, List<double> chartPoints, List<String>? dates) {
// Определение осей графика
List<FlSpot> spots = [];
for (int i = 0; i < 4; i++) {
spots.add(
@@ -428,7 +426,7 @@ class MyProfileScreen extends StatelessWidget {
};
}
mapSpots() {
List<LineTooltipItem?> Function(List<LineBarSpot>) mapSpots() {
return (touchedSpots) {
return touchedSpots.map((LineBarSpot touchedSpot) {
final textStyle = TextStyle(
@@ -635,7 +633,7 @@ class MyProfileScreen extends StatelessWidget {
);
}
mapTouchedSpots() {
List<LineTooltipItem?> Function(List<LineBarSpot>) mapTouchedSpots() {
return (touchedSpots) {
return touchedSpots.map((LineBarSpot touchedSpot) {
final textStyle = TextStyle(

View File

@@ -76,16 +76,18 @@ class AdditionalInformationScreen extends StatelessWidget {
UserRegistrationSheet sheet =
state.additionalInformationModelObj!.userRegistrationSheet;
sheet.additionalInfo = state.editTextController!.text;
//print(json.encode(sheet.toJson()));
final response = await http.post(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/register'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(sheet.toJson()),
);
print(response.statusCode);
print(response.body);
Uri.parse(dotenv.env['SERVER_ROOT']! + '/register'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: jsonEncode(sheet.toJson()),
encoding: utf8);
var res = await jsonDecode(response.body);
if (response.statusCode == 200) {
var res = await jsonDecode(response.body);
final box = await Hive.openBox('token');
final token = res['token'];
box.put('token', token);
@@ -93,6 +95,8 @@ class AdditionalInformationScreen extends StatelessWidget {
context,
AppRoutes.myProfile,
);
} else {
showErrorMessage(context, res['message']);
}
};
}
@@ -152,12 +156,12 @@ class AdditionalInformationScreen extends StatelessWidget {
/// Displays an alert dialog when the action is triggered.
/// The dialog box contains a title and a message with the `Теперь вы можете пользоваться полным функционалом приложения`
showSuccessMessage(BuildContext context, String text) {
showErrorMessage(BuildContext context, String text) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('Регистрация завершена!'),
content: const Text('1'),
title: const Text('Ошибка'),
content: Text(text),
actions: [
TextButton(onPressed: onTapOk(context), child: const Text('Ok'))
],

View File

@@ -17,7 +17,7 @@ class SplashBloc extends Bloc<SplashEvent, SplashState> {
) async {
Future.delayed(const Duration(milliseconds: 3000), () {
NavigatorService.popAndPushNamed(
AppRoutes.authScreen,
AppRoutes.myProfile,
);
});
}

View File

@@ -21,12 +21,12 @@ class CustomBottomBarState extends State<CustomBottomBar> {
title: "diary".tr,
type: BottomBarEnum.tf,
),
BottomMenuModel(
/*BottomMenuModel(
icon: ImageConstant.imgNavBlueGray400,
activeIcon: ImageConstant.imgNavBlueGray400,
title: "my_blog".tr,
type: BottomBarEnum.tf,
),
),*/
BottomMenuModel(
icon: ImageConstant.imgNavBlueGray40024x23,
activeIcon: ImageConstant.imgNavBlueGray40024x23,
@@ -35,8 +35,8 @@ class CustomBottomBarState extends State<CustomBottomBar> {
),
BottomMenuModel(
icon: ImageConstant.imgNavPrimary,
activeIcon: ImageConstant.imgNavPrimary,
title: "admin".tr,
activeIcon: ImageConstant.imgNavBlueGray40024x23,
title: "Настройки",
type: BottomBarEnum.tf,
),
BottomMenuModel(
@@ -72,12 +72,33 @@ class CustomBottomBarState extends State<CustomBottomBar> {
}
void changeMeny(index) {
selectedIndex = index;
widget.onChanged?.call(bottomMenuList[index].type);
setState(() {});
setState(() {
selectedIndex = index;
});
switch (index) {
case 0:
Navigator.pushNamed(context, AppRoutes.diaryEdit);
break;
/*case 1:
Navigator.pushNamed(context, '/my_blog');
break;*/
case 1:
Navigator.pushNamed(context, AppRoutes.myProfile);
break;
case 2:
Navigator.pushNamed(context, AppRoutes.mySettings);
break;
case 3:
Navigator.pushNamed(context, '/chat');
break;
default:
}
}
BottomNavigationBarItem bottomMenu(index) {
bool isSelected = selectedIndex == index;
return BottomNavigationBarItem(
icon: Column(
crossAxisAlignment: CrossAxisAlignment.center,
@@ -86,14 +107,17 @@ class CustomBottomBarState extends State<CustomBottomBar> {
imagePath: bottomMenuList[index].icon,
height: 24.v,
width: 19.h,
color: appTheme.blueGray400,
color:
isSelected ? theme.colorScheme.primary : appTheme.blueGray400,
),
Padding(
padding: EdgeInsets.only(top: 8.v),
child: Text(
bottomMenuList[index].title ?? "",
style: CustomTextStyles.bodySmallMulishBluegray80012.copyWith(
color: appTheme.blueGray800,
color: isSelected
? theme.colorScheme.primary
: appTheme.blueGray800,
),
),
),

View File

@@ -20,6 +20,7 @@ class CustomTextFormField extends StatelessWidget {
this.prefixConstraints,
this.suffix,
this.suffixConstraints,
this.onFieldSubmitted,
this.initialValue,
this.contentPadding,
this.inputFormatters,
@@ -66,6 +67,8 @@ class CustomTextFormField extends StatelessWidget {
final Widget? suffix;
final Function(String)? onFieldSubmitted;
final BoxConstraints? suffixConstraints;
final EdgeInsets? contentPadding;
@@ -112,6 +115,7 @@ class CustomTextFormField extends StatelessWidget {
keyboardType: keyboardType,
onSaved: onSaved,
onChanged: onChanged,
onFieldSubmitted: onFieldSubmitted,
inputFormatters: inputFormatters,
initialValue: initialValue,
),

View File

@@ -255,7 +255,7 @@ class _ExpandableListState extends State<ExpandableList> {
],
),
NextStepButton(
onPressed: sendData(state),
onPressed: () => sendProfileData(state),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
text: "save".tr.toUpperCase(),
margin: EdgeInsets.only(top: 20.h, bottom: 20.h)),
@@ -327,65 +327,16 @@ class _ExpandableListState extends State<ExpandableList> {
Future<void> sendProfileData(MyProfileLoadedState? state) async {
try {
// Создаем JSON объект с данными профиля
Map<String, dynamic> profileData = {
'first_name': state?.firstNameFormController?.text,
'last_name': state?.lastNameFormController?.text,
'chest_circumference': state?.chestCircumferenceFormController?.text,
'waist_circumference': state?.waistCircumferenceFormController?.text,
'hip_circumference': state?.hipCircumferenceFormController?.text,
'birthdate': state?.dateFormController?.text,
'height': state?.heightFormController?.text,
'weight': state?.weightFormController?.text,
'blood_pressure_up': state?.upPressureFormController?.text,
'blood_pressure_down': state?.downPressureFormController?.text,
'about_alergy': state?.allergiesFormController?.text,
'about_taking_pils': state?.pillsFormController?.text,
'about_sport_hobbies': state?.sportsFormController?.text,
'favorite_foods': state?.favoriteFormController?.text,
'unfavorite_foods': state?.unfavoriteFormController?.text,
'additional_info': state?.additionalFormController?.text,
'country': state?.selectedCountry ?? 'Россия',
'city': state?.selectedCity ?? 'Москва',
'has_hypo': state?.hypoFormController,
'has_hyper': state?.hyperFormController,
'activity_coefficient':
determineActivityFactor(state?.selectedActivity ?? 'сидячий')
.toString,
};
final box = await Hive.openBox('token');
final token = box.get('token');
String apiUrl = dotenv.env['SERVER_ROOT']! + '/profile/update';
http.Response response = await http.post(
Uri.parse(apiUrl),
body: json.encode(profileData),
encoding: utf8,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
);
final profileData = _createProfileData(state);
final token = await _getTokenFromBox();
final apiUrl = _buildApiUrl();
final response = await _sendHttpRequest(profileData, apiUrl, token);
print(response.body);
if (response.statusCode == 200) {
Navigator.pushNamed(
context,
AppRoutes.myProfile,
);
_navigateToProfilePage();
} else {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Ошибка'),
content: Text('Ошибка при отправке данных профиля'.tr),
actions: [
TextButton(
onPressed: onTapOk(context),
child: Text('OK'),
),
],
),
);
final errorMessage = _getErrorMessage(response);
_showErrorDialog(errorMessage);
throw Exception('Ошибка при отправке данных профиля');
}
} catch (error) {
@@ -393,6 +344,84 @@ class _ExpandableListState extends State<ExpandableList> {
}
}
Map<String, dynamic> _createProfileData(MyProfileLoadedState? state) {
return {
'first_name': state?.firstNameFormController?.text,
'last_name': state?.lastNameFormController?.text,
'chest_circumference': state?.chestCircumferenceFormController?.text,
'waist_circumference': state?.waistCircumferenceFormController?.text,
'hip_circumference': state?.hipCircumferenceFormController?.text,
'birthdate': state?.dateFormController?.text,
'height': state?.heightFormController?.text,
'weight': state?.weightFormController?.text,
'blood_pressure_up': state?.upPressureFormController?.text,
'blood_pressure_down': state?.downPressureFormController?.text,
'about_alergy': state?.allergiesFormController?.text ?? 'Нет аллергий',
'about_taking_pils': state?.pillsFormController?.text,
'about_sport_hobbies': state?.sportsFormController?.text,
'favorite_foods': state?.favoriteFormController?.text,
'unfavorite_foods': state?.unfavoriteFormController?.text,
'additional_info': state?.additionalFormController?.text,
'country': state?.selectedCountry ?? 'Россия',
'city': state?.selectedCity ?? 'Москва',
'has_hypo': state?.hypoFormController,
'has_hyper': state?.hyperFormController,
'activity_coefficient':
determineActivityFactor(state?.selectedActivity ?? 'сидячий'),
};
}
Future<String?> _getTokenFromBox() async {
final box = await Hive.openBox('token');
return box.get('token');
}
String _buildApiUrl() {
return dotenv.env['SERVER_ROOT']! + '/profile/update';
}
Future<http.Response> _sendHttpRequest(
Map<String, dynamic> profileData, String apiUrl, String? token) {
return http.post(
Uri.parse(apiUrl),
body: json.encode(profileData),
encoding: utf8,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $token',
},
);
}
void _navigateToProfilePage() {
Navigator.pushNamed(
context,
AppRoutes.myProfile,
);
}
String _getErrorMessage(http.Response response) {
final res = jsonDecode(response.body);
return res['message'];
}
void _showErrorDialog(String errorMessage) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Ошибка'),
content: Text(errorMessage),
actions: [
TextButton(
onPressed: onTapOk(context),
child: Text('OK'),
),
],
),
);
}
onTapOk(BuildContext context) {
return () {
Navigator.pop(context);

View File

@@ -24,6 +24,7 @@ void main() {
diaryId: 1,
selectedSatiety: 4,
autocompleteList: [],
photos: [],
portions: [],
wakeupExpanded: false,
moderationExpanded: false,