Merge pull request 'feat(settings): add weight' (#178) from mso7nueh/eatvio_app:main into main

Reviewed-on: eatvio/eatvio_app#178
This commit is contained in:
mso7nueh
2024-12-02 08:22:37 +00:00
94 changed files with 2097 additions and 1589 deletions

View File

@@ -6,22 +6,22 @@ plugins {
android {
namespace = "com.eatvioapp.app"
compileSdk = 35
ndkVersion = "28.0.12433566"
compileSdk = flutter.compileSdkVersion
ndkVersion = "25.1.8937393"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = 21
jvmTarget = JavaVersion.VERSION_1_8
}
defaultConfig {
applicationId = "com.eatvioapp.app"
minSdk = flutter.minSdkVersion
targetSdk = 35
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@@ -1,12 +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> -->
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
<item android:bottom="0dp">
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@@ -1,12 +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:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
<item android:bottom="0dp">
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
</item>
</layer-list>

View File

@@ -0,0 +1,22 @@
<?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">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#FFFFFF</item>
<item name="android:windowSplashScreenBrandingImage">@drawable/android12branding</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</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

@@ -5,6 +5,10 @@
<!-- 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>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</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

View File

@@ -0,0 +1,22 @@
<?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 off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#FFFFFF</item>
<item name="android:windowSplashScreenBrandingImage">@drawable/android12branding</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</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.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -5,6 +5,10 @@
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</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

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip

BIN
assets/splash/branding.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/splash/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "background.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@@ -1,23 +1,56 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "LaunchImageDark.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "LaunchImage@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "LaunchImageDark@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "LaunchImage@3x.png",
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "LaunchImageDark@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -16,13 +16,19 @@
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="3T2-ad-Qdv"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="RPx-PI-7Xg"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="SdS-ul-q2q"/>
<constraint firstAttribute="trailing" secondItem="tWc-Dq-wcI" secondAttribute="trailing" id="Swv-Gf-Rwn"/>
<constraint firstAttribute="trailing" secondItem="YRO-k0-Ey4" secondAttribute="trailing" id="TQA-XW-tRk"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="duK-uY-Gun"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="kV7-tw-vXt"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="xPn-NY-SIU"/>
</constraints>
</view>
</viewController>
@@ -32,6 +38,7 @@
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
<image name="LaunchImage" width="1152" height="1152"/>
<image name="LaunchBackground" width="1" height="1"/>
</resources>
</document>

View File

@@ -1,65 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>eatvio_app</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Нужно для загрузки фото</string>
<key>NSCameraUsageDescription</key>
<string>Нужно для загрузки фото</string>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
</dict>
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>eatvio_app</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Нужно для загрузки фото</string>
<key>NSCameraUsageDescription</key>
<string>Нужно для загрузки фото</string>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>UIStatusBarHidden</key>
<false/>
</dict>
</plist>

View File

@@ -17,6 +17,7 @@ import 'package:eatvio_app/features/diets/presentation/pages/about_diet_screen.d
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
import 'package:eatvio_app/features/menu/presentation/pages/menu_screen.dart';
import 'package:eatvio_app/features/notifications/presentation/pages/notifications_screen.dart';
@@ -163,7 +164,9 @@ class AppRouter {
GoRoute(
path: '/blog-post-create-screen',
name: 'blogPostCreateScreen',
builder: (context, state) => BlogPostCreateScreen(),
builder: (context, state) => BlogPostCreateScreen(
feedCubit: state.extra != null ? state.extra as FeedCubit : null,
),
),
GoRoute(
path: '/blog-post-screen',

View File

@@ -6,6 +6,7 @@ import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -14,9 +15,11 @@ import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
class BlogPostCreateScreen extends StatefulWidget {
final FeedCubit? feedCubit;
const BlogPostCreateScreen({
Key? key,
}) : super(key: key);
required this.feedCubit,
}) : super();
@override
State<BlogPostCreateScreen> createState() => _BlogPostCreateScreenState();
@@ -134,16 +137,17 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
),
onPressed: isLoading
? null
: () {
: () async {
setState(() {
isLoading = true;
});
blogPostCubit.createBlogPost(
await blogPostCubit.createBlogPost(
title: titleController.text,
description: descriptionController.text,
videoUrl: videoUrlController.text,
image: image,
);
widget.feedCubit?.loadFeedBlogPostList();
},
),
),

View File

@@ -34,7 +34,7 @@ class MealModel with _$MealModel {
const factory MealModel({
@JsonKey(name: "id") required int id,
@JsonKey(name: "time") required String time,
@JsonKey(name: "type") required int type,
@JsonKey(name: "type_no") required int typeNumber,
@JsonKey(name: "satiety") required int satiety,
@JsonKey(name: "mood") required int mood,
@JsonKey(name: "portions") required List<Portion> portions,

View File

@@ -538,8 +538,8 @@ mixin _$MealModel {
int get id => throw _privateConstructorUsedError;
@JsonKey(name: "time")
String get time => throw _privateConstructorUsedError;
@JsonKey(name: "type")
int get type => throw _privateConstructorUsedError;
@JsonKey(name: "type_no")
int get typeNumber => throw _privateConstructorUsedError;
@JsonKey(name: "satiety")
int get satiety => throw _privateConstructorUsedError;
@JsonKey(name: "mood")
@@ -567,7 +567,7 @@ abstract class $MealModelCopyWith<$Res> {
$Res call(
{@JsonKey(name: "id") int id,
@JsonKey(name: "time") String time,
@JsonKey(name: "type") int type,
@JsonKey(name: "type_no") int typeNumber,
@JsonKey(name: "satiety") int satiety,
@JsonKey(name: "mood") int mood,
@JsonKey(name: "portions") List<Portion> portions,
@@ -591,7 +591,7 @@ class _$MealModelCopyWithImpl<$Res, $Val extends MealModel>
$Res call({
Object? id = null,
Object? time = null,
Object? type = null,
Object? typeNumber = null,
Object? satiety = null,
Object? mood = null,
Object? portions = null,
@@ -606,9 +606,9 @@ class _$MealModelCopyWithImpl<$Res, $Val extends MealModel>
? _value.time
: time // ignore: cast_nullable_to_non_nullable
as String,
type: null == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
typeNumber: null == typeNumber
? _value.typeNumber
: typeNumber // ignore: cast_nullable_to_non_nullable
as int,
satiety: null == satiety
? _value.satiety
@@ -641,7 +641,7 @@ abstract class _$$MealModelImplCopyWith<$Res>
$Res call(
{@JsonKey(name: "id") int id,
@JsonKey(name: "time") String time,
@JsonKey(name: "type") int type,
@JsonKey(name: "type_no") int typeNumber,
@JsonKey(name: "satiety") int satiety,
@JsonKey(name: "mood") int mood,
@JsonKey(name: "portions") List<Portion> portions,
@@ -663,7 +663,7 @@ class __$$MealModelImplCopyWithImpl<$Res>
$Res call({
Object? id = null,
Object? time = null,
Object? type = null,
Object? typeNumber = null,
Object? satiety = null,
Object? mood = null,
Object? portions = null,
@@ -678,9 +678,9 @@ class __$$MealModelImplCopyWithImpl<$Res>
? _value.time
: time // ignore: cast_nullable_to_non_nullable
as String,
type: null == type
? _value.type
: type // ignore: cast_nullable_to_non_nullable
typeNumber: null == typeNumber
? _value.typeNumber
: typeNumber // ignore: cast_nullable_to_non_nullable
as int,
satiety: null == satiety
? _value.satiety
@@ -708,7 +708,7 @@ class _$MealModelImpl implements _MealModel {
const _$MealModelImpl(
{@JsonKey(name: "id") required this.id,
@JsonKey(name: "time") required this.time,
@JsonKey(name: "type") required this.type,
@JsonKey(name: "type_no") required this.typeNumber,
@JsonKey(name: "satiety") required this.satiety,
@JsonKey(name: "mood") required this.mood,
@JsonKey(name: "portions") required final List<Portion> portions,
@@ -726,8 +726,8 @@ class _$MealModelImpl implements _MealModel {
@JsonKey(name: "time")
final String time;
@override
@JsonKey(name: "type")
final int type;
@JsonKey(name: "type_no")
final int typeNumber;
@override
@JsonKey(name: "satiety")
final int satiety;
@@ -754,7 +754,7 @@ class _$MealModelImpl implements _MealModel {
@override
String toString() {
return 'MealModel(id: $id, time: $time, type: $type, satiety: $satiety, mood: $mood, portions: $portions, photos: $photos)';
return 'MealModel(id: $id, time: $time, typeNumber: $typeNumber, satiety: $satiety, mood: $mood, portions: $portions, photos: $photos)';
}
@override
@@ -764,7 +764,8 @@ class _$MealModelImpl implements _MealModel {
other is _$MealModelImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.time, time) || other.time == time) &&
(identical(other.type, type) || other.type == type) &&
(identical(other.typeNumber, typeNumber) ||
other.typeNumber == typeNumber) &&
(identical(other.satiety, satiety) || other.satiety == satiety) &&
(identical(other.mood, mood) || other.mood == mood) &&
const DeepCollectionEquality().equals(other._portions, _portions) &&
@@ -777,7 +778,7 @@ class _$MealModelImpl implements _MealModel {
runtimeType,
id,
time,
type,
typeNumber,
satiety,
mood,
const DeepCollectionEquality().hash(_portions),
@@ -803,7 +804,7 @@ abstract class _MealModel implements MealModel {
const factory _MealModel(
{@JsonKey(name: "id") required final int id,
@JsonKey(name: "time") required final String time,
@JsonKey(name: "type") required final int type,
@JsonKey(name: "type_no") required final int typeNumber,
@JsonKey(name: "satiety") required final int satiety,
@JsonKey(name: "mood") required final int mood,
@JsonKey(name: "portions") required final List<Portion> portions,
@@ -820,8 +821,8 @@ abstract class _MealModel implements MealModel {
@JsonKey(name: "time")
String get time;
@override
@JsonKey(name: "type")
int get type;
@JsonKey(name: "type_no")
int get typeNumber;
@override
@JsonKey(name: "satiety")
int get satiety;

View File

@@ -49,7 +49,7 @@ _$MealModelImpl _$$MealModelImplFromJson(Map<String, dynamic> json) =>
_$MealModelImpl(
id: (json['id'] as num).toInt(),
time: json['time'] as String,
type: (json['type'] as num).toInt(),
typeNumber: (json['type_no'] as num).toInt(),
satiety: (json['satiety'] as num).toInt(),
mood: (json['mood'] as num).toInt(),
portions: (json['portions'] as List<dynamic>)
@@ -64,7 +64,7 @@ Map<String, dynamic> _$$MealModelImplToJson(_$MealModelImpl instance) =>
<String, dynamic>{
'id': instance.id,
'time': instance.time,
'type': instance.type,
'type_no': instance.typeNumber,
'satiety': instance.satiety,
'mood': instance.mood,
'portions': instance.portions,

View File

@@ -10,27 +10,43 @@ import 'package:eatvio_app/features/glossary/repositories/glossary_repository.da
import 'package:flutter_bloc/flutter_bloc.dart';
class DiaryCubit extends Cubit<DiaryState> {
late final DateTime today;
late DateTime selectedDay;
late int selectedMeal;
final DiaryRepository diaryRepository;
final GlossaryRepository glossaryRepository;
DiaryCubit({
required this.diaryRepository,
required this.glossaryRepository,
}) : super(const DiaryState.loading());
}) : super(const DiaryState.loading()) {
today = DateTime.now();
selectedDay = today;
}
getDiaryDay(String date, [bool needLoading = true]) async {
if (needLoading) emit(const DiaryState.loading());
setSelectedDay(DateTime day) async {
selectedDay = day;
await fetchDiary(true);
}
setSelectedMeal(int meal) {
selectedMeal = meal;
emit((state as DiaryStateLoaded).copyWith(selectedMeal: selectedMeal));
}
fetchDiary([bool needLoading = false]) async {
if (needLoading) {
emit(const DiaryState.loading());
selectedMeal = 1;
}
try {
final diaryDay = await diaryRepository.getDiaryByDate(date);
final List<int> satiety = [];
for (int i = 0; i < diaryDay.meals.length; i++) {
satiety.add(diaryDay.meals[i].satiety);
}
final diaryDay = await diaryRepository.getDiaryByDate('$selectedDay');
emit(
DiaryState.loaded(
diary: diaryDay,
nutrition: await getNutrition((await diaryRepository.getDiaryByDate(date)).id),
currentSelectedSatietyRating: satiety,
nutrition: await getNutrition((await diaryRepository.getDiaryByDate('$selectedDay')).id),
selectedDay: selectedDay,
selectedMeal: selectedMeal,
),
);
} catch (e) {
@@ -40,16 +56,18 @@ class DiaryCubit extends Cubit<DiaryState> {
}
finishDiaryDay({
required String date,
required int diaryId,
required String wakeUp,
required String retire,
required String comment,
}) async {
await diaryRepository.finishDiaryDay(
diaryId: diaryId,
wakeUp: '',
retire: '',
comment: '',
wakeUp: wakeUp,
retire: retire,
comment: comment,
);
await getDiaryDay(date, false);
await fetchDiary();
}
Future<NutritionDataModel> getNutrition(int index) async {
@@ -62,7 +80,7 @@ class DiaryCubit extends Cubit<DiaryState> {
required String productName,
required String portion,
required int quantity,
required String date,
int? productNutritionalValueId,
}) async {
await diaryRepository.addPortionToMeal(
diaryId: diaryId,
@@ -70,8 +88,9 @@ class DiaryCubit extends Cubit<DiaryState> {
productName: productName,
portion: portion,
quantity: quantity,
productNutritionalValueId: productNutritionalValueId,
);
await getDiaryDay(date, false);
await fetchDiary();
}
updateProductInMeal({
@@ -81,7 +100,7 @@ class DiaryCubit extends Cubit<DiaryState> {
required String productName,
required String portion,
required int quantity,
required String date,
int? productNutritionalValueId,
}) async {
await diaryRepository.updateProductInMeal(
diaryId: diaryId,
@@ -90,22 +109,22 @@ class DiaryCubit extends Cubit<DiaryState> {
productName: productName,
portion: portion,
quantity: quantity,
productNutritionalValueId: productNutritionalValueId,
);
await getDiaryDay(date, false);
await fetchDiary();
}
deleteProductFromMeal({
required int diaryId,
required int mealId,
required int portionId,
required String date,
}) async {
await diaryRepository.deleteProductFromMeal(
diaryId: diaryId,
mealId: mealId,
portionId: portionId,
);
await getDiaryDay(date, false);
await fetchDiary();
}
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
@@ -115,40 +134,25 @@ class DiaryCubit extends Cubit<DiaryState> {
uploadPhotos({
required List<File> files,
required int diaryId,
required int mealId,
required String date,
}) async {
await diaryRepository.uploadPhotos(
files: await ImageCompressUtility.compressFilesList(files),
diaryId: diaryId,
mealId: mealId,
mealId: selectedMeal,
);
await getDiaryDay(date, false);
await fetchDiary();
}
deletePhoto({
required DiaryPhotoModel photo,
required int diaryId,
required int mealId,
required String date,
}) async {
await diaryRepository.deletePhoto(
photo: photo,
diaryId: diaryId,
mealId: mealId,
mealId: selectedMeal,
);
await getDiaryDay(date, false);
}
updateCurrentSelectedSatietyRating({
required int diaryId,
required int meal,
required int value,
}) {
List<int> satiety = [...(state as DiaryStateLoaded).currentSelectedSatietyRating];
satiety[meal] = value;
diaryRepository.fillSatiety(diaryId: diaryId, meal: meal, satiety: value.toInt() - 1);
emit((state as DiaryStateLoaded).copyWith(currentSelectedSatietyRating: satiety));
await fetchDiary();
}
sendProductToModeration({
@@ -166,4 +170,27 @@ class DiaryCubit extends Cubit<DiaryState> {
carbohydrates: carbohydrates,
);
}
fillTime({
required int diaryId,
required String time,
}) async {
await diaryRepository.fillTime(
diaryId: diaryId,
mealId: selectedMeal,
time: time,
);
await fetchDiary();
}
fillSatiety({
required int diaryId,
required int satiety,
}) async {
await diaryRepository.fillSatiety(
diaryId: diaryId,
mealId: selectedMeal,
satiety: satiety,
);
}
}

View File

@@ -17,6 +17,7 @@ class DiaryState with _$DiaryState {
const factory DiaryState.loaded({
required DiaryDayModel diary,
required NutritionDataModel nutrition,
required List<int> currentSelectedSatietyRating,
required DateTime selectedDay,
required int selectedMeal,
}) = DiaryStateLoaded;
}

View File

@@ -22,7 +22,7 @@ mixin _$DiaryState {
required TResult Function() loading,
required TResult Function(String errorMessage) error,
required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
List<int> currentSelectedSatietyRating)
DateTime selectedDay, int selectedMeal)
loaded,
}) =>
throw _privateConstructorUsedError;
@@ -32,7 +32,7 @@ mixin _$DiaryState {
TResult? Function()? loading,
TResult? Function(String errorMessage)? error,
TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition,
List<int> currentSelectedSatietyRating)?
DateTime selectedDay, int selectedMeal)?
loaded,
}) =>
throw _privateConstructorUsedError;
@@ -42,7 +42,7 @@ mixin _$DiaryState {
TResult Function()? loading,
TResult Function(String errorMessage)? error,
TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
List<int> currentSelectedSatietyRating)?
DateTime selectedDay, int selectedMeal)?
loaded,
required TResult orElse(),
}) =>
@@ -140,7 +140,7 @@ class _$DiaryStateImpl implements _DiaryState {
required TResult Function() loading,
required TResult Function(String errorMessage) error,
required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
List<int> currentSelectedSatietyRating)
DateTime selectedDay, int selectedMeal)
loaded,
}) {
return $default();
@@ -153,7 +153,7 @@ class _$DiaryStateImpl implements _DiaryState {
TResult? Function()? loading,
TResult? Function(String errorMessage)? error,
TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition,
List<int> currentSelectedSatietyRating)?
DateTime selectedDay, int selectedMeal)?
loaded,
}) {
return $default?.call();
@@ -166,7 +166,7 @@ class _$DiaryStateImpl implements _DiaryState {
TResult Function()? loading,
TResult Function(String errorMessage)? error,
TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
List<int> currentSelectedSatietyRating)?
DateTime selectedDay, int selectedMeal)?
loaded,
required TResult orElse(),
}) {
@@ -263,7 +263,7 @@ class _$DiaryStateLoadingImpl implements DiaryStateLoading {
required TResult Function() loading,
required TResult Function(String errorMessage) error,
required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
List<int> currentSelectedSatietyRating)
DateTime selectedDay, int selectedMeal)
loaded,
}) {
return loading();
@@ -276,7 +276,7 @@ class _$DiaryStateLoadingImpl implements DiaryStateLoading {
TResult? Function()? loading,
TResult? Function(String errorMessage)? error,
TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition,
List<int> currentSelectedSatietyRating)?
DateTime selectedDay, int selectedMeal)?
loaded,
}) {
return loading?.call();
@@ -289,7 +289,7 @@ class _$DiaryStateLoadingImpl implements DiaryStateLoading {
TResult Function()? loading,
TResult Function(String errorMessage)? error,
TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
List<int> currentSelectedSatietyRating)?
DateTime selectedDay, int selectedMeal)?
loaded,
required TResult orElse(),
}) {
@@ -415,7 +415,7 @@ class _$DiaryStateErrorImpl implements DiaryStateError {
required TResult Function() loading,
required TResult Function(String errorMessage) error,
required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
List<int> currentSelectedSatietyRating)
DateTime selectedDay, int selectedMeal)
loaded,
}) {
return error(errorMessage);
@@ -428,7 +428,7 @@ class _$DiaryStateErrorImpl implements DiaryStateError {
TResult? Function()? loading,
TResult? Function(String errorMessage)? error,
TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition,
List<int> currentSelectedSatietyRating)?
DateTime selectedDay, int selectedMeal)?
loaded,
}) {
return error?.call(errorMessage);
@@ -441,7 +441,7 @@ class _$DiaryStateErrorImpl implements DiaryStateError {
TResult Function()? loading,
TResult Function(String errorMessage)? error,
TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
List<int> currentSelectedSatietyRating)?
DateTime selectedDay, int selectedMeal)?
loaded,
required TResult orElse(),
}) {
@@ -511,7 +511,8 @@ abstract class _$$DiaryStateLoadedImplCopyWith<$Res> {
$Res call(
{DiaryDayModel diary,
NutritionDataModel nutrition,
List<int> currentSelectedSatietyRating});
DateTime selectedDay,
int selectedMeal});
$DiaryDayModelCopyWith<$Res> get diary;
$NutritionDataModelCopyWith<$Res> get nutrition;
@@ -532,7 +533,8 @@ class __$$DiaryStateLoadedImplCopyWithImpl<$Res>
$Res call({
Object? diary = null,
Object? nutrition = null,
Object? currentSelectedSatietyRating = null,
Object? selectedDay = null,
Object? selectedMeal = null,
}) {
return _then(_$DiaryStateLoadedImpl(
diary: null == diary
@@ -543,10 +545,14 @@ class __$$DiaryStateLoadedImplCopyWithImpl<$Res>
? _value.nutrition
: nutrition // ignore: cast_nullable_to_non_nullable
as NutritionDataModel,
currentSelectedSatietyRating: null == currentSelectedSatietyRating
? _value._currentSelectedSatietyRating
: currentSelectedSatietyRating // ignore: cast_nullable_to_non_nullable
as List<int>,
selectedDay: null == selectedDay
? _value.selectedDay
: selectedDay // ignore: cast_nullable_to_non_nullable
as DateTime,
selectedMeal: null == selectedMeal
? _value.selectedMeal
: selectedMeal // ignore: cast_nullable_to_non_nullable
as int,
));
}
@@ -577,25 +583,21 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded {
const _$DiaryStateLoadedImpl(
{required this.diary,
required this.nutrition,
required final List<int> currentSelectedSatietyRating})
: _currentSelectedSatietyRating = currentSelectedSatietyRating;
required this.selectedDay,
required this.selectedMeal});
@override
final DiaryDayModel diary;
@override
final NutritionDataModel nutrition;
final List<int> _currentSelectedSatietyRating;
@override
List<int> get currentSelectedSatietyRating {
if (_currentSelectedSatietyRating is EqualUnmodifiableListView)
return _currentSelectedSatietyRating;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_currentSelectedSatietyRating);
}
final DateTime selectedDay;
@override
final int selectedMeal;
@override
String toString() {
return 'DiaryState.loaded(diary: $diary, nutrition: $nutrition, currentSelectedSatietyRating: $currentSelectedSatietyRating)';
return 'DiaryState.loaded(diary: $diary, nutrition: $nutrition, selectedDay: $selectedDay, selectedMeal: $selectedMeal)';
}
@override
@@ -606,14 +608,15 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded {
(identical(other.diary, diary) || other.diary == diary) &&
(identical(other.nutrition, nutrition) ||
other.nutrition == nutrition) &&
const DeepCollectionEquality().equals(
other._currentSelectedSatietyRating,
_currentSelectedSatietyRating));
(identical(other.selectedDay, selectedDay) ||
other.selectedDay == selectedDay) &&
(identical(other.selectedMeal, selectedMeal) ||
other.selectedMeal == selectedMeal));
}
@override
int get hashCode => Object.hash(runtimeType, diary, nutrition,
const DeepCollectionEquality().hash(_currentSelectedSatietyRating));
int get hashCode =>
Object.hash(runtimeType, diary, nutrition, selectedDay, selectedMeal);
/// Create a copy of DiaryState
/// with the given fields replaced by the non-null parameter values.
@@ -631,10 +634,10 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded {
required TResult Function() loading,
required TResult Function(String errorMessage) error,
required TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
List<int> currentSelectedSatietyRating)
DateTime selectedDay, int selectedMeal)
loaded,
}) {
return loaded(diary, nutrition, currentSelectedSatietyRating);
return loaded(diary, nutrition, selectedDay, selectedMeal);
}
@override
@@ -644,10 +647,10 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded {
TResult? Function()? loading,
TResult? Function(String errorMessage)? error,
TResult? Function(DiaryDayModel diary, NutritionDataModel nutrition,
List<int> currentSelectedSatietyRating)?
DateTime selectedDay, int selectedMeal)?
loaded,
}) {
return loaded?.call(diary, nutrition, currentSelectedSatietyRating);
return loaded?.call(diary, nutrition, selectedDay, selectedMeal);
}
@override
@@ -657,12 +660,12 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded {
TResult Function()? loading,
TResult Function(String errorMessage)? error,
TResult Function(DiaryDayModel diary, NutritionDataModel nutrition,
List<int> currentSelectedSatietyRating)?
DateTime selectedDay, int selectedMeal)?
loaded,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(diary, nutrition, currentSelectedSatietyRating);
return loaded(diary, nutrition, selectedDay, selectedMeal);
}
return orElse();
}
@@ -707,14 +710,15 @@ class _$DiaryStateLoadedImpl implements DiaryStateLoaded {
abstract class DiaryStateLoaded implements DiaryState {
const factory DiaryStateLoaded(
{required final DiaryDayModel diary,
required final NutritionDataModel nutrition,
required final List<int> currentSelectedSatietyRating}) =
_$DiaryStateLoadedImpl;
{required final DiaryDayModel diary,
required final NutritionDataModel nutrition,
required final DateTime selectedDay,
required final int selectedMeal}) = _$DiaryStateLoadedImpl;
DiaryDayModel get diary;
NutritionDataModel get nutrition;
List<int> get currentSelectedSatietyRating;
DateTime get selectedDay;
int get selectedMeal;
/// Create a copy of DiaryState
/// with the given fields replaced by the non-null parameter values.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,73 @@
import 'package:eatvio_app/core/theme/theme_helper.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/add_product_modal.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class AddProductButtonWidget extends StatefulWidget {
const AddProductButtonWidget({super.key});
@override
State<AddProductButtonWidget> createState() => _AddProductButtonWidgetState();
}
class _AddProductButtonWidgetState extends State<AddProductButtonWidget> {
late final DiaryCubit diaryCubit;
@override
void initState() {
super.initState();
diaryCubit = context.read<DiaryCubit>();
}
addProductInMeal(DiaryStateLoaded state, [int? portionId]) {
return showModalBottomSheet(
useRootNavigator: true,
context: context,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
),
isScrollControlled: true,
builder: (context) => AddProductModal(
state: state,
diaryCubit: diaryCubit,
),
);
}
@override
Widget build(BuildContext context) {
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (context, state) {
if (state is DiaryStateLoaded) {
return InkWell(
onTap: () => addProductInMeal(state),
borderRadius: BorderRadius.circular(20),
child: Ink(
padding: EdgeInsets.symmetric(horizontal: 16),
width: MediaQuery.of(context).size.width - 32,
decoration: BoxDecoration(
color: appTheme.blueA400,
borderRadius: BorderRadius.circular(20),
),
child: Container(
padding: EdgeInsets.all(16),
alignment: Alignment.center,
child: Text(
'Добавить',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
),
);
}
return const SizedBox.shrink();
},
);
}
}

View File

@@ -0,0 +1,263 @@
import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/custom_divider.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/diary/models/diary_day_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/glossary/models/glossary_model.dart';
import 'package:flutter/material.dart';
class AddProductModal extends StatefulWidget {
final Portion? portion;
final DiaryStateLoaded state;
final DiaryCubit diaryCubit;
const AddProductModal({
super.key,
this.portion,
required this.state,
required this.diaryCubit,
});
@override
State<AddProductModal> createState() => _AddProductModalState();
}
class _AddProductModalState extends State<AddProductModal> {
final productNameController = TextEditingController();
final quantityController = TextEditingController();
GlossaryModel? selectedGlossary;
List<GlossaryModel> glossariesList = [];
@override
void initState() {
super.initState();
if (widget.portion != null) {
productNameController.text = widget.portion!.productName;
quantityController.text = '${widget.portion!.quantity}';
selectedGlossary = GlossaryModel(
id: widget.portion!.id,
name: widget.portion!.productName,
grInPortion: widget.portion!.portion == 'gr' ? 0 : 1,
);
}
}
@override
Widget build(BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, void Function(void Function()) setState) {
return Padding(
padding: const EdgeInsets.all(16) +
EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Добавление блюда',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Color(0xFF323F4B),
),
),
const SizedBox(height: 16),
Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.8 - 32,
child: CustomTextFormField(
controller: productNameController,
autofocus: true,
hintText: 'Введите название продукта/блюда',
onChanged: (query) {
EasyDebounce.debounce(
'client-typing',
Duration(milliseconds: 250),
() async {
glossariesList = await widget.diaryCubit.getGlossaryListByQuery(query);
setState(() {});
},
);
},
),
),
const SizedBox(width: 4),
SizedBox(
width: MediaQuery.of(context).size.width * 0.2 - 4,
child: CustomTextFormField(
controller: quantityController,
keyboardType: TextInputType.number,
hintText: selectedGlossary == null
? '100'
: selectedGlossary!.grInPortion == 0
? '100'
: '1',
suffixText: selectedGlossary == null
? 'гр'
: selectedGlossary?.grInPortion == 0
? 'гр'
: 'шт',
),
),
],
),
glossariesList.isEmpty ? const SizedBox(height: 16) : const SizedBox.shrink(),
Container(
constraints: BoxConstraints(
maxHeight: 256,
),
child: ListView.separated(
physics: const BouncingScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, i) {
return ListTile(
onTap: () async {
setState(() {
selectedGlossary = glossariesList[i];
productNameController.text = selectedGlossary!.name;
});
glossariesList = await widget.diaryCubit.getGlossaryListByQuery(glossariesList[i].name);
setState(() {});
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
title: Text(
glossariesList[i].name,
style: TextStyle(
height: 1,
),
),
subtitle: glossariesList[i].grInPortion == 0
? null
: Text(
'${glossariesList[i].grInPortion} г',
style: TextStyle(
height: 1,
color: Colors.grey.shade600,
),
),
trailing: Icon(
Icons.keyboard_arrow_right_rounded,
color: Colors.grey.shade400,
),
contentPadding: EdgeInsets.only(left: 12),
);
},
itemCount: glossariesList.length,
separatorBuilder: (BuildContext context, int index) {
return CustomDivider();
},
),
),
Row(
children: [
if (widget.portion != null)
InkWell(
onTap: () {
widget.diaryCubit.deleteProductFromMeal(
diaryId: widget.state.diary.id,
mealId: widget.state.selectedMeal,
portionId: widget.portion!.id,
);
Navigator.of(context).pop();
},
borderRadius: BorderRadius.circular(20),
child: Ink(
width: 52,
height: 52,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Color(0xFFF6F6F6),
),
child: Icon(
Icons.delete_outline_rounded,
color: Colors.red,
),
),
),
if (widget.portion != null) const SizedBox(width: 8),
InkWell(
onTap: () {
if (widget.portion == null) {
widget.diaryCubit.addProductToMeal(
diaryId: widget.state.diary.id,
mealId: widget.state.selectedMeal,
productName: productNameController.text,
portion: selectedGlossary == null
? 'gr'
: selectedGlossary!.grInPortion == 0
? 'gr'
: 'pcs',
quantity: int.parse(quantityController.text),
productNutritionalValueId: selectedGlossary?.id,
);
} else {
widget.diaryCubit.updateProductInMeal(
diaryId: widget.state.diary.id,
mealId: widget.state.selectedMeal,
portionId: widget.portion!.id,
productName: productNameController.text,
portion: selectedGlossary == null
? 'gr'
: selectedGlossary!.grInPortion == 0
? 'gr'
: 'pcs',
quantity: int.parse(quantityController.text),
productNutritionalValueId: selectedGlossary?.id,
);
}
Navigator.of(context).pop();
if (selectedGlossary == null) {
showModalBottomSheet(
isScrollControlled: true,
useRootNavigator: true,
context: context,
builder: (context) => Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: GlossaryModerationRequestModalWidget(
productName: productNameController.text,
diaryCubit: widget.diaryCubit,
),
),
);
}
},
borderRadius: BorderRadius.circular(20),
child: Ink(
width: MediaQuery.of(context).size.width - (widget.portion == null ? 32 : 92),
height: 52,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: appTheme.blueA400,
),
child: Center(
child: Text(
widget.portion == null ? 'Добавить' : 'Сохранить',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
),
],
),
],
),
);
},
);
}
}

View File

@@ -0,0 +1,130 @@
import 'package:eatvio_app/core/theme/theme_helper.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:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:table_calendar/table_calendar.dart';
class CalendarWidget extends StatefulWidget {
const CalendarWidget({super.key});
@override
State<CalendarWidget> createState() => _CalendarWidgetState();
}
class _CalendarWidgetState extends State<CalendarWidget> {
CalendarFormat _calendarFormat = CalendarFormat.week;
DateTime _focusedDay = DateTime.now();
DateTime _selectedDay = DateTime.now();
@override
Widget build(BuildContext context) {
return BlocConsumer<DiaryCubit, DiaryState>(
listener: (BuildContext context, DiaryState state) {
if (state is DiaryStateLoaded) {
setState(() {
_selectedDay = state.selectedDay;
});
}
},
builder: (BuildContext context, DiaryState state) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(top: 16),
child: TableCalendar(
focusedDay: _focusedDay,
firstDay: DateTime.utc(2020, 10, 16),
lastDay: DateTime.utc(2030, 3, 14),
locale: 'ru_RU',
startingDayOfWeek: StartingDayOfWeek.monday,
calendarFormat: _calendarFormat,
headerStyle: HeaderStyle(
titleCentered: true,
formatButtonVisible: false,
leftChevronVisible: false,
rightChevronVisible: false,
),
onDaySelected: (selectedDay, focusedDay) {
if (!isSameDay(_selectedDay, selectedDay)) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
context.read<DiaryCubit>().setSelectedDay(_selectedDay);
}
},
selectedDayPredicate: (day) {
return isSameDay(_selectedDay, day);
},
onPageChanged: (focusedDay) {
_focusedDay = focusedDay;
},
onFormatChanged: (format) {
if (_calendarFormat != format) {
setState(() {
_calendarFormat = format;
});
}
},
calendarStyle: CalendarStyle(
weekendDecoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
defaultDecoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
selectedDecoration: BoxDecoration(
color: appTheme.blueA400,
shape: BoxShape.circle,
),
todayTextStyle: TextStyle(
color: Colors.black,
),
todayDecoration: BoxDecoration(
border: Border.all(
color: appTheme.blueA400,
width: 2,
),
color: Colors.white,
shape: BoxShape.circle,
),
),
),
),
_arrowDown(),
],
);
},
);
}
_nextCalendarFormat() {
setState(() {
_calendarFormat = CalendarFormat.values[(_calendarFormat.index - 1) % CalendarFormat.values.length];
});
}
_arrowDown() {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onVerticalDragStart: (_) {
_nextCalendarFormat();
},
onTap: () {
_nextCalendarFormat();
},
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,
),
),
);
}
}

