diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index e8e16098..a6d70a2a 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -1,15 +1,15 @@ -name: Android Release +name: Eatvio App Android Test -on: [ push, pull_request ] +on: [ pull_request ] env: - flutter_version: "3.22.0" - java_version: "12.x" + flutter_version: "3.24.5" + java_version: "21.x" flutter_channel: "stable" jobs: build: - runs-on: ubuntu-latest + runs-on: [ dev-office ] steps: - uses: actions/checkout@v3 @@ -17,9 +17,9 @@ jobs: - name: Set up JDK uses: actions/setup-java@v3 with: - distribution: 'zulu' + distribution: "zulu" java-version: ${{ env.java_version }} - cache: 'gradle' + id: setup-jdk - name: Install Android SDK run: | @@ -31,18 +31,17 @@ jobs: mv cmdline-tools/* $HOME/Android/Sdk/cmdline-tools/latest/ export ANDROID_SDK_ROOT=$HOME/Android/Sdk export PATH=$PATH:$HOME/Android/Sdk/cmdline-tools/latest/bin - sdkmanager "platform-tools" "platforms;android-30" "build-tools;30.0.3" + yes | sdkmanager --licenses 2>/dev/null || true - name: Set up Android environment variables run: | echo "ANDROID_SDK_ROOT=$HOME/Android/Sdk" >> $GITHUB_ENV - echo "PATH=$PATH:$HOME/Android/Sdk/platform-tools:$HOME/Android/Sdk/build-tools/30.0.3" >> $GITHUB_ENV - echo "ANDROID_HOME=$HOME/Android/Sdk" >> $GITHUB_ENV + echo "PATH=$PATH:$HOME/Android/Sdk/cmdline-tools/latest/bin:$HOME/Android/Sdk/platform-tools:$HOME/Android/Sdk/build-tools/30.0.3" >> $GITHUB_ENV + echo "ANDROID_HOME=$HOME/Android/Sdk" >> $GITHUB_ENV - - uses: actions/cache@v2 - with: - path: ${{ runner.tool_cache }}/flutter - key: flutter-${{ env.flutter_version }}-${{ env.flutter_channel }} + - name: Install jq + run: | + apt-get install -y jq - id: flutter-action uses: actions/flutter-action@v2 @@ -51,21 +50,11 @@ jobs: flutter-version: ${{ env.flutter_version }} cache: true - - name: show cache keys - run: | - echo CACHE-PATH=${{ steps.flutter-action.outputs.CACHE-PATH }} - echo CACHE-KEY=${{ steps.flutter-action.outputs.CACHE-KEY }} - echo CHANNEL=${{ steps.flutter-action.outputs.CHANNEL }} - echo VERSION=${{ steps.flutter-action.outputs.VERSION }} - echo ARCHITECTURE=${{ steps.flutter-action.outputs.ARCHITECTURE }} - echo PUB-CACHE-PATH=${{ steps.flutter-action.outputs.PUB-CACHE-PATH }} - echo PUB-CACHE-KEY=${{ steps.flutter-action.outputs.PUB-CACHE-KEY }} - shell: bash - - - name: fix dir permissions + - name: Fix dir permissions run: | git config --global --add safe.directory /opt/hostedtoolcache/flutter/${{ env.flutter_channel }}-${{ env.flutter_version }}-${{ steps.flutter-action.outputs.ARCHITECTURE }} - flutter --version + flutter config --android-sdk="$ANDROID_HOME" + flutter doctor -v - name: Get dependencies run: flutter pub get @@ -73,15 +62,13 @@ jobs: - name: Native Analyze run: flutter analyze - - name: Run Code Metrics - run: flutter pub run dart_code_metrics:metrics lib --disable-sunset-warning --fatal-warnings --fatal-performance - - name: Test run: | - flutter test + flutter test - name: Build apk - run: flutter build apk + run: | + flutter build apk - name: Rename and move apk run: | @@ -89,3 +76,10 @@ jobs: APK_NAME="eatvio_app_${TIMESTAMP}.apk" mv build/app/outputs/flutter-apk/app-release.apk $APK_NAME echo "APK build finished $APK_NAME" + echo "APK_NAME=$APK_NAME" >> $GITHUB_ENV + + - name: Upload APK artifact + uses: actions/upload-artifact@v3 + with: + name: ${{ env.APK_NAME }} + path: ${{ env.APK_NAME }} diff --git a/.gitea/workflows/docker-run-tests.yml b/.gitea/workflows/docker-run-tests.yml new file mode 100644 index 00000000..99cc866e --- /dev/null +++ b/.gitea/workflows/docker-run-tests.yml @@ -0,0 +1,57 @@ +name: Eatvio App Android Test on custom docker + +on: [ pull_request ] + +env: + flutter_version: "3.24.5" + java_version: "21.x" + flutter_channel: "stable" + my_local_cache_dir: "/opt/hostedtoolcache/" + ANDROID_SDK_ROOT: "/opt/hostedtoolcache/Android/Sdk" + PUB_CACHE: "/opt/hostedtoolcache/.pub-cache" + +jobs: + build: + runs-on: [ dev-office-flutter ] + + steps: + - uses: actions/checkout@v3 + + - name: Set up Android environment variables + id: "android-env-setup" + run: | + echo "ANDROID_SDK_ROOT=${{ env.my_local_cache_dir }}Android/Sdk" >> $GITHUB_ENV + echo "PATH=$PATH:${{ env.my_local_cache_dir }}Android/Sdk/cmdline-tools/latest/bin:${{ env.my_local_cache_dir }}Android/Sdk/platform-tools:${{ env.my_local_cache_dir }}Android/Sdk/build-tools/30.0.3:${{ env.my_local_cache_dir }}Android/Sdk/build-tools/34.0.0" >> $GITHUB_ENV + echo "ANDROID_HOME=${{ env.my_local_cache_dir }}Android/Sdk" >> $GITHUB_ENV + + - name: Fix dir permissions + run: | + git config --global --add safe.directory /opt/hostedtoolcache/flutter/${{ env.flutter_channel }}-${{ env.flutter_version }}-${{ steps.flutter-action.outputs.ARCHITECTURE }} + flutter config --android-sdk="$ANDROID_HOME" + flutter doctor -v + + - name: Get dependencies + run: flutter pub get + + - name: Native Analyze + run: flutter analyze + + - name: Test + run: flutter test + + # - name: Build apk + # run: flutter build apk + + # - name: Rename and move apk + # run: | + # TIMESTAMP=$(TZ=Europe/Moscow date +'%Y-%m-%d-%H-%M') + # APK_NAME="eatvio_app_${TIMESTAMP}.apk" + # mv build/app/outputs/flutter-apk/app-release.apk $APK_NAME + # echo "APK build finished $APK_NAME" + # echo "APK_NAME=$APK_NAME" >> $GITHUB_ENV + + # - name: Upload APK artifact + # uses: actions/upload-artifact@v3 + # with: + # name: ${{ env.APK_NAME }} + # path: ${{ env.APK_NAME }} diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 0b639038..24078471 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -3,56 +3,61 @@ name: Eatvio App Android Test on: [ pull_request ] env: - flutter_version: "3.24.4" + flutter_version: "3.24.5" java_version: "21.x" flutter_channel: "stable" + my_local_cache_dir: "/opt/hostedtoolcache/" + ANDROID_SDK_ROOT: "/opt/hostedtoolcache/Android/Sdk" jobs: build: - runs-on: ubuntu-latest + runs-on: [ dev-office ] steps: - uses: actions/checkout@v3 + - name: Cache Android SDK + id: cache-sdk + uses: "https://gitea.com/actions/cache@v4" + with: + path: ${{ env.my_local_cache_dir }} + key: ${{ runner.os }}-cache-sdk + - name: Set up JDK uses: actions/setup-java@v3 with: distribution: "zulu" java-version: ${{ env.java_version }} - cache: "gradle" + cache: gradle - - name: Install Android SDK + - name: Install utils for Android SDK run: | apt-get update - apt-get install -y openjdk-11-jdk wget + apt-get install -y openjdk-11-jdk wget jq clang cmake ninja-build libgtk-3-dev + + - name: Install Android SDK + if: steps.cache-sdk.outputs.cache-hit != 'true' + run: | wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip -O commandlinetools.zip unzip commandlinetools.zip - mkdir -p $HOME/Android/Sdk/cmdline-tools/latest - mv cmdline-tools/* $HOME/Android/Sdk/cmdline-tools/latest/ - export ANDROID_SDK_ROOT=$HOME/Android/Sdk - export PATH=$PATH:$HOME/Android/Sdk/cmdline-tools/latest/bin + mkdir -p ${{ env.my_local_cache_dir }}Android/Sdk/cmdline-tools/latest + mv cmdline-tools/* ${{ env.my_local_cache_dir }}Android/Sdk/cmdline-tools/latest/ + export ANDROID_SDK_ROOT=${{ env.my_local_cache_dir }}Android/Sdk + export PATH=$PATH:${{ env.my_local_cache_dir }}Android/Sdk/cmdline-tools/latest/bin yes | sdkmanager --licenses 2>/dev/null || true + sdkmanager --list | egrep "platform-tools|build-tools|ndk" + sdkmanager "platform-tools" + sdkmanager "platform-tools" "platforms;android-35" "platforms;android-34" "platforms;android-33" "platforms;android-31" + sdkmanager "build-tools;34.0.0" + sdkmanager "ndk;25.1.8937393" + sdkmanager --install "cmake;3.22.1" - name: Set up Android environment variables + id: "android-env-setup" run: | - echo "ANDROID_SDK_ROOT=$HOME/Android/Sdk" >> $GITHUB_ENV - echo "PATH=$PATH:$HOME/Android/Sdk/cmdline-tools/latest/bin:$HOME/Android/Sdk/platform-tools:$HOME/Android/Sdk/build-tools/30.0.3" >> $GITHUB_ENV - echo "ANDROID_HOME=$HOME/Android/Sdk" >> $GITHUB_ENV - - - name: Debug Environment Variables - run: | - echo "ANDROID_SDK_ROOT: $ANDROID_SDK_ROOT" - echo "ANDROID_HOME: $ANDROID_HOME" - echo "PATH: $PATH" - - - uses: actions/cache@v2 - with: - path: ${{ runner.tool_cache }}/flutter - key: flutter-${{ env.flutter_version }}-${{ env.flutter_channel }} - - - name: Install jq - run: | - apt-get install -y jq + echo "ANDROID_SDK_ROOT=${{ env.my_local_cache_dir }}Android/Sdk" >> $GITHUB_ENV + echo "PATH=$PATH:${{ env.my_local_cache_dir }}Android/Sdk/cmdline-tools/latest/bin:${{ env.my_local_cache_dir }}Android/Sdk/platform-tools:${{ env.my_local_cache_dir }}Android/Sdk/build-tools/30.0.3:${{ env.my_local_cache_dir }}Android/Sdk/build-tools/34.0.0" >> $GITHUB_ENV + echo "ANDROID_HOME=${{ env.my_local_cache_dir }}Android/Sdk" >> $GITHUB_ENV - id: flutter-action uses: actions/flutter-action@v2 @@ -60,23 +65,30 @@ jobs: channel: ${{ env.flutter_channel }} flutter-version: ${{ env.flutter_version }} cache: true + cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" # optional, change this to force refresh cache + cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" # optional, change this to specify the cache path + pub-cache-key: "flutter-pub:os:-:channel:-:version:-:arch:-:hash:" # optional, change this to force refresh cache of dart pub get dependencies + pub-cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" # optional, change this to specify the cache path - - name: Show cache keys - run: | - echo CACHE-PATH=${{ steps.flutter-action.outputs.CACHE-PATH }} - echo CACHE-KEY=${{ steps.flutter-action.outputs.CACHE-KEY }} - echo CHANNEL=${{ steps.flutter-action.outputs.CHANNEL }} - echo VERSION=${{ steps.flutter-action.outputs.VERSION }} - echo ARCHITECTURE=${{ steps.flutter-action.outputs.ARCHITECTURE }} - echo PUB-CACHE-PATH=${{ steps.flutter-action.outputs.PUB-CACHE-PATH }} - echo PUB-CACHE-KEY=${{ steps.flutter-action.outputs.PUB-CACHE-KEY }} - shell: bash + # - name: Print flutter-action outputs + # shell: bash + # run: | + # echo CACHE-PATH=${{ steps.flutter-action.outputs.CACHE-PATH }} + # echo CACHE-KEY=${{ steps.flutter-action.outputs.CACHE-KEY }} + # echo CHANNEL=${{ steps.flutter-action.outputs.CHANNEL }} + # echo VERSION=${{ steps.flutter-action.outputs.VERSION }} + # echo ARCHITECTURE=${{ steps.flutter-action.outputs.ARCHITECTURE }} + # echo PUB-CACHE-PATH=${{ steps.flutter-action.outputs.PUB-CACHE-PATH }} + # echo PUB-CACHE-KEY=${{ steps.flutter-action.outputs.PUB-CACHE-KEY }} + # echo "PATH is $PATH" + # echo "ANDROID_SDK_ROOT is $ANDROID_SDK_ROOT" + # echo "ANDROID_HOME is $ANDROID_HOME" - name: Fix dir permissions run: | git config --global --add safe.directory /opt/hostedtoolcache/flutter/${{ env.flutter_channel }}-${{ env.flutter_version }}-${{ steps.flutter-action.outputs.ARCHITECTURE }} flutter config --android-sdk="$ANDROID_HOME" - flutter doctor -v + flutter doctor -v - name: Get dependencies run: flutter pub get @@ -84,24 +96,19 @@ jobs: - name: Native Analyze run: flutter analyze - #- name: Run Code Metrics - # run: flutter pub run dart_code_metrics:metrics lib --disable-sunset-warning --fatal-warnings --fatal-performance - - name: Test - run: | - flutter test + run: flutter test - name: Build apk - run: | - flutter build apk + run: flutter build apk - name: Rename and move apk run: | - TIMESTAMP=$(date +'%Y-%m-%d-%H-%M') + TIMESTAMP=$(TZ=Europe/Moscow date +'%Y-%m-%d-%H-%M') APK_NAME="eatvio_app_${TIMESTAMP}.apk" mv build/app/outputs/flutter-apk/app-release.apk $APK_NAME echo "APK build finished $APK_NAME" - echo "APK_NAME=$APK_NAME" >> $GITHUB_ENV + echo "APK_NAME=$APK_NAME" >> $GITHUB_ENV - name: Upload APK artifact uses: actions/upload-artifact@v3 diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 5841cc1f..2496707b 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,3 @@ -#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 7454c98b..b335af4e 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -361,7 +361,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = VL68C36HYS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -369,6 +369,8 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Eatvio; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -377,6 +379,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); + MARKETING_VERSION = 2.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -504,7 +507,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = VL68C36HYS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -512,6 +515,8 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Eatvio; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -520,6 +525,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); + MARKETING_VERSION = 2.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -535,7 +541,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = VL68C36HYS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -543,6 +549,8 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Eatvio; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -551,6 +559,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); + MARKETING_VERSION = 2.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 8795a89d..9efe0f5f 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -27,7 +27,9 @@ UIMainStoryboardFile Main NSPhotoLibraryUsageDescription - UserPhotoUpload + Нужно для загрузки фото + NSCameraUsageDescription + Нужно для загрузки фото UIBackgroundModes fetch diff --git a/lib/app_router.dart b/lib/app_router.dart index 43c2e357..9d4df0ba 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -160,7 +160,7 @@ class AppRouter { GoRoute( path: '/blog-post-create-screen', name: 'blogPostCreateScreen', - builder: (context, state) => BlogPostCreateScreen.builder(context), + builder: (context, state) => BlogPostCreateScreen(), ), GoRoute( path: '/blog-post-screen', diff --git a/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart b/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart index d76b7bd4..d41a1967 100644 --- a/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart +++ b/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart @@ -1,10 +1,10 @@ import 'dart:async'; import 'dart:io'; import 'package:eatvio_app/old/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart'; -import 'package:eatvio_app/old/feed/blog_post_create_screen/models/blog_post_create_model.dart'; import 'package:eatvio_app/theme/app_decoration.dart'; import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:eatvio_app/widgets/custom_text_form_field.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; @@ -13,22 +13,13 @@ import 'package:image_picker/image_picker.dart'; class BlogPostCreateScreen extends StatefulWidget { const BlogPostCreateScreen({Key? key}) : super(key: key); - static Widget builder(BuildContext context) { - final blog_postID = ModalRoute.of(context)!.settings.arguments; - return BlocProvider( - create: (context) => BlogPostCreateBloc(BlogPostCreateState(blog_postModelObj: BlogPostCreateModel(blog_postID: blog_postID.toString()))) - ..add(BlogPostCreateInitialEvent()), - child: BlogPostCreateScreen(), - ); - } - @override State createState() => _BlogPostCreateScreenState(); } class _BlogPostCreateScreenState extends State { + final blogPostCreateBloc = BlogPostCreateBloc(BlogPostCreateState())..add(BlogPostCreateInitialEvent()); bool shrinkImage = false; - late StreamSubscription keyboardSubscription; @override @@ -49,24 +40,72 @@ class _BlogPostCreateScreenState extends State { } Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - if (state is BlogPostCreateInitialState) { - // Это начальное состояние, данные ещё не загружены. - return Container( - color: Colors.white, - ); - } else if (state is BlogPostCreateLoadedState) { - // Данные загружены, можно строить экран. - return _buildBlogPostCreate(context, state); - } else if (state is BlogPostCreateErrorState) { - // Произошла ошибка при загрузке данных. - return Center(child: Text(state.errorMessage)); - } else { - // Другие состояния, если они есть. - return Container(); // Пустой контейнер для необработанных состояний. - } - }, + return BlocProvider( + create: (BuildContext context) => blogPostCreateBloc, + child: BlocBuilder( + builder: (context, state) { + if (state is BlogPostCreateInitialState) { + // Это начальное состояние, данные ещё не загружены. + return Container( + color: Colors.white, + ); + } else if (state is BlogPostCreateLoadedState) { + // Данные загружены, можно строить экран. + return _buildBlogPostCreate(context, state); + } else if (state is BlogPostCreateErrorState) { + // Произошла ошибка при загрузке данных. + return Center(child: Text(state.errorMessage)); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ), + ); + } + + _showImagePicker() { + showCupertinoModalPopup( + context: context, + builder: (context) => CupertinoActionSheet( + actions: [ + CupertinoActionSheetAction( + onPressed: () async { + final picker = ImagePicker(); + XFile? image = await picker.pickImage(source: ImageSource.camera); + if (image != null) blogPostCreateBloc.add(UploadImages(image)); + Navigator.of(context).pop(); + }, + child: const Row( + children: const [ + Icon(Icons.photo_camera), + SizedBox(width: 8), + Text('Фото'), + ], + ), + ), + CupertinoActionSheetAction( + onPressed: () async { + final picker = ImagePicker(); + XFile? image = await picker.pickImage(source: ImageSource.gallery); + if (image != null) blogPostCreateBloc.add(UploadImages(image)); + Navigator.of(context).pop(); + }, + child: const Row( + children: const [ + Icon(Icons.photo), + SizedBox(width: 8), + Text('Галерея'), + ], + ), + ), + ], + cancelButton: CupertinoActionSheetAction( + onPressed: () => Navigator.of(context).pop(), + isDestructiveAction: true, + child: const Text('Отмена'), + ), + ), ); } @@ -164,15 +203,15 @@ class _BlogPostCreateScreenState extends State { ), ), if (state.image != null) - AnimatedContainer( + Container( width: shrinkImage ? 52 : MediaQuery.of(context).size.width - 32, - curve: Curves.easeIn, - duration: Duration(milliseconds: 150), child: ClipRRect( borderRadius: BorderRadius.circular(16), child: Image( image: FileImage( - File(state.image!.path), + File( + state.image!.path, + ), ), ), ), @@ -181,6 +220,14 @@ class _BlogPostCreateScreenState extends State { ), ); } + + addImage(BuildContext context, bool isLoading) { + if (!isLoading) { + return () { + _showImagePicker(); + }; + } + } } _submitForm( @@ -191,15 +238,3 @@ _submitForm( ) async { context.read().add(CreateBlogPost()); } - -addImage(BuildContext context, bool isLoading) { - if (!isLoading) { - return () async { - final picker = ImagePicker(); - XFile? image = await picker.pickImage(source: ImageSource.gallery); - if (image != null) { - context.read().add(UploadImages(image)); - } - }; - } -} diff --git a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart index ce8a8bca..ebab1850 100644 --- a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart +++ b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart @@ -62,157 +62,154 @@ class _BottomNavigationBarWidgetState extends State { final s = S.of(context); return Scaffold( body: widget.child, - bottomNavigationBar: SizedBox( - height: 58, - child: BottomNavigationBar( - type: BottomNavigationBarType.fixed, - onTap: _onItemTapped, - selectedFontSize: 10, - unselectedFontSize: 10, - selectedItemColor: appTheme.blueA400, - currentIndex: currentBottomNavBarIndex, - items: [ - BottomNavigationBarItem( - label: '', - icon: BlocProvider( - create: (context) => sl()..getMyProfile(), - child: BlocBuilder( - builder: (BuildContext context, ProfileAvatarState state) { - if (state is ProfileAvatarStateLoaded) { - return CircleAvatar( - backgroundImage: NetworkImage(state.profile.profilePhotoUrl), - radius: 16, - ); - } + bottomNavigationBar: BottomNavigationBar( + type: BottomNavigationBarType.fixed, + onTap: _onItemTapped, + selectedFontSize: 10, + unselectedFontSize: 10, + selectedItemColor: appTheme.blueA400, + currentIndex: currentBottomNavBarIndex, + items: [ + BottomNavigationBarItem( + label: '', + icon: BlocProvider( + create: (context) => sl()..getMyProfile(), + child: BlocBuilder( + builder: (BuildContext context, ProfileAvatarState state) { + if (state is ProfileAvatarStateLoaded) { return CircleAvatar( + backgroundImage: NetworkImage(state.profile.profilePhotoUrl), radius: 16, - backgroundColor: Colors.black12, ); - }, - ), - ), - ), - BottomNavigationBarItem( - label: 'Лента', - icon: Padding( - padding: const EdgeInsets.only(bottom: 2), - child: SvgPicture.asset( - colorFilter: ColorFilter.mode(Colors.grey, BlendMode.srcIn), - width: 20, - height: 20, - ImageConstant.icnFeed, - ), - ), - activeIcon: Padding( - padding: const EdgeInsets.only(bottom: 2), - child: SvgPicture.asset( - ImageConstant.icnFeed, - width: 20, - height: 20, - colorFilter: ColorFilter.mode( - appTheme.blueA400, - BlendMode.srcIn, - ), - ), - ), - ), - BottomNavigationBarItem( - label: s.diary, - icon: Padding( - padding: const EdgeInsets.only(bottom: 2), - child: SvgPicture.asset( - colorFilter: ColorFilter.mode(Colors.grey, BlendMode.srcIn), - ImageConstant.icnDiary, - width: 20, - height: 20, - ), - ), - activeIcon: Padding( - padding: const EdgeInsets.only(bottom: 2), - child: SvgPicture.asset( - ImageConstant.icnDiary, - width: 20, - height: 20, - colorFilter: ColorFilter.mode( - appTheme.blueA400, - BlendMode.srcIn, - ), - ), - ), - ), - BottomNavigationBarItem( - label: s.chat, - icon: BlocBuilder( - builder: (BuildContext context, NewMessagesState state) { - return badges.Badge( - position: badges.BadgePosition.topEnd(), - badgeAnimation: badges.BadgeAnimation.fade(), - badgeContent: Text( - (state is NewMessagesStateLoaded) ? state.messagesCount.toString() : '0', - style: TextStyle( - color: Colors.white, - fontSize: 8, - ), - ), - child: Padding( - padding: const EdgeInsets.only(bottom: 2), - child: SvgPicture.asset( - ImageConstant.icnChat, - width: 20, - height: 20, - colorFilter: currentBottomNavBarIndex == 3 - ? ColorFilter.mode( - appTheme.blueA400, - BlendMode.srcIn, - ) - : ColorFilter.mode(Colors.grey, BlendMode.srcIn), - ), - ), + } + return CircleAvatar( + radius: 16, + backgroundColor: Colors.black12, ); }, ), ), - BottomNavigationBarItem( - label: 'Уведомления', - icon: BlocBuilder( - builder: (BuildContext context, NotificationsState state) { - return badges.Badge( - position: badges.BadgePosition.topEnd(), - badgeAnimation: badges.BadgeAnimation.fade(), - badgeContent: Text( - (state is NotificationsStateLoaded) ? state.notificationsCount.toString() : '0', - style: TextStyle( - color: Colors.white, - fontSize: 8, - ), - ), - child: Padding( - padding: const EdgeInsets.only(bottom: 2), - child: SvgPicture.asset( - ImageConstant.icnBell, - colorFilter: currentBottomNavBarIndex == 4 - ? ColorFilter.mode( - appTheme.blueA400, - BlendMode.srcIn, - ) - : ColorFilter.mode(Colors.grey, BlendMode.srcIn), - width: 20, - height: 20, - ), - ), - ); - }, + ), + BottomNavigationBarItem( + label: 'Лента', + icon: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset( + colorFilter: ColorFilter.mode(Colors.grey, BlendMode.srcIn), + width: 20, + height: 20, + ImageConstant.icnFeed, ), ), - BottomNavigationBarItem( - label: '', - icon: SvgPicture.asset( - ImageConstant.imgLogo, - height: 32, + activeIcon: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset( + ImageConstant.icnFeed, + width: 20, + height: 20, + colorFilter: ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ), ), ), - ], - ), + ), + BottomNavigationBarItem( + label: s.diary, + icon: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset( + colorFilter: ColorFilter.mode(Colors.grey, BlendMode.srcIn), + ImageConstant.icnDiary, + width: 20, + height: 20, + ), + ), + activeIcon: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset( + ImageConstant.icnDiary, + width: 20, + height: 20, + colorFilter: ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ), + ), + ), + ), + BottomNavigationBarItem( + label: s.chat, + icon: BlocBuilder( + builder: (BuildContext context, NewMessagesState state) { + return badges.Badge( + position: badges.BadgePosition.topEnd(), + badgeAnimation: badges.BadgeAnimation.fade(), + badgeContent: Text( + (state is NewMessagesStateLoaded) ? state.messagesCount.toString() : '0', + style: TextStyle( + color: Colors.white, + fontSize: 8, + ), + ), + child: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset( + ImageConstant.icnChat, + width: 20, + height: 20, + colorFilter: currentBottomNavBarIndex == 3 + ? ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ) + : ColorFilter.mode(Colors.grey, BlendMode.srcIn), + ), + ), + ); + }, + ), + ), + BottomNavigationBarItem( + label: 'Уведомления', + icon: BlocBuilder( + builder: (BuildContext context, NotificationsState state) { + return badges.Badge( + position: badges.BadgePosition.topEnd(), + badgeAnimation: badges.BadgeAnimation.fade(), + badgeContent: Text( + (state is NotificationsStateLoaded) ? state.notificationsCount.toString() : '0', + style: TextStyle( + color: Colors.white, + fontSize: 8, + ), + ), + child: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset( + ImageConstant.icnBell, + colorFilter: currentBottomNavBarIndex == 4 + ? ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ) + : ColorFilter.mode(Colors.grey, BlendMode.srcIn), + width: 20, + height: 20, + ), + ), + ); + }, + ), + ), + BottomNavigationBarItem( + label: '', + icon: SvgPicture.asset( + ImageConstant.imgLogo, + height: 32, + ), + ), + ], ), ); } diff --git a/lib/features/diary/models/product_moderation_model.dart b/lib/features/diary/models/product_moderation_model.dart new file mode 100644 index 00000000..c28ffe64 --- /dev/null +++ b/lib/features/diary/models/product_moderation_model.dart @@ -0,0 +1,21 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'product_moderation_model.freezed.dart'; +part 'product_moderation_model.g.dart'; + +@freezed +class ProductModerationModel with _$ProductModerationModel { + const factory ProductModerationModel({ + required String name, + @JsonKey(name: 'energy_on_100gr') + required int energy, + @JsonKey(name: 'proteins_on_100gr') + required int proteins, + @JsonKey(name: 'fats_on_100gr') + required int fats, + @JsonKey(name: 'carbohydrates_on_100gr') + required int carbohydrates, + }) = _ProductModerationModel; + + factory ProductModerationModel.fromJson(Map json) => _$ProductModerationModelFromJson(json); +} diff --git a/lib/features/diary/models/product_moderation_model.freezed.dart b/lib/features/diary/models/product_moderation_model.freezed.dart new file mode 100644 index 00000000..9566e207 --- /dev/null +++ b/lib/features/diary/models/product_moderation_model.freezed.dart @@ -0,0 +1,270 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'product_moderation_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +ProductModerationModel _$ProductModerationModelFromJson( + Map json) { + return _ProductModerationModel.fromJson(json); +} + +/// @nodoc +mixin _$ProductModerationModel { + String get name => throw _privateConstructorUsedError; + @JsonKey(name: 'energy_on_100gr') + int get energy => throw _privateConstructorUsedError; + @JsonKey(name: 'proteins_on_100gr') + int get proteins => throw _privateConstructorUsedError; + @JsonKey(name: 'fats_on_100gr') + int get fats => throw _privateConstructorUsedError; + @JsonKey(name: 'carbohydrates_on_100gr') + int get carbohydrates => throw _privateConstructorUsedError; + + /// Serializes this ProductModerationModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ProductModerationModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProductModerationModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductModerationModelCopyWith<$Res> { + factory $ProductModerationModelCopyWith(ProductModerationModel value, + $Res Function(ProductModerationModel) then) = + _$ProductModerationModelCopyWithImpl<$Res, ProductModerationModel>; + @useResult + $Res call( + {String name, + @JsonKey(name: 'energy_on_100gr') int energy, + @JsonKey(name: 'proteins_on_100gr') int proteins, + @JsonKey(name: 'fats_on_100gr') int fats, + @JsonKey(name: 'carbohydrates_on_100gr') int carbohydrates}); +} + +/// @nodoc +class _$ProductModerationModelCopyWithImpl<$Res, + $Val extends ProductModerationModel> + implements $ProductModerationModelCopyWith<$Res> { + _$ProductModerationModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductModerationModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? energy = null, + Object? proteins = null, + Object? fats = null, + Object? carbohydrates = null, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + energy: null == energy + ? _value.energy + : energy // ignore: cast_nullable_to_non_nullable + as int, + proteins: null == proteins + ? _value.proteins + : proteins // ignore: cast_nullable_to_non_nullable + as int, + fats: null == fats + ? _value.fats + : fats // ignore: cast_nullable_to_non_nullable + as int, + carbohydrates: null == carbohydrates + ? _value.carbohydrates + : carbohydrates // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ProductModerationModelImplCopyWith<$Res> + implements $ProductModerationModelCopyWith<$Res> { + factory _$$ProductModerationModelImplCopyWith( + _$ProductModerationModelImpl value, + $Res Function(_$ProductModerationModelImpl) then) = + __$$ProductModerationModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String name, + @JsonKey(name: 'energy_on_100gr') int energy, + @JsonKey(name: 'proteins_on_100gr') int proteins, + @JsonKey(name: 'fats_on_100gr') int fats, + @JsonKey(name: 'carbohydrates_on_100gr') int carbohydrates}); +} + +/// @nodoc +class __$$ProductModerationModelImplCopyWithImpl<$Res> + extends _$ProductModerationModelCopyWithImpl<$Res, + _$ProductModerationModelImpl> + implements _$$ProductModerationModelImplCopyWith<$Res> { + __$$ProductModerationModelImplCopyWithImpl( + _$ProductModerationModelImpl _value, + $Res Function(_$ProductModerationModelImpl) _then) + : super(_value, _then); + + /// Create a copy of ProductModerationModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? energy = null, + Object? proteins = null, + Object? fats = null, + Object? carbohydrates = null, + }) { + return _then(_$ProductModerationModelImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + energy: null == energy + ? _value.energy + : energy // ignore: cast_nullable_to_non_nullable + as int, + proteins: null == proteins + ? _value.proteins + : proteins // ignore: cast_nullable_to_non_nullable + as int, + fats: null == fats + ? _value.fats + : fats // ignore: cast_nullable_to_non_nullable + as int, + carbohydrates: null == carbohydrates + ? _value.carbohydrates + : carbohydrates // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProductModerationModelImpl implements _ProductModerationModel { + const _$ProductModerationModelImpl( + {required this.name, + @JsonKey(name: 'energy_on_100gr') required this.energy, + @JsonKey(name: 'proteins_on_100gr') required this.proteins, + @JsonKey(name: 'fats_on_100gr') required this.fats, + @JsonKey(name: 'carbohydrates_on_100gr') required this.carbohydrates}); + + factory _$ProductModerationModelImpl.fromJson(Map json) => + _$$ProductModerationModelImplFromJson(json); + + @override + final String name; + @override + @JsonKey(name: 'energy_on_100gr') + final int energy; + @override + @JsonKey(name: 'proteins_on_100gr') + final int proteins; + @override + @JsonKey(name: 'fats_on_100gr') + final int fats; + @override + @JsonKey(name: 'carbohydrates_on_100gr') + final int carbohydrates; + + @override + String toString() { + return 'ProductModerationModel(name: $name, energy: $energy, proteins: $proteins, fats: $fats, carbohydrates: $carbohydrates)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProductModerationModelImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.energy, energy) || other.energy == energy) && + (identical(other.proteins, proteins) || + other.proteins == proteins) && + (identical(other.fats, fats) || other.fats == fats) && + (identical(other.carbohydrates, carbohydrates) || + other.carbohydrates == carbohydrates)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => + Object.hash(runtimeType, name, energy, proteins, fats, carbohydrates); + + /// Create a copy of ProductModerationModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProductModerationModelImplCopyWith<_$ProductModerationModelImpl> + get copyWith => __$$ProductModerationModelImplCopyWithImpl< + _$ProductModerationModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ProductModerationModelImplToJson( + this, + ); + } +} + +abstract class _ProductModerationModel implements ProductModerationModel { + const factory _ProductModerationModel( + {required final String name, + @JsonKey(name: 'energy_on_100gr') required final int energy, + @JsonKey(name: 'proteins_on_100gr') required final int proteins, + @JsonKey(name: 'fats_on_100gr') required final int fats, + @JsonKey(name: 'carbohydrates_on_100gr') + required final int carbohydrates}) = _$ProductModerationModelImpl; + + factory _ProductModerationModel.fromJson(Map json) = + _$ProductModerationModelImpl.fromJson; + + @override + String get name; + @override + @JsonKey(name: 'energy_on_100gr') + int get energy; + @override + @JsonKey(name: 'proteins_on_100gr') + int get proteins; + @override + @JsonKey(name: 'fats_on_100gr') + int get fats; + @override + @JsonKey(name: 'carbohydrates_on_100gr') + int get carbohydrates; + + /// Create a copy of ProductModerationModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProductModerationModelImplCopyWith<_$ProductModerationModelImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/features/diary/models/product_moderation_model.g.dart b/lib/features/diary/models/product_moderation_model.g.dart new file mode 100644 index 00000000..2199a5f6 --- /dev/null +++ b/lib/features/diary/models/product_moderation_model.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'product_moderation_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ProductModerationModelImpl _$$ProductModerationModelImplFromJson( + Map json) => + _$ProductModerationModelImpl( + name: json['name'] as String, + energy: (json['energy_on_100gr'] as num).toInt(), + proteins: (json['proteins_on_100gr'] as num).toInt(), + fats: (json['fats_on_100gr'] as num).toInt(), + carbohydrates: (json['carbohydrates_on_100gr'] as num).toInt(), + ); + +Map _$$ProductModerationModelImplToJson( + _$ProductModerationModelImpl instance) => + { + 'name': instance.name, + 'energy_on_100gr': instance.energy, + 'proteins_on_100gr': instance.proteins, + 'fats_on_100gr': instance.fats, + 'carbohydrates_on_100gr': instance.carbohydrates, + }; diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart index 74df5385..976d16e6 100644 --- a/lib/features/diary/presentation/bloc/diary_cubit.dart +++ b/lib/features/diary/presentation/bloc/diary_cubit.dart @@ -25,7 +25,7 @@ class DiaryCubit extends Cubit { emit( DiaryState.loaded( diary: await diaryRepository.getDiaryByDate(date), - nutrition: await getNutrition(await diaryRepository.getDiaryIndexByDate(date)), + nutrition: await getNutrition((await diaryRepository.getDiaryByDate(date)).id), currentSelectedSatietyRating: currentSelectedSatietyRating, ), ); @@ -140,4 +140,20 @@ class DiaryCubit extends Cubit { currentSelectedSatietyRating = value; emit((state as DiaryStateLoaded).copyWith(currentSelectedSatietyRating: currentSelectedSatietyRating)); } + + sendProductToModeration({ + required String name, + required int energy, + required int proteins, + required int fats, + required int carbohydrates, + }) async { + return await diaryRepository.sendProductToModeration( + name: name, + energy: energy, + proteins: proteins, + fats: fats, + carbohydrates: carbohydrates, + ); + } } diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart index cfd571cf..6c19ceb6 100644 --- a/lib/features/diary/presentation/pages/diary_screen.dart +++ b/lib/features/diary/presentation/pages/diary_screen.dart @@ -130,7 +130,7 @@ class _DiaryScreenState extends State { state.diary, state.currentSelectedSatietyRating, ), - if (state is DiaryStateLoaded) _chart(state.nutrition), + if (state is DiaryStateLoaded && _selectedDay!.compareTo(today!) != 1) _chart(state.nutrition), if (state is DiaryStateLoaded && state.diary.closed == 0 && _selectedDay == today) Padding( padding: const EdgeInsets.only(top: 12), @@ -278,23 +278,25 @@ class _DiaryScreenState extends State { _mealButton(5), ], ), - const SizedBox(height: 12), - _divider(context), - const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Время приема пищи', - style: TextStyle( - color: Color(0xFF323F4B), - fontSize: 16, - fontWeight: FontWeight.w500, + _selectedDay!.compareTo(today!) == 1 ? const SizedBox.shrink() : const SizedBox(height: 12), + _selectedDay!.compareTo(today!) == 1 ? const SizedBox.shrink() : _divider(context), + _selectedDay!.compareTo(today!) == 1 ? const SizedBox.shrink() : const SizedBox(height: 12), + _selectedDay!.compareTo(today!) == 1 + ? const SizedBox.shrink() + : Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Время приема пищи', + style: TextStyle( + color: Color(0xFF323F4B), + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + _timeOfMeal(time), + ], ), - ), - _timeOfMeal(time), - ], - ), const SizedBox(height: 12), _divider(context), const SizedBox(height: 12), @@ -303,21 +305,25 @@ class _DiaryScreenState extends State { _addProductButton(diaryDay), const SizedBox(height: 24), if (diaryDay.meals[currentSelectedMealNumber - 1].photos.isEmpty) - GestureDetector( - onTap: () => _showImagePicker(diaryDay), - child: SvgPicture.asset( - width: MediaQuery.of(context).size.width - 32, - 'assets/no_one_image_was_added.svg', - ), - ), + _selectedDay!.compareTo(today!) == 1 + ? const SizedBox.shrink() + : GestureDetector( + onTap: () => _showImagePicker(diaryDay), + child: SvgPicture.asset( + width: MediaQuery.of(context).size.width - 32, + 'assets/no_one_image_was_added.svg', + ), + ), if (diaryDay.meals[currentSelectedMealNumber - 1].photos.isNotEmpty) _photoGallery( diaryDay.meals[currentSelectedMealNumber - 1].photos, diaryDay, ), - SatietyRatingWidget( - currentSelectedSatietyRating: currentSelectedSatietyRating, - ), + _selectedDay!.compareTo(today!) == 1 + ? const SizedBox.shrink() + : SatietyRatingWidget( + currentSelectedSatietyRating: currentSelectedSatietyRating, + ), ], ), ), @@ -1017,7 +1023,10 @@ class _DiaryScreenState extends State { padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), - child: GlossaryModerationRequestModalWidget(productName: productNameController.text), + child: GlossaryModerationRequestModalWidget( + productName: productNameController.text, + diaryCubit: diaryCubit, + ), ), ); } @@ -1057,6 +1066,7 @@ class _DiaryScreenState extends State { } _addProductButton(DiaryDayModel diaryDay) { + if (_selectedDay!.compareTo(today!) == 1) return const SizedBox.shrink(); return InkWell( onTap: () { setState(() { diff --git a/lib/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart b/lib/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart index 956a9113..57896321 100644 --- a/lib/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart +++ b/lib/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart @@ -1,29 +1,38 @@ +import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:eatvio_app/widgets/custom_text_form_field.dart'; import 'package:flutter/material.dart'; +import 'package:lottie/lottie.dart'; class GlossaryModerationRequestModalWidget extends StatefulWidget { + final DiaryCubit diaryCubit; final String productName; const GlossaryModerationRequestModalWidget({ super.key, - required this.productName, + required this.productName, required this.diaryCubit, }); @override State createState() => _GlossaryModerationRequestModalWidgetState(); } -enum GlossaryState { alert, addNewProduct, finish } +enum GlossaryState { alert, addNewProduct, success } class _GlossaryModerationRequestModalWidgetState extends State { GlossaryState glossaryState = GlossaryState.alert; final productNameController = TextEditingController(); + final energyController = TextEditingController(); + final proteinsController = TextEditingController(); + final fatsController = TextEditingController(); + final carbohydratesController = TextEditingController(); @override Widget build(BuildContext context) { if (glossaryState == GlossaryState.alert) { return _alert(); + } else if (glossaryState == GlossaryState.success) { + return _success(); } else { return _addNewGlossaryProduct(); } @@ -126,6 +135,58 @@ class _GlossaryModerationRequestModalWidgetState extends State getDiaryByDate(String date) async { - return (await diaryService.getDiaryDay( - index: await getDiaryIndexByDate(date), - )) - .diary; + return (await getDiaryIndexByDate(date)).last; } - Future getDiaryIndexByDate(String date) async { - return (await diaryService.getDiaryIndexByDate(date: date)).id; + Future> getDiaryIndexByDate(String date) async { + return (await diaryService.getDiaryIndexByDate(date: date)); } Future getNutritionData(int index) async { @@ -123,4 +121,22 @@ class DiaryRepository { // ), ); } + + Future sendProductToModeration({ + required String name, + required int energy, + required int proteins, + required int fats, + required int carbohydrates, + }) async { + return await diaryService.sendProductToModeration( + data: ProductModerationModel( + name: name, + energy: energy, + proteins: proteins, + fats: fats, + carbohydrates: carbohydrates, + ).toJson(), + ); + } } diff --git a/lib/features/diary/services/diary_service.dart b/lib/features/diary/services/diary_service.dart index 833f9604..4ff8b2e1 100644 --- a/lib/features/diary/services/diary_service.dart +++ b/lib/features/diary/services/diary_service.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; -import 'package:eatvio_app/features/diary/models/diary_index_model.dart'; import 'package:eatvio_app/features/diary/models/nutrition_model.dart'; import 'package:retrofit/retrofit.dart'; @@ -12,7 +11,7 @@ abstract class DiaryService { factory DiaryService(Dio dio, {String baseUrl}) = _DiaryService; @GET('/api/diary/index/{date}') - Future getDiaryIndexByDate({ + Future> getDiaryIndexByDate({ @Path('date') required String date, // yyyy-mm-dd }); @@ -71,4 +70,9 @@ abstract class DiaryService { @Path('diary_id') required int diaryId, // @Body() required FinishDiaryDayModel finishDiaryDayModel, }); + + @POST('/api/diary/product_nutritional_values/sendToModeration') + Future sendProductToModeration({ + @Body() required Map data, + }); } diff --git a/lib/features/diary/services/diary_service.g.dart b/lib/features/diary/services/diary_service.g.dart index 03fa20ed..b9327c5f 100644 --- a/lib/features/diary/services/diary_service.g.dart +++ b/lib/features/diary/services/diary_service.g.dart @@ -22,12 +22,13 @@ class _DiaryService implements DiaryService { final ParseErrorLogger? errorLogger; @override - Future getDiaryIndexByDate({required String date}) async { + Future> getDiaryIndexByDate( + {required String date}) async { final _extra = {}; final queryParameters = {}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType(Options( + final _options = _setStreamType>(Options( method: 'GET', headers: _headers, extra: _extra, @@ -43,10 +44,12 @@ class _DiaryService implements DiaryService { _dio.options.baseUrl, baseUrl, ))); - final _result = await _dio.fetch>(_options); - late DiaryIndexModel _value; + final _result = await _dio.fetch>(_options); + late List _value; try { - _value = DiaryIndexModel.fromJson(_result.data!); + _value = _result.data! + .map((dynamic i) => DiaryDayModel.fromJson(i as Map)) + .toList(); } on Object catch (e, s) { errorLogger?.logError(e, s, _options); rethrow; @@ -317,6 +320,33 @@ class _DiaryService implements DiaryService { await _dio.fetch(_options); } + @override + Future sendProductToModeration( + {required Map data}) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = {}; + _data.addAll(data); + final _options = _setStreamType(Options( + method: 'POST', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/diary/product_nutritional_values/sendToModeration', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + await _dio.fetch(_options); + } + RequestOptions _setStreamType(RequestOptions requestOptions) { if (T != dynamic && !(requestOptions.responseType == ResponseType.bytes || diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index 380d37c4..48ace714 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -124,7 +124,7 @@ class _ProfileScreenState extends State { if (widget.author == null) const SizedBox(height: 16), _activePlans(state), if (widget.author == null) const SizedBox(height: 16), - _newBlogPostButton(), + if (widget.author == null) _newBlogPostButton(), _blogPosts(state), ], ), diff --git a/lib/features/settings/presentation/pages/other_info_screen.dart b/lib/features/settings/presentation/pages/other_info_screen.dart index b786a746..4cb62dc4 100644 --- a/lib/features/settings/presentation/pages/other_info_screen.dart +++ b/lib/features/settings/presentation/pages/other_info_screen.dart @@ -4,7 +4,6 @@ import 'package:eatvio_app/generated/l10n.dart'; import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:eatvio_app/widgets/custom_text_form_field.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; class OtherInfoScreen extends StatefulWidget { @@ -60,6 +59,7 @@ class _OtherInfoScreenState extends State { autofocus: true, minLines: 2, maxLines: 8, + maxLength: 250, onChanged: (newValue) {}, ), SizedBox(height: 16), @@ -70,6 +70,7 @@ class _OtherInfoScreenState extends State { hintText: s.enter_information, autofocus: true, minLines: 2, + maxLength: 250, maxLines: 8, onChanged: (newValue) {}, ), @@ -82,6 +83,7 @@ class _OtherInfoScreenState extends State { "Я занимаюсь плаванием , почти каждый вечер прогулки на свежем воздухе, пару раз в месяц играю в волейбол. Стараюсь не сидеть на месте!", autofocus: true, maxLines: 8, + maxLength: 250, onChanged: (newValue) {}, ), SizedBox(height: 16), @@ -92,6 +94,7 @@ class _OtherInfoScreenState extends State { hintText: s.enter_information, maxLines: 7, minLines: 2, + maxLength: 250, onChanged: (newValue) {}, ), SizedBox(height: 16), @@ -102,6 +105,7 @@ class _OtherInfoScreenState extends State { hintText: s.enter_information, maxLines: 7, minLines: 2, + maxLength: 250, onChanged: (newValue) {}, ), SizedBox(height: 16), @@ -114,9 +118,7 @@ class _OtherInfoScreenState extends State { SizedBox(height: 4), CustomTextFormField( controller: additionalInfoController, - inputFormatters: [ - new LengthLimitingTextInputFormatter(250), - ], + maxLength: 250, hintText: "У меня крепкое здоровье, но последнее время часто утомляюсь, однако, это мне не мешает радоваться жизни", maxLines: 8, onChanged: (newValue) {}, diff --git a/lib/widgets/custom_text_form_field.dart b/lib/widgets/custom_text_form_field.dart index b493f9c9..6054079f 100644 --- a/lib/widgets/custom_text_form_field.dart +++ b/lib/widgets/custom_text_form_field.dart @@ -34,6 +34,7 @@ class CustomTextFormField extends StatelessWidget { this.onChanged, this.onEditingComplete, this.minLines = 1, + this.maxLength, super.key, }); @@ -67,6 +68,7 @@ class CustomTextFormField extends StatelessWidget { final Function()? onEditingComplete; final void Function(String?)? onSaved; final void Function(String)? onChanged; + final int? maxLength; @override Widget build(BuildContext context) { @@ -74,6 +76,7 @@ class CustomTextFormField extends StatelessWidget { alignment: alignment ?? Alignment.center, child: TextFormField( minLines: minLines, + maxLength: maxLength, onChanged: onChanged, controller: controller, focusNode: focusNode ?? null, diff --git a/pubspec.yaml b/pubspec.yaml index 652f6273..5b2c8596 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: eatvio_app description: A new Flutter project. -version: 0.0.0+1 +version: 1.0.2+17 publish_to: "none" environment: