diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml
index 196d2aa1..0b639038 100644
--- a/.gitea/workflows/test.yml
+++ b/.gitea/workflows/test.yml
@@ -24,7 +24,8 @@ jobs:
- name: Install Android SDK
run: |
apt-get update
- https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip -O commandlinetools.zip
+ apt-get install -y openjdk-11-jdk wget
+ wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip -O commandlinetools.zip
unzip commandlinetools.zip
mkdir -p $HOME/Android/Sdk/cmdline-tools/latest
mv cmdline-tools/* $HOME/Android/Sdk/cmdline-tools/latest/
diff --git a/android/.idea/caches/deviceStreaming.xml b/android/.idea/caches/deviceStreaming.xml
new file mode 100644
index 00000000..5a2f8669
--- /dev/null
+++ b/android/.idea/caches/deviceStreaming.xml
@@ -0,0 +1,329 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/.idea/gradle.xml b/android/.idea/gradle.xml
new file mode 100644
index 00000000..efe09167
--- /dev/null
+++ b/android/.idea/gradle.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/.idea/migrations.xml b/android/.idea/migrations.xml
new file mode 100644
index 00000000..f8051a6f
--- /dev/null
+++ b/android/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml
new file mode 100644
index 00000000..48725873
--- /dev/null
+++ b/android/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/.idea/modules.xml b/android/.idea/modules.xml
new file mode 100644
index 00000000..bf609e00
--- /dev/null
+++ b/android/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/.idea/modules/android.iml b/android/.idea/modules/android.iml
new file mode 100644
index 00000000..2af55b99
--- /dev/null
+++ b/android/.idea/modules/android.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/.idea/runConfigurations.xml b/android/.idea/runConfigurations.xml
new file mode 100644
index 00000000..16660f1d
--- /dev/null
+++ b/android/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/.idea/vcs.xml b/android/.idea/vcs.xml
new file mode 100644
index 00000000..6c0b8635
--- /dev/null
+++ b/android/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/.idea/workspace.xml b/android/.idea/workspace.xml
new file mode 100644
index 00000000..df82a64e
--- /dev/null
+++ b/android/.idea/workspace.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1731295514828
+
+
+ 1731295514828
+
+
+
+
\ No newline at end of file
diff --git a/android/app/build.gradle b/android/app/build.gradle
index e5218dce..26791c12 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -7,7 +7,7 @@ plugins {
android {
namespace = "com.eatvioapp.app"
compileSdk = flutter.compileSdkVersion
- ndkVersion = "28.0.12433566"
+ ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_21
diff --git a/android/settings.gradle b/android/settings.gradle
index b5e1b3f5..42a51c65 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -18,8 +18,8 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
- id "com.android.application" version "8.3.2" apply false
- id "org.jetbrains.kotlin.android" version "2.0.20" apply false
+ id "com.android.application" version "8.7.2" apply false
+ id "org.jetbrains.kotlin.android" version "2.0.21" apply false
}
include ":app"
diff --git a/assets/icons/satiety_emoji_left.svg b/assets/icons/satiety_emoji_left.svg
new file mode 100644
index 00000000..e2a21bda
--- /dev/null
+++ b/assets/icons/satiety_emoji_left.svg
@@ -0,0 +1,7 @@
+
diff --git a/assets/icons/satiety_emoji_right.svg b/assets/icons/satiety_emoji_right.svg
new file mode 100644
index 00000000..6791ee88
--- /dev/null
+++ b/assets/icons/satiety_emoji_right.svg
@@ -0,0 +1,11 @@
+
diff --git a/lib/features/diary/models/nutrition_model.dart b/lib/features/diary/models/nutrition_model.dart
new file mode 100644
index 00000000..73103842
--- /dev/null
+++ b/lib/features/diary/models/nutrition_model.dart
@@ -0,0 +1,27 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'nutrition_model.freezed.dart';
+
+part 'nutrition_model.g.dart';
+
+@freezed
+class NutritionDataModel with _$NutritionDataModel {
+ const factory NutritionDataModel({
+ required NutritionModel dailyNutritionIntake,
+ required NutritionModel dailyNutritionRecommendation,
+ }) = _NutritionDataModel;
+
+ factory NutritionDataModel.fromJson(Map json) => _$NutritionDataModelFromJson(json);
+}
+
+@freezed
+class NutritionModel with _$NutritionModel {
+ const factory NutritionModel({
+ required double energy,
+ required double proteins,
+ required double fats,
+ required double carbohydrates,
+ }) = _NutritionModel;
+
+ factory NutritionModel.fromJson(Map json) => _$NutritionModelFromJson(json);
+}
diff --git a/lib/features/diary/models/nutrition_model.freezed.dart b/lib/features/diary/models/nutrition_model.freezed.dart
new file mode 100644
index 00000000..46da50b5
--- /dev/null
+++ b/lib/features/diary/models/nutrition_model.freezed.dart
@@ -0,0 +1,437 @@
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
+
+part of 'nutrition_model.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+
+NutritionDataModel _$NutritionDataModelFromJson(Map json) {
+ return _NutritionDataModel.fromJson(json);
+}
+
+/// @nodoc
+mixin _$NutritionDataModel {
+ NutritionModel get dailyNutritionIntake => throw _privateConstructorUsedError;
+ NutritionModel get dailyNutritionRecommendation =>
+ throw _privateConstructorUsedError;
+
+ /// Serializes this NutritionDataModel to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of NutritionDataModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $NutritionDataModelCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $NutritionDataModelCopyWith<$Res> {
+ factory $NutritionDataModelCopyWith(
+ NutritionDataModel value, $Res Function(NutritionDataModel) then) =
+ _$NutritionDataModelCopyWithImpl<$Res, NutritionDataModel>;
+ @useResult
+ $Res call(
+ {NutritionModel dailyNutritionIntake,
+ NutritionModel dailyNutritionRecommendation});
+
+ $NutritionModelCopyWith<$Res> get dailyNutritionIntake;
+ $NutritionModelCopyWith<$Res> get dailyNutritionRecommendation;
+}
+
+/// @nodoc
+class _$NutritionDataModelCopyWithImpl<$Res, $Val extends NutritionDataModel>
+ implements $NutritionDataModelCopyWith<$Res> {
+ _$NutritionDataModelCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of NutritionDataModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? dailyNutritionIntake = null,
+ Object? dailyNutritionRecommendation = null,
+ }) {
+ return _then(_value.copyWith(
+ dailyNutritionIntake: null == dailyNutritionIntake
+ ? _value.dailyNutritionIntake
+ : dailyNutritionIntake // ignore: cast_nullable_to_non_nullable
+ as NutritionModel,
+ dailyNutritionRecommendation: null == dailyNutritionRecommendation
+ ? _value.dailyNutritionRecommendation
+ : dailyNutritionRecommendation // ignore: cast_nullable_to_non_nullable
+ as NutritionModel,
+ ) as $Val);
+ }
+
+ /// Create a copy of NutritionDataModel
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @pragma('vm:prefer-inline')
+ $NutritionModelCopyWith<$Res> get dailyNutritionIntake {
+ return $NutritionModelCopyWith<$Res>(_value.dailyNutritionIntake, (value) {
+ return _then(_value.copyWith(dailyNutritionIntake: value) as $Val);
+ });
+ }
+
+ /// Create a copy of NutritionDataModel
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @pragma('vm:prefer-inline')
+ $NutritionModelCopyWith<$Res> get dailyNutritionRecommendation {
+ return $NutritionModelCopyWith<$Res>(_value.dailyNutritionRecommendation,
+ (value) {
+ return _then(
+ _value.copyWith(dailyNutritionRecommendation: value) as $Val);
+ });
+ }
+}
+
+/// @nodoc
+abstract class _$$NutritionDataModelImplCopyWith<$Res>
+ implements $NutritionDataModelCopyWith<$Res> {
+ factory _$$NutritionDataModelImplCopyWith(_$NutritionDataModelImpl value,
+ $Res Function(_$NutritionDataModelImpl) then) =
+ __$$NutritionDataModelImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call(
+ {NutritionModel dailyNutritionIntake,
+ NutritionModel dailyNutritionRecommendation});
+
+ @override
+ $NutritionModelCopyWith<$Res> get dailyNutritionIntake;
+ @override
+ $NutritionModelCopyWith<$Res> get dailyNutritionRecommendation;
+}
+
+/// @nodoc
+class __$$NutritionDataModelImplCopyWithImpl<$Res>
+ extends _$NutritionDataModelCopyWithImpl<$Res, _$NutritionDataModelImpl>
+ implements _$$NutritionDataModelImplCopyWith<$Res> {
+ __$$NutritionDataModelImplCopyWithImpl(_$NutritionDataModelImpl _value,
+ $Res Function(_$NutritionDataModelImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of NutritionDataModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? dailyNutritionIntake = null,
+ Object? dailyNutritionRecommendation = null,
+ }) {
+ return _then(_$NutritionDataModelImpl(
+ dailyNutritionIntake: null == dailyNutritionIntake
+ ? _value.dailyNutritionIntake
+ : dailyNutritionIntake // ignore: cast_nullable_to_non_nullable
+ as NutritionModel,
+ dailyNutritionRecommendation: null == dailyNutritionRecommendation
+ ? _value.dailyNutritionRecommendation
+ : dailyNutritionRecommendation // ignore: cast_nullable_to_non_nullable
+ as NutritionModel,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$NutritionDataModelImpl implements _NutritionDataModel {
+ const _$NutritionDataModelImpl(
+ {required this.dailyNutritionIntake,
+ required this.dailyNutritionRecommendation});
+
+ factory _$NutritionDataModelImpl.fromJson(Map json) =>
+ _$$NutritionDataModelImplFromJson(json);
+
+ @override
+ final NutritionModel dailyNutritionIntake;
+ @override
+ final NutritionModel dailyNutritionRecommendation;
+
+ @override
+ String toString() {
+ return 'NutritionDataModel(dailyNutritionIntake: $dailyNutritionIntake, dailyNutritionRecommendation: $dailyNutritionRecommendation)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$NutritionDataModelImpl &&
+ (identical(other.dailyNutritionIntake, dailyNutritionIntake) ||
+ other.dailyNutritionIntake == dailyNutritionIntake) &&
+ (identical(other.dailyNutritionRecommendation,
+ dailyNutritionRecommendation) ||
+ other.dailyNutritionRecommendation ==
+ dailyNutritionRecommendation));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode => Object.hash(
+ runtimeType, dailyNutritionIntake, dailyNutritionRecommendation);
+
+ /// Create a copy of NutritionDataModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$NutritionDataModelImplCopyWith<_$NutritionDataModelImpl> get copyWith =>
+ __$$NutritionDataModelImplCopyWithImpl<_$NutritionDataModelImpl>(
+ this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$NutritionDataModelImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _NutritionDataModel implements NutritionDataModel {
+ const factory _NutritionDataModel(
+ {required final NutritionModel dailyNutritionIntake,
+ required final NutritionModel dailyNutritionRecommendation}) =
+ _$NutritionDataModelImpl;
+
+ factory _NutritionDataModel.fromJson(Map json) =
+ _$NutritionDataModelImpl.fromJson;
+
+ @override
+ NutritionModel get dailyNutritionIntake;
+ @override
+ NutritionModel get dailyNutritionRecommendation;
+
+ /// Create a copy of NutritionDataModel
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$NutritionDataModelImplCopyWith<_$NutritionDataModelImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+NutritionModel _$NutritionModelFromJson(Map json) {
+ return _NutritionModel.fromJson(json);
+}
+
+/// @nodoc
+mixin _$NutritionModel {
+ double get energy => throw _privateConstructorUsedError;
+ double get proteins => throw _privateConstructorUsedError;
+ double get fats => throw _privateConstructorUsedError;
+ double get carbohydrates => throw _privateConstructorUsedError;
+
+ /// Serializes this NutritionModel to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of NutritionModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $NutritionModelCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $NutritionModelCopyWith<$Res> {
+ factory $NutritionModelCopyWith(
+ NutritionModel value, $Res Function(NutritionModel) then) =
+ _$NutritionModelCopyWithImpl<$Res, NutritionModel>;
+ @useResult
+ $Res call(
+ {double energy, double proteins, double fats, double carbohydrates});
+}
+
+/// @nodoc
+class _$NutritionModelCopyWithImpl<$Res, $Val extends NutritionModel>
+ implements $NutritionModelCopyWith<$Res> {
+ _$NutritionModelCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of NutritionModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? energy = null,
+ Object? proteins = null,
+ Object? fats = null,
+ Object? carbohydrates = null,
+ }) {
+ return _then(_value.copyWith(
+ energy: null == energy
+ ? _value.energy
+ : energy // ignore: cast_nullable_to_non_nullable
+ as double,
+ proteins: null == proteins
+ ? _value.proteins
+ : proteins // ignore: cast_nullable_to_non_nullable
+ as double,
+ fats: null == fats
+ ? _value.fats
+ : fats // ignore: cast_nullable_to_non_nullable
+ as double,
+ carbohydrates: null == carbohydrates
+ ? _value.carbohydrates
+ : carbohydrates // ignore: cast_nullable_to_non_nullable
+ as double,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$NutritionModelImplCopyWith<$Res>
+ implements $NutritionModelCopyWith<$Res> {
+ factory _$$NutritionModelImplCopyWith(_$NutritionModelImpl value,
+ $Res Function(_$NutritionModelImpl) then) =
+ __$$NutritionModelImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call(
+ {double energy, double proteins, double fats, double carbohydrates});
+}
+
+/// @nodoc
+class __$$NutritionModelImplCopyWithImpl<$Res>
+ extends _$NutritionModelCopyWithImpl<$Res, _$NutritionModelImpl>
+ implements _$$NutritionModelImplCopyWith<$Res> {
+ __$$NutritionModelImplCopyWithImpl(
+ _$NutritionModelImpl _value, $Res Function(_$NutritionModelImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of NutritionModel
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? energy = null,
+ Object? proteins = null,
+ Object? fats = null,
+ Object? carbohydrates = null,
+ }) {
+ return _then(_$NutritionModelImpl(
+ energy: null == energy
+ ? _value.energy
+ : energy // ignore: cast_nullable_to_non_nullable
+ as double,
+ proteins: null == proteins
+ ? _value.proteins
+ : proteins // ignore: cast_nullable_to_non_nullable
+ as double,
+ fats: null == fats
+ ? _value.fats
+ : fats // ignore: cast_nullable_to_non_nullable
+ as double,
+ carbohydrates: null == carbohydrates
+ ? _value.carbohydrates
+ : carbohydrates // ignore: cast_nullable_to_non_nullable
+ as double,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$NutritionModelImpl implements _NutritionModel {
+ const _$NutritionModelImpl(
+ {required this.energy,
+ required this.proteins,
+ required this.fats,
+ required this.carbohydrates});
+
+ factory _$NutritionModelImpl.fromJson(Map json) =>
+ _$$NutritionModelImplFromJson(json);
+
+ @override
+ final double energy;
+ @override
+ final double proteins;
+ @override
+ final double fats;
+ @override
+ final double carbohydrates;
+
+ @override
+ String toString() {
+ return 'NutritionModel(energy: $energy, proteins: $proteins, fats: $fats, carbohydrates: $carbohydrates)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$NutritionModelImpl &&
+ (identical(other.energy, energy) || other.energy == energy) &&
+ (identical(other.proteins, proteins) ||
+ other.proteins == proteins) &&
+ (identical(other.fats, fats) || other.fats == fats) &&
+ (identical(other.carbohydrates, carbohydrates) ||
+ other.carbohydrates == carbohydrates));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode =>
+ Object.hash(runtimeType, energy, proteins, fats, carbohydrates);
+
+ /// Create a copy of NutritionModel
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$NutritionModelImplCopyWith<_$NutritionModelImpl> get copyWith =>
+ __$$NutritionModelImplCopyWithImpl<_$NutritionModelImpl>(
+ this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$NutritionModelImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _NutritionModel implements NutritionModel {
+ const factory _NutritionModel(
+ {required final double energy,
+ required final double proteins,
+ required final double fats,
+ required final double carbohydrates}) = _$NutritionModelImpl;
+
+ factory _NutritionModel.fromJson(Map json) =
+ _$NutritionModelImpl.fromJson;
+
+ @override
+ double get energy;
+ @override
+ double get proteins;
+ @override
+ double get fats;
+ @override
+ double get carbohydrates;
+
+ /// Create a copy of NutritionModel
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$NutritionModelImplCopyWith<_$NutritionModelImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/lib/features/diary/models/nutrition_model.g.dart b/lib/features/diary/models/nutrition_model.g.dart
new file mode 100644
index 00000000..6ecd533c
--- /dev/null
+++ b/lib/features/diary/models/nutrition_model.g.dart
@@ -0,0 +1,40 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'nutrition_model.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+_$NutritionDataModelImpl _$$NutritionDataModelImplFromJson(
+ Map json) =>
+ _$NutritionDataModelImpl(
+ dailyNutritionIntake: NutritionModel.fromJson(
+ json['dailyNutritionIntake'] as Map),
+ dailyNutritionRecommendation: NutritionModel.fromJson(
+ json['dailyNutritionRecommendation'] as Map),
+ );
+
+Map _$$NutritionDataModelImplToJson(
+ _$NutritionDataModelImpl instance) =>
+ {
+ 'dailyNutritionIntake': instance.dailyNutritionIntake,
+ 'dailyNutritionRecommendation': instance.dailyNutritionRecommendation,
+ };
+
+_$NutritionModelImpl _$$NutritionModelImplFromJson(Map json) =>
+ _$NutritionModelImpl(
+ energy: (json['energy'] as num).toDouble(),
+ proteins: (json['proteins'] as num).toDouble(),
+ fats: (json['fats'] as num).toDouble(),
+ carbohydrates: (json['carbohydrates'] as num).toDouble(),
+ );
+
+Map _$$NutritionModelImplToJson(
+ _$NutritionModelImpl instance) =>
+ {
+ 'energy': instance.energy,
+ 'proteins': instance.proteins,
+ 'fats': instance.fats,
+ 'carbohydrates': instance.carbohydrates,
+ };
diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart
index d7f31da6..16df5d4f 100644
--- a/lib/features/diary/presentation/bloc/diary_cubit.dart
+++ b/lib/features/diary/presentation/bloc/diary_cubit.dart
@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
+import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
import 'package:eatvio_app/features/diary/repositories/diary_repository.dart';
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
@@ -10,6 +11,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
class DiaryCubit extends Cubit {
final DiaryRepository diaryRepository;
final GlossaryRepository glossaryRepository;
+ double currentSelectedSatietyRating = 3;
DiaryCubit({
required this.diaryRepository,
@@ -19,13 +21,23 @@ class DiaryCubit extends Cubit {
getDiaryDay(String date, [bool needLoading = true]) async {
if (needLoading) emit(const DiaryState.loading());
try {
- emit(DiaryState.loaded(diary: await diaryRepository.getDiaryByDate(date)));
+ emit(
+ DiaryState.loaded(
+ diary: await diaryRepository.getDiaryByDate(date),
+ nutrition: await getNutrition(await diaryRepository.getDiaryIndexByDate(date)),
+ currentSelectedSatietyRating: currentSelectedSatietyRating,
+ ),
+ );
} catch (e) {
emit(DiaryState.error(errorMessage: e.toString()));
throw e;
}
}
+ Future getNutrition(int index) async {
+ return (await diaryRepository.getNutritionData(index));
+ }
+
addProductToMeal({
required int diaryId,
required int mealId,
@@ -109,4 +121,9 @@ class DiaryCubit extends Cubit {
);
await getDiaryDay(date, false);
}
+
+ updateCurrentSelectedSatietyRating(double value) {
+ currentSelectedSatietyRating = value;
+ emit((state as DiaryStateLoaded).copyWith(currentSelectedSatietyRating: currentSelectedSatietyRating));
+ }
}
diff --git a/lib/features/diary/presentation/bloc/diary_state.dart b/lib/features/diary/presentation/bloc/diary_state.dart
index c9b58a8d..001c9d16 100644
--- a/lib/features/diary/presentation/bloc/diary_state.dart
+++ b/lib/features/diary/presentation/bloc/diary_state.dart
@@ -1,4 +1,5 @@
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
+import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'diary_state.freezed.dart';
@@ -15,5 +16,7 @@ class DiaryState with _$DiaryState {
const factory DiaryState.loaded({
required DiaryDayModel diary,
+ required NutritionDataModel nutrition,
+ required double currentSelectedSatietyRating,
}) = DiaryStateLoaded;
}
diff --git a/lib/features/diary/presentation/bloc/diary_state.freezed.dart b/lib/features/diary/presentation/bloc/diary_state.freezed.dart
index 513d217d..58fa1471 100644
--- a/lib/features/diary/presentation/bloc/diary_state.freezed.dart
+++ b/lib/features/diary/presentation/bloc/diary_state.freezed.dart
@@ -21,7 +21,9 @@ mixin _$DiaryState {
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(String errorMessage) error,
- required TResult Function(DiaryDayModel diary) loaded,
+ required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
+ double currentSelectedSatietyRating)
+ loaded,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
@@ -29,7 +31,9 @@ mixin _$DiaryState {
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(String errorMessage)? error,
- TResult? Function(DiaryDayModel diary)? loaded,
+ TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition,
+ double currentSelectedSatietyRating)?
+ loaded,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
@@ -37,7 +41,9 @@ mixin _$DiaryState {
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(String errorMessage)? error,
- TResult Function(DiaryDayModel diary)? loaded,
+ TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
+ double currentSelectedSatietyRating)?
+ loaded,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@@ -133,7 +139,9 @@ class _$DiaryStateImpl implements _DiaryState {
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(String errorMessage) error,
- required TResult Function(DiaryDayModel diary) loaded,
+ required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
+ double currentSelectedSatietyRating)
+ loaded,
}) {
return $default();
}
@@ -144,7 +152,9 @@ class _$DiaryStateImpl implements _DiaryState {
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(String errorMessage)? error,
- TResult? Function(DiaryDayModel diary)? loaded,
+ TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition,
+ double currentSelectedSatietyRating)?
+ loaded,
}) {
return $default?.call();
}
@@ -155,7 +165,9 @@ class _$DiaryStateImpl implements _DiaryState {
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(String errorMessage)? error,
- TResult Function(DiaryDayModel diary)? loaded,
+ TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
+ double currentSelectedSatietyRating)?
+ loaded,
required TResult orElse(),
}) {
if ($default != null) {
@@ -250,7 +262,9 @@ class _$DiaryStateLoadingImpl implements DiaryStateLoading {
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(String errorMessage) error,
- required TResult Function(DiaryDayModel diary) loaded,
+ required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
+ double currentSelectedSatietyRating)
+ loaded,
}) {
return loading();
}
@@ -261,7 +275,9 @@ class _$DiaryStateLoadingImpl implements DiaryStateLoading {
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(String errorMessage)? error,
- TResult? Function(DiaryDayModel diary)? loaded,
+ TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition,
+ double currentSelectedSatietyRating)?
+ loaded,
}) {
return loading?.call();
}
@@ -272,7 +288,9 @@ class _$DiaryStateLoadingImpl implements DiaryStateLoading {
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(String errorMessage)? error,
- TResult Function(DiaryDayModel diary)? loaded,
+ TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
+ double currentSelectedSatietyRating)?
+ loaded,
required TResult orElse(),
}) {
if (loading != null) {
@@ -396,7 +414,9 @@ class _$DiaryStateErrorImpl implements DiaryStateError {
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(String errorMessage) error,
- required TResult Function(DiaryDayModel diary) loaded,
+ required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
+ double currentSelectedSatietyRating)
+ loaded,
}) {
return error(errorMessage);
}
@@ -407,7 +427,9 @@ class _$DiaryStateErrorImpl implements DiaryStateError {
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(String errorMessage)? error,
- TResult? Function(DiaryDayModel diary)? loaded,
+ TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition,
+ double currentSelectedSatietyRating)?
+ loaded,
}) {
return error?.call(errorMessage);
}
@@ -418,7 +440,9 @@ class _$DiaryStateErrorImpl implements DiaryStateError {
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(String errorMessage)? error,
- TResult Function(DiaryDayModel diary)? loaded,
+ TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
+ double currentSelectedSatietyRating)?
+ loaded,
required TResult orElse(),
}) {
if (error != null) {
@@ -484,9 +508,13 @@ abstract class _$$DiaryStateLoadedImplCopyWith<$Res> {
$Res Function(_$DiaryStateLoadedImpl) then) =
__$$DiaryStateLoadedImplCopyWithImpl<$Res>;
@useResult
- $Res call({DiaryDayModel diary});
+ $Res call(
+ {DiaryDayModel diary,
+ NutritionDataModel nutrition,
+ double currentSelectedSatietyRating});
$DiaryDayModelCopyWith<$Res> get diary;
+ $NutritionDataModelCopyWith<$Res> get nutrition;
}
/// @nodoc
@@ -503,12 +531,22 @@ class __$$DiaryStateLoadedImplCopyWithImpl<$Res>
@override
$Res call({
Object? diary = null,
+ Object? nutrition = null,
+ Object? currentSelectedSatietyRating = null,
}) {
return _then(_$DiaryStateLoadedImpl(
diary: null == diary
? _value.diary
: diary // ignore: cast_nullable_to_non_nullable
as DiaryDayModel,
+ nutrition: null == nutrition
+ ? _value.nutrition
+ : nutrition // ignore: cast_nullable_to_non_nullable
+ as NutritionDataModel,
+ currentSelectedSatietyRating: null == currentSelectedSatietyRating
+ ? _value.currentSelectedSatietyRating
+ : currentSelectedSatietyRating // ignore: cast_nullable_to_non_nullable
+ as double,
));
}
@@ -521,19 +559,36 @@ class __$$DiaryStateLoadedImplCopyWithImpl<$Res>
return _then(_value.copyWith(diary: value));
});
}
+
+ /// Create a copy of DiaryState
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @pragma('vm:prefer-inline')
+ $NutritionDataModelCopyWith<$Res> get nutrition {
+ return $NutritionDataModelCopyWith<$Res>(_value.nutrition, (value) {
+ return _then(_value.copyWith(nutrition: value));
+ });
+ }
}
/// @nodoc
class _$DiaryStateLoadedImpl implements DiaryStateLoaded {
- const _$DiaryStateLoadedImpl({required this.diary});
+ const _$DiaryStateLoadedImpl(
+ {required this.diary,
+ required this.nutrition,
+ required this.currentSelectedSatietyRating});
@override
final DiaryDayModel diary;
+ @override
+ final NutritionDataModel nutrition;
+ @override
+ final double currentSelectedSatietyRating;
@override
String toString() {
- return 'DiaryState.loaded(diary: $diary)';
+ return 'DiaryState.loaded(diary: $diary, nutrition: $nutrition, currentSelectedSatietyRating: $currentSelectedSatietyRating)';
}
@override
@@ -541,11 +596,18 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$DiaryStateLoadedImpl &&
- (identical(other.diary, diary) || other.diary == diary));
+ (identical(other.diary, diary) || other.diary == diary) &&
+ (identical(other.nutrition, nutrition) ||
+ other.nutrition == nutrition) &&
+ (identical(other.currentSelectedSatietyRating,
+ currentSelectedSatietyRating) ||
+ other.currentSelectedSatietyRating ==
+ currentSelectedSatietyRating));
}
@override
- int get hashCode => Object.hash(runtimeType, diary);
+ int get hashCode =>
+ Object.hash(runtimeType, diary, nutrition, currentSelectedSatietyRating);
/// Create a copy of DiaryState
/// with the given fields replaced by the non-null parameter values.
@@ -562,9 +624,11 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded {
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(String errorMessage) error,
- required TResult Function(DiaryDayModel diary) loaded,
+ required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
+ double currentSelectedSatietyRating)
+ loaded,
}) {
- return loaded(diary);
+ return loaded(diary, nutrition, currentSelectedSatietyRating);
}
@override
@@ -573,9 +637,11 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded {
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(String errorMessage)? error,
- TResult? Function(DiaryDayModel diary)? loaded,
+ TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition,
+ double currentSelectedSatietyRating)?
+ loaded,
}) {
- return loaded?.call(diary);
+ return loaded?.call(diary, nutrition, currentSelectedSatietyRating);
}
@override
@@ -584,11 +650,13 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded {
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(String errorMessage)? error,
- TResult Function(DiaryDayModel diary)? loaded,
+ TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
+ double currentSelectedSatietyRating)?
+ loaded,
required TResult orElse(),
}) {
if (loaded != null) {
- return loaded(diary);
+ return loaded(diary, nutrition, currentSelectedSatietyRating);
}
return orElse();
}
@@ -632,10 +700,15 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded {
}
abstract class DiaryStateLoaded implements DiaryState {
- const factory DiaryStateLoaded({required final DiaryDayModel diary}) =
+ const factory DiaryStateLoaded(
+ {required final DiaryDayModel diary,
+ required final NutritionDataModel nutrition,
+ required final double currentSelectedSatietyRating}) =
_$DiaryStateLoadedImpl;
DiaryDayModel get diary;
+ NutritionDataModel get nutrition;
+ double get currentSelectedSatietyRating;
/// Create a copy of DiaryState
/// with the given fields replaced by the non-null parameter values.
diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart
index bb9e530a..79f9ac2f 100644
--- a/lib/features/diary/presentation/pages/diary_screen.dart
+++ b/lib/features/diary/presentation/pages/diary_screen.dart
@@ -2,8 +2,11 @@ import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
+import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
+import 'package:eatvio_app/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart';
+import 'package:eatvio_app/features/diary/presentation/widgets/satiety_rating_widget.dart';
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
import 'package:eatvio_app/locator_service.dart';
import 'package:eatvio_app/theme/theme_helper.dart';
@@ -15,6 +18,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:image_picker/image_picker.dart';
import 'package:rive/rive.dart';
+import 'package:syncfusion_flutter_gauges/gauges.dart';
import 'package:table_calendar/table_calendar.dart';
class DiaryScreen extends StatefulWidget {
@@ -34,6 +38,65 @@ class _DiaryScreenState extends State {
final productNameController = TextEditingController();
final quantityController = TextEditingController();
int currentSelectedPhotoIndex = 0;
+ String time = "${DateTime.now().hour.toString().padLeft(2, '0')}:${DateTime.now().minute.toString().padLeft(2, '0')}";
+ DateTime _time = DateTime.now();
+
+ _showDatePicker() {
+ showModalBottomSheet(
+ context: context,
+ useRootNavigator: true,
+ builder: (context) => SizedBox(
+ height: MediaQuery.of(context).copyWith().size.height / 3,
+ child: Column(
+ children: [
+ Expanded(
+ child: CupertinoDatePicker(
+ mode: CupertinoDatePickerMode.time,
+ use24hFormat: true,
+ initialDateTime: DateTime.now(),
+ onDateTimeChanged: (newTime) {
+ setState(() {
+ _time = newTime;
+ time = "${_time.hour.toString().padLeft(2, '0')}:${_time.minute.toString().padLeft(2, '0')}";
+ });
+ },
+ ),
+ ),
+ InkWell(
+ onTap: () {
+ Navigator.of(context).pop();
+ showModalBottomSheet(
+ useRootNavigator: true,
+ useSafeArea: false,
+ context: context,
+ builder: (context) => GlossaryModerationRequestModalWidget(),
+ );
+ },
+ borderRadius: BorderRadius.circular(20),
+ child: Ink(
+ width: MediaQuery.of(context).size.width - 16,
+ height: 52,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(20),
+ color: appTheme.blueA400,
+ ),
+ child: Center(
+ child: Text(
+ 'Сохранить',
+ style: TextStyle(
+ fontSize: 18,
+ fontWeight: FontWeight.w600,
+ color: Colors.white,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
@override
Widget build(BuildContext context) {
@@ -59,7 +122,12 @@ class _DiaryScreenState extends State {
children: [
_calendar(state),
if (state is DiaryStateLoading) _loader(),
- if (state is DiaryStateLoaded) _meals(state.diary),
+ if (state is DiaryStateLoaded)
+ _meals(
+ state.diary,
+ state.currentSelectedSatietyRating,
+ ),
+ if (state is DiaryStateLoaded) _chart(state.nutrition),
const SizedBox(height: 32),
],
),
@@ -70,13 +138,47 @@ class _DiaryScreenState extends State {
);
}
+ _timeOfMeal(String time) {
+ return InkWell(
+ onTap: () => _showDatePicker(),
+ borderRadius: BorderRadius.circular(100),
+ child: Ink(
+ height: 32,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(100),
+ color: Color(0xFFF1F2F6),
+ ),
+ child: Container(
+ alignment: Alignment.center,
+ padding: EdgeInsets.symmetric(horizontal: 16),
+ child: Text(
+ time,
+ style: TextStyle(
+ color: Color(0xFF6D6F71),
+ fontSize: 17,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
_arrowDown() {
- return Container(
- padding: const EdgeInsets.only(top: 8, bottom: 16),
- width: double.infinity,
- child: Icon(
- _calendarFormat == CalendarFormat.month ? Icons.keyboard_arrow_up_rounded : Icons.keyboard_arrow_down_rounded,
- color: Colors.grey.shade600,
+ return GestureDetector(
+ behavior: HitTestBehavior.translucent,
+ onVerticalDragStart: (_) {
+ setState(() {
+ _calendarFormat = CalendarFormat.values[(_calendarFormat.index - 1) % CalendarFormat.values.length];
+ });
+ },
+ child: Container(
+ width: double.infinity,
+ padding: const EdgeInsets.only(top: 8, bottom: 16),
+ child: Icon(
+ _calendarFormat == CalendarFormat.month ? Icons.keyboard_arrow_up_rounded : Icons.keyboard_arrow_down_rounded,
+ color: Colors.grey.shade600,
+ ),
),
);
}
@@ -130,7 +232,10 @@ class _DiaryScreenState extends State {
);
}
- _meals(DiaryDayModel diaryDay) {
+ _meals(
+ DiaryDayModel diaryDay,
+ double currentSelectedSatietyRating,
+ ) {
return Material(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
@@ -161,13 +266,19 @@ class _DiaryScreenState extends State {
const SizedBox(height: 12),
_divider(context),
const SizedBox(height: 12),
- Text(
- 'Время приема пищи',
- style: TextStyle(
- color: Color(0xFF323F4B),
- fontSize: 16,
- fontWeight: FontWeight.w500,
- ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ 'Время приема пищи',
+ style: TextStyle(
+ color: Color(0xFF323F4B),
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ _timeOfMeal(time),
+ ],
),
const SizedBox(height: 12),
_divider(context),
@@ -189,12 +300,254 @@ class _DiaryScreenState extends State {
diaryDay.meals[currentSelectedMealNumber - 1].photos,
diaryDay,
),
+ SatietyRatingWidget(
+ currentSelectedSatietyRating: currentSelectedSatietyRating,
+ ),
],
),
),
);
}
+ _chart(NutritionDataModel nutrition) {
+ final List chartData = [
+ ChartData(
+ nutrition.dailyNutritionIntake.carbohydrates,
+ nutrition.dailyNutritionRecommendation.carbohydrates,
+ ),
+ ChartData(
+ nutrition.dailyNutritionIntake.fats,
+ nutrition.dailyNutritionRecommendation.fats,
+ ),
+ ChartData(
+ nutrition.dailyNutritionIntake.proteins,
+ nutrition.dailyNutritionRecommendation.proteins,
+ ),
+ ChartData(
+ nutrition.dailyNutritionIntake.energy,
+ nutrition.dailyNutritionRecommendation.energy,
+ ),
+ ];
+
+ return Container(
+ padding: const EdgeInsets.all(16),
+ margin: const EdgeInsets.only(top: 12),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(20),
+ color: Colors.white,
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Text(
+ 'Ваш КБЖУ от дневной\nсуточной нормы',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: Color(0xFF323F4B),
+ fontWeight: FontWeight.w500,
+ fontSize: 18,
+ height: 1,
+ ),
+ ),
+ const SizedBox(height: 48),
+ Stack(
+ alignment: Alignment.bottomCenter,
+ children: [
+ SfRadialGauge(
+ axes: [
+ RadialAxis(
+ startAngle: 180,
+ endAngle: 0,
+ minimum: 0,
+ maximum: 100,
+ showLabels: false,
+ showTicks: false,
+ radiusFactor: 0.7,
+ showAxisLine: false,
+ pointers: [
+ RangePointer(
+ value: 100,
+ width: 0.16,
+ animationDuration: 2000,
+ color: Color(0xFFF6FAFF),
+ sizeUnit: GaugeSizeUnit.factor,
+ enableAnimation: true,
+ cornerStyle: CornerStyle.bothCurve,
+ ),
+ RangePointer(
+ value: (chartData[0].intake / chartData[0].recommendation) * 100,
+ width: 0.16,
+ color: chartData[0].intake < chartData[0].recommendation ? appTheme.blueA400 : Colors.red,
+ sizeUnit: GaugeSizeUnit.factor,
+ enableAnimation: true,
+ cornerStyle: CornerStyle.bothCurve,
+ ),
+ RangePointer(
+ value: 100,
+ width: 0.16,
+ animationDuration: 2000,
+ color: Color(0xFFF6FAFF),
+ sizeUnit: GaugeSizeUnit.factor,
+ enableAnimation: true,
+ cornerStyle: CornerStyle.bothCurve,
+ pointerOffset: -0.18,
+ ),
+ RangePointer(
+ value: (chartData[1].intake / chartData[1].recommendation) * 100,
+ width: 0.16,
+ color: chartData[1].intake < chartData[1].recommendation ? appTheme.blueA400 : Colors.red,
+ sizeUnit: GaugeSizeUnit.factor,
+ enableAnimation: true,
+ cornerStyle: CornerStyle.bothCurve,
+ pointerOffset: -0.18,
+ ),
+ RangePointer(
+ value: 100,
+ width: 0.16,
+ animationDuration: 2000,
+ color: Color(0xFFF6FAFF),
+ sizeUnit: GaugeSizeUnit.factor,
+ enableAnimation: true,
+ cornerStyle: CornerStyle.bothCurve,
+ pointerOffset: -0.36,
+ ),
+ RangePointer(
+ value: (chartData[2].intake / chartData[2].recommendation) * 100,
+ width: 0.16,
+ color: chartData[2].intake < chartData[2].recommendation ? appTheme.blueA400 : Colors.red,
+ sizeUnit: GaugeSizeUnit.factor,
+ enableAnimation: true,
+ cornerStyle: CornerStyle.bothCurve,
+ pointerOffset: -0.36,
+ ),
+ RangePointer(
+ value: 100,
+ width: 0.16,
+ animationDuration: 2000,
+ color: Color(0xFFF6FAFF),
+ sizeUnit: GaugeSizeUnit.factor,
+ enableAnimation: true,
+ cornerStyle: CornerStyle.bothCurve,
+ pointerOffset: -0.54,
+ ),
+ RangePointer(
+ value: (chartData[3].intake / chartData[3].recommendation) * 100,
+ width: 0.16,
+ color: chartData[3].intake < chartData[3].recommendation ? appTheme.blueA400 : Colors.red,
+ sizeUnit: GaugeSizeUnit.factor,
+ enableAnimation: true,
+ cornerStyle: CornerStyle.bothCurve,
+ pointerOffset: -0.54,
+ ),
+ ],
+ annotations: [
+ GaugeAnnotation(
+ widget: Transform.rotate(
+ angle: 30.1,
+ child: Text(
+ 'Углеводы',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 12,
+ ),
+ ),
+ ),
+ angle: 198,
+ positionFactor: 0.92,
+ ),
+ GaugeAnnotation(
+ widget: Transform.rotate(
+ angle: 30,
+ child: Text(
+ 'Жиры',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 12,
+ ),
+ ),
+ ),
+ angle: 190,
+ positionFactor: 1.1,
+ ),
+ GaugeAnnotation(
+ widget: Transform.rotate(
+ angle: 30,
+ child: Text(
+ 'Белки',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 12,
+ ),
+ ),
+ ),
+ angle: 190,
+ positionFactor: 1.27,
+ ),
+ GaugeAnnotation(
+ widget: Transform.rotate(
+ angle: 30,
+ child: Text(
+ 'Калории',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 12,
+ ),
+ ),
+ ),
+ angle: 192,
+ positionFactor: 1.46,
+ ),
+ ],
+ ),
+ ],
+ ),
+ Column(
+ children: [
+ _chartData('Калории',
+ '${nutrition.dailyNutritionIntake.energy.toStringAsFixed(0)} из ${nutrition.dailyNutritionRecommendation.energy.toStringAsFixed(0)} ккал'),
+ const SizedBox(height: 8),
+ _chartData('Белки',
+ '${nutrition.dailyNutritionIntake.proteins.toStringAsFixed(0)} из ${nutrition.dailyNutritionRecommendation.proteins.toStringAsFixed(0)} грамм'),
+ const SizedBox(height: 8),
+ _chartData('Жиры',
+ '${nutrition.dailyNutritionIntake.fats.toStringAsFixed(0)} из ${nutrition.dailyNutritionRecommendation.fats.toStringAsFixed(0)} грамм'),
+ const SizedBox(height: 8),
+ _chartData('Углеводы',
+ '${nutrition.dailyNutritionIntake.carbohydrates.toStringAsFixed(0)} из ${nutrition.dailyNutritionRecommendation.carbohydrates.toStringAsFixed(0)} грамм'),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ),
+ );
+ }
+
+ _chartData(String title, String value) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ title,
+ style: TextStyle(
+ fontSize: 18,
+ fontWeight: FontWeight.w500,
+ color: Color(0xFF323F4B),
+ ),
+ ),
+ Text(
+ value,
+ style: TextStyle(
+ fontSize: 18,
+ fontWeight: FontWeight.w500,
+ color: Color(0xFF323F4B),
+ ),
+ ),
+ ],
+ );
+ }
+
_showImagePicker(DiaryDayModel diaryDay) {
showCupertinoModalPopup(
context: context,
@@ -271,19 +624,20 @@ class _DiaryScreenState extends State {
child: IconButton.filled(
style: IconButton.styleFrom(
backgroundColor: Colors.white,
+ elevation: 0,
),
- onPressed: () {
- setState(() {
- if (currentSelectedPhotoIndex == photos.length - 1 && currentSelectedPhotoIndex != 0) {
- currentSelectedPhotoIndex = photos.length - 2;
- }
- });
- diaryCubit.deletePhoto(
+ onPressed: () async {
+ await diaryCubit.deletePhoto(
photo: photos[currentSelectedPhotoIndex],
diaryId: diaryDay.id,
mealId: diaryDay.meals[currentSelectedMealNumber - 1].id,
date: _selectedDay.toString(),
);
+ setState(() {
+ if (currentSelectedPhotoIndex == photos.length - 1 && currentSelectedPhotoIndex != 0) {
+ currentSelectedPhotoIndex = photos.length - 2;
+ }
+ });
},
icon: Icon(
Icons.close_rounded,
@@ -372,7 +726,7 @@ class _DiaryScreenState extends State {
child: Text(
'$mealNumber',
style: TextStyle(
- color: isActive ? Colors.white : Colors.black,
+ color: isActive ? Colors.white : Color(0xFF6D6F71),
fontSize: 16,
fontWeight: FontWeight.w600,
),
@@ -669,3 +1023,13 @@ class _DiaryScreenState extends State {
);
}
}
+
+class ChartData {
+ final double intake;
+ final double recommendation;
+
+ ChartData(
+ this.intake,
+ this.recommendation,
+ );
+}
diff --git a/lib/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart b/lib/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart
new file mode 100644
index 00000000..c51a4cb7
--- /dev/null
+++ b/lib/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart
@@ -0,0 +1,17 @@
+import 'package:flutter/material.dart';
+
+class GlossaryModerationRequestModalWidget extends StatefulWidget {
+ const GlossaryModerationRequestModalWidget({super.key});
+
+ @override
+ State createState() => _GlossaryModerationRequestModalWidgetState();
+}
+
+class _GlossaryModerationRequestModalWidgetState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return Dialog(
+ child: Column(),
+ );
+ }
+}
diff --git a/lib/features/diary/presentation/widgets/satiety_rating_widget.dart b/lib/features/diary/presentation/widgets/satiety_rating_widget.dart
new file mode 100644
index 00000000..69af3f22
--- /dev/null
+++ b/lib/features/diary/presentation/widgets/satiety_rating_widget.dart
@@ -0,0 +1,179 @@
+import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_rating_bar/flutter_rating_bar.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+
+class SatietyRatingWidget extends StatefulWidget {
+ final double currentSelectedSatietyRating;
+
+ const SatietyRatingWidget({
+ super.key,
+ required this.currentSelectedSatietyRating,
+ });
+
+ @override
+ State createState() => _SatietyRatingWidgetState();
+}
+
+class _SatietyRatingWidgetState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.only(top: 24),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ 'Ваша сытость после приёма пищи',
+ style: TextStyle(
+ fontSize: 16,
+ ),
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ SvgPicture.asset(
+ 'assets/icons/satiety_emoji_left.svg',
+ height: 32,
+ width: 32,
+ ),
+ RatingBar.builder(
+ initialRating: 3,
+ itemCount: 6,
+ itemSize: MediaQuery.of(context).size.width / 6 - (124 / 6),
+ updateOnDrag: true,
+ itemBuilder: (context, index) {
+ switch (index) {
+ case 0:
+ return Container(
+ width: MediaQuery.of(context).size.width / 6,
+ height: 24,
+ margin: widget.currentSelectedSatietyRating == 1 ? EdgeInsets.symmetric(horizontal: 2) : null,
+ decoration: BoxDecoration(
+ color: Color(0xFFC82222),
+ border: widget.currentSelectedSatietyRating == 1
+ ? Border.all(
+ width: 2,
+ color: Color(0xFFAA3333),
+ strokeAlign: BorderSide.strokeAlignOutside,
+ )
+ : null,
+ borderRadius: widget.currentSelectedSatietyRating == 1
+ ? BorderRadius.horizontal(
+ left: Radius.circular(8),
+ right: Radius.circular(2),
+ )
+ : BorderRadius.horizontal(
+ left: Radius.circular(8),
+ ),
+ ),
+ );
+ case 1:
+ return Container(
+ width: MediaQuery.of(context).size.width / 6,
+ height: 24,
+ margin: widget.currentSelectedSatietyRating == 2 ? EdgeInsets.symmetric(horizontal: 2) : null,
+ decoration: BoxDecoration(
+ color: Color(0xFFFF3E3E),
+ border: widget.currentSelectedSatietyRating == 2
+ ? Border.all(
+ width: 2,
+ color: Color(0xFF550000),
+ strokeAlign: BorderSide.strokeAlignOutside,
+ )
+ : null,
+ borderRadius: widget.currentSelectedSatietyRating == 2 ? BorderRadius.circular(2) : null,
+ ),
+ );
+ case 2:
+ return Container(
+ width: MediaQuery.of(context).size.width / 6,
+ height: 24,
+ margin: widget.currentSelectedSatietyRating == 3 ? EdgeInsets.symmetric(horizontal: 2) : null,
+ decoration: BoxDecoration(
+ color: Color(0xFFF8C926),
+ border: widget.currentSelectedSatietyRating == 3
+ ? Border.all(
+ width: 2,
+ color: Color(0xFF8C5D00),
+ strokeAlign: BorderSide.strokeAlignOutside,
+ )
+ : null,
+ borderRadius: widget.currentSelectedSatietyRating == 3 ? BorderRadius.circular(2) : null,
+ ),
+ );
+ case 3:
+ return Container(
+ width: MediaQuery.of(context).size.width / 6,
+ height: 24,
+ margin: widget.currentSelectedSatietyRating == 4 ? EdgeInsets.symmetric(horizontal: 2) : null,
+ decoration: BoxDecoration(
+ color: Color(0xFFFFF3BF),
+ border: widget.currentSelectedSatietyRating == 4
+ ? Border.all(
+ width: 2,
+ color: Color(0xFFB5911B),
+ strokeAlign: BorderSide.strokeAlignOutside,
+ )
+ : null,
+ borderRadius: widget.currentSelectedSatietyRating == 4 ? BorderRadius.circular(2) : null,
+ ),
+ );
+ case 4:
+ return Container(
+ width: MediaQuery.of(context).size.width / 6,
+ height: 24,
+ margin: widget.currentSelectedSatietyRating == 5 ? EdgeInsets.symmetric(horizontal: 2) : null,
+ decoration: BoxDecoration(
+ color: Color(0xFF62E833),
+ border: widget.currentSelectedSatietyRating == 5
+ ? Border.all(
+ width: 2,
+ color: Color(0xFF276918),
+ strokeAlign: BorderSide.strokeAlignOutside,
+ )
+ : null,
+ borderRadius: widget.currentSelectedSatietyRating == 5 ? BorderRadius.circular(2) : null,
+ ),
+ );
+ default:
+ return Container(
+ width: MediaQuery.of(context).size.width / 6,
+ height: 24,
+ margin: widget.currentSelectedSatietyRating == 6 ? EdgeInsets.symmetric(horizontal: 2) : null,
+ decoration: BoxDecoration(
+ color: Color(0xFF4DCE20),
+ border: widget.currentSelectedSatietyRating == 6
+ ? Border.all(
+ width: 2,
+ color: Color(0xFF184D12),
+ strokeAlign: BorderSide.strokeAlignOutside,
+ )
+ : null,
+ borderRadius: widget.currentSelectedSatietyRating == 6
+ ? BorderRadius.horizontal(
+ left: Radius.circular(2),
+ right: Radius.circular(8),
+ )
+ : BorderRadius.horizontal(
+ right: Radius.circular(8),
+ ),
+ ),
+ );
+ }
+ },
+ onRatingUpdate: (value) => context.read().updateCurrentSelectedSatietyRating(value),
+ ),
+ SvgPicture.asset(
+ 'assets/icons/satiety_emoji_right.svg',
+ height: 32,
+ width: 32,
+ ),
+ ],
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/features/diary/repositories/diary_repository.dart b/lib/features/diary/repositories/diary_repository.dart
index b9b8243c..abb05a33 100644
--- a/lib/features/diary/repositories/diary_repository.dart
+++ b/lib/features/diary/repositories/diary_repository.dart
@@ -1,5 +1,6 @@
import 'dart:io';
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
+import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
import 'package:eatvio_app/features/diary/services/diary_service.dart';
class DiaryRepository {
@@ -11,15 +12,19 @@ class DiaryRepository {
Future getDiaryByDate(String date) async {
return (await diaryService.getDiaryDay(
- index: await _getDiaryIndexByDate(date),
+ index: await getDiaryIndexByDate(date),
))
.diary;
}
- Future _getDiaryIndexByDate(String date) async {
+ Future getDiaryIndexByDate(String date) async {
return (await diaryService.getDiaryIndexByDate(date: date)).id;
}
+ Future getNutritionData(int index) async {
+ return await diaryService.getNutrition(index: index);
+ }
+
Future addPortionToMeal({
required int diaryId,
required int mealId,
diff --git a/lib/features/diary/services/diary_service.dart b/lib/features/diary/services/diary_service.dart
index 16990b1b..bf7f1763 100644
--- a/lib/features/diary/services/diary_service.dart
+++ b/lib/features/diary/services/diary_service.dart
@@ -2,6 +2,7 @@ import 'dart:io';
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
import 'package:eatvio_app/features/diary/models/diary_index_model.dart';
+import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
import 'package:retrofit/retrofit.dart';
part 'diary_service.g.dart';
@@ -20,6 +21,11 @@ abstract class DiaryService {
@Path('diary_id') required int index,
});
+ @GET('/api/diary/{diary_id}/dailyNutritionData')
+ Future getNutrition({
+ @Path('diary_id') required int index,
+ });
+
@POST('/api/diary/{diary_id}/meal/{meal_id}/photo/upload')
@MultiPart()
Future uploadPhotoOfMeal({
diff --git a/lib/features/diary/services/diary_service.g.dart b/lib/features/diary/services/diary_service.g.dart
index 74de16af..1672b50a 100644
--- a/lib/features/diary/services/diary_service.g.dart
+++ b/lib/features/diary/services/diary_service.g.dart
@@ -87,6 +87,39 @@ class _DiaryService implements DiaryService {
return _value;
}
+ @override
+ Future getNutrition({required int index}) async {
+ final _extra = {};
+ final queryParameters = {};
+ final _headers = {};
+ const Map? _data = null;
+ final _options = _setStreamType(Options(
+ method: 'GET',
+ headers: _headers,
+ extra: _extra,
+ )
+ .compose(
+ _dio.options,
+ '/api/diary/${index}/dailyNutritionData',
+ queryParameters: queryParameters,
+ data: _data,
+ )
+ .copyWith(
+ baseUrl: _combineBaseUrls(
+ _dio.options.baseUrl,
+ baseUrl,
+ )));
+ final _result = await _dio.fetch