View File

@@ -0,0 +1,246 @@
import 'package:eatvio_app/core/theme/theme_helper.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:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncfusion_flutter_gauges/gauges.dart';
class CaloriesChartWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (BuildContext context, DiaryState state) {
if (state is DiaryStateLoaded) {
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>[
RadialAxis(
startAngle: 180,
endAngle: 0,
minimum: 0,
maximum: 100,
showLabels: false,
showTicks: false,
radiusFactor: 0.7,
showAxisLine: false,
pointers: <GaugePointer>[
RangePointer(
value: 100,
width: 0.16,
animationDuration: 2000,
color: Color(0xFFF6FAFF),
sizeUnit: GaugeSizeUnit.factor,
enableAnimation: true,
cornerStyle: CornerStyle.bothCurve,
),
RangePointer(
value: (state.nutrition.dailyNutritionIntake.carbohydrates / state.nutrition.dailyNutritionRecommendation.carbohydrates) * 100,
width: 0.16,
color: state.nutrition.dailyNutritionIntake.carbohydrates < state.nutrition.dailyNutritionRecommendation.carbohydrates ? 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: (state.nutrition.dailyNutritionIntake.fats / state.nutrition.dailyNutritionRecommendation.fats) * 100,
width: 0.16,
color: state.nutrition.dailyNutritionIntake.fats < state.nutrition.dailyNutritionRecommendation.fats ? 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: (state.nutrition.dailyNutritionIntake.proteins / state.nutrition.dailyNutritionRecommendation.proteins) * 100,
width: 0.16,
color: state.nutrition.dailyNutritionIntake.proteins < state.nutrition.dailyNutritionRecommendation.proteins ? 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: (state.nutrition.dailyNutritionIntake.energy / state.nutrition.dailyNutritionRecommendation.energy) * 100,
width: 0.16,
color: state.nutrition.dailyNutritionIntake.energy < state.nutrition.dailyNutritionRecommendation.energy ? appTheme.blueA400 : Colors.red,
sizeUnit: GaugeSizeUnit.factor,
enableAnimation: true,
cornerStyle: CornerStyle.bothCurve,
pointerOffset: -0.54,
),
],
annotations: <GaugeAnnotation>[
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(
'Калории',
'${state.nutrition.dailyNutritionIntake.energy.toStringAsFixed(0)} из ${state.nutrition.dailyNutritionRecommendation.energy.toStringAsFixed(0)} ккал',
),
const SizedBox(height: 8),
_chartData(
'Белки',
'${state.nutrition.dailyNutritionIntake.proteins.toStringAsFixed(0)} из ${state.nutrition.dailyNutritionRecommendation.proteins.toStringAsFixed(
0,
)} грамм',
),
const SizedBox(height: 8),
_chartData(
'Жиры',
'${state.nutrition.dailyNutritionIntake.fats.toStringAsFixed(0)} из ${state.nutrition.dailyNutritionRecommendation.fats.toStringAsFixed(0)} грамм',
),
const SizedBox(height: 8),
_chartData(
'Углеводы',
'${state.nutrition.dailyNutritionIntake.carbohydrates.toStringAsFixed(0)} из ${state.nutrition.dailyNutritionRecommendation.carbohydrates.toStringAsFixed(0)} грамм',
),
],
),
],
),
],
),
);
}
return const SizedBox.shrink();
},
);
}
_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),
),
),
],
);
}
}

View File

@@ -1,50 +1,50 @@
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class FinishDayButtonWidget extends StatefulWidget {
final DiaryDayModel diaryDay;
const FinishDayButtonWidget({
super.key,
required this.diaryDay,
});
@override
State<FinishDayButtonWidget> createState() => _FinishDayButtonWidgetState();
}
class _FinishDayButtonWidgetState extends State<FinishDayButtonWidget> {
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => context.read<DiaryCubit>().finishDiaryDay(
diaryId: widget.diaryDay.id,
date: widget.diaryDay.date,
),
borderRadius: BorderRadius.circular(20),
child: Ink(
padding: EdgeInsets.symmetric(horizontal: 8),
width: MediaQuery.of(context).size.width - 16,
decoration: BoxDecoration(
color: appTheme.blueA400,
borderRadius: BorderRadius.circular(20),
),
child: Container(
padding: EdgeInsets.all(16),
alignment: Alignment.center,
child: Text(
'Закончить день',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
),
);
}
}
// import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
// import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
// import 'package:eatvio_app/core/theme/theme_helper.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter_bloc/flutter_bloc.dart';
//
// class FinishDayButtonWidget extends StatefulWidget {
// final DiaryDayModel diaryDay;
//
// const FinishDayButtonWidget({
// super.key,
// required this.diaryDay,
// });
//
// @override
// State<FinishDayButtonWidget> createState() => _FinishDayButtonWidgetState();
// }
//
// class _FinishDayButtonWidgetState extends State<FinishDayButtonWidget> {
// @override
// Widget build(BuildContext context) {
// return InkWell(
// onTap: () => context.read<DiaryCubit>().finishDiaryDay(
// diaryId: widget.diaryDay.id,
// date: widget.diaryDay.date,
// ),
// borderRadius: BorderRadius.circular(20),
// child: Ink(
// padding: EdgeInsets.symmetric(horizontal: 8),
// width: MediaQuery.of(context).size.width - 16,
// decoration: BoxDecoration(
// color: appTheme.blueA400,
// borderRadius: BorderRadius.circular(20),
// ),
// child: Container(
// padding: EdgeInsets.all(16),
// alignment: Alignment.center,
// child: Text(
// 'Закончить день',
// style: TextStyle(
// color: Colors.white,
// fontSize: 18,
// fontWeight: FontWeight.w600,
// ),
// ),
// ),
// ),
// );
// }
// }

View File

@@ -225,7 +225,7 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
child: CustomTextFormField(
hintText: 'Калории',
controller: energyController,
keyboardType: TextInputType.number,
keyboardType: TextInputType.numberWithOptions(),
),
),
const SizedBox(width: 8),
@@ -233,7 +233,7 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
child: CustomTextFormField(
hintText: 'Белки',
controller: proteinsController,
keyboardType: TextInputType.number,
keyboardType: TextInputType.numberWithOptions(),
),
),
],
@@ -246,7 +246,7 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
child: CustomTextFormField(
hintText: 'Жиры',
controller: fatsController,
keyboardType: TextInputType.number,
keyboardType: TextInputType.numberWithOptions(),
),
),
const SizedBox(width: 8),
@@ -254,7 +254,7 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
child: CustomTextFormField(
hintText: 'Углеводы',
controller: carbohydratesController,
keyboardType: TextInputType.number,
keyboardType: TextInputType.numberWithOptions(),
),
),
],

View File

@@ -0,0 +1,54 @@
import 'package:eatvio_app/core/theme/theme_helper.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:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MealButtonWidget extends StatefulWidget {
final int mealNumber;
const MealButtonWidget({
super.key,
required this.mealNumber,
});
@override
State<MealButtonWidget> createState() => _MealButtonWidgetState();
}
class _MealButtonWidgetState extends State<MealButtonWidget> {
@override
Widget build(BuildContext context) {
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (context, state) {
if (state is DiaryStateLoaded) {
return InkWell(
onTap: () {
context.read<DiaryCubit>().setSelectedMeal(widget.mealNumber);
},
borderRadius: BorderRadius.circular(100),
child: Ink(
width: MediaQuery.of(context).size.width / 5 - (96 / 5),
height: 32,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: state.selectedMeal == widget.mealNumber ? appTheme.blueA400 : Color(0xFFF1F2F6),
),
child: Center(
child: Text(
'${widget.mealNumber}',
style: TextStyle(
color: state.selectedMeal == widget.mealNumber ? Colors.white : Color(0xFF6D6F71),
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
);
}
return const SizedBox.shrink();
},
);
}
}

View File

@@ -0,0 +1,61 @@
import 'package:eatvio_app/features/diary/presentation/widgets/add_product_button_widget.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/meal_button_widget.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/photo_gallery_widget.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/products_list_widget.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/satiety_rating_widget.dart';
import 'package:eatvio_app/features/diary/presentation/widgets/time_of_meal_picker_widget.dart';
import 'package:flutter/material.dart';
class MealWidget extends StatefulWidget {
const MealWidget({super.key});
@override
State<MealWidget> createState() => _MealWidgetState();
}
class _MealWidgetState extends State<MealWidget> {
@override
Widget build(BuildContext context) {
return Material(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Прием пищи',
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MealButtonWidget(mealNumber: 1),
MealButtonWidget(mealNumber: 2),
MealButtonWidget(mealNumber: 3),
MealButtonWidget(mealNumber: 4),
MealButtonWidget(mealNumber: 5),
],
),
const SizedBox(height: 12),
TimeOfMealPickerWidget(),
const SizedBox(height: 12),
ProductsListWidget(),
const SizedBox(height: 12),
AddProductButtonWidget(),
const SizedBox(height: 12),
PhotoGalleryWidget(),
const SizedBox(height: 12),
SatietyRatingWidget(),
],
),
),
);
}
}

View File

@@ -0,0 +1,200 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/diary/models/diary_day_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:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:image_picker/image_picker.dart';
class PhotoGalleryWidget extends StatefulWidget {
const PhotoGalleryWidget({super.key});
@override
State<PhotoGalleryWidget> createState() => _PhotoGalleryWidgetState();
}
class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
late final DiaryCubit diaryCubit;
int currentSelectedPhotoIndex = 0;
@override
void initState() {
super.initState();
diaryCubit = context.read<DiaryCubit>();
}
showImagePicker(DiaryDayModel diaryDay) {
showCupertinoModalPopup<void>(
context: context,
builder: (context) => CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
onPressed: () async {
Navigator.of(context).pop();
final picker = ImagePicker();
XFile? image = await picker.pickImage(source: ImageSource.camera);
if (image != null)
diaryCubit.uploadPhotos(
files: [File(image.path)],
diaryId: diaryDay.id,
);
},
child: const Row(
children: const [
Icon(Icons.photo_camera),
SizedBox(width: 8),
Text('Фото'),
],
),
),
CupertinoActionSheetAction(
onPressed: () async {
Navigator.of(context).pop();
final picker = ImagePicker();
List<XFile>? images = await picker.pickMultiImage();
if (images.isNotEmpty)
diaryCubit.uploadPhotos(
files: images.map((f) => File(f.path)).toList(),
diaryId: diaryDay.id,
);
},
child: const Row(
children: const [
Icon(Icons.photo),
SizedBox(width: 8),
Text('Галерея'),
],
),
),
],
cancelButton: CupertinoActionSheetAction(
onPressed: () => Navigator.of(context).pop(),
isDestructiveAction: true,
child: const Text('Отмена'),
),
),
);
}
@override
Widget build(BuildContext context) {
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (context, state) {
if (state is DiaryStateLoaded) {
int mealIndex = state.selectedMeal - 1;
if (state.diary.meals[mealIndex].photos.isEmpty) {
return GestureDetector(
onTap: () => showImagePicker(state.diary),
child: SvgPicture.asset(
width: MediaQuery.of(context).size.width - 32,
'assets/no_one_image_was_added.svg',
),
);
} else {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
alignment: Alignment.topRight,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + state.diary.meals[mealIndex].photos[currentSelectedPhotoIndex].path,
),
),
Padding(
padding: const EdgeInsets.all(12),
child: IconButton.filled(
style: IconButton.styleFrom(
backgroundColor: Colors.white,
elevation: 0,
),
onPressed: () async {
await diaryCubit.deletePhoto(
photo: state.diary.meals[mealIndex].photos[currentSelectedPhotoIndex],
diaryId: state.diary.id,
);
setState(() {
if (currentSelectedPhotoIndex == state.diary.meals[mealIndex].photos.length - 1 && currentSelectedPhotoIndex != 0) {
currentSelectedPhotoIndex = state.diary.meals[mealIndex].photos.length - 2;
}
});
},
icon: Icon(
Icons.close_rounded,
color: Colors.grey,
),
),
),
],
),
const SizedBox(height: 8),
SingleChildScrollView(
physics: const BouncingScrollPhysics(),
scrollDirection: Axis.horizontal,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
for (int i = 0; i < state.diary.meals[mealIndex].photos.length + 1; i++) ...[
if (i == state.diary.meals[mealIndex].photos.length)
Padding(
padding: const EdgeInsets.only(left: 8),
child: InkWell(
onTap: () => showImagePicker(state.diary),
borderRadius: BorderRadius.circular(8),
child: Ink(
height: 52,
width: 52,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Color(0xFFF1F2F6),
),
child: Icon(
Icons.add_rounded,
color: appTheme.blueA400,
),
),
),
),
if (i < state.diary.meals[mealIndex].photos.length)
GestureDetector(
onTap: () {
setState(() {
currentSelectedPhotoIndex = i;
});
},
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: SizedBox(
height: 52,
width: 52,
child: CachedNetworkImage(
imageUrl: baseUrl + '/storage/' + state.diary.meals[mealIndex].photos[i].path,
fit: BoxFit.fitWidth,
),
),
),
),
if (i < state.diary.meals[mealIndex].photos.length - 1) const SizedBox(width: 8),
],
],
),
),
],
);
}
}
return const SizedBox.shrink();
},
);
}
}

