forked from eatvio/eatvio_app
Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
087436da1c | ||
|
|
6ac93d42df | ||
|
|
1a6fb39d84 | ||
|
|
b4776c3be3 | ||
|
|
06e07a92b7 | ||
|
|
0fce3f9d30 | ||
|
|
4bc8cba941 | ||
|
|
8c348f61b1 | ||
|
|
0446e64409 | ||
|
|
74b5c6732d | ||
|
|
b840c936bf | ||
|
|
f3dbbf3537 | ||
|
|
ff49562e86 | ||
|
|
8b7180c2da | ||
|
|
5c911707a3 | ||
|
|
aa604b667b | ||
|
|
0a5d78be1f | ||
|
|
f1f5c79661 | ||
|
|
1c517807c4 | ||
|
|
cad54913d1 | ||
|
|
89f0a46192 | ||
|
|
65542020a7 | ||
|
|
b6b1c8b3c1 | ||
|
|
bf6e23e96a | ||
|
|
952cf76164 | ||
|
|
b665692779 | ||
|
|
54ac20978f | ||
|
|
b9dc5c0136 | ||
|
|
39fce4b0f9 | ||
|
|
73ff67aa13 | ||
|
|
dcf2805df7 | ||
|
|
d13eb78f0e | ||
|
|
5f21a65028 | ||
|
|
12bf9f9e5f | ||
|
|
1680bd2556 | ||
|
|
91d5118f60 | ||
|
|
d8324c1635 | ||
|
|
0f1dbe16f1 | ||
|
|
020682ae80 | ||
|
|
e54476656b | ||
|
|
dd56924076 | ||
|
|
b47da44cf2 | ||
|
|
964550a325 | ||
|
|
7ffa061f3e | ||
|
|
997da4f940 | ||
|
|
ca1777ece0 | ||
|
|
1a427d6f5d | ||
|
|
f190e70f3a | ||
|
|
bc6469a6ae | ||
|
|
e3ff359e1b | ||
|
|
b5edf13bd9 | ||
|
|
c05d027e3d | ||
|
|
bae79b60fd | ||
|
|
6061e413b2 | ||
|
|
5a8fa7089c | ||
|
|
886cfa8f1c | ||
|
|
a416912d2a | ||
|
|
f46392ec8e | ||
|
|
766d06fc4d | ||
|
|
6f4023dd0b | ||
|
|
f892dfed9f | ||
|
|
98803e56cf | ||
|
|
4391870e72 | ||
|
|
7fd24f57e3 | ||
|
|
384a25c4a6 | ||
|
|
88558d62ef | ||
|
|
09b04118aa | ||
|
|
f76b142117 | ||
|
|
13e66edd27 | ||
|
|
5b74ae45b3 | ||
|
|
3d2634b96a | ||
|
|
1281abaf24 | ||
|
|
7dfac3f792 | ||
|
|
9d4687553a | ||
|
|
442da40622 | ||
|
|
ccf586234b | ||
|
|
4f249499a9 | ||
|
|
2f74387df2 | ||
|
|
45774c3875 | ||
|
|
62c0914fe2 | ||
|
|
48f5b64223 | ||
| ed4db5b59e | |||
|
|
91bb5cf483 | ||
|
|
c4dedac3f3 | ||
|
|
8ed243f63c | ||
|
|
a10fe6d2f5 | ||
|
|
35b78bdeee | ||
|
|
ef037eb4d5 | ||
|
|
5a3385cacf | ||
|
|
dca4b4c471 | ||
|
|
48f433288f | ||
|
|
96e80ced38 |
@@ -21,20 +21,19 @@ jobs:
|
|||||||
|
|
||||||
- name: Copy .env file
|
- name: Copy .env file
|
||||||
run: |
|
run: |
|
||||||
mkdir -p env
|
cp env_repo/.env .env
|
||||||
cp env_repo/.env env/.env
|
|
||||||
|
|
||||||
- name: Load environment variables from .env
|
- name: Load environment variables from .env
|
||||||
run: |
|
run: |
|
||||||
set -a
|
set -a
|
||||||
source env/.env
|
source .env
|
||||||
set +a
|
set +a
|
||||||
# Export all variables to GITHUB_ENV
|
# Export all variables to GITHUB_ENV
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
if [[ $line =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
|
if [[ $line =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
|
||||||
echo "$line" >> $GITHUB_ENV
|
echo "$line" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
done < env/.env
|
done < .env
|
||||||
|
|
||||||
- name: Unlock login keychain
|
- name: Unlock login keychain
|
||||||
run: |
|
run: |
|
||||||
@@ -59,6 +58,8 @@ jobs:
|
|||||||
flutter pub get
|
flutter pub get
|
||||||
flutter pub upgrade
|
flutter pub upgrade
|
||||||
flutter pub run build_runner build --delete-conflicting-outputs
|
flutter pub run build_runner build --delete-conflicting-outputs
|
||||||
|
# Configure Flutter for release mode
|
||||||
|
flutter build ios --config-only --release
|
||||||
cd ios
|
cd ios
|
||||||
pod update
|
pod update
|
||||||
fastlane beta
|
fastlane beta
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name: Eatvio App Android Test
|
|||||||
on: [ pull_request ]
|
on: [ pull_request ]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
flutter_version: "3.32.8"
|
flutter_version: "3.35.1"
|
||||||
java_version: "21.x"
|
java_version: "21.x"
|
||||||
flutter_channel: "stable"
|
flutter_channel: "stable"
|
||||||
my_local_cache_dir: "/opt/hostedtoolcache/"
|
my_local_cache_dir: "/opt/hostedtoolcache/"
|
||||||
@@ -26,20 +26,19 @@ jobs:
|
|||||||
|
|
||||||
- name: Copy .env file
|
- name: Copy .env file
|
||||||
run: |
|
run: |
|
||||||
mkdir -p env
|
cp env_repo/.env .env
|
||||||
cp env_repo/.env env/.env
|
|
||||||
|
|
||||||
- name: Load environment variables from .env
|
- name: Load environment variables from .env
|
||||||
run: |
|
run: |
|
||||||
set -a
|
set -a
|
||||||
source env/.env
|
source .env
|
||||||
set +a
|
set +a
|
||||||
# Export all variables to GITHUB_ENV
|
# Export all variables to GITHUB_ENV
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
if [[ $line =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
|
if [[ $line =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
|
||||||
echo "$line" >> $GITHUB_ENV
|
echo "$line" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
done < env/.env
|
done < .env
|
||||||
|
|
||||||
- name: Create key.properties file
|
- name: Create key.properties file
|
||||||
run: |
|
run: |
|
||||||
@@ -144,7 +143,11 @@ jobs:
|
|||||||
run: flutter test
|
run: flutter test
|
||||||
|
|
||||||
- name: Build apk
|
- name: Build apk
|
||||||
run: flutter build apk --release --dart-define=HIDE_PLANS=false
|
run: |
|
||||||
|
BUILD_DATE=$(TZ=Europe/Moscow date +'%Y-%m-%d %H:%M:%S')
|
||||||
|
flutter build apk --release \
|
||||||
|
--dart-define=HIDE_PLANS=false \
|
||||||
|
--dart-define=BUILD_DATE="$BUILD_DATE"
|
||||||
|
|
||||||
- name: Rename and move apk
|
- name: Rename and move apk
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,6 +16,5 @@
|
|||||||
*.mocks.dart
|
*.mocks.dart
|
||||||
android/build/
|
android/build/
|
||||||
ios/XCBuildData/
|
ios/XCBuildData/
|
||||||
.env
|
|
||||||
env.example
|
|
||||||
env/.env
|
env/.env
|
||||||
|
.env
|
||||||
@@ -29,7 +29,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.eatvioapp.app"
|
applicationId = "com.eatvioapp.app"
|
||||||
minSdk = 23
|
minSdkVersion flutter.minSdkVersion
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-all.zip
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
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
|
// 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
|
// END: FlutterFire Configuration
|
||||||
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
|
id "org.jetbrains.kotlin.android" version "2.2.10" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
include ":app"
|
include ":app"
|
||||||
|
|||||||
3
assets/icons/resend.svg
Normal file
3
assets/icons/resend.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M19.377 11L15.3193 6.94225C15.1641 6.78709 15.0865 6.60892 15.0865 6.40775C15.0865 6.20642 15.1641 6.02817 15.3193 5.873C15.4744 5.72817 15.6542 5.65734 15.8587 5.6605C16.0631 5.66384 16.2345 5.73467 16.373 5.873L20.8673 10.3673C20.9609 10.4609 21.0269 10.5597 21.0653 10.6635C21.1038 10.7673 21.123 10.8795 21.123 11C21.123 11.1205 21.1038 11.2327 21.0653 11.3365C21.0269 11.4403 20.9609 11.5391 20.8673 11.6328L16.373 16.127C16.2178 16.282 16.0422 16.357 15.8462 16.352C15.6501 16.3468 15.4744 16.2718 15.3193 16.127C15.1641 15.9718 15.0856 15.7946 15.0837 15.5953C15.0817 15.3959 15.1551 15.2218 15.3038 15.073L19.377 11ZM14.0693 11.75H7.25C6.3525 11.75 5.5865 12.0673 4.952 12.702C4.31733 13.3365 4 14.1025 4 15V17.75C4 17.9628 3.92817 18.141 3.7845 18.2845C3.641 18.4282 3.46283 18.5 3.25 18.5C3.03717 18.5 2.859 18.4282 2.7155 18.2845C2.57183 18.141 2.5 17.9628 2.5 17.75V15C2.5 13.6872 2.9635 12.5673 3.8905 11.6405C4.81733 10.7135 5.93717 10.25 7.25 10.25H14.0693L10.7615 6.94225C10.6065 6.78709 10.529 6.60892 10.529 6.40775C10.529 6.20642 10.6065 6.02817 10.7615 5.873C10.9167 5.72817 11.0965 5.65734 11.301 5.6605C11.5055 5.66384 11.677 5.73467 11.8155 5.873L16.3095 10.3673C16.4032 10.4609 16.4692 10.5597 16.5078 10.6635C16.5463 10.7673 16.5655 10.8795 16.5655 11C16.5655 11.1205 16.5463 11.2327 16.5078 11.3365C16.4692 11.4403 16.4032 11.5391 16.3095 11.6328L11.8155 16.127C11.6603 16.282 11.4847 16.357 11.2885 16.352C11.0923 16.3468 10.9167 16.2718 10.7615 16.127C10.6065 15.9718 10.528 15.7946 10.526 15.5953C10.524 15.3959 10.5974 15.2218 10.7463 15.073L14.0693 11.75Z" fill="#2F80ED"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -21,6 +21,6 @@
|
|||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
<string>12.0</string>
|
<string>16.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Uncomment this line to define a global platform for your project
|
# 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.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
@@ -39,7 +39,7 @@ post_install do |installer|
|
|||||||
flutter_additional_ios_build_settings(target)
|
flutter_additional_ios_build_settings(target)
|
||||||
target.build_configurations.each do |config|
|
target.build_configurations.each do |config|
|
||||||
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
|
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'] ||= [
|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
||||||
'$(inherited)',
|
'$(inherited)',
|
||||||
## dart: PermissionGroup.camera
|
## dart: PermissionGroup.camera
|
||||||
|
|||||||
@@ -213,14 +213,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
@@ -287,14 +283,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Copy Pods Resources";
|
name = "[CP] Copy Pods Resources";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
@@ -378,7 +370,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
@@ -396,7 +388,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 67;
|
CURRENT_PROJECT_VERSION = 72;
|
||||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
@@ -418,6 +410,7 @@
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.eatvioapp.app 1747050044";
|
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.eatvioapp.app 1747050044";
|
||||||
|
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
@@ -474,7 +467,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -526,7 +519,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
@@ -546,7 +539,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 67;
|
CURRENT_PROJECT_VERSION = 72;
|
||||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
@@ -568,6 +561,7 @@
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.eatvioapp.app 1747050044";
|
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.eatvioapp.app 1747050044";
|
||||||
|
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -584,7 +578,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 67;
|
CURRENT_PROJECT_VERSION = 72;
|
||||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
@@ -606,6 +600,7 @@
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.eatvioapp.app 1747050044";
|
PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.eatvioapp.app 1747050044";
|
||||||
|
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
</Testables>
|
</Testables>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Release"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>67</string>
|
<string>72</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ platform :ios do
|
|||||||
|
|
||||||
UI.message "Using auth_key_path: #{auth_key_path}"
|
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)
|
match(type: "appstore", readonly: true)
|
||||||
increment_build_number(xcodeproj: "Runner.xcodeproj")
|
increment_build_number(xcodeproj: "Runner.xcodeproj")
|
||||||
|
|
||||||
@@ -52,7 +56,6 @@ platform :ios do
|
|||||||
scheme: "Runner",
|
scheme: "Runner",
|
||||||
export_method: "app-store",
|
export_method: "app-store",
|
||||||
codesigning_identity: "Apple Distribution",
|
codesigning_identity: "Apple Distribution",
|
||||||
xcargs: "--dart-define=HIDE_PLANS=true"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,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/consultant_finances_screen.dart';
|
||||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_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/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/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/create_diet_screen.dart';
|
||||||
import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart';
|
import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart';
|
||||||
@@ -111,23 +112,13 @@ class AppRouter {
|
|||||||
name: 'authScreen',
|
name: 'authScreen',
|
||||||
builder: (context, state) => const AuthScreen(),
|
builder: (context, state) => const AuthScreen(),
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(path: 'registration-screen', name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()),
|
||||||
path: 'registration-screen',
|
|
||||||
name: 'registrationScreen',
|
|
||||||
builder: (context, state) => const RegistrationScreen(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'forget-password-screen',
|
path: 'forget-password-screen',
|
||||||
name: 'forgetPasswordScreen',
|
name: 'forgetPasswordScreen',
|
||||||
builder: (context, state) => ForgetPasswordScreen(
|
builder: (context, state) => ForgetPasswordScreen(isAuth: state.extra as bool),
|
||||||
isAuth: state.extra as bool,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: 'scan-qr-code-screen',
|
|
||||||
name: 'scanQRCodeScreen',
|
|
||||||
builder: (context, state) => const ScanQRCodeScreen(),
|
|
||||||
),
|
),
|
||||||
|
GoRoute(path: 'scan-qr-code-screen', name: 'scanQRCodeScreen', builder: (context, state) => const ScanQRCodeScreen()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
@@ -165,19 +156,23 @@ class AppRouter {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/diary-screen',
|
path: '/diary-screen',
|
||||||
name: 'diaryScreen',
|
name: 'diaryScreen',
|
||||||
pageBuilder: (context, state) => const NoTransitionPage(
|
pageBuilder: (context, state) => const NoTransitionPage(child: DiaryScreen()),
|
||||||
child: DiaryScreen(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: ConsultationsScreen.path,
|
path: ConsultationsScreen.path,
|
||||||
name: ConsultationsScreen.name,
|
name: ConsultationsScreen.name,
|
||||||
pageBuilder: (context, state) => const NoTransitionPage(child: ConsultationsScreen())),
|
pageBuilder: (context, state) => const NoTransitionPage(child: ConsultationsScreen()),
|
||||||
GoRoute(path: '/chat-select-screen', name: 'chatSelectScreen', pageBuilder: (context, state) => const NoTransitionPage(child: ChatsScreen())),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/notifications-screen',
|
path: '/chat-select-screen',
|
||||||
name: 'notificationsScreen',
|
name: 'chatSelectScreen',
|
||||||
pageBuilder: (context, state) => const NoTransitionPage(child: NotificationsScreen())),
|
pageBuilder: (context, state) => const NoTransitionPage(child: ChatsScreen()),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/notifications-screen',
|
||||||
|
name: 'notificationsScreen',
|
||||||
|
pageBuilder: (context, state) => const NoTransitionPage(child: NotificationsScreen()),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/menu-screen',
|
path: '/menu-screen',
|
||||||
name: 'menuScreen',
|
name: 'menuScreen',
|
||||||
@@ -194,11 +189,7 @@ class AppRouter {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(name: 'blockedUsersScreen', path: '/blocked-users-screen', builder: (context, state) => const BlockedUsersScreen()),
|
||||||
name: 'blockedUsersScreen',
|
|
||||||
path: '/blocked-users-screen',
|
|
||||||
builder: (context, state) => const BlockedUsersScreen(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/welcome-screen',
|
path: '/welcome-screen',
|
||||||
name: 'welcomeScreen',
|
name: 'welcomeScreen',
|
||||||
@@ -241,26 +232,10 @@ class AppRouter {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(path: ConsultationsArchiveScreen.path, name: ConsultationsArchiveScreen.name, builder: (context, state) => const ConsultationsArchiveScreen()),
|
||||||
path: ConsultationsArchiveScreen.path,
|
GoRoute(path: ConsultantFinancesScreen.path, name: ConsultantFinancesScreen.name, builder: (context, state) => const ConsultantFinancesScreen()),
|
||||||
name: ConsultationsArchiveScreen.name,
|
GoRoute(path: UserPurchasesScreen.path, name: UserPurchasesScreen.name, builder: (context, state) => const UserPurchasesScreen()),
|
||||||
builder: (context, state) => const ConsultationsArchiveScreen(),
|
GoRoute(path: '/user-finance-screen', name: 'userFinanceScreen', builder: (context, state) => const UserFinanceScreen()),
|
||||||
),
|
|
||||||
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(
|
GoRoute(
|
||||||
path: CardSettingsScreen.path,
|
path: CardSettingsScreen.path,
|
||||||
name: CardSettingsScreen.name,
|
name: CardSettingsScreen.name,
|
||||||
@@ -275,11 +250,7 @@ class AppRouter {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final slug = state.uri.queryParameters['slug'];
|
final slug = state.uri.queryParameters['slug'];
|
||||||
if (slug != null) {
|
if (slug != null) {
|
||||||
return AboutPlanScreen(
|
return AboutPlanScreen(slug: slug, navigateToComments: false, index: null);
|
||||||
slug: slug,
|
|
||||||
navigateToComments: false,
|
|
||||||
index: null,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
@@ -293,29 +264,23 @@ class AppRouter {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final slug = state.uri.queryParameters['slug'];
|
final slug = state.uri.queryParameters['slug'];
|
||||||
if (slug != null) {
|
if (slug != null) {
|
||||||
return AboutRecipeScreen(
|
return AboutRecipeScreen(recipeSlug: slug, navigateToComments: false, index: null);
|
||||||
recipeSlug: slug,
|
|
||||||
navigateToComments: false,
|
|
||||||
index: null,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
return AboutRecipeScreen(
|
return AboutRecipeScreen(recipeSlug: data['recipe'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
|
||||||
recipeSlug: data['recipe'] as String,
|
|
||||||
index: data['index'] as int?,
|
|
||||||
navigateToComments: data['navigateToComments'] as bool,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: RecipeCreateScreen.path,
|
path: RecipeCreateScreen.path,
|
||||||
name: RecipeCreateScreen.name,
|
name: RecipeCreateScreen.name,
|
||||||
builder: (context, state) => RecipeCreateScreen(
|
builder: (context, state) => RecipeCreateScreen(recipe: state.extra != null ? state.extra as Recipe : null),
|
||||||
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(
|
GoRoute(
|
||||||
path: '/subs-screen/:slug',
|
path: '/subs-screen/:slug',
|
||||||
name: 'subsScreen',
|
name: 'subsScreen',
|
||||||
@@ -327,11 +292,7 @@ class AppRouter {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final slug = state.uri.queryParameters['slug'];
|
final slug = state.uri.queryParameters['slug'];
|
||||||
if (slug != null) {
|
if (slug != null) {
|
||||||
return AboutDietScreen(
|
return AboutDietScreen(slug: slug, index: null, navigateToComments: false);
|
||||||
slug: slug,
|
|
||||||
index: null,
|
|
||||||
navigateToComments: false,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
return AboutDietScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
|
return AboutDietScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
|
||||||
@@ -343,19 +304,18 @@ class AppRouter {
|
|||||||
builder: (context, state) => BlogPostCreateScreen(feedBlogPostModel: state.extra != null ? state.extra as FeedBlogPostModel : null),
|
builder: (context, state) => BlogPostCreateScreen(feedBlogPostModel: state.extra != null ? state.extra as FeedBlogPostModel : null),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/blog-post-screen',
|
path: '/blog-post-screen',
|
||||||
name: 'blogPostScreen',
|
name: 'blogPostScreen',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final id = state.uri.queryParameters['id'];
|
final id = state.uri.queryParameters['id'];
|
||||||
final parsedId = int.tryParse(id ?? '');
|
final parsedId = int.tryParse(id ?? '');
|
||||||
if (parsedId != null) {
|
if (parsedId != null) {
|
||||||
return BlogPostScreen(
|
return BlogPostScreen(id: parsedId);
|
||||||
id: parsedId,
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return BlogPostScreen(id: state.extra as int);
|
return BlogPostScreen(id: state.extra as int);
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/chat-screen',
|
path: '/chat-screen',
|
||||||
name: 'chatScreen',
|
name: 'chatScreen',
|
||||||
@@ -363,10 +323,7 @@ class AppRouter {
|
|||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
final uuid = data['uuid'] as String;
|
final uuid = data['uuid'] as String;
|
||||||
final sendInitialMessage = data['sendInitialMessage'] as bool?;
|
final sendInitialMessage = data['sendInitialMessage'] as bool?;
|
||||||
return ChatScreen(
|
return ChatScreen(uuid: uuid, sendInitialMessage: sendInitialMessage);
|
||||||
uuid: uuid,
|
|
||||||
sendInitialMessage: sendInitialMessage,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -399,7 +356,11 @@ class AppRouter {
|
|||||||
name: 'physicalFormScreen',
|
name: 'physicalFormScreen',
|
||||||
builder: (context, state) => PhysicalFormScreen(settingsEntity: state.extra as SettingsEntity),
|
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(
|
GoRoute(
|
||||||
path: 'change-password-screen',
|
path: 'change-password-screen',
|
||||||
name: 'changePasswordScreen',
|
name: 'changePasswordScreen',
|
||||||
@@ -416,9 +377,7 @@ class AppRouter {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/stepper-become-consultant-screen',
|
path: '/stepper-become-consultant-screen',
|
||||||
name: 'stepperBecomeConsultantScreen',
|
name: 'stepperBecomeConsultantScreen',
|
||||||
builder: (context, state) => StepperBecomeConsultantScreen(
|
builder: (context, state) => StepperBecomeConsultantScreen(slug: state.extra == null ? null : state.extra as String),
|
||||||
slug: state.extra == null ? null : state.extra as String,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
GoRoute(path: '/calendar-screen', name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
|
GoRoute(path: '/calendar-screen', name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
|
||||||
GoRoute(path: '/support-screen', name: 'supportScreen', builder: (context, state) => const SupportScreen()),
|
GoRoute(path: '/support-screen', name: 'supportScreen', builder: (context, state) => const SupportScreen()),
|
||||||
@@ -446,7 +405,11 @@ class AppRouter {
|
|||||||
GoRoute(path: CreatePlanScreen.path, name: CreatePlanScreen.name, builder: (context, state) => const CreatePlanScreen()),
|
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: MyDietsScreen.path, name: MyDietsScreen.name, builder: (context, state) => const MyDietsScreen()),
|
||||||
GoRoute(path: MyPlansScreen.path, name: MyPlansScreen.name, builder: (context, state) => const MyPlansScreen()),
|
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: DevToolsScreen.path, name: DevToolsScreen.name, builder: (context, state) => const DevToolsScreen()),
|
||||||
GoRoute(path: ConsultantRatingScreen.path, name: ConsultantRatingScreen.name, builder: (context, state) => const ConsultantRatingScreen()),
|
GoRoute(path: ConsultantRatingScreen.path, name: ConsultantRatingScreen.name, builder: (context, state) => const ConsultantRatingScreen()),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -456,10 +419,7 @@ class AppRouter {
|
|||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
final planId = data['planId'] as int;
|
final planId = data['planId'] as int;
|
||||||
final successfulPayment = data['successfulPayment'] as bool?;
|
final successfulPayment = data['successfulPayment'] as bool?;
|
||||||
return InfoByOrderScreen(
|
return InfoByOrderScreen(planId: planId, successfulPayment: successfulPayment);
|
||||||
planId: planId,
|
|
||||||
successfulPayment: successfulPayment,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -469,10 +429,7 @@ class AppRouter {
|
|||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
final planId = data['planId'] as int;
|
final planId = data['planId'] as int;
|
||||||
final url = data['url'] as String;
|
final url = data['url'] as String;
|
||||||
return PaymentScreen(
|
return PaymentScreen(url: url, planId: planId);
|
||||||
url: url,
|
|
||||||
planId: planId,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -482,10 +439,7 @@ class AppRouter {
|
|||||||
final data = state.extra as Map<String, int>;
|
final data = state.extra as Map<String, int>;
|
||||||
final diaryId = data['diaryId'] as int;
|
final diaryId = data['diaryId'] as int;
|
||||||
final currentPlanDay = data['currentDiaryDay'] as int;
|
final currentPlanDay = data['currentDiaryDay'] as int;
|
||||||
return UserDiaryScreen(
|
return UserDiaryScreen(diaryId: diaryId, currentPlanDay: currentPlanDay);
|
||||||
diaryId: diaryId,
|
|
||||||
currentPlanDay: currentPlanDay,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -493,16 +447,9 @@ class AppRouter {
|
|||||||
name: 'verifyScreen',
|
name: 'verifyScreen',
|
||||||
builder: (context, state) => VerifyScreen(passKey: state.extra as String),
|
builder: (context, state) => VerifyScreen(passKey: state.extra as String),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(path: '/verify-email-screen', name: 'verifyEmailScreen', builder: (context, state) => const VerifyEmailScreen()),
|
||||||
path: '/verify-email-screen',
|
GoRoute(path: MyRecipesScreen.path, name: MyRecipesScreen.name, builder: (context, state) => const MyRecipesScreen()),
|
||||||
name: 'verifyEmailScreen',
|
GoRoute(path: '/notifications-settings-screen', name: 'notificationsSettingsScreen', builder: (context, state) => const NotificationsSettingsScreen()),
|
||||||
builder: (context, state) => const VerifyEmailScreen(),
|
|
||||||
),
|
|
||||||
GoRoute(
|
|
||||||
path: MyRecipesScreen.path,
|
|
||||||
name: MyRecipesScreen.name,
|
|
||||||
builder: (context, state) => const MyRecipesScreen(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
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/bottom_sheet_handler_bar.dart';
|
||||||
import 'package:eatvio_app/core/widgets/custom_divider.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/core/widgets/custom_text_form_field.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
void showCommentsActionsBottomSheet(
|
void showCommentsActionsBottomSheet(
|
||||||
@@ -19,25 +18,23 @@ void showCommentsActionsBottomSheet(
|
|||||||
required bool isMyComment,
|
required bool isMyComment,
|
||||||
}) {
|
}) {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
builder: (context) => Container(
|
builder: (context) => Container(
|
||||||
margin: EdgeInsets.only(left: 8, right: 8, bottom: MediaQuery.of(context).padding.bottom + 20),
|
margin: EdgeInsets.only(left: 8, right: 8, bottom: MediaQuery.of(context).viewInsets.bottom + MediaQuery.of(context).padding.bottom + 20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: appTheme.whiteA700),
|
||||||
borderRadius: BorderRadius.circular(12),
|
child: CommentsActionsBottomSheet(
|
||||||
color: appTheme.whiteA700,
|
isMyComment: isMyComment,
|
||||||
),
|
reply: reply,
|
||||||
child: CommentsActionsBottomSheet(
|
copy: copy,
|
||||||
isMyComment: isMyComment,
|
edit: edit,
|
||||||
reply: reply,
|
delete: delete,
|
||||||
copy: copy,
|
complain: complain,
|
||||||
edit: edit,
|
commentText: commentText,
|
||||||
delete: delete,
|
),
|
||||||
complain: complain,
|
),
|
||||||
commentText: commentText,
|
);
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class CommentsActionsBottomSheet extends StatefulWidget {
|
class CommentsActionsBottomSheet extends StatefulWidget {
|
||||||
@@ -83,47 +80,36 @@ class _CommentsActionsBottomSheetState extends State<CommentsActionsBottomSheet>
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Row(children: [
|
Row(
|
||||||
Expanded(
|
children: [
|
||||||
child: CustomTextFormField(
|
Expanded(
|
||||||
autofocus: true,
|
child: CustomTextFormField(autofocus: true, controller: controller, onChanged: (value) => setState(() {})),
|
||||||
controller: controller,
|
|
||||||
onChanged: (value) => setState(() {}),
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
const SizedBox(width: 8),
|
GestureDetector(
|
||||||
GestureDetector(
|
onTap: () {
|
||||||
onTap: () {
|
widget.edit(controller.text);
|
||||||
widget.edit(controller.text);
|
GoRouter.of(context).pop();
|
||||||
GoRouter.of(context).pop();
|
},
|
||||||
},
|
child: Container(
|
||||||
child: Container(
|
decoration: BoxDecoration(color: controller.text.length > 2 ? appTheme.blueA400 : appTheme.gray50, shape: BoxShape.circle),
|
||||||
decoration: BoxDecoration(
|
height: 32,
|
||||||
color: controller.text.length > 2 ? appTheme.blueA400 : appTheme.gray50,
|
width: 32,
|
||||||
shape: BoxShape.circle,
|
child: Icon(Icons.send_rounded, size: 18, color: controller.text.length > 2 ? appTheme.whiteA700 : appTheme.black),
|
||||||
),
|
|
||||||
height: 32,
|
|
||||||
width: 32,
|
|
||||||
child: Icon(
|
|
||||||
Icons.send_rounded,
|
|
||||||
size: 18,
|
|
||||||
color: controller.text.length > 2 ? appTheme.whiteA700 : appTheme.black,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
],
|
||||||
])
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Column(
|
: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(
|
const SizedBox(height: 7),
|
||||||
height: 7,
|
|
||||||
),
|
|
||||||
const BottomSheetHandlerBar(),
|
const BottomSheetHandlerBar(),
|
||||||
if (widget.isMyComment)
|
if (widget.isMyComment)
|
||||||
_ActionWidget(
|
BottomSheetActionWidget(
|
||||||
text: s.edit,
|
text: s.edit,
|
||||||
iconPath: 'assets/icons/edit_paint.svg',
|
iconPath: 'assets/icons/edit_paint.svg',
|
||||||
needToPop: false,
|
needToPop: false,
|
||||||
@@ -132,73 +118,22 @@ class _CommentsActionsBottomSheetState extends State<CommentsActionsBottomSheet>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
else if (widget.reply != null)
|
else if (widget.reply != null)
|
||||||
_ActionWidget(
|
BottomSheetActionWidget(text: s.reply, iconPath: 'assets/icons/reply.svg', onTap: widget.reply!),
|
||||||
text: s.reply,
|
|
||||||
iconPath: 'assets/icons/reply.svg',
|
|
||||||
onTap: widget.reply!,
|
|
||||||
),
|
|
||||||
if (widget.reply != null) const CustomDivider(),
|
if (widget.reply != null) const CustomDivider(),
|
||||||
_ActionWidget(
|
BottomSheetActionWidget(text: s.copy, iconPath: 'assets/icons/copy2.svg', onTap: widget.copy),
|
||||||
text: s.copy,
|
|
||||||
iconPath: 'assets/icons/copy2.svg',
|
|
||||||
onTap: widget.copy,
|
|
||||||
),
|
|
||||||
if (widget.isMyComment) ...[
|
if (widget.isMyComment) ...[
|
||||||
if (widget.reply != null) ...[
|
if (widget.reply != null) ...[
|
||||||
const CustomDivider(),
|
const CustomDivider(),
|
||||||
_ActionWidget(
|
BottomSheetActionWidget(text: s.reply, iconPath: 'assets/icons/reply.svg', onTap: widget.reply!),
|
||||||
text: s.reply,
|
|
||||||
iconPath: 'assets/icons/reply.svg',
|
|
||||||
onTap: widget.reply!,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
const CustomDivider(),
|
const CustomDivider(),
|
||||||
_ActionWidget(
|
BottomSheetActionWidget(text: s.deleteComment, iconPath: 'assets/icons/red_trash.svg', onTap: widget.delete),
|
||||||
text: s.deleteComment,
|
|
||||||
iconPath: 'assets/icons/red_trash.svg',
|
|
||||||
onTap: widget.delete,
|
|
||||||
),
|
|
||||||
] else ...[
|
] else ...[
|
||||||
const CustomDivider(),
|
const CustomDivider(),
|
||||||
_ActionWidget(
|
BottomSheetActionWidget(text: s.complain, iconPath: 'assets/icons/complain.svg', onTap: widget.complain),
|
||||||
text: s.complain,
|
],
|
||||||
iconPath: 'assets/icons/complain.svg',
|
|
||||||
onTap: widget.complain,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ActionWidget extends StatelessWidget {
|
|
||||||
const _ActionWidget({required this.text, required this.iconPath, required this.onTap, this.needToPop = true});
|
|
||||||
final String text;
|
|
||||||
final String iconPath;
|
|
||||||
final void Function() onTap;
|
|
||||||
final bool needToPop;
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
if (needToPop) Navigator.pop(context);
|
|
||||||
onTap();
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
text,
|
|
||||||
style: TextStyles.textLMedium.copyWith(color: appTheme.black),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SvgPicture.asset(iconPath)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:eatvio_app/core/env_config.dart';
|
|||||||
|
|
||||||
String baseUrl = EnvConfig.baseUrl;
|
String baseUrl = EnvConfig.baseUrl;
|
||||||
bool hidePlans = const bool.fromEnvironment('HIDE_PLANS', defaultValue: false);
|
bool hidePlans = const bool.fromEnvironment('HIDE_PLANS', defaultValue: false);
|
||||||
|
const buildDate = String.fromEnvironment('BUILD_DATE', defaultValue: '');
|
||||||
|
|
||||||
const List<int> durationValues = [7, 14, 21];
|
const List<int> durationValues = [7, 14, 21];
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
||||||
|
|
||||||
abstract class CredentialsStorage {
|
|
||||||
static Future<bool> 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<Map<String, String>> getCredentialsFromStorage() async {
|
|
||||||
return {
|
|
||||||
'email': (await const FlutterSecureStorage().read(key: 'email'))!,
|
|
||||||
'password': (await const FlutterSecureStorage().read(key: 'password'))!,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> saveCredentials(String email, String password) async {
|
|
||||||
await const FlutterSecureStorage().write(key: 'email', value: email);
|
|
||||||
await const FlutterSecureStorage().write(key: 'password', value: password);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> removeCredentials() async {
|
|
||||||
await const FlutterSecureStorage().delete(key: 'email');
|
|
||||||
await const FlutterSecureStorage().delete(key: 'password');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,9 @@ class EnvConfig {
|
|||||||
// Sentry Configuration
|
// Sentry Configuration
|
||||||
static String get sentryDsn => dotenv.env['SENTRY_DSN'] ?? '';
|
static String get sentryDsn => dotenv.env['SENTRY_DSN'] ?? '';
|
||||||
|
|
||||||
|
// AppMetrica Configuration
|
||||||
|
static String get appMetricaApiKey => dotenv.env['APPMETRICA_KEY'] ?? '';
|
||||||
|
|
||||||
// Firebase Configuration
|
// Firebase Configuration
|
||||||
static String get firebaseProjectId => dotenv.env['FIREBASE_PROJECT_ID'] ?? '';
|
static String get firebaseProjectId => dotenv.env['FIREBASE_PROJECT_ID'] ?? '';
|
||||||
static String get firebaseDatabaseUrl => dotenv.env['FIREBASE_DATABASE_URL'] ?? '';
|
static String get firebaseDatabaseUrl => dotenv.env['FIREBASE_DATABASE_URL'] ?? '';
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import 'package:eatvio_app/core/const.dart';
|
|||||||
import 'package:eatvio_app/core/dio/phone_verify_error_interceptor.dart';
|
import 'package:eatvio_app/core/dio/phone_verify_error_interceptor.dart';
|
||||||
import 'package:eatvio_app/core/firebase_api.dart';
|
import 'package:eatvio_app/core/firebase_api.dart';
|
||||||
import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
|
import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
|
||||||
|
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
|
||||||
|
import 'package:eatvio_app/core/views/repository/views_repository.dart';
|
||||||
|
import 'package:eatvio_app/core/views/service/views_service.dart';
|
||||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||||
import 'package:eatvio_app/core/websockets/ws_service.dart';
|
import 'package:eatvio_app/core/websockets/ws_service.dart';
|
||||||
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
|
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
|
||||||
@@ -20,6 +23,9 @@ import 'package:eatvio_app/features/cancel_plan_bottom_sheet/service/cancel_plan
|
|||||||
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart';
|
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart';
|
||||||
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart';
|
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart';
|
||||||
import 'package:eatvio_app/features/complaining/services/complaining_service.dart';
|
import 'package:eatvio_app/features/complaining/services/complaining_service.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/repositories/consultant_rating_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/services/consultant_rating_service.dart';
|
||||||
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
|
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
|
||||||
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
|
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
|
||||||
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
|
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
|
||||||
@@ -40,8 +46,11 @@ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_
|
|||||||
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart';
|
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart';
|
||||||
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart';
|
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart';
|
||||||
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
|
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_cubit.dart';
|
||||||
import 'package:eatvio_app/features/menu/presentation/repositories/blocked_users_repository.dart';
|
import 'package:eatvio_app/features/menu/presentation/repositories/blocked_users_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.dart';
|
||||||
import 'package:eatvio_app/features/menu/services/blocked_users_service.dart';
|
import 'package:eatvio_app/features/menu/services/blocked_users_service.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/services/notification_settings_service.dart';
|
||||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||||
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart';
|
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart';
|
||||||
@@ -107,7 +116,6 @@ import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/se
|
|||||||
import 'package:eatvio_app/features/settings/repositories/service_repository.dart';
|
import 'package:eatvio_app/features/settings/repositories/service_repository.dart';
|
||||||
import 'package:eatvio_app/features/settings/services/settings_service.dart';
|
import 'package:eatvio_app/features/settings/services/settings_service.dart';
|
||||||
import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_cubit.dart';
|
import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_cubit.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart' as profile_plans;
|
import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart' as profile_plans;
|
||||||
|
|
||||||
@@ -132,16 +140,18 @@ Future<void> init() async {
|
|||||||
sl.registerFactory<DietsCubit>(() => DietsCubit(feedRepository: sl(), dietsRepository: sl()));
|
sl.registerFactory<DietsCubit>(() => DietsCubit(feedRepository: sl(), dietsRepository: sl()));
|
||||||
sl.registerFactory<CommentsCubit>(() => CommentsCubit(feedRepository: sl(), commentsRepository: sl()));
|
sl.registerFactory<CommentsCubit>(() => CommentsCubit(feedRepository: sl(), commentsRepository: sl()));
|
||||||
sl.registerFactory<PlansCubit>(() => PlansCubit(feedRepository: sl(), planRepository: sl()));
|
sl.registerFactory<PlansCubit>(() => PlansCubit(feedRepository: sl(), planRepository: sl()));
|
||||||
sl.registerFactory<SettingsCubit>(() => SettingsCubit(profileRepository: sl(), settingsRepository: sl(), consultantRepository: sl()));
|
sl.registerFactory<SettingsCubit>(() => SettingsCubit(profileRepository: sl(), settingsRepository: sl(), consultantRepository: sl(), consultationsRepository: sl()));
|
||||||
sl.registerFactory<RegisterConsultantCubit>(() => RegisterConsultantCubit(consultantRepository: sl()));
|
sl.registerFactory<RegisterConsultantCubit>(() => RegisterConsultantCubit(consultantRepository: sl()));
|
||||||
sl.registerFactory<DiaryCubit>(() => DiaryCubit(
|
sl.registerFactory<DiaryCubit>(
|
||||||
chatRepository: sl(),
|
() => DiaryCubit(
|
||||||
diaryRepository: sl(),
|
chatRepository: sl(),
|
||||||
glossaryRepository: sl(),
|
diaryRepository: sl(),
|
||||||
profileRepository: sl(),
|
glossaryRepository: sl(),
|
||||||
dietRepository: sl(),
|
profileRepository: sl(),
|
||||||
settingsRepository: sl(),
|
dietRepository: sl(),
|
||||||
));
|
settingsRepository: sl(),
|
||||||
|
),
|
||||||
|
);
|
||||||
sl.registerFactory<AboutDietCubit>(() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl()));
|
sl.registerFactory<AboutDietCubit>(() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl()));
|
||||||
sl.registerFactory<BlogPostCreateCubit>(() => BlogPostCreateCubit(blogPostRepository: sl()));
|
sl.registerFactory<BlogPostCreateCubit>(() => BlogPostCreateCubit(blogPostRepository: sl()));
|
||||||
sl.registerFactory<SubCubit>(() => SubCubit(profileRepository: sl()));
|
sl.registerFactory<SubCubit>(() => SubCubit(profileRepository: sl()));
|
||||||
@@ -170,6 +180,9 @@ Future<void> init() async {
|
|||||||
sl.registerFactory<ResetPasswordCubit>(() => ResetPasswordCubit(resetPasswordRepository: sl()));
|
sl.registerFactory<ResetPasswordCubit>(() => ResetPasswordCubit(resetPasswordRepository: sl()));
|
||||||
sl.registerFactory<BlogPostCubit>(() => BlogPostCubit(sl()));
|
sl.registerFactory<BlogPostCubit>(() => BlogPostCubit(sl()));
|
||||||
sl.registerFactory<BlockedUsersCubit>(() => BlockedUsersCubit(blockedUsersRepository: sl()));
|
sl.registerFactory<BlockedUsersCubit>(() => BlockedUsersCubit(blockedUsersRepository: sl()));
|
||||||
|
sl.registerFactory<NotificationSettingsCubit>(() => NotificationSettingsCubit(notificationSettingsRepository: sl()));
|
||||||
|
sl.registerFactory<ConsultantRatingCubit>(() => ConsultantRatingCubit(sl()));
|
||||||
|
sl.registerFactory<ViewsCubit>(() => ViewsCubit(viewsRepository: sl()));
|
||||||
|
|
||||||
// Repository
|
// Repository
|
||||||
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
|
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
|
||||||
@@ -201,6 +214,9 @@ Future<void> init() async {
|
|||||||
sl.registerFactory<ResetPasswordRepository>(() => ResetPasswordRepository(resetPasswordService: sl()));
|
sl.registerFactory<ResetPasswordRepository>(() => ResetPasswordRepository(resetPasswordService: sl()));
|
||||||
sl.registerLazySingleton<BlockedUsersRepository>(() => BlockedUsersRepository(blockedUserService: sl()));
|
sl.registerLazySingleton<BlockedUsersRepository>(() => BlockedUsersRepository(blockedUserService: sl()));
|
||||||
sl.registerLazySingleton<CommentsRepository>(() => CommentsRepository(service: sl()));
|
sl.registerLazySingleton<CommentsRepository>(() => CommentsRepository(service: sl()));
|
||||||
|
sl.registerLazySingleton<NotificationSettingsRepository>(() => NotificationSettingsRepository(sl()));
|
||||||
|
sl.registerFactory<ConsultantRatingRepository>(() => ConsultantRatingRepository(sl()));
|
||||||
|
sl.registerFactory<ViewsRepository>(() => ViewsRepository(viewsService: sl()));
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
|
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
|
||||||
@@ -229,28 +245,19 @@ Future<void> init() async {
|
|||||||
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
|
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
|
||||||
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
|
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
|
||||||
sl.registerLazySingleton<NotificationsService>(() => NotificationsService(sl()));
|
sl.registerLazySingleton<NotificationsService>(() => NotificationsService(sl()));
|
||||||
sl.registerLazySingleton<MyRecipesService>(() => MyRecipesService(sl()));
|
|
||||||
sl.registerLazySingleton<FirebaseApi>(() => FirebaseApi());
|
sl.registerLazySingleton<FirebaseApi>(() => FirebaseApi());
|
||||||
sl.registerLazySingleton<ResetPasswordService>(() => ResetPasswordService(sl()));
|
sl.registerLazySingleton<ResetPasswordService>(() => ResetPasswordService(sl()));
|
||||||
sl.registerLazySingleton<BlockedUserService>(() => BlockedUserService(sl()));
|
sl.registerLazySingleton<BlockedUserService>(() => BlockedUserService(sl()));
|
||||||
sl.registerLazySingleton<CommentsService>(() => CommentsService(sl()));
|
sl.registerLazySingleton<CommentsService>(() => CommentsService(sl()));
|
||||||
|
sl.registerLazySingleton<MyRecipesService>(() => MyRecipesService(sl()));
|
||||||
|
sl.registerLazySingleton<ConsultantRatingService>(() => ConsultantRatingService(sl()));
|
||||||
|
sl.registerLazySingleton<NotificationSettingsService>(() => NotificationSettingsService(sl()));
|
||||||
|
sl.registerLazySingleton<ViewsService>(() => ViewsService(sl()));
|
||||||
|
|
||||||
// External
|
// External
|
||||||
sl.registerLazySingleton(
|
sl.registerLazySingleton(
|
||||||
() => Dio(
|
() =>
|
||||||
BaseOptions(
|
Dio(BaseOptions(baseUrl: baseUrl, headers: {'accept': 'application/json', 'content-type': 'application/json'}))
|
||||||
baseUrl: baseUrl,
|
..interceptors.addAll([TokenInterceptor(), PhoneVerifyErrorInterceptor()]),
|
||||||
headers: {
|
|
||||||
'accept': 'application/json',
|
|
||||||
'content-type': 'application/json',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)..interceptors.addAll(
|
|
||||||
[
|
|
||||||
TokenInterceptor(),
|
|
||||||
PhoneVerifyErrorInterceptor(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
sl.registerLazySingleton(() async => const FlutterSecureStorage());
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,6 +202,8 @@ class PrimaryColors {
|
|||||||
Color get darkGray => const Color(0XFFBBBCC0);
|
Color get darkGray => const Color(0XFFBBBCC0);
|
||||||
|
|
||||||
Color get commentBg => const Color(0XFFFFFDD8);
|
Color get commentBg => const Color(0XFFFFFDD8);
|
||||||
|
|
||||||
|
Color get cardNotificationsBG => const Color(0xFFA4A4A4);
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimaryColors get appTheme => ThemeHelper().themeColor();
|
PrimaryColors get appTheme => ThemeHelper().themeColor();
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
abstract class TokenStorage {
|
abstract class TokenStorage {
|
||||||
static Future<bool> haveTokenInStorage() async {
|
static Future<bool> haveTokenInStorage() async {
|
||||||
|
final sharedPreferences = await SharedPreferences.getInstance();
|
||||||
try {
|
try {
|
||||||
if (await const FlutterSecureStorage().read(key: 'token') == null) {
|
if (sharedPreferences.getString('token') == null) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
@@ -13,19 +14,27 @@ abstract class TokenStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<String?> getTokenFromStorage() async => await const FlutterSecureStorage().read(key: 'token');
|
static Future<String?> getTokenFromStorage() async {
|
||||||
|
final sharedPreferences = await SharedPreferences.getInstance();
|
||||||
static Future<void> saveTokenInStorage(String token) async {
|
return sharedPreferences.getString('token');
|
||||||
await const FlutterSecureStorage().write(key: 'token', value: token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> removeTokenInStorage() async => (await const FlutterSecureStorage().delete(key: 'token'));
|
static Future<void> saveTokenInStorage(String token) async {
|
||||||
|
final sharedPreferences = await SharedPreferences.getInstance();
|
||||||
|
await sharedPreferences.setString('token', token);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> removeTokenInStorage() async {
|
||||||
|
final sharedPreferences = await SharedPreferences.getInstance();
|
||||||
|
await sharedPreferences.remove('token');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class SocketIdStorage {
|
abstract class SocketIdStorage {
|
||||||
static Future<bool> haveSocketIdInStorage() async {
|
static Future<bool> haveSocketIdInStorage() async {
|
||||||
|
final sharedPreferences = await SharedPreferences.getInstance();
|
||||||
try {
|
try {
|
||||||
if (await const FlutterSecureStorage().read(key: 'socketId') == null) {
|
if (sharedPreferences.getString('socketId') == null) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
@@ -35,11 +44,18 @@ abstract class SocketIdStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<String?> getSocketIdFromStorage() async => await const FlutterSecureStorage().read(key: 'socketId');
|
static Future<String?> getSocketIdFromStorage() async {
|
||||||
|
final sharedPreferences = await SharedPreferences.getInstance();
|
||||||
static Future<void> saveSocketIdInStorage(String socketId) async {
|
return sharedPreferences.getString('socketId');
|
||||||
await const FlutterSecureStorage().write(key: 'socketId', value: socketId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> removeSocketIdInStorage() async => (await const FlutterSecureStorage().delete(key: 'socketId'));
|
static Future<void> saveSocketIdInStorage(String socketId) async {
|
||||||
|
final sharedPreferences = await SharedPreferences.getInstance();
|
||||||
|
await sharedPreferences.setString('socketId', socketId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> removeSocketIdInStorage() async {
|
||||||
|
final sharedPreferences = await SharedPreferences.getInstance();
|
||||||
|
await sharedPreferences.remove('socketId');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,30 @@ import 'package:intl/intl.dart';
|
|||||||
|
|
||||||
const String dateTimeFormatPattern = 'dd/MM/yyyy';
|
const String dateTimeFormatPattern = 'dd/MM/yyyy';
|
||||||
|
|
||||||
|
/// Преобразует дату из формата гггг-мм-дд чч:мм:сс в дд.мм.гггг
|
||||||
|
String formatBuildDate(String dateString) {
|
||||||
|
if (dateString.isEmpty) return '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
final dateTimeParts = dateString.split(' ');
|
||||||
|
if (dateTimeParts.isNotEmpty) {
|
||||||
|
final datePart = dateTimeParts[0];
|
||||||
|
final dateParts = datePart.split('-');
|
||||||
|
if (dateParts.length == 3) {
|
||||||
|
final year = dateParts[0];
|
||||||
|
final month = dateParts[1];
|
||||||
|
final day = dateParts[2];
|
||||||
|
return '$day.$month.$year';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return dateString;
|
||||||
|
}
|
||||||
|
return dateString;
|
||||||
|
}
|
||||||
|
|
||||||
extension DateTimeExtension on DateTime {
|
extension DateTimeExtension on DateTime {
|
||||||
String format([
|
String format([String pattern = dateTimeFormatPattern, String? locale]) {
|
||||||
String pattern = dateTimeFormatPattern,
|
|
||||||
String? locale,
|
|
||||||
]) {
|
|
||||||
final localDate = isUtc ? toLocal() : this;
|
final localDate = isUtc ? toLocal() : this;
|
||||||
if (locale != null && locale.isNotEmpty) {
|
if (locale != null && locale.isNotEmpty) {
|
||||||
initializeDateFormatting(locale);
|
initializeDateFormatting(locale);
|
||||||
@@ -19,17 +38,13 @@ extension DateTimeExtension on DateTime {
|
|||||||
final localDate = isUtc ? toLocal() : this;
|
final localDate = isUtc ? toLocal() : this;
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final yesterday = DateTime(now.year, now.month, now.day).subtract(const Duration(days: 1));
|
final yesterday = DateTime(now.year, now.month, now.day).subtract(const Duration(days: 1));
|
||||||
return localDate.year == yesterday.year &&
|
return localDate.year == yesterday.year && localDate.month == yesterday.month && localDate.day == yesterday.day;
|
||||||
localDate.month == yesterday.month &&
|
|
||||||
localDate.day == yesterday.day;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isToday() {
|
bool isToday() {
|
||||||
final localDate = isUtc ? toLocal() : this;
|
final localDate = isUtc ? toLocal() : this;
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
return now.year == localDate.year &&
|
return now.year == localDate.year && now.month == localDate.month && now.day == localDate.day;
|
||||||
now.month == localDate.month &&
|
|
||||||
now.day == localDate.day;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
128
lib/core/views/cubit/views_cubit.dart
Normal file
128
lib/core/views/cubit/views_cubit.dart
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:eatvio_app/core/views/cubit/views_state.dart';
|
||||||
|
import 'package:eatvio_app/core/views/repository/views_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/feed/models/view_model.dart';
|
||||||
|
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class ViewsCubit extends Cubit<ViewsCubitState> {
|
||||||
|
final ViewsRepository viewsRepository;
|
||||||
|
ViewsCubit({required this.viewsRepository}) : super(ViewsCubitState()) {
|
||||||
|
_listener = AppLifecycleListener(onPause: _sendViews, onInactive: _sendViews);
|
||||||
|
}
|
||||||
|
|
||||||
|
late final AppLifecycleListener _listener;
|
||||||
|
|
||||||
|
final Set<int> postsIds = <int>{};
|
||||||
|
final Set<int> plansIds = <int>{};
|
||||||
|
final Set<int> dietsIds = <int>{};
|
||||||
|
final Set<int> recipesIds = <int>{};
|
||||||
|
|
||||||
|
final Set<int> _sentPosts = <int>{};
|
||||||
|
final Set<int> _sentPlans = <int>{};
|
||||||
|
final Set<int> _sentDiets = <int>{};
|
||||||
|
final Set<int> _sentRecipes = <int>{};
|
||||||
|
|
||||||
|
final Map<ViewableContentType, Map<int, Timer>> _visibilityTimers = {
|
||||||
|
ViewableContentType.posts: <int, Timer>{},
|
||||||
|
ViewableContentType.plans: <int, Timer>{},
|
||||||
|
ViewableContentType.diets: <int, Timer>{},
|
||||||
|
ViewableContentType.recipes: <int, Timer>{},
|
||||||
|
};
|
||||||
|
|
||||||
|
Timer? _debounceTimer;
|
||||||
|
|
||||||
|
Set<int> _idsForType(ViewableContentType type) {
|
||||||
|
switch (type) {
|
||||||
|
case ViewableContentType.posts:
|
||||||
|
return postsIds;
|
||||||
|
case ViewableContentType.plans:
|
||||||
|
return plansIds;
|
||||||
|
case ViewableContentType.diets:
|
||||||
|
return dietsIds;
|
||||||
|
case ViewableContentType.recipes:
|
||||||
|
return recipesIds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<int> _sentIdsForType(ViewableContentType type) {
|
||||||
|
switch (type) {
|
||||||
|
case ViewableContentType.posts:
|
||||||
|
return _sentPosts;
|
||||||
|
case ViewableContentType.plans:
|
||||||
|
return _sentPlans;
|
||||||
|
case ViewableContentType.diets:
|
||||||
|
return _sentDiets;
|
||||||
|
case ViewableContentType.recipes:
|
||||||
|
return _sentRecipes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleVisibility({required double visibleFraction, required int id, required ViewableContentType type}) {
|
||||||
|
final timers = _visibilityTimers[type]!;
|
||||||
|
final ids = _idsForType(type);
|
||||||
|
final sent = _sentIdsForType(type);
|
||||||
|
|
||||||
|
if (sent.contains(id)) {
|
||||||
|
timers[id]?.cancel();
|
||||||
|
timers.remove(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ids.contains(id)) return;
|
||||||
|
|
||||||
|
if (visibleFraction >= 0.5) {
|
||||||
|
if (timers.containsKey(id)) return;
|
||||||
|
timers[id] = Timer(const Duration(seconds: 1), () {
|
||||||
|
ids.add(id);
|
||||||
|
timers.remove(id);
|
||||||
|
_scheduleDebouncedSend();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
timers[id]?.cancel();
|
||||||
|
timers.remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _viewContent({required ViewableContentType type, required List<int> ids}) async {
|
||||||
|
await viewsRepository.viewContent(
|
||||||
|
viewModel: ViewModel(type: type.getStringifiedVersion(), ids: ids),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _scheduleDebouncedSend() {
|
||||||
|
_debounceTimer?.cancel();
|
||||||
|
_debounceTimer = Timer(const Duration(seconds: 4), _sendViews);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _send(Set<int> ids, Set<int> sent, ViewableContentType type) {
|
||||||
|
final newIds = ids.difference(sent);
|
||||||
|
if (newIds.isNotEmpty) {
|
||||||
|
_viewContent(type: type, ids: newIds.toList());
|
||||||
|
sent.addAll(newIds);
|
||||||
|
ids.removeAll(newIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _sendViews() {
|
||||||
|
_send(postsIds, _sentPosts, ViewableContentType.posts);
|
||||||
|
_send(plansIds, _sentPlans, ViewableContentType.plans);
|
||||||
|
_send(dietsIds, _sentDiets, ViewableContentType.diets);
|
||||||
|
_send(recipesIds, _sentRecipes, ViewableContentType.recipes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
for (final map in _visibilityTimers.values) {
|
||||||
|
for (final t in map.values) {
|
||||||
|
t.cancel();
|
||||||
|
}
|
||||||
|
map.clear();
|
||||||
|
}
|
||||||
|
_debounceTimer?.cancel();
|
||||||
|
_listener.dispose();
|
||||||
|
_sendViews();
|
||||||
|
await super.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
3
lib/core/views/cubit/views_state.dart
Normal file
3
lib/core/views/cubit/views_state.dart
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
class ViewsCubitState {
|
||||||
|
ViewsCubitState();
|
||||||
|
}
|
||||||
16
lib/core/views/repository/views_repository.dart
Normal file
16
lib/core/views/repository/views_repository.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import 'package:eatvio_app/core/views/service/views_service.dart';
|
||||||
|
import 'package:eatvio_app/features/feed/models/view_model.dart';
|
||||||
|
|
||||||
|
class ViewsRepository {
|
||||||
|
final ViewsService viewsService;
|
||||||
|
|
||||||
|
ViewsRepository({required this.viewsService});
|
||||||
|
|
||||||
|
Future<void> viewContent({required ViewModel viewModel}) async {
|
||||||
|
try {
|
||||||
|
await viewsService.viewContent(viewModel: viewModel);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
lib/core/views/service/views_service.dart
Normal file
15
lib/core/views/service/views_service.dart
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:eatvio_app/features/feed/models/view_model.dart';
|
||||||
|
import 'package:retrofit/retrofit.dart';
|
||||||
|
|
||||||
|
part 'views_service.g.dart';
|
||||||
|
|
||||||
|
@RestApi()
|
||||||
|
abstract class ViewsService {
|
||||||
|
factory ViewsService(Dio dio, {String baseUrl}) = _ViewsService;
|
||||||
|
|
||||||
|
@POST('/api/views')
|
||||||
|
Future<void> viewContent({
|
||||||
|
@Body() required ViewModel viewModel,
|
||||||
|
});
|
||||||
|
}
|
||||||
33
lib/core/widgets/bottom_sheet_action_widget.dart
Normal file
33
lib/core/widgets/bottom_sheet_action_widget.dart
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
||||||
|
class BottomSheetActionWidget extends StatelessWidget {
|
||||||
|
const BottomSheetActionWidget({super.key, required this.text, required this.iconPath, required this.onTap, this.needToPop = true, this.color});
|
||||||
|
final String text;
|
||||||
|
final String iconPath;
|
||||||
|
final void Function() onTap;
|
||||||
|
final bool needToPop;
|
||||||
|
final Color? color;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
if (needToPop) Navigator.pop(context);
|
||||||
|
onTap();
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(text, style: TextStyles.textLMedium.copyWith(color: appTheme.black)),
|
||||||
|
),
|
||||||
|
SvgPicture.asset(iconPath, colorFilter: color != null ? ColorFilter.mode(color!, BlendMode.srcIn) : null),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,8 @@ import 'package:eatvio_app/core/data/selectionPopupModel/selection_popup_model.d
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CustomDropDown extends StatelessWidget {
|
class CustomDropDown extends StatelessWidget {
|
||||||
const CustomDropDown({super.key,
|
const CustomDropDown({
|
||||||
|
super.key,
|
||||||
this.alignment,
|
this.alignment,
|
||||||
this.width,
|
this.width,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
@@ -48,12 +49,7 @@ class CustomDropDown extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return alignment != null
|
return alignment != null ? Align(alignment: alignment ?? Alignment.center, child: _buildDropDownWidget(context)) : _buildDropDownWidget(context);
|
||||||
? Align(
|
|
||||||
alignment: alignment ?? Alignment.center,
|
|
||||||
child: _buildDropDownWidget(context),
|
|
||||||
)
|
|
||||||
: _buildDropDownWidget(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DropdownButtonFormField<SelectionPopupModel> _buildDropDownWidget(BuildContext context) {
|
DropdownButtonFormField<SelectionPopupModel> _buildDropDownWidget(BuildContext context) {
|
||||||
@@ -61,7 +57,7 @@ class CustomDropDown extends StatelessWidget {
|
|||||||
focusNode: focusNode ?? FocusNode(),
|
focusNode: focusNode ?? FocusNode(),
|
||||||
icon: const Icon(Icons.keyboard_arrow_down_rounded),
|
icon: const Icon(Icons.keyboard_arrow_down_rounded),
|
||||||
autofocus: autofocus!,
|
autofocus: autofocus!,
|
||||||
value: value,
|
initialValue: value,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
items: items?.map((SelectionPopupModel item) {
|
items: items?.map((SelectionPopupModel item) {
|
||||||
@@ -69,12 +65,7 @@ class CustomDropDown extends StatelessWidget {
|
|||||||
value: item,
|
value: item,
|
||||||
child: Text(
|
child: Text(
|
||||||
item.title,
|
item.title,
|
||||||
style: hintStyle ??
|
style: hintStyle ?? const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black),
|
||||||
const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
@@ -90,19 +81,9 @@ class CustomDropDown extends StatelessWidget {
|
|||||||
|
|
||||||
InputDecoration _buildInputDecoration(BuildContext context) {
|
InputDecoration _buildInputDecoration(BuildContext context) {
|
||||||
return InputDecoration(
|
return InputDecoration(
|
||||||
labelStyle: const TextStyle(
|
labelStyle: const TextStyle(fontSize: 16, color: Colors.black, fontWeight: FontWeight.w400),
|
||||||
fontSize: 16,
|
|
||||||
color: Colors.black,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
hintText: hintText ?? "",
|
hintText: hintText ?? "",
|
||||||
hintStyle: hintStyle ??
|
hintStyle: hintStyle ?? TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.grey.shade400, height: 1),
|
||||||
TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: Colors.grey.shade400,
|
|
||||||
height: 1,
|
|
||||||
),
|
|
||||||
prefixIcon: prefix,
|
prefixIcon: prefix,
|
||||||
prefixIconConstraints: prefixConstraints,
|
prefixIconConstraints: prefixConstraints,
|
||||||
suffixIcon: suffix,
|
suffixIcon: suffix,
|
||||||
@@ -111,18 +92,9 @@ class CustomDropDown extends StatelessWidget {
|
|||||||
contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
|
contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
|
||||||
fillColor: const Color(0xFFF6F6F6),
|
fillColor: const Color(0xFFF6F6F6),
|
||||||
filled: filled,
|
filled: filled,
|
||||||
border: const OutlineInputBorder(
|
border: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(20)), borderSide: BorderSide.none),
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
enabledBorder: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(20)), borderSide: BorderSide.none),
|
||||||
borderSide: BorderSide.none,
|
focusedBorder: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(20)), borderSide: BorderSide.none),
|
||||||
),
|
|
||||||
enabledBorder: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
|
||||||
borderSide: BorderSide.none,
|
|
||||||
),
|
|
||||||
focusedBorder: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
|
||||||
borderSide: BorderSide.none,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
||||||
class CustomDropDownButton<T> extends StatelessWidget {
|
class CustomDropDownButton<T> extends StatelessWidget {
|
||||||
const CustomDropDownButton({
|
const CustomDropDownButton({super.key, required this.items, required this.onChanged, this.value, this.hint, this.hasError = false});
|
||||||
super.key,
|
|
||||||
required this.items,
|
|
||||||
required this.onChanged,
|
|
||||||
this.value,
|
|
||||||
this.hint,
|
|
||||||
this.hasError = false,
|
|
||||||
});
|
|
||||||
final T? value;
|
final T? value;
|
||||||
final List<DropdownMenuItem<T>> items;
|
final List<DropdownMenuItem<T>> items;
|
||||||
final void Function(T?)? onChanged;
|
final void Function(T?)? onChanged;
|
||||||
@@ -20,11 +13,7 @@ class CustomDropDownButton<T> extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DropdownButtonFormField(
|
return DropdownButtonFormField(
|
||||||
hint: hint,
|
hint: hint,
|
||||||
icon: SvgPicture.asset(
|
icon: SvgPicture.asset('assets/icons/arrow_down2.svg', width: 12, height: 8),
|
||||||
'assets/icons/arrow_down2.svg',
|
|
||||||
width: 12,
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
dropdownColor: appTheme.lightGray0,
|
dropdownColor: appTheme.lightGray0,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@@ -43,7 +32,7 @@ class CustomDropDownButton<T> extends StatelessWidget {
|
|||||||
fillColor: appTheme.lightGray0,
|
fillColor: appTheme.lightGray0,
|
||||||
filled: true,
|
filled: true,
|
||||||
),
|
),
|
||||||
value: value,
|
initialValue: value,
|
||||||
items: items,
|
items: items,
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -47,12 +47,20 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
var keyboardVisibilityController = KeyboardVisibilityController();
|
var keyboardVisibilityController = KeyboardVisibilityController();
|
||||||
keyboardSubscription = keyboardVisibilityController.onChange.listen((bool visible) {
|
keyboardSubscription = keyboardVisibilityController.onChange.listen((bool visible) {
|
||||||
setState(() {
|
if (mounted) {
|
||||||
isFocused = visible;
|
setState(() {
|
||||||
});
|
isFocused = visible;
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
keyboardSubscription.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _reconnectWebSocket(BuildContext context) async {
|
Future<void> _reconnectWebSocket(BuildContext context) async {
|
||||||
try {
|
try {
|
||||||
await sl<WsManager>().clear();
|
await sl<WsManager>().clear();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:io';
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/core/app_router.dart';
|
import 'package:eatvio_app/core/app_router.dart';
|
||||||
import 'package:eatvio_app/core/const.dart';
|
import 'package:eatvio_app/core/const.dart';
|
||||||
|
import 'package:eatvio_app/core/firebase_api.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/core/token_storage.dart';
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||||
@@ -92,6 +93,7 @@ class _ScanQRCodeScreenState extends State<ScanQRCodeScreen> {
|
|||||||
await sl<ChatsCubit>().connectToWebSocket();
|
await sl<ChatsCubit>().connectToWebSocket();
|
||||||
await sl<NotificationsCubit>().getNotifications();
|
await sl<NotificationsCubit>().getNotifications();
|
||||||
await sl<NotificationsCubit>().connectToWebSocket();
|
await sl<NotificationsCubit>().connectToWebSocket();
|
||||||
|
await sl<FirebaseApi>().registerPushToken();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,55 @@
|
|||||||
|
import 'package:appmetrica_plugin/appmetrica_plugin.dart';
|
||||||
import 'package:eatvio_app/core/app_router.dart';
|
import 'package:eatvio_app/core/app_router.dart';
|
||||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
|
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
|
||||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
|
class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
|
||||||
final ProfileRepository profileRepository;
|
final ProfileRepository profileRepository;
|
||||||
|
|
||||||
ProfileAvatarCubit({
|
ProfileAvatarCubit({required this.profileRepository}) : super(const ProfileAvatarState.loading());
|
||||||
required this.profileRepository,
|
|
||||||
}) : super(const ProfileAvatarState.loading());
|
int _calculateAge(String birthdate) {
|
||||||
|
try {
|
||||||
|
final dateParts = birthdate.split('-');
|
||||||
|
if (dateParts.length == 3) {
|
||||||
|
final birthYear = int.parse(dateParts[0]);
|
||||||
|
final birthMonth = int.parse(dateParts[1]);
|
||||||
|
final birthDay = int.parse(dateParts[2]);
|
||||||
|
|
||||||
|
final now = DateTime.now();
|
||||||
|
int age = now.year - birthYear;
|
||||||
|
|
||||||
|
if (now.month < birthMonth || (now.month == birthMonth && now.day < birthDay)) {
|
||||||
|
age--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return age;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> getMyProfile() async {
|
Future<void> getMyProfile() async {
|
||||||
emit(const ProfileAvatarState.loading());
|
emit(const ProfileAvatarState.loading());
|
||||||
|
final profile = await profileRepository.getMyProfile();
|
||||||
|
final profileDetails = await profileRepository.getMyProfileDetails();
|
||||||
try {
|
try {
|
||||||
emit(
|
emit(ProfileAvatarState.loaded(profile: profile));
|
||||||
ProfileAvatarState.loaded(
|
final notificationStatus = await Permission.notification.status;
|
||||||
profile: await profileRepository.getMyProfile(),
|
final isNotificationEnabled = notificationStatus.isGranted;
|
||||||
),
|
AppMetricaUserProfile userProfile = AppMetricaUserProfile([
|
||||||
);
|
AppMetricaNameAttribute.withValue(profile.name),
|
||||||
|
AppMetricaGenderAttribute.withValue(profileDetails.gender == 'male' ? AppMetricaGender.male : AppMetricaGender.female),
|
||||||
|
AppMetricaBirthDateAttribute.withAge(_calculateAge(profileDetails.birthdate)),
|
||||||
|
AppMetricaNotificationEnabledAttribute.withValue(isNotificationEnabled),
|
||||||
|
]);
|
||||||
|
AppMetrica.setUserProfileID(profile.slug);
|
||||||
|
AppMetrica.reportUserProfile(userProfile);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
AppRouter.navigatorKey.currentContext?.go('/auth-screen');
|
AppRouter.navigatorKey.currentContext?.go('/auth-screen');
|
||||||
emit(ProfileAvatarState.error(errorMessage: e.toString()));
|
emit(ProfileAvatarState.error(errorMessage: e.toString()));
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ abstract class ChatMessageModel with _$ChatMessageModel {
|
|||||||
@JsonKey(name: 'read_by_to_user', defaultValue: false) required bool isRead,
|
@JsonKey(name: 'read_by_to_user', defaultValue: false) required bool isRead,
|
||||||
@JsonKey(name: 'is_author_message_auth_user') required bool? isCurrentUserAuthor,
|
@JsonKey(name: 'is_author_message_auth_user') required bool? isCurrentUserAuthor,
|
||||||
@JsonKey(name: 'room_id') required String? roomId,
|
@JsonKey(name: 'room_id') required String? roomId,
|
||||||
|
@Default(MessageState.sent) @JsonKey(includeToJson: false, includeFromJson: false) MessageState messageState,
|
||||||
|
@Default(null) @JsonKey(includeToJson: false, includeFromJson: false) String? uuid,
|
||||||
}) = _ChatMessageModel;
|
}) = _ChatMessageModel;
|
||||||
|
|
||||||
factory ChatMessageModel.fromJson(Map<String, Object?> json) => _$ChatMessageModelFromJson(json);
|
factory ChatMessageModel.fromJson(Map<String, Object?> json) => _$ChatMessageModelFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum MessageState { loading, sent, error }
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'dart:developer';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:audioplayers/audioplayers.dart';
|
import 'package:audioplayers/audioplayers.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
|
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
|
||||||
import 'package:easy_debounce/easy_debounce.dart';
|
import 'package:easy_debounce/easy_debounce.dart';
|
||||||
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
||||||
@@ -19,6 +20,7 @@ import 'package:eatvio_app/features/my_finance/models/card_model.dart';
|
|||||||
import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
|
import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
|
||||||
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class ChatRoomCubit extends Cubit<ChatRoomState> {
|
class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||||
final ChatRepository chatRepository;
|
final ChatRepository chatRepository;
|
||||||
@@ -31,12 +33,9 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
late int lastPage;
|
late int lastPage;
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
|
var uuid = const Uuid();
|
||||||
|
|
||||||
ChatRoomCubit({
|
ChatRoomCubit({required this.chatRepository, required this.wsManager, required this.cardsRepository}) : super(const ChatRoomState.loading());
|
||||||
required this.chatRepository,
|
|
||||||
required this.wsManager,
|
|
||||||
required this.cardsRepository,
|
|
||||||
}) : super(const ChatRoomState.loading());
|
|
||||||
|
|
||||||
void setTimer() {
|
void setTimer() {
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
@@ -89,10 +88,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initChat(
|
Future<void> initChat(String uuid, {bool sendInitialMessage = false}) async {
|
||||||
String uuid, {
|
|
||||||
bool sendInitialMessage = false,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
final chatRoom = await chatRepository.getChatRoom(uuid);
|
final chatRoom = await chatRepository.getChatRoom(uuid);
|
||||||
ChosenPlanModel? chosenPlan;
|
ChosenPlanModel? chosenPlan;
|
||||||
@@ -171,11 +167,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
List<ChatMessageModel> messagesList = [message, ...(state as ChatRoomStateLoaded).chatMessages];
|
List<ChatMessageModel> messagesList = [message, ...(state as ChatRoomStateLoaded).chatMessages];
|
||||||
chatRepository.emitNewMessageToChats(message);
|
chatRepository.emitNewMessageToChats(message);
|
||||||
emit((state as ChatRoomStateLoaded).copyWith(chatMessages: messagesList, isLoading: isLoading));
|
emit((state as ChatRoomStateLoaded).copyWith(chatMessages: messagesList, isLoading: isLoading));
|
||||||
EasyDebounce.debounce(
|
EasyDebounce.debounce('searchChatRoom', const Duration(milliseconds: 300), () => sl<ChatsCubit>().getChatRooms());
|
||||||
'searchChatRoom',
|
|
||||||
const Duration(milliseconds: 300),
|
|
||||||
() => sl<ChatsCubit>().getChatRooms(),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
typingSubscription = wsManager.clientTypingStream?.listen((event) {
|
typingSubscription = wsManager.clientTypingStream?.listen((event) {
|
||||||
@@ -183,22 +175,14 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
EasyDebounce.debounce(
|
EasyDebounce.debounce(
|
||||||
'client-typing',
|
'client-typing',
|
||||||
const Duration(seconds: 3),
|
const Duration(seconds: 3),
|
||||||
() => emit(
|
() => emit((state as ChatRoomStateLoaded).copyWith(isTyping: false, isLoading: isLoading)),
|
||||||
(state as ChatRoomStateLoaded).copyWith(
|
|
||||||
isTyping: false,
|
|
||||||
isLoading: isLoading,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> sendMessage({
|
Future<bool> sendMessage({String? data, List<File>? files, required String userSlug}) async {
|
||||||
String? data,
|
|
||||||
List<File>? files,
|
|
||||||
required String userSlug,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
|
final messageUuid = uuid.v4();
|
||||||
final compressedFiles = files != null ? await ImageCompressUtility.compressFilesList(List<File>.from(files)) : null;
|
final compressedFiles = files != null ? await ImageCompressUtility.compressFilesList(List<File>.from(files)) : null;
|
||||||
AudioPlayer().play((AssetSource('audio/send_message.wav')));
|
AudioPlayer().play((AssetSource('audio/send_message.wav')));
|
||||||
emit(
|
emit(
|
||||||
@@ -213,16 +197,38 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
isRead: true,
|
isRead: true,
|
||||||
isCurrentUserAuthor: true,
|
isCurrentUserAuthor: true,
|
||||||
roomId: '',
|
roomId: '',
|
||||||
|
messageState: MessageState.loading,
|
||||||
|
uuid: messageUuid,
|
||||||
),
|
),
|
||||||
...(state as ChatRoomStateLoaded).chatMessages,
|
...(state as ChatRoomStateLoaded).chatMessages,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await chatRepository.sendMessage(
|
|
||||||
data: data,
|
try {
|
||||||
files: compressedFiles,
|
await chatRepository.sendMessage(data: data, files: compressedFiles, slug: userSlug);
|
||||||
slug: userSlug,
|
emit(
|
||||||
);
|
(state as ChatRoomStateLoaded).copyWith(
|
||||||
|
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
|
||||||
|
if (elem.uuid == messageUuid) {
|
||||||
|
return elem.copyWith(messageState: MessageState.sent);
|
||||||
|
}
|
||||||
|
return elem;
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
(state as ChatRoomStateLoaded).copyWith(
|
||||||
|
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
|
||||||
|
if (elem.uuid == messageUuid) {
|
||||||
|
return elem.copyWith(messageState: MessageState.error);
|
||||||
|
}
|
||||||
|
return elem;
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
sl<ChatsCubit>().getChatRooms();
|
sl<ChatsCubit>().getChatRooms();
|
||||||
return true;
|
return true;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
@@ -268,7 +274,10 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
try {
|
try {
|
||||||
final planId = (state as ChatRoomStateLoaded).chatRoom.chosenPlan?.id;
|
final planId = (state as ChatRoomStateLoaded).chatRoom.chosenPlan?.id;
|
||||||
if (planId == null) return false;
|
if (planId == null) return false;
|
||||||
await chatRepository.complainAboutConsultation(id: planId, body: PlanComplainModel(description: description));
|
await chatRepository.complainAboutConsultation(
|
||||||
|
id: planId,
|
||||||
|
body: PlanComplainModel(description: description),
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
@@ -296,4 +305,70 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
typingSubscription?.cancel();
|
typingSubscription?.cancel();
|
||||||
super.close();
|
super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void deleteMessage(String uuid) {
|
||||||
|
final currentState = state;
|
||||||
|
if (currentState is ChatRoomStateLoaded) {
|
||||||
|
emit(currentState.copyWith(chatMessages: currentState.chatMessages.where((message) => message.uuid != uuid).toList()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> resendMessage({required String uuid, required String userSlug}) async {
|
||||||
|
final currentState = state;
|
||||||
|
if (currentState is ChatRoomStateLoaded) {
|
||||||
|
final message = currentState.chatMessages.firstWhereOrNull((message) => message.uuid == uuid);
|
||||||
|
if (message != null) {
|
||||||
|
try {
|
||||||
|
AudioPlayer().play((AssetSource('audio/send_message.wav')));
|
||||||
|
emit(
|
||||||
|
(state as ChatRoomStateLoaded).copyWith(
|
||||||
|
chatMessages: [
|
||||||
|
ChatMessageModel(
|
||||||
|
id: 1,
|
||||||
|
data: message.data,
|
||||||
|
mediaData: message.mediaData,
|
||||||
|
authorId: 1,
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
isRead: true,
|
||||||
|
isCurrentUserAuthor: true,
|
||||||
|
roomId: '',
|
||||||
|
messageState: MessageState.loading,
|
||||||
|
uuid: uuid,
|
||||||
|
),
|
||||||
|
...(state as ChatRoomStateLoaded).chatMessages.where((message) => message.uuid != uuid),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await chatRepository.sendMessage(data: message.data, files: message.mediaData as List<File>?, slug: userSlug);
|
||||||
|
emit(
|
||||||
|
(state as ChatRoomStateLoaded).copyWith(
|
||||||
|
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
|
||||||
|
if (elem.uuid == uuid) {
|
||||||
|
return elem.copyWith(messageState: MessageState.sent);
|
||||||
|
}
|
||||||
|
return elem;
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
(state as ChatRoomStateLoaded).copyWith(
|
||||||
|
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
|
||||||
|
if (elem.uuid == uuid) {
|
||||||
|
return elem.copyWith(messageState: MessageState.error);
|
||||||
|
}
|
||||||
|
return elem;
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
sl<ChatsCubit>().getChatRooms();
|
||||||
|
} catch (e, st) {
|
||||||
|
log(e.toString(), stackTrace: st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.da
|
|||||||
import 'package:eatvio_app/features/chat/presentation/widgets/about_order_button.dart';
|
import 'package:eatvio_app/features/chat/presentation/widgets/about_order_button.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/widgets/chat_bubble_widget.dart';
|
import 'package:eatvio_app/features/chat/presentation/widgets/chat_bubble_widget.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_state.dart';
|
||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/widgets/notification_banner.dart';
|
import 'package:eatvio_app/features/chat/presentation/widgets/notification_banner.dart';
|
||||||
@@ -93,7 +95,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
if (state is ChatRoomStateLoaded) {
|
if (state is ChatRoomStateLoaded) {
|
||||||
return _buildChat(context, state);
|
return _buildChat(context, state);
|
||||||
} else if (state is ChatRoomStateError) {
|
} else if (state is ChatRoomStateError) {
|
||||||
return Scaffold(appBar: AppBar(), body: DataErrorWidget(onReload: () => chatRoomCubit.initChat(widget.uuid)));
|
return Scaffold(
|
||||||
|
appBar: AppBar(),
|
||||||
|
body: DataErrorWidget(onReload: () => chatRoomCubit.initChat(widget.uuid)),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return const Scaffold(body: Loader());
|
return const Scaffold(body: Loader());
|
||||||
}
|
}
|
||||||
@@ -168,34 +173,19 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(color: Color(0xFFEBF4FF), shape: BoxShape.circle),
|
||||||
color: Color(0xFFEBF4FF),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: Text(
|
child: Text(
|
||||||
state.chatRoom.users.receiver.name.split(' ').first[0] + state.chatRoom.users.receiver.name.split(' ')[1][0],
|
state.chatRoom.users.receiver.name.split(' ').first[0] + state.chatRoom.users.receiver.name.split(' ')[1][0],
|
||||||
style: const TextStyle(
|
style: const TextStyle(color: Color(0xFF7F9CF5), fontWeight: FontWeight.w400, fontSize: 20),
|
||||||
color: Color(0xFF7F9CF5),
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
fontSize: 20,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(state.chatRoom.users.receiver.name, style: CustomTextStyles.titleSmallBold.copyWith(color: PrimaryColors().black)),
|
||||||
state.chatRoom.users.receiver.name,
|
|
||||||
style: CustomTextStyles.titleSmallBold.copyWith(
|
|
||||||
color: PrimaryColors().black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AnimatedCrossFade(
|
AnimatedCrossFade(
|
||||||
firstChild: Text(
|
firstChild: Text('Печатает...', style: CustomTextStyles.bodySmallMulishPrimary),
|
||||||
'Печатает...',
|
|
||||||
style: CustomTextStyles.bodySmallMulishPrimary,
|
|
||||||
),
|
|
||||||
firstCurve: Curves.easeIn,
|
firstCurve: Curves.easeIn,
|
||||||
secondCurve: Curves.easeOut,
|
secondCurve: Curves.easeOut,
|
||||||
secondChild: const SizedBox.shrink(),
|
secondChild: const SizedBox.shrink(),
|
||||||
@@ -251,9 +241,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
if (!result) return;
|
if (!result) return;
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
context.read<ChatsCubit>().reduceUnreadMessagesCount(
|
context.read<ChatsCubit>().reduceUnreadMessagesCount(
|
||||||
chatId: state.chatRoom.id,
|
chatId: state.chatRoom.id,
|
||||||
count: unreadMessagesIdsCopy.length,
|
count: unreadMessagesIdsCopy.length,
|
||||||
);
|
);
|
||||||
unreadMessagesIdsCopy.clear();
|
unreadMessagesIdsCopy.clear();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -264,6 +254,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
text: state.chatMessages[index].data.trim(),
|
text: state.chatMessages[index].data.trim(),
|
||||||
isCurrentUser: isCurrentUserAuthor,
|
isCurrentUser: isCurrentUserAuthor,
|
||||||
mediaData: state.chatMessages[index].mediaData ?? [],
|
mediaData: state.chatMessages[index].mediaData ?? [],
|
||||||
|
messageState: state.chatMessages[index].messageState,
|
||||||
|
messageUuid: state.chatMessages[index].uuid ?? '',
|
||||||
|
userSlug: state.chatRoom.users.receiver.slug,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -290,6 +283,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
text: state.chatMessages[index].data.trim(),
|
text: state.chatMessages[index].data.trim(),
|
||||||
isCurrentUser: isCurrentUserAuthor,
|
isCurrentUser: isCurrentUserAuthor,
|
||||||
mediaData: state.chatMessages[index].mediaData ?? [],
|
mediaData: state.chatMessages[index].mediaData ?? [],
|
||||||
|
messageState: state.chatMessages[index].messageState,
|
||||||
|
messageUuid: state.chatMessages[index].uuid ?? '',
|
||||||
|
userSlug: state.chatRoom.users.receiver.slug,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -361,11 +357,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
final imageIndex = _imageIndexAt(pickedFiles, i);
|
final imageIndex = _imageIndexAt(pickedFiles, i);
|
||||||
|
|
||||||
context.pushTransparentRoute(
|
context.pushTransparentRoute(
|
||||||
PhotoViewScreen(
|
PhotoViewScreen(imageUrls: imageFiles.map((e) => e.path).toList(), isLocal: true, index: imageIndex),
|
||||||
imageUrls: imageFiles.map((e) => e.path).toList(),
|
|
||||||
isLocal: true,
|
|
||||||
index: imageIndex,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Hero(
|
child: Hero(
|
||||||
@@ -373,14 +365,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
transitionOnUserGestures: true,
|
transitionOnUserGestures: true,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: SizedBox(
|
child: SizedBox(height: 104, width: 104, child: Image.file(pickedFiles[i], fit: BoxFit.cover)),
|
||||||
height: 104,
|
|
||||||
width: 104,
|
|
||||||
child: Image.file(
|
|
||||||
pickedFiles[i],
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -397,15 +382,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(color: appTheme.gray50, shape: BoxShape.circle),
|
||||||
color: appTheme.gray50,
|
child: const Icon(size: 20, Icons.close, color: Colors.black),
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
size: 20,
|
|
||||||
Icons.close,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -427,7 +405,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
child: Ink(
|
child: Ink(
|
||||||
height: 40,
|
height: 40,
|
||||||
width: 40,
|
width: 40,
|
||||||
child: Transform.rotate(angle: pi / 4, child: const Icon(Icons.attach_file_rounded, size: 30, color: Color(0xFF858E99))),
|
child: Transform.rotate(
|
||||||
|
angle: pi / 4,
|
||||||
|
child: const Icon(Icons.attach_file_rounded, size: 30, color: Color(0xFF858E99)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
@@ -449,7 +430,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)),
|
hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)),
|
||||||
filled: true,
|
filled: true,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(100), borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1)),
|
borderRadius: BorderRadius.circular(100),
|
||||||
|
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||||
|
),
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(100),
|
borderRadius: BorderRadius.circular(100),
|
||||||
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||||
@@ -465,7 +448,27 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
InkWell(
|
InkWell(
|
||||||
customBorder: const CircleBorder(),
|
customBorder: const CircleBorder(),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
|
// Проверяем, заблокирован ли пользователь
|
||||||
|
final blockedUsersCubit = sl<BlockedUsersCubit>();
|
||||||
|
await blockedUsersCubit.getBlockedUsers();
|
||||||
|
final blockedState = blockedUsersCubit.state;
|
||||||
|
|
||||||
|
if (blockedState is BlockedUsersStateLoaded) {
|
||||||
|
final isBlocked = blockedState.profilesList.any((profile) => profile.slug == state.chatRoom.users.receiver.slug);
|
||||||
|
if (isBlocked) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Вы не можете отправить сообщение заблокированному пользователю'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (textEditingController.text.isNotEmpty || pickedFiles.isNotEmpty) {
|
if (textEditingController.text.isNotEmpty || pickedFiles.isNotEmpty) {
|
||||||
chatRoomCubit.sendMessage(
|
chatRoomCubit.sendMessage(
|
||||||
data: textEditingController.text.isEmpty ? null : textEditingController.text.trim(),
|
data: textEditingController.text.isEmpty ? null : textEditingController.text.trim(),
|
||||||
@@ -525,13 +528,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
const Icon(Icons.attach_file, size: 32, color: Colors.black54),
|
const Icon(Icons.attach_file, size: 32, color: Colors.black54),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
child: Text(
|
child: Text(fileName, textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 12)),
|
||||||
fileName,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: const TextStyle(fontSize: 12),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -579,13 +576,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
// await chatRoomCubit.sendMessage(files: [File(image.path)], userSlug: slug);
|
// await chatRoomCubit.sendMessage(files: [File(image.path)], userSlug: slug);
|
||||||
// }
|
// }
|
||||||
},
|
},
|
||||||
child: const Row(
|
child: const Row(children: [Icon(Icons.photo_camera), SizedBox(width: 8), Text('Фото')]),
|
||||||
children: [
|
|
||||||
Icon(Icons.photo_camera),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text('Фото'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
CupertinoActionSheetAction(
|
CupertinoActionSheetAction(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@@ -600,13 +591,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
// await chatRoomCubit.sendMessage(files: files, userSlug: slug);
|
// await chatRoomCubit.sendMessage(files: files, userSlug: slug);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Row(
|
child: const Row(children: [Icon(Icons.photo_library_rounded), SizedBox(width: 8), Text('Галерея')]),
|
||||||
children: [
|
|
||||||
Icon(Icons.photo_library_rounded),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text('Галерея'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
CupertinoActionSheetAction(
|
CupertinoActionSheetAction(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@@ -643,20 +628,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
// await chatRoomCubit.sendMessage(files: files, userSlug: slug);
|
// await chatRoomCubit.sendMessage(files: files, userSlug: slug);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Row(
|
child: const Row(children: [Icon(Icons.file_present_rounded), SizedBox(width: 8), Text('Файлы')]),
|
||||||
children: [
|
|
||||||
Icon(Icons.file_present_rounded),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text('Файлы'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
cancelButton: CupertinoActionSheetAction(
|
cancelButton: CupertinoActionSheetAction(onPressed: () => Navigator.of(context).pop(), isDestructiveAction: true, child: const Text('Отмена')),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
isDestructiveAction: true,
|
|
||||||
child: const Text('Отмена'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -665,7 +640,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
return Center(
|
return Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(top: 32),
|
padding: const EdgeInsets.only(top: 32),
|
||||||
child: Text(DateFormat.yMMMMd('RU').format(date), style: const TextStyle(height: 1, color: Color(0xFF8393A1), fontWeight: FontWeight.w500)),
|
child: Text(
|
||||||
|
DateFormat.yMMMMd('RU').format(date),
|
||||||
|
style: const TextStyle(height: 1, color: Color(0xFF8393A1), fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import 'package:flutter/cupertino.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_state.dart';
|
||||||
|
|
||||||
class EmptyChatScreen extends StatefulWidget {
|
class EmptyChatScreen extends StatefulWidget {
|
||||||
final Author author;
|
final Author author;
|
||||||
@@ -133,21 +135,45 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
|||||||
InkWell(
|
InkWell(
|
||||||
customBorder: const CircleBorder(),
|
customBorder: const CircleBorder(),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
// Проверяем, заблокирован ли пользователь
|
||||||
|
final blockedUsersCubit = sl<BlockedUsersCubit>();
|
||||||
|
await blockedUsersCubit.getBlockedUsers();
|
||||||
|
final blockedState = blockedUsersCubit.state;
|
||||||
|
|
||||||
|
if (blockedState is BlockedUsersStateLoaded) {
|
||||||
|
final isBlocked = blockedState.profilesList.any((profile) => profile.slug == widget.author.authorSlug);
|
||||||
|
if (isBlocked) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Вы не можете отправить сообщение заблокированному пользователю'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (textEditingController.text.isNotEmpty) {
|
if (textEditingController.text.isNotEmpty) {
|
||||||
await sl<ChatRepository>().sendMessage(
|
await sl<ChatRepository>().sendMessage(
|
||||||
data: textEditingController.text.trim(),
|
data: textEditingController.text.trim(),
|
||||||
slug: widget.author.authorSlug,
|
slug: widget.author.authorSlug,
|
||||||
);
|
);
|
||||||
setState(() {
|
if (mounted) {
|
||||||
textEditingController.clear();
|
setState(() {
|
||||||
});
|
textEditingController.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
|
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
|
||||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
if (context.mounted) {
|
||||||
'chatScreen',
|
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||||
extra: {
|
'chatScreen',
|
||||||
'uuid': chatIdOrError,
|
extra: {
|
||||||
},
|
'uuid': chatIdOrError,
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Ink(
|
child: Ink(
|
||||||
@@ -175,20 +201,44 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
|||||||
CupertinoActionSheetAction(
|
CupertinoActionSheetAction(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
|
// Проверяем, заблокирован ли пользователь
|
||||||
|
final blockedUsersCubit = sl<BlockedUsersCubit>();
|
||||||
|
await blockedUsersCubit.getBlockedUsers();
|
||||||
|
final blockedState = blockedUsersCubit.state;
|
||||||
|
|
||||||
|
if (blockedState is BlockedUsersStateLoaded) {
|
||||||
|
final isBlocked = blockedState.profilesList.any((profile) => profile.slug == widget.author.authorSlug);
|
||||||
|
if (isBlocked) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Вы не можете отправить сообщение заблокированному пользователю'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final picker = ImagePicker();
|
final picker = ImagePicker();
|
||||||
XFile? image = await picker.pickImage(source: ImageSource.camera);
|
List<XFile>? images = await picker.pickMultiImage();
|
||||||
if (image != null) {
|
if (images.isNotEmpty) {
|
||||||
|
List<File> files = images.map((image) => File(image.path)).toList();
|
||||||
await sl<ChatRepository>().sendMessage(
|
await sl<ChatRepository>().sendMessage(
|
||||||
files: [File(image.path)],
|
files: files,
|
||||||
slug: widget.author.authorSlug,
|
slug: widget.author.authorSlug,
|
||||||
);
|
);
|
||||||
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
|
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
|
||||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
if (mounted) {
|
||||||
'chatScreen',
|
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||||
extra: {
|
'chatScreen',
|
||||||
'uuid': chatIdOrError,
|
extra: {
|
||||||
},
|
'uuid': chatIdOrError,
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Row(
|
child: const Row(
|
||||||
@@ -211,12 +261,14 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
|||||||
slug: widget.author.authorSlug,
|
slug: widget.author.authorSlug,
|
||||||
);
|
);
|
||||||
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
|
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
|
||||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
if (mounted) {
|
||||||
'chatScreen',
|
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||||
extra: {
|
'chatScreen',
|
||||||
'uuid': chatIdOrError,
|
extra: {
|
||||||
},
|
'uuid': chatIdOrError,
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Row(
|
child: const Row(
|
||||||
@@ -261,12 +313,14 @@ class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
|||||||
slug: widget.author.authorSlug,
|
slug: widget.author.authorSlug,
|
||||||
);
|
);
|
||||||
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
|
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
|
||||||
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
if (mounted) {
|
||||||
'chatScreen',
|
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||||
extra: {
|
'chatScreen',
|
||||||
'uuid': chatIdOrError,
|
extra: {
|
||||||
},
|
'uuid': chatIdOrError,
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Row(
|
child: const Row(
|
||||||
|
|||||||
@@ -5,8 +5,13 @@ import 'package:dismissible_page/dismissible_page.dart';
|
|||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||||
|
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
||||||
|
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
||||||
|
import 'package:eatvio_app/features/chat/presentation/widgets/message_action_bottom_sheet.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class ChatBubbleWidget extends StatelessWidget {
|
class ChatBubbleWidget extends StatelessWidget {
|
||||||
@@ -17,6 +22,9 @@ class ChatBubbleWidget extends StatelessWidget {
|
|||||||
required this.isCurrentUser,
|
required this.isCurrentUser,
|
||||||
required this.mediaData,
|
required this.mediaData,
|
||||||
required this.isRead,
|
required this.isRead,
|
||||||
|
required this.messageState,
|
||||||
|
required this.messageUuid,
|
||||||
|
required this.userSlug
|
||||||
});
|
});
|
||||||
|
|
||||||
final DateTime sendTime;
|
final DateTime sendTime;
|
||||||
@@ -24,7 +32,9 @@ class ChatBubbleWidget extends StatelessWidget {
|
|||||||
final bool isCurrentUser;
|
final bool isCurrentUser;
|
||||||
final bool isRead;
|
final bool isRead;
|
||||||
final List<dynamic> mediaData;
|
final List<dynamic> mediaData;
|
||||||
|
final MessageState messageState;
|
||||||
|
final String messageUuid;
|
||||||
|
final String userSlug;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final size = MediaQuery.sizeOf(context);
|
final size = MediaQuery.sizeOf(context);
|
||||||
@@ -38,17 +48,33 @@ class ChatBubbleWidget extends StatelessWidget {
|
|||||||
mainAxisAlignment: isCurrentUser ? MainAxisAlignment.end : MainAxisAlignment.start,
|
mainAxisAlignment: isCurrentUser ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (isCurrentUser)
|
if (isCurrentUser)
|
||||||
Padding(
|
switch (messageState) {
|
||||||
padding: const EdgeInsets.only(right: 4, bottom: 4),
|
MessageState.loading => Padding(
|
||||||
child: Text(
|
padding: const EdgeInsets.only(right: 4, bottom: 4),
|
||||||
sendTime.format('HH:mm', 'ru'),
|
child: SizedBox(width: 10, height: 10, child: CircularProgressIndicator(strokeWidth: 2, color: appTheme.blueA400)),
|
||||||
style: const TextStyle(
|
|
||||||
color: Color(0xFF7B8794),
|
|
||||||
height: 1,
|
|
||||||
fontSize: 10,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
MessageState.sent => Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 4, bottom: 4),
|
||||||
|
child: Text(sendTime.format('HH:mm', 'ru'), style: const TextStyle(color: Color(0xFF7B8794), height: 1, fontSize: 10)),
|
||||||
|
),
|
||||||
|
|
||||||
|
MessageState.error => GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
showMessageActionsBottomSheet(
|
||||||
|
context,
|
||||||
|
resend: () {
|
||||||
|
context.read<ChatRoomCubit>().resendMessage(uuid: messageUuid, userSlug: userSlug);
|
||||||
|
},
|
||||||
|
delete: () {
|
||||||
|
context.read<ChatRoomCubit>().deleteMessage(messageUuid);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Padding(padding: const EdgeInsets.only(right: 4, bottom: 4), child: SvgPicture.asset('assets/icons/error_icon.svg')),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
Container(
|
Container(
|
||||||
constraints: BoxConstraints(maxWidth: size.width * 0.7),
|
constraints: BoxConstraints(maxWidth: size.width * 0.7),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -87,33 +113,19 @@ class ChatBubbleWidget extends StatelessWidget {
|
|||||||
throw Exception('Could not launch ${mediaData[index].toString()}');
|
throw Exception('Could not launch ${mediaData[index].toString()}');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
leading: const Icon(
|
leading: const Icon(Icons.file_copy_outlined, color: Colors.black),
|
||||||
Icons.file_copy_outlined,
|
trailing: const Icon(Icons.download_outlined, color: Colors.black),
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
trailing: const Icon(
|
|
||||||
Icons.download_outlined,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
title: Text(
|
title: Text(
|
||||||
mediaData[index].toString().replaceAll('chat_files/', ''),
|
mediaData[index].toString().replaceAll('chat_files/', ''),
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
style: const TextStyle(
|
style: const TextStyle(color: Colors.black, fontSize: 12, overflow: TextOverflow.ellipsis),
|
||||||
color: Colors.black,
|
|
||||||
fontSize: 12,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushTransparentRoute(
|
context.pushTransparentRoute(
|
||||||
PhotoViewScreen(
|
PhotoViewScreen(imageUrls: mediaData, index: index, isLocal: mediaData[index] is File ? true : false),
|
||||||
imageUrls: mediaData,
|
|
||||||
index: index,
|
|
||||||
isLocal: mediaData[index] is File ? true : false,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Hero(
|
child: Hero(
|
||||||
@@ -121,11 +133,7 @@ class ChatBubbleWidget extends StatelessWidget {
|
|||||||
transitionOnUserGestures: true,
|
transitionOnUserGestures: true,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: mediaData[index] is File
|
child: mediaData[index] is File ? Image.file(mediaData[index]) : CachedNetworkImage(imageUrl: '${mediaData[index]}'),
|
||||||
? Image.file(mediaData[index])
|
|
||||||
: CachedNetworkImage(
|
|
||||||
imageUrl: '${mediaData[index]}',
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -137,11 +145,7 @@ class ChatBubbleWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (mediaData.isNotEmpty && text.isNotEmpty) const SizedBox(height: 8),
|
if (mediaData.isNotEmpty && text.isNotEmpty) const SizedBox(height: 8),
|
||||||
if (text.isNotEmpty)
|
if (text.isNotEmpty) Text(text, style: TextStyles.textFS13FW400.copyWith(color: appTheme.black, fontSize: 16)),
|
||||||
Text(
|
|
||||||
text,
|
|
||||||
style: TextStyles.textFS13FW400.copyWith(color: appTheme.black, fontSize: 16),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -151,12 +155,7 @@ class ChatBubbleWidget extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.only(left: 4, bottom: 4),
|
padding: const EdgeInsets.only(left: 4, bottom: 4),
|
||||||
child: Text(
|
child: Text(
|
||||||
sendTime.format('HH:mm', 'ru'),
|
sendTime.format('HH:mm', 'ru'),
|
||||||
style: const TextStyle(
|
style: const TextStyle(color: Color(0xFF7B8794), height: 1, fontSize: 10, fontWeight: FontWeight.w500),
|
||||||
color: Color(0xFF7B8794),
|
|
||||||
height: 1,
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
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/generated/l10n.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
void showMessageActionsBottomSheet(BuildContext context, {required void Function() resend, required void Function() delete}) {
|
||||||
|
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: MessageActionsBottomSheet(resend: resend, delete: delete),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageActionsBottomSheet extends StatefulWidget {
|
||||||
|
const MessageActionsBottomSheet({super.key, required this.resend, required this.delete});
|
||||||
|
final void Function() resend;
|
||||||
|
final void Function() delete;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MessageActionsBottomSheet> createState() => _MessageActionsBottomSheet();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MessageActionsBottomSheet extends State<MessageActionsBottomSheet> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final s = S.of(context);
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 7),
|
||||||
|
const BottomSheetHandlerBar(),
|
||||||
|
BottomSheetActionWidget(text: s.resend, iconPath: 'assets/icons/resend.svg', onTap: widget.resend),
|
||||||
|
const CustomDivider(),
|
||||||
|
BottomSheetActionWidget(text: s.delete, iconPath: 'assets/icons/trash_can.svg', onTap: widget.delete, color: appTheme.red),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,81 +23,105 @@ class _RegistrationAsScreenState extends State<RegistrationAsScreen> {
|
|||||||
children: [
|
children: [
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Text(
|
child: Text('Регистрация в качестве', style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700, height: 1.1)),
|
||||||
'Регистрация в качестве',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
height: 1.1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
RadioListTile<RegistrationAsType>(
|
Column(
|
||||||
value: RegistrationAsType.physical,
|
children: [
|
||||||
groupValue: registrationAsType,
|
Row(
|
||||||
title: const Text(
|
children: [
|
||||||
'Физического лица',
|
Radio<RegistrationAsType>(
|
||||||
style: TextStyle(
|
value: RegistrationAsType.physical,
|
||||||
fontSize: 18,
|
// ignore: deprecated_member_use
|
||||||
height: 1,
|
groupValue: registrationAsType,
|
||||||
|
activeColor: appTheme.blueA400,
|
||||||
|
// ignore: deprecated_member_use
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
registrationAsType = value!;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: Text('Физического лица', style: TextStyle(fontSize: 18, height: 1)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
Row(
|
||||||
activeColor: appTheme.blueA400,
|
children: [
|
||||||
onChanged: (value) {},
|
Radio<RegistrationAsType>(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
value: RegistrationAsType.selfBusy,
|
||||||
),
|
// ignore: deprecated_member_use
|
||||||
RadioListTile<RegistrationAsType>(
|
groupValue: registrationAsType,
|
||||||
value: RegistrationAsType.selfBusy,
|
activeColor: appTheme.blueA400,
|
||||||
groupValue: registrationAsType,
|
// ignore: deprecated_member_use
|
||||||
title: const Text(
|
onChanged: (value) {
|
||||||
'Самозанятого',
|
setState(() {
|
||||||
style: TextStyle(
|
registrationAsType = value!;
|
||||||
fontSize: 18,
|
});
|
||||||
height: 1,
|
},
|
||||||
|
),
|
||||||
|
const Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: Text('Самозанятого', style: TextStyle(fontSize: 18, height: 1)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
Row(
|
||||||
onChanged: null,
|
children: [
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
Radio<RegistrationAsType>(
|
||||||
),
|
value: RegistrationAsType.iP,
|
||||||
RadioListTile<RegistrationAsType>(
|
// ignore: deprecated_member_use
|
||||||
value: RegistrationAsType.iP,
|
groupValue: registrationAsType,
|
||||||
groupValue: registrationAsType,
|
activeColor: appTheme.blueA400,
|
||||||
title: const Text(
|
// ignore: deprecated_member_use
|
||||||
'Индивидуального предпринимателя',
|
onChanged: (value) {
|
||||||
style: TextStyle(
|
setState(() {
|
||||||
fontSize: 18,
|
registrationAsType = value!;
|
||||||
height: 1,
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: Text('Индивидуального предпринимателя', style: TextStyle(fontSize: 18, height: 1)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
Row(
|
||||||
onChanged: null,
|
children: [
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
Radio<RegistrationAsType>(
|
||||||
),
|
value: RegistrationAsType.ooo,
|
||||||
RadioListTile<RegistrationAsType>(
|
// ignore: deprecated_member_use
|
||||||
value: RegistrationAsType.ooo,
|
groupValue: registrationAsType,
|
||||||
groupValue: registrationAsType,
|
activeColor: appTheme.blueA400,
|
||||||
title: const Text(
|
// ignore: deprecated_member_use
|
||||||
'Общества с ограниченной ответственностью',
|
onChanged: (value) {
|
||||||
style: TextStyle(
|
setState(() {
|
||||||
fontSize: 18,
|
registrationAsType = value!;
|
||||||
height: 1,
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: Text('Общества с ограниченной ответственностью', style: TextStyle(fontSize: 18, height: 1)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
onChanged: null,
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
vertical: 16,
|
|
||||||
),
|
|
||||||
child: _divider(context),
|
|
||||||
),
|
),
|
||||||
|
Padding(padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: _divider(context)),
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Text(
|
child: Text('* На данный момент регистрация доступна только в качестве физического лица. Остальные варианты скоро появятся'),
|
||||||
'* На данный момент регистрация доступна только в качестве физического лица. Остальные варианты скоро появятся',
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -108,10 +132,7 @@ class _RegistrationAsScreenState extends State<RegistrationAsScreen> {
|
|||||||
return Container(
|
return Container(
|
||||||
height: 0.4,
|
height: 0.4,
|
||||||
width: MediaQuery.of(context).size.width - 32,
|
width: MediaQuery.of(context).size.width - 32,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(color: Colors.black26, borderRadius: BorderRadius.circular(16)),
|
||||||
color: Colors.black26,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,42 @@
|
|||||||
|
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
part 'rating_model.freezed.dart';
|
part 'rating_model.freezed.dart';
|
||||||
part 'rating_model.g.dart';
|
part 'rating_model.g.dart';
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
abstract class RatingModel with _$RatingModel {
|
abstract class RatingPaginationModel with _$RatingPaginationModel {
|
||||||
|
factory RatingPaginationModel({
|
||||||
|
required List<RatingModel> data,
|
||||||
|
}) = _RatingPaginationModel;
|
||||||
|
|
||||||
|
factory RatingPaginationModel.fromJson(Map<String, dynamic> json) => _$RatingPaginationModelFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class RatingModel with _$RatingModel {
|
||||||
factory RatingModel({
|
factory RatingModel({
|
||||||
required DateTime date,
|
@JsonKey(name: 'created_at', fromJson: parseDate) required DateTime date,
|
||||||
required String title,
|
@JsonKey(name: 'log_data') required RatingEnum type,
|
||||||
required int amount,
|
@JsonKey(name: 'log_scores') required int amount,
|
||||||
}) = _RatingModel;
|
}) = _RatingModel;
|
||||||
|
|
||||||
factory RatingModel.fromJson(Map<String, dynamic> json) => _$RatingModelFromJson(json);
|
factory RatingModel.fromJson(Map<String, dynamic> json) => _$RatingModelFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum RatingEnum {
|
||||||
|
@JsonValue('ADDED_RECIPE')
|
||||||
|
addedRecipe,
|
||||||
|
@JsonValue('ADDED_PUBLIC_DIET')
|
||||||
|
addedPublicDiet,
|
||||||
|
@JsonValue('ADDED_BLOG_POST')
|
||||||
|
addedBlogPost,
|
||||||
|
@JsonValue('INVITED_A_NEW_USER')
|
||||||
|
invitedUser,
|
||||||
|
@JsonValue('HAS_MEDICAL_DEGREE')
|
||||||
|
hasMedicalDegree,
|
||||||
|
@JsonValue('CONSULTED')
|
||||||
|
consulted,
|
||||||
|
@JsonValue('PRODUCT_NUTRITINAL_VALUE_APPROVED')
|
||||||
|
addedGlossary;
|
||||||
|
}
|
||||||
|
|||||||
19
lib/features/consultant_rating/models/statistics_model.dart
Normal file
19
lib/features/consultant_rating/models/statistics_model.dart
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'statistics_model.freezed.dart';
|
||||||
|
part 'statistics_model.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class StatisticsModel with _$StatisticsModel {
|
||||||
|
|
||||||
|
factory StatisticsModel({
|
||||||
|
@JsonKey(name: 'count_referal') int? countReferal,
|
||||||
|
@JsonKey(name: 'count_blog_posts') int? blogPostsCount,
|
||||||
|
@JsonKey(name: 'count_recipes') int? recipesCount,
|
||||||
|
@JsonKey(name: 'count_diets') int? dietsCount,
|
||||||
|
@JsonKey(name: 'count_consultants') int? consultationsCount,
|
||||||
|
@JsonKey(name: 'count_products_nutritional_values') int? qwe,
|
||||||
|
}) = _StatisticsModel;
|
||||||
|
|
||||||
|
factory StatisticsModel.fromJson(Map<String, dynamic> json) => _$StatisticsModelFromJson(json);
|
||||||
|
}
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
|
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_state.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/rating_history_widget.dart';
|
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/rating_history_widget.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/rating_widget.dart';
|
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/rating_widget.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/statistic_widget.dart';
|
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/statistic_widget.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class ConsultantRatingScreen extends StatelessWidget {
|
class ConsultantRatingScreen extends StatelessWidget {
|
||||||
static const name = 'consultantRatingScreen';
|
static const name = 'consultantRatingScreen';
|
||||||
@@ -15,7 +19,10 @@ class ConsultantRatingScreen extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const ConsultantRatingPageContent();
|
return BlocProvider<ConsultantRatingCubit>(
|
||||||
|
create: (context) => sl<ConsultantRatingCubit>()..getConsultantStatistics()..getScoreLogs(),
|
||||||
|
child: const ConsultantRatingPageContent(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,52 +32,56 @@ class ConsultantRatingPageContent extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
return Scaffold(
|
return BlocBuilder<ConsultantRatingCubit, ConsultantRatingState>(
|
||||||
backgroundColor: appTheme.gray100,
|
builder: (context, state) {
|
||||||
appBar: AppBar(
|
return Scaffold(
|
||||||
centerTitle: true,
|
backgroundColor: appTheme.gray100,
|
||||||
title: Text(
|
appBar: AppBar(
|
||||||
s.rating_and_statistics,
|
centerTitle: true,
|
||||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
title: Text(
|
||||||
),
|
s.rating_and_statistics,
|
||||||
),
|
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||||
body: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
const RatingWidget(
|
|
||||||
rating: 1058,
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
Padding(
|
body: state.error != null
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
? DataErrorWidget(onReload: () async {
|
||||||
child: CustomOutlinedButton(
|
await context.read<ConsultantRatingCubit>().getConsultantStatistics();
|
||||||
text: s.copy_referral_link,
|
if (!context.mounted) return;
|
||||||
height: 52,
|
await context.read<ConsultantRatingCubit>().getScoreLogs();
|
||||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
})
|
||||||
buttonStyle: ButtonStyle(
|
: SingleChildScrollView(
|
||||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
child: Column(
|
||||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
children: [
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const RatingWidget(
|
||||||
|
rating: 1058,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: CustomOutlinedButton(
|
||||||
|
text: s.copy_referral_link,
|
||||||
|
height: 52,
|
||||||
|
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||||
|
buttonStyle: ButtonStyle(
|
||||||
|
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||||
|
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
StatisticWidget(isLoading: state.isStatisticsLoading, statistics: state.statistics,),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
RatingHistoryWidget(
|
||||||
|
isLoading: state.isRatingLoading,
|
||||||
|
ratingHistory: state.ratings,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onPressed: () {},
|
);
|
||||||
),
|
},
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
const StatisticWidget(),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
RatingHistoryWidget(
|
|
||||||
ratingHistory: [
|
|
||||||
RatingModel(date: DateTime.now(), title: ' djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 201300),
|
|
||||||
RatingModel(date: DateTime.now(), title: 'djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 89323),
|
|
||||||
RatingModel(date: DateTime.now(), title: 'djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 20),
|
|
||||||
RatingModel(date: DateTime.now(), title: 'djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 1212),
|
|
||||||
RatingModel(date: DateTime.now(), title: 'qweqweeq', amount: 323),
|
|
||||||
RatingModel(date: DateTime.now(), title: 'ee', amount: 1),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_state.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/repositories/consultant_rating_repository.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class ConsultantRatingCubit extends Cubit<ConsultantRatingState> {
|
||||||
|
final ConsultantRatingRepository repository;
|
||||||
|
ConsultantRatingCubit(this.repository) : super(ConsultantRatingState());
|
||||||
|
|
||||||
|
Future<void> getConsultantStatistics({String? date}) async {
|
||||||
|
try {
|
||||||
|
emit(state.copyWith(isStatisticsLoading: true));
|
||||||
|
final statistics = await repository.getConsultantStatistics(date: date);
|
||||||
|
emit(state.copyWith(isStatisticsLoading: false, statistics: statistics, error: null));
|
||||||
|
} catch (e, st) {
|
||||||
|
emit(state.copyWith(isStatisticsLoading: true, error: e.toString()));
|
||||||
|
log(e.toString(), stackTrace: st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getScoreLogs({String? date}) async {
|
||||||
|
try {
|
||||||
|
emit(state.copyWith(isRatingLoading: true));
|
||||||
|
final scoreLogs = await repository.getScoreLogs(date: date);
|
||||||
|
emit(state.copyWith(isRatingLoading: false, ratings: scoreLogs, error: null));
|
||||||
|
} catch (e, st) {
|
||||||
|
emit(state.copyWith(isRatingLoading: true, error: e.toString()));
|
||||||
|
log(e.toString(), stackTrace: st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'consultant_rating_state.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ConsultantRatingState with _$ConsultantRatingState {
|
||||||
|
factory ConsultantRatingState({
|
||||||
|
@Default(false) bool isStatisticsLoading,
|
||||||
|
@Default(false) bool isRatingLoading,
|
||||||
|
@Default(null) StatisticsModel? statistics,
|
||||||
|
@Default(null) String? error,
|
||||||
|
@Default(<RatingModel>[]) List<RatingModel> ratings,
|
||||||
|
}) = _ConsultantRatingState;
|
||||||
|
}
|
||||||
@@ -1,42 +1,29 @@
|
|||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||||
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
|
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class PeriodFilterWidget extends StatefulWidget {
|
class PeriodFilterWidget extends StatefulWidget {
|
||||||
const PeriodFilterWidget({super.key});
|
const PeriodFilterWidget({super.key, required this.isStatistic});
|
||||||
|
final bool isStatistic;
|
||||||
@override
|
@override
|
||||||
State<PeriodFilterWidget> createState() => _PeriodFilterWidgetState();
|
State<PeriodFilterWidget> createState() => _PeriodFilterWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PeriodFilterWidgetState extends State<PeriodFilterWidget> {
|
class _PeriodFilterWidgetState extends State<PeriodFilterWidget> {
|
||||||
final List<String> periodDropDownTitles = [];
|
PeriodFilterStatus _currentState = PeriodFilterStatus.all;
|
||||||
int periodDropDownIndex = 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
final s = S.of(context);
|
|
||||||
if (periodDropDownTitles.isEmpty) {
|
|
||||||
periodDropDownTitles.addAll([
|
|
||||||
s.for_whole_period_of_time,
|
|
||||||
s.for_a_year,
|
|
||||||
s.for_a_month,
|
|
||||||
s.for_a_week,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
CustomDropDownButton(
|
CustomDropDownButton<PeriodFilterStatus>(
|
||||||
hint: Text(
|
hint: Text(
|
||||||
s.choose_diet_duration,
|
s.choose_diet_duration,
|
||||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||||
@@ -44,24 +31,59 @@ class _PeriodFilterWidgetState extends State<PeriodFilterWidget> {
|
|||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
periodDropDownIndex = periodDropDownTitles.indexOf(value);
|
_currentState = value;
|
||||||
});
|
});
|
||||||
|
if (widget.isStatistic) {
|
||||||
|
if (value == PeriodFilterStatus.all) {
|
||||||
|
context.read<ConsultantRatingCubit>().getConsultantStatistics();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.read<ConsultantRatingCubit>().getConsultantStatistics(date: value.value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value == PeriodFilterStatus.all) {
|
||||||
|
context.read<ConsultantRatingCubit>().getScoreLogs();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.read<ConsultantRatingCubit>().getScoreLogs(date: value.value);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
items: periodDropDownTitles
|
items: PeriodFilterStatus.values
|
||||||
.mapIndexed(
|
.mapIndexed(
|
||||||
(index, value) => DropdownMenuItem(
|
(index, value) => DropdownMenuItem(
|
||||||
value: periodDropDownTitles[index],
|
value: PeriodFilterStatus.values[index],
|
||||||
child: Text(
|
child: Text(
|
||||||
periodDropDownTitles[index],
|
PeriodFilterStatus.values[index].name,
|
||||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
value: periodDropDownTitles[periodDropDownIndex],
|
value: _currentState,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum PeriodFilterStatus {
|
||||||
|
all,
|
||||||
|
month,
|
||||||
|
week,
|
||||||
|
year;
|
||||||
|
|
||||||
|
String get name => switch (this) {
|
||||||
|
PeriodFilterStatus.all => 'Все периоды',
|
||||||
|
PeriodFilterStatus.month => 'За месяц',
|
||||||
|
PeriodFilterStatus.week => 'За неделю',
|
||||||
|
PeriodFilterStatus.year => 'За год',
|
||||||
|
};
|
||||||
|
|
||||||
|
String? get value => switch (this) {
|
||||||
|
PeriodFilterStatus.all => null,
|
||||||
|
PeriodFilterStatus.month => DateTime.now().subtract(const Duration(days: 30)).format('yyyy-MM-dd'),
|
||||||
|
PeriodFilterStatus.week => DateTime.now().subtract(const Duration(days: 7)).format('yyyy-MM-dd'),
|
||||||
|
PeriodFilterStatus.year => DateTime.now().subtract(const Duration(days: 365)).format('yyyy-MM-dd'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ class RatingHistoryItem extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 80,
|
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: appTheme.gray100,
|
color: appTheme.gray100,
|
||||||
@@ -29,7 +28,7 @@ class RatingHistoryItem extends StatelessWidget {
|
|||||||
date.format('dd.MM.yyyy'),
|
date.format('dd.MM.yyyy'),
|
||||||
style: TextStyles.textXSSemibold.copyWith(color: appTheme.gray),
|
style: TextStyles.textXSSemibold.copyWith(color: appTheme.gray),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
|
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
|
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/period_filter_widget.dart';
|
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/period_filter_widget.dart';
|
||||||
@@ -9,7 +10,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
||||||
class RatingHistoryWidget extends StatelessWidget {
|
class RatingHistoryWidget extends StatelessWidget {
|
||||||
const RatingHistoryWidget({super.key, required this.ratingHistory});
|
const RatingHistoryWidget({super.key, required this.ratingHistory, required this.isLoading});
|
||||||
|
final bool isLoading;
|
||||||
final List<RatingModel> ratingHistory;
|
final List<RatingModel> ratingHistory;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -20,50 +22,60 @@ class RatingHistoryWidget extends StatelessWidget {
|
|||||||
right: 16,
|
right: 16,
|
||||||
top: 16,
|
top: 16,
|
||||||
),
|
),
|
||||||
child: Column(
|
child: isLoading
|
||||||
children: [
|
? const Center(child: Loader())
|
||||||
Row(
|
: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Row(
|
||||||
s.rating_history,
|
children: [
|
||||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
Text(
|
||||||
),
|
s.rating_history,
|
||||||
const Spacer(),
|
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||||
SvgPicture.asset(
|
),
|
||||||
'assets/icons/question_mark.svg',
|
const Spacer(),
|
||||||
height: 22,
|
SvgPicture.asset(
|
||||||
width: 22,
|
'assets/icons/question_mark.svg',
|
||||||
colorFilter: ColorFilter.mode(
|
height: 22,
|
||||||
appTheme.blueA400,
|
width: 22,
|
||||||
BlendMode.srcIn,
|
colorFilter: ColorFilter.mode(
|
||||||
|
appTheme.blueA400,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(
|
||||||
],
|
height: 16,
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
const PeriodFilterWidget(),
|
|
||||||
const SizedBox(
|
|
||||||
height: 12,
|
|
||||||
),
|
|
||||||
ListView.builder(
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return Padding(
|
|
||||||
padding: index == ratingHistory.length - 1 ? EdgeInsets.zero : const EdgeInsets.only(bottom: 12),
|
|
||||||
child: RatingHistoryItem(
|
|
||||||
title: ratingHistory[index].title,
|
|
||||||
amount: ratingHistory[index].amount,
|
|
||||||
date: ratingHistory[index].date,
|
|
||||||
),
|
),
|
||||||
);
|
const PeriodFilterWidget(isStatistic: false),
|
||||||
},
|
const SizedBox(
|
||||||
itemCount: ratingHistory.length,
|
height: 12,
|
||||||
shrinkWrap: true,
|
),
|
||||||
),
|
ListView.builder(
|
||||||
],
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
),
|
itemBuilder: (context, index) {
|
||||||
|
return Padding(
|
||||||
|
padding: index == ratingHistory.length - 1 ? EdgeInsets.zero : const EdgeInsets.only(bottom: 12),
|
||||||
|
child: RatingHistoryItem(
|
||||||
|
title: switch (ratingHistory[index].type) {
|
||||||
|
RatingEnum.addedRecipe => 'Добавление рецепта',
|
||||||
|
RatingEnum.addedPublicDiet => 'Добавление публичной диеты',
|
||||||
|
RatingEnum.addedBlogPost => 'Добавление поста',
|
||||||
|
RatingEnum.invitedUser => 'Регистрация по реф. ссылке',
|
||||||
|
RatingEnum.hasMedicalDegree => 'Получение мед. степени',
|
||||||
|
RatingEnum.consulted => 'Консультация',
|
||||||
|
RatingEnum.addedGlossary => 'Добавление продукта в глоссарий',
|
||||||
|
},
|
||||||
|
amount: ratingHistory[index].amount,
|
||||||
|
date: ratingHistory[index].date,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: ratingHistory.length,
|
||||||
|
shrinkWrap: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
|
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/period_filter_widget.dart';
|
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/period_filter_widget.dart';
|
||||||
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/statistic_bouble.dart';
|
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/statistic_bouble.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
@@ -8,14 +10,15 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
||||||
class StatisticWidget extends StatelessWidget {
|
class StatisticWidget extends StatelessWidget {
|
||||||
const StatisticWidget({super.key});
|
const StatisticWidget({super.key, required this.isLoading, required this.statistics});
|
||||||
|
final bool isLoading;
|
||||||
|
final StatisticsModel? statistics;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
return MainBackgroundBouble(
|
return MainBackgroundBouble(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: isLoading ? const Center(child: Loader()) : Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -38,15 +41,17 @@ class StatisticWidget extends StatelessWidget {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
),
|
),
|
||||||
const PeriodFilterWidget(),
|
const PeriodFilterWidget(
|
||||||
|
isStatistic: true,
|
||||||
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: StatisticBouble(number: 12, title: s.invited_people_count)),
|
Expanded(child: StatisticBouble(number: statistics?.countReferal ?? 0, title: s.invited_people_count)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: StatisticBouble(number: 12, title: s.published_posts_count)),
|
Expanded(child: StatisticBouble(number: statistics?.blogPostsCount ?? 0, title: s.published_posts_count)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
@@ -54,9 +59,9 @@ class StatisticWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: StatisticBouble(number: 12, title: s.published_recipes_count)),
|
Expanded(child: StatisticBouble(number: statistics?.recipesCount ?? 0, title: s.published_recipes_count)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: StatisticBouble(number: 12, title: s.published_diets_count)),
|
Expanded(child: StatisticBouble(number: statistics?.dietsCount ?? 0, title: s.published_diets_count)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
@@ -64,9 +69,9 @@ class StatisticWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: StatisticBouble(number: 12, title: s.consultations_count)),
|
Expanded(child: StatisticBouble(number: statistics?.consultationsCount ?? 0, title: s.consultations_count)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: StatisticBouble(number: 12, title: s.active_consultations_count)),
|
Expanded(child: StatisticBouble(number: statistics?.qwe ?? 0, title: s.active_consultations_count)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/services/consultant_rating_service.dart';
|
||||||
|
|
||||||
|
class ConsultantRatingRepository {
|
||||||
|
final ConsultantRatingService service;
|
||||||
|
|
||||||
|
ConsultantRatingRepository(this.service);
|
||||||
|
|
||||||
|
Future<StatisticsModel> getConsultantStatistics({String? date}) async {
|
||||||
|
try {
|
||||||
|
return await service.getConsultantStatistics(date: date);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<RatingModel>> getScoreLogs({String? date}) async {
|
||||||
|
try {
|
||||||
|
final data = (await service.getScoreLogs(date: date)).data;
|
||||||
|
return data;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart';
|
||||||
|
|
||||||
|
import 'package:retrofit/retrofit.dart';
|
||||||
|
|
||||||
|
part 'consultant_rating_service.g.dart';
|
||||||
|
|
||||||
|
@RestApi()
|
||||||
|
abstract class ConsultantRatingService {
|
||||||
|
factory ConsultantRatingService(Dio dio, {String baseUrl}) = _ConsultantRatingService;
|
||||||
|
|
||||||
|
@GET('/api/consultants/rating/statistics')
|
||||||
|
Future<StatisticsModel> getConsultantStatistics({@Query('filters[duration][\$gte]') String? date});
|
||||||
|
|
||||||
|
@GET('/api/consultants/score/logs')
|
||||||
|
Future<RatingPaginationModel> getScoreLogs({@Query('filters[duration][\$gte]') String? date});
|
||||||
|
}
|
||||||
@@ -26,10 +26,7 @@ class DietPreviewScreen extends StatefulWidget {
|
|||||||
late final List<int> selectedIndexes;
|
late final List<int> selectedIndexes;
|
||||||
static const name = 'createDietScreenPreview';
|
static const name = 'createDietScreenPreview';
|
||||||
static const path = '/create-diet-screen/preview';
|
static const path = '/create-diet-screen/preview';
|
||||||
DietPreviewScreen({
|
DietPreviewScreen({super.key, required this.diet}) {
|
||||||
super.key,
|
|
||||||
required this.diet,
|
|
||||||
}) {
|
|
||||||
selectedIndexes = List.generate(diet.dailySchedule.length, (_) => 0);
|
selectedIndexes = List.generate(diet.dailySchedule.length, (_) => 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,11 +62,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 12) + (Platform.isIOS ? const EdgeInsets.only(bottom: 16) : EdgeInsets.zero),
|
padding: const EdgeInsets.symmetric(vertical: 12) + (Platform.isIOS ? const EdgeInsets.only(bottom: 16) : EdgeInsets.zero),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
border: Border(
|
border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.1))),
|
||||||
top: BorderSide(
|
|
||||||
color: Colors.black.withValues(alpha: 0.1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
@@ -79,34 +72,16 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SizedBox(
|
child: BookmarkButton(
|
||||||
child: FloatingActionButton.extended(
|
isFavorite: widget.diet.isFavorite,
|
||||||
heroTag: 'nextButtonRegistration',
|
onTap: () {},
|
||||||
highlightElevation: 0,
|
width: 52,
|
||||||
elevation: 0,
|
height: 52,
|
||||||
focusElevation: 0,
|
size: 24,
|
||||||
shape: RoundedRectangleBorder(
|
shape: BoxShape.circle,
|
||||||
borderRadius: BorderRadius.circular(16),
|
color: appTheme.gray50,
|
||||||
),
|
|
||||||
backgroundColor: appTheme.greenA700,
|
|
||||||
onPressed: () {},
|
|
||||||
label: Text(
|
|
||||||
s.startDiet,
|
|
||||||
style: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
|
||||||
BookmarkButton(
|
|
||||||
isFavorite: widget.diet.isFavorite,
|
|
||||||
onTap: () {},
|
|
||||||
width: 52,
|
|
||||||
height: 52,
|
|
||||||
size: 24,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
color: appTheme.gray50,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -133,24 +108,15 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
filled: true,
|
filled: true,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
borderSide: const BorderSide(
|
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||||
color: Color(0xFFD1D1D6),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
borderSide: const BorderSide(
|
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||||
color: Color(0xFFD1D1D6),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
borderSide: const BorderSide(
|
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||||
color: Color(0xFFD1D1D6),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -160,17 +126,10 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(color: textEditingController.text.length > 2 ? appTheme.blueA400 : appTheme.gray50, shape: BoxShape.circle),
|
||||||
color: textEditingController.text.length > 2 ? appTheme.blueA400 : appTheme.gray50,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
height: 32,
|
height: 32,
|
||||||
width: 32,
|
width: 32,
|
||||||
child: Icon(
|
child: Icon(Icons.send_rounded, size: 18, color: textEditingController.text.length > 2 ? appTheme.whiteA700 : appTheme.black),
|
||||||
Icons.send_rounded,
|
|
||||||
size: 18,
|
|
||||||
color: textEditingController.text.length > 2 ? appTheme.whiteA700 : appTheme.black,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -212,12 +171,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
Image.memory(
|
Image.memory(imageData, height: 300, width: MediaQuery.sizeOf(context).width, fit: BoxFit.fill),
|
||||||
imageData,
|
|
||||||
height: 300,
|
|
||||||
width: MediaQuery.sizeOf(context).width,
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
),
|
|
||||||
// CachedNetworkImage(
|
// CachedNetworkImage(
|
||||||
// imageUrl: baseUrl + '/storage/' + widget.diet.image,
|
// imageUrl: baseUrl + '/storage/' + widget.diet.image,
|
||||||
// ),
|
// ),
|
||||||
@@ -233,10 +187,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
children: [
|
children: [
|
||||||
SvgPicture.asset('assets/icons/eye.svg', width: 18, height: 12),
|
SvgPicture.asset('assets/icons/eye.svg', width: 18, height: 12),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(widget.diet.totalViews.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)),
|
||||||
widget.diet.totalViews.toString(),
|
|
||||||
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
@@ -252,10 +203,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
colorFilter: widget.diet.wasLikedByMe ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null,
|
colorFilter: widget.diet.wasLikedByMe ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(widget.diet.totalLikes.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)),
|
||||||
widget.diet.totalLikes.toString(),
|
|
||||||
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -265,10 +213,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
children: [
|
children: [
|
||||||
SvgPicture.asset('assets/icons/comment2.svg', width: 13, height: 12),
|
SvgPicture.asset('assets/icons/comment2.svg', width: 13, height: 12),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(widget.diet.totalComments.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)),
|
||||||
widget.diet.totalComments.toString(),
|
|
||||||
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -279,23 +224,15 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Text(
|
child: Text(s.about_diet, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
|
||||||
s.about_diet,
|
|
||||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
InfoRow(diet: widget.diet, type: ProductType.diet,),
|
InfoRow(diet: widget.diet, type: ProductType.diet),
|
||||||
ExpandableText(
|
ExpandableText(text: widget.diet.details),
|
||||||
text: widget.diet.details,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: AuthorCard(
|
child: AuthorCard(author: widget.diet.author, title: s.diet_author),
|
||||||
author: widget.diet.author,
|
|
||||||
title: s.diet_author,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
@@ -308,9 +245,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'${s.diet_plan}(${widget.diet.duration.toString()} ${getDayForm(widget.diet.duration, s)})',
|
'${s.diet_plan}(${widget.diet.duration.toString()} ${getDayForm(widget.diet.duration, s)})',
|
||||||
style: TextStyles.headingXLSemibold.copyWith(
|
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||||
color: appTheme.black,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -320,7 +255,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return _dayOfPlan(widget.diet.dailySchedule[index] ?? {}, index, s);
|
return _dayOfPlan(widget.diet.dailySchedule[index + 1] ?? {}, index, s);
|
||||||
},
|
},
|
||||||
itemCount: widget.diet.dailySchedule.length,
|
itemCount: widget.diet.dailySchedule.length,
|
||||||
),
|
),
|
||||||
@@ -331,12 +266,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Text(
|
child: Text('0 ${getCommentForm(widget.diet.totalComments, s)}', style: TextStyles.textMMedium.copyWith(color: appTheme.gray500)),
|
||||||
'0 ${getCommentForm(widget.diet.totalComments, s)}',
|
|
||||||
style: TextStyles.textMMedium.copyWith(
|
|
||||||
color: appTheme.gray500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
@@ -355,14 +285,10 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
pressElevation: 0,
|
pressElevation: 0,
|
||||||
showCheckmark: false,
|
showCheckmark: false,
|
||||||
side: BorderSide.none,
|
side: BorderSide.none,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
label: Text((index + 1).toString()),
|
label: Text((index + 1).toString()),
|
||||||
labelStyle: TextStyles.textXSSemibold.copyWith(
|
labelStyle: TextStyles.textXSSemibold.copyWith(color: index == widget.selectedIndexes[dayNumber] ? Colors.white : appTheme.gray600),
|
||||||
color: index == widget.selectedIndexes[dayNumber] ? Colors.white : appTheme.gray600,
|
|
||||||
),
|
|
||||||
selected: widget.selectedIndexes[dayNumber] == index,
|
selected: widget.selectedIndexes[dayNumber] == index,
|
||||||
backgroundColor: appTheme.gray100,
|
backgroundColor: appTheme.gray100,
|
||||||
onSelected: (bool selected) {
|
onSelected: (bool selected) {
|
||||||
@@ -376,9 +302,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 8),
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
child: MainBackgroundBouble(
|
child: MainBackgroundBouble(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(top: 16),
|
||||||
top: 16,
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
@@ -386,31 +310,12 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text('${s.day} ${dayNumber + 1}', style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
|
||||||
'${s.day} ${dayNumber + 1}',
|
const Padding(padding: EdgeInsets.symmetric(vertical: 12), child: CustomDivider()),
|
||||||
style: TextStyles.headingXLSemibold.copyWith(
|
Text(s.meal, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
|
||||||
color: appTheme.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 12),
|
|
||||||
child: CustomDivider(),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
s.meal,
|
|
||||||
style: TextStyles.textMMedium.copyWith(
|
|
||||||
color: appTheme.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [choiceChipWidget(0), choiceChipWidget(1), choiceChipWidget(2), choiceChipWidget(3), choiceChipWidget(4)],
|
||||||
choiceChipWidget(0),
|
|
||||||
choiceChipWidget(1),
|
|
||||||
choiceChipWidget(2),
|
|
||||||
choiceChipWidget(3),
|
|
||||||
choiceChipWidget(4),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
ListView.builder(
|
ListView.builder(
|
||||||
@@ -443,20 +348,10 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: MediaQuery.sizeOf(context).width * 0.6,
|
width: MediaQuery.sizeOf(context).width * 0.6,
|
||||||
child: Text(
|
child: Text(daily.name, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
|
||||||
daily.name,
|
|
||||||
style: TextStyles.textMMedium.copyWith(
|
|
||||||
color: appTheme.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Text(
|
Text('${daily.quantity} ${getLocalizedMeasurementUnits(s, daily.portion)}', style: TextStyles.textMSemibold.copyWith(color: appTheme.black)),
|
||||||
'${daily.quantity} ${getLocalizedMeasurementUnits(s, daily.portion)}',
|
|
||||||
style: TextStyles.textMSemibold.copyWith(
|
|
||||||
color: appTheme.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -46,21 +46,14 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16) +
|
padding: const EdgeInsets.all(16) + EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||||
EdgeInsets.only(
|
|
||||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
'Добавление блюда',
|
'Добавление блюда',
|
||||||
style: TextStyle(
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: Color(0xFF323F4B)),
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Color(0xFF323F4B),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Row(
|
Row(
|
||||||
@@ -73,21 +66,17 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
|||||||
autofocus: true,
|
autofocus: true,
|
||||||
hintText: 'Введите название продукта/блюда',
|
hintText: 'Введите название продукта/блюда',
|
||||||
onChanged: (query) {
|
onChanged: (query) {
|
||||||
EasyDebounce.debounce(
|
EasyDebounce.debounce('glossary-search', const Duration(milliseconds: 250), () async {
|
||||||
'glossary-search',
|
selectedGlossary = null;
|
||||||
const Duration(milliseconds: 250),
|
if (query.isEmpty) {
|
||||||
() async {
|
glossariesList = [];
|
||||||
selectedGlossary = null;
|
selectedGlossary = null;
|
||||||
if (query.isEmpty) {
|
|
||||||
glossariesList = [];
|
|
||||||
selectedGlossary = null;
|
|
||||||
setState(() {});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
glossariesList = await widget.createDietCubit.getGlossaryListByQuery(query);
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
return;
|
||||||
);
|
}
|
||||||
|
glossariesList = await widget.createDietCubit.getGlossaryListByQuery(query);
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -100,22 +89,20 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
|||||||
hintText: selectedGlossary == null
|
hintText: selectedGlossary == null
|
||||||
? '100'
|
? '100'
|
||||||
: selectedGlossary!.grInPortion == 0
|
: selectedGlossary!.grInPortion == 0
|
||||||
? '100'
|
? '100'
|
||||||
: '1',
|
: '1',
|
||||||
suffixText: selectedGlossary == null
|
suffixText: selectedGlossary == null
|
||||||
? 'гр'
|
? 'гр'
|
||||||
: selectedGlossary?.grInPortion == 0
|
: selectedGlossary?.grInPortion == 0
|
||||||
? 'гр'
|
? 'гр'
|
||||||
: 'шт',
|
: 'шт',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
glossariesList.isEmpty ? const SizedBox(height: 16) : const SizedBox.shrink(),
|
glossariesList.isEmpty ? const SizedBox(height: 16) : const SizedBox.shrink(),
|
||||||
Container(
|
Container(
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(maxHeight: 256),
|
||||||
maxHeight: 256,
|
|
||||||
),
|
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
@@ -130,28 +117,12 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
FocusManager.instance.primaryFocus?.nextFocus();
|
FocusManager.instance.primaryFocus?.nextFocus();
|
||||||
},
|
},
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||||
borderRadius: BorderRadius.circular(20),
|
title: Text(glossariesList[i].name, style: const TextStyle(height: 1)),
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
glossariesList[i].name,
|
|
||||||
style: const TextStyle(
|
|
||||||
height: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: glossariesList[i].grInPortion == 0
|
subtitle: glossariesList[i].grInPortion == 0
|
||||||
? null
|
? null
|
||||||
: Text(
|
: Text('${glossariesList[i].grInPortion} г', style: TextStyle(height: 1, color: Colors.grey.shade600)),
|
||||||
'${glossariesList[i].grInPortion} г',
|
trailing: Icon(Icons.add_rounded, color: Colors.grey.shade400),
|
||||||
style: TextStyle(
|
|
||||||
height: 1,
|
|
||||||
color: Colors.grey.shade600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
trailing: Icon(
|
|
||||||
Icons.add_rounded,
|
|
||||||
color: Colors.grey.shade400,
|
|
||||||
),
|
|
||||||
contentPadding: const EdgeInsets.only(left: 12),
|
contentPadding: const EdgeInsets.only(left: 12),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -166,25 +137,15 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
|||||||
if (widget.portionIndex != null)
|
if (widget.portionIndex != null)
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
widget.createDietCubit.removePortion(
|
widget.createDietCubit.removePortion(day: widget.dayIndex, meal: widget.mealIndex, portionIndex: widget.portionIndex!);
|
||||||
day: widget.dayIndex,
|
|
||||||
meal: widget.mealIndex,
|
|
||||||
portionIndex: widget.portionIndex!,
|
|
||||||
);
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
child: Ink(
|
child: Ink(
|
||||||
width: 52,
|
width: 52,
|
||||||
height: 52,
|
height: 52,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0xFFF6F6F6)),
|
||||||
borderRadius: BorderRadius.circular(20),
|
child: const Icon(Icons.delete_outline_rounded, color: Colors.red),
|
||||||
color: const Color(0xFFF6F6F6),
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.delete_outline_rounded,
|
|
||||||
color: Colors.red,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.portionIndex != null) const SizedBox(width: 8),
|
if (widget.portionIndex != null) const SizedBox(width: 8),
|
||||||
@@ -194,18 +155,11 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
|||||||
child: Ink(
|
child: Ink(
|
||||||
width: MediaQuery.of(context).size.width - (widget.portionIndex == null ? 32 : 92),
|
width: MediaQuery.of(context).size.width - (widget.portionIndex == null ? 32 : 92),
|
||||||
height: 52,
|
height: 52,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400),
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
color: appTheme.blueA400,
|
|
||||||
),
|
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.portionIndex == null ? 'Добавить' : 'Сохранить',
|
widget.portionIndex == null ? 'Добавить' : 'Сохранить',
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.white),
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -222,8 +176,8 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
|||||||
var measurementUnits = selectedGlossary?.grInPortion == null
|
var measurementUnits = selectedGlossary?.grInPortion == null
|
||||||
? Portion.pcs
|
? Portion.pcs
|
||||||
: selectedGlossary?.grInPortion == 0
|
: selectedGlossary?.grInPortion == 0
|
||||||
? Portion.grams
|
? Portion.grams
|
||||||
: Portion.pcs;
|
: Portion.pcs;
|
||||||
final glossary = selectedGlossary;
|
final glossary = selectedGlossary;
|
||||||
if (glossary != null) {
|
if (glossary != null) {
|
||||||
final [calories, carbohydrates, proteins, fats] = _getCPFC(glossary, measurementUnits);
|
final [calories, carbohydrates, proteins, fats] = _getCPFC(glossary, measurementUnits);
|
||||||
@@ -235,19 +189,11 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
|||||||
portion: measurementUnits,
|
portion: measurementUnits,
|
||||||
);
|
);
|
||||||
if (portionIndex == null) {
|
if (portionIndex == null) {
|
||||||
widget.createDietCubit.addPortion(
|
widget.createDietCubit.addPortion(dayIndex: widget.dayIndex, mealIndex: widget.mealIndex, portion: portion);
|
||||||
dayIndex: widget.dayIndex,
|
|
||||||
mealIndex: widget.mealIndex,
|
|
||||||
portion: portion,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
widget.createDietCubit.updatePortion(
|
widget.createDietCubit.updatePortion(dayIndex: widget.dayIndex, mealIndex: widget.mealIndex, portionIndex: portionIndex, portion: portion);
|
||||||
dayIndex: widget.dayIndex,
|
|
||||||
mealIndex: widget.mealIndex,
|
|
||||||
portionIndex: portionIndex,
|
|
||||||
portion: portion,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
Navigator.of(context).pop();
|
||||||
} else {
|
} else {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
final newGlossary = await showModalBottomSheet<GlossaryModel>(
|
final newGlossary = await showModalBottomSheet<GlossaryModel>(
|
||||||
@@ -255,13 +201,8 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
|||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => Padding(
|
builder: (context) => Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
child: GlossaryModerationRequestModalWidget(productName: productNameController.text, diaryCubit: sl<DiaryCubit>()),
|
||||||
),
|
|
||||||
child: GlossaryModerationRequestModalWidget(
|
|
||||||
productName: productNameController.text,
|
|
||||||
diaryCubit: sl<DiaryCubit>(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (newGlossary != null) {
|
if (newGlossary != null) {
|
||||||
@@ -275,18 +216,9 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
|||||||
portion: measurementUnits,
|
portion: measurementUnits,
|
||||||
);
|
);
|
||||||
if (portionIndex == null) {
|
if (portionIndex == null) {
|
||||||
widget.createDietCubit.addPortion(
|
widget.createDietCubit.addPortion(dayIndex: widget.dayIndex, mealIndex: widget.mealIndex, portion: portion);
|
||||||
dayIndex: widget.dayIndex,
|
|
||||||
mealIndex: widget.mealIndex,
|
|
||||||
portion: portion,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
widget.createDietCubit.updatePortion(
|
widget.createDietCubit.updatePortion(dayIndex: widget.dayIndex, mealIndex: widget.mealIndex, portion: portion, portionIndex: portionIndex);
|
||||||
dayIndex: widget.dayIndex,
|
|
||||||
mealIndex: widget.mealIndex,
|
|
||||||
portion: portion,
|
|
||||||
portionIndex: portionIndex,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,12 +22,7 @@ enum PortionState {
|
|||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
abstract class DiaryDayWrapperModel with _$DiaryDayWrapperModel {
|
abstract class DiaryDayWrapperModel with _$DiaryDayWrapperModel {
|
||||||
const factory DiaryDayWrapperModel({
|
const factory DiaryDayWrapperModel({@JsonKey(name: "diary") required DiaryDayModel diary}) = _DiaryDayWrapperModel;
|
||||||
@JsonKey(
|
|
||||||
name: "diary",
|
|
||||||
)
|
|
||||||
required DiaryDayModel diary,
|
|
||||||
}) = _DiaryDayWrapperModel;
|
|
||||||
|
|
||||||
factory DiaryDayWrapperModel.fromJson(Map<String, dynamic> json) => _$DiaryDayWrapperModelFromJson(json);
|
factory DiaryDayWrapperModel.fromJson(Map<String, dynamic> json) => _$DiaryDayWrapperModelFromJson(json);
|
||||||
}
|
}
|
||||||
@@ -71,21 +66,13 @@ DiaryDietModel? _parseDiet(dynamic json) {
|
|||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
abstract class DiaryDietModel with _$DiaryDietModel {
|
abstract class DiaryDietModel with _$DiaryDietModel {
|
||||||
const factory DiaryDietModel({
|
const factory DiaryDietModel({required int id, required int duration, required String name}) = _DiaryDietModel;
|
||||||
required int id,
|
|
||||||
required int duration,
|
|
||||||
required String name,
|
|
||||||
}) = _DiaryDietModel;
|
|
||||||
|
|
||||||
factory DiaryDietModel.fromJson(Map<String, dynamic> json) => _$DiaryDietModelFromJson(json);
|
factory DiaryDietModel.fromJson(Map<String, dynamic> json) => _$DiaryDietModelFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeOfDiaryModel _parseDayTypeForFutureDay(dynamic json) {
|
TypeOfDiaryModel _parseDayTypeForFutureDay(dynamic json) {
|
||||||
return TypeOfDiaryModel(
|
return TypeOfDiaryModel(hasChosenDiet: json['has_chosen_diet'] as bool, hasChosenPlan: json['has_chosen_plan'] as bool, diaryState: DiaryStateEnum.init);
|
||||||
hasChosenDiet: json['has_chosen_diet'] as bool,
|
|
||||||
hasChosenPlan: json['has_chosen_plan'] as bool,
|
|
||||||
diaryState: DiaryStateEnum.init,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@@ -115,11 +102,30 @@ abstract class DiaryPhotoModel with _$DiaryPhotoModel {
|
|||||||
factory DiaryPhotoModel.fromJson(Map<String, dynamic> json) => _$DiaryPhotoModelFromJson(json);
|
factory DiaryPhotoModel.fromJson(Map<String, dynamic> json) => _$DiaryPhotoModelFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ProductCaloriesInfoModel with _$ProductCaloriesInfoModel {
|
||||||
|
const factory ProductCaloriesInfoModel({
|
||||||
|
@JsonKey(name: "id") required int id,
|
||||||
|
@JsonKey(name: "product_name") required String productName,
|
||||||
|
@JsonKey(name: "energy_on_100gr") required double calories,
|
||||||
|
@JsonKey(name: "proteins_on_100gr") required double proteins,
|
||||||
|
@JsonKey(name: "fats_on_100gr") required double fats,
|
||||||
|
@JsonKey(name: "carbohydrates_on_100gr") required double carbohydrates,
|
||||||
|
@JsonKey(name: "energy_on_portion") required double energyOnPortion,
|
||||||
|
@JsonKey(name: "proteins_on_portion") required double proteinsOnPortion,
|
||||||
|
@JsonKey(name: "fats_on_portion") required double fatsOnPortion,
|
||||||
|
@JsonKey(name: "carbohydrates_on_portion") required double carbohydratesOnPortion,
|
||||||
|
}) = _ProductCaloriesInfoModel;
|
||||||
|
|
||||||
|
factory ProductCaloriesInfoModel.fromJson(Map<String, dynamic> json) => _$ProductCaloriesInfoModelFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
abstract class PortionModel with _$PortionModel {
|
abstract class PortionModel with _$PortionModel {
|
||||||
const factory PortionModel({
|
const factory PortionModel({
|
||||||
@JsonKey(name: "id") required int id,
|
@JsonKey(name: "id") required int id,
|
||||||
@JsonKey(name: "product_nutritional_value_id") int? productNutritionalValueId,
|
@JsonKey(name: "product_nutritional_value_id") int? productNutritionalValueId,
|
||||||
|
ProductCaloriesInfoModel? productCaloriesInfo,
|
||||||
@JsonKey(name: "product_name") required String productName,
|
@JsonKey(name: "product_name") required String productName,
|
||||||
@JsonKey(name: "quantity") required int quantity,
|
@JsonKey(name: "quantity") required int quantity,
|
||||||
@JsonKey(name: "portion") required String portion,
|
@JsonKey(name: "portion") required String portion,
|
||||||
|
|||||||
@@ -14,12 +14,7 @@ class AddProductModal extends StatefulWidget {
|
|||||||
final DiaryStateLoaded state;
|
final DiaryStateLoaded state;
|
||||||
final DiaryCubit diaryCubit;
|
final DiaryCubit diaryCubit;
|
||||||
|
|
||||||
const AddProductModal({
|
const AddProductModal({super.key, this.portion, required this.state, required this.diaryCubit});
|
||||||
super.key,
|
|
||||||
this.portion,
|
|
||||||
required this.state,
|
|
||||||
required this.diaryCubit,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AddProductModal> createState() => _AddProductModalState();
|
State<AddProductModal> createState() => _AddProductModalState();
|
||||||
@@ -58,75 +53,62 @@ class _AddProductModalState extends State<AddProductModal> {
|
|||||||
return StatefulBuilder(
|
return StatefulBuilder(
|
||||||
builder: (BuildContext context, void Function(void Function()) setState) {
|
builder: (BuildContext context, void Function(void Function()) setState) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16) +
|
padding: const EdgeInsets.all(16) + EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||||
EdgeInsets.only(
|
|
||||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
'Добавление блюда',
|
'Добавление блюда',
|
||||||
style: TextStyle(
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: Color(0xFF323F4B)),
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Color(0xFF323F4B),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: MediaQuery.of(context).size.width * 0.8 - 32,
|
width: MediaQuery.of(context).size.width * 0.76 - 32,
|
||||||
child: CustomTextFormField(
|
child: CustomTextFormField(
|
||||||
controller: productNameController,
|
controller: productNameController,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
hintText: 'Введите название продукта/блюда',
|
hintText: 'Введите название продукта/блюда',
|
||||||
onChanged: (query) {
|
onChanged: (query) {
|
||||||
EasyDebounce.debounce(
|
EasyDebounce.debounce('glossary-search', const Duration(milliseconds: 250), () async {
|
||||||
'glossary-search',
|
selectedGlossary = null;
|
||||||
const Duration(milliseconds: 250),
|
if (query.isEmpty) {
|
||||||
() async {
|
glossariesList = [];
|
||||||
selectedGlossary = null;
|
|
||||||
if (query.isEmpty) {
|
|
||||||
glossariesList = [];
|
|
||||||
setState(() {});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
glossariesList = await widget.diaryCubit.getGlossaryListByQuery(query);
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
return;
|
||||||
);
|
}
|
||||||
|
glossariesList = await widget.diaryCubit.getGlossaryListByQuery(query);
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: MediaQuery.of(context).size.width * 0.2 - 4,
|
width: MediaQuery.of(context).size.width * 0.24 - 4,
|
||||||
child: CustomTextFormField(
|
child: CustomTextFormField(
|
||||||
controller: quantityController,
|
controller: quantityController,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
hintText: selectedGlossary == null
|
hintText: selectedGlossary == null
|
||||||
? '100'
|
? '100'
|
||||||
: selectedGlossary!.grInPortion == 0
|
: selectedGlossary!.grInPortion == 0
|
||||||
? '100'
|
? '100'
|
||||||
: '1',
|
: '1',
|
||||||
suffixText: selectedGlossary == null
|
suffixText: selectedGlossary == null
|
||||||
? 'гр'
|
? 'гр'
|
||||||
: selectedGlossary?.grInPortion == 0
|
: selectedGlossary?.grInPortion == 0
|
||||||
? 'гр'
|
? 'гр'
|
||||||
: 'шт',
|
: 'шт',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
glossariesList.isEmpty ? const SizedBox(height: 16) : const SizedBox.shrink(),
|
glossariesList.isEmpty ? const SizedBox(height: 16) : const SizedBox.shrink(),
|
||||||
Container(
|
Container(
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(maxHeight: 256),
|
||||||
maxHeight: 256,
|
|
||||||
),
|
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
@@ -141,28 +123,12 @@ class _AddProductModalState extends State<AddProductModal> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
FocusManager.instance.primaryFocus?.nextFocus();
|
FocusManager.instance.primaryFocus?.nextFocus();
|
||||||
},
|
},
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||||
borderRadius: BorderRadius.circular(20),
|
title: Text(glossariesList[i].name, style: const TextStyle(height: 1)),
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
glossariesList[i].name,
|
|
||||||
style: const TextStyle(
|
|
||||||
height: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: glossariesList[i].grInPortion == 0
|
subtitle: glossariesList[i].grInPortion == 0
|
||||||
? null
|
? null
|
||||||
: Text(
|
: Text('${glossariesList[i].grInPortion} г', style: TextStyle(height: 1, color: Colors.grey.shade600)),
|
||||||
'${glossariesList[i].grInPortion} г',
|
trailing: Icon(Icons.add_rounded, color: Colors.grey.shade400),
|
||||||
style: TextStyle(
|
|
||||||
height: 1,
|
|
||||||
color: Colors.grey.shade600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
trailing: Icon(
|
|
||||||
Icons.add_rounded,
|
|
||||||
color: Colors.grey.shade400,
|
|
||||||
),
|
|
||||||
contentPadding: const EdgeInsets.only(left: 12),
|
contentPadding: const EdgeInsets.only(left: 12),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -188,14 +154,8 @@ class _AddProductModalState extends State<AddProductModal> {
|
|||||||
child: Ink(
|
child: Ink(
|
||||||
width: 52,
|
width: 52,
|
||||||
height: 52,
|
height: 52,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: const Color(0xFFF6F6F6)),
|
||||||
borderRadius: BorderRadius.circular(20),
|
child: const Icon(Icons.delete_outline_rounded, color: Colors.red),
|
||||||
color: const Color(0xFFF6F6F6),
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.delete_outline_rounded,
|
|
||||||
color: Colors.red,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.portion != null) const SizedBox(width: 8),
|
if (widget.portion != null) const SizedBox(width: 8),
|
||||||
@@ -209,8 +169,8 @@ class _AddProductModalState extends State<AddProductModal> {
|
|||||||
portion: selectedGlossary == null
|
portion: selectedGlossary == null
|
||||||
? 'gr'
|
? 'gr'
|
||||||
: selectedGlossary!.grInPortion == 0
|
: selectedGlossary!.grInPortion == 0
|
||||||
? 'gr'
|
? 'gr'
|
||||||
: 'pcs',
|
: 'pcs',
|
||||||
quantity: int.parse(quantityController.text),
|
quantity: int.parse(quantityController.text),
|
||||||
productNutritionalValueId: selectedGlossary?.id,
|
productNutritionalValueId: selectedGlossary?.id,
|
||||||
);
|
);
|
||||||
@@ -223,8 +183,8 @@ class _AddProductModalState extends State<AddProductModal> {
|
|||||||
portion: selectedGlossary == null
|
portion: selectedGlossary == null
|
||||||
? 'gr'
|
? 'gr'
|
||||||
: selectedGlossary!.grInPortion == 0
|
: selectedGlossary!.grInPortion == 0
|
||||||
? 'gr'
|
? 'gr'
|
||||||
: 'pcs',
|
: 'pcs',
|
||||||
quantity: int.parse(quantityController.text),
|
quantity: int.parse(quantityController.text),
|
||||||
productNutritionalValueId: selectedGlossary?.id,
|
productNutritionalValueId: selectedGlossary?.id,
|
||||||
portionState: widget.portion!.portionState,
|
portionState: widget.portion!.portionState,
|
||||||
@@ -237,13 +197,8 @@ class _AddProductModalState extends State<AddProductModal> {
|
|||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => Padding(
|
builder: (context) => Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
child: GlossaryModerationRequestModalWidget(productName: productNameController.text, diaryCubit: widget.diaryCubit),
|
||||||
),
|
|
||||||
child: GlossaryModerationRequestModalWidget(
|
|
||||||
productName: productNameController.text,
|
|
||||||
diaryCubit: widget.diaryCubit,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -252,18 +207,11 @@ class _AddProductModalState extends State<AddProductModal> {
|
|||||||
child: Ink(
|
child: Ink(
|
||||||
width: MediaQuery.of(context).size.width - (widget.portion == null ? 32 : 92),
|
width: MediaQuery.of(context).size.width - (widget.portion == null ? 32 : 92),
|
||||||
height: 52,
|
height: 52,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400),
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
color: appTheme.blueA400,
|
|
||||||
),
|
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.portion == null ? 'Добавить' : 'Сохранить',
|
widget.portion == null ? 'Добавить' : 'Сохранить',
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.white),
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.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_cubit.dart';
|
||||||
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
|
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
|
||||||
|
import 'package:eatvio_app/features/diary/presentation/widgets/meal_summary_widget.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class MealButtonWidget extends StatefulWidget {
|
class MealButtonWidget extends StatefulWidget {
|
||||||
final int mealNumber;
|
final int mealNumber;
|
||||||
const MealButtonWidget({
|
const MealButtonWidget({super.key, required this.mealNumber});
|
||||||
super.key,
|
|
||||||
required this.mealNumber,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MealButtonWidget> createState() => _MealButtonWidgetState();
|
State<MealButtonWidget> createState() => _MealButtonWidgetState();
|
||||||
@@ -22,9 +21,7 @@ class _MealButtonWidgetState extends State<MealButtonWidget> {
|
|||||||
return BlocBuilder<DiaryCubit, DiaryState>(
|
return BlocBuilder<DiaryCubit, DiaryState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is DiaryStateLoaded) {
|
if (state is DiaryStateLoaded) {
|
||||||
final consultantComment = state.consultantReview?.consultantComments.mealsComments.firstWhereOrNull(
|
final consultantComment = state.consultantReview?.consultantComments.mealsComments.firstWhereOrNull((element) => element.typeNo == widget.mealNumber);
|
||||||
(element) => element.typeNo == widget.mealNumber,
|
|
||||||
);
|
|
||||||
|
|
||||||
final Color bgColor;
|
final Color bgColor;
|
||||||
if (state.selectedMeal == widget.mealNumber) {
|
if (state.selectedMeal == widget.mealNumber) {
|
||||||
@@ -34,55 +31,84 @@ class _MealButtonWidgetState extends State<MealButtonWidget> {
|
|||||||
} else {
|
} else {
|
||||||
bgColor = const Color(0xFFF1F2F6);
|
bgColor = const Color(0xFFF1F2F6);
|
||||||
}
|
}
|
||||||
return InkWell(
|
|
||||||
onTap: () {
|
final mealIndex = widget.mealNumber - 1;
|
||||||
context.read<DiaryCubit>().setSelectedMeal(widget.mealNumber);
|
final portions = mealIndex < state.diary.meals.length ? state.diary.meals[mealIndex].portions : <PortionModel>[];
|
||||||
},
|
final totals = MealSummaryWidget.calculateMealTotals(portions);
|
||||||
borderRadius: BorderRadius.circular(100),
|
final calories = totals['calories']!.round();
|
||||||
child: Ink(
|
|
||||||
width: MediaQuery.of(context).size.width / 5 - (96 / 5),
|
return Column(
|
||||||
height: 32,
|
mainAxisSize: MainAxisSize.min,
|
||||||
decoration: BoxDecoration(
|
children: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
context.read<DiaryCubit>().setSelectedMeal(widget.mealNumber);
|
||||||
|
},
|
||||||
borderRadius: BorderRadius.circular(100),
|
borderRadius: BorderRadius.circular(100),
|
||||||
color: bgColor,
|
child: Ink(
|
||||||
),
|
width: MediaQuery.of(context).size.width / 5 - (96 / 5),
|
||||||
child: Center(
|
height: 32,
|
||||||
child: Text(
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(100), color: bgColor),
|
||||||
'${widget.mealNumber}',
|
child: Center(
|
||||||
style: TextStyle(
|
child: Text(
|
||||||
color: state.selectedMeal == widget.mealNumber ? Colors.white : const Color(0xFF6D6F71),
|
'${widget.mealNumber}',
|
||||||
fontSize: 16,
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
color: state.selectedMeal == widget.mealNumber ? Colors.white : const Color(0xFF6D6F71),
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
calories > 0 ? '$calories ккал' : '— ккал',
|
||||||
|
style: const TextStyle(color: Color(0xFF6D6F71), fontSize: 12, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (state is DiaryStateLoadedTomorrowWithDiet) {
|
if (state is DiaryStateLoadedTomorrowWithDiet) {
|
||||||
return InkWell(
|
|
||||||
onTap: () {
|
final mealIndex = widget.mealNumber - 1;
|
||||||
context.read<DiaryCubit>().setSelectedMeal(widget.mealNumber);
|
final portions = mealIndex < state.diary.meals.length ? state.diary.meals[mealIndex].portions : <PortionModel>[];
|
||||||
},
|
final totals = MealSummaryWidget.calculateMealTotals(portions);
|
||||||
borderRadius: BorderRadius.circular(100),
|
final calories = totals['calories']!.round();
|
||||||
child: Ink(
|
|
||||||
width: MediaQuery.of(context).size.width / 5 - (96 / 5),
|
return Column(
|
||||||
height: 32,
|
mainAxisSize: MainAxisSize.min,
|
||||||
decoration: BoxDecoration(
|
children: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
context.read<DiaryCubit>().setSelectedMeal(widget.mealNumber);
|
||||||
|
},
|
||||||
borderRadius: BorderRadius.circular(100),
|
borderRadius: BorderRadius.circular(100),
|
||||||
color: state.selectedMeal == widget.mealNumber ? appTheme.blueA400 : const Color(0xFFF1F2F6),
|
child: Ink(
|
||||||
),
|
width: MediaQuery.of(context).size.width / 5 - (96 / 5),
|
||||||
child: Center(
|
height: 32,
|
||||||
child: Text(
|
decoration: BoxDecoration(
|
||||||
'${widget.mealNumber}',
|
borderRadius: BorderRadius.circular(100),
|
||||||
style: TextStyle(
|
color: state.selectedMeal == widget.mealNumber ? appTheme.blueA400 : const Color(0xFFF1F2F6),
|
||||||
color: state.selectedMeal == widget.mealNumber ? Colors.white : const Color(0xFF6D6F71),
|
),
|
||||||
fontSize: 16,
|
child: Center(
|
||||||
fontWeight: FontWeight.w600,
|
child: Text(
|
||||||
|
'${widget.mealNumber}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: state.selectedMeal == widget.mealNumber ? Colors.white : const Color(0xFF6D6F71),
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
calories > 0 ? '$calories ккал' : '— ккал',
|
||||||
|
style: const TextStyle(color: Color(0xFF6D6F71), fontSize: 12, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
|
|||||||
117
lib/features/diary/presentation/widgets/meal_summary_widget.dart
Normal file
117
lib/features/diary/presentation/widgets/meal_summary_widget.dart
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class MealSummaryWidget extends StatelessWidget {
|
||||||
|
final List<PortionModel> portions;
|
||||||
|
final int mealNumber;
|
||||||
|
|
||||||
|
const MealSummaryWidget({super.key, required this.portions, required this.mealNumber});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final totals = _calculateTotals();
|
||||||
|
|
||||||
|
if (totals['calories'] == 0 && totals['proteins'] == 0 && totals['fats'] == 0 && totals['carbohydrates'] == 0) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(top: 8),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: const Color(0xFF2F80ED), width: 2),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Прием $mealNumber',
|
||||||
|
style: const TextStyle(color: Color(0xFF323F4B), fontSize: 16, fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
_buildNutritionChip('К', totals['calories']!.round()),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
_buildNutritionChip('Б', totals['proteins']!.round()),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
_buildNutritionChip('Ж', totals['fats']!.round()),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
_buildNutritionChip('У', totals['carbohydrates']!.round()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNutritionChip(String label, int value) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||||
|
decoration: BoxDecoration(color: const Color(0xFFF6FAFF), borderRadius: BorderRadius.circular(12)),
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: '$label ',
|
||||||
|
style: const TextStyle(color: Color(0xFF2F80ED), fontSize: 12, fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: '$value',
|
||||||
|
style: const TextStyle(color: Color(0xFF6D6F71), fontSize: 12, fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, double> _calculateTotals() {
|
||||||
|
return calculateMealTotals(portions);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, double> calculateMealTotals(List<PortionModel> portions) {
|
||||||
|
double totalCalories = 0;
|
||||||
|
double totalProteins = 0;
|
||||||
|
double totalFats = 0;
|
||||||
|
double totalCarbohydrates = 0;
|
||||||
|
|
||||||
|
for (final portion in portions) {
|
||||||
|
if (portion.portionState == PortionState.eatenDeleted) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Для продуктов из диеты учитываем только съеденные (с галочкой)
|
||||||
|
if (portion.fromDiet && portion.portionState != PortionState.eatenFromDiet) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final caloriesInfo = portion.productCaloriesInfo;
|
||||||
|
if (caloriesInfo == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double calories, proteins, fats, carbohydrates;
|
||||||
|
|
||||||
|
if (portion.portion == 'gr') {
|
||||||
|
calories = (caloriesInfo.calories * portion.quantity) / 100;
|
||||||
|
proteins = (caloriesInfo.proteins * portion.quantity) / 100;
|
||||||
|
fats = (caloriesInfo.fats * portion.quantity) / 100;
|
||||||
|
carbohydrates = (caloriesInfo.carbohydrates * portion.quantity) / 100;
|
||||||
|
} else {
|
||||||
|
calories = caloriesInfo.energyOnPortion;
|
||||||
|
proteins = caloriesInfo.proteinsOnPortion;
|
||||||
|
fats = caloriesInfo.fatsOnPortion;
|
||||||
|
carbohydrates = caloriesInfo.carbohydratesOnPortion;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalCalories += calories;
|
||||||
|
totalProteins += proteins;
|
||||||
|
totalFats += fats;
|
||||||
|
totalCarbohydrates += carbohydrates;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {'calories': totalCalories, 'proteins': totalProteins, 'fats': totalFats, 'carbohydrates': totalCarbohydrates};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,11 +40,7 @@ class _MealWidgetState extends State<MealWidget> {
|
|||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
'Прием пищи',
|
'Прием пищи',
|
||||||
style: TextStyle(
|
style: TextStyle(color: Color(0xFF323F4B), fontSize: 18, fontWeight: FontWeight.w500),
|
||||||
color: Color(0xFF323F4B),
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Row(
|
const Row(
|
||||||
@@ -57,23 +53,17 @@ class _MealWidgetState extends State<MealWidget> {
|
|||||||
MealButtonWidget(mealNumber: 5),
|
MealButtonWidget(mealNumber: 5),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 16),
|
||||||
const TimeOfMealPickerWidget(),
|
const TimeOfMealPickerWidget(),
|
||||||
const ChosenDietWidget(),
|
const ChosenDietWidget(),
|
||||||
const ChosenPlanWidget(),
|
const ChosenPlanWidget(),
|
||||||
const ProductsListWidget(),
|
const ProductsListWidget(),
|
||||||
if (widget.showAddButton) ...[
|
if (widget.showAddButton) ...[const SizedBox(height: 12), const AddProductButtonWidget()],
|
||||||
const SizedBox(height: 12),
|
|
||||||
const AddProductButtonWidget(),
|
|
||||||
],
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
const PhotoGalleryWidget(),
|
const PhotoGalleryWidget(),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
const SatietyRatingWidget(),
|
const SatietyRatingWidget(),
|
||||||
if (widget.showMood) ...[
|
if (widget.showMood) ...[const SizedBox(height: 12), const MoodRatingWidget()],
|
||||||
const SizedBox(height: 12),
|
|
||||||
const MoodRatingWidget(),
|
|
||||||
],
|
|
||||||
BlocBuilder<DiaryCubit, DiaryState>(
|
BlocBuilder<DiaryCubit, DiaryState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is DiaryStateLoaded && state.isConsultantView && !state.isDiaryCommented) {
|
if (state is DiaryStateLoaded && state.isConsultantView && !state.isDiaryCommented) {
|
||||||
@@ -91,10 +81,7 @@ class _MealWidgetState extends State<MealWidget> {
|
|||||||
if (consultantCommentOfMeal != null) {
|
if (consultantCommentOfMeal != null) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text('${s.mealComment} ${state.selectedMeal}', style: TextStyles.textLSemibold.copyWith(color: appTheme.black)),
|
||||||
'${s.mealComment} ${state.selectedMeal}',
|
|
||||||
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
CommentContainer(comment: consultantCommentOfMeal.comment),
|
CommentContainer(comment: consultantCommentOfMeal.comment),
|
||||||
],
|
],
|
||||||
@@ -103,7 +90,7 @@ class _MealWidgetState extends State<MealWidget> {
|
|||||||
}
|
}
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:dotted_border/dotted_border.dart';
|
import 'package:dotted_border/dotted_border.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
@@ -15,10 +16,7 @@ import 'package:flutter_svg/flutter_svg.dart';
|
|||||||
class ProductWidget extends StatefulWidget {
|
class ProductWidget extends StatefulWidget {
|
||||||
final PortionModel portion;
|
final PortionModel portion;
|
||||||
|
|
||||||
const ProductWidget({
|
const ProductWidget({super.key, required this.portion});
|
||||||
super.key,
|
|
||||||
required this.portion,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ProductWidget> createState() => _ProductWidgetState();
|
State<ProductWidget> createState() => _ProductWidgetState();
|
||||||
@@ -37,6 +35,60 @@ class _ProductWidgetState extends State<ProductWidget> {
|
|||||||
isEaten = widget.portion.portionState == PortionState.eatenFromDiet ? true : false;
|
isEaten = widget.portion.portionState == PortionState.eatenFromDiet ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildNutritionalInfo() {
|
||||||
|
if (widget.portion.productNutritionalValueId == null) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12)),
|
||||||
|
child: const Text(
|
||||||
|
'Продукт отправлен на модерацию',
|
||||||
|
style: TextStyle(color: Color(0xFF2F80ED), fontSize: 12, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final caloriesInfo = widget.portion.productCaloriesInfo;
|
||||||
|
if (caloriesInfo == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
double calories, proteins, fats, carbohydrates;
|
||||||
|
|
||||||
|
if (widget.portion.portion == 'gr') {
|
||||||
|
calories = (caloriesInfo.calories * widget.portion.quantity) / 100;
|
||||||
|
proteins = (caloriesInfo.proteins * widget.portion.quantity) / 100;
|
||||||
|
fats = (caloriesInfo.fats * widget.portion.quantity) / 100;
|
||||||
|
carbohydrates = (caloriesInfo.carbohydrates * widget.portion.quantity) / 100;
|
||||||
|
} else {
|
||||||
|
calories = caloriesInfo.energyOnPortion;
|
||||||
|
proteins = caloriesInfo.proteinsOnPortion;
|
||||||
|
fats = caloriesInfo.fatsOnPortion;
|
||||||
|
carbohydrates = caloriesInfo.carbohydratesOnPortion;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
_buildNutritionChip('К', calories.round()),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
_buildNutritionChip('Б', proteins.round()),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
_buildNutritionChip('Ж', fats.round()),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
_buildNutritionChip('У', carbohydrates.round()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNutritionChip(String label, int value) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
|
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
|
||||||
|
child: Text(
|
||||||
|
'$label $value',
|
||||||
|
style: const TextStyle(color: Color(0xFF6D6F71), fontSize: 12, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(ProductWidget oldWidget) {
|
void didUpdateWidget(ProductWidget oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
@@ -48,15 +100,9 @@ class _ProductWidgetState extends State<ProductWidget> {
|
|||||||
return showModalBottomSheet(
|
return showModalBottomSheet(
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
context: context,
|
context: context,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(12))),
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
|
|
||||||
),
|
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (context) => AddProductModal(
|
builder: (context) => AddProductModal(state: state, portion: portion, diaryCubit: diaryCubit),
|
||||||
state: state,
|
|
||||||
portion: portion,
|
|
||||||
diaryCubit: diaryCubit,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,15 +115,21 @@ class _ProductWidgetState extends State<ProductWidget> {
|
|||||||
horizontalTitleGap: 0,
|
horizontalTitleGap: 0,
|
||||||
contentPadding: widget.portion.fromDiet ? const EdgeInsets.only(right: 24, left: 12) : null,
|
contentPadding: widget.portion.fromDiet ? const EdgeInsets.only(right: 24, left: 12) : null,
|
||||||
title: Text(widget.portion.productName),
|
title: Text(widget.portion.productName),
|
||||||
subtitle: Text('${widget.portion.quantity} ${widget.portion.portion == 'gr' ? 'гр' : 'шт'}'),
|
subtitle: Column(
|
||||||
shape: RoundedRectangleBorder(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
borderRadius: BorderRadius.circular(20),
|
children: [
|
||||||
|
Text('${widget.portion.quantity} ${widget.portion.portion == 'gr' ? 'гр' : 'шт'}'),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
_buildNutritionalInfo(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||||
tileColor: const Color(0xFFF6FAFF),
|
tileColor: const Color(0xFFF6FAFF),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (state is DiaryStateLoaded) {
|
if (state is DiaryStateLoaded) {
|
||||||
final isDiaryCommented = state.diary.typeOfDiary.diaryState == DiaryStateEnum.commented ||
|
final isDiaryCommented =
|
||||||
|
state.diary.typeOfDiary.diaryState == DiaryStateEnum.commented ||
|
||||||
state.diary.typeOfDiary.diaryState == DiaryStateEnum.lastDayCommented ||
|
state.diary.typeOfDiary.diaryState == DiaryStateEnum.lastDayCommented ||
|
||||||
state.diary.typeOfDiary.diaryState == DiaryStateEnum.reviewedByUser;
|
state.diary.typeOfDiary.diaryState == DiaryStateEnum.reviewedByUser;
|
||||||
final isDiaryFinished =
|
final isDiaryFinished =
|
||||||
@@ -99,21 +151,20 @@ class _ProductWidgetState extends State<ProductWidget> {
|
|||||||
color: Color(0xFF6D6F71),
|
color: Color(0xFF6D6F71),
|
||||||
),
|
),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
onTap: () => diaryCubit.restore(
|
onTap: () => diaryCubit.restore(diaryId: state.diary.id, mealId: state.selectedMeal, portionId: widget.portion.id),
|
||||||
diaryId: state.diary.id,
|
trailing: SvgPicture.asset('assets/icons/revert_product.svg'),
|
||||||
mealId: state.selectedMeal,
|
|
||||||
portionId: widget.portion.id,
|
|
||||||
),
|
|
||||||
trailing: SvgPicture.asset(
|
|
||||||
'assets/icons/revert_product.svg',
|
|
||||||
),
|
|
||||||
horizontalTitleGap: 0,
|
horizontalTitleGap: 0,
|
||||||
contentPadding: widget.portion.fromDiet ? const EdgeInsets.only(right: 24, left: 4) : null,
|
contentPadding: widget.portion.fromDiet ? const EdgeInsets.only(right: 24, left: 4) : null,
|
||||||
title: Text(widget.portion.productName),
|
title: Text(widget.portion.productName),
|
||||||
subtitle: Text('${widget.portion.quantity} ${widget.portion.portion == 'gr' ? 'гр' : 'шт'}'),
|
subtitle: Column(
|
||||||
shape: RoundedRectangleBorder(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
borderRadius: BorderRadius.circular(20),
|
children: [
|
||||||
|
Text('${widget.portion.quantity} ${widget.portion.portion == 'gr' ? 'гр' : 'шт'}'),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
_buildNutritionalInfo(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||||
// ignore: use_full_hex_values_for_flutter_colors
|
// ignore: use_full_hex_values_for_flutter_colors
|
||||||
tileColor: const Color(0xfff6faff03).withValues(alpha: 0.01),
|
tileColor: const Color(0xfff6faff03).withValues(alpha: 0.01),
|
||||||
),
|
),
|
||||||
@@ -122,12 +173,13 @@ class _ProductWidgetState extends State<ProductWidget> {
|
|||||||
Widget? trailing;
|
Widget? trailing;
|
||||||
if (state.isConsultantView && !isDiaryCommented) {
|
if (state.isConsultantView && !isDiaryCommented) {
|
||||||
trailing = GestureDetector(
|
trailing = GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
showCommentWidget = !showCommentWidget;
|
showCommentWidget = !showCommentWidget;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: SvgPicture.asset('assets/icons/edit.svg'));
|
child: SvgPicture.asset('assets/icons/edit.svg'),
|
||||||
|
);
|
||||||
} else if (!isDiaryCommented && !widget.portion.fromDiet) {
|
} else if (!isDiaryCommented && !widget.portion.fromDiet) {
|
||||||
trailing = const Icon(Icons.more_vert_rounded);
|
trailing = const Icon(Icons.more_vert_rounded);
|
||||||
}
|
}
|
||||||
@@ -147,47 +199,36 @@ class _ProductWidgetState extends State<ProductWidget> {
|
|||||||
isEaten = value!;
|
isEaten = value!;
|
||||||
});
|
});
|
||||||
if (isEaten) {
|
if (isEaten) {
|
||||||
diaryCubit.markEaten(
|
diaryCubit.markEaten(diaryId: state.diary.id, mealId: state.selectedMeal, portionId: widget.portion.id);
|
||||||
diaryId: state.diary.id,
|
|
||||||
mealId: state.selectedMeal,
|
|
||||||
portionId: widget.portion.id,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
diaryCubit.unmarkEaten(
|
diaryCubit.unmarkEaten(diaryId: state.diary.id, mealId: state.selectedMeal, portionId: widget.portion.id);
|
||||||
diaryId: state.diary.id,
|
|
||||||
mealId: state.selectedMeal,
|
|
||||||
portionId: widget.portion.id,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
activeColor: appTheme.blueA400,
|
activeColor: appTheme.blueA400,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||||
borderRadius: BorderRadius.circular(4),
|
side: const BorderSide(color: Color(0xFF809CB9)),
|
||||||
),
|
|
||||||
side: const BorderSide(
|
|
||||||
color: Color(0xFF809CB9),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
horizontalTitleGap: 0,
|
horizontalTitleGap: 0,
|
||||||
contentPadding: widget.portion.fromDiet ? const EdgeInsets.only(right: 24, left: 4) : null,
|
contentPadding: widget.portion.fromDiet ? const EdgeInsets.only(right: 24, left: 4) : null,
|
||||||
title: Text(widget.portion.productName),
|
title: Text(widget.portion.productName),
|
||||||
subtitle: Text('${widget.portion.quantity} ${widget.portion.portion == 'gr' ? 'гр' : 'шт'}'),
|
subtitle: Column(
|
||||||
shape: RoundedRectangleBorder(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
borderRadius: BorderRadius.circular(20),
|
children: [
|
||||||
|
Text('${widget.portion.quantity} ${widget.portion.portion == 'gr' ? 'гр' : 'шт'}'),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
_buildNutritionalInfo(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||||
tileColor: const Color(0xFFF6FAFF),
|
tileColor: const Color(0xFFF6FAFF),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
child: PortionCommentWidget(
|
child: PortionCommentWidget(enabled: showCommentWidget, portionId: widget.portion.id, comment: portionComment?.comment),
|
||||||
enabled: showCommentWidget,
|
),
|
||||||
portionId: widget.portion.id,
|
|
||||||
comment: portionComment?.comment,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.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/bloc/diary_state.dart';
|
||||||
|
import 'package:eatvio_app/features/diary/presentation/widgets/meal_summary_widget.dart';
|
||||||
import 'package:eatvio_app/features/diary/presentation/widgets/product_loader_widget.dart';
|
import 'package:eatvio_app/features/diary/presentation/widgets/product_loader_widget.dart';
|
||||||
import 'package:eatvio_app/features/diary/presentation/widgets/product_widget.dart';
|
import 'package:eatvio_app/features/diary/presentation/widgets/product_widget.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -19,30 +20,30 @@ class _ProductsListWidgetState extends State<ProductsListWidget> {
|
|||||||
builder: (BuildContext context, DiaryState state) {
|
builder: (BuildContext context, DiaryState state) {
|
||||||
if (state is DiaryStateLoaded) {
|
if (state is DiaryStateLoaded) {
|
||||||
int mealIndex = state.selectedMeal - 1;
|
int mealIndex = state.selectedMeal - 1;
|
||||||
|
final portions = state.diary.meals[mealIndex].portions;
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
for (int i = 0; i < state.diary.meals[mealIndex].portions.length; i++) ...[
|
for (int i = 0; i < portions.length; i++) ...[
|
||||||
ProductWidget(portion: state.diary.meals[mealIndex].portions[i]),
|
ProductWidget(portion: portions[i]),
|
||||||
i < state.diary.meals[mealIndex].portions.length - 1 ? const SizedBox(height: 8) : const SizedBox.shrink(),
|
i < portions.length - 1 ? const SizedBox(height: 8) : const SizedBox.shrink(),
|
||||||
],
|
],
|
||||||
if (state.isLoadingAddingProductInMeal)
|
if (portions.isNotEmpty) MealSummaryWidget(portions: portions, mealNumber: state.selectedMeal),
|
||||||
const Padding(
|
if (state.isLoadingAddingProductInMeal) const Padding(padding: EdgeInsets.only(top: 8), child: ProductLoaderWidget()),
|
||||||
padding: EdgeInsets.only(top: 8),
|
|
||||||
child: ProductLoaderWidget(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (state is DiaryStateLoadedTomorrowWithDiet) {
|
if (state is DiaryStateLoadedTomorrowWithDiet) {
|
||||||
int mealIndex = state.selectedMeal - 1;
|
int mealIndex = state.selectedMeal - 1;
|
||||||
|
final portions = state.diary.meals[mealIndex].portions;
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
for (int i = 0; i < state.diary.meals[mealIndex].portions.length; i++) ...[
|
for (int i = 0; i < portions.length; i++) ...[
|
||||||
ProductWidget(portion: state.diary.meals[mealIndex].portions[i]),
|
ProductWidget(portion: portions[i]),
|
||||||
i < state.diary.meals[mealIndex].portions.length - 1 ? const SizedBox(height: 8) : const SizedBox.shrink(),
|
i < portions.length - 1 ? const SizedBox(height: 8) : const SizedBox.shrink(),
|
||||||
],
|
],
|
||||||
|
if (portions.isNotEmpty) MealSummaryWidget(portions: portions, mealNumber: state.selectedMeal),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,55 @@ class DiaryRepository {
|
|||||||
|
|
||||||
Stream<String> get forceFetchStream => _diaryForceFetchController.stream;
|
Stream<String> get forceFetchStream => _diaryForceFetchController.stream;
|
||||||
|
|
||||||
DiaryRepository({
|
DiaryRepository({required this.diaryService});
|
||||||
required this.diaryService,
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<DiaryDayModel> getDiaryByDate(String date) async {
|
Future<DiaryDayModel> getDiaryByDate(String date) async {
|
||||||
try {
|
try {
|
||||||
return (await getDiaryIndexByDate(date)).last;
|
Set<int> ids = {};
|
||||||
|
final DiaryDayModel diaryDay = (await getDiaryIndexByDate(date)).last;
|
||||||
|
for (var meal in diaryDay.meals) {
|
||||||
|
for (var portion in meal.portions) {
|
||||||
|
if (portion.productNutritionalValueId != null) {
|
||||||
|
ids.add(portion.productNutritionalValueId!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ids.isNotEmpty) {
|
||||||
|
final glossary = await getGlossariesIndex(ids: ids.toList());
|
||||||
|
final List<MealModel> updatedMeals = List.from(diaryDay.meals);
|
||||||
|
for (var mealIndex = 0; mealIndex < updatedMeals.length; mealIndex++) {
|
||||||
|
final meal = updatedMeals[mealIndex];
|
||||||
|
final updatedPortions = meal.portions.map((portion) {
|
||||||
|
if (portion.productNutritionalValueId != null) {
|
||||||
|
try {
|
||||||
|
final glossaryItem = glossary.glossariesList.firstWhere((g) => g.id == portion.productNutritionalValueId);
|
||||||
|
return portion.copyWith(
|
||||||
|
productCaloriesInfo: ProductCaloriesInfoModel(
|
||||||
|
id: glossaryItem.id,
|
||||||
|
productName: glossaryItem.name,
|
||||||
|
calories: glossaryItem.energyOn100Gr,
|
||||||
|
proteins: glossaryItem.proteinsOn100Gr,
|
||||||
|
fats: glossaryItem.fatsOn100Gr,
|
||||||
|
carbohydrates: glossaryItem.carbohydratesOn100Gr,
|
||||||
|
energyOnPortion: glossaryItem.energyOnPortion,
|
||||||
|
proteinsOnPortion: glossaryItem.proteinsOnPortion,
|
||||||
|
fatsOnPortion: glossaryItem.fatsOnPortion,
|
||||||
|
carbohydratesOnPortion: glossaryItem.carbohydratesOnPortion,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
return portion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return portion;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
updatedMeals[mealIndex] = meal.copyWith(portions: updatedPortions);
|
||||||
|
}
|
||||||
|
final updatedDiaryDay = diaryDay.copyWith(meals: updatedMeals);
|
||||||
|
return updatedDiaryDay;
|
||||||
|
}
|
||||||
|
return diaryDay;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
@@ -75,109 +117,47 @@ class DiaryRepository {
|
|||||||
int? productNutritionalValueId,
|
int? productNutritionalValueId,
|
||||||
required PortionState portionState,
|
required PortionState portionState,
|
||||||
}) async {
|
}) async {
|
||||||
Map<String, Object?> data = ProductInMeal(
|
Map<String, Object?> data = ProductInMeal(productName: productName, portion: portion, quantity: quantity).toJson();
|
||||||
productName: productName,
|
|
||||||
portion: portion,
|
|
||||||
quantity: quantity,
|
|
||||||
).toJson();
|
|
||||||
data['product_nutritional_value_id'] = productNutritionalValueId;
|
data['product_nutritional_value_id'] = productNutritionalValueId;
|
||||||
data['state'] = portionState.name;
|
data['state'] = portionState.name;
|
||||||
return await diaryService.updateProductInMeal(
|
return await diaryService.updateProductInMeal(diary: diaryId, typeNo: mealId, portionModel: portionId, productInMeal: data);
|
||||||
diary: diaryId,
|
|
||||||
typeNo: mealId,
|
|
||||||
portionModel: portionId,
|
|
||||||
productInMeal: data,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> markEaten({
|
Future<void> markEaten({required int diaryId, required int mealId, required int portionId}) async {
|
||||||
required int diaryId,
|
return await diaryService.markEaten(diary: diaryId, typeNo: mealId, portionModel: portionId);
|
||||||
required int mealId,
|
|
||||||
required int portionId,
|
|
||||||
}) async {
|
|
||||||
return await diaryService.markEaten(
|
|
||||||
diary: diaryId,
|
|
||||||
typeNo: mealId,
|
|
||||||
portionModel: portionId,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> unmarkEaten({
|
Future<void> unmarkEaten({required int diaryId, required int mealId, required int portionId}) async {
|
||||||
required int diaryId,
|
return await diaryService.unmarkEaten(diary: diaryId, typeNo: mealId, portionModel: portionId);
|
||||||
required int mealId,
|
|
||||||
required int portionId,
|
|
||||||
}) async {
|
|
||||||
return await diaryService.unmarkEaten(
|
|
||||||
diary: diaryId,
|
|
||||||
typeNo: mealId,
|
|
||||||
portionModel: portionId,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> restore({
|
Future<void> restore({required int diaryId, required int mealId, required int portionId}) async {
|
||||||
required int diaryId,
|
return await diaryService.restore(diary: diaryId, typeNo: mealId, portionModel: portionId);
|
||||||
required int mealId,
|
|
||||||
required int portionId,
|
|
||||||
}) async {
|
|
||||||
return await diaryService.restore(
|
|
||||||
diary: diaryId,
|
|
||||||
typeNo: mealId,
|
|
||||||
portionModel: portionId,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteProductFromMeal({
|
Future<void> deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async {
|
||||||
required int diaryId,
|
return await diaryService.deleteProductFromMeal(diary: diaryId, typeNo: mealId, portionModel: portionId);
|
||||||
required int mealId,
|
|
||||||
required int portionId,
|
|
||||||
}) async {
|
|
||||||
return await diaryService.deleteProductFromMeal(
|
|
||||||
diary: diaryId,
|
|
||||||
typeNo: mealId,
|
|
||||||
portionModel: portionId,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> uploadPhotos({
|
Future<void> uploadPhotos({required int diaryId, required int mealId, required List<File> files}) async {
|
||||||
required int diaryId,
|
|
||||||
required int mealId,
|
|
||||||
required List<File> files,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < files.length; i++) {
|
for (int i = 0; i < files.length; i++) {
|
||||||
await diaryService.uploadPhotoOfMeal(
|
await diaryService.uploadPhotoOfMeal(diary: diaryId, typeNo: mealId, photo: files[i]);
|
||||||
diary: diaryId,
|
|
||||||
typeNo: mealId,
|
|
||||||
photo: files[i],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deletePhoto({
|
Future<void> deletePhoto({required int diaryId, required int mealId, required DiaryPhotoModel photo}) async {
|
||||||
required int diaryId,
|
|
||||||
required int mealId,
|
|
||||||
required DiaryPhotoModel photo,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
await diaryService.deletePhotoOfMeal(
|
await diaryService.deletePhotoOfMeal(diaryId: diaryId, typeNo: mealId, userAttachment: photo.id);
|
||||||
diaryId: diaryId,
|
|
||||||
typeNo: mealId,
|
|
||||||
userAttachment: photo.id,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> finishDiaryDay({
|
Future<void> finishDiaryDay({required int diaryId, required String wakeUp, required String retire, String? comment}) async {
|
||||||
required int diaryId,
|
|
||||||
required String wakeUp,
|
|
||||||
required String retire,
|
|
||||||
String? comment,
|
|
||||||
}) async {
|
|
||||||
return await diaryService.finishDiaryDay(
|
return await diaryService.finishDiaryDay(
|
||||||
diary: diaryId,
|
diary: diaryId,
|
||||||
finishDiaryDayModel: FinishDiaryDayModel(wakeUp: wakeUp, retire: retire, comment: comment),
|
finishDiaryDayModel: FinishDiaryDayModel(wakeUp: wakeUp, retire: retire, comment: comment),
|
||||||
@@ -192,87 +172,45 @@ class DiaryRepository {
|
|||||||
required double carbohydrates,
|
required double carbohydrates,
|
||||||
}) async {
|
}) async {
|
||||||
return await diaryService.sendProductToModeration(
|
return await diaryService.sendProductToModeration(
|
||||||
data: ProductModerationModel(
|
data: ProductModerationModel(name: name, energy: energy, proteins: proteins, fats: fats, carbohydrates: carbohydrates).toJson(),
|
||||||
name: name,
|
|
||||||
energy: energy,
|
|
||||||
proteins: proteins,
|
|
||||||
fats: fats,
|
|
||||||
carbohydrates: carbohydrates,
|
|
||||||
).toJson(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fillSatiety({
|
Future<void> fillSatiety({required int diaryId, required int mealId, required int satiety}) async {
|
||||||
required int diaryId,
|
|
||||||
required int mealId,
|
|
||||||
required int satiety,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return await diaryService.fillSatiety(
|
return await diaryService.fillSatiety(diary: diaryId, typeNo: mealId, satiety: satiety);
|
||||||
diary: diaryId,
|
|
||||||
typeNo: mealId,
|
|
||||||
satiety: satiety,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fillMood({
|
Future<void> fillMood({required int diaryId, required int mealId, required int mood}) async {
|
||||||
required int diaryId,
|
|
||||||
required int mealId,
|
|
||||||
required int mood,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return await diaryService.fillMood(
|
return await diaryService.fillMood(diary: diaryId, typeNo: mealId, mood: mood);
|
||||||
diary: diaryId,
|
|
||||||
typeNo: mealId,
|
|
||||||
mood: mood,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fillTime({
|
Future<void> fillTime({required int diaryId, required int mealId, required String time}) async {
|
||||||
required int diaryId,
|
|
||||||
required int mealId,
|
|
||||||
required String time,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return await diaryService.fillTime(
|
return await diaryService.fillTime(diary: diaryId, typeNo: mealId, time: time);
|
||||||
diary: diaryId,
|
|
||||||
typeNo: mealId,
|
|
||||||
time: time,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fillWakeup({
|
Future<void> fillWakeup({required int diaryId, required String time}) async {
|
||||||
required int diaryId,
|
|
||||||
required String time,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return await diaryService.fillWakeup(
|
return await diaryService.fillWakeup(diary: diaryId, wakeUp: time);
|
||||||
diary: diaryId,
|
|
||||||
wakeUp: time,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fillRetire({
|
Future<void> fillRetire({required int diaryId, required String time}) async {
|
||||||
required int diaryId,
|
|
||||||
required String time,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return await diaryService.fillRetire(
|
return await diaryService.fillRetire(diary: diaryId, retire: time);
|
||||||
diary: diaryId,
|
|
||||||
retire: time,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
@@ -287,10 +225,7 @@ class DiaryRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ConsultantReviewModel> getDiaryComments({
|
Future<ConsultantReviewModel> getDiaryComments({required int planId, required int diaryId}) async {
|
||||||
required int planId,
|
|
||||||
required int diaryId,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return await diaryService.getDiaryComments(planId: planId, diaryId: diaryId);
|
return await diaryService.getDiaryComments(planId: planId, diaryId: diaryId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -305,4 +240,18 @@ class DiaryRepository {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<GlossaryPaginationModel> getGlossariesIndex({required List<int> ids}) async {
|
||||||
|
Map<String, dynamic> filters = {};
|
||||||
|
if (ids.isNotEmpty) {
|
||||||
|
for (int i = 0; i < ids.length; i++) {
|
||||||
|
filters['filters[id][\$in][$i]'] = ids[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return await diaryService.getGlossariesIndex(filters: filters);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,32 +15,20 @@ abstract class DiaryService {
|
|||||||
factory DiaryService(Dio dio, {String baseUrl}) = _DiaryService;
|
factory DiaryService(Dio dio, {String baseUrl}) = _DiaryService;
|
||||||
|
|
||||||
@GET('/api/diary/index/{date}')
|
@GET('/api/diary/index/{date}')
|
||||||
Future<List<DiaryDayModel>> getDiaryIndexByDate({
|
Future<List<DiaryDayModel>> getDiaryIndexByDate({@Path('date') required String date});
|
||||||
@Path('date') required String date,
|
|
||||||
});
|
|
||||||
|
|
||||||
@GET('/api/diary/index/{date}')
|
@GET('/api/diary/index/{date}')
|
||||||
Future<List<DiaryFutureDietDayModel>> getDiaryIndexByDateForDietOnly({
|
Future<List<DiaryFutureDietDayModel>> getDiaryIndexByDateForDietOnly({@Path('date') required String date});
|
||||||
@Path('date') required String date,
|
|
||||||
});
|
|
||||||
|
|
||||||
@GET('/api/diary/{diary}/show')
|
@GET('/api/diary/{diary}/show')
|
||||||
Future<DiaryDayWrapperModel> getDiaryDay({
|
Future<DiaryDayWrapperModel> getDiaryDay({@Path('diary') required int diary});
|
||||||
@Path('diary') required int diary,
|
|
||||||
});
|
|
||||||
|
|
||||||
@GET('/api/diary/{diary}/dailyNutritionData')
|
@GET('/api/diary/{diary}/dailyNutritionData')
|
||||||
Future<NutritionDataModel> getNutrition({
|
Future<NutritionDataModel> getNutrition({@Path('diary') required int diary});
|
||||||
@Path('diary') required int diary,
|
|
||||||
});
|
|
||||||
|
|
||||||
@POST('/api/diary/{diary}/meals/{type_no}/photo/upload')
|
@POST('/api/diary/{diary}/meals/{type_no}/photo/upload')
|
||||||
@MultiPart()
|
@MultiPart()
|
||||||
Future<void> uploadPhotoOfMeal({
|
Future<void> uploadPhotoOfMeal({@Path('diary') required int diary, @Path('type_no') required int typeNo, @Part(name: 'photo') required File photo});
|
||||||
@Path('diary') required int diary,
|
|
||||||
@Path('type_no') required int typeNo,
|
|
||||||
@Part(name: 'photo') required File photo,
|
|
||||||
});
|
|
||||||
|
|
||||||
@DELETE('/api/diary/{diary}/meals/{type_no}/photo/{user_attachment}/delete')
|
@DELETE('/api/diary/{diary}/meals/{type_no}/photo/{user_attachment}/delete')
|
||||||
Future<void> deletePhotoOfMeal({
|
Future<void> deletePhotoOfMeal({
|
||||||
@@ -68,25 +56,13 @@ abstract class DiaryService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@POST('/api/diary/{diary}/meals/{type_no}/{portionModel}/mark_eaten')
|
@POST('/api/diary/{diary}/meals/{type_no}/{portionModel}/mark_eaten')
|
||||||
Future<void> markEaten({
|
Future<void> markEaten({@Path('diary') required int diary, @Path('type_no') required int typeNo, @Path('portionModel') required int portionModel});
|
||||||
@Path('diary') required int diary,
|
|
||||||
@Path('type_no') required int typeNo,
|
|
||||||
@Path('portionModel') required int portionModel,
|
|
||||||
});
|
|
||||||
|
|
||||||
@POST('/api/diary/{diary}/meals/{type_no}/{portionModel}/unmark_eaten')
|
@POST('/api/diary/{diary}/meals/{type_no}/{portionModel}/unmark_eaten')
|
||||||
Future<void> unmarkEaten({
|
Future<void> unmarkEaten({@Path('diary') required int diary, @Path('type_no') required int typeNo, @Path('portionModel') required int portionModel});
|
||||||
@Path('diary') required int diary,
|
|
||||||
@Path('type_no') required int typeNo,
|
|
||||||
@Path('portionModel') required int portionModel,
|
|
||||||
});
|
|
||||||
|
|
||||||
@POST('/api/diary/{diary}/meals/{type_no}/portions/{portionModel}/restore')
|
@POST('/api/diary/{diary}/meals/{type_no}/portions/{portionModel}/restore')
|
||||||
Future<void> restore({
|
Future<void> restore({@Path('diary') required int diary, @Path('type_no') required int typeNo, @Path('portionModel') required int portionModel});
|
||||||
@Path('diary') required int diary,
|
|
||||||
@Path('type_no') required int typeNo,
|
|
||||||
@Path('portionModel') required int portionModel,
|
|
||||||
});
|
|
||||||
|
|
||||||
@DELETE('/api/diary/{diary}/meals/{type_no}/portions/{portionModel}/delete')
|
@DELETE('/api/diary/{diary}/meals/{type_no}/portions/{portionModel}/delete')
|
||||||
Future<void> deleteProductFromMeal({
|
Future<void> deleteProductFromMeal({
|
||||||
@@ -96,63 +72,35 @@ abstract class DiaryService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@POST('/api/diary/{diary}/finishDay')
|
@POST('/api/diary/{diary}/finishDay')
|
||||||
Future<void> finishDiaryDay({
|
Future<void> finishDiaryDay({@Path('diary') required int diary, @Body(nullToAbsent: true) required FinishDiaryDayModel finishDiaryDayModel});
|
||||||
@Path('diary') required int diary,
|
|
||||||
@Body(nullToAbsent: true) required FinishDiaryDayModel finishDiaryDayModel,
|
|
||||||
});
|
|
||||||
|
|
||||||
@POST('/api/diary/{diary}/meals/{type_no}/satiety/fill')
|
@POST('/api/diary/{diary}/meals/{type_no}/satiety/fill')
|
||||||
Future<void> fillSatiety({
|
Future<void> fillSatiety({@Path('diary') required int diary, @Path('type_no') required int typeNo, @Field() required int satiety});
|
||||||
@Path('diary') required int diary,
|
|
||||||
@Path('type_no') required int typeNo,
|
|
||||||
@Field() required int satiety,
|
|
||||||
});
|
|
||||||
|
|
||||||
@POST('/api/diary/{diary}/meals/{type_no}/mood/fill')
|
@POST('/api/diary/{diary}/meals/{type_no}/mood/fill')
|
||||||
Future<void> fillMood({
|
Future<void> fillMood({@Path('diary') required int diary, @Path('type_no') required int typeNo, @Field() required int mood});
|
||||||
@Path('diary') required int diary,
|
|
||||||
@Path('type_no') required int typeNo,
|
|
||||||
@Field() required int mood,
|
|
||||||
});
|
|
||||||
|
|
||||||
@POST('/api/diary/{diary}/meals/{type_no}/time/fill')
|
@POST('/api/diary/{diary}/meals/{type_no}/time/fill')
|
||||||
Future<void> fillTime({
|
Future<void> fillTime({@Path('diary') required int diary, @Path('type_no') required int typeNo, @Field() required String time});
|
||||||
@Path('diary') required int diary,
|
|
||||||
@Path('type_no') required int typeNo,
|
|
||||||
@Field() required String time,
|
|
||||||
});
|
|
||||||
|
|
||||||
@POST('/api/diary/{diary}/wakeUp/fill')
|
@POST('/api/diary/{diary}/wakeUp/fill')
|
||||||
Future<void> fillWakeup({
|
Future<void> fillWakeup({@Path('diary') required int diary, @Field() required String wakeUp});
|
||||||
@Path('diary') required int diary,
|
|
||||||
@Field() required String wakeUp,
|
|
||||||
});
|
|
||||||
|
|
||||||
@POST('/api/diary/{diary}/retire/fill')
|
@POST('/api/diary/{diary}/retire/fill')
|
||||||
Future<void> fillRetire({
|
Future<void> fillRetire({@Path('diary') required int diary, @Field() required String retire});
|
||||||
@Path('diary') required int diary,
|
|
||||||
@Field() required String retire,
|
|
||||||
});
|
|
||||||
|
|
||||||
@POST('/api/diary/product_nutritional_values/sendToModeration')
|
@POST('/api/diary/product_nutritional_values/sendToModeration')
|
||||||
Future<GlossaryModerationModel> sendProductToModeration({
|
Future<GlossaryModerationModel> sendProductToModeration({@Body() required Map<String, dynamic> data});
|
||||||
@Body() required Map<String, dynamic> data,
|
|
||||||
});
|
|
||||||
|
|
||||||
@POST('/api/diary/{id}/consultant/comment')
|
@POST('/api/diary/{id}/consultant/comment')
|
||||||
Future<void> commentUserDay({
|
Future<void> commentUserDay({@Path('id') required int id, @Body() required ConsultantCommentModel body});
|
||||||
@Path('id') required int id,
|
|
||||||
@Body() required ConsultantCommentModel body,
|
|
||||||
});
|
|
||||||
|
|
||||||
@GET('/api/chosen_plans/{planId}/{diaryId}/comments/show')
|
@GET('/api/chosen_plans/{planId}/{diaryId}/comments/show')
|
||||||
Future<ConsultantReviewModel> getDiaryComments({
|
Future<ConsultantReviewModel> getDiaryComments({@Path() required int planId, @Path() required int diaryId});
|
||||||
@Path() required int planId,
|
|
||||||
@Path() required int diaryId,
|
|
||||||
});
|
|
||||||
|
|
||||||
@POST('/api/diary/{diaryId}/consultant/comment/view')
|
@POST('/api/diary/{diaryId}/consultant/comment/view')
|
||||||
Future<void> viewCommentOfDiaryDayByConsultant({
|
Future<void> viewCommentOfDiaryDayByConsultant({@Path() required int diaryId});
|
||||||
@Path() required int diaryId,
|
|
||||||
});
|
@GET('/api/glossaries/index')
|
||||||
|
Future<GlossaryPaginationModel> getGlossariesIndex({@Queries() Map<String, dynamic>? filters});
|
||||||
}
|
}
|
||||||
|
|||||||
16
lib/features/feed/models/view_model.dart
Normal file
16
lib/features/feed/models/view_model.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'view_model.freezed.dart';
|
||||||
|
part 'view_model.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ViewModel with _$ViewModel {
|
||||||
|
|
||||||
|
factory ViewModel({
|
||||||
|
required String type,
|
||||||
|
List<int>? ids,
|
||||||
|
List<String>? slugs,
|
||||||
|
}) = _ViewModel;
|
||||||
|
|
||||||
|
factory ViewModel.fromJson(Map<String, dynamic> json) => _$ViewModelFromJson(json);
|
||||||
|
}
|
||||||
@@ -12,70 +12,57 @@ class FeedCubit extends Cubit<FeedState> {
|
|||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
String? _query;
|
String? _query;
|
||||||
|
|
||||||
FeedCubit({
|
FeedCubit({required this.feedRepository}) : super(const FeedState.loading());
|
||||||
required this.feedRepository,
|
|
||||||
}) : super(const FeedState.loading());
|
|
||||||
|
|
||||||
void updateFeedBlogPostList(FeedBlogPostModel? feedBlogPostModel, bool wasLikedByMe) {
|
void updateFeedBlogPostList(FeedBlogPostModel? feedBlogPostModel, bool wasLikedByMe) {
|
||||||
if (feedBlogPostModel == null) return;
|
if (feedBlogPostModel == null || isClosed) return;
|
||||||
List<FeedBlogPostModel> feedBlogPostList = [...(state as FeedStateLoaded).feedBlogPostList];
|
List<FeedBlogPostModel> feedBlogPostList = [...(state as FeedStateLoaded).feedBlogPostList];
|
||||||
final int index = feedBlogPostList.indexOf(feedBlogPostModel);
|
final int index = feedBlogPostList.indexOf(feedBlogPostModel);
|
||||||
feedBlogPostList[index] = feedBlogPostModel.copyWith(
|
feedBlogPostList[index] = feedBlogPostModel.copyWith(
|
||||||
wasLikedByMe: wasLikedByMe,
|
wasLikedByMe: wasLikedByMe,
|
||||||
totalLikes: wasLikedByMe == false ? feedBlogPostList[index].totalLikes - 1 : feedBlogPostList[index].totalLikes + 1,
|
totalLikes: wasLikedByMe == false ? feedBlogPostList[index].totalLikes - 1 : feedBlogPostList[index].totalLikes + 1,
|
||||||
);
|
);
|
||||||
emit(
|
if (!isClosed) {
|
||||||
FeedState.loaded(
|
emit(FeedState.loaded(feedBlogPostList: feedBlogPostList, isLoading: isLoading));
|
||||||
feedBlogPostList: feedBlogPostList,
|
}
|
||||||
isLoading: isLoading,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadFeedBlogPostList({
|
Future<void> loadFeedBlogPostList({String? query}) async {
|
||||||
String? query,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
_query = query;
|
_query = query;
|
||||||
final feedBlogPosts = await feedRepository.getFeedBlogPosts(query: query);
|
final feedBlogPosts = await feedRepository.getFeedBlogPosts(query: query);
|
||||||
lastPage = feedBlogPosts.meta.lastPage;
|
lastPage = feedBlogPosts.meta.lastPage;
|
||||||
emit(
|
if (!isClosed) {
|
||||||
FeedState.loaded(
|
emit(FeedState.loaded(feedBlogPostList: feedBlogPosts.feedBlogPostList, isLoading: false));
|
||||||
feedBlogPostList: feedBlogPosts.feedBlogPostList,
|
}
|
||||||
isLoading: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(FeedState.error(errorMessage: e.toString()));
|
if (!isClosed) {
|
||||||
|
emit(FeedState.error(errorMessage: e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadNextPage() async {
|
Future<void> loadNextPage() async {
|
||||||
if (currentPage < lastPage && !isLoading) {
|
if (currentPage < lastPage && !isLoading && !isClosed) {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
|
|
||||||
emit((state as FeedStateLoaded).copyWith(isLoading: isLoading));
|
if (!isClosed) {
|
||||||
|
emit((state as FeedStateLoaded).copyWith(isLoading: isLoading));
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (currentPage < lastPage) {
|
if (currentPage < lastPage && !isClosed) {
|
||||||
currentPage++;
|
currentPage++;
|
||||||
final feedBlogPosts = await feedRepository.getFeedBlogPosts(
|
final feedBlogPosts = await feedRepository.getFeedBlogPosts(page: currentPage, query: _query);
|
||||||
page: currentPage,
|
|
||||||
query: _query,
|
|
||||||
);
|
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
emit(
|
if (!isClosed) {
|
||||||
FeedState.loaded(
|
emit(FeedState.loaded(feedBlogPostList: [...(state as FeedStateLoaded).feedBlogPostList, ...feedBlogPosts.feedBlogPostList], isLoading: isLoading));
|
||||||
feedBlogPostList: [
|
}
|
||||||
...(state as FeedStateLoaded).feedBlogPostList,
|
|
||||||
...feedBlogPosts.feedBlogPostList,
|
|
||||||
],
|
|
||||||
isLoading: isLoading,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(FeedState.error(errorMessage: e.toString()));
|
if (!isClosed) {
|
||||||
|
emit(FeedState.error(errorMessage: e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,4 +81,5 @@ class FeedCubit extends Cubit<FeedState> {
|
|||||||
Future<void> viewRecipe(String slug) async => await feedRepository.viewRecipe(slug);
|
Future<void> viewRecipe(String slug) async => await feedRepository.viewRecipe(slug);
|
||||||
Future<void> viewDiet(int id) async => await feedRepository.viewDiet(id);
|
Future<void> viewDiet(int id) async => await feedRepository.viewDiet(id);
|
||||||
Future<void> viewPlan(String slug) async => await feedRepository.viewPlan(slug);
|
Future<void> viewPlan(String slug) async => await feedRepository.viewPlan(slug);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:easy_debounce/easy_debounce.dart';
|
import 'package:easy_debounce/easy_debounce.dart';
|
||||||
import 'package:eatvio_app/core/const.dart';
|
import 'package:eatvio_app/core/const.dart';
|
||||||
|
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
|
||||||
import 'package:eatvio_app/core/widgets/add_item_button.dart';
|
import 'package:eatvio_app/core/widgets/add_item_button.dart';
|
||||||
import 'package:eatvio_app/core/widgets/custom_search_bar.dart';
|
import 'package:eatvio_app/core/widgets/custom_search_bar.dart';
|
||||||
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
|
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
|
||||||
@@ -66,12 +67,9 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
|||||||
final dietsScrollController = ScrollController();
|
final dietsScrollController = ScrollController();
|
||||||
final plansScrollController = ScrollController();
|
final plansScrollController = ScrollController();
|
||||||
final consultantsScrollController = ScrollController();
|
final consultantsScrollController = ScrollController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
// Initialize chipsLabelList based on hidePlans setting
|
|
||||||
chipsLabelList = hidePlans ? ['Все', 'Рецепты', 'Диеты', 'Консультанты'] : ['Все', 'Рецепты', 'Диеты', 'Планы питания', 'Консультанты'];
|
chipsLabelList = hidePlans ? ['Все', 'Рецепты', 'Диеты', 'Консультанты'] : ['Все', 'Рецепты', 'Диеты', 'Планы питания', 'Консультанты'];
|
||||||
|
|
||||||
consultantsScrollController.addListener(_onScrollConsultants);
|
consultantsScrollController.addListener(_onScrollConsultants);
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
@@ -80,6 +78,8 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
|||||||
if (consultantsScrollController.offset >= consultantsScrollController.position.maxScrollExtent && !consultantsScrollController.position.outOfRange) {}
|
if (consultantsScrollController.offset >= consultantsScrollController.position.maxScrollExtent && !consultantsScrollController.position.outOfRange) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
blogPostsScrollController.dispose();
|
blogPostsScrollController.dispose();
|
||||||
@@ -90,6 +90,8 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
@@ -228,10 +230,10 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
|||||||
];
|
];
|
||||||
},
|
},
|
||||||
body: switch (_selectedChipIndex) {
|
body: switch (_selectedChipIndex) {
|
||||||
0 => const BlogPosts(),
|
0 => BlogPosts(handleVisibility: context.read<ViewsCubit>().handleVisibility),
|
||||||
1 => const Recipes(),
|
1 => Recipes(handleVisibility: context.read<ViewsCubit>().handleVisibility),
|
||||||
2 => const Diets(),
|
2 => Diets(handleVisibility: context.read<ViewsCubit>().handleVisibility),
|
||||||
3 when !hidePlans => const Plans(),
|
3 when !hidePlans => Plans(handleVisibility: context.read<ViewsCubit>().handleVisibility),
|
||||||
3 when hidePlans => const Consultants(),
|
3 when hidePlans => const Consultants(),
|
||||||
4 when !hidePlans => const Consultants(),
|
4 when !hidePlans => const Consultants(),
|
||||||
_ => Center(child: Text(chipsLabelList[_selectedChipIndex])),
|
_ => Center(child: Text(chipsLabelList[_selectedChipIndex])),
|
||||||
@@ -240,3 +242,20 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ViewableContentType {
|
||||||
|
plans,
|
||||||
|
posts,
|
||||||
|
diets,
|
||||||
|
recipes;
|
||||||
|
|
||||||
|
String getStringifiedVersion() => switch (this) {
|
||||||
|
ViewableContentType.plans => 'plans',
|
||||||
|
|
||||||
|
ViewableContentType.posts => 'blog_posts',
|
||||||
|
|
||||||
|
ViewableContentType.diets => 'diets',
|
||||||
|
|
||||||
|
ViewableContentType.recipes => 'recipes',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -245,9 +245,9 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
FeedIconButton(
|
FeedIconButton(
|
||||||
svgIcon: SvgPicture.asset(
|
svgIcon: SvgPicture.asset(
|
||||||
height: 20,
|
height: 15,
|
||||||
width: 20,
|
width: 12,
|
||||||
'assets/icons/share.svg',
|
'assets/icons/repost.svg',
|
||||||
colorFilter: ColorFilter.mode(appTheme.gray600, BlendMode.srcIn),
|
colorFilter: ColorFilter.mode(appTheme.gray600, BlendMode.srcIn),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
|||||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
|
||||||
|
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/widgets/blog_post_widget.dart';
|
import 'package:eatvio_app/features/feed/presentation/widgets/blog_post_widget.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:visibility_detector/visibility_detector.dart';
|
||||||
|
|
||||||
class BlogPosts extends StatelessWidget {
|
class BlogPosts extends StatelessWidget {
|
||||||
const BlogPosts({super.key});
|
const BlogPosts({super.key, required this.handleVisibility});
|
||||||
|
final Function({required double visibleFraction,required int id, required ViewableContentType type}) handleVisibility;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -29,25 +32,26 @@ class BlogPosts extends StatelessWidget {
|
|||||||
if (index == state.feedBlogPostList.length) {
|
if (index == state.feedBlogPostList.length) {
|
||||||
return const Loader();
|
return const Loader();
|
||||||
}
|
}
|
||||||
return BlogPostWidget(index: index);
|
return VisibilityDetector(
|
||||||
|
onVisibilityChanged: (info) {
|
||||||
|
final id = state.feedBlogPostList[index].id;
|
||||||
|
handleVisibility(visibleFraction: info.visibleFraction, id: id, type: ViewableContentType.posts);
|
||||||
|
},
|
||||||
|
key: ValueKey(index),
|
||||||
|
child: BlogPostWidget(index: index),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, index) => const SizedBox(height: 8),
|
separatorBuilder: (context, index) => const SizedBox(height: 8),
|
||||||
itemCount: state.isLoading ? state.feedBlogPostList.length + 1 : state.feedBlogPostList.length,
|
itemCount: state.isLoading ? state.feedBlogPostList.length + 1 : state.feedBlogPostList.length,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (state is FeedStateError) {
|
} else if (state is FeedStateError) {
|
||||||
return DataErrorWidget(
|
return DataErrorWidget(onReload: () => context.read<FeedCubit>().loadFeedBlogPostList());
|
||||||
onReload: () => context.read<FeedCubit>().loadFeedBlogPostList(),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return Scaffold(
|
return Scaffold(backgroundColor: Colors.grey.shade200, body: const Loader());
|
||||||
backgroundColor: Colors.grey.shade200,
|
|
||||||
body: const Loader(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,13 +25,7 @@ class CommentsWidget extends StatefulWidget {
|
|||||||
final CommentModel comment;
|
final CommentModel comment;
|
||||||
final int? replyId;
|
final int? replyId;
|
||||||
|
|
||||||
const CommentsWidget({
|
const CommentsWidget({super.key, required this.id, required this.commentSourceType, required this.comment, this.replyId});
|
||||||
super.key,
|
|
||||||
required this.id,
|
|
||||||
required this.commentSourceType,
|
|
||||||
required this.comment,
|
|
||||||
this.replyId,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CommentsWidget> createState() => _CommentsWidgetState();
|
State<CommentsWidget> createState() => _CommentsWidgetState();
|
||||||
@@ -47,10 +41,7 @@ class _CommentsWidgetState extends State<CommentsWidget> {
|
|||||||
_comment(s, widget.comment.data[i]),
|
_comment(s, widget.comment.data[i]),
|
||||||
if (widget.comment.data[i].childrenComments.isNotEmpty) const SizedBox(height: 8),
|
if (widget.comment.data[i].childrenComments.isNotEmpty) const SizedBox(height: 8),
|
||||||
for (int j = 0; j < widget.comment.data[i].childrenComments.length; j++)
|
for (int j = 0; j < widget.comment.data[i].childrenComments.length; j++)
|
||||||
Padding(
|
Padding(padding: const EdgeInsets.only(left: 40), child: _comment(s, widget.comment.data[i].childrenComments[j], 16, true)),
|
||||||
padding: const EdgeInsets.only(left: 40),
|
|
||||||
child: _comment(s, widget.comment.data[i].childrenComments[j], 16, true),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 48),
|
const SizedBox(height: 48),
|
||||||
@@ -72,7 +63,7 @@ class _CommentsWidgetState extends State<CommentsWidget> {
|
|||||||
: null,
|
: null,
|
||||||
copy: () {
|
copy: () {
|
||||||
Clipboard.setData(ClipboardData(text: comment.body));
|
Clipboard.setData(ClipboardData(text: comment.body));
|
||||||
showInfoToast(s.link_was_copied);
|
showInfoToast(s.text_was_copied);
|
||||||
},
|
},
|
||||||
edit: (String text) {
|
edit: (String text) {
|
||||||
context.read<CommentsCubit>().updateComment(id: comment.id, body: text);
|
context.read<CommentsCubit>().updateComment(id: comment.id, body: text);
|
||||||
@@ -84,10 +75,7 @@ class _CommentsWidgetState extends State<CommentsWidget> {
|
|||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (context) => ComplainingBottomSheet(
|
builder: (context) => ComplainingBottomSheet(complainedContentType: ComplainedContent.comment, id: comment.id),
|
||||||
complainedContentType: ComplainedContent.comment,
|
|
||||||
id: comment.id,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
isMyComment: isMe,
|
isMyComment: isMe,
|
||||||
@@ -117,9 +105,7 @@ class _CommentsWidgetState extends State<CommentsWidget> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
foregroundImage: NetworkImage(
|
foregroundImage: NetworkImage(comment.authorProfileIcon.contains('https') ? comment.authorProfileIcon : baseUrl + comment.authorProfileIcon),
|
||||||
comment.authorProfileIcon.contains('https') ? comment.authorProfileIcon : baseUrl + comment.authorProfileIcon,
|
|
||||||
),
|
|
||||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||||
radius: radius,
|
radius: radius,
|
||||||
),
|
),
|
||||||
@@ -131,24 +117,14 @@ class _CommentsWidgetState extends State<CommentsWidget> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
comment.authorName,
|
comment.authorName,
|
||||||
style: TextStyle(
|
style: TextStyle(height: 1, color: appTheme.gray, fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
height: 1,
|
|
||||||
color: appTheme.gray,
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: isChild ? MediaQuery.of(context).size.width * 0.72 : MediaQuery.of(context).size.width * 0.8,
|
width: isChild ? MediaQuery.of(context).size.width * 0.72 : MediaQuery.of(context).size.width * 0.8,
|
||||||
child: Text(
|
child: Text(
|
||||||
comment.body,
|
comment.body,
|
||||||
style: TextStyle(
|
style: TextStyle(height: 1, color: appTheme.black, fontSize: 16, fontWeight: FontWeight.w500),
|
||||||
height: 1,
|
|
||||||
color: appTheme.black,
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
@@ -156,12 +132,7 @@ class _CommentsWidgetState extends State<CommentsWidget> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
comment.createdAt.format('dd MMMM', 'ru'),
|
comment.createdAt.format('dd MMMM', 'ru'),
|
||||||
style: TextStyle(
|
style: TextStyle(height: 1, color: appTheme.gray, fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
height: 1,
|
|
||||||
color: appTheme.gray,
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
if (!isChild)
|
if (!isChild)
|
||||||
@@ -171,12 +142,7 @@ class _CommentsWidgetState extends State<CommentsWidget> {
|
|||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
s.answer,
|
s.answer,
|
||||||
style: TextStyle(
|
style: TextStyle(height: 1, color: appTheme.gray, fontSize: 14, fontWeight: FontWeight.w600),
|
||||||
height: 1,
|
|
||||||
color: appTheme.gray,
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -3,15 +3,17 @@ import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
|||||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart';
|
||||||
|
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/widgets/diet_filter_bottom_sheet.dart';
|
import 'package:eatvio_app/features/feed/presentation/widgets/diet_filter_bottom_sheet.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/widgets/diets_widget.dart';
|
import 'package:eatvio_app/features/feed/presentation/widgets/diets_widget.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/widgets/filter_badge.dart';
|
import 'package:eatvio_app/features/feed/presentation/widgets/filter_badge.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:visibility_detector/visibility_detector.dart';
|
||||||
|
|
||||||
class Diets extends StatelessWidget {
|
class Diets extends StatelessWidget {
|
||||||
const Diets({super.key});
|
const Diets({super.key, required this.handleVisibility});
|
||||||
|
final Function({required double visibleFraction, required int id, required ViewableContentType type}) handleVisibility;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
@@ -19,10 +21,7 @@ class Diets extends StatelessWidget {
|
|||||||
child: BlocBuilder<DietsCubit, DietsState>(
|
child: BlocBuilder<DietsCubit, DietsState>(
|
||||||
builder: (BuildContext context, DietsState state) {
|
builder: (BuildContext context, DietsState state) {
|
||||||
if (state.isLoading) {
|
if (state.isLoading) {
|
||||||
return Scaffold(
|
return Scaffold(backgroundColor: Colors.grey.shade200, body: const Loader());
|
||||||
backgroundColor: Colors.grey.shade200,
|
|
||||||
body: const Loader(),
|
|
||||||
);
|
|
||||||
} else if (state.errorMessage != null) {
|
} else if (state.errorMessage != null) {
|
||||||
return DataErrorWidget(onReload: () => context.read<DietsCubit>().loadDiets());
|
return DataErrorWidget(onReload: () => context.read<DietsCubit>().loadDiets());
|
||||||
} else {
|
} else {
|
||||||
@@ -39,7 +38,14 @@ class Diets extends StatelessWidget {
|
|||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return DietsWidget(diet: state.dietsList[index], index: index);
|
return VisibilityDetector(
|
||||||
|
key: ValueKey(index),
|
||||||
|
onVisibilityChanged: (info) {
|
||||||
|
final id = state.dietsList[index].id;
|
||||||
|
handleVisibility(visibleFraction: info.visibleFraction, id: id, type: ViewableContentType.diets);
|
||||||
|
},
|
||||||
|
child: DietsWidget(diet: state.dietsList[index], index: index),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, index) {
|
separatorBuilder: (context, index) {
|
||||||
return const SizedBox(height: 8);
|
return const SizedBox(height: 8);
|
||||||
@@ -50,11 +56,7 @@ class Diets extends StatelessWidget {
|
|||||||
Positioned(
|
Positioned(
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 74,
|
bottom: 74,
|
||||||
child: FilterBadge(
|
child: FilterBadge(filterBottomSheet: DietFilterBottomSheet(dietsCubit: context.read<DietsCubit>())),
|
||||||
filterBottomSheet: DietFilterBottomSheet(
|
|
||||||
dietsCubit: context.read<DietsCubit>(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,15 +3,17 @@ import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
|||||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_state.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_state.dart';
|
||||||
|
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/widgets/filter_badge.dart';
|
import 'package:eatvio_app/features/feed/presentation/widgets/filter_badge.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/widgets/plan_widget.dart';
|
import 'package:eatvio_app/features/feed/presentation/widgets/plan_widget.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/widgets/plans_filter_bottom_sheet.dart';
|
import 'package:eatvio_app/features/feed/presentation/widgets/plans_filter_bottom_sheet.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:visibility_detector/visibility_detector.dart';
|
||||||
|
|
||||||
class Plans extends StatelessWidget {
|
class Plans extends StatelessWidget {
|
||||||
const Plans({super.key});
|
const Plans({super.key, required this.handleVisibility});
|
||||||
|
final Function({required double visibleFraction, required int id, required ViewableContentType type}) handleVisibility;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
@@ -19,10 +21,7 @@ class Plans extends StatelessWidget {
|
|||||||
child: BlocBuilder<PlansCubit, PlansState>(
|
child: BlocBuilder<PlansCubit, PlansState>(
|
||||||
builder: (BuildContext context, PlansState state) {
|
builder: (BuildContext context, PlansState state) {
|
||||||
if (state.isLoading) {
|
if (state.isLoading) {
|
||||||
return Scaffold(
|
return Scaffold(backgroundColor: Colors.grey.shade200, body: const Loader());
|
||||||
backgroundColor: Colors.grey.shade200,
|
|
||||||
body: const Loader(),
|
|
||||||
);
|
|
||||||
} else if (state.errorMessage != null) {
|
} else if (state.errorMessage != null) {
|
||||||
return DataErrorWidget(onReload: () => context.read<PlansCubit>().loadPlans());
|
return DataErrorWidget(onReload: () => context.read<PlansCubit>().loadPlans());
|
||||||
}
|
}
|
||||||
@@ -39,9 +38,13 @@ class Plans extends StatelessWidget {
|
|||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return PlanWidget(
|
return VisibilityDetector(
|
||||||
plan: state.plans[index],
|
key: ValueKey(index),
|
||||||
index: index,
|
onVisibilityChanged: (info) {
|
||||||
|
final id = state.plans[index].id;
|
||||||
|
handleVisibility(visibleFraction: info.visibleFraction, id: id, type: ViewableContentType.plans);
|
||||||
|
},
|
||||||
|
child: PlanWidget(plan: state.plans[index], index: index),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, index) {
|
separatorBuilder: (context, index) {
|
||||||
@@ -53,11 +56,7 @@ class Plans extends StatelessWidget {
|
|||||||
Positioned(
|
Positioned(
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 74,
|
bottom: 74,
|
||||||
child: FilterBadge(
|
child: FilterBadge(filterBottomSheet: PlansFilterBottomSheet(plansCubit: context.read<PlansCubit>())),
|
||||||
filterBottomSheet: PlansFilterBottomSheet(
|
|
||||||
plansCubit: context.read<PlansCubit>(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
|||||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_state.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_state.dart';
|
||||||
|
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/widgets/recipes_widget.dart';
|
import 'package:eatvio_app/features/feed/presentation/widgets/recipes_widget.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:visibility_detector/visibility_detector.dart';
|
||||||
|
|
||||||
class Recipes extends StatelessWidget {
|
class Recipes extends StatelessWidget {
|
||||||
const Recipes({super.key});
|
const Recipes({super.key, required this.handleVisibility});
|
||||||
|
final Function({required double visibleFraction,required int id, required ViewableContentType type}) handleVisibility;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
@@ -40,9 +42,16 @@ class Recipes extends StatelessWidget {
|
|||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return RecipesWidget(
|
return VisibilityDetector(
|
||||||
recipe: state.recipesList[index],
|
key: ValueKey(index),
|
||||||
index: index,
|
onVisibilityChanged: (info) {
|
||||||
|
final id = state.recipesList[index].id;
|
||||||
|
handleVisibility(visibleFraction: info.visibleFraction, id: id, type: ViewableContentType.recipes);
|
||||||
|
},
|
||||||
|
child: RecipesWidget(
|
||||||
|
recipe: state.recipesList[index],
|
||||||
|
index: index,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, index) {
|
separatorBuilder: (context, index) {
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ class _RecipesWidgetState extends State<RecipesWidget> {
|
|||||||
final box = context.findRenderObject() as RenderBox?;
|
final box = context.findRenderObject() as RenderBox?;
|
||||||
SharePlus.instance.share(
|
SharePlus.instance.share(
|
||||||
ShareParams(
|
ShareParams(
|
||||||
uri: Uri.parse('$baseUrl/ru/blog_post/${widget.recipe.id}'),
|
uri: Uri.parse('$baseUrl/ru/recipe/${widget.recipe.id}'),
|
||||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,103 +5,55 @@ import 'package:eatvio_app/features/feed/models/feed_blog_post_settings_model.da
|
|||||||
import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart';
|
import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||||
|
import 'package:eatvio_app/features/feed/models/view_model.dart';
|
||||||
import 'package:eatvio_app/features/feed/services/feed_service.dart';
|
import 'package:eatvio_app/features/feed/services/feed_service.dart';
|
||||||
|
|
||||||
class FeedRepository {
|
class FeedRepository {
|
||||||
final FeedService feedService;
|
final FeedService feedService;
|
||||||
|
|
||||||
FeedRepository({
|
FeedRepository({required this.feedService});
|
||||||
required this.feedService,
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<FeedBlogPostSettingsModel> getFeedBlogPosts({
|
Future<FeedBlogPostSettingsModel> getFeedBlogPosts({String? query, int page = 1}) async {
|
||||||
String? query,
|
|
||||||
int page = 1,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return (await feedService.getBlogPostList(
|
return (await feedService.getBlogPostList(page: page, query: query));
|
||||||
page: page,
|
|
||||||
query: query,
|
|
||||||
));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<FeedBlogPostModel> getBlogPost({
|
Future<FeedBlogPostModel> getBlogPost({required int id}) async {
|
||||||
required int id
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return await feedService.getBlogPost(
|
return await feedService.getBlogPost(postId: id);
|
||||||
postId: id
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Recipe>> getRecipes({
|
Future<List<Recipe>> getRecipes({String? query, List<int>? tags, int page = 1}) async {
|
||||||
String? query,
|
|
||||||
List<int>? tags,
|
|
||||||
int page = 1,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return (await feedService.getRecipes(
|
return (await feedService.getRecipes(query: query, tags: tags, page: page)).recipesList;
|
||||||
query: query,
|
|
||||||
tags: tags,
|
|
||||||
page: page,
|
|
||||||
))
|
|
||||||
.recipesList;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DietsPagination> getDiets({
|
Future<DietsPagination> getDiets({int page = 1, String? query, String? types, int? duration, String? sorting}) async {
|
||||||
int page = 1,
|
|
||||||
String? query,
|
|
||||||
String? types,
|
|
||||||
int? duration,
|
|
||||||
String? sorting,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return (await feedService.getDiets(
|
return (await feedService.getDiets(page: page, query: query, types: types, duration: duration, sorting: sorting));
|
||||||
page: page,
|
|
||||||
query: query,
|
|
||||||
types: types,
|
|
||||||
duration: duration,
|
|
||||||
sorting: sorting,
|
|
||||||
));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<PlanPaginationModel> getPlans({
|
Future<PlanPaginationModel> getPlans({int page = 1, String? query, String? types, int? duration, int? minPrice, int? maxPrice}) async {
|
||||||
int page = 1,
|
|
||||||
String? query,
|
|
||||||
String? types,
|
|
||||||
int? duration,
|
|
||||||
int? minPrice,
|
|
||||||
int? maxPrice,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return (await feedService.getPlans(
|
return (await feedService.getPlans(page: page, query: query, types: types, duration: duration, minPrice: minPrice, maxPrice: maxPrice));
|
||||||
page: page,
|
|
||||||
query: query,
|
|
||||||
types: types,
|
|
||||||
duration: duration,
|
|
||||||
minPrice: minPrice,
|
|
||||||
maxPrice: maxPrice,
|
|
||||||
));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<CommentModel> getBlogPostComments({
|
Future<CommentModel> getBlogPostComments({required String id}) async {
|
||||||
required String id,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return (await feedService.getBlogPostComments(id: id));
|
return (await feedService.getBlogPostComments(id: id));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -109,25 +61,15 @@ class FeedRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendBlogPostComment({
|
Future<void> sendBlogPostComment({required String id, required String commentText, int? parentCommentId}) async {
|
||||||
required String id,
|
|
||||||
required String commentText,
|
|
||||||
int? parentCommentId,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
await feedService.sendBlogPostComment(
|
await feedService.sendBlogPostComment(id: id, body: commentText, parentCommentId: parentCommentId);
|
||||||
id: id,
|
|
||||||
body: commentText,
|
|
||||||
parentCommentId: parentCommentId,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<CommentModel> getDietsComments({
|
Future<CommentModel> getDietsComments({required String id}) async {
|
||||||
required String id,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return (await feedService.getDietsComments(id: id));
|
return (await feedService.getDietsComments(id: id));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -135,25 +77,15 @@ class FeedRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendDietsComment({
|
Future<void> sendDietsComment({required String id, required String commentText, int? parentCommentId}) async {
|
||||||
required String id,
|
|
||||||
required String commentText,
|
|
||||||
int? parentCommentId,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
await feedService.sendDietsComment(
|
await feedService.sendDietsComment(id: id, body: commentText, parentCommentId: parentCommentId);
|
||||||
id: id,
|
|
||||||
body: commentText,
|
|
||||||
parentCommentId: parentCommentId,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<CommentModel> getRecipesComments({
|
Future<CommentModel> getRecipesComments({required String id}) async {
|
||||||
required String id,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return (await feedService.getRecipesComments(id: id));
|
return (await feedService.getRecipesComments(id: id));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -161,25 +93,15 @@ class FeedRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendRecipesComment({
|
Future<void> sendRecipesComment({required String id, required String commentText, int? parentCommentId}) async {
|
||||||
required String id,
|
|
||||||
required String commentText,
|
|
||||||
int? parentCommentId,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
await feedService.sendRecipesComment(
|
await feedService.sendRecipesComment(id: id, body: commentText, parentCommentId: parentCommentId);
|
||||||
id: id,
|
|
||||||
body: commentText,
|
|
||||||
parentCommentId: parentCommentId,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<CommentModel> getPlansComments({
|
Future<CommentModel> getPlansComments({required String slug}) async {
|
||||||
required String slug,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return (await feedService.getPlansComments(slug: slug));
|
return (await feedService.getPlansComments(slug: slug));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -187,25 +109,15 @@ class FeedRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendPlansComment({
|
Future<void> sendPlansComment({required String slug, required String commentText, int? parentCommentId}) async {
|
||||||
required String slug,
|
|
||||||
required String commentText,
|
|
||||||
int? parentCommentId,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
await feedService.sendPlansComment(
|
await feedService.sendPlansComment(slug: slug, body: commentText, parentCommentId: parentCommentId);
|
||||||
slug: slug,
|
|
||||||
body: commentText,
|
|
||||||
parentCommentId: parentCommentId,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> likeBlogPost({
|
Future<void> likeBlogPost({required int id}) async {
|
||||||
required int id,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
await feedService.likeBlogPost(id: id);
|
await feedService.likeBlogPost(id: id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -213,9 +125,7 @@ class FeedRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> unlikedBlogPost({
|
Future<void> unlikedBlogPost({required int id}) async {
|
||||||
required int id,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
await feedService.unlikedBlogPost(id: id);
|
await feedService.unlikedBlogPost(id: id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -262,4 +172,12 @@ class FeedRepository {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> viewContent({required ViewModel viewModel}) async {
|
||||||
|
try {
|
||||||
|
await feedService.viewContent(viewModel: viewModel);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:eatvio_app/features/feed/models/feed_blog_post_settings_model.da
|
|||||||
import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart';
|
import 'package:eatvio_app/features/feed/models/feed_consultant_model.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/recipes_pagination_model.dart';
|
import 'package:eatvio_app/features/feed/models/recipes_pagination_model.dart';
|
||||||
|
import 'package:eatvio_app/features/feed/models/view_model.dart';
|
||||||
import 'package:retrofit/retrofit.dart';
|
import 'package:retrofit/retrofit.dart';
|
||||||
|
|
||||||
part 'feed_service.g.dart';
|
part 'feed_service.g.dart';
|
||||||
@@ -131,4 +132,9 @@ abstract class FeedService {
|
|||||||
Future<void> viewPlan({
|
Future<void> viewPlan({
|
||||||
@Path() required String slug,
|
@Path() required String slug,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@POST('/api/views')
|
||||||
|
Future<void> viewContent({
|
||||||
|
@Body() required ViewModel viewModel,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,21 +33,12 @@ class _UserPurchasesScreen extends State<UserPurchasesScreen> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: const Color(0xFFF0F2F5),
|
backgroundColor: const Color(0xFFF0F2F5),
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(borderRadius: BorderRadiusDirectional.vertical(bottom: Radius.circular(12))),
|
||||||
borderRadius: BorderRadiusDirectional.vertical(
|
title: Text('Финансы', style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
|
||||||
bottom: Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
'Финансы',
|
|
||||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
|
||||||
),
|
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
body: state.isLoading
|
body: state.isLoading
|
||||||
? const Center(
|
? const Center(child: Loader())
|
||||||
child: Loader(),
|
|
||||||
)
|
|
||||||
: SingleChildScrollView(
|
: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -67,10 +58,7 @@ class _UserPurchasesScreen extends State<UserPurchasesScreen> {
|
|||||||
.map(
|
.map(
|
||||||
(value) => DropdownMenuItem(
|
(value) => DropdownMenuItem(
|
||||||
value: value,
|
value: value,
|
||||||
child: Text(
|
child: Text(value.name, style: TextStyles.textMMedium.copyWith(color: appTheme.black)),
|
||||||
value.name,
|
|
||||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
@@ -105,60 +93,41 @@ class _UserPurchasesScreen extends State<UserPurchasesScreen> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Builder(builder: (context) {
|
Builder(
|
||||||
final idLen = state.purchasesModels[index].bankId.length;
|
builder: (context) {
|
||||||
return Text(
|
final idLen = state.purchasesModels[index].bankId.length;
|
||||||
'${state.purchasesModels[index].bankId.substring(0, 4)}...${state.purchasesModels[index].bankId.substring(idLen - 4, idLen)} ',
|
return Text(
|
||||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
'${state.purchasesModels[index].bankId.substring(0, 4)}...${state.purchasesModels[index].bankId.substring(idLen - 4, idLen)} ',
|
||||||
);
|
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||||
}),
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
state.purchasesModels[index].startDate.format('yyyy.MM.dd'),
|
state.purchasesModels[index].startDate.format('dd.MM.yyyy'),
|
||||||
style: const TextStyle(
|
style: const TextStyle(color: Color(0xFF6D6F71), height: 1, fontWeight: FontWeight.w500),
|
||||||
color: Color(0xFF6D6F71),
|
|
||||||
height: 1,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
state.purchasesModels[index].title,
|
state.purchasesModels[index].title,
|
||||||
style: TextStyle(
|
style: TextStyle(color: appTheme.blueA400, height: 1, fontSize: 18, fontWeight: FontWeight.w600),
|
||||||
color: appTheme.blueA400,
|
|
||||||
height: 1,
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Продавец: ${state.purchasesModels[index].consultantName}',
|
'Продавец: ${state.purchasesModels[index].consultantName}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(color: Color(0xFF323F4B), height: 1, fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
color: Color(0xFF323F4B),
|
|
||||||
height: 1,
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'${state.purchasesModels[index].duration} ${getDayForm(state.purchasesModels[index].duration, s)}',
|
'${state.purchasesModels[index].duration} ${getDayForm(state.purchasesModels[index].duration, s)}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(color: Color(0xFF323F4B), height: 1, fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
color: Color(0xFF323F4B),
|
|
||||||
height: 1,
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Container(
|
Container(
|
||||||
height: 1,
|
height: 1,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(color: Color(0xFFDEDEDF)),
|
||||||
color: Color(0xFFDEDEDF),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Row(
|
Row(
|
||||||
@@ -166,16 +135,9 @@ class _UserPurchasesScreen extends State<UserPurchasesScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'${state.purchasesModels[index].paymentPrice} ₽',
|
'${state.purchasesModels[index].paymentPrice} ₽',
|
||||||
style: const TextStyle(
|
style: const TextStyle(color: Color(0xFF323F4B), height: 1, fontSize: 18, fontWeight: FontWeight.w600),
|
||||||
color: Color(0xFF323F4B),
|
|
||||||
height: 1,
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
StatusWidget(
|
|
||||||
status: purchaseStatus,
|
|
||||||
),
|
),
|
||||||
|
StatusWidget(status: purchaseStatus),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -189,10 +151,7 @@ class _UserPurchasesScreen extends State<UserPurchasesScreen> {
|
|||||||
else
|
else
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 40.0),
|
padding: EdgeInsets.symmetric(vertical: 40.0),
|
||||||
child: Text(
|
child: Text('У вас нет таких консультаций', style: TextStyles.textMMedium),
|
||||||
'У вас нет таких консультаций',
|
|
||||||
style: TextStyles.textMMedium,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -200,7 +159,8 @@ class _UserPurchasesScreen extends State<UserPurchasesScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
39
lib/features/menu/models/notification_settings_model.dart
Normal file
39
lib/features/menu/models/notification_settings_model.dart
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'notification_settings_model.g.dart';
|
||||||
|
part 'notification_settings_model.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class NotificationSettingsModel with _$NotificationSettingsModel {
|
||||||
|
const factory NotificationSettingsModel({
|
||||||
|
@JsonKey(name: 'new_updates_notification') required bool newUpdatesNotification,
|
||||||
|
@JsonKey(name: 'weight_measurements_notification') required bool weightMeasurementsNotification,
|
||||||
|
@JsonKey(name: 'is_email_notifications_enabled') required bool isEmailNotificationsEnabled,
|
||||||
|
@JsonKey(name: 'is_push_notifications_enabled') required bool isPushNotificationsEnabled,
|
||||||
|
@JsonKey(name: 'diary_reminder_notifications') required DiaryReminderNotifications diaryReminderNotifications,
|
||||||
|
}) = _NotificationSettingsModel;
|
||||||
|
|
||||||
|
factory NotificationSettingsModel.fromJson(Map<String, Object?> json) => _$NotificationSettingsModelFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class DiaryReminderNotifications with _$DiaryReminderNotifications {
|
||||||
|
const factory DiaryReminderNotifications({
|
||||||
|
@JsonKey(name: 'first_reminder') required ReminderSettings firstReminder,
|
||||||
|
@JsonKey(name: 'second_reminder') required ReminderSettings secondReminder,
|
||||||
|
@JsonKey(name: 'third_reminder') required ReminderSettings thirdReminder,
|
||||||
|
}) = _DiaryReminderNotifications;
|
||||||
|
|
||||||
|
factory DiaryReminderNotifications.fromJson(Map<String, Object?> json) => _$DiaryReminderNotificationsFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ReminderSettings with _$ReminderSettings {
|
||||||
|
const factory ReminderSettings({
|
||||||
|
required bool enabled,
|
||||||
|
@JsonKey(name: 'local_time_hours') required int localTimeHours,
|
||||||
|
@JsonKey(name: 'local_time_mins') required int localTimeMins,
|
||||||
|
}) = _ReminderSettings;
|
||||||
|
|
||||||
|
factory ReminderSettings.fromJson(Map<String, Object?> json) => _$ReminderSettingsFromJson(json);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_state.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.dart';
|
||||||
|
|
||||||
|
class NotificationSettingsCubit extends Cubit<NotificationSettingsState> {
|
||||||
|
final NotificationSettingsRepository _notificationSettingsRepository;
|
||||||
|
|
||||||
|
NotificationSettingsCubit({required NotificationSettingsRepository notificationSettingsRepository})
|
||||||
|
: _notificationSettingsRepository = notificationSettingsRepository,
|
||||||
|
super(const NotificationSettingsStateLoading());
|
||||||
|
|
||||||
|
Future<void> getNotificationSettings() async {
|
||||||
|
emit(const NotificationSettingsStateLoading());
|
||||||
|
try {
|
||||||
|
final notificationSettings = await _notificationSettingsRepository.getNotificationSettings();
|
||||||
|
emit(NotificationSettingsStateLoaded(notificationSettings: notificationSettings));
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
log('Error getting notification settings: $e', stackTrace: stackTrace);
|
||||||
|
emit(NotificationSettingsStateError(errorMessage: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateNotificationSettings(Map<String, dynamic> data) async {
|
||||||
|
try {
|
||||||
|
final notificationSettings = await _notificationSettingsRepository.updateNotificationSettings(data);
|
||||||
|
emit(NotificationSettingsStateLoaded(notificationSettings: notificationSettings));
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
log('Error updating notification settings: $e', stackTrace: stackTrace);
|
||||||
|
emit(NotificationSettingsStateError(errorMessage: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/models/notification_settings_model.dart';
|
||||||
|
|
||||||
|
abstract class NotificationSettingsState extends Equatable {
|
||||||
|
const NotificationSettingsState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationSettingsStateLoading extends NotificationSettingsState {
|
||||||
|
const NotificationSettingsStateLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationSettingsStateLoaded extends NotificationSettingsState {
|
||||||
|
final NotificationSettingsModel notificationSettings;
|
||||||
|
|
||||||
|
const NotificationSettingsStateLoaded({required this.notificationSettings});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [notificationSettings];
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationSettingsStateError extends NotificationSettingsState {
|
||||||
|
final String errorMessage;
|
||||||
|
|
||||||
|
const NotificationSettingsStateError({required this.errorMessage});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [errorMessage];
|
||||||
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/core/const.dart';
|
import 'package:eatvio_app/core/const.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
import 'package:eatvio_app/core/token_storage.dart';
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
import 'package:eatvio_app/core/utils/image_constant.dart';
|
import 'package:eatvio_app/core/utils/image_constant.dart';
|
||||||
|
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant_rating/presentation/consultant_rating_screen.dart';
|
||||||
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart';
|
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/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
|
||||||
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
|
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
|
||||||
@@ -14,6 +17,7 @@ import 'package:flutter/cupertino.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
class MenuScreen extends StatelessWidget {
|
class MenuScreen extends StatelessWidget {
|
||||||
final ProfileModel profileModel;
|
final ProfileModel profileModel;
|
||||||
@@ -51,9 +55,20 @@ class MenuScreen extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
if (profileModel.roles.contains('consultant')) _divider(context),
|
||||||
|
if (profileModel.roles.contains('consultant'))
|
||||||
|
_tile(
|
||||||
|
leading: 'assets/icons/finance.svg',
|
||||||
|
title: 'Мой рейтинг',
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).push(ConsultantRatingScreen.path);
|
||||||
|
},
|
||||||
|
),
|
||||||
_divider(context),
|
_divider(context),
|
||||||
_tile(leading: ImageConstant.icnGlossary, title: 'Глоссарий', onTap: () => GoRouter.of(context).push('/glossary-screen')),
|
_tile(leading: ImageConstant.icnGlossary, title: 'Глоссарий', onTap: () => GoRouter.of(context).push('/glossary-screen')),
|
||||||
_divider(context),
|
_divider(context),
|
||||||
|
_tile(leading: 'assets/icons/notifications.svg', title: 'Уведомления', onTap: () => GoRouter.of(context).pushNamed('notificationsSettingsScreen')),
|
||||||
|
_divider(context),
|
||||||
_tile(leading: ImageConstant.icnSupport, title: 'Поддержка', onTap: () => GoRouter.of(context).push('/support-screen')),
|
_tile(leading: ImageConstant.icnSupport, title: 'Поддержка', onTap: () => GoRouter.of(context).push('/support-screen')),
|
||||||
_divider(context),
|
_divider(context),
|
||||||
_tile(leading: 'assets/icons/blocked_users.svg', title: 'Чёрный список', onTap: () => GoRouter.of(context).pushNamed('blockedUsersScreen')),
|
_tile(leading: 'assets/icons/blocked_users.svg', title: 'Чёрный список', onTap: () => GoRouter.of(context).pushNamed('blockedUsersScreen')),
|
||||||
@@ -67,6 +82,8 @@ class MenuScreen extends StatelessWidget {
|
|||||||
// ),
|
// ),
|
||||||
_divider(context),
|
_divider(context),
|
||||||
_tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)),
|
_tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
const AppInfo(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -124,3 +141,42 @@ class MenuScreen extends StatelessWidget {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AppInfo extends StatefulWidget {
|
||||||
|
const AppInfo({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AppInfo> createState() => _AppInfoState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppInfoState extends State<AppInfo> {
|
||||||
|
String version = '';
|
||||||
|
String buildNumber = '';
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
PackageInfo.fromPlatform().then((PackageInfo packageInfo) {
|
||||||
|
setState(() {
|
||||||
|
version = packageInfo.version;
|
||||||
|
buildNumber = packageInfo.buildNumber;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsetsGeometry.only(left: 16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Версия приложения: $version ($buildNumber)', style: TextStyles.textXSSemibold.copyWith(color: appTheme.gray)),
|
||||||
|
if (buildDate != '') Text('Дата сборки: ${formatBuildDate(buildDate)}', style: TextStyles.textXSSemibold.copyWith(color: appTheme.gray)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,285 @@
|
|||||||
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/models/notification_settings_model.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_state.dart';
|
||||||
|
import 'package:eatvio_app/generated/l10n.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';
|
||||||
|
|
||||||
|
class NotificationsSettingsScreen extends StatelessWidget {
|
||||||
|
const NotificationsSettingsScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(create: (context) => sl<NotificationSettingsCubit>()..getNotificationSettings(), child: const NotificationsSettingsContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotificationsSettingsContent extends StatelessWidget {
|
||||||
|
const NotificationsSettingsContent({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final s = S.of(context);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text(s.notifications)),
|
||||||
|
body: BlocBuilder<NotificationSettingsCubit, NotificationSettingsState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state is NotificationSettingsStateLoading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
} else if (state is NotificationSettingsStateError) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text('Ошибка: ${state.errorMessage}'),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.read<NotificationSettingsCubit>().getNotificationSettings();
|
||||||
|
},
|
||||||
|
child: const Text('Повторить'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (state is NotificationSettingsStateLoaded) {
|
||||||
|
return _buildSettingsContent(context, state.notificationSettings);
|
||||||
|
}
|
||||||
|
return const Center(child: Text('Нет данных'));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSettingsContent(BuildContext context, NotificationSettingsModel notificationSettings) {
|
||||||
|
final cubit = context.read<NotificationSettingsCubit>();
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
_tile(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
onTap: () {},
|
||||||
|
title: 'Новые обновления',
|
||||||
|
subtitle: null,
|
||||||
|
trailing: CupertinoSwitch(
|
||||||
|
value: notificationSettings.newUpdatesNotification,
|
||||||
|
activeTrackColor: appTheme.blueA400,
|
||||||
|
onChanged: (newValue) {
|
||||||
|
_updateNotificationSetting(cubit, {'new_updates_notification': newValue});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_divider(context),
|
||||||
|
_tile(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
onTap: () {},
|
||||||
|
title: 'Напоминать о взвешивании',
|
||||||
|
subtitle: null,
|
||||||
|
trailing: CupertinoSwitch(
|
||||||
|
value: notificationSettings.weightMeasurementsNotification,
|
||||||
|
activeTrackColor: appTheme.blueA400,
|
||||||
|
onChanged: (newValue) {
|
||||||
|
_updateNotificationSetting(cubit, {'weight_measurements_notification': newValue});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_divider(context),
|
||||||
|
_tile(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
onTap: () {},
|
||||||
|
title: 'Email уведомления',
|
||||||
|
subtitle: null,
|
||||||
|
trailing: CupertinoSwitch(
|
||||||
|
value: notificationSettings.isEmailNotificationsEnabled,
|
||||||
|
activeTrackColor: appTheme.blueA400,
|
||||||
|
onChanged: (newValue) {
|
||||||
|
_updateNotificationSetting(cubit, {'is_email_notifications_enabled': newValue});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_divider(context),
|
||||||
|
_tile(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
onTap: () {},
|
||||||
|
title: 'Push уведомления',
|
||||||
|
subtitle: null,
|
||||||
|
trailing: CupertinoSwitch(
|
||||||
|
value: notificationSettings.isPushNotificationsEnabled,
|
||||||
|
activeTrackColor: appTheme.blueA400,
|
||||||
|
onChanged: (newValue) {
|
||||||
|
_updateNotificationSetting(cubit, {'is_push_notifications_enabled': newValue});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_divider(context),
|
||||||
|
_buildDiaryRemindersSection(context, notificationSettings.diaryReminderNotifications, cubit),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDiaryRemindersSection(BuildContext context, DiaryReminderNotifications diaryReminderNotifications, NotificationSettingsCubit cubit) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Напоминание о заполнении дневника',
|
||||||
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: appTheme.cardNotificationsBG),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildReminderTile(context, '№1 Напоминание о приеме пищи', diaryReminderNotifications.firstReminder, 'first_reminder', cubit),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildReminderTile(context, '№2 Напоминание о приеме пищи', diaryReminderNotifications.secondReminder, 'second_reminder', cubit),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildReminderTile(context, '№3 Напоминание о приеме пищи', diaryReminderNotifications.thirdReminder, 'third_reminder', cubit),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateNotificationSetting(NotificationSettingsCubit cubit, Map<String, dynamic> data) {
|
||||||
|
cubit.updateNotificationSettings(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateReminderSetting(NotificationSettingsCubit cubit, String reminderType, Map<String, dynamic> data) {
|
||||||
|
final updateData = {
|
||||||
|
'diary_reminder_notifications': {reminderType: data},
|
||||||
|
};
|
||||||
|
cubit.updateNotificationSettings(updateData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildReminderTile(BuildContext context, String title, ReminderSettings reminder, String reminderType, NotificationSettingsCubit cubit) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: appTheme.dividerColor),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
reminderType == 'third_reminder' ? SvgPicture.asset('assets/icons/retire.svg') : SvgPicture.asset('assets/icons/wakeup.svg'),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(color: Color(0xFF323F4B), fontSize: 16, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: reminder.enabled
|
||||||
|
? () {
|
||||||
|
_showTimePicker(context, reminder, reminderType, cubit);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
decoration: BoxDecoration(color: reminder.enabled ? Colors.white : Colors.white.withAlpha((255 * 0.7).toInt()), borderRadius: BorderRadius.circular(48)),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'${reminder.localTimeHours.toString().padLeft(2, '0')}:${reminder.localTimeMins.toString().padLeft(2, '0')}',
|
||||||
|
style: TextStyle(fontSize: 17, color: appTheme.gray, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
CupertinoSwitch(
|
||||||
|
value: reminder.enabled,
|
||||||
|
activeTrackColor: appTheme.blueA400,
|
||||||
|
onChanged: (newValue) {
|
||||||
|
_updateReminderSetting(cubit, reminderType, {
|
||||||
|
'enabled': newValue,
|
||||||
|
'local_time_hours': reminder.localTimeHours,
|
||||||
|
'local_time_mins': reminder.localTimeMins,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showTimePicker(BuildContext context, ReminderSettings reminder, String reminderType, NotificationSettingsCubit cubit) {
|
||||||
|
int selectedHour = reminder.localTimeHours;
|
||||||
|
int selectedMinute = reminder.localTimeMins;
|
||||||
|
|
||||||
|
showCupertinoModalPopup<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
height: 300,
|
||||||
|
padding: const EdgeInsets.only(top: 6.0),
|
||||||
|
margin: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||||
|
color: CupertinoColors.systemBackground.resolveFrom(context),
|
||||||
|
child: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
CupertinoButton(child: const Text('Отмена'), onPressed: () => Navigator.of(context).pop()),
|
||||||
|
CupertinoButton(
|
||||||
|
child: const Text('Готово'),
|
||||||
|
onPressed: () {
|
||||||
|
_updateReminderSetting(cubit, reminderType, {
|
||||||
|
'enabled': reminder.enabled,
|
||||||
|
'local_time_hours': selectedHour,
|
||||||
|
'local_time_mins': selectedMinute,
|
||||||
|
});
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: CupertinoDatePicker(
|
||||||
|
mode: CupertinoDatePickerMode.time,
|
||||||
|
use24hFormat: true,
|
||||||
|
initialDateTime: DateTime(2024, 1, 1, selectedHour, selectedMinute),
|
||||||
|
onDateTimeChanged: (DateTime newDateTime) {
|
||||||
|
selectedHour = newDateTime.hour;
|
||||||
|
selectedMinute = newDateTime.minute;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListTile _tile({required String title, required Function()? onTap, Widget? trailing, EdgeInsets? padding, Widget? subtitle}) {
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: padding,
|
||||||
|
onTap: onTap,
|
||||||
|
title: Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500, height: 1)),
|
||||||
|
subtitle: subtitle,
|
||||||
|
trailing: trailing ?? const Icon(Icons.arrow_forward_ios_rounded, size: 12, color: Colors.black38),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Center _divider(BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
child: Container(
|
||||||
|
height: 0.4,
|
||||||
|
width: MediaQuery.of(context).size.width - 32,
|
||||||
|
decoration: BoxDecoration(color: Colors.black26, borderRadius: BorderRadius.circular(16)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import 'package:eatvio_app/features/menu/models/notification_settings_model.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/services/notification_settings_service.dart';
|
||||||
|
|
||||||
|
class NotificationSettingsRepository {
|
||||||
|
final NotificationSettingsService _notificationSettingsService;
|
||||||
|
|
||||||
|
NotificationSettingsRepository(this._notificationSettingsService);
|
||||||
|
|
||||||
|
Future<NotificationSettingsModel> getNotificationSettings() async {
|
||||||
|
return await _notificationSettingsService.getNotificationSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<NotificationSettingsModel> updateNotificationSettings(Map<String, dynamic> data) async {
|
||||||
|
return await _notificationSettingsService.updateNotificationSettings(data: data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/models/notification_settings_model.dart';
|
||||||
|
import 'package:retrofit/retrofit.dart';
|
||||||
|
|
||||||
|
part 'notification_settings_service.g.dart';
|
||||||
|
|
||||||
|
@RestApi()
|
||||||
|
abstract class NotificationSettingsService {
|
||||||
|
factory NotificationSettingsService(Dio dio, {String baseUrl}) = _NotificationSettingsService;
|
||||||
|
|
||||||
|
@GET('/api/profile/notification_settings')
|
||||||
|
Future<NotificationSettingsModel> getNotificationSettings();
|
||||||
|
|
||||||
|
@POST('/api/profile/update_notification_settings')
|
||||||
|
Future<NotificationSettingsModel> updateNotificationSettings({@Body() required Map<String, dynamic> data});
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -66,12 +66,6 @@ class NotificationsCubit extends Cubit<NotificationsState> {
|
|||||||
isRead: isRead,
|
isRead: isRead,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
),
|
),
|
||||||
NotificationType.chosenPlanFinishedToClient => ChosenPlanFinishedToClientNotification.fromJson(
|
|
||||||
notificationJson,
|
|
||||||
type,
|
|
||||||
isRead: isRead,
|
|
||||||
createdAt: createdAt,
|
|
||||||
),
|
|
||||||
NotificationType.planSubscriptionCanceledToCustomer => PlanSubscriptionCanceledToAuthorNotification.fromJson(
|
NotificationType.planSubscriptionCanceledToCustomer => PlanSubscriptionCanceledToAuthorNotification.fromJson(
|
||||||
notificationJson,
|
notificationJson,
|
||||||
type,
|
type,
|
||||||
@@ -96,12 +90,6 @@ class NotificationsCubit extends Cubit<NotificationsState> {
|
|||||||
isRead: isRead,
|
isRead: isRead,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
),
|
),
|
||||||
NotificationType.chosenPlanFinishedToConsultant => ChosenPlanFinishedToConsultantNotification.fromJson(
|
|
||||||
notificationJson,
|
|
||||||
type,
|
|
||||||
isRead: isRead,
|
|
||||||
createdAt: createdAt,
|
|
||||||
),
|
|
||||||
NotificationType.chosenPlanLastDayFinishedForUser => ChosenPlanLastDayFinishedForUser.fromJson(
|
NotificationType.chosenPlanLastDayFinishedForUser => ChosenPlanLastDayFinishedForUser.fromJson(
|
||||||
notificationJson,
|
notificationJson,
|
||||||
type,
|
type,
|
||||||
@@ -113,6 +101,166 @@ class NotificationsCubit extends Cubit<NotificationsState> {
|
|||||||
NotificationType.commentReplied => CommentReplied.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
NotificationType.commentReplied => CommentReplied.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
NotificationType.scoreAdded => ScoreAdded.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
NotificationType.scoreAdded => ScoreAdded.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
NotificationType.entityCommented => EntityCommented.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
NotificationType.entityCommented => EntityCommented.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
// Новые типы уведомлений
|
||||||
|
NotificationType.planSubscriptionExpired => PlanSubscriptionExpiredNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.planSubscriptionRenewed => PlanSubscriptionRenewedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.consultantAssigned => ConsultantAssignedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.consultantUnassigned => ConsultantUnassignedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.paymentFailed => PaymentFailedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.paymentSuccessful => PaymentSuccessfulNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.goalAchieved => GoalAchievedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.workoutReminder => WorkoutReminderNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.mealReminder => MealReminderNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
// New notification types
|
||||||
|
NotificationType.dietCancelled => DietCancelledNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.dietStarted => DietStartedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.dietExpired => DietExpiredNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.dietWillStartedIn => DietWillStartedInNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.dietCanNotBeStarted => DietCanNotBeStartedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.personalDietCreationNeeded => PersonalDietCreationNeededNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.chosenPlanClientLastDayFinished => ChosenPlanClientLastDayFinishedNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.chosenPlanClientLastDayStarted => ChosenPlanClientLastDayStartedNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.stayOnTrackToAuthor => StayOnTrackToAuthorNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.chosenPlanStartedToClient => ChosenPlanStartedToClientNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientStartPlanSubscriptionToSubscriber => ClientStartPlanSubscriptionToSubscriberNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.consultantConfirmPlanSubscriptionToSubscriber => ConsultantConfirmPlanSubscriptionToSubscriberNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientPaidPlanSubscriptionToAuthor => ClientPaidPlanSubscriptionToAuthorNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.chosenPlanTomorrowReminderToClient => ChosenPlanTomorrowReminderToClientNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.chosenPlanTomorrowReminderToConsultant => ChosenPlanTomorrowReminderToConsultantNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.chosenPlanStartedToConsultant => ChosenPlanStartedToConsultantNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.consultantLeftDiaryComment => ConsultantLeftDiaryCommentNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.chosenPlanFinishedToClient => ChosenPlanFinishedToClientNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.consultantConfirmPlanSubscriptionToAuthor => ConsultantConfirmPlanSubscriptionToAuthorNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientPaidPlanSubscriptionToSubscriber => ClientPaidPlanSubscriptionToSubscriberNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientStartPlanSubscriptionToAuthor => ClientStartPlanSubscriptionToAuthorNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientCancelPlanSubscriptionToAuthor => ClientCancelPlanSubscriptionToAuthorNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientCancelPlanSubscriptionToSubscriber => ClientCancelPlanSubscriptionToSubscriberNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.userDailyReportCompleted => UserDailyReportCompletedNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.chosenPlanFinishedToConsultant => ChosenPlanFinishedToConsultantNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.stayOnTrackToCustomer => StayOnTrackToCustomerNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.phoneChanged => PhoneChangedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.subscription => SubscriptionNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.badgeAwarded => BadgeAwardedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.badgeRevoked => BadgeRevokedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.consultantRequestStarted => ConsultantRequestStartedNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientCloseComplaintSuccess => ClientCloseComplaintSuccessNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientRefundSuccess => ClientRefundSuccessNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.consultantRefundSuccess => ConsultantRefundSuccessNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.clientRejectSuccess => ClientRejectSuccessNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.consultantRejectSuccess => ConsultantRejectSuccessNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.scoreSubstracted => ScoreSubstractedNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
|
NotificationType.productStatusUpdatedForAuthor => ProductStatusUpdatedForAuthorNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: isRead,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.diaryReminderForUsers => DiaryReminderForUsersNotification.fromJson(notificationJson, type, isRead: isRead, createdAt: createdAt),
|
||||||
NotificationType.unknown => UnknownNotification(type: type),
|
NotificationType.unknown => UnknownNotification(type: type),
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -122,8 +270,36 @@ class NotificationsCubit extends Cubit<NotificationsState> {
|
|||||||
|
|
||||||
void _handleNotification(String notificationData) {
|
void _handleNotification(String notificationData) {
|
||||||
final json = jsonDecode(notificationData) as Map<String, dynamic>;
|
final json = jsonDecode(notificationData) as Map<String, dynamic>;
|
||||||
final stringifiedType = json['notification_class_name'];
|
|
||||||
final type = NotificationType.getTypeFromString(stringifiedType);
|
// Улучшенная логика определения типа уведомления
|
||||||
|
String? stringifiedType;
|
||||||
|
|
||||||
|
// Сначала проверяем поле 'type' в корне JSON
|
||||||
|
if (json.containsKey('type') && json['type'] != null) {
|
||||||
|
stringifiedType = json['type'].toString();
|
||||||
|
log('Found type in root: $stringifiedType');
|
||||||
|
}
|
||||||
|
// Затем проверяем поле 'notification_class_name' в корне JSON
|
||||||
|
else if (json.containsKey('notification_class_name') && json['notification_class_name'] != null) {
|
||||||
|
stringifiedType = json['notification_class_name'].toString();
|
||||||
|
log('Found notification_class_name in root: $stringifiedType');
|
||||||
|
}
|
||||||
|
// Если есть поле 'data', проверяем внутри него
|
||||||
|
else if (json.containsKey('data') && json['data'] is Map<String, dynamic>) {
|
||||||
|
final data = json['data'] as Map<String, dynamic>;
|
||||||
|
if (data.containsKey('type') && data['type'] != null) {
|
||||||
|
stringifiedType = data['type'].toString();
|
||||||
|
log('Found type in data: $stringifiedType');
|
||||||
|
} else if (data.containsKey('notification_class_name') && data['notification_class_name'] != null) {
|
||||||
|
stringifiedType = data['notification_class_name'].toString();
|
||||||
|
log('Found notification_class_name in data: $stringifiedType');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если тип не найден, используем unknown
|
||||||
|
final type = stringifiedType != null ? NotificationType.getTypeFromString(stringifiedType) : NotificationType.unknown;
|
||||||
|
log('Final type: $type, stringifiedType: $stringifiedType');
|
||||||
|
|
||||||
final notification = _parseNotification(json, type);
|
final notification = _parseNotification(json, type);
|
||||||
_notifications.insert(0, notification);
|
_notifications.insert(0, notification);
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,72 @@ class NotificationWidget extends StatelessWidget {
|
|||||||
CommentReplied() => _CommentRepliedWidget(notification: notification as CommentReplied),
|
CommentReplied() => _CommentRepliedWidget(notification: notification as CommentReplied),
|
||||||
ScoreAdded() => _ScoreAddedWidget(notification: notification as ScoreAdded),
|
ScoreAdded() => _ScoreAddedWidget(notification: notification as ScoreAdded),
|
||||||
EntityCommented() => _EntityCommentedWidget(notification: notification as EntityCommented),
|
EntityCommented() => _EntityCommentedWidget(notification: notification as EntityCommented),
|
||||||
|
// Новые типы уведомлений
|
||||||
|
PlanSubscriptionExpiredNotification() => _PlanSubscriptionExpiredWidget(notification: notification as PlanSubscriptionExpiredNotification),
|
||||||
|
PlanSubscriptionRenewedNotification() => _PlanSubscriptionRenewedWidget(notification: notification as PlanSubscriptionRenewedNotification),
|
||||||
|
ConsultantAssignedNotification() => _ConsultantAssignedWidget(notification: notification as ConsultantAssignedNotification),
|
||||||
|
ConsultantUnassignedNotification() => _ConsultantUnassignedWidget(notification: notification as ConsultantUnassignedNotification),
|
||||||
|
PaymentFailedNotification() => _PaymentFailedWidget(notification: notification as PaymentFailedNotification),
|
||||||
|
PaymentSuccessfulNotification() => _PaymentSuccessfulWidget(notification: notification as PaymentSuccessfulNotification),
|
||||||
|
GoalAchievedNotification() => _GoalAchievedWidget(notification: notification as GoalAchievedNotification),
|
||||||
|
WorkoutReminderNotification() => _WorkoutReminderWidget(notification: notification as WorkoutReminderNotification),
|
||||||
|
MealReminderNotification() => _MealReminderWidget(notification: notification as MealReminderNotification),
|
||||||
|
// New notification types
|
||||||
|
DietCancelledNotification() => _DietCancelledWidget(notification: notification as DietCancelledNotification),
|
||||||
|
DietStartedNotification() => _DietStartedWidget(notification: notification as DietStartedNotification),
|
||||||
|
DietExpiredNotification() => _DietExpiredWidget(notification: notification as DietExpiredNotification),
|
||||||
|
DietWillStartedInNotification() => _DietWillStartedInWidget(notification: notification as DietWillStartedInNotification),
|
||||||
|
DietCanNotBeStartedNotification() => _DietCanNotBeStartedWidget(notification: notification as DietCanNotBeStartedNotification),
|
||||||
|
PersonalDietCreationNeededNotification() => _PersonalDietCreationNeededWidget(notification: notification as PersonalDietCreationNeededNotification),
|
||||||
|
ChosenPlanClientLastDayFinishedNotification() => _ChosenPlanClientLastDayFinishedWidget(
|
||||||
|
notification: notification as ChosenPlanClientLastDayFinishedNotification,
|
||||||
|
),
|
||||||
|
ChosenPlanClientLastDayStartedNotification() => _ChosenPlanClientLastDayStartedWidget(
|
||||||
|
notification: notification as ChosenPlanClientLastDayStartedNotification,
|
||||||
|
),
|
||||||
|
StayOnTrackToAuthorNotification() => _StayOnTrackToAuthorWidget(notification: notification as StayOnTrackToAuthorNotification),
|
||||||
|
ClientStartPlanSubscriptionToSubscriberNotification() => _ClientStartPlanSubscriptionToSubscriberWidget(
|
||||||
|
notification: notification as ClientStartPlanSubscriptionToSubscriberNotification,
|
||||||
|
),
|
||||||
|
ConsultantConfirmPlanSubscriptionToSubscriberNotification() => _ConsultantConfirmPlanSubscriptionToSubscriberWidget(
|
||||||
|
notification: notification as ConsultantConfirmPlanSubscriptionToSubscriberNotification,
|
||||||
|
),
|
||||||
|
ClientPaidPlanSubscriptionToAuthorNotification() => _ClientPaidPlanSubscriptionToAuthorWidget(
|
||||||
|
notification: notification as ClientPaidPlanSubscriptionToAuthorNotification,
|
||||||
|
),
|
||||||
|
ConsultantLeftDiaryCommentNotification() => _ConsultantLeftDiaryCommentWidget(notification: notification as ConsultantLeftDiaryCommentNotification),
|
||||||
|
ConsultantConfirmPlanSubscriptionToAuthorNotification() => _ConsultantConfirmPlanSubscriptionToAuthorWidget(
|
||||||
|
notification: notification as ConsultantConfirmPlanSubscriptionToAuthorNotification,
|
||||||
|
),
|
||||||
|
ClientPaidPlanSubscriptionToSubscriberNotification() => _ClientPaidPlanSubscriptionToSubscriberWidget(
|
||||||
|
notification: notification as ClientPaidPlanSubscriptionToSubscriberNotification,
|
||||||
|
),
|
||||||
|
ClientStartPlanSubscriptionToAuthorNotification() => _ClientStartPlanSubscriptionToAuthorWidget(
|
||||||
|
notification: notification as ClientStartPlanSubscriptionToAuthorNotification,
|
||||||
|
),
|
||||||
|
ClientCancelPlanSubscriptionToAuthorNotification() => _ClientCancelPlanSubscriptionToAuthorWidget(
|
||||||
|
notification: notification as ClientCancelPlanSubscriptionToAuthorNotification,
|
||||||
|
),
|
||||||
|
ClientCancelPlanSubscriptionToSubscriberNotification() => _ClientCancelPlanSubscriptionToSubscriberWidget(
|
||||||
|
notification: notification as ClientCancelPlanSubscriptionToSubscriberNotification,
|
||||||
|
),
|
||||||
|
UserDailyReportCompletedNotification() => _UserDailyReportCompletedWidget(notification: notification as UserDailyReportCompletedNotification),
|
||||||
|
StayOnTrackToCustomerNotification() => _StayOnTrackToCustomerWidget(notification: notification as StayOnTrackToCustomerNotification),
|
||||||
|
PhoneChangedNotification() => _PhoneChangedWidget(notification: notification as PhoneChangedNotification),
|
||||||
|
SubscriptionNotification() => _SubscriptionWidget(notification: notification as SubscriptionNotification),
|
||||||
|
BadgeAwardedNotification() => _BadgeAwardedWidget(notification: notification as BadgeAwardedNotification),
|
||||||
|
BadgeRevokedNotification() => _BadgeRevokedWidget(notification: notification as BadgeRevokedNotification),
|
||||||
|
ConsultantRequestStartedNotification() => _ConsultantRequestStartedWidget(notification: notification as ConsultantRequestStartedNotification),
|
||||||
|
ClientCloseComplaintSuccessNotification() => _ClientCloseComplaintSuccessWidget(notification: notification as ClientCloseComplaintSuccessNotification),
|
||||||
|
ClientRefundSuccessNotification() => _ClientRefundSuccessWidget(notification: notification as ClientRefundSuccessNotification),
|
||||||
|
ConsultantRefundSuccessNotification() => _ConsultantRefundSuccessWidget(notification: notification as ConsultantRefundSuccessNotification),
|
||||||
|
ClientRejectSuccessNotification() => _ClientRejectSuccessWidget(notification: notification as ClientRejectSuccessNotification),
|
||||||
|
ConsultantRejectSuccessNotification() => _ConsultantRejectSuccessWidget(notification: notification as ConsultantRejectSuccessNotification),
|
||||||
|
ScoreSubstractedNotification() => _ScoreSubstractedWidget(notification: notification as ScoreSubstractedNotification),
|
||||||
|
ProductStatusUpdatedForAuthorNotification() => _ProductStatusUpdatedForAuthorWidget(
|
||||||
|
notification: notification as ProductStatusUpdatedForAuthorNotification,
|
||||||
|
),
|
||||||
|
DiaryReminderForUsersNotification() => _DiaryReminderForUsersWidget(notification: notification as DiaryReminderForUsersNotification),
|
||||||
UnknownNotification() => _UnknownNotificationWidget(notification: notification as UnknownNotification),
|
UnknownNotification() => _UnknownNotificationWidget(notification: notification as UnknownNotification),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -443,3 +509,547 @@ class _UnknownNotificationWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Новые виджеты для дополнительных типов уведомлений
|
||||||
|
class _PlanSubscriptionExpiredWidget extends StatelessWidget {
|
||||||
|
const _PlanSubscriptionExpiredWidget({required this.notification});
|
||||||
|
final PlanSubscriptionExpiredNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Подписка на план "${notification.planName}" истекла',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlanSubscriptionRenewedWidget extends StatelessWidget {
|
||||||
|
const _PlanSubscriptionRenewedWidget({required this.notification});
|
||||||
|
final PlanSubscriptionRenewedNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Подписка на план "${notification.planName}" продлена до ${notification.renewalDate}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConsultantAssignedWidget extends StatelessWidget {
|
||||||
|
const _ConsultantAssignedWidget({required this.notification});
|
||||||
|
final ConsultantAssignedNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'К плану "${notification.planName}" назначен консультант ${notification.consultantName}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConsultantUnassignedWidget extends StatelessWidget {
|
||||||
|
const _ConsultantUnassignedWidget({required this.notification});
|
||||||
|
final ConsultantUnassignedNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Консультант ${notification.consultantName} отстранен от плана "${notification.planName}"',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PaymentFailedWidget extends StatelessWidget {
|
||||||
|
const _PaymentFailedWidget({required this.notification});
|
||||||
|
final PaymentFailedNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Ошибка оплаты плана "${notification.planName}": ${notification.errorMessage}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PaymentSuccessfulWidget extends StatelessWidget {
|
||||||
|
const _PaymentSuccessfulWidget({required this.notification});
|
||||||
|
final PaymentSuccessfulNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Успешная оплата плана "${notification.planName}" на сумму ${notification.amount} ${notification.currency}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GoalAchievedWidget extends StatelessWidget {
|
||||||
|
const _GoalAchievedWidget({required this.notification});
|
||||||
|
final GoalAchievedNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Достигнута цель "${notification.goalName}": ${notification.achievedValue}/${notification.targetValue}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WorkoutReminderWidget extends StatelessWidget {
|
||||||
|
const _WorkoutReminderWidget({required this.notification});
|
||||||
|
final WorkoutReminderNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Напоминание о тренировке "${notification.workoutName}" (${notification.duration} мин)',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MealReminderWidget extends StatelessWidget {
|
||||||
|
const _MealReminderWidget({required this.notification});
|
||||||
|
final MealReminderNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Напоминание о приеме пищи "${notification.mealName}" (${notification.calories} ккал)',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New notification widget classes
|
||||||
|
class _DietCancelledWidget extends StatelessWidget {
|
||||||
|
const _DietCancelledWidget({required this.notification});
|
||||||
|
final DietCancelledNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Диета "${notification.dietName}" отменена',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DietStartedWidget extends StatelessWidget {
|
||||||
|
const _DietStartedWidget({required this.notification});
|
||||||
|
final DietStartedNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Диета "${notification.dietName}" началась',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DietExpiredWidget extends StatelessWidget {
|
||||||
|
const _DietExpiredWidget({required this.notification});
|
||||||
|
final DietExpiredNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Диета "${notification.dietName}" истекла',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DietWillStartedInWidget extends StatelessWidget {
|
||||||
|
const _DietWillStartedInWidget({required this.notification});
|
||||||
|
final DietWillStartedInNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Диета "${notification.dietName}" начнется ${notification.startDate}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DietCanNotBeStartedWidget extends StatelessWidget {
|
||||||
|
const _DietCanNotBeStartedWidget({required this.notification});
|
||||||
|
final DietCanNotBeStartedNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Диета "${notification.dietName}" не может быть начата: ${notification.reason}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PersonalDietCreationNeededWidget extends StatelessWidget {
|
||||||
|
const _PersonalDietCreationNeededWidget({required this.notification});
|
||||||
|
final PersonalDietCreationNeededNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Требуется создание персональной диеты для клиента ${notification.clientName} по плану "${notification.planName}"',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChosenPlanClientLastDayFinishedWidget extends StatelessWidget {
|
||||||
|
const _ChosenPlanClientLastDayFinishedWidget({required this.notification});
|
||||||
|
final ChosenPlanClientLastDayFinishedNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Последний день плана "${notification.planName}" завершен',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChosenPlanClientLastDayStartedWidget extends StatelessWidget {
|
||||||
|
const _ChosenPlanClientLastDayStartedWidget({required this.notification});
|
||||||
|
final ChosenPlanClientLastDayStartedNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Последний день плана "${notification.planName}" начался',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StayOnTrackToAuthorWidget extends StatelessWidget {
|
||||||
|
const _StayOnTrackToAuthorWidget({required this.notification});
|
||||||
|
final StayOnTrackToAuthorNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Клиент ${notification.clientName} остается на плане "${notification.planName}"',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ClientStartPlanSubscriptionToSubscriberWidget extends StatelessWidget {
|
||||||
|
const _ClientStartPlanSubscriptionToSubscriberWidget({required this.notification});
|
||||||
|
final ClientStartPlanSubscriptionToSubscriberNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Подписка на план "${notification.planName}" началась',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConsultantConfirmPlanSubscriptionToSubscriberWidget extends StatelessWidget {
|
||||||
|
const _ConsultantConfirmPlanSubscriptionToSubscriberWidget({required this.notification});
|
||||||
|
final ConsultantConfirmPlanSubscriptionToSubscriberNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Консультант ${notification.consultantName} подтвердил подписку на план "${notification.planName}"',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ClientPaidPlanSubscriptionToAuthorWidget extends StatelessWidget {
|
||||||
|
const _ClientPaidPlanSubscriptionToAuthorWidget({required this.notification});
|
||||||
|
final ClientPaidPlanSubscriptionToAuthorNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Клиент ${notification.clientName} оплатил подписку на план "${notification.planName}" на сумму ${notification.amount}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConsultantLeftDiaryCommentWidget extends StatelessWidget {
|
||||||
|
const _ConsultantLeftDiaryCommentWidget({required this.notification});
|
||||||
|
final ConsultantLeftDiaryCommentNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Консультант ${notification.consultantName} оставил комментарий к дневнику по плану "${notification.planName}"',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConsultantConfirmPlanSubscriptionToAuthorWidget extends StatelessWidget {
|
||||||
|
const _ConsultantConfirmPlanSubscriptionToAuthorWidget({required this.notification});
|
||||||
|
final ConsultantConfirmPlanSubscriptionToAuthorNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Консультант ${notification.consultantName} подтвердил подписку на план "${notification.planName}"',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ClientPaidPlanSubscriptionToSubscriberWidget extends StatelessWidget {
|
||||||
|
const _ClientPaidPlanSubscriptionToSubscriberWidget({required this.notification});
|
||||||
|
final ClientPaidPlanSubscriptionToSubscriberNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Оплачена подписка на план "${notification.planName}" на сумму ${notification.amount}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ClientStartPlanSubscriptionToAuthorWidget extends StatelessWidget {
|
||||||
|
const _ClientStartPlanSubscriptionToAuthorWidget({required this.notification});
|
||||||
|
final ClientStartPlanSubscriptionToAuthorNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Клиент ${notification.clientName} начал подписку на план "${notification.planName}"',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ClientCancelPlanSubscriptionToAuthorWidget extends StatelessWidget {
|
||||||
|
const _ClientCancelPlanSubscriptionToAuthorWidget({required this.notification});
|
||||||
|
final ClientCancelPlanSubscriptionToAuthorNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Клиент ${notification.clientName} отменил подписку на план "${notification.planName}"',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ClientCancelPlanSubscriptionToSubscriberWidget extends StatelessWidget {
|
||||||
|
const _ClientCancelPlanSubscriptionToSubscriberWidget({required this.notification});
|
||||||
|
final ClientCancelPlanSubscriptionToSubscriberNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Подписка на план "${notification.planName}" отменена',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UserDailyReportCompletedWidget extends StatelessWidget {
|
||||||
|
const _UserDailyReportCompletedWidget({required this.notification});
|
||||||
|
final UserDailyReportCompletedNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Клиент ${notification.clientName} завершил ежедневный отчет по плану "${notification.planName}"',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StayOnTrackToCustomerWidget extends StatelessWidget {
|
||||||
|
const _StayOnTrackToCustomerWidget({required this.notification});
|
||||||
|
final StayOnTrackToCustomerNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Консультант ${notification.consultantName} остается на плане "${notification.planName}"',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PhoneChangedWidget extends StatelessWidget {
|
||||||
|
const _PhoneChangedWidget({required this.notification});
|
||||||
|
final PhoneChangedNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Номер телефона изменен с ${notification.oldPhone} на ${notification.newPhone}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SubscriptionWidget extends StatelessWidget {
|
||||||
|
const _SubscriptionWidget({required this.notification});
|
||||||
|
final SubscriptionNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Подписка ${notification.subscriptionType} на план "${notification.planName}"',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BadgeAwardedWidget extends StatelessWidget {
|
||||||
|
const _BadgeAwardedWidget({required this.notification});
|
||||||
|
final BadgeAwardedNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Получен значок "${notification.badgeName}": ${notification.badgeDescription}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BadgeRevokedWidget extends StatelessWidget {
|
||||||
|
const _BadgeRevokedWidget({required this.notification});
|
||||||
|
final BadgeRevokedNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Отозван значок "${notification.badgeName}": ${notification.reason}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConsultantRequestStartedWidget extends StatelessWidget {
|
||||||
|
const _ConsultantRequestStartedWidget({required this.notification});
|
||||||
|
final ConsultantRequestStartedNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Запрос к консультанту ${notification.consultantName} начат',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ClientCloseComplaintSuccessWidget extends StatelessWidget {
|
||||||
|
const _ClientCloseComplaintSuccessWidget({required this.notification});
|
||||||
|
final ClientCloseComplaintSuccessNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Жалоба на ${notification.complaintType} "${notification.entityName}" успешно закрыта',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ClientRefundSuccessWidget extends StatelessWidget {
|
||||||
|
const _ClientRefundSuccessWidget({required this.notification});
|
||||||
|
final ClientRefundSuccessNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Возврат средств по плану "${notification.planName}" на сумму ${notification.amount}: ${notification.reason}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConsultantRefundSuccessWidget extends StatelessWidget {
|
||||||
|
const _ConsultantRefundSuccessWidget({required this.notification});
|
||||||
|
final ConsultantRefundSuccessNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Возврат средств клиенту ${notification.clientName} по плану "${notification.planName}" на сумму ${notification.amount}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ClientRejectSuccessWidget extends StatelessWidget {
|
||||||
|
const _ClientRejectSuccessWidget({required this.notification});
|
||||||
|
final ClientRejectSuccessNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Отклонение по плану "${notification.planName}": ${notification.reason}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConsultantRejectSuccessWidget extends StatelessWidget {
|
||||||
|
const _ConsultantRejectSuccessWidget({required this.notification});
|
||||||
|
final ConsultantRejectSuccessNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Отклонение клиентом ${notification.clientName} по плану "${notification.planName}": ${notification.reason}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScoreSubstractedWidget extends StatelessWidget {
|
||||||
|
const _ScoreSubstractedWidget({required this.notification});
|
||||||
|
final ScoreSubstractedNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Вычтено ${notification.scoreValue} баллов за ${notification.scorableName}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProductStatusUpdatedForAuthorWidget extends StatelessWidget {
|
||||||
|
const _ProductStatusUpdatedForAuthorWidget({required this.notification});
|
||||||
|
final ProductStatusUpdatedForAuthorNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(
|
||||||
|
isRead: notification.isRead,
|
||||||
|
notificationsText: 'Статус продукта "${notification.productName}" изменен с ${notification.oldStatus} на ${notification.newStatus}',
|
||||||
|
createdAt: notification.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DiaryReminderForUsersWidget extends StatelessWidget {
|
||||||
|
const _DiaryReminderForUsersWidget({required this.notification});
|
||||||
|
final DiaryReminderForUsersNotification notification;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Напоминание о заполнении дневника', createdAt: notification.createdAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,10 +13,30 @@ class NotificationsService {
|
|||||||
|
|
||||||
final notifications = data.map((notificationJson) {
|
final notifications = data.map((notificationJson) {
|
||||||
try {
|
try {
|
||||||
final stringifiedType = notificationJson['data']['notification_class_name'];
|
// Улучшенная логика определения типа уведомления
|
||||||
|
String? stringifiedType;
|
||||||
|
|
||||||
|
// Сначала проверяем поле 'type' в корне JSON
|
||||||
|
if (notificationJson.containsKey('type') && notificationJson['type'] != null) {
|
||||||
|
stringifiedType = notificationJson['type'].toString();
|
||||||
|
}
|
||||||
|
// Затем проверяем поле 'notification_class_name' в корне JSON
|
||||||
|
else if (notificationJson.containsKey('notification_class_name') && notificationJson['notification_class_name'] != null) {
|
||||||
|
stringifiedType = notificationJson['notification_class_name'].toString();
|
||||||
|
}
|
||||||
|
// Если есть поле 'data', проверяем внутри него
|
||||||
|
else if (notificationJson.containsKey('data') && notificationJson['data'] is Map<String, dynamic>) {
|
||||||
|
final data = notificationJson['data'] as Map<String, dynamic>;
|
||||||
|
if (data.containsKey('type') && data['type'] != null) {
|
||||||
|
stringifiedType = data['type'].toString();
|
||||||
|
} else if (data.containsKey('notification_class_name') && data['notification_class_name'] != null) {
|
||||||
|
stringifiedType = data['notification_class_name'].toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final readDate = notificationJson['read_at'];
|
final readDate = notificationJson['read_at'];
|
||||||
final createdAt = notificationJson['created_at'];
|
final createdAt = notificationJson['created_at'];
|
||||||
final type = NotificationType.getTypeFromString(stringifiedType);
|
final type = stringifiedType != null ? NotificationType.getTypeFromString(stringifiedType) : NotificationType.unknown;
|
||||||
|
|
||||||
return switch (type) {
|
return switch (type) {
|
||||||
NotificationType.planSubscriptionStartedToAuthor => PlanSubscriptionStartedToAuthorNotification.fromJson(
|
NotificationType.planSubscriptionStartedToAuthor => PlanSubscriptionStartedToAuthorNotification.fromJson(
|
||||||
@@ -119,6 +139,274 @@ class NotificationsService {
|
|||||||
NotificationType.commentReplied => CommentReplied.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt),
|
NotificationType.commentReplied => CommentReplied.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt),
|
||||||
NotificationType.scoreAdded => ScoreAdded.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt),
|
NotificationType.scoreAdded => ScoreAdded.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt),
|
||||||
NotificationType.entityCommented => EntityCommented.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt),
|
NotificationType.entityCommented => EntityCommented.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt),
|
||||||
|
// Новые типы уведомлений
|
||||||
|
NotificationType.planSubscriptionExpired => PlanSubscriptionExpiredNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.planSubscriptionRenewed => PlanSubscriptionRenewedNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.consultantAssigned => ConsultantAssignedNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.consultantUnassigned => ConsultantUnassignedNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.paymentFailed => PaymentFailedNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.paymentSuccessful => PaymentSuccessfulNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.goalAchieved => GoalAchievedNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.workoutReminder => WorkoutReminderNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.mealReminder => MealReminderNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
// New notification types
|
||||||
|
NotificationType.dietCancelled => DietCancelledNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.dietStarted => DietStartedNotification.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt),
|
||||||
|
NotificationType.dietExpired => DietExpiredNotification.fromJson(notificationJson['data'], type, isRead: readDate != null, createdAt: createdAt),
|
||||||
|
NotificationType.dietWillStartedIn => DietWillStartedInNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.dietCanNotBeStarted => DietCanNotBeStartedNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.personalDietCreationNeeded => PersonalDietCreationNeededNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.chosenPlanClientLastDayFinished => ChosenPlanClientLastDayFinishedNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.chosenPlanClientLastDayStarted => ChosenPlanClientLastDayStartedNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.stayOnTrackToAuthor => StayOnTrackToAuthorNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.chosenPlanStartedToClient => ChosenPlanStartedToClientNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientStartPlanSubscriptionToSubscriber => ClientStartPlanSubscriptionToSubscriberNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.consultantConfirmPlanSubscriptionToSubscriber => ConsultantConfirmPlanSubscriptionToSubscriberNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientPaidPlanSubscriptionToAuthor => ClientPaidPlanSubscriptionToAuthorNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.chosenPlanTomorrowReminderToClient => ChosenPlanTomorrowReminderToClientNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.chosenPlanTomorrowReminderToConsultant => ChosenPlanTomorrowReminderToConsultantNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.chosenPlanStartedToConsultant => ChosenPlanStartedToConsultantNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.consultantLeftDiaryComment => ConsultantLeftDiaryCommentNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.consultantConfirmPlanSubscriptionToAuthor => ConsultantConfirmPlanSubscriptionToAuthorNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientPaidPlanSubscriptionToSubscriber => ClientPaidPlanSubscriptionToSubscriberNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientStartPlanSubscriptionToAuthor => ClientStartPlanSubscriptionToAuthorNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientCancelPlanSubscriptionToAuthor => ClientCancelPlanSubscriptionToAuthorNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientCancelPlanSubscriptionToSubscriber => ClientCancelPlanSubscriptionToSubscriberNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.userDailyReportCompleted => UserDailyReportCompletedNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.stayOnTrackToCustomer => StayOnTrackToCustomerNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.phoneChanged => PhoneChangedNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.subscription => SubscriptionNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.badgeAwarded => BadgeAwardedNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.badgeRevoked => BadgeRevokedNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.consultantRequestStarted => ConsultantRequestStartedNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientCloseComplaintSuccess => ClientCloseComplaintSuccessNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientRefundSuccess => ClientRefundSuccessNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.consultantRefundSuccess => ConsultantRefundSuccessNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.clientRejectSuccess => ClientRejectSuccessNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.consultantRejectSuccess => ConsultantRejectSuccessNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.scoreSubstracted => ScoreSubstractedNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.productStatusUpdatedForAuthor => ProductStatusUpdatedForAuthorNotification.fromJson(
|
||||||
|
notificationJson['data'],
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
|
NotificationType.diaryReminderForUsers => DiaryReminderForUsersNotification.fromJson(
|
||||||
|
notificationJson,
|
||||||
|
type,
|
||||||
|
isRead: readDate != null,
|
||||||
|
createdAt: createdAt,
|
||||||
|
),
|
||||||
NotificationType.unknown => UnknownNotification(type: type, createdAt: parseDate(createdAt)),
|
NotificationType.unknown => UnknownNotification(type: type, createdAt: parseDate(createdAt)),
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ abstract class ProfileModel with _$ProfileModel {
|
|||||||
@JsonKey(name: "updated_at") DateTime? updatedAt,
|
@JsonKey(name: "updated_at") DateTime? updatedAt,
|
||||||
@JsonKey(name: "is_public_profile", fromJson: parseBool) required bool isPublicProfile,
|
@JsonKey(name: "is_public_profile", fromJson: parseBool) required bool isPublicProfile,
|
||||||
@JsonKey(name: "is_blocked_by_me", fromJson: parseBool) required bool isBlockedByMe,
|
@JsonKey(name: "is_blocked_by_me", fromJson: parseBool) required bool isBlockedByMe,
|
||||||
|
@JsonKey(name: "is_blocked_by_user", fromJson: parseBool, defaultValue: false) required bool isBlockedByUser,
|
||||||
@JsonKey(name: 'is_entity_subscribed_by_me') required bool isEntitySubscribedByMe,
|
@JsonKey(name: 'is_entity_subscribed_by_me') required bool isEntitySubscribedByMe,
|
||||||
@JsonKey(name: "slug") required String slug,
|
@JsonKey(name: "slug") required String slug,
|
||||||
@JsonKey(name: "profile_photo_url") required String profilePhotoUrl,
|
@JsonKey(name: "profile_photo_url") required String profilePhotoUrl,
|
||||||
|
|||||||
@@ -93,6 +93,31 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
}
|
}
|
||||||
isFirstFetch = false;
|
isFirstFetch = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Проверяем, заблокировал ли пользователь меня
|
||||||
|
if (state.profile.isBlockedByUser) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(),
|
||||||
|
body: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(32.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.block, size: 64, color: Colors.grey[400]),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Пользователь ограничил доступ к своей странице',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, color: Colors.grey[600]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return widget.author == null || widget.author!.authorIsMe ? _myProfileScreen(state) : _profileScreen(state);
|
return widget.author == null || widget.author!.authorIsMe ? _myProfileScreen(state) : _profileScreen(state);
|
||||||
} else if (state is ProfileStateError) {
|
} else if (state is ProfileStateError) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -174,10 +199,18 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
),
|
),
|
||||||
_subscriptionsAndSubscribers(state),
|
_subscriptionsAndSubscribers(state),
|
||||||
if (state.profile.badges.isNotEmpty) Padding(padding: const EdgeInsets.only(bottom: 16), child: _badges(state)),
|
if (state.profile.badges.isNotEmpty) Padding(padding: const EdgeInsets.only(bottom: 16), child: _badges(state)),
|
||||||
if (state.profile.roles.contains('consultant') && state.consultantInfo != null) _socialLinks(state),
|
if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe) _socialLinks(state),
|
||||||
if (state.profile.roles.contains('consultant') && state.consultantInfo != null) _education(state),
|
if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe) _education(state),
|
||||||
if (state.profile.roles.contains('consultant') && state.consultantInfo != null) _diplomasAndCertificates(state),
|
if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe) _diplomasAndCertificates(state),
|
||||||
if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !hidePlans) _activePlans(state),
|
if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !hidePlans && !state.profile.isBlockedByMe) _activePlans(state),
|
||||||
|
if (state.blogPosts.isNotEmpty)
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(left: 8, top: 16, bottom: 8),
|
||||||
|
child: Text(
|
||||||
|
'Блог',
|
||||||
|
style: TextStyle(color: Color(0xFF323F4B), fontSize: 24, fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
),
|
||||||
_blogPosts(state),
|
_blogPosts(state),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -244,9 +277,9 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
_subscriptionsAndSubscribers(state),
|
_subscriptionsAndSubscribers(state),
|
||||||
if (state.profile.badges.isNotEmpty) Padding(padding: const EdgeInsets.only(bottom: 16), child: _badges(state)),
|
if (state.profile.badges.isNotEmpty) Padding(padding: const EdgeInsets.only(bottom: 16), child: _badges(state)),
|
||||||
if (state.profile.roles.contains('consultant')) _socialLinks(state),
|
if (state.profile.roles.contains('consultant')) _socialLinks(state),
|
||||||
|
Padding(padding: const EdgeInsets.only(bottom: 16), child: _charts()),
|
||||||
if (state.profile.roles.contains('consultant')) _education(state),
|
if (state.profile.roles.contains('consultant')) _education(state),
|
||||||
if (state.profile.roles.contains('consultant')) _diplomasAndCertificates(state),
|
if (state.profile.roles.contains('consultant')) _diplomasAndCertificates(state),
|
||||||
Padding(padding: const EdgeInsets.only(bottom: 16), child: _charts()),
|
|
||||||
if (state.profile.roles.contains('consultant') && !hidePlans) _activePlans(state),
|
if (state.profile.roles.contains('consultant') && !hidePlans) _activePlans(state),
|
||||||
_newBlogPostButton(),
|
_newBlogPostButton(),
|
||||||
_blogPosts(state),
|
_blogPosts(state),
|
||||||
@@ -479,6 +512,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
children: [
|
children: [
|
||||||
ListView.separated(
|
ListView.separated(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemBuilder: (context, index) => _educationBlock(
|
itemBuilder: (context, index) => _educationBlock(
|
||||||
state.consultantInfo!.educationModel.completedCourses![index].values.first.title,
|
state.consultantInfo!.educationModel.completedCourses![index].values.first.title,
|
||||||
@@ -733,7 +767,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||||||
|
|
||||||
ListView _blogPosts(ProfileStateLoaded state) {
|
ListView _blogPosts(ProfileStateLoaded state) {
|
||||||
return ListView.separated(
|
return ListView.separated(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
padding: const EdgeInsets.only(bottom: 16),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemBuilder: (context, index) => ProfileBlogPostWidget(index: index, profileCubit: profileCubit),
|
itemBuilder: (context, index) => ProfileBlogPostWidget(index: index, profileCubit: profileCubit),
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ import 'package:eatvio_app/features/profile/services/profile_service.dart';
|
|||||||
class ProfileRepository {
|
class ProfileRepository {
|
||||||
final ProfileService profileService;
|
final ProfileService profileService;
|
||||||
|
|
||||||
ProfileRepository({
|
ProfileRepository({required this.profileService});
|
||||||
required this.profileService,
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<ProfileModel> getProfileBySlug(String slug) async {
|
Future<ProfileModel> getProfileBySlug(String slug) async {
|
||||||
try {
|
try {
|
||||||
@@ -48,11 +46,7 @@ class ProfileRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addCircumference({
|
Future<void> addCircumference({required int chestCircumference, required int waistCircumference, required int hipCircumference}) async {
|
||||||
required int chestCircumference,
|
|
||||||
required int waistCircumference,
|
|
||||||
required int hipCircumference,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return (await profileService.addCircumference(
|
return (await profileService.addCircumference(
|
||||||
chestCircumference: chestCircumference,
|
chestCircumference: chestCircumference,
|
||||||
@@ -64,9 +58,7 @@ class ProfileRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addWeight({
|
Future<void> addWeight({required double weight}) async {
|
||||||
required double weight,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return (await profileService.addWeight(weight: weight));
|
return (await profileService.addWeight(weight: weight));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -74,52 +66,32 @@ class ProfileRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<FeedBlogPostModel>> getBlogPostsByAuthorSlug({
|
Future<List<FeedBlogPostModel>> getBlogPostsByAuthorSlug({required String slug}) async {
|
||||||
required String slug,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
return (await profileService.getBlogPostByAuthorSlug(
|
return (await profileService.getBlogPostByAuthorSlug(slug: slug));
|
||||||
slug: slug,
|
|
||||||
));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<WeightModel>> getWeight({
|
Future<List<WeightModel>> getWeight({required String period, String? slug}) async {
|
||||||
required String period,
|
|
||||||
String? slug,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
if (slug != null) {
|
if (slug != null) {
|
||||||
return (await profileService.getWeightBySlug(
|
return (await profileService.getWeightBySlug(period: period, slug: slug));
|
||||||
period: period,
|
|
||||||
slug: slug,
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
return (await profileService.getWeight(
|
return (await profileService.getWeight(period: period));
|
||||||
period: period,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<CircumferenceModel>> getCircumferences({
|
Future<List<CircumferenceModel>> getCircumferences({required String period, String? slug}) async {
|
||||||
required String period,
|
|
||||||
String? slug,
|
|
||||||
}) async {
|
|
||||||
try {
|
try {
|
||||||
if (slug != null) {
|
if (slug != null) {
|
||||||
return (await profileService.getCircumferencesBySlug(
|
return (await profileService.getCircumferencesBySlug(period: period, slug: slug));
|
||||||
period: period,
|
|
||||||
slug: slug,
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
return (await profileService.getCircumferences(
|
return (await profileService.getCircumferences(period: period));
|
||||||
period: period,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
@@ -184,15 +156,20 @@ class ProfileRepository {
|
|||||||
|
|
||||||
Future<String> getPassKey(String phone) async {
|
Future<String> getPassKey(String phone) async {
|
||||||
return (jsonDecode(
|
return (jsonDecode(
|
||||||
(await profileService.getPassKey(json: '{"route_name": "user_detailz.change-phone-verify", "route_params": {"phone": "$phone"}}'))))['key_is']!;
|
(await profileService.getPassKey(json: '{"route_name": "user_detailz.change-phone-verify", "route_params": {"phone": "$phone"}}')),
|
||||||
|
))['key_is']!;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getMyPhone() async {
|
Future<String> getMyPhone() async {
|
||||||
return (jsonDecode(await profileService.getMyPhone())['phone']);
|
return (jsonDecode(await profileService.getMyPhone())['phone']);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendEmailCode() async {
|
Future<void> sendEmailCode({String? newEmail}) async {
|
||||||
return await profileService.sendEmailCode();
|
if (newEmail != null) {
|
||||||
|
return await profileService.sendEmailCode(data: {'new_email': newEmail});
|
||||||
|
} else {
|
||||||
|
return await profileService.sendEmailCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> tryVerifyEmailByCode(String code) async {
|
Future<bool> tryVerifyEmailByCode(String code) async {
|
||||||
|
|||||||
@@ -16,9 +16,7 @@ abstract class ProfileService {
|
|||||||
factory ProfileService(Dio dio, {String baseUrl}) = _ProfileService;
|
factory ProfileService(Dio dio, {String baseUrl}) = _ProfileService;
|
||||||
|
|
||||||
@GET('/api/profile/show/{slug}')
|
@GET('/api/profile/show/{slug}')
|
||||||
Future<ProfileModel> getProfileBySlug({
|
Future<ProfileModel> getProfileBySlug({@Path() required String slug});
|
||||||
@Path() required String slug,
|
|
||||||
});
|
|
||||||
|
|
||||||
@GET('/api/profile/index')
|
@GET('/api/profile/index')
|
||||||
Future<ProfileModel> getMyProfile();
|
Future<ProfileModel> getMyProfile();
|
||||||
@@ -27,9 +25,7 @@ abstract class ProfileService {
|
|||||||
Future<ProfileDetailsModel> getMyProfileDetails();
|
Future<ProfileDetailsModel> getMyProfileDetails();
|
||||||
|
|
||||||
@GET('/api/profile/detailz/{slug}')
|
@GET('/api/profile/detailz/{slug}')
|
||||||
Future<ProfileDetailsModel> getProfileDetailsBySlug({
|
Future<ProfileDetailsModel> getProfileDetailsBySlug({@Path() required String slug});
|
||||||
@Path() required String slug,
|
|
||||||
});
|
|
||||||
|
|
||||||
@POST('/api/profile/addCircumference')
|
@POST('/api/profile/addCircumference')
|
||||||
Future<void> addCircumference({
|
Future<void> addCircumference({
|
||||||
@@ -39,64 +35,38 @@ abstract class ProfileService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@POST('/api/profile/addWeight')
|
@POST('/api/profile/addWeight')
|
||||||
Future<void> addWeight({
|
Future<void> addWeight({@Part() required double weight});
|
||||||
@Part() required double weight,
|
|
||||||
});
|
|
||||||
|
|
||||||
@GET('/api/blogPosts/search_by_author/{slug}')
|
@GET('/api/blogPosts/search_by_author/{slug}')
|
||||||
Future<List<FeedBlogPostModel>> getBlogPostByAuthorSlug({
|
Future<List<FeedBlogPostModel>> getBlogPostByAuthorSlug({@Path() required String slug});
|
||||||
@Path() required String slug,
|
|
||||||
});
|
|
||||||
|
|
||||||
@GET('/api/profile/measure/weight/{period}')
|
@GET('/api/profile/measure/weight/{period}')
|
||||||
Future<List<WeightModel>> getWeight({
|
Future<List<WeightModel>> getWeight({@Path() required String period});
|
||||||
@Path() required String period,
|
|
||||||
});
|
|
||||||
|
|
||||||
@GET('/api/profile/measure/weight/{period}/{slug}')
|
@GET('/api/profile/measure/weight/{period}/{slug}')
|
||||||
Future<List<WeightModel>> getWeightBySlug({
|
Future<List<WeightModel>> getWeightBySlug({@Path() required String period, @Path() required String slug});
|
||||||
@Path() required String period,
|
|
||||||
@Path() required String slug,
|
|
||||||
});
|
|
||||||
|
|
||||||
@GET('/api/profile/measure/circumferences/{period}')
|
@GET('/api/profile/measure/circumferences/{period}')
|
||||||
Future<List<CircumferenceModel>> getCircumferences({
|
Future<List<CircumferenceModel>> getCircumferences({@Path() required String period});
|
||||||
@Path() required String period,
|
|
||||||
});
|
|
||||||
|
|
||||||
@GET('/api/profile/measure/circumferences/{period}/{user}')
|
@GET('/api/profile/measure/circumferences/{period}/{slug}')
|
||||||
Future<List<CircumferenceModel>> getCircumferencesBySlug({
|
Future<List<CircumferenceModel>> getCircumferencesBySlug({@Path() required String period, @Path() required String slug});
|
||||||
@Path() required String period,
|
|
||||||
@Path() required String slug,
|
|
||||||
});
|
|
||||||
|
|
||||||
@GET('/api/profile/{slug}/subscribers')
|
@GET('/api/profile/{slug}/subscribers')
|
||||||
Future<SubPaginationModel> getSubscribers({
|
Future<SubPaginationModel> getSubscribers({@Path() required String slug, @Query('filters[name][\$startsWith]') String? query});
|
||||||
@Path() required String slug,
|
|
||||||
@Query('filters[name][\$startsWith]') String? query,
|
|
||||||
});
|
|
||||||
|
|
||||||
@GET('/api/profile/{slug}/subscriptions')
|
@GET('/api/profile/{slug}/subscriptions')
|
||||||
Future<SubPaginationModel> getSubscriptions({
|
Future<SubPaginationModel> getSubscriptions({@Path() required String slug, @Query('filters[name][\$startsWith]') String? query});
|
||||||
@Path() required String slug,
|
|
||||||
@Query('filters[name][\$startsWith]') String? query,
|
|
||||||
});
|
|
||||||
|
|
||||||
@POST('/api/profile/subscribe/{slug}')
|
@POST('/api/profile/subscribe/{slug}')
|
||||||
Future<void> subscribe({
|
Future<void> subscribe({@Path() required String slug});
|
||||||
@Path() required String slug,
|
|
||||||
});
|
|
||||||
|
|
||||||
@POST('/api/profile/unsubscribe/{slug}')
|
@POST('/api/profile/unsubscribe/{slug}')
|
||||||
Future<void> unsubscribe({
|
Future<void> unsubscribe({@Path() required String slug});
|
||||||
@Path() required String slug,
|
|
||||||
});
|
|
||||||
|
|
||||||
@POST('/api/profile/photo/upload')
|
@POST('/api/profile/photo/upload')
|
||||||
@MultiPart()
|
@MultiPart()
|
||||||
Future<void> uploadPhoto({
|
Future<void> uploadPhoto({@Part(name: 'photo') required File photo});
|
||||||
@Part(name: 'photo') required File photo,
|
|
||||||
});
|
|
||||||
|
|
||||||
@DELETE('/api/profile/photo/delete')
|
@DELETE('/api/profile/photo/delete')
|
||||||
Future<void> deletePhoto();
|
Future<void> deletePhoto();
|
||||||
@@ -105,20 +75,16 @@ abstract class ProfileService {
|
|||||||
Future<ChosenDietsPaginationModel> getChosenDiets();
|
Future<ChosenDietsPaginationModel> getChosenDiets();
|
||||||
|
|
||||||
@POST('/api/profile/make_pass_key')
|
@POST('/api/profile/make_pass_key')
|
||||||
Future<String> getPassKey({
|
Future<String> getPassKey({@Body() required String json});
|
||||||
@Body() required String json,
|
|
||||||
});
|
|
||||||
|
|
||||||
@GET('/api/profile/my_phone')
|
@GET('/api/profile/my_phone')
|
||||||
Future<String> getMyPhone();
|
Future<String> getMyPhone();
|
||||||
|
|
||||||
@POST('/api/profile/send_email_code')
|
@POST('/api/profile/send_email_code')
|
||||||
Future<void> sendEmailCode();
|
Future<void> sendEmailCode({@Body() Map<String, dynamic> data = const {}});
|
||||||
|
|
||||||
@POST('/api/profile/verificate_email_code')
|
@POST('/api/profile/verificate_email_code')
|
||||||
Future<void> tryVerifyEmailByCode({
|
Future<void> tryVerifyEmailByCode({@Body() required String code});
|
||||||
@Body() required String code,
|
|
||||||
});
|
|
||||||
|
|
||||||
@DELETE('/api/profile/delete_my_account')
|
@DELETE('/api/profile/delete_my_account')
|
||||||
Future<void> deleteAccount();
|
Future<void> deleteAccount();
|
||||||
|
|||||||
@@ -8,62 +8,61 @@ import 'package:eatvio_app/core/token_storage.dart';
|
|||||||
import 'package:eatvio_app/features/registration/models/user_registration_model.dart';
|
import 'package:eatvio_app/features/registration/models/user_registration_model.dart';
|
||||||
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_state.dart';
|
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_state.dart';
|
||||||
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
|
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/registration/services/registration_analytics_service.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
class RegistrationCubit extends Cubit<RegistrationState> {
|
class RegistrationCubit extends Cubit<RegistrationState> {
|
||||||
final RegistrationRepository registrationRepository;
|
final RegistrationRepository registrationRepository;
|
||||||
|
|
||||||
RegistrationCubit({
|
RegistrationCubit({required this.registrationRepository})
|
||||||
required this.registrationRepository,
|
: super(
|
||||||
}) : super(
|
const RegistrationState(
|
||||||
const RegistrationState(
|
sheet: UserRegistrationSheet(
|
||||||
sheet: UserRegistrationSheet(
|
firstName: '',
|
||||||
firstName: '',
|
lastName: '',
|
||||||
lastName: '',
|
email: '',
|
||||||
email: '',
|
password: '',
|
||||||
password: '',
|
passwordConfirmation: '',
|
||||||
passwordConfirmation: '',
|
birthdate: '',
|
||||||
birthdate: '',
|
gender: '',
|
||||||
gender: '',
|
location: '',
|
||||||
location: '',
|
responseBody: '{}',
|
||||||
responseBody: '{}',
|
lang: 'Русский',
|
||||||
lang: 'Русский',
|
weight: 0,
|
||||||
weight: 0,
|
height: 0,
|
||||||
height: 0,
|
chestCircumference: 0,
|
||||||
chestCircumference: 0,
|
waistCircumference: 0,
|
||||||
waistCircumference: 0,
|
hipCircumference: 0,
|
||||||
hipCircumference: 0,
|
bloodPressureUp: 0,
|
||||||
bloodPressureUp: 0,
|
bloodPressureDown: 0,
|
||||||
bloodPressureDown: 0,
|
hasHypo: 0,
|
||||||
hasHypo: 0,
|
hasHyper: 0,
|
||||||
hasHyper: 0,
|
aboutAllergy: '',
|
||||||
aboutAllergy: '',
|
aboutTakingPils: '',
|
||||||
aboutTakingPils: '',
|
aboutSportHobbies: '',
|
||||||
aboutSportHobbies: '',
|
favoriteFoods: '',
|
||||||
favoriteFoods: '',
|
unfavoriteFoods: '',
|
||||||
unfavoriteFoods: '',
|
additionalInfo: '',
|
||||||
additionalInfo: '',
|
phone: '',
|
||||||
phone: '',
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
|
||||||
void updateSheet(UserRegistrationSheet sheet) => emit(RegistrationState(sheet: sheet));
|
void updateSheet(UserRegistrationSheet sheet) => emit(RegistrationState(sheet: sheet));
|
||||||
|
|
||||||
Future<void> tryRegistration() async {
|
Future<void> tryRegistration() async {
|
||||||
try {
|
try {
|
||||||
final token = await registrationRepository.tryRegister(
|
final token = await registrationRepository.tryRegister(
|
||||||
state.sheet.copyWith(
|
state.sheet.copyWith(phone: state.sheet.phone.replaceAll(' ', '').replaceAll('(', '').replaceAll(')', ''), responseBody: '{}'),
|
||||||
phone: state.sheet.phone.replaceAll(' ', '').replaceAll('(', '').replaceAll(')', ''),
|
|
||||||
responseBody: '{}',
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
await TokenStorage.saveTokenInStorage(token);
|
await TokenStorage.saveTokenInStorage(token);
|
||||||
await sl<FirebaseApi>().registerPushToken();
|
await sl<FirebaseApi>().registerPushToken();
|
||||||
|
RegistrationAnalyticsService.reportRegistrationComplete();
|
||||||
AppRouter.navigatorKey.currentContext?.replaceNamed('verifyEmailScreen');
|
AppRouter.navigatorKey.currentContext?.replaceNamed('verifyEmailScreen');
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
log(e.toString(), stackTrace: st);
|
log(e.toString(), stackTrace: st);
|
||||||
|
RegistrationAnalyticsService.reportRegistrationError(e.toString());
|
||||||
emit(RegistrationState(sheet: state.sheet, error: e.toString()));
|
emit(RegistrationState(sheet: state.sheet, error: e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||||
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart';
|
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart';
|
||||||
|
import 'package:eatvio_app/features/registration/services/registration_analytics_service.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class VerifyEmailCubit extends Cubit<VerifyEmailState> {
|
class VerifyEmailCubit extends Cubit<VerifyEmailState> {
|
||||||
final ProfileRepository profileRepository;
|
final ProfileRepository profileRepository;
|
||||||
|
|
||||||
VerifyEmailCubit({
|
VerifyEmailCubit({required this.profileRepository}) : super(const VerifyEmailState.loading());
|
||||||
required this.profileRepository,
|
|
||||||
}) : super(const VerifyEmailState.loading());
|
|
||||||
|
|
||||||
Future<void> sendEmailCode() async {
|
Future<void> sendEmailCode() async {
|
||||||
try {
|
try {
|
||||||
@@ -24,6 +23,7 @@ class VerifyEmailCubit extends Cubit<VerifyEmailState> {
|
|||||||
emit(const VerifyEmailState.verifyLoading());
|
emit(const VerifyEmailState.verifyLoading());
|
||||||
final result = await profileRepository.tryVerifyEmailByCode(code);
|
final result = await profileRepository.tryVerifyEmailByCode(code);
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
|
RegistrationAnalyticsService.reportEmailVerification();
|
||||||
emit(const VerifyEmailState.verifySuccess());
|
emit(const VerifyEmailState.verifySuccess());
|
||||||
} else {
|
} else {
|
||||||
emit(const VerifyEmailState.verifyError());
|
emit(const VerifyEmailState.verifyError());
|
||||||
@@ -32,4 +32,16 @@ class VerifyEmailCubit extends Cubit<VerifyEmailState> {
|
|||||||
emit(const VerifyEmailState.verifyError());
|
emit(const VerifyEmailState.verifyError());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> changeEmail(String newEmail) async {
|
||||||
|
try {
|
||||||
|
emit(const VerifyEmailState.changeEmailLoading());
|
||||||
|
await profileRepository.sendEmailCode(newEmail: newEmail);
|
||||||
|
emit(const VerifyEmailState.changeEmailSuccess());
|
||||||
|
// Обновляем email в состоянии после успешной смены
|
||||||
|
emit(VerifyEmailState.loaded(email: newEmail));
|
||||||
|
} catch (e) {
|
||||||
|
emit(const VerifyEmailState.changeEmailError());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,9 @@ part 'verify_email_state.freezed.dart';
|
|||||||
abstract class VerifyEmailState with _$VerifyEmailState {
|
abstract class VerifyEmailState with _$VerifyEmailState {
|
||||||
const factory VerifyEmailState.loading() = VerifyEmailStateLoading;
|
const factory VerifyEmailState.loading() = VerifyEmailStateLoading;
|
||||||
|
|
||||||
const factory VerifyEmailState.loaded({
|
const factory VerifyEmailState.loaded({required String email}) = VerifyEmailStateLoaded;
|
||||||
required String email,
|
|
||||||
}) = VerifyEmailStateLoaded;
|
|
||||||
|
|
||||||
const factory VerifyEmailState.error({
|
const factory VerifyEmailState.error({required String errorMessage}) = VerifyEmailStateError;
|
||||||
required String errorMessage,
|
|
||||||
}) = VerifyEmailStateError;
|
|
||||||
|
|
||||||
const factory VerifyEmailState() = _VerifyEmailState;
|
const factory VerifyEmailState() = _VerifyEmailState;
|
||||||
|
|
||||||
@@ -21,4 +17,10 @@ abstract class VerifyEmailState with _$VerifyEmailState {
|
|||||||
const factory VerifyEmailState.verifySuccess() = VerifyEmailStateVerifySuccess;
|
const factory VerifyEmailState.verifySuccess() = VerifyEmailStateVerifySuccess;
|
||||||
|
|
||||||
const factory VerifyEmailState.verifyError() = VerifyEmailStateVerifyError;
|
const factory VerifyEmailState.verifyError() = VerifyEmailStateVerifyError;
|
||||||
|
|
||||||
|
const factory VerifyEmailState.changeEmailLoading() = VerifyEmailStateChangeEmailLoading;
|
||||||
|
|
||||||
|
const factory VerifyEmailState.changeEmailSuccess() = VerifyEmailStateChangeEmailSuccess;
|
||||||
|
|
||||||
|
const factory VerifyEmailState.changeEmailError() = VerifyEmailStateChangeEmailError;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,48 +27,46 @@ class _GenderScreenState extends State<GenderScreen> {
|
|||||||
padding: const EdgeInsets.only(bottom: 80, top: 16, left: 16, right: 16),
|
padding: const EdgeInsets.only(bottom: 80, top: 16, left: 16, right: 16),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
RadioListTile(
|
Row(
|
||||||
contentPadding: EdgeInsets.zero,
|
children: [
|
||||||
shape: RoundedRectangleBorder(
|
Radio<String>(
|
||||||
borderRadius: BorderRadius.circular(16),
|
value: 'male',
|
||||||
),
|
// ignore: deprecated_member_use
|
||||||
title: const Text('Мужской'),
|
groupValue: currentValue,
|
||||||
value: 'male',
|
// ignore: deprecated_member_use
|
||||||
groupValue: currentValue,
|
onChanged: (newValue) {
|
||||||
onChanged: (newValue) {
|
final registrationCubit = context.read<RegistrationCubit>();
|
||||||
final registrationCubit = context.read<RegistrationCubit>();
|
registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(gender: 'male'));
|
||||||
registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(gender: 'male'));
|
setState(() {
|
||||||
setState(() {
|
currentValue = 'male';
|
||||||
currentValue = 'male';
|
});
|
||||||
});
|
},
|
||||||
},
|
),
|
||||||
secondary: Image.asset(
|
Expanded(
|
||||||
'assets/images/img_male.png',
|
child: Row(children: [Image.asset('assets/images/img_male.png', width: 40, height: 40), const SizedBox(width: 8), const Text('Мужской')]),
|
||||||
width: 40,
|
),
|
||||||
height: 40,
|
],
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
RadioListTile(
|
Row(
|
||||||
contentPadding: EdgeInsets.zero,
|
children: [
|
||||||
shape: RoundedRectangleBorder(
|
Radio<String>(
|
||||||
borderRadius: BorderRadius.circular(16),
|
value: 'female',
|
||||||
),
|
// ignore: deprecated_member_use
|
||||||
title: const Text('Женский'),
|
groupValue: currentValue,
|
||||||
value: 'female',
|
// ignore: deprecated_member_use
|
||||||
groupValue: currentValue,
|
onChanged: (newValue) {
|
||||||
onChanged: (newValue) {
|
final registrationCubit = context.read<RegistrationCubit>();
|
||||||
final registrationCubit = context.read<RegistrationCubit>();
|
registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(gender: 'female'));
|
||||||
registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(gender: 'female'));
|
setState(() {
|
||||||
setState(() {
|
currentValue = 'female';
|
||||||
currentValue = 'female';
|
});
|
||||||
});
|
},
|
||||||
},
|
),
|
||||||
secondary: Image.asset(
|
Expanded(
|
||||||
'assets/images/img_female.png',
|
child: Row(children: [Image.asset('assets/images/img_female.png', width: 40, height: 40), const SizedBox(width: 8), const Text('Женский')]),
|
||||||
width: 40,
|
),
|
||||||
height: 40,
|
],
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import 'package:eatvio_app/features/registration/presentation/pages/medicine_int
|
|||||||
import 'package:eatvio_app/features/registration/presentation/pages/name_screen.dart';
|
import 'package:eatvio_app/features/registration/presentation/pages/name_screen.dart';
|
||||||
import 'package:eatvio_app/features/registration/presentation/pages/phone_screen.dart';
|
import 'package:eatvio_app/features/registration/presentation/pages/phone_screen.dart';
|
||||||
import 'package:eatvio_app/features/registration/presentation/pages/weight_screen.dart';
|
import 'package:eatvio_app/features/registration/presentation/pages/weight_screen.dart';
|
||||||
|
import 'package:eatvio_app/features/registration/services/registration_analytics_service.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
@@ -39,6 +40,12 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
final pageController = PageController();
|
final pageController = PageController();
|
||||||
final RegistrationCubit registrationCubit = sl<RegistrationCubit>();
|
final RegistrationCubit registrationCubit = sl<RegistrationCubit>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
RegistrationAnalyticsService.reportRegistrationStep(step);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider<RegistrationCubit>(
|
return BlocProvider<RegistrationCubit>(
|
||||||
@@ -51,12 +58,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Ошибка при регистрации'),
|
title: const Text('Ошибка при регистрации'),
|
||||||
content: Text(state.error!),
|
content: Text(state.error!),
|
||||||
actions: [
|
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: const Text('OK'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -76,22 +78,14 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
highlightElevation: 0,
|
highlightElevation: 0,
|
||||||
focusElevation: 0,
|
focusElevation: 0,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
backgroundColor: const Color(0xFFEAF2FD),
|
backgroundColor: const Color(0xFFEAF2FD),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
setState(() {
|
setState(() {
|
||||||
pageController.previousPage(
|
pageController.previousPage(duration: const Duration(milliseconds: 350), curve: Curves.easeInOut);
|
||||||
duration: const Duration(milliseconds: 350),
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
label: Icon(
|
label: Icon(Icons.arrow_back_ios_new_rounded, color: appTheme.blueA400),
|
||||||
Icons.arrow_back_ios_new_rounded,
|
|
||||||
color: appTheme.blueA400,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
@@ -103,24 +97,16 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
highlightElevation: 0,
|
highlightElevation: 0,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
focusElevation: 0,
|
focusElevation: 0,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
backgroundColor: appTheme.blueA400,
|
backgroundColor: appTheme.blueA400,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (await canGoNext(state, context)) {
|
if (await canGoNext(state, context)) {
|
||||||
setState(() {
|
setState(() {
|
||||||
pageController.nextPage(
|
pageController.nextPage(duration: const Duration(milliseconds: 350), curve: Curves.easeInOut);
|
||||||
duration: const Duration(milliseconds: 350),
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
label: Text(
|
label: Text('Продолжить', style: theme.textTheme.titleSmall!.copyWith(color: Colors.white)),
|
||||||
'Продолжить',
|
|
||||||
style: theme.textTheme.titleSmall!.copyWith(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -128,23 +114,11 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
leading: Builder(
|
leading: Builder(
|
||||||
builder: (context) => IconButton(
|
builder: (context) => IconButton(icon: const Icon(Icons.close_rounded), onPressed: () => GoRouter.of(context).pop()),
|
||||||
icon: const Icon(Icons.close_rounded),
|
|
||||||
onPressed: () => GoRouter.of(context).pop(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: SvgPicture.asset(
|
title: SvgPicture.asset(ImageConstant.imgLogoEatvio, height: 48, fit: BoxFit.contain),
|
||||||
ImageConstant.imgLogoEatvio,
|
actions: [Padding(padding: const EdgeInsets.only(right: 16), child: Text('шаг $step из 15'))],
|
||||||
height: 48,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 16),
|
|
||||||
child: Text('шаг $step из 15'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
preferredSize: Size(MediaQuery.of(context).size.width, 32),
|
preferredSize: Size(MediaQuery.of(context).size.width, 32),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -152,10 +126,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
child: TweenAnimationBuilder<double>(
|
child: TweenAnimationBuilder<double>(
|
||||||
duration: const Duration(milliseconds: 350),
|
duration: const Duration(milliseconds: 350),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
tween: Tween<double>(
|
tween: Tween<double>(begin: 1 / 15, end: progressValue),
|
||||||
begin: 1 / 15,
|
|
||||||
end: progressValue,
|
|
||||||
),
|
|
||||||
builder: (context, value, _) => LinearProgressIndicator(
|
builder: (context, value, _) => LinearProgressIndicator(
|
||||||
value: value,
|
value: value,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
@@ -175,6 +146,8 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
step = index + 1;
|
step = index + 1;
|
||||||
progressValue = step / 15;
|
progressValue = step / 15;
|
||||||
});
|
});
|
||||||
|
// Report analytics for step completion
|
||||||
|
RegistrationAnalyticsService.reportRegistrationStep(step);
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
// LanguageSelectScreen(sheet: state.sheet),
|
// LanguageSelectScreen(sheet: state.sheet),
|
||||||
@@ -211,12 +184,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Ошибка'),
|
title: const Text('Ошибка'),
|
||||||
content: const Text('Введите корректный номер телефона'),
|
content: const Text('Введите корректный номер телефона'),
|
||||||
actions: [
|
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: const Text('OK'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
@@ -224,12 +192,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case 2:
|
case 2:
|
||||||
List<String> errors = validateCredentialsFields(
|
List<String> errors = validateCredentialsFields(state.sheet.email, state.sheet.password, state.sheet.passwordConfirmation, context);
|
||||||
state.sheet.email,
|
|
||||||
state.sheet.password,
|
|
||||||
state.sheet.passwordConfirmation,
|
|
||||||
context,
|
|
||||||
);
|
|
||||||
if (errors.isEmpty) {
|
if (errors.isEmpty) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -244,22 +207,13 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Ошибка'),
|
title: const Text('Ошибка'),
|
||||||
content: Text(errorMessage),
|
content: Text(errorMessage),
|
||||||
actions: [
|
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: const Text('OK'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case 3:
|
case 3:
|
||||||
List<String> errors = validateNameFields(
|
List<String> errors = validateNameFields(state.sheet.firstName.toString(), state.sheet.lastName.toString(), context);
|
||||||
state.sheet.firstName.toString(),
|
|
||||||
state.sheet.lastName.toString(),
|
|
||||||
context,
|
|
||||||
);
|
|
||||||
if (errors.isEmpty) {
|
if (errors.isEmpty) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -274,12 +228,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Ошибка'),
|
title: const Text('Ошибка'),
|
||||||
content: Text(errorMessage),
|
content: Text(errorMessage),
|
||||||
actions: [
|
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: const Text('OK'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
@@ -293,12 +242,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Ошибка'),
|
title: const Text('Ошибка'),
|
||||||
content: const Text('Выберете пол'),
|
content: const Text('Выберете пол'),
|
||||||
actions: [
|
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: const Text('OK'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
@@ -312,12 +256,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Ошибка'),
|
title: const Text('Ошибка'),
|
||||||
content: const Text('Заполните все поля'),
|
content: const Text('Заполните все поля'),
|
||||||
actions: [
|
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: const Text('OK'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
@@ -343,12 +282,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Ошибка'),
|
title: const Text('Ошибка'),
|
||||||
content: Text(errorMessage),
|
content: Text(errorMessage),
|
||||||
actions: [
|
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))],
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: const Text('OK'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
@@ -359,10 +293,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
useSafeArea: false,
|
useSafeArea: false,
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => const PopScope(
|
builder: (context) => const PopScope(canPop: false, child: Loader()),
|
||||||
canPop: false,
|
|
||||||
child: Loader(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
await registrationCubit.tryRegistration();
|
await registrationCubit.tryRegistration();
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
@@ -373,9 +304,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => const AlertDialog(
|
builder: (context) => const AlertDialog(title: Text('Ошибка при попытке регистрации')),
|
||||||
title: Text('Ошибка при попытке регистрации'),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -385,12 +314,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> validateCircumferencesFields(
|
List<String> validateCircumferencesFields(String chestCircumference, String waistCircumference, String hipCircumference, BuildContext context) {
|
||||||
String chestCircumference,
|
|
||||||
String waistCircumference,
|
|
||||||
String hipCircumference,
|
|
||||||
BuildContext context,
|
|
||||||
) {
|
|
||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
|
|
||||||
List<String> errors = [];
|
List<String> errors = [];
|
||||||
@@ -406,12 +330,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> validateCredentialsFields(
|
List<String> validateCredentialsFields(String email, String password, String passwordConfirmation, BuildContext context) {
|
||||||
String email,
|
|
||||||
String password,
|
|
||||||
String passwordConfirmation,
|
|
||||||
BuildContext context,
|
|
||||||
) {
|
|
||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
|
|
||||||
List<String> errors = [];
|
List<String> errors = [];
|
||||||
@@ -435,11 +354,7 @@ class _RegistrationScreenState extends State<RegistrationScreen> {
|
|||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> validateNameFields(
|
List<String> validateNameFields(String firstName, String lastName, BuildContext context) {
|
||||||
String firstName,
|
|
||||||
String lastName,
|
|
||||||
BuildContext context,
|
|
||||||
) {
|
|
||||||
List<String> errors = [];
|
List<String> errors = [];
|
||||||
|
|
||||||
if (firstName.isEmpty) {
|
if (firstName.isEmpty) {
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||||
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart';
|
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart';
|
||||||
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart';
|
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart';
|
||||||
|
import 'package:eatvio_app/features/registration/presentation/widgets/change_email_dialog.dart';
|
||||||
|
import 'package:eatvio_app/features/registration/presentation/widgets/code_not_received_dialog.dart';
|
||||||
|
import 'package:eatvio_app/features/registration/services/registration_analytics_service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@@ -18,11 +26,17 @@ class _VerifyEmailScreenState extends State<VerifyEmailScreen> {
|
|||||||
final List<TextEditingController> _controllers = List.generate(4, (_) => TextEditingController());
|
final List<TextEditingController> _controllers = List.generate(4, (_) => TextEditingController());
|
||||||
String email = '';
|
String email = '';
|
||||||
late final GoRouter goRouter;
|
late final GoRouter goRouter;
|
||||||
|
late Timer _timer;
|
||||||
|
int _secondsRemaining = 60;
|
||||||
|
bool _canSendAgain = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
goRouter = GoRouter.of(context);
|
goRouter = GoRouter.of(context);
|
||||||
|
_startTimer();
|
||||||
|
// Report email verification screen opened
|
||||||
|
RegistrationAnalyticsService.reportRegistrationStep(16); // Email verification step
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -36,6 +50,23 @@ class _VerifyEmailScreenState extends State<VerifyEmailScreen> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _startTimer() {
|
||||||
|
setState(() {
|
||||||
|
_canSendAgain = false;
|
||||||
|
_secondsRemaining = 60;
|
||||||
|
});
|
||||||
|
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||||
|
setState(() {
|
||||||
|
if (_secondsRemaining > 0) {
|
||||||
|
_secondsRemaining--;
|
||||||
|
} else {
|
||||||
|
_canSendAgain = true;
|
||||||
|
_timer.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void _checkAndSubmitCode(BuildContext context) {
|
void _checkAndSubmitCode(BuildContext context) {
|
||||||
final code = _controllers.map((c) => c.text).join();
|
final code = _controllers.map((c) => c.text).join();
|
||||||
if (code.length == 4) {
|
if (code.length == 4) {
|
||||||
@@ -47,48 +78,96 @@ class _VerifyEmailScreenState extends State<VerifyEmailScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider<VerifyEmailCubit>(
|
return BlocProvider<VerifyEmailCubit>(
|
||||||
create: (_) => sl<VerifyEmailCubit>()..sendEmailCode(),
|
create: (_) => sl<VerifyEmailCubit>()..sendEmailCode(),
|
||||||
child: BlocConsumer<VerifyEmailCubit, VerifyEmailState>(
|
child: Builder(
|
||||||
listener: (context, state) {
|
builder: (context) {
|
||||||
if (state is VerifyEmailStateLoaded) {
|
return BlocConsumer<VerifyEmailCubit, VerifyEmailState>(
|
||||||
setState(() {
|
listener: (context, state) {
|
||||||
email = state.email;
|
if (state is VerifyEmailStateLoaded) {
|
||||||
});
|
setState(() {
|
||||||
} else if (state is VerifyEmailStateVerifySuccess) {
|
email = state.email;
|
||||||
showSuccessToast('Почта успешно подтверждена');
|
});
|
||||||
goRouter.pushNamed('feedScreen');
|
_startTimer();
|
||||||
} else if (state is VerifyEmailStateVerifyError) {
|
} else if (state is VerifyEmailStateVerifySuccess) {
|
||||||
showErrorToast("Неверный код. Попробуйте снова.");
|
showSuccessToast('Почта успешно подтверждена');
|
||||||
for (var controller in _controllers) {
|
goRouter.pushNamed('feedScreen');
|
||||||
controller.clear();
|
} else if (state is VerifyEmailStateVerifyError) {
|
||||||
}
|
showErrorToast("Неверный код. Попробуйте снова.");
|
||||||
FocusScope.of(context).requestFocus(_focusNodes[0]);
|
for (var controller in _controllers) {
|
||||||
}
|
controller.clear();
|
||||||
},
|
}
|
||||||
builder: (context, state) {
|
FocusScope.of(context).requestFocus(_focusNodes[0]);
|
||||||
return Scaffold(
|
} else if (state is VerifyEmailStateChangeEmailSuccess) {
|
||||||
appBar: AppBar(title: const Text("Подтвердите почту")),
|
showSuccessToast('Код подтверждения отправлен на новый email');
|
||||||
body: Padding(
|
// Диалог закроется автоматически в ChangeEmailDialog при успешной смене
|
||||||
padding: const EdgeInsets.all(24.0),
|
} else if (state is VerifyEmailStateChangeEmailError) {
|
||||||
child: Column(
|
showErrorToast('Ошибка при смене email. Попробуйте снова.');
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
}
|
||||||
children: [
|
},
|
||||||
Text("Код отправлен на почту\n$email", textAlign: TextAlign.center),
|
builder: (context, state) {
|
||||||
const SizedBox(height: 20),
|
return Scaffold(
|
||||||
Row(
|
appBar: AppBar(title: const Text("Подтвердите почту")),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
body: Padding(
|
||||||
children: List.generate(4, (index) => _buildCodeField(context, index)),
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text("Код отправлен на почту\n$email", textAlign: TextAlign.center),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate(4, (index) => _buildCodeField(context, index))),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
final verifyEmailCubit = context.read<VerifyEmailCubit>();
|
||||||
|
showDialog(context: context, builder: (context) => CodeNotReceivedDialog(
|
||||||
|
currentEmail: email,
|
||||||
|
verifyEmailCubit: verifyEmailCubit,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'Не приходит код?',
|
||||||
|
style: TextStyles.textSMedium.copyWith(color: appTheme.blueA400, decoration: TextDecoration.underline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
CustomOutlinedButton(
|
||||||
|
text: 'Запросить код повторно',
|
||||||
|
height: 52,
|
||||||
|
isDisabled: !_canSendAgain,
|
||||||
|
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||||
|
buttonStyle: ButtonStyle(
|
||||||
|
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||||
|
backgroundColor: WidgetStatePropertyAll(_canSendAgain ? appTheme.blueA400 : appTheme.gray),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
await context.read<VerifyEmailCubit>().sendEmailCode();
|
||||||
|
_startTimer();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (!_canSendAgain)
|
||||||
|
Text("Зпросить код повторно можно через $_secondsRemaining с.", style: TextStyles.textSMedium.copyWith(color: appTheme.gray)),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
CustomOutlinedButton(
|
||||||
|
text: 'Сменить email',
|
||||||
|
height: 52,
|
||||||
|
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.blueA400),
|
||||||
|
buttonStyle: ButtonStyle(
|
||||||
|
side: WidgetStatePropertyAll(BorderSide(color: appTheme.blueA400)),
|
||||||
|
backgroundColor: const WidgetStatePropertyAll(Colors.transparent),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
final verifyEmailCubit = context.read<VerifyEmailCubit>();
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ChangeEmailDialog(currentEmail: email, verifyEmailCubit: verifyEmailCubit),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
const Text("Запросить код повторно", style: TextStyle(color: Colors.grey)),
|
);
|
||||||
TextButton(
|
},
|
||||||
onPressed: () {
|
|
||||||
context.read<VerifyEmailCubit>().sendEmailCode();
|
|
||||||
},
|
|
||||||
child: const Text("Не приходит код?"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:eatvio_app/core/const.dart';
|
import 'package:eatvio_app/core/const.dart';
|
||||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
|
import 'package:eatvio_app/features/registration/services/registration_analytics_service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:lottie/lottie.dart';
|
import 'package:lottie/lottie.dart';
|
||||||
@@ -9,10 +10,7 @@ import 'package:webview_flutter/webview_flutter.dart';
|
|||||||
class VerifyScreen extends StatefulWidget {
|
class VerifyScreen extends StatefulWidget {
|
||||||
final String passKey;
|
final String passKey;
|
||||||
|
|
||||||
const VerifyScreen({
|
const VerifyScreen({super.key, required this.passKey});
|
||||||
super.key,
|
|
||||||
required this.passKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<VerifyScreen> createState() => _VerifyScreenState();
|
State<VerifyScreen> createState() => _VerifyScreenState();
|
||||||
@@ -25,6 +23,8 @@ class _VerifyScreenState extends State<VerifyScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
// Report phone verification screen opened
|
||||||
|
RegistrationAnalyticsService.reportRegistrationStep(17); // Phone verification step
|
||||||
_controller = WebViewController()
|
_controller = WebViewController()
|
||||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||||
..setNavigationDelegate(
|
..setNavigationDelegate(
|
||||||
@@ -36,6 +36,8 @@ class _VerifyScreenState extends State<VerifyScreen> {
|
|||||||
},
|
},
|
||||||
onNavigationRequest: (NavigationRequest request) async {
|
onNavigationRequest: (NavigationRequest request) async {
|
||||||
if (request.url.contains('user_detailz') && !request.url.contains('change-phone-verify') || request.url.contains('email/verify')) {
|
if (request.url.contains('user_detailz') && !request.url.contains('change-phone-verify') || request.url.contains('email/verify')) {
|
||||||
|
// Report phone verification success
|
||||||
|
RegistrationAnalyticsService.reportPhoneVerification();
|
||||||
GoRouter.of(context).pop();
|
GoRouter.of(context).pop();
|
||||||
await showModalBottomSheet(
|
await showModalBottomSheet(
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
@@ -50,27 +52,15 @@ class _VerifyScreenState extends State<VerifyScreen> {
|
|||||||
const Text(
|
const Text(
|
||||||
'Номер телефона\nуспешно привязан',
|
'Номер телефона\nуспешно привязан',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(fontSize: 20, height: 1, fontWeight: FontWeight.w600),
|
||||||
fontSize: 20,
|
|
||||||
height: 1,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Lottie.asset(
|
Lottie.asset('assets/lottie_animations/success.json', width: 160, height: 160),
|
||||||
'assets/lottie_animations/success.json',
|
|
||||||
width: 160,
|
|
||||||
height: 160,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Text(
|
const Text(
|
||||||
'Теперь Вам доступен весь функционал!',
|
'Теперь Вам доступен весь функционал!',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(fontSize: 16, height: 1, fontWeight: FontWeight.w500),
|
||||||
fontSize: 16,
|
|
||||||
height: 1,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
InkWell(
|
InkWell(
|
||||||
@@ -80,18 +70,11 @@ class _VerifyScreenState extends State<VerifyScreen> {
|
|||||||
child: Ink(
|
child: Ink(
|
||||||
width: MediaQuery.of(context).size.width - 32,
|
width: MediaQuery.of(context).size.width - 32,
|
||||||
height: 52,
|
height: 52,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400),
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
color: appTheme.blueA400,
|
|
||||||
),
|
|
||||||
child: const Center(
|
child: const Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Закрыть',
|
'Закрыть',
|
||||||
style: TextStyle(
|
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600),
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -117,10 +100,7 @@ class _VerifyScreenState extends State<VerifyScreen> {
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
WebViewWidget(controller: _controller),
|
WebViewWidget(controller: _controller),
|
||||||
if (_isLoading)
|
if (_isLoading) const Center(child: Loader()),
|
||||||
const Center(
|
|
||||||
child: Loader(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'dart:developer';
|
|||||||
import 'package:eatvio_app/core/const.dart';
|
import 'package:eatvio_app/core/const.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
|
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/registration/services/registration_analytics_service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:webview_flutter/webview_flutter.dart';
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
|
|
||||||
@@ -24,6 +25,8 @@ class _WebViewRegistrationScreenState extends State<WebViewRegistrationScreen> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
authCubit = sl<AuthCubit>();
|
authCubit = sl<AuthCubit>();
|
||||||
|
// Report webview registration started
|
||||||
|
RegistrationAnalyticsService.reportRegistrationStep(18); // Webview registration step
|
||||||
_controller = WebViewController()
|
_controller = WebViewController()
|
||||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||||
..addJavaScriptChannel(
|
..addJavaScriptChannel(
|
||||||
@@ -43,10 +46,9 @@ class _WebViewRegistrationScreenState extends State<WebViewRegistrationScreen> {
|
|||||||
NavigationDelegate(
|
NavigationDelegate(
|
||||||
onNavigationRequest: (NavigationRequest request) {
|
onNavigationRequest: (NavigationRequest request) {
|
||||||
if (request.url.contains('profile')) {
|
if (request.url.contains('profile')) {
|
||||||
authCubit.getTokenByCredentials(
|
// Report webview registration completion
|
||||||
email!,
|
RegistrationAnalyticsService.reportRegistrationComplete();
|
||||||
password!,
|
authCubit.getTokenByCredentials(email!, password!);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return NavigationDecision.navigate;
|
return NavigationDecision.navigate;
|
||||||
},
|
},
|
||||||
@@ -86,12 +88,8 @@ class _WebViewRegistrationScreenState extends State<WebViewRegistrationScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(toolbarHeight: 0),
|
||||||
toolbarHeight: 0,
|
body: SafeArea(child: WebViewWidget(controller: _controller)),
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: WebViewWidget(controller: _controller),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||||
|
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||||
|
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class ChangeEmailDialog extends StatefulWidget {
|
||||||
|
final String currentEmail;
|
||||||
|
final VerifyEmailCubit verifyEmailCubit;
|
||||||
|
|
||||||
|
const ChangeEmailDialog({super.key, required this.currentEmail, required this.verifyEmailCubit});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChangeEmailDialog> createState() => _ChangeEmailDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChangeEmailDialogState extends State<ChangeEmailDialog> {
|
||||||
|
final TextEditingController _emailController = TextEditingController();
|
||||||
|
final FocusNode _emailFocusNode = FocusNode();
|
||||||
|
bool _isLoading = false;
|
||||||
|
String? _errorText;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_emailController.text = widget.currentEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_emailController.dispose();
|
||||||
|
_emailFocusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isValidEmail(String email) {
|
||||||
|
return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _validateEmail() {
|
||||||
|
final email = _emailController.text.trim();
|
||||||
|
if (email.isEmpty) {
|
||||||
|
setState(() {
|
||||||
|
_errorText = 'Введите email';
|
||||||
|
});
|
||||||
|
} else if (!_isValidEmail(email)) {
|
||||||
|
setState(() {
|
||||||
|
_errorText = 'Введите корректный email';
|
||||||
|
});
|
||||||
|
} else if (email == widget.currentEmail) {
|
||||||
|
setState(() {
|
||||||
|
_errorText = 'Новый email должен отличаться от текущего';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_errorText = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _changeEmail() async {
|
||||||
|
_validateEmail();
|
||||||
|
if (_errorText != null) return;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isLoading = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await widget.verifyEmailCubit.changeEmail(_emailController.text.trim());
|
||||||
|
// Диалог закроется автоматически при успешной смене email через основной экран
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
showErrorToast('Ошибка при смене email. Попробуйте снова.');
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider.value(
|
||||||
|
value: widget.verifyEmailCubit,
|
||||||
|
child: BlocListener<VerifyEmailCubit, VerifyEmailState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state is VerifyEmailStateChangeEmailSuccess) {
|
||||||
|
Navigator.of(context).pop(); // Закрываем диалог при успешной смене
|
||||||
|
} else if (state is VerifyEmailStateChangeEmailError) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Dialog(
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Сменить email', style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text('Введите новый email для получения кода подтверждения', style: TextStyles.textSMedium.copyWith(color: appTheme.gray)),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
TextField(
|
||||||
|
controller: _emailController,
|
||||||
|
focusNode: _emailFocusNode,
|
||||||
|
keyboardType: TextInputType.emailAddress,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Введите новый email',
|
||||||
|
hintStyle: TextStyles.textSMedium.copyWith(color: appTheme.gray),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(color: appTheme.gray),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(color: appTheme.blueA400),
|
||||||
|
),
|
||||||
|
errorBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(color: appTheme.red),
|
||||||
|
),
|
||||||
|
errorText: _errorText,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||||
|
),
|
||||||
|
onChanged: (_) => _validateEmail(),
|
||||||
|
onSubmitted: (_) => _changeEmail(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: CustomOutlinedButton(
|
||||||
|
text: 'Отмена',
|
||||||
|
height: 52,
|
||||||
|
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.black),
|
||||||
|
buttonStyle: ButtonStyle(
|
||||||
|
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||||
|
backgroundColor: WidgetStatePropertyAll(appTheme.lightGray0),
|
||||||
|
),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: CustomOutlinedButton(
|
||||||
|
text: _isLoading ? 'Отправка...' : 'Сменить',
|
||||||
|
height: 52,
|
||||||
|
isDisabled: _isLoading,
|
||||||
|
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||||
|
buttonStyle: ButtonStyle(
|
||||||
|
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||||
|
backgroundColor: WidgetStatePropertyAll(_isLoading ? appTheme.gray : appTheme.blueA400),
|
||||||
|
),
|
||||||
|
onPressed: _isLoading ? null : _changeEmail,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||||
|
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||||
|
import 'package:eatvio_app/features/registration/presentation/widgets/change_email_dialog.dart';
|
||||||
|
import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart';
|
||||||
|
|
||||||
|
class CodeNotReceivedDialog extends StatelessWidget {
|
||||||
|
final String currentEmail;
|
||||||
|
final VerifyEmailCubit verifyEmailCubit;
|
||||||
|
|
||||||
|
const CodeNotReceivedDialog({super.key, required this.currentEmail, required this.verifyEmailCubit});
|
||||||
|
|
||||||
|
void _showChangeEmailDialog(BuildContext context) {
|
||||||
|
Navigator.of(context).pop(); // Закрываем текущий диалог
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ChangeEmailDialog(currentEmail: currentEmail, verifyEmailCubit: verifyEmailCubit),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16.0)),
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text('Почему может не приходить код?', style: TextStyles.textLSemibold.copyWith(color: appTheme.black)),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: TextStyles.textMMedium.copyWith(color: appTheme.gray600),
|
||||||
|
children: [
|
||||||
|
const TextSpan(text: 'Опечатка в email: Если адрес введён неправильно, код не придёт. ', style: TextStyles.textFS13FW400),
|
||||||
|
TextSpan(
|
||||||
|
text: 'Ввести email повторно.',
|
||||||
|
style: TextStyles.textMMedium.copyWith(color: appTheme.blueA400, decoration: TextDecoration.underline),
|
||||||
|
recognizer: TapGestureRecognizer()..onTap = () => _showChangeEmailDialog(context),
|
||||||
|
),
|
||||||
|
const TextSpan(
|
||||||
|
style: TextStyles.textFS13FW400,
|
||||||
|
text:
|
||||||
|
'\n\nПроверьте спам/промо/мусорную папку: Код может попасть туда из-за фильтров почтового провайдера\n\nПодождите 1-2 минуты: Иногда email задерживается из-за сетевых проблем.\n\nУбедитесь в стабильном соединении: Проверьте интернет и уведомления на устройстве.',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: appTheme.blueA400,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
),
|
||||||
|
child: Text('Понятно', style: TextStyles.textMSemibold.copyWith(color: Colors.white)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user