diff --git a/.gitea/workflows/test-flight-cd.yaml b/.gitea/workflows/test-flight-cd.yaml
index 9d810d8e..c9649f0e 100644
--- a/.gitea/workflows/test-flight-cd.yaml
+++ b/.gitea/workflows/test-flight-cd.yaml
@@ -11,6 +11,29 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
+ - name: Checkout env repository
+ uses: actions/checkout@v2
+ with:
+ repository: mso7nueh/eatvio_app_env
+ path: env_repo
+ token: ${{ vars.MSO7NUEH_TOKEN }}
+ ref: main
+
+ - name: Copy .env file
+ run: |
+ cp env_repo/.env .env
+
+ - name: Load environment variables from .env
+ run: |
+ set -a
+ source .env
+ set +a
+ # Export all variables to GITHUB_ENV
+ while IFS= read -r line; do
+ if [[ $line =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
+ echo "$line" >> $GITHUB_ENV
+ fi
+ done < .env
- name: Unlock login keychain
run: |
@@ -18,12 +41,25 @@ jobs:
env:
KEYCHAIN_PASSWORD: ${{ vars.IOS_RUNNER_CI_PASSWORD }}
+ - name: Setup API key
+ run: |
+ echo "${API_APPSTORE_KEY}" | tr -d '\n' | base64 --decode > ios/AuthKey.p8
+ chmod 600 ios/AuthKey.p8
+ ls -la ios/AuthKey.p8
+ echo "File size: $(wc -c < ios/AuthKey.p8) bytes"
+ echo "Contents of ios directory:"
+ ls -la ios/
+ env:
+ API_APPSTORE_KEY: ${{ vars.API_APPSTORE_KEY }}
+
- name: Flutter build
run: |
flutter clean
flutter pub get
flutter pub upgrade
flutter pub run build_runner build --delete-conflicting-outputs
+ # Configure Flutter for release mode
+ flutter build ios --config-only --release
cd ios
pod update
fastlane beta
@@ -31,6 +67,8 @@ jobs:
LANG: en_US.UTF-8
LC_ALL: en_US.UTF-8
MATCH_PASSWORD: ${{ vars.IOS_RUNNER_CI_PASSWORD }}
+ FASTLANE_PASSWORD: ${{ vars.FASTLANE_PASSWORD }}
MATCH_GIT_URL: http://gitea.office.local:3000/mso7nueh/fastlane
FASTLANE_USER: eatvio2023@icloud.com
- FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ vars.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
\ No newline at end of file
+ FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ vars.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
+ API_APPSTORE_KEY: ${{ vars.API_APPSTORE_KEY }}
diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml
index 75372b09..f1f63162 100644
--- a/.gitea/workflows/test.yml
+++ b/.gitea/workflows/test.yml
@@ -3,7 +3,7 @@ name: Eatvio App Android Test
on: [ pull_request ]
env:
- flutter_version: "3.32.5"
+ flutter_version: "3.35.1"
java_version: "21.x"
flutter_channel: "stable"
my_local_cache_dir: "/opt/hostedtoolcache/"
@@ -16,6 +16,30 @@ jobs:
steps:
- uses: actions/checkout@v3
+ - name: Checkout env repository
+ uses: actions/checkout@v3
+ with:
+ repository: mso7nueh/eatvio_app_env
+ path: env_repo
+ token: ${{ vars.MSO7NUEH_TOKEN }}
+ ref: main
+
+ - name: Copy .env file
+ run: |
+ cp env_repo/.env .env
+
+ - name: Load environment variables from .env
+ run: |
+ set -a
+ source .env
+ set +a
+ # Export all variables to GITHUB_ENV
+ while IFS= read -r line; do
+ if [[ $line =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
+ echo "$line" >> $GITHUB_ENV
+ fi
+ done < .env
+
- name: Create key.properties file
run: |
cat > android/key.properties <
+
+
+
diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html
index f21ab9cc..4921e6e4 100644
--- a/android/build/reports/problems/problems-report.html
+++ b/android/build/reports/problems/problems-report.html
@@ -650,12 +650,12 @@ code + .copy-button {
diff --git a/android/gradle.properties b/android/gradle.properties
index 25971708..f018a618 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,3 +1,3 @@
-org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
+org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index f587a472..e603bd33 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-all.zip
diff --git a/android/settings.gradle b/android/settings.gradle
index 833c476b..e173b2c9 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -18,11 +18,11 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
- id "com.android.application" version "8.7.3" apply false
+ id "com.android.application" version "8.12.0" apply false
// START: FlutterFire Configuration
- id "com.google.gms.google-services" version "4.4.2" apply false
+ id "com.google.gms.google-services" version "4.4.3" apply false
// END: FlutterFire Configuration
- id "org.jetbrains.kotlin.android" version "2.0.21" apply false
+ id "org.jetbrains.kotlin.android" version "2.2.10" apply false
}
include ":app"
diff --git a/assets/icons/reply.svg b/assets/icons/reply.svg
new file mode 100644
index 00000000..b62c76ae
--- /dev/null
+++ b/assets/icons/reply.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/resend.svg b/assets/icons/resend.svg
new file mode 100644
index 00000000..245a03be
--- /dev/null
+++ b/assets/icons/resend.svg
@@ -0,0 +1,3 @@
+
diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist
index 8c6e5614..86343d86 100644
--- a/ios/Flutter/AppFrameworkInfo.plist
+++ b/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 12.0
+ 16.0
diff --git a/ios/Gemfile b/ios/Gemfile
index 7a118b49..791ac593 100644
--- a/ios/Gemfile
+++ b/ios/Gemfile
@@ -1,3 +1,3 @@
source "https://rubygems.org"
-gem "fastlane"
+gem "fastlane", "~> 2.217"
diff --git a/ios/Podfile b/ios/Podfile
index 53a8d390..4fe34ec9 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-platform :ios, '14.0'
+platform :ios, '16.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@@ -39,7 +39,7 @@ post_install do |installer|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
- config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0'
+ config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
## dart: PermissionGroup.camera
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 65f4f4f7..7d740632 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -370,7 +370,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -388,7 +388,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 38;
+ CURRENT_PROJECT_VERSION = 71;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -406,7 +406,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
- MARKETING_VERSION = 1.0.50;
+ MARKETING_VERSION = 1.2.3;
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.eatvioapp.app 1747050044";
@@ -466,7 +466,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -518,7 +518,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -538,7 +538,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 38;
+ CURRENT_PROJECT_VERSION = 71;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -556,7 +556,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
- MARKETING_VERSION = 1.0.50;
+ MARKETING_VERSION = 1.2.3;
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.eatvioapp.app 1747050044";
@@ -576,7 +576,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
CODE_SIGN_STYLE = Manual;
- CURRENT_PROJECT_VERSION = 38;
+ CURRENT_PROJECT_VERSION = 71;
DEVELOPMENT_TEAM = VL68C36HYS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -594,7 +594,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
- MARKETING_VERSION = 1.0.50;
+ MARKETING_VERSION = 1.2.3;
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.eatvioapp.app 1747050044";
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index 9c12df59..1a6f2553 100644
--- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -41,7 +41,7 @@
CFBundleInfoDictionaryVersion
6.0
CFBundleName
- eatvio_app
+ Итвио
CFBundlePackageType
APPL
CFBundleShortVersionString
@@ -21,7 +21,7 @@
CFBundleSignature
????
CFBundleVersion
- 38
+ 71
ITSAppUsesNonExemptEncryption
LSRequiresIPhoneOS
diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile
index 4073b355..3a74adf3 100644
--- a/ios/fastlane/Fastfile
+++ b/ios/fastlane/Fastfile
@@ -18,9 +18,69 @@ default_platform(:ios)
platform :ios do
desc "Push a new beta build to TestFlight"
lane :beta do
- match(type: "appstore")
+ UI.message "Current working directory: #{Dir.pwd}"
+ UI.message "Checking API key file..."
+
+ possible_paths = [
+ "../AuthKey.p8",
+ "./AuthKey.p8",
+ "../../AuthKey.p8",
+ File.expand_path("../AuthKey.p8"),
+ File.expand_path("./AuthKey.p8")
+ ]
+
+ auth_key_path = nil
+ possible_paths.each do |path|
+ if File.exist?(path)
+ UI.message "Found AuthKey.p8 at: #{path}"
+ auth_key_path = File.expand_path(path)
+ break
+ end
+ end
+
+ if auth_key_path.nil?
+ UI.user_error!("API key file not found. Tried paths: #{possible_paths.join(', ')}")
+ end
+
+ UI.message "Using auth_key_path: #{auth_key_path}"
+
+ # Configure Flutter for release mode
+ UI.message "Configuring Flutter for release mode..."
+ sh("cd .. && flutter build ios --config-only --release")
+
+ match(type: "appstore", readonly: true)
increment_build_number(xcodeproj: "Runner.xcodeproj")
- build_app(workspace: "Runner.xcworkspace", scheme: "Runner", export_method: "app-store", codesigning_identity: "Apple Distribution")
- upload_to_testflight
+
+ build_app(
+ workspace: "Runner.xcworkspace",
+ scheme: "Runner",
+ export_method: "app-store",
+ codesigning_identity: "Apple Distribution",
+ )
+
+
+
+ app_store_connect_api_key(
+ key_id: "325JS9JCZH",
+ issuer_id: "dfa27d1d-2bb8-4d4d-847e-0b32f9b880b3",
+ key_filepath: auth_key_path,
+ duration: 1200,
+ in_house: false
+ )
+
+ begin
+ upload_to_testflight(
+ skip_waiting_for_build_processing: true,
+ skip_submission: true,
+ distribute_external: false,
+ notify_external_testers: false,
+ changelog: "TestFlight build #{get_build_number(xcodeproj: 'Runner.xcodeproj')}",
+ verbose: true
+ )
+ rescue => ex
+ UI.error "Upload to TestFlight failed: #{ex.message}"
+ UI.error "Full error: #{ex.backtrace.join("\n")}"
+ raise ex
+ end
end
end
diff --git a/ios/fastlane/report.xml b/ios/fastlane/report.xml
index c86e2b65..0d039029 100644
--- a/ios/fastlane/report.xml
+++ b/ios/fastlane/report.xml
@@ -5,22 +5,24 @@
-
+
-
+
-
+
-
+
+
+
diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart
index 70107e0a..cc028255 100644
--- a/lib/core/app_router.dart
+++ b/lib/core/app_router.dart
@@ -1,5 +1,3 @@
-import 'dart:io';
-import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/firebase_api.dart';
@@ -26,6 +24,7 @@ import 'package:eatvio_app/features/consultations/presentation/pages/consultatio
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
import 'package:eatvio_app/features/menu/presentation/pages/blocked_users_screen.dart';
+import 'package:eatvio_app/features/menu/presentation/pages/notifications_settings_screen.dart';
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart';
@@ -113,21 +112,13 @@ class AppRouter {
name: 'authScreen',
builder: (context, state) => const AuthScreen(),
routes: [
- GoRoute(
- path: 'registration-screen',
- name: 'registrationScreen',
- builder: (context, state) => const RegistrationScreen(),
- ),
+ GoRoute(path: 'registration-screen', name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()),
GoRoute(
path: 'forget-password-screen',
name: 'forgetPasswordScreen',
- builder: (context, state) => const ForgetPasswordScreen(),
- ),
- GoRoute(
- path: 'scan-qr-code-screen',
- name: 'scanQRCodeScreen',
- builder: (context, state) => const ScanQRCodeScreen(),
+ builder: (context, state) => ForgetPasswordScreen(isAuth: state.extra as bool),
),
+ GoRoute(path: 'scan-qr-code-screen', name: 'scanQRCodeScreen', builder: (context, state) => const ScanQRCodeScreen()),
],
),
ShellRoute(
@@ -165,19 +156,23 @@ class AppRouter {
GoRoute(
path: '/diary-screen',
name: 'diaryScreen',
- pageBuilder: (context, state) => const NoTransitionPage(
- child: DiaryScreen(),
- ),
+ pageBuilder: (context, state) => const NoTransitionPage(child: DiaryScreen()),
),
GoRoute(
- path: ConsultationsScreen.path,
- name: ConsultationsScreen.name,
- pageBuilder: (context, state) => const NoTransitionPage(child: ConsultationsScreen())),
- GoRoute(path: '/chat-select-screen', name: 'chatSelectScreen', pageBuilder: (context, state) => const NoTransitionPage(child: ChatsScreen())),
+ path: ConsultationsScreen.path,
+ name: ConsultationsScreen.name,
+ pageBuilder: (context, state) => const NoTransitionPage(child: ConsultationsScreen()),
+ ),
GoRoute(
- path: '/notifications-screen',
- name: 'notificationsScreen',
- pageBuilder: (context, state) => const NoTransitionPage(child: NotificationsScreen())),
+ path: '/chat-select-screen',
+ name: 'chatSelectScreen',
+ pageBuilder: (context, state) => const NoTransitionPage(child: ChatsScreen()),
+ ),
+ GoRoute(
+ path: '/notifications-screen',
+ name: 'notificationsScreen',
+ pageBuilder: (context, state) => const NoTransitionPage(child: NotificationsScreen()),
+ ),
GoRoute(
path: '/menu-screen',
name: 'menuScreen',
@@ -194,32 +189,16 @@ class AppRouter {
),
],
),
- GoRoute(
- name: 'blockedUsersScreen',
- path: '/blocked-users-screen',
- builder: (context, state) => const BlockedUsersScreen(),
- ),
+ GoRoute(name: 'blockedUsersScreen', path: '/blocked-users-screen', builder: (context, state) => const BlockedUsersScreen()),
GoRoute(
path: '/welcome-screen',
name: 'welcomeScreen',
builder: (context, state) => const WelcomeScreen(),
redirect: (context, state) async {
try {
- if (await TokenStorage.haveTokenInStorage()) {
- final firebaseApi = sl();
- final fbToken = await firebaseApi.getToken();
- final deviceInfo = DeviceInfoPlugin();
- await sl().post(
- '/api/profile/notification/token/register',
- data: {
- 'platform': Platform.isIOS ? 'ios' : 'android',
- 'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id,
- 'token': fbToken,
- },
- );
- }
final sharedPreferences = await SharedPreferences.getInstance();
final welcomeScreenWasShowed = sharedPreferences.getBool('welcomeScreenWasShowed') ?? false;
+ await sl().registerPushToken();
if (welcomeScreenWasShowed == false) {
return null;
} else if (await TokenStorage.haveTokenInStorage()) {
@@ -228,7 +207,7 @@ class AppRouter {
return '/auth-screen';
}
} catch (e) {
- rethrow;
+ return '/feed-screen';
}
},
),
@@ -253,26 +232,10 @@ class AppRouter {
}
},
),
- GoRoute(
- path: ConsultationsArchiveScreen.path,
- name: ConsultationsArchiveScreen.name,
- builder: (context, state) => const ConsultationsArchiveScreen(),
- ),
- GoRoute(
- path: ConsultantFinancesScreen.path,
- name: ConsultantFinancesScreen.name,
- builder: (context, state) => const ConsultantFinancesScreen(),
- ),
- GoRoute(
- path: UserPurchasesScreen.path,
- name: UserPurchasesScreen.name,
- builder: (context, state) => const UserPurchasesScreen(),
- ),
- GoRoute(
- path: '/user-finance-screen',
- name: 'userFinanceScreen',
- builder: (context, state) => const UserFinanceScreen(),
- ),
+ GoRoute(path: ConsultationsArchiveScreen.path, name: ConsultationsArchiveScreen.name, builder: (context, state) => const ConsultationsArchiveScreen()),
+ GoRoute(path: ConsultantFinancesScreen.path, name: ConsultantFinancesScreen.name, builder: (context, state) => const ConsultantFinancesScreen()),
+ GoRoute(path: UserPurchasesScreen.path, name: UserPurchasesScreen.name, builder: (context, state) => const UserPurchasesScreen()),
+ GoRoute(path: '/user-finance-screen', name: 'userFinanceScreen', builder: (context, state) => const UserFinanceScreen()),
GoRoute(
path: CardSettingsScreen.path,
name: CardSettingsScreen.name,
@@ -287,11 +250,7 @@ class AppRouter {
builder: (context, state) {
final slug = state.uri.queryParameters['slug'];
if (slug != null) {
- return AboutPlanScreen(
- slug: slug,
- navigateToComments: false,
- index: null,
- );
+ return AboutPlanScreen(slug: slug, navigateToComments: false, index: null);
}
final data = state.extra as Map;
@@ -305,29 +264,23 @@ class AppRouter {
builder: (context, state) {
final slug = state.uri.queryParameters['slug'];
if (slug != null) {
- return AboutRecipeScreen(
- recipeSlug: slug,
- navigateToComments: false,
- index: null,
- );
+ return AboutRecipeScreen(recipeSlug: slug, navigateToComments: false, index: null);
}
final data = state.extra as Map;
- return AboutRecipeScreen(
- recipeSlug: data['recipe'] as String,
- index: data['index'] as int?,
- navigateToComments: data['navigateToComments'] as bool,
- );
+ return AboutRecipeScreen(recipeSlug: data['recipe'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
},
),
GoRoute(
path: RecipeCreateScreen.path,
name: RecipeCreateScreen.name,
- builder: (context, state) => RecipeCreateScreen(
- recipe: state.extra != null ? state.extra as Recipe : null,
- ),
+ builder: (context, state) => RecipeCreateScreen(recipe: state.extra != null ? state.extra as Recipe : null),
+ ),
+ GoRoute(
+ path: '/profile-screen',
+ name: 'profileScreen',
+ builder: (context, state) => ProfileScreen(author: state.extra as Author),
),
- GoRoute(path: '/profile-screen', name: 'profileScreen', builder: (context, state) => ProfileScreen(author: state.extra as Author)),
GoRoute(
path: '/subs-screen/:slug',
name: 'subsScreen',
@@ -339,11 +292,7 @@ class AppRouter {
builder: (context, state) {
final slug = state.uri.queryParameters['slug'];
if (slug != null) {
- return AboutDietScreen(
- slug: slug,
- index: null,
- navigateToComments: false,
- );
+ return AboutDietScreen(slug: slug, index: null, navigateToComments: false);
}
final data = state.extra as Map;
return AboutDietScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
@@ -355,30 +304,26 @@ class AppRouter {
builder: (context, state) => BlogPostCreateScreen(feedBlogPostModel: state.extra != null ? state.extra as FeedBlogPostModel : null),
),
GoRoute(
- path: '/blog-post-screen',
- name: 'blogPostScreen',
- builder: (context, state) {
- final id = state.uri.queryParameters['id'];
- final parsedId = int.tryParse(id ?? '');
- if (parsedId != null) {
- return BlogPostScreen(
- id: parsedId,
- );
- }
+ path: '/blog-post-screen',
+ name: 'blogPostScreen',
+ builder: (context, state) {
+ final id = state.uri.queryParameters['id'];
+ final parsedId = int.tryParse(id ?? '');
+ if (parsedId != null) {
+ return BlogPostScreen(id: parsedId);
+ }
- return BlogPostScreen(id: state.extra as int);
- }),
+ return BlogPostScreen(id: state.extra as int);
+ },
+ ),
GoRoute(
path: '/chat-screen',
name: 'chatScreen',
builder: (context, state) {
final data = state.extra as Map;
- final chatId = data['chatId'] as String;
+ final uuid = data['uuid'] as String;
final sendInitialMessage = data['sendInitialMessage'] as bool?;
- return ChatScreen(
- chatId: chatId,
- sendInitialMessage: sendInitialMessage,
- );
+ return ChatScreen(uuid: uuid, sendInitialMessage: sendInitialMessage);
},
),
GoRoute(
@@ -411,7 +356,11 @@ class AppRouter {
name: 'physicalFormScreen',
builder: (context, state) => PhysicalFormScreen(settingsEntity: state.extra as SettingsEntity),
),
- GoRoute(path: 'pressure-screen', name: 'pressureScreen', builder: (context, state) => PressureScreen(settingsEntity: state.extra as SettingsEntity)),
+ GoRoute(
+ path: 'pressure-screen',
+ name: 'pressureScreen',
+ builder: (context, state) => PressureScreen(settingsEntity: state.extra as SettingsEntity),
+ ),
GoRoute(
path: 'change-password-screen',
name: 'changePasswordScreen',
@@ -428,9 +377,7 @@ class AppRouter {
GoRoute(
path: '/stepper-become-consultant-screen',
name: 'stepperBecomeConsultantScreen',
- builder: (context, state) => StepperBecomeConsultantScreen(
- slug: state.extra == null ? null : state.extra as String,
- ),
+ builder: (context, state) => StepperBecomeConsultantScreen(slug: state.extra == null ? null : state.extra as String),
),
GoRoute(path: '/calendar-screen', name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
GoRoute(path: '/support-screen', name: 'supportScreen', builder: (context, state) => const SupportScreen()),
@@ -458,7 +405,11 @@ class AppRouter {
GoRoute(path: CreatePlanScreen.path, name: CreatePlanScreen.name, builder: (context, state) => const CreatePlanScreen()),
GoRoute(path: MyDietsScreen.path, name: MyDietsScreen.name, builder: (context, state) => const MyDietsScreen()),
GoRoute(path: MyPlansScreen.path, name: MyPlansScreen.name, builder: (context, state) => const MyPlansScreen()),
- GoRoute(path: UserDetailsScreen.path, name: UserDetailsScreen.name, builder: (context, state) => UserDetailsScreen(slug: state.extra as String)),
+ GoRoute(
+ path: UserDetailsScreen.path,
+ name: UserDetailsScreen.name,
+ builder: (context, state) => UserDetailsScreen(slug: state.extra as String),
+ ),
GoRoute(path: DevToolsScreen.path, name: DevToolsScreen.name, builder: (context, state) => const DevToolsScreen()),
GoRoute(path: ConsultantRatingScreen.path, name: ConsultantRatingScreen.name, builder: (context, state) => const ConsultantRatingScreen()),
GoRoute(
@@ -468,10 +419,7 @@ class AppRouter {
final data = state.extra as Map;
final planId = data['planId'] as int;
final successfulPayment = data['successfulPayment'] as bool?;
- return InfoByOrderScreen(
- planId: planId,
- successfulPayment: successfulPayment,
- );
+ return InfoByOrderScreen(planId: planId, successfulPayment: successfulPayment);
},
),
GoRoute(
@@ -481,10 +429,7 @@ class AppRouter {
final data = state.extra as Map;
final planId = data['planId'] as int;
final url = data['url'] as String;
- return PaymentScreen(
- url: url,
- planId: planId,
- );
+ return PaymentScreen(url: url, planId: planId);
},
),
GoRoute(
@@ -494,10 +439,7 @@ class AppRouter {
final data = state.extra as Map;
final diaryId = data['diaryId'] as int;
final currentPlanDay = data['currentDiaryDay'] as int;
- return UserDiaryScreen(
- diaryId: diaryId,
- currentPlanDay: currentPlanDay,
- );
+ return UserDiaryScreen(diaryId: diaryId, currentPlanDay: currentPlanDay);
},
),
GoRoute(
@@ -505,16 +447,9 @@ class AppRouter {
name: 'verifyScreen',
builder: (context, state) => VerifyScreen(passKey: state.extra as String),
),
- GoRoute(
- path: '/verify-email-screen',
- name: 'verifyEmailScreen',
- builder: (context, state) => const VerifyEmailScreen(),
- ),
- GoRoute(
- path: MyRecipesScreen.path,
- name: MyRecipesScreen.name,
- builder: (context, state) => const MyRecipesScreen(),
- ),
+ GoRoute(path: '/verify-email-screen', name: 'verifyEmailScreen', builder: (context, state) => const VerifyEmailScreen()),
+ GoRoute(path: MyRecipesScreen.path, name: MyRecipesScreen.name, builder: (context, state) => const MyRecipesScreen()),
+ GoRoute(path: '/notifications-settings-screen', name: 'notificationsSettingsScreen', builder: (context, state) => const NotificationsSettingsScreen()),
],
);
}
diff --git a/lib/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart b/lib/core/comments/comments_cubit/comments_cubit.dart
similarity index 62%
rename from lib/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart
rename to lib/core/comments/comments_cubit/comments_cubit.dart
index 5bb62317..7e5df141 100644
--- a/lib/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart
+++ b/lib/core/comments/comments_cubit/comments_cubit.dart
@@ -1,20 +1,25 @@
-import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_state.dart';
+import 'dart:developer';
+
+import 'package:eatvio_app/core/comments/comments_cubit/comments_state.dart';
+import 'package:eatvio_app/core/comments/repositories/comments_repository.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CommentsCubit extends Cubit {
final FeedRepository feedRepository;
+ final CommentsRepository commentsRepository;
CommentsCubit({
required this.feedRepository,
+ required this.commentsRepository,
}) : super(const CommentsState());
void setReplyId(int? replyId) {
emit((state as CommentsStateLoaded).copyWith(replyId: replyId));
}
- Future getComments(String id, CommentSourceType commentSourceType, [bool isFirstFetch = false]) async {
+ Future getComments(String id, CommentSourceType commentSourceType, [bool isFirstFetch = false]) async {
if (isFirstFetch) emit(const CommentsState.loading());
try {
switch (commentSourceType) {
@@ -27,7 +32,7 @@ class CommentsCubit extends Cubit {
case CommentSourceType.recipe:
emit(CommentsState.loaded(comment: await feedRepository.getRecipesComments(id: id)));
break;
- case CommentSourceType.plan:
+ case CommentSourceType.plan:
emit(CommentsState.loaded(comment: await feedRepository.getPlansComments(slug: id)));
break;
}
@@ -78,4 +83,34 @@ class CommentsCubit extends Cubit {
emit(CommentsState.error(errorMessage: e.toString()));
}
}
+
+ Future deleteComment({required int id}) async {
+ if (state is! CommentsStateLoaded) return false;
+ final commentModel = (state as CommentsStateLoaded).comment;
+ final updatedCommentModel = commentModel.copyWith(data: commentModel.data.where((comment) => comment.id != id).toList());
+ try {
+ await commentsRepository.deleteComment(id: id);
+ emit(CommentsState.loaded(comment: updatedCommentModel));
+ return true;
+ } catch (e, st) {
+ emit(CommentsState.loaded(comment: commentModel));
+ log(e.toString(), stackTrace: st);
+ }
+ return false;
+ }
+
+ Future updateComment({required int id, required String body}) async {
+ final commentModel = (state as CommentsStateLoaded).comment;
+ final updatedCommentModel =
+ commentModel.copyWith(data: commentModel.data.map((comment) => comment.id == id ? comment.copyWith(body: body) : comment).toList());
+ try {
+ await commentsRepository.updateComment(id: id, body: body);
+ emit(CommentsState.loaded(comment: updatedCommentModel));
+ return true;
+ } catch (e, st) {
+ emit(CommentsState.loaded(comment: commentModel));
+ log(e.toString(), stackTrace: st);
+ }
+ return false;
+ }
}
diff --git a/lib/features/feed/presentation/bloc/comments_cubit/comments_state.dart b/lib/core/comments/comments_cubit/comments_state.dart
similarity index 95%
rename from lib/features/feed/presentation/bloc/comments_cubit/comments_state.dart
rename to lib/core/comments/comments_cubit/comments_state.dart
index 0f9c49ea..79fed119 100644
--- a/lib/features/feed/presentation/bloc/comments_cubit/comments_state.dart
+++ b/lib/core/comments/comments_cubit/comments_state.dart
@@ -12,6 +12,7 @@ abstract class CommentsState with _$CommentsState {
const factory CommentsState.loaded({
required CommentModel comment,
int? replyId,
+ int? editedCommentId,
}) = CommentsStateLoaded;
const factory CommentsState.error({
diff --git a/lib/core/comments/models/comment_update_model.dart b/lib/core/comments/models/comment_update_model.dart
new file mode 100644
index 00000000..dfe26dfe
--- /dev/null
+++ b/lib/core/comments/models/comment_update_model.dart
@@ -0,0 +1,12 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'comment_update_model.freezed.dart';
+part 'comment_update_model.g.dart';
+
+@freezed
+abstract class CommentUpdateModel with _$CommentUpdateModel {
+
+ factory CommentUpdateModel({required String body}) = _CommentUpdateModel;
+
+ factory CommentUpdateModel.fromJson(Map json) => _$CommentUpdateModelFromJson(json);
+}
\ No newline at end of file
diff --git a/lib/core/comments/presentation/comments_actions_bottom_sheet.dart b/lib/core/comments/presentation/comments_actions_bottom_sheet.dart
new file mode 100644
index 00000000..3c1c3859
--- /dev/null
+++ b/lib/core/comments/presentation/comments_actions_bottom_sheet.dart
@@ -0,0 +1,173 @@
+import 'package:eatvio_app/core/theme/theme_helper.dart';
+import 'package:eatvio_app/core/widgets/bottom_sheet_action_widget.dart';
+import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.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/generated/l10n.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+void showCommentsActionsBottomSheet(
+ BuildContext context, {
+ required void Function()? reply,
+ required void Function() copy,
+ required void Function(String text) edit,
+ required void Function() delete,
+ required void Function() complain,
+ required String commentText,
+ required bool isMyComment,
+}) {
+ showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ backgroundColor: Colors.transparent,
+ builder: (context) => Container(
+ margin: EdgeInsets.only(left: 8, right: 8, bottom: MediaQuery.of(context).padding.bottom + 20),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(12),
+ color: appTheme.whiteA700,
+ ),
+ child: CommentsActionsBottomSheet(
+ isMyComment: isMyComment,
+ reply: reply,
+ copy: copy,
+ edit: edit,
+ delete: delete,
+ complain: complain,
+ commentText: commentText,
+ ),
+ ));
+}
+
+class CommentsActionsBottomSheet extends StatefulWidget {
+ const CommentsActionsBottomSheet({
+ super.key,
+ required this.isMyComment,
+ required this.reply,
+ required this.copy,
+ required this.edit,
+ required this.delete,
+ required this.complain,
+ required this.commentText,
+ });
+ final bool isMyComment;
+ final void Function()? reply;
+ final void Function() copy;
+ final void Function(String text) edit;
+ final void Function() delete;
+ final void Function() complain;
+ final String commentText;
+
+ @override
+ State createState() => _CommentsActionsBottomSheetState();
+}
+
+class _CommentsActionsBottomSheetState extends State {
+ bool isUserEditingComment = false;
+ final controller = TextEditingController();
+ @override
+ void initState() {
+ super.initState();
+ controller.text = widget.commentText;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final s = S.of(context);
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: isUserEditingComment
+ ? Padding(
+ padding: const EdgeInsets.symmetric(vertical: 16.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Row(children: [
+ Expanded(
+ child: CustomTextFormField(
+ autofocus: true,
+ controller: controller,
+ onChanged: (value) => setState(() {}),
+ ),
+ ),
+ const SizedBox(width: 8),
+ GestureDetector(
+ onTap: () {
+ widget.edit(controller.text);
+ GoRouter.of(context).pop();
+ },
+ child: Container(
+ decoration: BoxDecoration(
+ color: controller.text.length > 2 ? appTheme.blueA400 : appTheme.gray50,
+ shape: BoxShape.circle,
+ ),
+ height: 32,
+ width: 32,
+ child: Icon(
+ Icons.send_rounded,
+ size: 18,
+ color: controller.text.length > 2 ? appTheme.whiteA700 : appTheme.black,
+ ),
+ ),
+ )
+ ])
+ ],
+ ),
+ )
+ : Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const SizedBox(
+ height: 7,
+ ),
+ const BottomSheetHandlerBar(),
+ if (widget.isMyComment)
+ BottomSheetActionWidget(
+ text: s.edit,
+ iconPath: 'assets/icons/edit_paint.svg',
+ needToPop: false,
+ onTap: () => setState(() {
+ isUserEditingComment = true;
+ }),
+ )
+ else if (widget.reply != null)
+ BottomSheetActionWidget(
+ text: s.reply,
+ iconPath: 'assets/icons/reply.svg',
+ onTap: widget.reply!,
+ ),
+ if (widget.reply != null) const CustomDivider(),
+ BottomSheetActionWidget(
+ text: s.copy,
+ iconPath: 'assets/icons/copy2.svg',
+ onTap: widget.copy,
+ ),
+ if (widget.isMyComment) ...[
+ if (widget.reply != null) ...[
+ const CustomDivider(),
+ BottomSheetActionWidget(
+ text: s.reply,
+ iconPath: 'assets/icons/reply.svg',
+ onTap: widget.reply!,
+ ),
+ ],
+ const CustomDivider(),
+ BottomSheetActionWidget(
+ text: s.deleteComment,
+ iconPath: 'assets/icons/red_trash.svg',
+ onTap: widget.delete,
+ ),
+ ] else ...[
+ const CustomDivider(),
+ BottomSheetActionWidget(
+ text: s.complain,
+ iconPath: 'assets/icons/complain.svg',
+ onTap: widget.complain,
+ ),
+ ]
+ ],
+ ),
+ );
+ }
+}
+
diff --git a/lib/core/comments/repositories/comments_repository.dart b/lib/core/comments/repositories/comments_repository.dart
new file mode 100644
index 00000000..096c3690
--- /dev/null
+++ b/lib/core/comments/repositories/comments_repository.dart
@@ -0,0 +1,24 @@
+import 'package:eatvio_app/core/comments/models/comment_update_model.dart';
+import 'package:eatvio_app/core/comments/services/comments_service.dart';
+
+class CommentsRepository {
+ final CommentsService service;
+
+ CommentsRepository({required this.service});
+
+ Future deleteComment({required int id}) async {
+ try {
+ return service.deleteComment(id);
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future updateComment({required int id, required String body}) async {
+ try {
+ return service.updateComment(id, CommentUpdateModel(body: body));
+ } catch (e) {
+ rethrow;
+ }
+ }
+}
diff --git a/lib/core/comments/services/comments_service.dart b/lib/core/comments/services/comments_service.dart
new file mode 100644
index 00000000..606f61bd
--- /dev/null
+++ b/lib/core/comments/services/comments_service.dart
@@ -0,0 +1,16 @@
+import 'package:dio/dio.dart';
+import 'package:eatvio_app/core/comments/models/comment_update_model.dart';
+import 'package:retrofit/retrofit.dart';
+
+part 'comments_service.g.dart';
+
+@RestApi()
+abstract class CommentsService {
+ factory CommentsService(Dio dio, {String baseUrl}) = _CommentsService;
+
+ @DELETE('/api/comments/{id}')
+ Future deleteComment(@Path('id') int id);
+
+ @PATCH('/api/comments/{id}')
+ Future updateComment(@Path('id') int id, @Body() CommentUpdateModel body);
+}
diff --git a/lib/core/const.dart b/lib/core/const.dart
index 618a9866..98f7bc4a 100644
--- a/lib/core/const.dart
+++ b/lib/core/const.dart
@@ -1,4 +1,8 @@
-String baseUrl = 'https://eatvio.ru';
+import 'package:eatvio_app/core/env_config.dart';
+
+String baseUrl = EnvConfig.baseUrl;
+bool hidePlans = const bool.fromEnvironment('HIDE_PLANS', defaultValue: false);
+const buildDate = String.fromEnvironment('BUILD_DATE', defaultValue: '');
const List durationValues = [7, 14, 21];
diff --git a/lib/core/credentials_storage.dart b/lib/core/credentials_storage.dart
deleted file mode 100644
index 6e9870e5..00000000
--- a/lib/core/credentials_storage.dart
+++ /dev/null
@@ -1,32 +0,0 @@
-import 'package:flutter_secure_storage/flutter_secure_storage.dart';
-
-abstract class CredentialsStorage {
- static Future haveCredentialsInStorage() async {
- try {
- if (await const FlutterSecureStorage().read(key: 'email') == null || await const FlutterSecureStorage().read(key: 'password') == null) {
- return false;
- } else {
- return true;
- }
- } catch (e) {
- return false;
- }
- }
-
- static Future