View File

@@ -0,0 +1,67 @@
import 'package:eatvio_app/features/diary/models/diary_day_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/add_product_modal.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ProductWidget extends StatefulWidget {
final Portion portion;
const ProductWidget({
super.key,
required this.portion,
});
@override
State<ProductWidget> createState() => _ProductWidgetState();
}
class _ProductWidgetState extends State<ProductWidget> {
late final DiaryCubit diaryCubit;
late String portionName;
@override
void initState() {
super.initState();
diaryCubit = context.read<DiaryCubit>();
portionName = widget.portion == 'gr' ? 'гр' : 'шт';
}
updateProductInMeal(DiaryStateLoaded state, Portion portion) {
return showModalBottomSheet(
useRootNavigator: true,
context: context,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
),
isScrollControlled: true,
builder: (context) => AddProductModal(
state: state,
portion: portion,
diaryCubit: diaryCubit,
),
);
}
@override
Widget build(BuildContext context) {
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (context, state) {
if (state is DiaryStateLoaded) {
return ListTile(
onTap: () => updateProductInMeal(state, widget.portion),
trailing: Icon(Icons.more_vert_rounded),
title: Text(widget.portion.productName),
subtitle: Text('${widget.portion.quantity} ${widget.portion.portion == 'gr' ? 'гр' : 'шт'}'),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
tileColor: Color(0xFFF6FAFF),
);
}
return const SizedBox.shrink();
},
);
}
}

View File

@@ -0,0 +1,35 @@
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/product_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ProductsListWidget extends StatefulWidget {
const ProductsListWidget({super.key});
@override
State<ProductsListWidget> createState() => _ProductsListWidgetState();
}
class _ProductsListWidgetState extends State<ProductsListWidget> {
@override
Widget build(BuildContext context) {
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (BuildContext context, DiaryState state) {
if (state is DiaryStateLoaded) {
int mealIndex = state.selectedMeal - 1;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
for (int i = 0; i < state.diary.meals[mealIndex].portions.length; i++) ...[
ProductWidget(portion: state.diary.meals[mealIndex].portions[i]),
i < state.diary.meals[mealIndex].portions.length - 1 ? const SizedBox(height: 8) : const SizedBox.shrink(),
],
],
);
}
return const SizedBox.shrink();
},
);
}
}

View File

@@ -1,187 +1,199 @@
import 'package:easy_debounce/easy_debounce.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: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 List<int> currentSelectedSatietyRating;
final int diaryId;
final int meal;
const SatietyRatingWidget({
super.key,
required this.currentSelectedSatietyRating,
required this.diaryId,
required this.meal,
});
@override
State<SatietyRatingWidget> createState() => _SatietyRatingWidgetState();
}
class _SatietyRatingWidgetState extends State<SatietyRatingWidget> {
late double currentSelectedRating;
bool isFirstFetch = true;
@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: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1,
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[widget.meal - 1].toDouble() + 1 == 1 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFFC82222),
border: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1 == 1
? Border.all(
width: 2,
color: Color(0xFFAA3333),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1 == 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[widget.meal - 1].toDouble() + 1 == 2 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFFFF3E3E),
border: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1 == 2
? Border.all(
width: 2,
color: Color(0xFF550000),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1 == 2 ? BorderRadius.circular(2) : null,
),
);
case 2:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1 == 3 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFFF8C926),
border: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1 == 3
? Border.all(
width: 2,
color: Color(0xFF8C5D00),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1 == 3 ? BorderRadius.circular(2) : null,
),
);
case 3:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1 == 4 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFFFFF3BF),
border: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1 == 4
? Border.all(
width: 2,
color: Color(0xFFB5911B),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1 == 4 ? BorderRadius.circular(2) : null,
),
);
case 4:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1 == 5 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFF62E833),
border: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1 == 5
? Border.all(
width: 2,
color: Color(0xFF276918),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1 == 5 ? BorderRadius.circular(2) : null,
),
);
default:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1 == 6 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFF4DCE20),
border: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1 == 6
? Border.all(
width: 2,
color: Color(0xFF184D12),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: widget.currentSelectedSatietyRating[widget.meal - 1].toDouble() + 1 == 6
? BorderRadius.horizontal(
left: Radius.circular(2),
right: Radius.circular(8),
)
: BorderRadius.horizontal(
right: Radius.circular(8),
),
),
);
}
},
onRatingUpdate: (value) => context.read<DiaryCubit>().updateCurrentSelectedSatietyRating(
diaryId: widget.diaryId,
meal: widget.meal,
value: value.toInt(),
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (BuildContext context, DiaryState state) {
if (state is DiaryStateLoaded) {
if (isFirstFetch) {
currentSelectedRating = state.diary.meals[state.selectedMeal - 1].satiety.toDouble();
isFirstFetch = false;
}
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,
),
),
SvgPicture.asset(
'assets/icons/satiety_emoji_right.svg',
height: 32,
width: 32,
),
],
),
],
),
RatingBar.builder(
initialRating: state.diary.meals[state.selectedMeal - 1].satiety.toDouble(),
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: currentSelectedRating == 1 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFFC82222),
border: currentSelectedRating == 1
? Border.all(
width: 2,
color: Color(0xFFAA3333),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 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: currentSelectedRating == 2 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFFFF3E3E),
border: currentSelectedRating == 2
? Border.all(
width: 2,
color: Color(0xFF550000),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 2 ? BorderRadius.circular(2) : null,
),
);
case 2:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 3 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFFF8C926),
border: currentSelectedRating == 3
? Border.all(
width: 2,
color: Color(0xFF8C5D00),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 3 ? BorderRadius.circular(2) : null,
),
);
case 3:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 4 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFFFFF3BF),
border: currentSelectedRating == 4
? Border.all(
width: 2,
color: Color(0xFFB5911B),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 4 ? BorderRadius.circular(2) : null,
),
);
case 4:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 5 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFF62E833),
border: currentSelectedRating == 5
? Border.all(
width: 2,
color: Color(0xFF276918),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 5 ? BorderRadius.circular(2) : null,
),
);
default:
return Container(
width: MediaQuery.of(context).size.width / 6,
height: 24,
margin: currentSelectedRating == 6 ? EdgeInsets.symmetric(horizontal: 2) : null,
decoration: BoxDecoration(
color: Color(0xFF4DCE20),
border: currentSelectedRating == 6
? Border.all(
width: 2,
color: Color(0xFF184D12),
strokeAlign: BorderSide.strokeAlignOutside,
)
: null,
borderRadius: currentSelectedRating == 6
? BorderRadius.horizontal(
left: Radius.circular(2),
right: Radius.circular(8),
)
: BorderRadius.horizontal(
right: Radius.circular(8),
),
),
);
}
},
onRatingUpdate: (value) {
setState(() {
currentSelectedRating = value;
});
EasyDebounce.debounce(
'change-satiety',
Duration(milliseconds: 250),
() {
context.read<DiaryCubit>().fillSatiety(diaryId: state.diary.id, satiety: currentSelectedRating.toInt());
},
);
},
),
SvgPicture.asset(
'assets/icons/satiety_emoji_right.svg',
height: 32,
width: 32,
),
],
),
],
),
);
}
return const SizedBox.shrink();
},
);
}
}

View File

@@ -0,0 +1,133 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/custom_divider.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:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class TimeOfMealPickerWidget extends StatefulWidget {
const TimeOfMealPickerWidget({super.key});
@override
State<TimeOfMealPickerWidget> createState() => _TimeOfMealPickerWidgetState();
}
class _TimeOfMealPickerWidgetState extends State<TimeOfMealPickerWidget> {
late final DiaryCubit diaryCubit;
DateTime value = DateTime.now();
@override
void initState() {
super.initState();
diaryCubit = context.read<DiaryCubit>();
}
showTimePicker(int diaryId) {
showModalBottomSheet<void>(
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) {
value = newTime;
},
),
),
InkWell(
onTap: () {
Navigator.of(context).pop();
diaryCubit.fillTime(
diaryId: diaryId,
time: '${value.hour.toString().padLeft(2, '0')}:${value.minute.toString().padLeft(2, '0')}:00',
);
},
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) {
return BlocBuilder<DiaryCubit, DiaryState>(
builder: (context, state) {
if (state is DiaryStateLoaded) {
return Column(
children: [
CustomDivider(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Время приема пищи',
style: TextStyle(
color: Color(0xFF323F4B),
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
InkWell(
onTap: () => showTimePicker(state.diary.id),
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(
state.diary.meals[state.selectedMeal - 1].time.substring(0, state.diary.meals[state.selectedMeal - 1].time.length - 3),
style: TextStyle(
color: Color(0xFF6D6F71),
fontSize: 17,
fontWeight: FontWeight.w500,
),
),
),
),
),
],
),
),
CustomDivider(),
],
);
}
return const SizedBox.shrink();
},
);
}
}

View File

