Merge pull request 'diary' (#23) from maxxkim/eatvio_app:diary into main

Reviewed-on: eatvio/eatvio_app#23
Reviewed-by: apoloz <anatoliy.poloz@gmail.com>
This commit is contained in:
apoloz
2024-03-30 14:47:55 +00:00
15 changed files with 1225 additions and 306 deletions

45
.metadata Normal file
View File

@@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "ba393198430278b6595976de84fe170f553cc728"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: android
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: ios
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: linux
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: macos
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: web
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: windows
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@@ -0,0 +1,5 @@
package com.eatvioapp.eatvio_app
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android" name="Android">
<configuration>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/gen" />
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/gen" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/app/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/app/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/app/src/main/assets" />
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/app/src/main/libs" />
<option name="PROGUARD_LOGS_FOLDER_RELATIVE_PATH" value="/app/src/main/proguard_logs" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/app/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/app/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
</content>
<orderEntry type="jdk" jdkName="Android API 29 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Flutter for Android" level="project" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module>

17
eatvio_app.iml Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/lib" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/.idea" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Flutter Plugins" level="project" />
<orderEntry type="library" name="Dart Packages" level="project" />
</component>
</module>

View File

@@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

View File

@@ -18,8 +18,7 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
on<DiaryEditInitialEvent>(_onInitialize);
on<ChangeSatiety>(_changeSatiety); // пользователь нажимает на шкалу сытости
on<ChangeMood>(_changeMood); // пользователь нажимает на шкалу настроения
on<ChangeMealTime>(
_changeMealTime); // пользователь изменяет время приема пиши
on<ChangeMealTime>(_changeMealTime); // пользовательяет времяа пиши
on<ChangeMeal>(_changeMeal);
on<WakeupEvent>(_changeWakeup); // пользователь выбирает время пробуждения
on<RetireEvent>(_changeRetire); // пользователь выбирает время отхода ко сну
@@ -32,6 +31,10 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
on<AddComment>(_addComment); //пользователь нажимает "Добавить комментарий"
on<ProductInput>(
_inputProduct); //пользователь нажимает "Добавить комментарий"
on<FinishDay>(_finishDay); //пользователь нажимает "Добавить комментарий"
on<SendModeration>(_sendModeration);
on<RefreshEvent>(
reloadScreen); //пользователь нажимает "Добавить комментарий"
}
Future<void> _onInitialize(
@@ -40,7 +43,7 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
) async {
try {
final token =
"laravel_sanctum_1lPFtd9tpGmHYS243C4K7YF8BrVhxxgXm8cTmjuZda1da694";
"9|laravel_sanctum_KFXbccDcWEvTPfJmV99EQv1neAggmpR2TidVGDPN34fa201a";
final response = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/diary/index'),
headers: {
@@ -48,20 +51,20 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
'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'),
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();
@@ -73,13 +76,20 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
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,
@@ -95,9 +105,11 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
id: 1, title: "Прием пищи 1", isSelected: true),
wakeupExpanded: true,
moodExpanded: true,
moderationExpanded: false,
token: token,
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:
@@ -108,6 +120,10 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
TextEditingController(text: diary['comment']),
productNameController: TextEditingController(),
productQuantityController: TextEditingController(),
caloriesController: TextEditingController(),
proteinsController: TextEditingController(),
fatsController: TextEditingController(),
carbsController: TextEditingController(),
diaryId: diaryId,
diaryDate: diaryDate,
autocompleteList: [],
@@ -135,6 +151,90 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
}
} catch (e) {
// Ошибка при получении данных дневника
emit(DiaryEditErrorState('Ошибка при получении данных о дневнике: $e'));
}
}
reloadScreen(DiaryEditEvent event, Emitter<DiaryEditState> emit) async {
print("Reloading...");
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'];
final diaryResponse = await http.get(
Uri.parse(dotenv.env['SERVER_ROOT']! + '/diary/$diaryId/show/'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
);
final diary = jsonDecode(diaryResponse.body)['diary'];
if (diary is Map<String, dynamic>) {
final String diaryDate = diary['date'] ?? DateTime.now().toString();
// Продолжайте обработку данных дневника
// Если текущее состояние - начальное, создаем новое загруженное состояние
try {
await Future(() => emit(DiaryEditLoadedState(
currentMeal: SelectionPopupModel(
id: 1, title: "Прием пищи 1", isSelected: true),
wakeupExpanded: true,
moodExpanded: true,
moderationExpanded: false,
token: token,
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()))));
print("Updated");
} catch (e) {
print("Ошибка при создании нового состояния $e");
}
} else {
emit(DiaryEditErrorState(
'Ошибка при декодировании данных дневника'));
}
} else {
emit(
DiaryEditErrorState('Некоторые необходимые данные отсутствуют.'));
}
} else {
emit(DiaryEditErrorState('Ошибка сервера: ${response.statusCode}'));
}
} catch (e) {
emit(DiaryEditErrorState('Ошибка при получении данных дневника: $e'));
}
}
@@ -229,18 +329,31 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
WakeupEvent event, Emitter<DiaryEditState> emit) async {
if (state is DiaryEditLoadedState) {
var currentState = state as DiaryEditLoadedState;
var updatedState = //инициализируем новое состояние
currentState.copyWith(wakeupController: event.selectedTime);
// Отправляем данные по HTTP маршруту и ждем ответ
var response = await sendWakeupData(
currentState.token, event.selectedTime, currentState.diaryId);
if (response.statusCode == 200) {
//Чекаем статус ответа
updatedState.wakeupController = event.selectedTime;
var updatedState =
currentState.copyWith(wakeupController: event.selectedTime);
print("Wakeup time changed to ${event.selectedTime}");
emit(updatedState); // Передаем обновленное состояние в emit
}
} else
print("Wakeup time change error");
}
}
FutureOr<void> _changeRetire(
RetireEvent event, Emitter<DiaryEditState> emit) async {
if (state is DiaryEditLoadedState) {
var currentState = state as DiaryEditLoadedState;
var response = await sendRetireData(
currentState.token, event.selectedTime, currentState.diaryId);
if (response.statusCode == 200) {
var updatedState =
currentState.copyWith(retireController: event.selectedTime);
print("Retire time changed to ${event.selectedTime}");
emit(updatedState); // Передаем обновленное состояние в emit
} else
print("Retire time change error");
}
}
@@ -254,39 +367,20 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
final wakeupString =
formatter.format(DateTime(2022, 1, 1, wakeup.hour, wakeup.minute));
final Map<String, dynamic> credentials = {
'wakeup': wakeupString,
'wakeUp': wakeupString,
};
var response = await http.post(
Uri.parse(url),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
body: json.encode(credentials),
encoding: utf8,
);
print(response.body);
var response = await http.post(Uri.parse(url),
headers: {
'Content-Type': 'application/json',
'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);
// Отправляем данные по HTTP маршруту и ждем ответ
var response = await sendRetireData(
currentState.token, event.selectedTime, currentState.diaryId);
if (response.statusCode == 200) {
updatedState.retireController = event.selectedTime;
emit(updatedState); // Передаем обновленное состояние в emit
}
}
}
Future<http.Response> sendRetireData(
String? token, TimeOfDay retire, int diaryID) async {
String url = dotenv.env['SERVER_ROOT']! + '/diary/$diaryID/retire/fill';
@@ -342,18 +436,23 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
return response;
}
FutureOr<void> _changeMeal(ChangeMeal event, Emitter<DiaryEditState> emit) {
FutureOr<void> _changeMeal(
ChangeMeal event, Emitter<DiaryEditState> emit) async {
if (state is DiaryEditLoadedState) {
var currentState = state as DiaryEditLoadedState;
final meal = currentState.meals?.firstWhere(
(meal) => meal['type'] == event.value.id,
orElse: () => null);
int? id = event.value.id;
emit(currentState.copyWith(
DiaryEditLoadedState updatedState = 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;
portions: meal['portions'],
photos: meal['photos'],
mealTimeController: toTime(meal['time'] ?? "1$id:00"),
selectedMood: meal['mood'],
selectedSatiety: meal['satiety']);
emit(updatedState);
}
}
@@ -382,64 +481,60 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
if (state is DiaryEditLoadedState) {
var currentState = state as DiaryEditLoadedState;
int? id = currentState.currentMeal?.id;
// Отправляем данные по HTTP маршруту и ждем ответ
var response = await sendPortionData(
currentState.token,
currentState.productNameController?.text,
'pcs',
int.parse(currentState.productQuantityController!.text),
currentState.currentMeal!.id,
currentState.diaryId);
currentState.token,
currentState.productNameController?.text,
'pcs',
int.parse(currentState.productQuantityController!.text),
currentState.currentMeal!.id,
currentState.diaryId,
);
print(response.body);
if (response.statusCode == 200) {
var updatedState = currentState.copyWith(
portions: currentState.meals?[id ?? 0]['portions'],
);
emit(updatedState); // Передаем обновленное состояние в emit
await reloadScreen(event, emit);
} else {
print(
'Failed to add portion. Status Code: ${response.statusCode} ${response.body}');
}
}
}
Future<http.Response> sendPortionData(
String? token,
String? name,
String portion,
int? quantity,
int? mealID,
int diaryID,
) async {
String url = dotenv.env['SERVER_ROOT']! +
'/diary/$diaryID/meal/$mealID/portions/create';
FutureOr<http.Response> sendPortionData(String? token, String? name,
String portion, int? quantity, int? mealID, int diaryID) async {
String url =
'${dotenv.env['SERVER_ROOT']!}/diary/$diaryID/meal/$mealID/portions/create';
final Map<String, dynamic> credentials = {
'product_name': name,
'portion': portion,
'quantity': quantity,
};
print(credentials);
var response = await http.post(Uri.parse(url),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
body: json.encode(credentials),
encoding: utf8);
print(response.body);
var response = await http.post(
Uri.parse(url),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
body: json.encode(credentials),
encoding: utf8,
);
return response;
}
FutureOr<void> _inputProduct(
Future<void> _inputProduct(
ProductInput event, Emitter<DiaryEditState> emit) async {
if (state is DiaryEditLoadedState) {
var currentState = state as DiaryEditLoadedState;
String? token = currentState.token;
final Map<String, dynamic> credentials = {
'name': event.input,
};
final uri = Uri.parse(dotenv.env['SERVER_ROOT']! +
'/diary/product_nutritional_values/filter?' +
Uri(queryParameters: credentials).query);
final Map<String, dynamic> credentials = {'name': event.input};
final uri = Uri.parse(
'${dotenv.env['SERVER_ROOT']!}/diary/product_nutritional_values/filter?${Uri(queryParameters: credentials).query}');
final response = await http.get(
uri,
headers: {
@@ -447,29 +542,210 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
'Authorization': 'Bearer $token',
},
);
final productNutritionalValues =
jsonDecode(response.body)['product_nutritional_value'];
if (productNutritionalValues is List<dynamic>) {
var updatedState = currentState.copyWith(
productNameController: TextEditingController(text: event.input),
autocompleteList: productNutritionalValues
.map((product) => product['name'])
.toList());
emit(updatedState);
} else {
print("Ошибка в декодировании ответа из API");
if (productNutritionalValues is List<dynamic>) {
// Create a new controller with the input text and update the autocomplete list
var updatedState = currentState.copyWith(
productNameController: TextEditingController(text: event.input),
autocompleteList: productNutritionalValues
.map((product) => product['name'])
.toList(),
);
if (event.input.length > 3) {
updatedState = updatedState.copyWith(moderationExpanded: true);
} else {
updatedState = updatedState.copyWith(moderationExpanded: false);
}
emit(updatedState); // Emit the updated state
}
}
}
FutureOr<void> _removePortion(
RemovePortionEvent event, Emitter<DiaryEditState> emit) {}
Future<void> _removePortion(
RemovePortionEvent event, Emitter<DiaryEditState> emit) async {
if (state is DiaryEditLoadedState) {
var currentState = state as DiaryEditLoadedState;
Future<FutureOr<void>> _uploadImages(
int? mealId =
currentState.currentMeal?.id; // Rename id to mealId for clarity
var response = await sendRemovePortion(
currentState.token,
event.portionId,
mealId,
currentState.diaryId,
);
if (response.statusCode == 204) {
await reloadScreen(event, emit);
} else {
print(
'Failed to add portion. Status Code: ${response.statusCode} ${response.body}');
}
}
}
Future<http.Response> sendRemovePortion(
String? token, int? portionID, int? mealID, int diaryID) async {
String url =
'${dotenv.env['SERVER_ROOT']!}/diary/$diaryID/meal/$mealID/portions/$portionID/delete';
var response = await http.delete(
Uri.parse(url),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
encoding: utf8,
);
return response;
}
Future<void> _finishDay(FinishDay event, Emitter<DiaryEditState> emit) async {
if (state is DiaryEditLoadedState) {
var currentState = state as DiaryEditLoadedState;
var response = await sendFinishDay(
currentState.token,
currentState.diaryId,
currentState.wakeupController,
currentState.retireController,
currentState.commentController?.text,
);
if (response.statusCode == 200) {
print("finished");
}
}
}
Future<http.Response> sendFinishDay(String? token, int diaryID,
TimeOfDay? wakeup, TimeOfDay? retire, String? comment) async {
final formatter = DateFormat('HH:mm');
final wakeupString =
formatter.format(DateTime(2022, 1, 1, wakeup!.hour, wakeup.minute));
final retireString =
formatter.format(DateTime(2022, 1, 1, retire!.hour, retire.minute));
final Map<String, dynamic> credentials = {
'wakeup': wakeupString,
'retire': retireString,
'comment': comment ?? " ",
};
String url = '${dotenv.env['SERVER_ROOT']!}/diary/$diaryID/finishDay';
var response = await http.post(
Uri.parse(url),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
body: json.encode(credentials),
encoding: utf8,
);
return response;
}
Future<void> _uploadImages(
UploadImages event, Emitter<DiaryEditState> emit) async {
String imageUrl = await uploadImageToServer(event.image.path);
print(imageUrl);
if (state is DiaryEditLoadedState) {
var currentState = state as DiaryEditLoadedState;
await uploadImageToServer(event.image.path, currentState.diaryId,
currentState.currentMeal!.id!, currentState.token);
await reloadScreen(event, emit);
}
}
Future<String> uploadImageToServer(
String imagePath, int diaryID, int mealId, String? token) async {
String url =
'${dotenv.env['SERVER_ROOT']!}/diary/$diaryID/meal/$mealId/photo/upload';
var request = http.MultipartRequest('POST', Uri.parse(url));
request.headers['Authorization'] = 'Bearer $token';
request.files.add(await http.MultipartFile.fromPath('photo', imagePath));
var client = http.Client();
var streamedResponse = await client.send(request);
if (streamedResponse.statusCode == 200) {
var response = await http.Response.fromStream(streamedResponse);
var imageUrl = response.body;
print('Image successfully uploaded');
return imageUrl;
} else {
throw Exception(
'Failed to upload image. Status code: ${streamedResponse.statusCode}}');
}
}
FutureOr<void> _sendModeration(
SendModeration event, Emitter<DiaryEditState> emit) async {
if (state is DiaryEditLoadedState) {
var currentState = state as DiaryEditLoadedState;
final response = await _sendModerationData(
event.name,
event.energyOn100gr,
event.proteinsOn100gr,
event.fatsOn100gr,
event.carbohydratesOn100gr,
currentState.token,
);
if (response.statusCode == 200)
print("Moderation success");
else
print("Moderation error");
}
}
Future<http.Response> _sendModerationData(
String name,
String energy_on_100gr,
String proteins_on_100gr,
String fats_on_100gr,
String carbohydrates_on_100gr,
String? token) async {
final Uri url = Uri.parse(
'${dotenv.env['SERVER_ROOT']!}/diary/product_nutritional_values/sendToModeration');
final Uri serverURL = Uri.parse('${dotenv.env['SERVER_ROOT']!}');
String host = serverURL.host;
final Map<String, String> credentials = {
'name': name,
'energy_on_100gr': energy_on_100gr,
'proteins_on_100gr': proteins_on_100gr,
'fats_on_100gr': fats_on_100gr,
'carbohydrates_on_100gr': carbohydrates_on_100gr,
};
var headers = {
//'Content-Length': bodyBytes,
'Authorization': 'Bearer $token',
'Host': host,
};
var response = await http.post(
url,
headers: headers,
body: credentials,
encoding: utf8,
);
if (response.statusCode == 200) {
return response;
} else {
throw Exception(
'Failed to send a product for moderation ${response.statusCode}');
}
}
FutureOr<void> _addComment(
@@ -508,24 +784,6 @@ class DiaryEditBloc extends Bloc<DiaryEditEvent, DiaryEditState> {
return response;
}
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]);

View File

@@ -113,3 +113,34 @@ class ChangeMeal extends DiaryEditEvent {
@override
List<Object?> get props => [value, meals];
}
class FinishDay extends DiaryEditEvent {
FinishDay();
@override
List<Object?> get props => [];
}
class RefreshEvent extends DiaryEditEvent {
RefreshEvent();
@override
List<Object?> get props => [];
}
class SendModeration extends DiaryEditEvent {
SendModeration(
{required this.name,
required this.energyOn100gr,
required this.proteinsOn100gr,
required this.fatsOn100gr,
required this.carbohydratesOn100gr});
String name;
String energyOn100gr;
String proteinsOn100gr;
String fatsOn100gr;
String carbohydratesOn100gr;
@override
List<Object?> get props =>
[name, energyOn100gr, proteinsOn100gr, fatsOn100gr, carbohydratesOn100gr];
}

View File

@@ -24,6 +24,7 @@ class DiaryEditLoadedState extends DiaryEditState {
this.token,
this.meals,
this.portions,
this.photos,
this.autocompleteList,
this.wakeupController,
this.retireController,
@@ -33,8 +34,13 @@ class DiaryEditLoadedState extends DiaryEditState {
this.productQuantityController,
this.selectedMood,
this.currentMeal,
this.caloriesController,
this.fatsController,
this.proteinsController,
this.carbsController,
required this.diaryId,
required this.selectedSatiety,
required this.moderationExpanded,
required this.wakeupExpanded,
required this.moodExpanded})
: super(diaryEditModelObj: null);
@@ -44,6 +50,7 @@ class DiaryEditLoadedState extends DiaryEditState {
String? token;
List<dynamic>? meals;
List<dynamic>? portions;
List<dynamic>? photos;
List<dynamic>? autocompleteList;
SelectionPopupModel? currentMeal;
TimeOfDay? wakeupController;
@@ -52,10 +59,15 @@ class DiaryEditLoadedState extends DiaryEditState {
TextEditingController? commentController;
TextEditingController? productNameController;
TextEditingController? productQuantityController;
TextEditingController? caloriesController;
TextEditingController? fatsController;
TextEditingController? proteinsController;
TextEditingController? carbsController;
int? selectedMood;
int selectedSatiety;
int diaryId;
bool wakeupExpanded;
bool moderationExpanded;
bool moodExpanded;
@override
List<Object?> get props => [
@@ -63,6 +75,8 @@ class DiaryEditLoadedState extends DiaryEditState {
diaryDate,
token,
meals,
portions,
photos,
autocompleteList,
wakeupController,
retireController,
@@ -70,11 +84,16 @@ class DiaryEditLoadedState extends DiaryEditState {
commentController,
productNameController,
productQuantityController,
caloriesController,
fatsController,
proteinsController,
carbsController,
selectedMood,
selectedSatiety,
diaryId,
wakeupExpanded,
moodExpanded,
moderationExpanded,
currentMeal
];
DiaryEditLoadedState copyWith({
@@ -84,6 +103,7 @@ class DiaryEditLoadedState extends DiaryEditState {
String? token,
List<dynamic>? meals,
List<dynamic>? portions,
List<dynamic>? photos,
List<dynamic>? autocompleteList,
TimeOfDay? wakeupController,
TimeOfDay? retireController,
@@ -91,10 +111,15 @@ class DiaryEditLoadedState extends DiaryEditState {
TextEditingController? commentController,
TextEditingController? productNameController,
TextEditingController? productQuantityController,
TextEditingController? caloriesController,
TextEditingController? fatsController,
TextEditingController? proteinsController,
TextEditingController? carbsController,
int? selectedMood,
int? selectedSatiety,
int? diaryId,
bool? wakeupExpanded,
bool? moderationExpanded,
bool? moodExpanded,
}) {
return DiaryEditLoadedState(
@@ -116,7 +141,12 @@ class DiaryEditLoadedState extends DiaryEditState {
selectedSatiety: selectedSatiety ?? this.selectedSatiety,
diaryId: diaryId ?? this.diaryId,
currentMeal: currentMeal ?? this.currentMeal,
caloriesController: caloriesController ?? this.caloriesController,
fatsController: fatsController ?? this.fatsController,
proteinsController: proteinsController ?? this.proteinsController,
carbsController: carbsController ?? this.carbsController,
wakeupExpanded: wakeupExpanded ?? this.wakeupExpanded,
moderationExpanded: moderationExpanded ?? this.moderationExpanded,
moodExpanded: moodExpanded ?? this.moodExpanded);
}
}

View File

@@ -1,10 +1,13 @@
// ignore_for_file: unused_local_variable
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:fl_chart/fl_chart.dart';
import 'package:image_picker/image_picker.dart';
import 'package:flutter_dotenv/flutter_dotenv.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';
@@ -13,17 +16,17 @@ import 'package:eatvio_app/widgets/custom_scale_element.dart';
// ignore: must_be_immutable
class DiaryEditScreen extends StatelessWidget {
DiaryEditScreen({Key? key})
: super(
key: key,
);
final void Function() onInitialize;
DiaryEditScreen({Key? key, required this.onInitialize}) : super(key: key);
static Widget builder(BuildContext context) {
return BlocProvider<DiaryEditBloc>(
create: (context) =>
DiaryEditBloc(DiaryEditState(diaryEditModelObj: DiaryEditModel()))
..add(DiaryEditInitialEvent()),
child: DiaryEditScreen());
child: DiaryEditScreen(
onInitialize: () {},
));
}
Widget build(BuildContext context) {
@@ -31,9 +34,8 @@ class DiaryEditScreen extends StatelessWidget {
builder: (context, state) {
if (state is DiaryEditInitialState) {
// Это начальное состояние, данные ещё не загружены.
return CircularProgressIndicator(
return Container(
color: Colors.white,
backgroundColor: Colors.white,
);
} else if (state is DiaryEditLoadedState) {
// Данные загружены, можно строить экран.
@@ -52,43 +54,51 @@ class DiaryEditScreen extends StatelessWidget {
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),
_buildMeal(context, state),
SizedBox(height: 20.v),
_buildMood(context, state),
SizedBox(height: 30.v),
_buildNutritionChart(context),
SizedBox(height: 30.v),
_buildComment(context, state),
],
return RefreshIndicator(
onRefresh: refreshPage(context),
child: 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),
_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),
),
),
bottomNavigationBar: _buildEndDay(context),
),
));
)));
}
refreshPage(BuildContext context) {
return () async {
context.read<DiaryEditBloc>().add(RefreshEvent());
};
}
/// Section Widget
@@ -217,9 +227,15 @@ class DiaryEditScreen extends StatelessWidget {
final digitsOnly = FilteringTextInputFormatter.digitsOnly;
List<String> _options =
state.autocompleteList!.map((dynamic item) => item as String).toList();
print(_options);
//print(_options);
FocusNode focusNodeName = FocusNode();
FocusNode focusNodeQuantity = FocusNode();
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.h),
padding: EdgeInsets.symmetric(
horizontal: 24.h,
vertical: 12.v,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(12)),
@@ -294,15 +310,15 @@ class DiaryEditScreen extends StatelessWidget {
optionsBuilder: buildOptions(_options),
fieldViewBuilder: (
BuildContext context,
TextEditingController textEditingController,
FocusNode focusNode,
TextEditingController productEditingController,
FocusNode focusNodeName,
VoidCallback onFieldSubmitted,
) {
return CustomTextFormField(
hintText: "Введите название продукта",
controller: textEditingController,
focusNode: focusNode,
onChanged: getAutocomplete(context),
controller: productEditingController,
focusNode: focusNodeName,
onChanged: getAutocomplete(context, focusNodeQuantity),
/*onFieldSubmitted: (String value) {
onFieldSubmitted();
},*/
@@ -338,6 +354,15 @@ class DiaryEditScreen extends StatelessWidget {
}),
),
SizedBox(height: 20.v),
if (state.moderationExpanded)
Padding(
padding: EdgeInsets.symmetric(horizontal: 10.h),
child: GestureDetector(
onTap: () => openCaloriesDialog(context, state),
child: Text("Отправить на модерацию"),
),
),
SizedBox(height: 20.v),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10.h),
child: Row(
@@ -347,6 +372,7 @@ class DiaryEditScreen extends StatelessWidget {
width: 146.h,
hintText: "Кол-во",
textInputAction: TextInputAction.done,
focusNode: focusNodeQuantity,
controller: state.productQuantityController,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[digitsOnly],
@@ -367,6 +393,12 @@ 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()
: [],
SizedBox(height: 20.v),
_buildAddPhoto(context),
SizedBox(height: 30.v),
Text(
@@ -424,6 +456,94 @@ class DiaryEditScreen extends StatelessWidget {
));
}
openCaloriesDialog(BuildContext context, DiaryEditLoadedState state) {
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return AlertDialog(
title: Text('Введите информацию о продукте'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
CustomTextFormField(
hintText: "Название",
controller: state.productNameController,
),
SizedBox(height: 10.v),
CustomTextFormField(
hintText: "Энергетическая ценность на 100",
keyboardType: TextInputType.number,
controller: state.caloriesController,
),
SizedBox(height: 10.v),
CustomTextFormField(
hintText: "Белки на 100г",
keyboardType: TextInputType.number,
controller: state.proteinsController,
),
SizedBox(height: 10.v),
CustomTextFormField(
hintText: "Жиры на 100г",
keyboardType: TextInputType.number,
controller: state.fatsController,
),
SizedBox(height: 10.v),
CustomTextFormField(
hintText: "Углеводы на 100г",
keyboardType: TextInputType.number,
controller: state.carbsController,
),
],
),
actions: <Widget>[
TextButton(
onPressed: popDialog(context),
child: Text('Отмена'),
),
TextButton(
onPressed: triggerModeration(
context,
state.productNameController!.text,
state.caloriesController!.text,
state.proteinsController!.text,
state.fatsController!.text,
state.carbsController!.text,
dialogContext),
child: Text('Отправить'),
),
],
);
},
);
}
triggerModeration(
BuildContext context,
String name,
String energyOn100gr,
String proteinsOn100gr,
String fatsOn100gr,
String carbohydratesOn100gr,
BuildContext dialogContext) {
return () {
// Call function to send moderation with the entered values
context.read<DiaryEditBloc>().add(SendModeration(
name: name,
energyOn100gr: energyOn100gr,
proteinsOn100gr: proteinsOn100gr,
fatsOn100gr: fatsOn100gr,
carbohydratesOn100gr: carbohydratesOn100gr));
Navigator.of(dialogContext).pop(); // Dismiss the dialog
};
}
popDialog(BuildContext dialogContext) {
return () {
Navigator.of(dialogContext)
.pop(); // Dismiss the dialog on Cancel button press
};
}
changeSatiety(BuildContext context, int satiety) {
return () {
context.read<DiaryEditBloc>().add(ChangeSatiety(satiety));
@@ -436,7 +556,7 @@ class DiaryEditScreen extends StatelessWidget {
};
}
getAutocomplete(BuildContext context) {
getAutocomplete(BuildContext context, FocusNode focusNode) {
return (value) {
/*print(state.autocompleteList
?.map((dynamic item) => item as String)
@@ -478,15 +598,16 @@ class DiaryEditScreen extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.only(
left: 7.h,
top: 5.v,
),
child: Text(
portion['product_name'],
style: CustomTextStyles.titleMedium16,
),
),
padding: EdgeInsets.only(
left: 7.h,
top: 5.v,
),
child: Text(
portion['product_name'].length > 8
? portion['product_name'].substring(0, 8)
: portion['product_name'],
style: CustomTextStyles.titleMedium16,
)),
Padding(
padding: EdgeInsets.only(
left: 20.h,
@@ -514,6 +635,18 @@ class DiaryEditScreen extends StatelessWidget {
);
}
_buildPhoto(BuildContext context, Map<String, dynamic> photo) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 10.h, vertical: 10.h),
decoration: AppDecoration.fillGray.copyWith(
borderRadius: BorderRadiusStyle.roundedBorder5,
),
child: Image.network(
dotenv.env['STORAGE_URL']! + photo['path'],
),
);
}
removePortion(BuildContext context, Map<String, dynamic> portion) {
return () {
context.read<DiaryEditBloc>().add(RemovePortionEvent(portion['id']));
@@ -521,6 +654,13 @@ class DiaryEditScreen extends StatelessWidget {
}
_buildNutritionChart(BuildContext context) {
final List<double> recommendedValues = [
2000,
70,
50,
300
]; // Рекомендуемые значения: калории, жиры, белки, углеводы
final List<double> consumedValues = [1800, 60, 40, 250]; //
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.h),
padding: EdgeInsets.symmetric(
@@ -561,142 +701,96 @@ class DiaryEditScreen extends StatelessWidget {
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,
child: AspectRatio(
aspectRatio: 1.5,
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
barTouchData: BarTouchData(enabled: false),
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
//getTitlesWidget: (value, meta) => newMethod(value),
),
),
),
borderData: FlBorderData(show: false),
barGroups: [
BarChartGroupData(x: 0, barRods: [
BarChartRodData(
toY: consumedValues[0] / recommendedValues[0] * 100,
color: Colors.blue,
)
]),
BarChartGroupData(x: 1, barRods: [
BarChartRodData(
toY: consumedValues[1] / recommendedValues[1] * 100,
color: Colors.red,
)
]),
BarChartGroupData(x: 2, barRods: [
BarChartRodData(
toY: consumedValues[2] / recommendedValues[2] * 100,
color: Colors.green,
)
]),
BarChartGroupData(x: 3, barRods: [
BarChartRodData(
toY: consumedValues[3] / recommendedValues[3] * 100,
color: Colors.orange,
)
]),
],
),
Padding(
padding: EdgeInsets.only(
left: 6.h,
bottom: 128.v,
),
child: Text(
"*",
style: theme.textTheme.titleLarge,
),
),
],
),
),
),
),
SizedBox(height: 8.v),
SizedBox(height: 2.v),
Padding(
padding: EdgeInsets.only(left: 4.h),
padding: EdgeInsets.only(
left: 10.h,
right: 2.h,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Калории",
style: theme.textTheme.titleSmall,
"Калорий",
style: theme.textTheme.bodySmall,
),
Padding(
padding: EdgeInsets.only(left: 28.h),
padding: EdgeInsets.only(
left: 25.h,
bottom: 11.v,
),
child: Text(
"Белки",
style: theme.textTheme.titleSmall,
"Белков",
style: theme.textTheme.bodySmall,
),
),
Spacer(),
Text(
"Жиры",
style: theme.textTheme.titleSmall,
Padding(
padding: EdgeInsets.only(
left: 19.h,
bottom: 11.v,
),
child: Text(
"Жиров",
style: theme.textTheme.bodySmall,
),
),
Padding(
padding: EdgeInsets.only(left: 25.h),
padding: EdgeInsets.only(
left: 14.h,
bottom: 11.v,
),
child: Text(
"Углеводы",
style: theme.textTheme.titleSmall,
"Углеводов",
style: theme.textTheme.bodySmall,
),
),
],
@@ -713,7 +807,7 @@ class DiaryEditScreen extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"255 из 250Ккал",
"${consumedValues[0].toInt()} из ${recommendedValues[0].toInt()}",
style: theme.textTheme.bodySmall,
),
Padding(
@@ -722,7 +816,7 @@ class DiaryEditScreen extends StatelessWidget {
bottom: 11.v,
),
child: Text(
"50 из 250 гр",
"${consumedValues[1].toInt()} из ${recommendedValues[1].toInt()}",
style: theme.textTheme.bodySmall,
),
),
@@ -732,7 +826,7 @@ class DiaryEditScreen extends StatelessWidget {
bottom: 11.v,
),
child: Text(
"270 из 250 гр",
"${consumedValues[2].toInt()} из ${recommendedValues[2].toInt()}",
style: theme.textTheme.bodySmall,
),
),
@@ -742,7 +836,7 @@ class DiaryEditScreen extends StatelessWidget {
bottom: 11.v,
),
child: Text(
"400 из 250 гр",
"${consumedValues[3].toInt()} из ${recommendedValues[3].toInt()}",
style: theme.textTheme.bodySmall,
),
),
@@ -750,10 +844,11 @@ class DiaryEditScreen extends StatelessWidget {
),
),
SizedBox(height: 19.v),
Text(
"*Превышенная суточная норма углеводов",
style: theme.textTheme.labelLarge,
),
if (checkConsumedValues(recommendedValues, consumedValues))
Text(
"*Превышенная суточная норма углеводов",
style: theme.textTheme.labelLarge,
)
],
),
);
@@ -896,10 +991,51 @@ class DiaryEditScreen extends StatelessWidget {
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
decoration: BoxDecoration(
color: Colors.transparent,
), // Установите прозрачный фон
),
onPressed: onFinishDayPressed(context),
// Установите прозрачный фон
);
}
Function() onFinishDayPressed(BuildContext context) {
return () {
print("sad");
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return AlertDialog(
title: Text("Подтвердите действие"),
content: Text("Вы уверены, что хотите завершить день?"),
actions: <Widget>[
TextButton(
child: Text("Отмена"),
onPressed: closeDialog(dialogContext),
),
TextButton(
child: Text("Подтвердить"),
onPressed: sendFinishDayData(dialogContext, context),
),
],
);
},
);
};
}
Function() sendFinishDayData(
BuildContext dialogContext, BuildContext context) {
return () {
closeDialog(dialogContext);
context.read<DiaryEditBloc>().add(FinishDay());
};
}
Function() closeDialog(BuildContext dialogContext) {
return () {
Navigator.of(dialogContext).pop();
};
}
_buildMood(BuildContext context, DiaryEditLoadedState state) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.h),
@@ -997,9 +1133,7 @@ class DiaryEditScreen extends StatelessWidget {
);
if (pickedTime != null && pickedTime != state.wakeupController) {
state.wakeupController = pickedTime;
context.read<DiaryEditBloc>().add(WakeupEvent(
pickedTime,
));
context.read<DiaryEditBloc>().add(WakeupEvent(pickedTime));
}
}
@@ -1027,3 +1161,15 @@ class DiaryEditScreen extends StatelessWidget {
}
}
}
bool checkConsumedValues(
List<double> recommendedValues, List<double> consumedValues) {
for (int i = 0; i < recommendedValues.length; i++) {
if (consumedValues[i] > recommendedValues[i]) {
// Если хотя бы одно потребленное значение превышает рекомендованное
return true; // Возвращаем false
}
}
// Если ни одно потребленное значение не превышает рекомендованное с соответствующим индексом
return false; // Возвращаем true
}

145
lib/widgets/chart_axis.dart Normal file
View File

@@ -0,0 +1,145 @@
/*import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
buildChart(
BuildContext context, List<double> chartPoints, List<String>? dates) {
// Определение осей графика
List<FlSpot> spots = [];
for (int i = 0; i < 4; i++) {
spots.add(
FlSpot((i * 10 / 10) + 1, chartPoints[i]),
);
}
Widget bottomTitleWidgets(
double value, TitleMeta meta, double chartWidth, List<String>? dates) {
if (value % 1 != 0) {
return Container();
}
final style = TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
fontSize: min(13, 13 * chartWidth / 300),
);
int index = value.toInt();
if (index >= 0 && index < dates!.length) {
// Проверка на допустимый индекс в массиве dates
String formattedDate =
DateFormat('dd.MM').format(DateTime.parse(dates[index]));
return SideTitleWidget(
axisSide: meta.axisSide,
space: 16,
child: Text(formattedDate, style: style),
);
} else {
return Container();
}
}
Widget leftTitleWidgets(double value, TitleMeta meta, double chartWidth) {
final style = TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
fontSize: min(18, 18 * chartWidth / 300),
);
return SideTitleWidget(
axisSide: meta.axisSide,
space: 16,
child: Text(meta.formattedValue, style: style),
);
}
return Padding(
padding: const EdgeInsets.only(
left: 12,
right: 20,
),
child: AspectRatio(
aspectRatio: 1,
child: LayoutBuilder(
builder: (context, constraints) {
return LineChart(
LineChartData(
lineTouchData: LineTouchData(
touchTooltipData: LineTouchTooltipData(
maxContentWidth: 100,
tooltipBgColor: Colors.black,
getTooltipItems: mapSpots(),
),
handleBuiltInTouches: true,
getTouchLineStart: (data, index) => 0,
),
lineBarsData: [
LineChartBarData(
color: Colors.pink,
spots: spots,
isCurved: true,
isStrokeCapRound: true,
barWidth: 3,
belowBarData: BarAreaData(
show: false,
),
dotData: const FlDotData(show: false),
),
],
minY: chartPoints.reduce(
(value, element) => value < element ? value : element),
maxY: chartPoints.reduce(
(value, element) => value > element ? value : element),
minX: 1,
maxX: 4,
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) =>
leftTitleWidgets(value, meta, constraints.maxWidth),
reservedSize: 56,
),
drawBelowEverything: true,
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) => bottomTitleWidgets(
value, meta, constraints.maxWidth, dates),
reservedSize: 36,
interval: 1,
),
drawBelowEverything: true,
),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
gridData: FlGridData(
show: true,
drawHorizontalLine: true,
drawVerticalLine: true,
horizontalInterval: 1.5,
verticalInterval: 5,
checkToShowHorizontalLine: valuteToInt(),
getDrawingHorizontalLine: (_) => FlLine(
color: Colors.blue.withOpacity(1),
dashArray: [8, 2],
strokeWidth: 0.8,
),
getDrawingVerticalLine: (_) => FlLine(
color: Colors.blue.withOpacity(1),
dashArray: [8, 2],
strokeWidth: 0.8,
),
checkToShowVerticalLine: valuteToInt(),
),
borderData: FlBorderData(show: false),
),
);
},
),
),
);
}
*/

View File

@@ -0,0 +1,79 @@
/*import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Nutrition Chart'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
height: 300,
width: 300,
child: BarChart(
BarChartData(
barTouchData: BarTouchData(enabled: false),
titlesData: FlTitlesData(
show: true,
bottomTitles: SideTitles(
showTitles: true,
getTextStyles: (value) =>
const TextStyle(color: Colors.black, fontSize: 14),
margin: 10,
showTitles: (double value) {
switch (value.toInt()) {
case 0:
return 'Calories';
case 1:
return 'Protein';
case 2:
return 'Fat';
case 3:
return 'Carbs';
default:
return '';
}
},
),
leftTitles: SideTitles(showTitles: false),
),
borderData: FlBorderData(show: false),
barGroups: [
BarChartGroupData(x: 0, barRods: [
BarChartRodData(y: 200, color: Colors.blue),
BarChartRodData(y: 150, color: Colors.blueAccent),
]),
BarChartGroupData(x: 1, barRods: [
BarChartRodData(y: 50, color: Colors.red),
BarChartRodData(y: 60, color: Colors.deepOrange),
]),
BarChartGroupData(x: 2, barRods: [
BarChartRodData(y: 80, color: Colors.green),
BarChartRodData(y: 70, color: Colors.lightGreen),
]),
BarChartGroupData(x: 3, barRods: [
BarChartRodData(y: 100, color: Colors.orange),
BarChartRodData(y: 120, color: Colors.orangeAccent),
]),
],
),
),
),
],
),
),
),
);
}
}
*/

View File

@@ -0,0 +1,89 @@
import 'package:eatvio_app/core/app_export.dart';
import 'package:eatvio_app/widgets/base_button.dart';
import 'package:flutter/material.dart';
class NextStepButton extends BaseButton {
NextStepButton({
Key? key,
this.decoration,
this.leftIcon,
this.rightIcon,
this.label,
VoidCallback? onPressed,
ButtonStyle? buttonStyle,
TextStyle? buttonTextStyle,
bool? isDisabled,
Alignment? alignment,
double? height,
double? width,
EdgeInsets? margin,
required String text,
}) : super(
text: text,
onPressed: onPressed,
buttonStyle: buttonStyle,
isDisabled: isDisabled,
buttonTextStyle: buttonTextStyle,
height: height,
alignment: alignment,
width: width,
margin: margin,
);
final BoxDecoration? decoration;
final Widget? leftIcon;
final Widget? rightIcon;
final Widget? label;
@override
Widget build(BuildContext context) {
return (alignment != null)
? Align(
alignment: alignment!,
child: Container(
height: this.height ?? 30.v,
width: this.width ?? double.maxFinite,
margin: margin,
decoration: decoration,
child: OutlinedButton(
style: buttonStyle,
onPressed: isDisabled ?? false ? null : onPressed ?? () {},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
leftIcon ?? const SizedBox.shrink(),
Text(
text,
style: buttonTextStyle ?? theme.textTheme.titleSmall,
),
rightIcon ?? const SizedBox.shrink(),
],
),
),
),
)
: Container(
height: this.height ?? 30.v,
width: this.width ?? double.maxFinite,
margin: margin,
decoration: decoration,
child: OutlinedButton(
style: buttonStyle,
onPressed: isDisabled ?? false ? null : onPressed ?? () {},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
leftIcon ?? const SizedBox.shrink(),
Text(
text,
style: buttonTextStyle ?? theme.textTheme.titleSmall,
),
rightIcon ?? const SizedBox.shrink(),
],
),
),
);
}
}

View File

@@ -26,13 +26,16 @@ void main() {
autocompleteList: [],
portions: [],
wakeupExpanded: false,
moderationExpanded: false,
moodExpanded: false);
await tester.pumpWidget(
MaterialApp(
home: BlocProvider<DiaryEditBloc>.value(
value: diaryEditBloc,
child: DiaryEditScreen(),
child: DiaryEditScreen(
onInitialize: () {},
),
),
),
);