@@ -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/finish_diary_day_model.dart';
import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
import 'package:eatvio_app/features/diary/models/product_moderation_model.dart';
import 'package:eatvio_app/features/diary/services/diary_service.dart';
@@ -20,7 +21,7 @@ class DiaryRepository {
}
Future<NutritionDataModel> getNutritionData(int index) async {
return await diaryService.getNutrition(index: index);
return await diaryService.getNutrition(diary: index);
}
Future<void> addPortionToMeal({
@@ -29,13 +30,15 @@ class DiaryRepository {
required String productName,
required String portion,
required int quantity,
int? productNutritionalValueId,
}) async {
return await diaryService.addProductToMeal(
diaryId: diaryId,
mealId: mealId,
diary: diaryId,
typeNo: mealId,
productName: productName,
portion: portion,
quantity: quantity,
productNutritionalValueId: productNutritionalValueId,
);
}
@@ -46,16 +49,19 @@ class DiaryRepository {
required String productName,
required String portion,
required int quantity,
int? productNutritionalValueId,
}) async {
Map<String, Object?> data = ProductInMeal(
productName: productName,
portion: portion,
quantity: quantity,
).toJson();
data['product_nutritional_value_id'] = productNutritionalValueId;
return await diaryService.updateProductInMeal(
diaryId: diaryId,
mealId: mealId,
portionId: portionId,
productInMeal: ProductInMeal(
productName: productName,
portion: portion,
quantity: quantity,
),
diary: diaryId,
typeNo: mealId,
portionModel: portionId,
productInMeal: data,
);
}
@@ -65,9 +71,9 @@ class DiaryRepository {
required int portionId,
}) async {
return await diaryService.deleteProductFromMeal(
diaryId: diaryId,
mealId: mealId,
portionId: portionId,
diary: diaryId,
typeNo: mealId,
portionModel: portionId,
);
}
@@ -79,8 +85,8 @@ class DiaryRepository {
try {
for (int i = 0; i < files.length; i++) {
await diaryService.uploadPhotoOfMeal(
diaryId: diaryId,
mealId: mealId,
diary: diaryId,
typeNo: mealId,
photo: files[i],
);
}
@@ -97,9 +103,8 @@ class DiaryRepository {
try {
await diaryService.deletePhotoOfMeal(
diaryId: diaryId,
mealId: mealId,
id: photo.id,
path: photo.path,
typeNo: mealId,
userAttachment: photo.id,
);
} catch (e) {
rethrow;
@@ -113,12 +118,12 @@ class DiaryRepository {
required String comment,
}) async {
return await diaryService.finishDiaryDay(
diaryId: diaryId,
// finishDiaryDayModel: FinishDiaryDayModel(
// wakeUp: wakeUp,
// retire: retire,
// comment: comment,
// ),
diary: diaryId,
finishDiaryDayModel: FinishDiaryDayModel(
wakeUp: wakeUp,
retire: retire,
comment: comment,
),
);
}
@@ -142,11 +147,31 @@ class DiaryRepository {
Future<void> fillSatiety({
required int diaryId,
required int meal,
required int mealId,
required int satiety,
}) async {
try {
return await diaryService.fillSatiety(diaryId: diaryId, meal: meal, satiety: satiety);
return await diaryService.fillSatiety(
diary: diaryId,
typeNo: mealId,
satiety: satiety,
);
} catch (e) {
rethrow;
}
}
Future<void> fillTime({
required int diaryId,
required int mealId,
required String time,
}) async {
try {
return await diaryService.fillTime(
diary: diaryId,
typeNo: mealId,
time: time,
);
} catch (e) {
rethrow;
}

View File

@@ -1,6 +1,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/finish_diary_day_model.dart';
import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
import 'package:retrofit/retrofit.dart';
@@ -15,68 +16,76 @@ abstract class DiaryService {
@Path('date') required String date,
});
@GET('/api/diary/{diary_id}/show')
@GET('/api/diary/{diary}/show')
Future<DiaryDayWrapperModel> getDiaryDay({
@Path('diary_id') required int index,
@Path('diary') required int diary,
});
@GET('/api/diary/{diary_id}/dailyNutritionData')
@GET('/api/diary/{diary}/dailyNutritionData')
Future<NutritionDataModel> getNutrition({
@Path('diary_id') required int index,
@Path('diary') required int diary,
});
@POST('/api/diary/{diary_id}/meal/{meal_id}/photo/upload')
@POST('/api/diary/{diary}/meals/{type_no}/photo/upload')
@MultiPart()
Future<void> uploadPhotoOfMeal({
@Path('diary_id') required int diaryId,
@Path('meal_id') required int mealId,
@Path('diary') required int diary,
@Path('type_no') required int typeNo,
@Part(name: 'photo') required File photo,
});
@DELETE('/api/diary/{diary_id}/meal/{meal_id}/photo/delete')
@DELETE('/api/diary/{diary}/meals/{type_no}/photo/{user_attachment}/delete')
Future<void> deletePhotoOfMeal({
@Path('diary_id') required int diaryId,
@Path('meal_id') required int mealId,
@Query('id') required int id,
@Query('path') required String path,
@Path('diary') required int diaryId,
@Path('type_no') required int typeNo,
@Path('user_attachment') required int userAttachment,
});
@POST('/api/diary/{diary_id}/meal/{meal_id}/portions/create')
@POST('/api/diary/{diary}/meals/{type_no}/portion/create')
Future<void> addProductToMeal({
@Path('diary_id') required int diaryId,
@Path('meal_id') required int mealId,
@Path('diary') required int diary,
@Path('type_no') required int typeNo,
@Part(name: 'product_name') required String productName,
@Part(name: 'portion') required String portion,
@Part(name: 'quantity') required int quantity,
@Part(name: 'product_nutritional_value_id') int? productNutritionalValueId,
});
@PUT('/api/diary/{diary_id}/meal/{meal_id}/portions/{portion_id}/update')
@PUT('/api/diary/{diary}/meals/{type_no}/portions/{portionModel}/update')
Future<void> updateProductInMeal({
@Path('diary_id') required int diaryId,
@Path('meal_id') required int mealId,
@Path('portion_id') required int portionId,
@Body() required ProductInMeal productInMeal,
@Path('diary') required int diary,
@Path('type_no') required int typeNo,
@Path('portionModel') required int portionModel,
@Body() required Map<String, dynamic> productInMeal,
});
@DELETE('/api/diary/{diary_id}/meal/{meal_id}/portions/{portion_id}/delete')
@DELETE('/api/diary/{diary}/meals/{type_no}/portions/{portionModel}/delete')
Future<void> deleteProductFromMeal({
@Path('diary_id') required int diaryId,
@Path('meal_id') required int mealId,
@Path('portion_id') required int portionId,
@Path('diary') required int diary,
@Path('type_no') required int typeNo,
@Path('portionModel') required int portionModel,
});
@POST('/api/diary/{diary_id}/finishDay')
@POST('/api/diary/{diary}/finishDay')
Future<void> finishDiaryDay({
@Path('diary_id') required int diaryId,
@Path('diary') required int diary,
@Body() required FinishDiaryDayModel finishDiaryDayModel,
});
@POST('/api/diary/{diary_id}/meal/{meal}/satiety/fill')
@POST('/api/diary/{diary}/meals/{type_no}/satiety/fill')
Future<void> fillSatiety({
@Path('diary_id') required int diaryId,
@Path('meal') required int meal,
@Path('diary') required int diary,
@Path('type_no') required int typeNo,
@Field() required int satiety,
});
@POST('/api/diary/{diary}/meals/{type_no}/time/fill')
Future<void> fillTime({
@Path('diary') required int diary,
@Path('type_no') required int typeNo,
@Field() required String time,
});
@POST('/api/diary/product_nutritional_values/sendToModeration')
Future<void> sendProductToModeration({
@Body() required Map<String, dynamic> data,

View File

@@ -58,7 +58,7 @@ class _DiaryService implements DiaryService {
}
@override
Future<DiaryDayWrapperModel> getDiaryDay({required int index}) async {
Future<DiaryDayWrapperModel> getDiaryDay({required int diary}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
@@ -70,7 +70,7 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${index}/show',
'/api/diary/${diary}/show',
queryParameters: queryParameters,
data: _data,
)
@@ -91,7 +91,7 @@ class _DiaryService implements DiaryService {
}
@override
Future<NutritionDataModel> getNutrition({required int index}) async {
Future<NutritionDataModel> getNutrition({required int diary}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
@@ -103,7 +103,7 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${index}/dailyNutritionData',
'/api/diary/${diary}/dailyNutritionData',
queryParameters: queryParameters,
data: _data,
)
@@ -125,8 +125,8 @@ class _DiaryService implements DiaryService {
@override
Future<void> uploadPhotoOfMeal({
required int diaryId,
required int mealId,
required int diary,
required int typeNo,
required File photo,
}) async {
final _extra = <String, dynamic>{};
@@ -148,7 +148,7 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${diaryId}/meal/${mealId}/photo/upload',
'/api/diary/${diary}/meals/${typeNo}/photo/upload',
queryParameters: queryParameters,
data: _data,
)
@@ -163,15 +163,11 @@ class _DiaryService implements DiaryService {
@override
Future<void> deletePhotoOfMeal({
required int diaryId,
required int mealId,
required int id,
required String path,
required int typeNo,
required int userAttachment,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{
r'id': id,
r'path': path,
};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<void>(Options(
@@ -181,7 +177,7 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${diaryId}/meal/${mealId}/photo/delete',
'/api/diary/${diaryId}/meals/${typeNo}/photo/${userAttachment}/delete',
queryParameters: queryParameters,
data: _data,
)
@@ -195,14 +191,16 @@ class _DiaryService implements DiaryService {
@override
Future<void> addProductToMeal({
required int diaryId,
required int mealId,
required int diary,
required int typeNo,
required String productName,
required String portion,
required int quantity,
int? productNutritionalValueId,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
queryParameters.removeWhere((k, v) => v == null);
final _headers = <String, dynamic>{};
final _data = FormData();
_data.fields.add(MapEntry(
@@ -217,6 +215,12 @@ class _DiaryService implements DiaryService {
'quantity',
quantity.toString(),
));
if (productNutritionalValueId != null) {
_data.fields.add(MapEntry(
'product_nutritional_value_id',
productNutritionalValueId.toString(),
));
}
final _options = _setStreamType<void>(Options(
method: 'POST',
headers: _headers,
@@ -224,7 +228,7 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${diaryId}/meal/${mealId}/portions/create',
'/api/diary/${diary}/meals/${typeNo}/portion/create',
queryParameters: queryParameters,
data: _data,
)
@@ -238,15 +242,16 @@ class _DiaryService implements DiaryService {
@override
Future<void> updateProductInMeal({
required int diaryId,
required int mealId,
required int portionId,
required ProductInMeal productInMeal,
required int diary,
required int typeNo,
required int portionModel,
required Map<String, dynamic> productInMeal,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = productInMeal;
final _data = <String, dynamic>{};
_data.addAll(productInMeal);
final _options = _setStreamType<void>(Options(
method: 'PUT',
headers: _headers,
@@ -254,7 +259,7 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${diaryId}/meal/${mealId}/portions/${portionId}/update',
'/api/diary/${diary}/meals/${typeNo}/portions/${portionModel}/update',
queryParameters: queryParameters,
data: _data,
)
@@ -268,9 +273,9 @@ class _DiaryService implements DiaryService {
@override
Future<void> deleteProductFromMeal({
required int diaryId,
required int mealId,
required int portionId,
required int diary,
required int typeNo,
required int portionModel,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
@@ -283,7 +288,7 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${diaryId}/meal/${mealId}/portions/${portionId}/delete',
'/api/diary/${diary}/meals/${typeNo}/portions/${portionModel}/delete',
queryParameters: queryParameters,
data: _data,
)
@@ -296,11 +301,14 @@ class _DiaryService implements DiaryService {
}
@override
Future<void> finishDiaryDay({required int diaryId}) async {
Future<void> finishDiaryDay({
required int diary,
required FinishDiaryDayModel finishDiaryDayModel,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _data = finishDiaryDayModel;
final _options = _setStreamType<void>(Options(
method: 'POST',
headers: _headers,
@@ -308,7 +316,7 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${diaryId}/finishDay',
'/api/diary/${diary}/finishDay',
queryParameters: queryParameters,
data: _data,
)
@@ -322,8 +330,8 @@ class _DiaryService implements DiaryService {
@override
Future<void> fillSatiety({
required int diaryId,
required int meal,
required int diary,
required int typeNo,
required int satiety,
}) async {
final _extra = <String, dynamic>{};
@@ -337,7 +345,36 @@ class _DiaryService implements DiaryService {
)
.compose(
_dio.options,
'/api/diary/${diaryId}/meal/${meal}/satiety/fill',
'/api/diary/${diary}/meals/${typeNo}/satiety/fill',
queryParameters: queryParameters,
data: _data,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
)));
await _dio.fetch<void>(_options);
}
@override
Future<void> fillTime({
required int diary,
required int typeNo,
required String time,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = {'time': time};
final _options = _setStreamType<void>(Options(
method: 'POST',
headers: _headers,
extra: _extra,
)
.compose(
_dio.options,
'/api/diary/${diary}/meals/${typeNo}/time/fill',
queryParameters: queryParameters,
data: _data,
)

View File

@@ -140,7 +140,7 @@ class _FeedScreenState extends State<FeedScreen> {
children: [
_searchBar(),
SizedBox(width: 12),
_addItemButton(),
_addItemButton(feedCubit),
],
),
),
@@ -428,7 +428,7 @@ class _FeedScreenState extends State<FeedScreen> {
);
}
_addItemButton() {
_addItemButton(FeedCubit feedCubit) {
return InkWell(
onTap: () {
switch (_selectedChipIndex) {
@@ -436,7 +436,7 @@ class _FeedScreenState extends State<FeedScreen> {
GoRouter.of(context).pushNamed('recipeCreateScreen');
break;
default:
GoRouter.of(context).pushNamed('blogPostCreateScreen');
GoRouter.of(context).pushNamed('blogPostCreateScreen', extra: feedCubit);
}
},
borderRadius: BorderRadius.circular(64),

View File

@@ -23,8 +23,8 @@ class WeightChartWidget extends StatefulWidget {
class _WeightChartWidgetState extends State<WeightChartWidget> {
late FixedExtentScrollController weightKiloController;
late FixedExtentScrollController weightGramsController;
late int weightKilo;
late int weightGrams;
int weightKilo = 20;
int weightGrams = 0;
@override
Widget build(BuildContext context) {

View File

@@ -21,6 +21,8 @@ class _CredentialsScreenState extends State<CredentialsScreen> {
final passwordConfirmationController = TextEditingController();
bool hidePassword = true;
@override
void initState() {
super.initState();
@@ -55,10 +57,21 @@ class _CredentialsScreenState extends State<CredentialsScreen> {
Text(s.password),
SizedBox(height: 4),
CustomTextFormField(
obscureText: true,
obscureText: hidePassword,
controller: passwordController,
hintText: 'Введите пароль',
textInputAction: TextInputAction.next,
suffix: IconButton(
onPressed: () {
setState(() {
hidePassword = !hidePassword;
});
},
icon: Icon(
hidePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined,
color: Colors.grey,
),
),
onChanged: (newValue) {
final registrationCubit = context.read<RegistrationCubit>();
registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(password: newValue));
@@ -68,10 +81,21 @@ class _CredentialsScreenState extends State<CredentialsScreen> {
Text(s.password_confirmation),
SizedBox(height: 4),
CustomTextFormField(
obscureText: true,
obscureText: hidePassword,
controller: passwordConfirmationController,
textInputAction: TextInputAction.done,
hintText: 'Подтвердите пароль',
suffix: IconButton(
onPressed: () {
setState(() {
hidePassword = !hidePassword;
});
},
icon: Icon(
hidePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined,
color: Colors.grey,
),
),
onChanged: (newValue) {
final registrationCubit = context.read<RegistrationCubit>();
registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(passwordConfirmation: newValue));

View File

@@ -25,6 +25,7 @@ class _PhysicalFormScreenState extends State<PhysicalFormScreen> {
final waistCircumferenceController = TextEditingController();
final hipCircumferenceController = TextEditingController();
final heightController = TextEditingController();
final weightController = TextEditingController();
@override
void initState() {
@@ -33,6 +34,7 @@ class _PhysicalFormScreenState extends State<PhysicalFormScreen> {
waistCircumferenceController.text = widget.settingsEntity.profileDetailsModel.waistCircumference.toString();
hipCircumferenceController.text = widget.settingsEntity.profileDetailsModel.hipCircumference.toString();
heightController.text = widget.settingsEntity.profileDetailsModel.height.toString();
weightController.text = widget.settingsEntity.profileDetailsModel.weight.toString();
}
@override
@@ -83,6 +85,15 @@ class _PhysicalFormScreenState extends State<PhysicalFormScreen> {
hintText: 'см',
textInputAction: TextInputAction.done,
),
SizedBox(height: 16),
Text('Вес'),
SizedBox(height: 4),
CustomTextFormField(
inputFormatters: [digitsOnly],
controller: weightController,
hintText: 'кг',
textInputAction: TextInputAction.done,
),
],
),
),
@@ -98,6 +109,7 @@ class _PhysicalFormScreenState extends State<PhysicalFormScreen> {
waistCircumference: int.parse(waistCircumferenceController.text),
hipCircumference: int.parse(hipCircumferenceController.text),
height: int.parse(heightController.text),
weight: double.parse(weightController.text),
),
);
GoRouter.of(context).pop();

View File

@@ -11,10 +11,13 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:keyboard_dismisser/keyboard_dismisser.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
await di.init();
await Future.delayed(Duration(seconds: 1), () => FlutterNativeSplash.remove());
runApp(MyApp());
}

View File

@@ -1,6 +1,6 @@
name: eatvio_app
description: A new Flutter project.
version: 1.0.6+21
version: 1.0.8+23
publish_to: "none"
environment:
@@ -48,7 +48,7 @@ dependencies:
file_picker: ^8.1.4
audioplayers: ^6.1.0
intl: ^0.19.0
rive: ^0.13.17
rive: ^0.13.18
mask_text_input_formatter: ^2.9.0
lottie: ^3.1.3
flutter_portal: ^1.1.4
@@ -56,6 +56,7 @@ dependencies:
flutter_rating_bar: ^4.0.1
flutter_image_compress: ^2.3.0
image: ^4.3.0
flutter_native_splash: ^2.4.3
dev_dependencies:
build_runner: ^2.4.13
@@ -73,8 +74,24 @@ flutter:
- assets/images/
- assets/audio/
- assets/icons/
- assets/splash/
- assets/rive_animations/
- assets/lottie_animations/
flutter_intl:
enabled: true
flutter_native_splash:
android: true
ios: true
color: '#FFFFFF'
image_android: 'assets/splash/splash.png'
image_dark_android: 'assets/splash/splash.png'
image_ios: 'assets/splash/splash.png'
image_dark_ios: 'assets/splash/splash.png'
branding_android: 'assets/splash/branding.png'
branding_dark_android: 'assets/splash/branding.png'
android_12:
image: 'assets/splash/splash.png'
image_dark: 'assets/splash/splash.png'
branding: 'assets/splash/branding.png'