forked from eatvio/eatvio_app
Compare commits
62 Commits
consultant
...
notificati
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26ff548752 | ||
|
|
43e9098d8d | ||
|
|
ad538472f0 | ||
|
|
49827e979e | ||
|
|
24cff8b4e9 | ||
|
|
be5a858c73 | ||
|
|
e80b660210 | ||
|
|
d42604dc87 | ||
|
|
4bed103cec | ||
|
|
8d429862dd | ||
|
|
dd90f62286 | ||
|
|
7809624186 | ||
|
|
7c131f8970 | ||
|
|
0926b50537 | ||
|
|
3e96fdcfc2 | ||
|
|
3ce6caa0af | ||
|
|
58855b7f11 | ||
|
|
f0d5dacc00 | ||
|
|
86199d08f0 | ||
|
|
1801324fb5 | ||
|
|
d4a05f47d1 | ||
|
|
300a708dfc | ||
|
|
693260afb6 | ||
|
|
9b4328a2ee | ||
|
|
591a8a597d | ||
|
|
bc660e2aff | ||
|
|
d129687fc6 | ||
|
|
2e5e1b3af4 | ||
|
|
c103d214c4 | ||
|
|
9946abc0be | ||
|
|
87bf277248 | ||
|
|
23a606bfd4 | ||
|
|
5f57bded51 | ||
|
|
535dd595b7 | ||
|
|
80afcd8949 | ||
|
|
8c7c7f51cf | ||
|
|
9c168d37ce | ||
|
|
55bc361b05 | ||
|
|
d14fbbb8d3 | ||
|
|
7530270a2e | ||
|
|
afc36e6b63 | ||
|
|
f7e40e654d | ||
|
|
aa78d385a2 | ||
|
|
75fa9ebc5c | ||
|
|
105c7cb462 | ||
|
|
c3647288a6 | ||
|
|
85d67ba9c4 | ||
|
|
15ffb4a3ec | ||
|
|
2a705fdb66 | ||
|
|
41ca97d030 | ||
|
|
84b88282ad | ||
|
|
84d52de935 | ||
|
|
fee92801b8 | ||
|
|
3b25e30b41 | ||
|
|
6aef2a9a08 | ||
|
|
6a2525dbfd | ||
|
|
21ce0fc9f8 | ||
|
|
b0c29345a2 | ||
|
|
7682859d32 | ||
|
|
d0426f6e1e | ||
|
|
ca296040f2 | ||
|
|
0581da038a |
@@ -3,7 +3,7 @@ name: Eatvio App Android Test
|
|||||||
on: [ pull_request ]
|
on: [ pull_request ]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
flutter_version: "3.32.0"
|
flutter_version: "3.32.5"
|
||||||
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/"
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -12,3 +12,5 @@
|
|||||||
/android/app/.cxx
|
/android/app/.cxx
|
||||||
*.g.dart
|
*.g.dart
|
||||||
*.freezed.dart
|
*.freezed.dart
|
||||||
|
android/build/
|
||||||
|
ios/XCBuildData/
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ if (keystorePropertiesFile.exists()) {
|
|||||||
android {
|
android {
|
||||||
namespace = "com.eatvioapp.app"
|
namespace = "com.eatvioapp.app"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = "27.0.12077973"
|
ndkVersion = "27.0.12077973"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_21
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="Eatvio"
|
android:label="Eatvio"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
@@ -25,8 +26,30 @@
|
|||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme"
|
||||||
/>
|
/>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Deep link intent -->
|
||||||
|
<intent-filter android:autoVerify="true">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
<data android:host="eatvio.ru" />
|
||||||
|
<data android:host="www.eatvio.ru" />
|
||||||
|
|
||||||
|
<data android:pathPattern="/ru/recipe/.*" />
|
||||||
|
<data android:pathPattern="/en/recipe/.*" />
|
||||||
|
|
||||||
|
<data android:pathPattern="/ru/blog_post/.*" />
|
||||||
|
<data android:pathPattern="/en/blog_post/.*" />
|
||||||
|
|
||||||
|
<data android:pathPattern="/ru/diet/.*" />
|
||||||
|
<data android:pathPattern="/en/diet/.*" />
|
||||||
|
|
||||||
|
<data android:pathPattern="/ru/plan/.*" />
|
||||||
|
<data android:pathPattern="/en/plan/.*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
|
|||||||
29
assets/icons/blocked_users.svg
Normal file
29
assets/icons/blocked_users.svg
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="19.633818mm"
|
||||||
|
height="19.633816mm"
|
||||||
|
viewBox="0 0 19.633818 19.633816"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-64.066064,-72.435122)">
|
||||||
|
<g
|
||||||
|
id="g2">
|
||||||
|
<path
|
||||||
|
id="path1"
|
||||||
|
style="fill-opacity:0;stroke:#2f80ed;stroke-width:1.065"
|
||||||
|
d="m 83.167381,82.252022 a 9.2844086,9.2844086 0 0 1 -9.284408,9.284408 9.2844086,9.2844086 0 0 1 -9.284409,-9.284408 9.2844086,9.2844086 0 0 1 9.284409,-9.284409 9.2844086,9.2844086 0 0 1 9.284408,9.284409 z" />
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-opacity:0;stroke:#2f80ed;stroke-width:1.065;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 68.517124,89.22945 79.520546,75.54452"
|
||||||
|
id="path2" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 986 B |
@@ -388,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 = 27;
|
CURRENT_PROJECT_VERSION = 39;
|
||||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
@@ -538,7 +538,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 = 27;
|
CURRENT_PROJECT_VERSION = 39;
|
||||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
@@ -576,7 +576,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 = 27;
|
CURRENT_PROJECT_VERSION = 39;
|
||||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>27</string>
|
<string>39</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
@@ -31,6 +31,8 @@
|
|||||||
<key>NSAllowsArbitraryLoads</key>
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>NSAppleMusicUsageDescription</key>
|
||||||
|
<string>This permission is not needed by the app, but it is required by an underlying API. If you see this dialog, contact us.</string>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>Предоставление доступа позволит Вам делать фотографии и добавлять к записям</string>
|
<string>Предоставление доступа позволит Вам делать фотографии и добавлять к записям</string>
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
|
|||||||
@@ -4,5 +4,10 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>aps-environment</key>
|
<key>aps-environment</key>
|
||||||
<string>development</string>
|
<string>development</string>
|
||||||
|
<key>com.apple.developer.associated-domains</key>
|
||||||
|
<array>
|
||||||
|
<string>applinks:eatvio.ru</string>
|
||||||
|
<string>applinks:www.eatvio.ru</string>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import 'package:eatvio_app/core/token_storage.dart';
|
|||||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart';
|
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart';
|
||||||
|
import 'package:eatvio_app/features/auth/presentation/pages/forget_password_screen.dart';
|
||||||
|
import 'package:eatvio_app/features/auth/presentation/pages/scan_qr_code_screen.dart';
|
||||||
import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_screen.dart';
|
import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_screen.dart';
|
||||||
import 'package:eatvio_app/features/blog_post_create/presentation/pages/blog_post_create_screen.dart';
|
import 'package:eatvio_app/features/blog_post_create/presentation/pages/blog_post_create_screen.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';
|
||||||
@@ -15,6 +17,7 @@ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/b
|
|||||||
import 'package:eatvio_app/features/calendar/presentation/pages/calendar_screen.dart';
|
import 'package:eatvio_app/features/calendar/presentation/pages/calendar_screen.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart';
|
import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/pages/chats_screen.dart';
|
import 'package:eatvio_app/features/chat/presentation/pages/chats_screen.dart';
|
||||||
|
import 'package:eatvio_app/features/chat/presentation/pages/empty_chat_screen.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/pages/payment_screen.dart';
|
import 'package:eatvio_app/features/chat/presentation/pages/payment_screen.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/consultant/become_consultant/presentation/pages/become_consultant_screen.dart';
|
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/become_consultant_screen.dart';
|
||||||
@@ -22,6 +25,7 @@ import 'package:eatvio_app/features/consultant/become_consultant/presentation/pa
|
|||||||
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart';
|
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_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/menu/presentation/pages/blocked_users_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';
|
||||||
@@ -42,7 +46,6 @@ import 'package:eatvio_app/features/finances_and_consultations_archive/presentat
|
|||||||
import 'package:eatvio_app/features/my_finance/presentation/pages/user_finance_screen.dart';
|
import 'package:eatvio_app/features/my_finance/presentation/pages/user_finance_screen.dart';
|
||||||
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
|
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
|
||||||
import 'package:eatvio_app/features/notifications/presentation/notifications_screen.dart';
|
import 'package:eatvio_app/features/notifications/presentation/notifications_screen.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
|
||||||
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
|
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
|
||||||
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
|
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
|
||||||
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
|
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
|
||||||
@@ -67,10 +70,12 @@ import 'package:eatvio_app/features/user_details/presentation/user_details_scree
|
|||||||
import 'package:eatvio_app/features/welcome/presentation/pages/welcome_screen.dart';
|
import 'package:eatvio_app/features/welcome/presentation/pages/welcome_screen.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:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:version/version.dart';
|
import 'package:version/version.dart';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
class AppRouter {
|
class AppRouter {
|
||||||
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
@@ -78,6 +83,30 @@ class AppRouter {
|
|||||||
static GoRouter appRouter = GoRouter(
|
static GoRouter appRouter = GoRouter(
|
||||||
initialLocation: '/update-screen',
|
initialLocation: '/update-screen',
|
||||||
navigatorKey: navigatorKey,
|
navigatorKey: navigatorKey,
|
||||||
|
redirect: (context, state) {
|
||||||
|
final segments = state.uri.pathSegments;
|
||||||
|
if (segments.length >= 3) {
|
||||||
|
final type = segments[1];
|
||||||
|
final contentIdentifier = segments[2];
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'recipe':
|
||||||
|
FlutterNativeSplash.remove();
|
||||||
|
return '/about-recipes-screen?slug=$contentIdentifier';
|
||||||
|
case 'blog_post':
|
||||||
|
FlutterNativeSplash.remove();
|
||||||
|
return '/blog-post-screen?id=$contentIdentifier';
|
||||||
|
case 'plan':
|
||||||
|
FlutterNativeSplash.remove();
|
||||||
|
return '/about-plan-screen?slug=$contentIdentifier';
|
||||||
|
case 'diet':
|
||||||
|
FlutterNativeSplash.remove();
|
||||||
|
return '/about-diet-screen?slug=$contentIdentifier';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/auth-screen',
|
path: '/auth-screen',
|
||||||
@@ -89,6 +118,16 @@ class AppRouter {
|
|||||||
name: 'registrationScreen',
|
name: 'registrationScreen',
|
||||||
builder: (context, state) => const RegistrationScreen(),
|
builder: (context, state) => const RegistrationScreen(),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: 'forget-password-screen',
|
||||||
|
name: 'forgetPasswordScreen',
|
||||||
|
builder: (context, state) => const ForgetPasswordScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: 'scan-qr-code-screen',
|
||||||
|
name: 'scanQRCodeScreen',
|
||||||
|
builder: (context, state) => const ScanQRCodeScreen(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
@@ -117,10 +156,10 @@ class AppRouter {
|
|||||||
redirect: (context, state) async {
|
redirect: (context, state) async {
|
||||||
try {
|
try {
|
||||||
await sl<WsManager>().initialize();
|
await sl<WsManager>().initialize();
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
rethrow;
|
log(e.toString(), stackTrace: st);
|
||||||
}
|
}
|
||||||
return '/feed-screen';
|
return null;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -155,6 +194,11 @@ class AppRouter {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
name: 'blockedUsersScreen',
|
||||||
|
path: '/blocked-users-screen',
|
||||||
|
builder: (context, state) => const BlockedUsersScreen(),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/welcome-screen',
|
path: '/welcome-screen',
|
||||||
name: 'welcomeScreen',
|
name: 'welcomeScreen',
|
||||||
@@ -168,22 +212,24 @@ class AppRouter {
|
|||||||
await sl<Dio>().post(
|
await sl<Dio>().post(
|
||||||
'/api/profile/notification/token/register',
|
'/api/profile/notification/token/register',
|
||||||
data: {
|
data: {
|
||||||
'platform': Platform.isIOS ? 'ios': 'android',
|
'platform': Platform.isIOS ? 'ios' : 'android',
|
||||||
'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id,
|
'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id,
|
||||||
'token': fbToken,
|
'token': fbToken,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
final sharedPreferences = await SharedPreferences.getInstance();
|
||||||
|
final welcomeScreenWasShowed = sharedPreferences.getBool('welcomeScreenWasShowed') ?? false;
|
||||||
|
if (welcomeScreenWasShowed == false) {
|
||||||
|
return null;
|
||||||
|
} else if (await TokenStorage.haveTokenInStorage()) {
|
||||||
|
return '/feed-screen';
|
||||||
|
} else {
|
||||||
|
return '/auth-screen';
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
final sharedPreferences = await SharedPreferences.getInstance();
|
|
||||||
final welcomeScreenWasShowed = sharedPreferences.getBool('welcomeScreenWasShowed');
|
|
||||||
if (welcomeScreenWasShowed == true) {
|
|
||||||
return '/feed-screen';
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -191,14 +237,18 @@ class AppRouter {
|
|||||||
name: 'updateScreen',
|
name: 'updateScreen',
|
||||||
builder: (context, state) => const UpdateScreen(),
|
builder: (context, state) => const UpdateScreen(),
|
||||||
redirect: (context, state) async {
|
redirect: (context, state) async {
|
||||||
final versions = (await sl<Dio>().get('$baseUrl/api/app_versions')).data;
|
try {
|
||||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
final versions = (await sl<Dio>().get('$baseUrl/api/app_versions')).data;
|
||||||
String version = packageInfo.version;
|
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||||
Version currentVersion = Version.parse(version);
|
String version = packageInfo.version;
|
||||||
Version latestVersion = Version.parse(versions['min_version']);
|
Version currentVersion = Version.parse(version);
|
||||||
if (latestVersion > currentVersion) {
|
Version latestVersion = Version.parse(versions['min_version']);
|
||||||
return null;
|
if (latestVersion > currentVersion) {
|
||||||
} else {
|
return null;
|
||||||
|
} else {
|
||||||
|
return '/welcome-screen';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
return '/welcome-screen';
|
return '/welcome-screen';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -235,8 +285,17 @@ class AppRouter {
|
|||||||
path: '/about-plan-screen',
|
path: '/about-plan-screen',
|
||||||
name: 'aboutPlanScreen',
|
name: 'aboutPlanScreen',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
final slug = state.uri.queryParameters['slug'];
|
||||||
|
if (slug != null) {
|
||||||
|
return AboutPlanScreen(
|
||||||
|
slug: slug,
|
||||||
|
navigateToComments: false,
|
||||||
|
index: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
return AboutPlanScreen(plan: data['plan'] as PlanModel, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
|
return AboutPlanScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(path: '/glossary-screen', name: 'glossaryScreen', builder: (context, state) => const GlossaryScreen()),
|
GoRoute(path: '/glossary-screen', name: 'glossaryScreen', builder: (context, state) => const GlossaryScreen()),
|
||||||
@@ -244,9 +303,18 @@ class AppRouter {
|
|||||||
path: '/about-recipes-screen',
|
path: '/about-recipes-screen',
|
||||||
name: 'aboutRecipesScreen',
|
name: 'aboutRecipesScreen',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
final slug = state.uri.queryParameters['slug'];
|
||||||
|
if (slug != null) {
|
||||||
|
return AboutRecipeScreen(
|
||||||
|
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(
|
||||||
recipe: data['recipe'] as Recipe,
|
recipeSlug: data['recipe'] as String,
|
||||||
index: data['index'] as int?,
|
index: data['index'] as int?,
|
||||||
navigateToComments: data['navigateToComments'] as bool,
|
navigateToComments: data['navigateToComments'] as bool,
|
||||||
);
|
);
|
||||||
@@ -269,8 +337,16 @@ class AppRouter {
|
|||||||
path: '/about-diet-screen',
|
path: '/about-diet-screen',
|
||||||
name: 'aboutDietScreen',
|
name: 'aboutDietScreen',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
final slug = state.uri.queryParameters['slug'];
|
||||||
|
if (slug != null) {
|
||||||
|
return AboutDietScreen(
|
||||||
|
slug: slug,
|
||||||
|
index: null,
|
||||||
|
navigateToComments: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
final data = state.extra as Map<String, dynamic>;
|
final data = state.extra as Map<String, dynamic>;
|
||||||
return AboutDietScreen(diet: data['diet'] as Diet, 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);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -278,7 +354,20 @@ class AppRouter {
|
|||||||
name: 'blogPostCreateScreen',
|
name: 'blogPostCreateScreen',
|
||||||
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(path: '/blog-post-screen', name: 'blogPostScreen', builder: (context, state) => BlogPostScreen(blogPost: state.extra as FeedBlogPostModel)),
|
GoRoute(
|
||||||
|
path: '/blog-post-screen',
|
||||||
|
name: 'blogPostScreen',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.uri.queryParameters['id'];
|
||||||
|
final parsedId = int.tryParse(id ?? '');
|
||||||
|
if (parsedId != null) {
|
||||||
|
return BlogPostScreen(
|
||||||
|
id: parsedId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BlogPostScreen(id: state.extra as int);
|
||||||
|
}),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/chat-screen',
|
path: '/chat-screen',
|
||||||
name: 'chatScreen',
|
name: 'chatScreen',
|
||||||
@@ -292,6 +381,11 @@ class AppRouter {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/empty-chat-screen',
|
||||||
|
name: 'emptyChatScreen',
|
||||||
|
builder: (context, state) => EmptyChatScreen(author: state.extra as Author),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/settings-screen',
|
path: '/settings-screen',
|
||||||
name: 'settingsScreen',
|
name: 'settingsScreen',
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ abstract class CredentialsStorage {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static saveCredentials(String email, String password) async {
|
static Future<void> saveCredentials(String email, String password) async {
|
||||||
await const FlutterSecureStorage().write(key: 'email', value: email);
|
await const FlutterSecureStorage().write(key: 'email', value: email);
|
||||||
await const FlutterSecureStorage().write(key: 'password', value: password);
|
await const FlutterSecureStorage().write(key: 'password', value: password);
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeCredentials() async {
|
static Future<void> removeCredentials() async {
|
||||||
await const FlutterSecureStorage().delete(key: 'email');
|
await const FlutterSecureStorage().delete(key: 'email');
|
||||||
await const FlutterSecureStorage().delete(key: 'password');
|
await const FlutterSecureStorage().delete(key: 'password');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,25 @@ abstract class ImageCompressUtility {
|
|||||||
static Future<List<File>> compressFilesList(List<File> files) async {
|
static Future<List<File>> compressFilesList(List<File> files) async {
|
||||||
final List<File> compressedFilesList = [];
|
final List<File> compressedFilesList = [];
|
||||||
for (int i = 0; i < files.length; i++) {
|
for (int i = 0; i < files.length; i++) {
|
||||||
compressedFilesList.add(await compressFile(files[i]));
|
if (_isImageFile(files[i].path)) {
|
||||||
|
compressedFilesList.add(await compressFile(files[i]));
|
||||||
|
} else {
|
||||||
|
compressedFilesList.add(files[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return compressedFilesList;
|
return compressedFilesList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool _isImageFile(String path) {
|
||||||
|
final lower = path.toLowerCase();
|
||||||
|
return lower.endsWith('.png') ||
|
||||||
|
lower.endsWith('.jpg') ||
|
||||||
|
lower.endsWith('.jpeg') ||
|
||||||
|
lower.endsWith('.gif') ||
|
||||||
|
lower.endsWith('.bmp') ||
|
||||||
|
lower.endsWith('.webp');
|
||||||
|
}
|
||||||
|
|
||||||
static Future<File> compressFile(File file) async {
|
static Future<File> compressFile(File file) async {
|
||||||
final image = img.decodeImage(file.readAsBytesSync())!;
|
final image = img.decodeImage(file.readAsBytesSync())!;
|
||||||
int width = image.width;
|
int width = image.width;
|
||||||
|
|||||||
@@ -5,18 +5,19 @@ 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/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.dart';
|
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart';
|
||||||
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
|
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/auth/repositories/reset_password_repository.dart';
|
||||||
import 'package:eatvio_app/features/auth/services/auth_service.dart';
|
import 'package:eatvio_app/features/auth/services/auth_service.dart';
|
||||||
|
import 'package:eatvio_app/features/auth/services/reset_password_service.dart';
|
||||||
|
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_cubit.dart';
|
||||||
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart';
|
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart';
|
||||||
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart';
|
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart';
|
||||||
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/service/cancel_plan_service.dart';
|
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/service/cancel_plan_service.dart';
|
||||||
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';
|
||||||
@@ -36,6 +37,9 @@ import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.
|
|||||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
|
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_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/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/repositories/blocked_users_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/services/blocked_users_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';
|
||||||
@@ -161,7 +165,9 @@ Future<void> init() async {
|
|||||||
sl.registerFactory<ConsultationsArchiveCubit>(() => ConsultationsArchiveCubit(consultationsRepository: sl()));
|
sl.registerFactory<ConsultationsArchiveCubit>(() => ConsultationsArchiveCubit(consultationsRepository: sl()));
|
||||||
sl.registerFactory<ConsultantFinancesCubit>(() => ConsultantFinancesCubit(consultationsRepository: sl()));
|
sl.registerFactory<ConsultantFinancesCubit>(() => ConsultantFinancesCubit(consultationsRepository: sl()));
|
||||||
sl.registerFactory<UserPurchasesCubit>(() => UserPurchasesCubit(consultationsRepository: sl()));
|
sl.registerFactory<UserPurchasesCubit>(() => UserPurchasesCubit(consultationsRepository: sl()));
|
||||||
sl.registerFactory<ConsultantRatingCubit>(() => ConsultantRatingCubit(sl()));
|
sl.registerFactory<ResetPasswordCubit>(() => ResetPasswordCubit(resetPasswordRepository: sl()));
|
||||||
|
sl.registerFactory<BlogPostCubit>(() => BlogPostCubit(sl()));
|
||||||
|
sl.registerFactory<BlockedUsersCubit>(() => BlockedUsersCubit(blockedUsersRepository: sl()));
|
||||||
|
|
||||||
// Repository
|
// Repository
|
||||||
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
|
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
|
||||||
@@ -190,7 +196,8 @@ Future<void> init() async {
|
|||||||
sl.registerLazySingleton<CreatePlanRepository>(() => CreatePlanRepository(createPlanService: sl()));
|
sl.registerLazySingleton<CreatePlanRepository>(() => CreatePlanRepository(createPlanService: sl()));
|
||||||
sl.registerLazySingleton<NotificationsRepository>(() => NotificationsRepository(notificationsService: sl()));
|
sl.registerLazySingleton<NotificationsRepository>(() => NotificationsRepository(notificationsService: sl()));
|
||||||
sl.registerFactory<MyRecipesRepository>(() => MyRecipesRepository(myRecipesService: sl()));
|
sl.registerFactory<MyRecipesRepository>(() => MyRecipesRepository(myRecipesService: sl()));
|
||||||
sl.registerFactory<ConsultantRatingRepository>(() => ConsultantRatingRepository(sl()));
|
sl.registerFactory<ResetPasswordRepository>(() => ResetPasswordRepository(resetPasswordService: sl()));
|
||||||
|
sl.registerLazySingleton<BlockedUsersRepository>(() => BlockedUsersRepository(blockedUserService: sl()));
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
|
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
|
||||||
@@ -219,9 +226,10 @@ 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<FirebaseApi>(() => FirebaseApi());
|
|
||||||
sl.registerLazySingleton<MyRecipesService>(() => MyRecipesService(sl()));
|
sl.registerLazySingleton<MyRecipesService>(() => MyRecipesService(sl()));
|
||||||
sl.registerLazySingleton<ConsultantRatingService>(() => ConsultantRatingService(sl()));
|
sl.registerLazySingleton<FirebaseApi>(() => FirebaseApi());
|
||||||
|
sl.registerLazySingleton<ResetPasswordService>(() => ResetPasswordService(sl()));
|
||||||
|
sl.registerLazySingleton<BlockedUserService>(() => BlockedUserService(sl()));
|
||||||
|
|
||||||
// External
|
// External
|
||||||
sl.registerLazySingleton(
|
sl.registerLazySingleton(
|
||||||
@@ -232,6 +240,7 @@ Future<void> init() async {
|
|||||||
'accept': 'application/json',
|
'accept': 'application/json',
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
},
|
},
|
||||||
|
validateStatus: (status) => status != null && status < 500,
|
||||||
),
|
),
|
||||||
)..interceptors.addAll(
|
)..interceptors.addAll(
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -7,162 +7,162 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
class CustomTextStyles {
|
class CustomTextStyles {
|
||||||
// Headline style
|
// Headline style
|
||||||
static get headlineSmallMulish => theme.textTheme.headlineSmall!.mulish;
|
static TextStyle get headlineSmallMulish => theme.textTheme.headlineSmall!.mulish;
|
||||||
|
|
||||||
// Title text style
|
// Title text style
|
||||||
static get titleMediumSegoeUI => theme.textTheme.titleMedium!.segoeUI.copyWith(
|
static TextStyle get titleMediumSegoeUI => theme.textTheme.titleMedium!.segoeUI.copyWith(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get bodyMediumBlack900 => theme.textTheme.bodyMedium!.copyWith(
|
static TextStyle get bodyMediumBlack900 => theme.textTheme.bodyMedium!.copyWith(
|
||||||
color: appTheme.black900,
|
color: appTheme.black900,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get bodyMediumBlack90013 => theme.textTheme.bodyMedium!.copyWith(
|
static TextStyle get bodyMediumBlack90013 => theme.textTheme.bodyMedium!.copyWith(
|
||||||
color: appTheme.black900,
|
color: appTheme.black900,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get bodyMediumBluegray800 => theme.textTheme.bodyMedium!.copyWith(
|
static TextStyle get bodyMediumBluegray800 => theme.textTheme.bodyMedium!.copyWith(
|
||||||
color: appTheme.black,
|
color: appTheme.black,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get bodyMediumBluegray800_1 => theme.textTheme.bodyMedium!.copyWith(
|
static TextStyle get bodyMediumBluegray800_1 => theme.textTheme.bodyMedium!.copyWith(
|
||||||
color: appTheme.black,
|
color: appTheme.black,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get bodyMediumBluegray800_2 => theme.textTheme.bodyMedium!.copyWith(
|
static TextStyle get bodyMediumBluegray800_2 => theme.textTheme.bodyMedium!.copyWith(
|
||||||
color: appTheme.black,
|
color: appTheme.black,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get bodyMediumWhiteA700 => theme.textTheme.bodyMedium!.copyWith(
|
static TextStyle get bodyMediumWhiteA700 => theme.textTheme.bodyMedium!.copyWith(
|
||||||
color: appTheme.whiteA700,
|
color: appTheme.whiteA700,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get bodySmallMulishBlack900 => theme.textTheme.bodySmall!.mulish.copyWith(
|
static TextStyle get bodySmallMulishBlack900 => theme.textTheme.bodySmall!.mulish.copyWith(
|
||||||
color: appTheme.black900,
|
color: appTheme.black900,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get bodySmallMulishBluegray800 => theme.textTheme.bodySmall!.mulish.copyWith(
|
static TextStyle get bodySmallMulishBluegray800 => theme.textTheme.bodySmall!.mulish.copyWith(
|
||||||
color: appTheme.black,
|
color: appTheme.black,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get bodySmallMulishBluegray80012 => theme.textTheme.bodySmall!.mulish.copyWith(
|
static TextStyle get bodySmallMulishBluegray80012 => theme.textTheme.bodySmall!.mulish.copyWith(
|
||||||
color: appTheme.black,
|
color: appTheme.black,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get bodySmallMulishBluegray80012_1 => theme.textTheme.bodySmall!.mulish.copyWith(
|
static TextStyle get bodySmallMulishBluegray80012_1 => theme.textTheme.bodySmall!.mulish.copyWith(
|
||||||
color: appTheme.black,
|
color: appTheme.black,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get bodySmallMulishBluegray900 => theme.textTheme.bodySmall!.mulish.copyWith(
|
static TextStyle get bodySmallMulishBluegray900 => theme.textTheme.bodySmall!.mulish.copyWith(
|
||||||
color: appTheme.blueGray900,
|
color: appTheme.blueGray900,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get bodySmallMulishGray600 => theme.textTheme.bodySmall!.mulish.copyWith(
|
static TextStyle get bodySmallMulishGray600 => theme.textTheme.bodySmall!.mulish.copyWith(
|
||||||
color: appTheme.gray600,
|
color: appTheme.gray600,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get bodySmallMulishPrimary => theme.textTheme.bodySmall!.mulish.copyWith(
|
static TextStyle get bodySmallMulishPrimary => theme.textTheme.bodySmall!.mulish.copyWith(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get bodySmallSegoeUIBlack900 => theme.textTheme.bodySmall!.segoeUI.copyWith(
|
static TextStyle get bodySmallSegoeUIBlack900 => theme.textTheme.bodySmall!.segoeUI.copyWith(
|
||||||
color: appTheme.black900,
|
color: appTheme.black900,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Label text style
|
// Label text style
|
||||||
static get labelLargeGray60003 => theme.textTheme.labelLarge!.copyWith(
|
static TextStyle get labelLargeGray60003 => theme.textTheme.labelLarge!.copyWith(
|
||||||
color: appTheme.gray60003,
|
color: appTheme.gray60003,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get labelLargeWhiteA700 => theme.textTheme.labelLarge!.copyWith(
|
static TextStyle get labelLargeWhiteA700 => theme.textTheme.labelLarge!.copyWith(
|
||||||
color: appTheme.whiteA700,
|
color: appTheme.whiteA700,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Title text style
|
// Title text style
|
||||||
static get titleMedium16 => theme.textTheme.titleMedium!.copyWith(
|
static TextStyle get titleMedium16 => theme.textTheme.titleMedium!.copyWith(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get titleMediumBlack => theme.textTheme.titleMedium!.copyWith(
|
static TextStyle get titleMediumBlack => theme.textTheme.titleMedium!.copyWith(
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get titleMediumBlack900 => theme.textTheme.titleMedium!.copyWith(
|
static TextStyle get titleMediumBlack900 => theme.textTheme.titleMedium!.copyWith(
|
||||||
color: appTheme.black900,
|
color: appTheme.black900,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get titleMediumSemiBold => theme.textTheme.titleMedium!.copyWith(
|
static TextStyle get titleMediumSemiBold => theme.textTheme.titleMedium!.copyWith(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get titleMediumWhiteA700 => theme.textTheme.titleMedium!.copyWith(
|
static TextStyle get titleMediumWhiteA700 => theme.textTheme.titleMedium!.copyWith(
|
||||||
color: appTheme.whiteA700,
|
color: appTheme.whiteA700,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get titleSmallBlue800 => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallBlue800 => theme.textTheme.titleSmall!.copyWith(
|
||||||
color: appTheme.blue800,
|
color: appTheme.blue800,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get titleSmallBluegray800 => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallBluegray800 => theme.textTheme.titleSmall!.copyWith(
|
||||||
color: appTheme.black,
|
color: appTheme.black,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get titleSmallBluegray800Bold => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallBluegray800Bold => theme.textTheme.titleSmall!.copyWith(
|
||||||
color: appTheme.black,
|
color: appTheme.black,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get titleSmallBluegray800ExtraBold => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallBluegray800ExtraBold => theme.textTheme.titleSmall!.copyWith(
|
||||||
color: appTheme.black,
|
color: appTheme.black,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get titleSmallBold => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallBold => theme.textTheme.titleSmall!.copyWith(
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get titleSmallGreenA70001 => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallGreenA70001 => theme.textTheme.titleSmall!.copyWith(
|
||||||
color: appTheme.greenA70001,
|
color: appTheme.greenA70001,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get titleSmallGreenA70001ExtraBold => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallGreenA70001ExtraBold => theme.textTheme.titleSmall!.copyWith(
|
||||||
color: appTheme.greenA70001,
|
color: appTheme.greenA70001,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get titleSmallLightgreen300 => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallLightgreen300 => theme.textTheme.titleSmall!.copyWith(
|
||||||
color: appTheme.lightGreen300,
|
color: appTheme.lightGreen300,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get titleSmallRed200 => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallRed200 => theme.textTheme.titleSmall!.copyWith(
|
||||||
color: appTheme.red200,
|
color: appTheme.red200,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get titleSmallSecondaryContainer => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallSecondaryContainer => theme.textTheme.titleSmall!.copyWith(
|
||||||
color: theme.colorScheme.secondaryContainer,
|
color: theme.colorScheme.secondaryContainer,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get titleSmallSecondaryContainer_1 => theme.textTheme.titleSmall!.copyWith(
|
static TextStyle get titleSmallSecondaryContainer_1 => theme.textTheme.titleSmall!.copyWith(
|
||||||
color: theme.colorScheme.secondaryContainer,
|
color: theme.colorScheme.secondaryContainer,
|
||||||
);
|
);
|
||||||
|
|
||||||
static get titleSmallSegoeUIOnPrimary => theme.textTheme.titleSmall!.segoeUI.copyWith(
|
static TextStyle get titleSmallSegoeUIOnPrimary => theme.textTheme.titleSmall!.segoeUI.copyWith(
|
||||||
color: theme.colorScheme.onPrimary,
|
color: theme.colorScheme.onPrimary,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ abstract class TokenStorage {
|
|||||||
|
|
||||||
static Future<String?> getTokenFromStorage() async => await const FlutterSecureStorage().read(key: 'token');
|
static Future<String?> getTokenFromStorage() async => await const FlutterSecureStorage().read(key: 'token');
|
||||||
|
|
||||||
static saveTokenInStorage(String token) async {
|
static Future<void> saveTokenInStorage(String token) async {
|
||||||
await const FlutterSecureStorage().write(key: 'token', value: token);
|
await const FlutterSecureStorage().write(key: 'token', value: token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ extension DateTimeExtension on DateTime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String calculateDifference(DateTime dateTime) {
|
String calculateDifference(DateTime dateTime, [String? format]) {
|
||||||
final localDateTime = dateTime.isUtc ? dateTime.toLocal() : dateTime;
|
final localDateTime = dateTime.isUtc ? dateTime.toLocal() : dateTime;
|
||||||
|
|
||||||
if (localDateTime.isToday()) {
|
if (localDateTime.isToday()) {
|
||||||
@@ -41,6 +41,6 @@ String calculateDifference(DateTime dateTime) {
|
|||||||
} else if (localDateTime.isYesterday()) {
|
} else if (localDateTime.isYesterday()) {
|
||||||
return 'Вчера';
|
return 'Вчера';
|
||||||
} else {
|
} else {
|
||||||
return localDateTime.format('d MMMM', 'ru');
|
return localDateTime.format(format ?? 'd MMMM', 'ru');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
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: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/websockets/ws_service.dart';
|
import 'package:eatvio_app/core/websockets/ws_service.dart';
|
||||||
import 'package:eatvio_app/core/token_storage.dart';
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
|
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||||
|
|
||||||
class WsManager {
|
class WsManager {
|
||||||
final WsService wsService;
|
final WsService wsService;
|
||||||
@@ -39,6 +42,7 @@ class WsManager {
|
|||||||
metadata: const PusherChannelsOptionsMetadata.byDefault(),
|
metadata: const PusherChannelsOptionsMetadata.byDefault(),
|
||||||
port: 443,
|
port: 443,
|
||||||
);
|
);
|
||||||
|
|
||||||
client = PusherChannelsClient.websocket(
|
client = PusherChannelsClient.websocket(
|
||||||
options: options,
|
options: options,
|
||||||
connectionErrorHandler: (exception, trace, refresh) {
|
connectionErrorHandler: (exception, trace, refresh) {
|
||||||
@@ -70,6 +74,11 @@ class WsManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
client?.eventStream.listen((event) {
|
client?.eventStream.listen((event) {
|
||||||
|
EasyDebounce.debounce(
|
||||||
|
'searchChatRoom',
|
||||||
|
const Duration(milliseconds: 300),
|
||||||
|
() => sl<ChatsCubit>().getChatRooms(),
|
||||||
|
);
|
||||||
log(event.rootObject.toString());
|
log(event.rootObject.toString());
|
||||||
});
|
});
|
||||||
_subscribe();
|
_subscribe();
|
||||||
@@ -136,7 +145,7 @@ class WsManager {
|
|||||||
_privateDirectChatChannel?.unsubscribe();
|
_privateDirectChatChannel?.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> clear() async{
|
Future<void> clear() async {
|
||||||
await client?.disconnect();
|
await client?.disconnect();
|
||||||
client = null;
|
client = null;
|
||||||
userId = null;
|
userId = null;
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class CustomDropDown extends StatelessWidget {
|
|||||||
: _buildDropDownWidget(context);
|
: _buildDropDownWidget(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildDropDownWidget(BuildContext context) {
|
DropdownButtonFormField<SelectionPopupModel> _buildDropDownWidget(BuildContext context) {
|
||||||
return DropdownButtonFormField<SelectionPopupModel>(
|
return DropdownButtonFormField<SelectionPopupModel>(
|
||||||
focusNode: focusNode ?? FocusNode(),
|
focusNode: focusNode ?? FocusNode(),
|
||||||
icon: const Icon(Icons.keyboard_arrow_down_rounded),
|
icon: const Icon(Icons.keyboard_arrow_down_rounded),
|
||||||
@@ -84,7 +84,7 @@ class CustomDropDown extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDropDownChanged(value) {
|
void onDropDownChanged(dynamic value) {
|
||||||
onChanged!(value!);
|
onChanged!(value!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
|||||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
import 'package:eatvio_app/features/feed/models/recipes_model.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/recipes_cubit/recipes_cubit.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
|
||||||
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
|
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_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';
|
||||||
@@ -28,6 +29,7 @@ class CustomPopup extends StatelessWidget {
|
|||||||
final FeedBlogPostModel? feedBlogPostModel;
|
final FeedBlogPostModel? feedBlogPostModel;
|
||||||
final Recipe? recipe;
|
final Recipe? recipe;
|
||||||
final Diet? diet;
|
final Diet? diet;
|
||||||
|
final String userSlug;
|
||||||
|
|
||||||
const CustomPopup({
|
const CustomPopup({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -39,6 +41,7 @@ class CustomPopup extends StatelessWidget {
|
|||||||
this.feedBlogPostModel,
|
this.feedBlogPostModel,
|
||||||
this.recipe,
|
this.recipe,
|
||||||
this.diet,
|
this.diet,
|
||||||
|
required this.userSlug
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -73,7 +76,7 @@ class CustomPopup extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<PopupMenuEntry> _buildMenuItems(s, context) {
|
List<PopupMenuEntry> _buildMenuItems(S s,BuildContext context) {
|
||||||
final entries = <PopupMenuEntry>[];
|
final entries = <PopupMenuEntry>[];
|
||||||
|
|
||||||
void addItem(String icon, String text, Function() onTap) {
|
void addItem(String icon, String text, Function() onTap) {
|
||||||
@@ -107,28 +110,28 @@ class CustomPopup extends StatelessWidget {
|
|||||||
addItem('assets/icons/red_trash.svg', 'Удалить', () async {
|
addItem('assets/icons/red_trash.svg', 'Удалить', () async {
|
||||||
if (complainedContentType == ComplainedContent.posts) {
|
if (complainedContentType == ComplainedContent.posts) {
|
||||||
final result = await sl<BlogPostCreateCubit>().deleteBlogPost(id!);
|
final result = await sl<BlogPostCreateCubit>().deleteBlogPost(id!);
|
||||||
if (!result) {
|
if (!result && context.mounted) {
|
||||||
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
|
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showSuccessToast('Пост удален, обновите ленту');
|
showSuccessToast('Пост удален, обновите ленту');
|
||||||
} else if (complainedContentType == ComplainedContent.recipe) {
|
} else if (complainedContentType == ComplainedContent.recipe) {
|
||||||
final result = await sl<RecipesCubit>().deleteRecipe(slug!);
|
final result = await sl<RecipesCubit>().deleteRecipe(slug!);
|
||||||
if (!result) {
|
if (!result && context.mounted) {
|
||||||
showErrorDialog(context, title: 'Не удалось удалить рецепт');
|
showErrorDialog(context, title: 'Не удалось удалить рецепт');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showSuccessToast('Рецепт удален, обновите ленту');
|
showSuccessToast('Рецепт удален, обновите ленту');
|
||||||
} else if (complainedContentType == ComplainedContent.diet) {
|
} else if (complainedContentType == ComplainedContent.diet) {
|
||||||
final result = await sl<DietsCubit>().deleteDiet(id!);
|
final result = await sl<DietsCubit>().deleteDiet(id!);
|
||||||
if (!result) {
|
if (!result && context.mounted) {
|
||||||
showErrorDialog(context, title: 'Не удалось удалить диету');
|
showErrorDialog(context, title: 'Не удалось удалить диету');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showSuccessToast('Диета удалена, обновите ленту');
|
showSuccessToast('Диета удалена, обновите ленту');
|
||||||
} else if (complainedContentType == ComplainedContent.plan) {
|
} else if (complainedContentType == ComplainedContent.plan) {
|
||||||
final result = await sl<MyPlansCubit>().deletePlan(slug!);
|
final result = await sl<MyPlansCubit>().deletePlan(slug!);
|
||||||
if (!result) {
|
if (!result && context.mounted) {
|
||||||
showErrorDialog(context, title: 'Не удалось удалить план');
|
showErrorDialog(context, title: 'Не удалось удалить план');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -164,6 +167,13 @@ class CustomPopup extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
addItem(
|
||||||
|
'assets/icons/complain.svg',
|
||||||
|
'Заблокировать автора',
|
||||||
|
() {
|
||||||
|
sl<BlockedUsersCubit>().blockUser(userSlug);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entries.isNotEmpty && entries.last is PopupMenuItem) {
|
if (entries.isNotEmpty && entries.last is PopupMenuItem) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'dart:developer';
|
|||||||
|
|
||||||
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';
|
||||||
import 'package:eatvio_app/features/auth/presentation/bloc/auth_state.dart';
|
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_state.dart';
|
||||||
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
|
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_state.dart';
|
||||||
|
import 'package:eatvio_app/features/auth/repositories/reset_password_repository.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class ResetPasswordCubit extends Cubit<ResetPasswordState> {
|
||||||
|
final ResetPasswordRepository resetPasswordRepository;
|
||||||
|
|
||||||
|
ResetPasswordCubit({required this.resetPasswordRepository}) : super(const ResetPasswordState.empty());
|
||||||
|
|
||||||
|
Future<void> trySendEmailCode(String email) async {
|
||||||
|
emit(const ResetPasswordState.loadingSendEmailCode());
|
||||||
|
try {
|
||||||
|
if (await resetPasswordRepository.sendEmailCodeForPasswordChange(email)) {
|
||||||
|
emit(const ResetPasswordState.codeSendSuccess());
|
||||||
|
} else {
|
||||||
|
emit(const ResetPasswordState.codeSendError());
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(const ResetPasswordState.codeSendError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tryResetPassword(String email, String code, String password, String passwordConfirmation) async {
|
||||||
|
emit(const ResetPasswordState.loadingResetPassword());
|
||||||
|
try {
|
||||||
|
if (await resetPasswordRepository.confirmPasswordChange(code, password, passwordConfirmation, email)) {
|
||||||
|
emit(const ResetPasswordState.resetPasswordSuccess());
|
||||||
|
} else {
|
||||||
|
emit(const ResetPasswordState.resetPasswordError());
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(const ResetPasswordState.resetPasswordError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'reset_password_state.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ResetPasswordState with _$ResetPasswordState {
|
||||||
|
const factory ResetPasswordState() = _ResetPasswordState;
|
||||||
|
|
||||||
|
const factory ResetPasswordState.empty() = ResetPasswordStateEmpty;
|
||||||
|
|
||||||
|
const factory ResetPasswordState.loadingSendEmailCode() = ResetPasswordStateLoadingSendEmailCode;
|
||||||
|
|
||||||
|
const factory ResetPasswordState.codeSendSuccess() = ResetPasswordStateCodeSendSuccess;
|
||||||
|
|
||||||
|
const factory ResetPasswordState.codeSendError() = ResetPasswordStateCodeSendError;
|
||||||
|
|
||||||
|
const factory ResetPasswordState.loadingResetPassword() = ResetPasswordStateLoadingResetPassword;
|
||||||
|
|
||||||
|
const factory ResetPasswordState.resetPasswordError() = ResetPasswordStateResetPasswordError;
|
||||||
|
|
||||||
|
const factory ResetPasswordState.resetPasswordSuccess() = ResetPasswordStateResetPassowrdSuccess;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
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/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';
|
||||||
@@ -6,8 +7,8 @@ import 'package:eatvio_app/core/utils/image_constant.dart';
|
|||||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
import 'package:eatvio_app/core/websockets/ws_manager.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/features/auth/presentation/bloc/auth_cubit.dart';
|
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
|
||||||
import 'package:eatvio_app/features/auth/presentation/bloc/auth_state.dart';
|
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_state.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/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||||
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
|
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
|
||||||
@@ -52,17 +53,28 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _reconnectWebSocket(BuildContext context) async {
|
Future<void> _reconnectWebSocket(BuildContext context) async {
|
||||||
await sl<WsManager>().clear();
|
try {
|
||||||
await sl<WsManager>().initialize();
|
await sl<WsManager>().clear();
|
||||||
if (context.mounted) {
|
await sl<WsManager>().initialize();
|
||||||
context.read<ProfileAvatarCubit>().getMyProfile();
|
} catch (e) {
|
||||||
context.read<ChatsCubit>().getChatRoomsWithLoading();
|
rethrow;
|
||||||
context.read<ChatsCubit>().connectToWebSocket();
|
|
||||||
context.read<NotificationsCubit>().getNotifications();
|
|
||||||
context.read<NotificationsCubit>().connectToWebSocket();
|
|
||||||
showSuccessToast('Успешный вход');
|
|
||||||
GoRouter.of(context).go('/feed-screen', extra: {'needToInitializeWebSocket': false});
|
|
||||||
}
|
}
|
||||||
|
if (context.mounted) {
|
||||||
|
try {
|
||||||
|
await sl<ProfileAvatarCubit>().getMyProfile();
|
||||||
|
await sl<ChatsCubit>().getChatRoomsWithLoading();
|
||||||
|
await sl<ChatsCubit>().connectToWebSocket();
|
||||||
|
await sl<NotificationsCubit>().getNotifications();
|
||||||
|
await sl<NotificationsCubit>().connectToWebSocket();
|
||||||
|
showSuccessToast('Успешный вход');
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AppRouter.navigatorKey.currentContext?.go(
|
||||||
|
'/feed-screen',
|
||||||
|
extra: {'needToInitializeWebSocket': false},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -80,10 +92,10 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
title: GestureDetector(
|
title: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
tapsCount++;
|
tapsCount++;
|
||||||
if (tapsCount == 6) {
|
if (tapsCount == 7) {
|
||||||
tapsCount = 0;
|
tapsCount = 0;
|
||||||
|
GoRouter.of(context).pushNamed(DevToolsScreen.name);
|
||||||
}
|
}
|
||||||
GoRouter.of(context).pushNamed(DevToolsScreen.name);
|
|
||||||
},
|
},
|
||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
ImageConstant.imgLogoEatvio,
|
ImageConstant.imgLogoEatvio,
|
||||||
@@ -127,6 +139,20 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
Center(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => GoRouter.of(context).goNamed('forgetPasswordScreen'),
|
||||||
|
child: Text(
|
||||||
|
'Забыли пароль?',
|
||||||
|
style: TextStyle(
|
||||||
|
color: appTheme.blueA400,
|
||||||
|
decorationColor: appTheme.blueA400,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@@ -182,38 +208,137 @@ class _AuthScreenState extends State<AuthScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
SizedBox(
|
Row(
|
||||||
height: 48,
|
children: [
|
||||||
width: MediaQuery.of(context).size.width - 32,
|
SizedBox(
|
||||||
child: FloatingActionButton.extended(
|
height: 48,
|
||||||
heroTag: 'authButton',
|
width: MediaQuery.of(context).size.width - 88,
|
||||||
highlightElevation: 0,
|
child: FloatingActionButton.extended(
|
||||||
elevation: 0,
|
heroTag: 'authButton',
|
||||||
focusElevation: 0,
|
highlightElevation: 0,
|
||||||
shape: RoundedRectangleBorder(
|
elevation: 0,
|
||||||
borderRadius: BorderRadius.circular(20),
|
focusElevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
backgroundColor: state is AuthStateLoading ? Colors.black.withAlpha(50) : appTheme.blueA400,
|
||||||
|
onPressed: state is AuthStateLoading
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
final isAuthenticated = await authCubit.getTokenByCredentials(
|
||||||
|
emailController.text,
|
||||||
|
passwordController.text,
|
||||||
|
);
|
||||||
|
if (isAuthenticated) {
|
||||||
|
if (context.mounted) {
|
||||||
|
_reconnectWebSocket(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: state is AuthStateLoading
|
||||||
|
? const CupertinoActivityIndicator()
|
||||||
|
: Text(
|
||||||
|
'Войти',
|
||||||
|
style: theme.textTheme.titleSmall!.copyWith(color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
backgroundColor: state is AuthStateLoading ? Colors.black.withAlpha(50) : appTheme.blueA400,
|
const SizedBox(width: 8),
|
||||||
onPressed: state is AuthStateLoading
|
SizedBox(
|
||||||
? null
|
height: 48,
|
||||||
: () async {
|
width: 48,
|
||||||
final isAuthenticated = await authCubit.getTokenByCredentials(
|
child: FloatingActionButton.extended(
|
||||||
emailController.text,
|
heroTag: 'scanQrCodeButton',
|
||||||
passwordController.text,
|
highlightElevation: 0,
|
||||||
);
|
elevation: 0,
|
||||||
if (isAuthenticated) {
|
focusElevation: 0,
|
||||||
if (context.mounted) {
|
shape: RoundedRectangleBorder(
|
||||||
_reconnectWebSocket(context);
|
borderRadius: BorderRadius.circular(16),
|
||||||
}
|
),
|
||||||
}
|
backgroundColor: state is AuthStateLoading ? Colors.black.withAlpha(50) : appTheme.blueA400,
|
||||||
},
|
onPressed: state is AuthStateLoading
|
||||||
label: state is AuthStateLoading
|
? null
|
||||||
? const CupertinoActivityIndicator()
|
: () async {
|
||||||
: Text(
|
await showDialog(
|
||||||
'Войти',
|
context: context,
|
||||||
style: theme.textTheme.titleSmall!.copyWith(color: Colors.white),
|
builder: (BuildContext context) {
|
||||||
),
|
return Dialog(
|
||||||
),
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Где взять QR-код?\n',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'1. Войдите на сайте $baseUrl в свой аккаунт\n\n2. Перейдите в настройки профиля\n\n3. В разделе "Доп информация" перейдите на "QR Login"'),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
onTap: () {
|
||||||
|
AppRouter.navigatorKey.currentContext?.pop();
|
||||||
|
},
|
||||||
|
child: Ink(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Отмена',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
onTap: () {
|
||||||
|
AppRouter.navigatorKey.currentContext?.pop();
|
||||||
|
AppRouter.navigatorKey.currentContext?.pushNamed('scanQRCodeScreen');
|
||||||
|
},
|
||||||
|
child: Ink(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Перейти',
|
||||||
|
style: TextStyle(
|
||||||
|
color: appTheme.blueA400,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
label: state is AuthStateLoading
|
||||||
|
? const CupertinoActivityIndicator()
|
||||||
|
: const Icon(
|
||||||
|
Icons.qr_code_scanner_rounded,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
165
lib/features/auth/presentation/pages/forget_password_screen.dart
Normal file
165
lib/features/auth/presentation/pages/forget_password_screen.dart
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import 'package:eatvio_app/core/locator_service.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_text_form_field.dart';
|
||||||
|
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_state.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
class ForgetPasswordScreen extends StatefulWidget {
|
||||||
|
const ForgetPasswordScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ForgetPasswordScreen> createState() => _ForgetPasswordScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ForgetPasswordScreenState extends State<ForgetPasswordScreen> {
|
||||||
|
final emailController = TextEditingController();
|
||||||
|
final codeController = TextEditingController();
|
||||||
|
final passwordController = TextEditingController();
|
||||||
|
final passwordConfirmController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider<ResetPasswordCubit>(
|
||||||
|
create: (BuildContext context) => sl<ResetPasswordCubit>(),
|
||||||
|
child: BlocConsumer<ResetPasswordCubit, ResetPasswordState>(
|
||||||
|
builder: (BuildContext context, ResetPasswordState state) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Смена пароля'),
|
||||||
|
centerTitle: false,
|
||||||
|
),
|
||||||
|
body: state is ResetPasswordStateLoadingResetPassword || state is ResetPasswordStateLoadingSendEmailCode
|
||||||
|
? const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
)
|
||||||
|
: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (state is ResetPasswordStateCodeSendError || state is ResetPasswordStateEmpty) ...[
|
||||||
|
const Text(
|
||||||
|
'Забыли пароль? Укажите свой email,\nи мы пришлём код для сброса.',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
const Text(
|
||||||
|
'E-mail',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
CustomTextFormField(
|
||||||
|
controller: emailController,
|
||||||
|
hintText: 'Введите e-mail',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (state is ResetPasswordStateCodeSendSuccess || state is ResetPasswordStateResetPasswordError) ...[
|
||||||
|
const Text(
|
||||||
|
'Код',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
CustomTextFormField(
|
||||||
|
controller: codeController,
|
||||||
|
hintText: 'Введите код',
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
const Text(
|
||||||
|
'Пароль',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
CustomTextFormField(
|
||||||
|
controller: passwordController,
|
||||||
|
hintText: 'Введите новый пароль',
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
const Text(
|
||||||
|
'Подтвердите пароль',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
CustomTextFormField(
|
||||||
|
controller: passwordConfirmController,
|
||||||
|
hintText: 'Введите новый пароль',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const Spacer(),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
if (state is ResetPasswordStateCodeSendSuccess || state is ResetPasswordStateResetPasswordError) {
|
||||||
|
context.read<ResetPasswordCubit>().tryResetPassword(
|
||||||
|
emailController.text,
|
||||||
|
codeController.text,
|
||||||
|
passwordController.text,
|
||||||
|
passwordConfirmController.text,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
context.read<ResetPasswordCubit>().trySendEmailCode(emailController.text);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
child: Ink(
|
||||||
|
width: MediaQuery.of(context).size.width - 32,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
color: appTheme.blueA400,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
state is ResetPasswordStateCodeSendSuccess || state is ResetPasswordStateResetPasswordError
|
||||||
|
? 'Сменить пароль'
|
||||||
|
: 'Сбросить пароль',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
listener: (BuildContext context, ResetPasswordState state) {
|
||||||
|
if (state is ResetPasswordStateCodeSendSuccess) {
|
||||||
|
showSuccessToast('Код на почту успешно отправлен');
|
||||||
|
}
|
||||||
|
if (state is ResetPasswordStateCodeSendError) {
|
||||||
|
showErrorToast('Ошибка отправки кода. Проверьте правильность ввёдной почты');
|
||||||
|
}
|
||||||
|
if (state is ResetPasswordStateResetPasswordError) {
|
||||||
|
showErrorToast('Ошибка смены пароля. Проверьте правильность ввёднного кода и совпадение пароля');
|
||||||
|
}
|
||||||
|
if (state is ResetPasswordStateResetPassowrdSuccess) {
|
||||||
|
GoRouter.of(context).pop();
|
||||||
|
showSuccessToast('Пароль успешно сменён. Войдите в аккаунт');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
109
lib/features/auth/presentation/pages/scan_qr_code_screen.dart
Normal file
109
lib/features/auth/presentation/pages/scan_qr_code_screen.dart
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:eatvio_app/core/app_router.dart';
|
||||||
|
import 'package:eatvio_app/core/const.dart';
|
||||||
|
import 'package:eatvio_app/core/locator_service.dart';
|
||||||
|
import 'package:eatvio_app/core/token_storage.dart';
|
||||||
|
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||||
|
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||||
|
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart';
|
||||||
|
|
||||||
|
class ScanQRCodeScreen extends StatefulWidget {
|
||||||
|
const ScanQRCodeScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ScanQRCodeScreen> createState() => _ScanQRCodeScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScanQRCodeScreenState extends State<ScanQRCodeScreen> {
|
||||||
|
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
|
||||||
|
Barcode? result;
|
||||||
|
QRViewController? controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void reassemble() {
|
||||||
|
super.reassemble();
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
controller!.pauseCamera();
|
||||||
|
} else if (Platform.isIOS) {
|
||||||
|
controller!.resumeCamera();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Сканирование QR-кода'),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: QRView(
|
||||||
|
key: qrKey,
|
||||||
|
onQRViewCreated: _onQRViewCreated,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onQRViewCreated(QRViewController controller) {
|
||||||
|
this.controller = controller;
|
||||||
|
controller.scannedDataStream.listen((scanData) async {
|
||||||
|
setState(() {
|
||||||
|
result = scanData;
|
||||||
|
});
|
||||||
|
await controller.pauseCamera();
|
||||||
|
try {
|
||||||
|
final scannedUrl = scanData.code;
|
||||||
|
final uri = Uri.parse(scannedUrl ?? '');
|
||||||
|
final scannedBaseUrl = (uri.hasAuthority) ? '${uri.scheme}://${uri.host}' : '';
|
||||||
|
baseUrl = scannedBaseUrl;
|
||||||
|
sl<Dio>().options.baseUrl = baseUrl;
|
||||||
|
final response = await Dio().getUri(
|
||||||
|
uri,
|
||||||
|
options: Options(
|
||||||
|
headers: {
|
||||||
|
'accept': 'application/json',
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final token = response.data['token'];
|
||||||
|
await TokenStorage.saveTokenInStorage(token);
|
||||||
|
try {
|
||||||
|
await sl<WsManager>().clear();
|
||||||
|
await sl<WsManager>().initialize();
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await sl<ProfileAvatarCubit>().getMyProfile();
|
||||||
|
await sl<ChatsCubit>().getChatRoomsWithLoading();
|
||||||
|
await sl<ChatsCubit>().connectToWebSocket();
|
||||||
|
await sl<NotificationsCubit>().getNotifications();
|
||||||
|
await sl<NotificationsCubit>().connectToWebSocket();
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
AppRouter.navigatorKey.currentContext?.go(
|
||||||
|
'/feed-screen',
|
||||||
|
extra: {'needToInitializeWebSocket': false},
|
||||||
|
);
|
||||||
|
showSuccessToast('Добро пожаловать!');
|
||||||
|
} catch (e) {
|
||||||
|
await controller.resumeCamera();
|
||||||
|
showErrorToast('Что-то пошло не так. Попробуйте обновить страницу с QR-кодом или войдите с помощью логина и пароля.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import 'package:eatvio_app/features/auth/services/reset_password_service.dart';
|
||||||
|
|
||||||
|
class ResetPasswordRepository {
|
||||||
|
final ResetPasswordService resetPasswordService;
|
||||||
|
|
||||||
|
ResetPasswordRepository({
|
||||||
|
required this.resetPasswordService,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<bool> sendEmailCodeForPasswordChange(String email) async {
|
||||||
|
try {
|
||||||
|
final response = await resetPasswordService.sendEmailCodeForPasswordChange(
|
||||||
|
data: {
|
||||||
|
'email': email,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
int statusCode = response.response.statusCode ?? 0;
|
||||||
|
if (statusCode >= 200 && statusCode < 300) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> confirmPasswordChange(
|
||||||
|
String code,
|
||||||
|
String password,
|
||||||
|
String passwordConfirmation,
|
||||||
|
String email,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final response = await resetPasswordService.confirmPasswordChange(
|
||||||
|
data: {
|
||||||
|
'code': code,
|
||||||
|
'password': password,
|
||||||
|
'password_confirmation': passwordConfirmation,
|
||||||
|
'email': email,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
int statusCode = response.response.statusCode ?? 0;
|
||||||
|
if (statusCode >= 200 && statusCode < 300) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
lib/features/auth/services/reset_password_service.dart
Normal file
19
lib/features/auth/services/reset_password_service.dart
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:retrofit/retrofit.dart';
|
||||||
|
|
||||||
|
part 'reset_password_service.g.dart';
|
||||||
|
|
||||||
|
@RestApi()
|
||||||
|
abstract class ResetPasswordService {
|
||||||
|
factory ResetPasswordService(Dio dio, {String baseUrl}) = _ResetPasswordService;
|
||||||
|
|
||||||
|
@POST('/api/send_password_reset_code')
|
||||||
|
Future<HttpResponse> sendEmailCodeForPasswordChange({
|
||||||
|
@Body() required Map<String, dynamic> data,
|
||||||
|
});
|
||||||
|
|
||||||
|
@POST('/api/confirm_password_reset_code')
|
||||||
|
Future<HttpResponse> confirmPasswordChange({
|
||||||
|
@Body() required Map<String, dynamic> data,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_state.dart';
|
||||||
|
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class BlogPostCubit extends Cubit<BlogPostState> {
|
||||||
|
final FeedRepository feedRepository;
|
||||||
|
BlogPostCubit(this.feedRepository) : super(BlogPostState());
|
||||||
|
|
||||||
|
Future<void> getBlogPost(int id) async {
|
||||||
|
try {
|
||||||
|
emit(state.copyWith(isLoading: true));
|
||||||
|
final postModel = await feedRepository.getBlogPost(id: id);
|
||||||
|
emit(state.copyWith(blogPostModel: postModel, isLoading: false));
|
||||||
|
} catch (e, st) {
|
||||||
|
log(e.toString(), stackTrace: st);
|
||||||
|
emit(state.copyWith(isLoading: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'blog_post_state.freezed.dart';
|
||||||
|
part 'blog_post_state.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class BlogPostState with _$BlogPostState {
|
||||||
|
|
||||||
|
factory BlogPostState({
|
||||||
|
@Default(null) FeedBlogPostModel? blogPostModel,
|
||||||
|
@Default(false) bool isLoading,
|
||||||
|
@Default(null) String? error,
|
||||||
|
}) = _BlogPostState;
|
||||||
|
|
||||||
|
factory BlogPostState.fromJson(Map<String, dynamic> json) => _$BlogPostStateFromJson(json);
|
||||||
|
}
|
||||||
@@ -5,7 +5,8 @@ import 'package:eatvio_app/core/enums/complained_content.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/core/widgets/custom_popup.dart';
|
import 'package:eatvio_app/core/widgets/custom_popup.dart';
|
||||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_state.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart';
|
||||||
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_state.dart';
|
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_state.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';
|
||||||
@@ -19,9 +20,9 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
class BlogPostScreen extends StatefulWidget {
|
class BlogPostScreen extends StatefulWidget {
|
||||||
final FeedBlogPostModel blogPost;
|
final int id;
|
||||||
|
|
||||||
const BlogPostScreen({super.key, required this.blogPost});
|
const BlogPostScreen({super.key, required this.id});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<BlogPostScreen> createState() => _BlogPostScreenState();
|
State<BlogPostScreen> createState() => _BlogPostScreenState();
|
||||||
@@ -31,263 +32,294 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
|||||||
final CommentsCubit commentsCubit = sl<CommentsCubit>();
|
final CommentsCubit commentsCubit = sl<CommentsCubit>();
|
||||||
final textEditingController = TextEditingController();
|
final textEditingController = TextEditingController();
|
||||||
int? replyId;
|
int? replyId;
|
||||||
late bool isLiked;
|
bool isLiked = false;
|
||||||
late int totalLikes;
|
int totalLikes = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
isLiked = widget.blogPost.wasLikedByMe;
|
context.read<FeedCubit>().viewPost(widget.id);
|
||||||
totalLikes = widget.blogPost.totalLikes;
|
|
||||||
context.read<FeedCubit>().viewPost(widget.blogPost.id);
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider<CommentsCubit>(
|
return BlocProvider(
|
||||||
create: (BuildContext context) => commentsCubit..getComments(widget.blogPost.id.toString(), CommentSourceType.blogPost, true),
|
create: (context) => sl<BlogPostCubit>()..getBlogPost(widget.id),
|
||||||
child: BlocConsumer<CommentsCubit, CommentsState>(
|
child: BlocProvider<CommentsCubit>(
|
||||||
listener: (BuildContext context, CommentsState state) {
|
create: (BuildContext context) => commentsCubit..getComments(widget.id.toString(), CommentSourceType.blogPost, true),
|
||||||
if (state is CommentsStateLoaded) {
|
child: BlocBuilder<BlogPostCubit, BlogPostState>(
|
||||||
setState(() {
|
builder: (context, blogPostState) {
|
||||||
replyId = state.replyId;
|
return BlocConsumer<CommentsCubit, CommentsState>(
|
||||||
});
|
listener: (BuildContext context, CommentsState state) {
|
||||||
}
|
if (state is CommentsStateLoaded) {
|
||||||
},
|
setState(() {
|
||||||
builder: (BuildContext context, CommentsState state) {
|
replyId = state.replyId;
|
||||||
return Scaffold(
|
});
|
||||||
resizeToAvoidBottomInset: true,
|
}
|
||||||
bottomSheet: Container(
|
},
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12) + (Platform.isIOS ? const EdgeInsets.only(bottom: 16) : EdgeInsets.zero),
|
builder: (BuildContext context, CommentsState state) {
|
||||||
decoration: BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.1)))),
|
if (blogPostState.isLoading) {
|
||||||
child: Row(
|
return Scaffold(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
appBar: AppBar(
|
||||||
children: [
|
leading: BackButton(
|
||||||
if (replyId != null)
|
onPressed: () {
|
||||||
GestureDetector(
|
if (context.canPop()) {
|
||||||
onTap: () {
|
GoRouter.of(context).pop();
|
||||||
context.read<CommentsCubit>().setReplyId(null);
|
return;
|
||||||
setState(() {
|
}
|
||||||
replyId = null;
|
GoRouter.of(context).go('/feed-screen');
|
||||||
textEditingController.clear();
|
|
||||||
});
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
|
|
||||||
height: 32,
|
|
||||||
width: 32,
|
|
||||||
child: const Icon(Icons.close_rounded, size: 18, color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (replyId != null) const SizedBox(width: 8),
|
|
||||||
SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width - (replyId == null ? 64 : 104),
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: const BoxConstraints(minHeight: 52),
|
|
||||||
child: TextFormField(
|
|
||||||
textCapitalization: TextCapitalization.sentences,
|
|
||||||
onChanged: (_) {
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
controller: textEditingController,
|
|
||||||
maxLines: 5,
|
|
||||||
minLines: 1,
|
|
||||||
keyboardType: TextInputType.multiline,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: Colors.black,
|
|
||||||
height: 1.2,
|
|
||||||
),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
isDense: false,
|
|
||||||
// убираем true, чтобы padding учитывался корректно
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
|
||||||
// вертикальный паддинг увеличен
|
|
||||||
hintText: 'Комментарий',
|
|
||||||
hintStyle: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
height: 1.2,
|
|
||||||
color: Color(0xFFAEAEB2),
|
|
||||||
),
|
|
||||||
filled: true,
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
|
||||||
),
|
|
||||||
enabledBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
|
||||||
),
|
|
||||||
focusedBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
if (textEditingController.text.length > 2) {
|
|
||||||
commentsCubit.sendComment(
|
|
||||||
id: widget.blogPost.id.toString(),
|
|
||||||
commentText: textEditingController.text,
|
|
||||||
parentCommentId: replyId,
|
|
||||||
commentSourceType: CommentSourceType.blogPost,
|
|
||||||
);
|
|
||||||
context.read<CommentsCubit>().setReplyId(null);
|
|
||||||
setState(() {
|
|
||||||
textEditingController.clear();
|
|
||||||
replyId = null;
|
|
||||||
});
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(color: textEditingController.text.length > 2 ? appTheme.blueA400 : Colors.grey, shape: BoxShape.circle),
|
|
||||||
height: 32,
|
|
||||||
width: 32,
|
|
||||||
child: const Icon(Icons.send_rounded, size: 18, color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(widget.blogPost.title),
|
|
||||||
actions: [
|
|
||||||
CustomPopup(
|
|
||||||
isAuthor: widget.blogPost.author.authorIsMe,
|
|
||||||
complainedContentType: ComplainedContent.posts,
|
|
||||||
path: 'blog_post/${(widget.blogPost.id)}',
|
|
||||||
id: widget.blogPost.id,
|
|
||||||
feedBlogPostModel: widget.blogPost,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: SingleChildScrollView(
|
|
||||||
padding: const EdgeInsets.only(bottom: 80),
|
|
||||||
physics: const BouncingScrollPhysics(),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () => GoRouter.of(context).pushNamed('profileScreen', extra: widget.blogPost.author),
|
|
||||||
child: Ink(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
CircleAvatar(
|
|
||||||
radius: 16,
|
|
||||||
foregroundImage: NetworkImage(
|
|
||||||
baseUrl + widget.blogPost.author.authorProfileIcon,
|
|
||||||
),
|
|
||||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
widget.blogPost.author.authorName,
|
|
||||||
style: const TextStyle(color: Colors.black, fontSize: 14, fontWeight: FontWeight.w400, height: 1),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 2),
|
|
||||||
Text(
|
|
||||||
widget.blogPost.createdAt.format('dd MMMM', 'ru'),
|
|
||||||
style: TextStyle(color: Colors.grey.shade600, fontSize: 12, fontWeight: FontWeight.w400, height: 1),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (widget.blogPost.description.isNotEmpty)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
child: Html(data: widget.blogPost.description, style: {"*": Style(color: Colors.black87)}),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
if (widget.blogPost.image.isNotEmpty)
|
|
||||||
CachedNetworkImage(
|
|
||||||
imageUrl: '$baseUrl/storage/${widget.blogPost.image}',
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
_iconButton(
|
|
||||||
icon: Icons.favorite_border_rounded,
|
|
||||||
text: totalLikes.toString(),
|
|
||||||
iconColor: isLiked ? Colors.red.withValues(alpha: 0.8) : null,
|
|
||||||
backgroundColor: isLiked ? Colors.red.withValues(alpha: 0.1) : null,
|
|
||||||
textColor: isLiked ? Colors.red.withValues(alpha: 0.8) : null,
|
|
||||||
onTap: () async {
|
|
||||||
setState(() {
|
|
||||||
isLiked = !isLiked;
|
|
||||||
isLiked ? totalLikes++ : totalLikes--;
|
|
||||||
});
|
|
||||||
isLiked
|
|
||||||
? await context.read<FeedCubit>().likePost(widget.blogPost.id)
|
|
||||||
: await context.read<FeedCubit>().unlikedPost(widget.blogPost.id);
|
|
||||||
sl<FeedCubit>().updateFeedBlogPostList(widget.blogPost, isLiked);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
body: const Loader());
|
||||||
|
}
|
||||||
|
isLiked = blogPostState.blogPostModel?.wasLikedByMe ?? false;
|
||||||
|
totalLikes = blogPostState.blogPostModel?.totalLikes ?? 0;
|
||||||
|
return Scaffold(
|
||||||
|
resizeToAvoidBottomInset: true,
|
||||||
|
bottomSheet: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12) + (Platform.isIOS ? const EdgeInsets.only(bottom: 16) : EdgeInsets.zero),
|
||||||
|
decoration: BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.1)))),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
if (replyId != null)
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
context.read<CommentsCubit>().setReplyId(null);
|
||||||
|
setState(() {
|
||||||
|
replyId = null;
|
||||||
|
textEditingController.clear();
|
||||||
|
});
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
|
||||||
|
height: 32,
|
||||||
|
width: 32,
|
||||||
|
child: const Icon(Icons.close_rounded, size: 18, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (replyId != null) const SizedBox(width: 8),
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width - (replyId == null ? 64 : 104),
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(minHeight: 52),
|
||||||
|
child: TextFormField(
|
||||||
|
textCapitalization: TextCapitalization.sentences,
|
||||||
|
onChanged: (_) {
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
controller: textEditingController,
|
||||||
|
maxLines: 5,
|
||||||
|
minLines: 1,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Colors.black,
|
||||||
|
height: 1.2,
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
isDense: false,
|
||||||
|
// убираем true, чтобы padding учитывался корректно
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||||
|
// вертикальный паддинг увеличен
|
||||||
|
hintText: 'Комментарий',
|
||||||
|
hintStyle: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
height: 1.2,
|
||||||
|
color: Color(0xFFAEAEB2),
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_iconButton(
|
GestureDetector(
|
||||||
icon: Icons.reply_rounded,
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
final box = context.findRenderObject() as RenderBox?;
|
if (textEditingController.text.length > 2) {
|
||||||
SharePlus.instance.share(
|
commentsCubit.sendComment(
|
||||||
ShareParams(
|
id: widget.id.toString(),
|
||||||
uri: Uri.parse('$baseUrl/ru/blog_post/${widget.blogPost.id}'),
|
commentText: textEditingController.text,
|
||||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
parentCommentId: replyId,
|
||||||
),
|
commentSourceType: CommentSourceType.blogPost,
|
||||||
);
|
);
|
||||||
|
context.read<CommentsCubit>().setReplyId(null);
|
||||||
|
setState(() {
|
||||||
|
textEditingController.clear();
|
||||||
|
replyId = null;
|
||||||
|
});
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(color: textEditingController.text.length > 2 ? appTheme.blueA400 : Colors.grey, shape: BoxShape.circle),
|
||||||
|
height: 32,
|
||||||
|
width: 32,
|
||||||
|
child: const Icon(Icons.send_rounded, size: 18, color: Colors.white),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
|
||||||
Icon(Icons.visibility_rounded, size: 16, color: appTheme.gray500),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(widget.blogPost.totalViews.toString(), style: TextStyle(fontSize: 12, color: appTheme.gray500, fontWeight: FontWeight.w400)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
appBar: AppBar(
|
||||||
width: MediaQuery.of(context).size.width - 32,
|
title: Text(blogPostState.blogPostModel?.title ?? ''),
|
||||||
height: 0.4,
|
leading: BackButton(
|
||||||
decoration: BoxDecoration(color: Colors.black.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(16)),
|
onPressed: () {
|
||||||
|
if (context.canPop()) {
|
||||||
|
GoRouter.of(context).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GoRouter.of(context).go('/feed-screen');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
CustomPopup(
|
||||||
|
isAuthor: blogPostState.blogPostModel?.author.authorIsMe ?? false,
|
||||||
|
complainedContentType: ComplainedContent.posts,
|
||||||
|
path: 'blog_post/${(widget.id)}',
|
||||||
|
id: widget.id,
|
||||||
|
feedBlogPostModel: blogPostState.blogPostModel,
|
||||||
|
userSlug: blogPostState.blogPostModel?.author.authorSlug ?? '',
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
body: SingleChildScrollView(
|
||||||
Container(
|
padding: const EdgeInsets.only(bottom: 80),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
physics: const BouncingScrollPhysics(),
|
||||||
alignment: Alignment.centerLeft,
|
child: Column(
|
||||||
child: Text(
|
children: [
|
||||||
'${widget.blogPost.totalComments} КОММЕНТАРИЕВ',
|
GestureDetector(
|
||||||
style: TextStyle(fontSize: 12, color: appTheme.gray500, fontWeight: FontWeight.w400),
|
onTap: () => GoRouter.of(context).pushNamed('profileScreen', extra: blogPostState.blogPostModel?.author),
|
||||||
|
child: Ink(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 16,
|
||||||
|
foregroundImage: NetworkImage(
|
||||||
|
baseUrl + (blogPostState.blogPostModel?.author.authorProfileIcon ?? ''),
|
||||||
|
),
|
||||||
|
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
blogPostState.blogPostModel?.author.authorName ?? '',
|
||||||
|
style: const TextStyle(color: Colors.black, fontSize: 14, fontWeight: FontWeight.w400, height: 1),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
(blogPostState.blogPostModel?.createdAt ?? DateTime.now()).format('dd MMMM', 'ru'),
|
||||||
|
style: TextStyle(color: Colors.grey.shade600, fontSize: 12, fontWeight: FontWeight.w400, height: 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (blogPostState.blogPostModel?.description.isNotEmpty ?? false)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: Html(data: blogPostState.blogPostModel?.description.replaceAll('\n', '<br>'), style: {"*": Style(color: Colors.black87)}),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (blogPostState.blogPostModel?.image.isNotEmpty ?? false)
|
||||||
|
CachedNetworkImage(
|
||||||
|
imageUrl: '$baseUrl/storage/${blogPostState.blogPostModel?.image ?? ''}',
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_iconButton(
|
||||||
|
icon: Icons.favorite_border_rounded,
|
||||||
|
text: totalLikes.toString(),
|
||||||
|
iconColor: isLiked ? Colors.red.withValues(alpha: 0.8) : null,
|
||||||
|
backgroundColor: isLiked ? Colors.red.withValues(alpha: 0.1) : null,
|
||||||
|
textColor: isLiked ? Colors.red.withValues(alpha: 0.8) : null,
|
||||||
|
onTap: () async {
|
||||||
|
setState(() {
|
||||||
|
isLiked = !isLiked;
|
||||||
|
isLiked ? totalLikes++ : totalLikes--;
|
||||||
|
});
|
||||||
|
isLiked ? await context.read<FeedCubit>().likePost(widget.id) : await context.read<FeedCubit>().unlikedPost(widget.id);
|
||||||
|
sl<FeedCubit>().updateFeedBlogPostList(blogPostState.blogPostModel, isLiked);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
_iconButton(
|
||||||
|
icon: Icons.reply_rounded,
|
||||||
|
onTap: () {
|
||||||
|
final box = context.findRenderObject() as RenderBox?;
|
||||||
|
SharePlus.instance.share(
|
||||||
|
ShareParams(
|
||||||
|
uri: Uri.parse('$baseUrl/ru/blog_post/${widget.id}'),
|
||||||
|
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Icon(Icons.visibility_rounded, size: 16, color: appTheme.gray500),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(blogPostState.blogPostModel?.totalViews.toString() ?? '',
|
||||||
|
style: TextStyle(fontSize: 12, color: appTheme.gray500, fontWeight: FontWeight.w400)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: MediaQuery.of(context).size.width - 32,
|
||||||
|
height: 0.4,
|
||||||
|
decoration: BoxDecoration(color: Colors.black.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(16)),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
'${blogPostState.blogPostModel?.totalComments} КОММЕНТАРИЕВ',
|
||||||
|
style: TextStyle(fontSize: 12, color: appTheme.gray500, fontWeight: FontWeight.w400),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
if (state is CommentsStateLoading) const Loader(),
|
||||||
|
if (state is CommentsStateLoaded)
|
||||||
|
CommentsWidget(id: widget.id.toString(), commentSourceType: CommentSourceType.blogPost, comment: state.comment, replyId: replyId),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
);
|
||||||
if (state is CommentsStateLoading) const Loader(),
|
},
|
||||||
if (state is CommentsStateLoaded)
|
);
|
||||||
CommentsWidget(id: widget.blogPost.id.toString(), commentSourceType: CommentSourceType.blogPost, comment: state.comment, replyId: replyId),
|
},
|
||||||
],
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_iconButton({required IconData icon, String? text, Function()? onTap, Color? iconColor, Color? backgroundColor, Color? textColor}) {
|
InkWell _iconButton({required IconData icon, String? text, Function()? onTap, Color? iconColor, Color? backgroundColor, Color? textColor}) {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTap ?? () {},
|
onTap: onTap ?? () {},
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_showImagePicker() {
|
void _showImagePicker() {
|
||||||
showCupertinoModalPopup<void>(
|
showCupertinoModalPopup<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
@@ -145,7 +145,7 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildBlogPostCreate(BuildContext context) {
|
Scaffold _buildBlogPostCreate(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
resizeToAvoidBottomInset: true,
|
resizeToAvoidBottomInset: true,
|
||||||
appBar: AppBar(),
|
appBar: AppBar(),
|
||||||
@@ -253,7 +253,7 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildAddPhoto() {
|
GestureDetector _buildAddPhoto() {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_showImagePicker();
|
_showImagePicker();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ class NewMessagesCubit extends Cubit<NewMessagesState> {
|
|||||||
|
|
||||||
NewMessagesCubit() : super(const NewMessagesState.empty());
|
NewMessagesCubit() : super(const NewMessagesState.empty());
|
||||||
|
|
||||||
incrementCounter() {
|
void incrementCounter() {
|
||||||
_messagesCount++;
|
_messagesCount++;
|
||||||
emit(NewMessagesState.loaded(messagesCount: _messagesCount));
|
emit(NewMessagesState.loaded(messagesCount: _messagesCount));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
|
class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
|
||||||
final ProfileRepository profileRepository;
|
final ProfileRepository profileRepository;
|
||||||
@@ -9,7 +11,7 @@ class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
|
|||||||
required this.profileRepository,
|
required this.profileRepository,
|
||||||
}) : super(const ProfileAvatarState.loading());
|
}) : super(const ProfileAvatarState.loading());
|
||||||
|
|
||||||
getMyProfile() async {
|
Future<void> getMyProfile() async {
|
||||||
emit(const ProfileAvatarState.loading());
|
emit(const ProfileAvatarState.loading());
|
||||||
try {
|
try {
|
||||||
emit(
|
emit(
|
||||||
@@ -18,6 +20,7 @@ class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
AppRouter.navigatorKey.currentContext?.go('/auth-screen');
|
||||||
emit(ProfileAvatarState.error(errorMessage: e.toString()));
|
emit(ProfileAvatarState.error(errorMessage: e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:badges/badges.dart' as badges;
|
import 'package:badges/badges.dart' as badges;
|
||||||
import 'package:eatvio_app/core/const.dart';
|
import 'package:eatvio_app/core/const.dart';
|
||||||
import 'package:eatvio_app/core/utils/image_constant.dart';
|
import 'package:eatvio_app/core/utils/image_constant.dart';
|
||||||
|
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.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/notifications/presentation/notifications_cubit/notifications_state.dart';
|
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_state.dart';
|
||||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||||
@@ -30,6 +31,7 @@ class BottomNavigationBarWidget extends StatefulWidget {
|
|||||||
class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
|
class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
|
||||||
int currentBottomNavBarIndex = 1;
|
int currentBottomNavBarIndex = 1;
|
||||||
late final List<String> pages;
|
late final List<String> pages;
|
||||||
|
int tapsCount = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -58,12 +60,29 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onItemTapped(int index) async {
|
void _onItemTapped(int index) async {
|
||||||
|
if (pages.length == 7) {
|
||||||
|
if (index == 6) {
|
||||||
|
tapsCount++;
|
||||||
|
if (tapsCount == 7) {
|
||||||
|
tapsCount = 0;
|
||||||
|
GoRouter.of(context).pushNamed(DevToolsScreen.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (index == 5) {
|
||||||
|
tapsCount++;
|
||||||
|
if (tapsCount == 7) {
|
||||||
|
tapsCount = 0;
|
||||||
|
GoRouter.of(context).pushNamed(DevToolsScreen.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (currentBottomNavBarIndex != index) {
|
if (currentBottomNavBarIndex != index) {
|
||||||
setState(() {
|
setState(() {
|
||||||
currentBottomNavBarIndex = index;
|
currentBottomNavBarIndex = index;
|
||||||
});
|
});
|
||||||
|
GoRouter.of(context).replace(pages[currentBottomNavBarIndex]);
|
||||||
}
|
}
|
||||||
GoRouter.of(context).replace(pages[currentBottomNavBarIndex]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -82,7 +101,9 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
|
|||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
label: '',
|
label: '',
|
||||||
icon: CircleAvatar(
|
icon: CircleAvatar(
|
||||||
foregroundImage: NetworkImage(baseUrl + widget.profileModel.profilePhotoUrl),
|
foregroundImage: NetworkImage(
|
||||||
|
widget.profileModel.profilePhotoUrl.contains('https') ? widget.profileModel.profilePhotoUrl : baseUrl + widget.profileModel.profilePhotoUrl,
|
||||||
|
),
|
||||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||||
radius: 16,
|
radius: 16,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class CalendarCubit extends Cubit<CalendarState> {
|
|||||||
|
|
||||||
CalendarCubit({required this.calendarRepository}) : super(const CalendarState.loading());
|
CalendarCubit({required this.calendarRepository}) : super(const CalendarState.loading());
|
||||||
|
|
||||||
loadCalendarList() async {
|
Future<void> loadCalendarList() async {
|
||||||
emit(const CalendarState.loading());
|
emit(const CalendarState.loading());
|
||||||
try {
|
try {
|
||||||
final calendarList = await calendarRepository.getCalendar();
|
final calendarList = await calendarRepository.getCalendar();
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class CalendarScreen extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildCalendarBody(BuildContext context, CalendarStateLoaded state) {
|
ListView _buildCalendarBody(BuildContext context, CalendarStateLoaded state) {
|
||||||
return ListView.separated(
|
return ListView.separated(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import 'package:audioplayers/audioplayers.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';
|
||||||
|
import 'package:eatvio_app/core/locator_service.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/core/websockets/ws_manager.dart';
|
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||||
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
||||||
import 'package:eatvio_app/features/chat/models/plan_complain_model.dart';
|
import 'package:eatvio_app/features/chat/models/plan_complain_model.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart';
|
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart';
|
||||||
|
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||||
import 'package:eatvio_app/features/my_finance/models/card_model.dart';
|
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';
|
||||||
@@ -45,7 +47,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
loadNextPage(String chatId) async {
|
Future<void> loadNextPage(String chatId) async {
|
||||||
if (currentPage < lastPage && !isLoading) {
|
if (currentPage < lastPage && !isLoading) {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
|
|
||||||
@@ -87,7 +89,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initChat(String chatId, {bool sendInitialMessage = false}) async {
|
Future<void> initChat(String chatId, {bool sendInitialMessage = false}) async {
|
||||||
try {
|
try {
|
||||||
final chatRoom = await chatRepository.getChatRoom(chatId);
|
final chatRoom = await chatRepository.getChatRoom(chatId);
|
||||||
ChosenPlanModel? chosenPlan;
|
ChosenPlanModel? chosenPlan;
|
||||||
@@ -167,6 +169,11 @@ 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(
|
||||||
|
'searchChatRoom',
|
||||||
|
const Duration(milliseconds: 300),
|
||||||
|
() => sl<ChatsCubit>().getChatRooms(),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
typingSubscription = wsManager.clientTypingStream?.listen((event) {
|
typingSubscription = wsManager.clientTypingStream?.listen((event) {
|
||||||
@@ -176,9 +183,17 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> sendMessage({required String chatId, String? data, List<File>? files}) async {
|
Future<bool> sendMessage({
|
||||||
|
String? data,
|
||||||
|
List<File>? files,
|
||||||
|
required String userSlug,
|
||||||
|
}) async {
|
||||||
try {
|
try {
|
||||||
await chatRepository.sendMessage(chatId: chatId, data: data, files: files != null ? await ImageCompressUtility.compressFilesList(files) : null);
|
await chatRepository.sendMessage(
|
||||||
|
data: data,
|
||||||
|
files: files != null ? await ImageCompressUtility.compressFilesList(List<File>.from(files)) : null,
|
||||||
|
slug: userSlug,
|
||||||
|
);
|
||||||
AudioPlayer().play((AssetSource('audio/send_message.wav')));
|
AudioPlayer().play((AssetSource('audio/send_message.wav')));
|
||||||
return true;
|
return true;
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:dismissible_page/dismissible_page.dart';
|
||||||
import 'package:easy_debounce/easy_debounce.dart';
|
import 'package:easy_debounce/easy_debounce.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/core/widgets/data_error_widget.dart';
|
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||||
@@ -8,6 +9,7 @@ import 'package:eatvio_app/core/widgets/loader.dart';
|
|||||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
|
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart';
|
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart';
|
||||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||||
|
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
||||||
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';
|
||||||
@@ -21,15 +23,18 @@ import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
|
|||||||
import 'package:eatvio_app/features/user_details/presentation/user_details_screen.dart';
|
import 'package:eatvio_app/features/user_details/presentation/user_details_screen.dart';
|
||||||
import 'package:eatvio_app/generated/l10n.dart';
|
import 'package:eatvio_app/generated/l10n.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:flutter/cupertino.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';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:visibility_detector/visibility_detector.dart';
|
import 'package:visibility_detector/visibility_detector.dart';
|
||||||
|
|
||||||
class ChatScreen extends StatefulWidget {
|
class ChatScreen extends StatefulWidget {
|
||||||
final String chatId;
|
final String chatId;
|
||||||
final bool? sendInitialMessage;
|
final bool? sendInitialMessage;
|
||||||
|
|
||||||
const ChatScreen({required this.chatId, super.key, required this.sendInitialMessage});
|
const ChatScreen({required this.chatId, super.key, required this.sendInitialMessage});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -44,6 +49,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
final List<({int id, int index})> unreadMessagesIdsCopy = [];
|
final List<({int id, int index})> unreadMessagesIdsCopy = [];
|
||||||
final showableChatBadgeStates = ChosenPlanStateEnum.getShowableChatBadgeStates();
|
final showableChatBadgeStates = ChosenPlanStateEnum.getShowableChatBadgeStates();
|
||||||
late AppLifecycleListener _listener;
|
late AppLifecycleListener _listener;
|
||||||
|
List<File> pickedFiles = [];
|
||||||
|
|
||||||
void _onHide() {
|
void _onHide() {
|
||||||
chatRoomCubit.cancelTimer();
|
chatRoomCubit.cancelTimer();
|
||||||
@@ -63,7 +69,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
_listener = AppLifecycleListener(onHide: _onHide, onResume: _onResume);
|
_listener = AppLifecycleListener(onHide: _onHide, onResume: _onResume);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onScroll() {
|
void _onScroll() {
|
||||||
if (scrollController.offset >= scrollController.position.maxScrollExtent && !scrollController.position.outOfRange) {
|
if (scrollController.offset >= scrollController.position.maxScrollExtent && !scrollController.position.outOfRange) {
|
||||||
chatRoomCubit.loadNextPage(widget.chatId);
|
chatRoomCubit.loadNextPage(widget.chatId);
|
||||||
}
|
}
|
||||||
@@ -96,7 +102,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildChat(BuildContext context, ChatRoomStateLoaded state) {
|
Scaffold _buildChat(BuildContext context, ChatRoomStateLoaded state) {
|
||||||
final chosenPlan = state.chatRoom.chosenPlan;
|
final chosenPlan = state.chatRoom.chosenPlan;
|
||||||
final amIConsultant = chosenPlan?.consultantId == state.chatRoom.users.sender.id;
|
final amIConsultant = chosenPlan?.consultantId == state.chatRoom.users.sender.id;
|
||||||
final showUserDetailsButton = [
|
final showUserDetailsButton = [
|
||||||
@@ -333,100 +339,155 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
child: Row(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 8),
|
SingleChildScrollView(
|
||||||
InkWell(
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
onTap: () async {
|
physics: const BouncingScrollPhysics(),
|
||||||
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
scrollDirection: Axis.horizontal,
|
||||||
allowMultiple: true,
|
child: Row(
|
||||||
type: FileType.custom,
|
mainAxisSize: MainAxisSize.min,
|
||||||
allowedExtensions: [
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
'pdf',
|
children: [
|
||||||
'doc',
|
for (int i = 0; i < pickedFiles.length; i++) ...[
|
||||||
'docx',
|
Stack(
|
||||||
'json',
|
clipBehavior: Clip.none,
|
||||||
'ppt',
|
children: [
|
||||||
'pptx',
|
_isImageFile(pickedFiles[i].path)
|
||||||
'rtf',
|
? GestureDetector(
|
||||||
'txt',
|
onTap: () {
|
||||||
'xls',
|
final imageFiles = pickedFiles.where((f) => _isImageFile(f.path)).toList();
|
||||||
'xlsx',
|
final imageIndex = _imageIndexAt(pickedFiles, i);
|
||||||
'csv',
|
|
||||||
'jpeg',
|
|
||||||
'jpg',
|
|
||||||
'png',
|
|
||||||
'gif',
|
|
||||||
'svg',
|
|
||||||
'xml',
|
|
||||||
'webp',
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result != null) {
|
context.pushTransparentRoute(
|
||||||
List<File> files = result.paths.map((path) => File(path!)).toList();
|
PhotoViewScreen(
|
||||||
await chatRoomCubit.sendMessage(chatId: widget.chatId, files: files);
|
imageUrls: imageFiles.map((e) => e.path).toList(),
|
||||||
}
|
isLocal: true,
|
||||||
},
|
index: imageIndex,
|
||||||
customBorder: const CircleBorder(),
|
),
|
||||||
child: Ink(
|
);
|
||||||
height: 40,
|
},
|
||||||
width: 40,
|
child: Hero(
|
||||||
child: Transform.rotate(angle: pi / 4, child: const Icon(Icons.attach_file_rounded, size: 30, color: Color(0xFF858E99))),
|
tag: pickedFiles[i].path,
|
||||||
|
transitionOnUserGestures: true,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 104,
|
||||||
|
width: 104,
|
||||||
|
child: Image.file(
|
||||||
|
pickedFiles[i],
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: _buildFilePreview(pickedFiles[i]),
|
||||||
|
Positioned(
|
||||||
|
top: -8,
|
||||||
|
right: -8,
|
||||||
|
child: InkWell(
|
||||||
|
customBorder: const CircleBorder(),
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
pickedFiles.remove(pickedFiles[i]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: appTheme.gray50,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
size: 20,
|
||||||
|
Icons.close,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
],
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(height: 12),
|
||||||
Expanded(
|
Row(
|
||||||
child: SizedBox(
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
height: 40,
|
children: [
|
||||||
child: TextFormField(
|
const SizedBox(width: 8),
|
||||||
expands: true,
|
InkWell(
|
||||||
textCapitalization: TextCapitalization.sentences,
|
onTap: () => showImagePicker(state.chatRoom.users.receiver.slug),
|
||||||
controller: textEditingController,
|
customBorder: const CircleBorder(),
|
||||||
maxLines: null,
|
child: Ink(
|
||||||
minLines: null,
|
height: 40,
|
||||||
keyboardType: TextInputType.multiline,
|
width: 40,
|
||||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1),
|
child: Transform.rotate(angle: pi / 4, child: const Icon(Icons.attach_file_rounded, size: 30, color: Color(0xFF858E99))),
|
||||||
decoration: InputDecoration(
|
),
|
||||||
isDense: true,
|
),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
const SizedBox(width: 8),
|
||||||
hintText: 'Сообщение',
|
Expanded(
|
||||||
hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)),
|
child: SizedBox(
|
||||||
filled: true,
|
height: 40,
|
||||||
border: OutlineInputBorder(
|
child: TextFormField(
|
||||||
borderRadius: BorderRadius.circular(100), borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1)),
|
expands: true,
|
||||||
enabledBorder: OutlineInputBorder(
|
textCapitalization: TextCapitalization.sentences,
|
||||||
borderRadius: BorderRadius.circular(100),
|
controller: textEditingController,
|
||||||
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
maxLines: null,
|
||||||
),
|
minLines: null,
|
||||||
focusedBorder: OutlineInputBorder(
|
keyboardType: TextInputType.multiline,
|
||||||
borderRadius: BorderRadius.circular(100),
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1),
|
||||||
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
decoration: InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||||
|
hintText: 'Сообщение',
|
||||||
|
hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)),
|
||||||
|
filled: true,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(100), borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1)),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(100),
|
||||||
|
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(100),
|
||||||
|
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
|
InkWell(
|
||||||
|
customBorder: const CircleBorder(),
|
||||||
|
onTap: () {
|
||||||
|
if (textEditingController.text.isNotEmpty || pickedFiles.isNotEmpty) {
|
||||||
|
chatRoomCubit.sendMessage(
|
||||||
|
data: textEditingController.text.isEmpty ? null : textEditingController.text.trim(),
|
||||||
|
files: pickedFiles.isEmpty ? null : pickedFiles,
|
||||||
|
userSlug: state.chatRoom.users.receiver.slug,
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
textEditingController.clear();
|
||||||
|
pickedFiles.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Ink(
|
||||||
|
decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
|
||||||
|
height: 40,
|
||||||
|
width: 40,
|
||||||
|
child: const Icon(Icons.arrow_upward_rounded, size: 24, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
|
||||||
InkWell(
|
|
||||||
customBorder: const CircleBorder(),
|
|
||||||
onTap: () {
|
|
||||||
if (textEditingController.text.isNotEmpty) {
|
|
||||||
chatRoomCubit.sendMessage(chatId: widget.chatId, data: textEditingController.text.trim());
|
|
||||||
setState(() {
|
|
||||||
textEditingController.clear();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Ink(
|
|
||||||
decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
|
|
||||||
height: 40,
|
|
||||||
width: 40,
|
|
||||||
child: const Icon(Icons.arrow_upward_rounded, size: 24, color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -434,21 +495,173 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
),
|
),
|
||||||
if (showAboutOrderButton)
|
if (showAboutOrderButton)
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 8,
|
top: 8,
|
||||||
right: 15,
|
right: 15,
|
||||||
left: 16,
|
left: 16,
|
||||||
child: AboutOrderButton(
|
child: AboutOrderButton(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
GoRouter.of(context).push(InfoByOrderScreen.path, extra: {'planId': state.chatRoom.chosenPlan?.id});
|
GoRouter.of(context).push(InfoByOrderScreen.path, extra: {'planId': state.chatRoom.chosenPlan?.id});
|
||||||
},
|
},
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_messageDate(DateTime date) {
|
Widget _buildFilePreview(File file) {
|
||||||
|
final fileName = file.path.split('/').last;
|
||||||
|
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Container(
|
||||||
|
height: 104,
|
||||||
|
width: 104,
|
||||||
|
color: const Color(0xFFE0E0E0), // Светло-серый фон
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.attach_file, size: 32, color: Colors.black54),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
|
child: Text(
|
||||||
|
fileName,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isImageFile(String path) {
|
||||||
|
final lower = path.toLowerCase();
|
||||||
|
return lower.endsWith('.png') ||
|
||||||
|
lower.endsWith('.jpg') ||
|
||||||
|
lower.endsWith('.jpeg') ||
|
||||||
|
lower.endsWith('.gif') ||
|
||||||
|
lower.endsWith('.bmp') ||
|
||||||
|
lower.endsWith('.webp');
|
||||||
|
}
|
||||||
|
|
||||||
|
int _imageIndexAt(List<File> allFiles, int targetIndex) {
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < allFiles.length; i++) {
|
||||||
|
if (_isImageFile(allFiles[i].path)) {
|
||||||
|
if (i == targetIndex) return count;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void showImagePicker(String slug) {
|
||||||
|
showCupertinoModalPopup<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => CupertinoActionSheet(
|
||||||
|
actions: [
|
||||||
|
CupertinoActionSheetAction(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
final picker = ImagePicker();
|
||||||
|
XFile? image = await picker.pickImage(source: ImageSource.camera);
|
||||||
|
setState(() {
|
||||||
|
if (image != null) {
|
||||||
|
pickedFiles.add(File(image.path));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// if (image != null) {
|
||||||
|
// await chatRoomCubit.sendMessage(files: [File(image.path)], userSlug: slug);
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.photo_camera),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Фото'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CupertinoActionSheetAction(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
final picker = ImagePicker();
|
||||||
|
List<XFile> images = await picker.pickMultiImage();
|
||||||
|
if (images != []) {
|
||||||
|
setState(() {
|
||||||
|
pickedFiles.addAll(images.map((image) => File(image.path)).toList());
|
||||||
|
});
|
||||||
|
// List<File> files = images.map((image) => File(image.path)).toList();
|
||||||
|
// await chatRoomCubit.sendMessage(files: files, userSlug: slug);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.photo_library_rounded),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Галерея'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CupertinoActionSheetAction(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||||
|
allowMultiple: true,
|
||||||
|
type: FileType.custom,
|
||||||
|
allowedExtensions: [
|
||||||
|
'pdf',
|
||||||
|
'doc',
|
||||||
|
'docx',
|
||||||
|
'json',
|
||||||
|
'ppt',
|
||||||
|
'pptx',
|
||||||
|
'rtf',
|
||||||
|
'txt',
|
||||||
|
'xls',
|
||||||
|
'xlsx',
|
||||||
|
'csv',
|
||||||
|
'jpeg',
|
||||||
|
'jpg',
|
||||||
|
'png',
|
||||||
|
'gif',
|
||||||
|
'svg',
|
||||||
|
'xml',
|
||||||
|
'webp',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
if (result != null) {
|
||||||
|
// List<File> files = result.paths.map((path) => File(path!)).toList();
|
||||||
|
setState(() {
|
||||||
|
pickedFiles.addAll(result.paths.map((path) => File(path!)).toList());
|
||||||
|
});
|
||||||
|
// await chatRoomCubit.sendMessage(files: files, userSlug: slug);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.file_present_rounded),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Файлы'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
cancelButton: CupertinoActionSheetAction(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
isDestructiveAction: true,
|
||||||
|
child: const Text('Отмена'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Center _messageDate(DateTime date) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(top: 32),
|
padding: const EdgeInsets.only(top: 32),
|
||||||
|
|||||||
@@ -67,18 +67,23 @@ class _ChatsScreenState extends State<ChatsScreen> {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
body: switch (state.state) {
|
body: RefreshIndicator(
|
||||||
BaseState.error => DataErrorWidget(onReload: () => context.read<ChatsCubit>().getChatRooms()),
|
child: switch (state.state) {
|
||||||
BaseState.success => _buildChatList(context, state),
|
BaseState.error => DataErrorWidget(onReload: () => context.read<ChatsCubit>().getChatRooms()),
|
||||||
_ => const Loader(),
|
BaseState.success => _buildChatList(context, state),
|
||||||
},
|
_ => const Loader(),
|
||||||
|
},
|
||||||
|
onRefresh: () async {
|
||||||
|
await context.read<ChatsCubit>().getChatRooms();
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildChatList(BuildContext context, ChatsState state) {
|
NotificationListener<ScrollNotification> _buildChatList(BuildContext context, ChatsState state) {
|
||||||
return NotificationListener<ScrollNotification>(
|
return NotificationListener<ScrollNotification>(
|
||||||
onNotification: (notification) {
|
onNotification: (notification) {
|
||||||
loadNewContent(notification, context.read<ChatsCubit>().loadMoreChatRooms);
|
loadNewContent(notification, context.read<ChatsCubit>().loadMoreChatRooms);
|
||||||
|
|||||||
289
lib/features/chat/presentation/pages/empty_chat_screen.dart
Normal file
289
lib/features/chat/presentation/pages/empty_chat_screen.dart
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:eatvio_app/core/app_router.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/features/chat/repositories/chat_repository.dart';
|
||||||
|
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
|
||||||
|
class EmptyChatScreen extends StatefulWidget {
|
||||||
|
final Author author;
|
||||||
|
|
||||||
|
const EmptyChatScreen({
|
||||||
|
super.key,
|
||||||
|
required this.author,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<EmptyChatScreen> createState() => _EmptyChatScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EmptyChatScreenState extends State<EmptyChatScreen> {
|
||||||
|
final TextEditingController textEditingController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
resizeToAvoidBottomInset: true,
|
||||||
|
appBar: AppBar(
|
||||||
|
titleSpacing: 0,
|
||||||
|
backgroundColor: appTheme.gray100,
|
||||||
|
centerTitle: false,
|
||||||
|
title: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed(
|
||||||
|
'profileScreen',
|
||||||
|
extra: Author(
|
||||||
|
authorSlug: widget.author.authorSlug,
|
||||||
|
authorName: widget.author.authorName,
|
||||||
|
authorProfileIcon: widget.author.authorProfileIcon,
|
||||||
|
authorIsMe: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 20,
|
||||||
|
foregroundImage: NetworkImage(widget.author.authorProfileIcon),
|
||||||
|
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.author.authorName,
|
||||||
|
style: CustomTextStyles.titleSmallBold.copyWith(
|
||||||
|
color: PrimaryColors().black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Spacer(),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => showImagePicker(widget.author.authorSlug),
|
||||||
|
customBorder: const CircleBorder(),
|
||||||
|
child: Ink(
|
||||||
|
height: 40,
|
||||||
|
width: 40,
|
||||||
|
child: Transform.rotate(
|
||||||
|
angle: pi / 4,
|
||||||
|
child: const Icon(
|
||||||
|
Icons.attach_file_rounded,
|
||||||
|
size: 30,
|
||||||
|
color: Color(0xFF858E99),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextFormField(
|
||||||
|
expands: true,
|
||||||
|
textCapitalization: TextCapitalization.sentences,
|
||||||
|
controller: textEditingController,
|
||||||
|
maxLines: null,
|
||||||
|
minLines: null,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||||
|
hintText: 'Сообщение',
|
||||||
|
hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)),
|
||||||
|
filled: true,
|
||||||
|
border:
|
||||||
|
OutlineInputBorder(borderRadius: BorderRadius.circular(100), borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1)),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(100),
|
||||||
|
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(100),
|
||||||
|
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
InkWell(
|
||||||
|
customBorder: const CircleBorder(),
|
||||||
|
onTap: () async {
|
||||||
|
if (textEditingController.text.isNotEmpty) {
|
||||||
|
await sl<ChatRepository>().sendMessage(
|
||||||
|
data: textEditingController.text.trim(),
|
||||||
|
slug: widget.author.authorSlug,
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
textEditingController.clear();
|
||||||
|
});
|
||||||
|
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
|
||||||
|
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||||
|
'chatScreen',
|
||||||
|
extra: {
|
||||||
|
'chatId': chatIdOrError,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Ink(
|
||||||
|
decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle),
|
||||||
|
height: 40,
|
||||||
|
width: 40,
|
||||||
|
child: const Icon(Icons.arrow_upward_rounded, size: 24, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showImagePicker(String slug) {
|
||||||
|
showCupertinoModalPopup<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => CupertinoActionSheet(
|
||||||
|
actions: [
|
||||||
|
CupertinoActionSheetAction(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
final picker = ImagePicker();
|
||||||
|
XFile? image = await picker.pickImage(source: ImageSource.camera);
|
||||||
|
if (image != null) {
|
||||||
|
await sl<ChatRepository>().sendMessage(
|
||||||
|
files: [File(image.path)],
|
||||||
|
slug: widget.author.authorSlug,
|
||||||
|
);
|
||||||
|
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
|
||||||
|
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||||
|
'chatScreen',
|
||||||
|
extra: {
|
||||||
|
'chatId': chatIdOrError,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.photo_library_rounded),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Галерея'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CupertinoActionSheetAction(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
final picker = ImagePicker();
|
||||||
|
List<XFile>? images = await picker.pickMultiImage();
|
||||||
|
if (images.isNotEmpty) {
|
||||||
|
List<File> files = images.map((image) => File(image.path)).toList();
|
||||||
|
await sl<ChatRepository>().sendMessage(
|
||||||
|
files: files,
|
||||||
|
slug: widget.author.authorSlug,
|
||||||
|
);
|
||||||
|
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
|
||||||
|
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||||
|
'chatScreen',
|
||||||
|
extra: {
|
||||||
|
'chatId': chatIdOrError,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.photo_camera),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Фото'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CupertinoActionSheetAction(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||||
|
allowMultiple: true,
|
||||||
|
type: FileType.custom,
|
||||||
|
allowedExtensions: [
|
||||||
|
'pdf',
|
||||||
|
'doc',
|
||||||
|
'docx',
|
||||||
|
'json',
|
||||||
|
'ppt',
|
||||||
|
'pptx',
|
||||||
|
'rtf',
|
||||||
|
'txt',
|
||||||
|
'xls',
|
||||||
|
'xlsx',
|
||||||
|
'csv',
|
||||||
|
'jpeg',
|
||||||
|
'jpg',
|
||||||
|
'png',
|
||||||
|
'gif',
|
||||||
|
'svg',
|
||||||
|
'xml',
|
||||||
|
'webp',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
if (result != null) {
|
||||||
|
List<File> files = result.paths.map((path) => File(path!)).toList();
|
||||||
|
await sl<ChatRepository>().sendMessage(
|
||||||
|
files: files,
|
||||||
|
slug: widget.author.authorSlug,
|
||||||
|
);
|
||||||
|
final chatIdOrError = await sl<ChatRepository>().getChatByUserSlug(widget.author.authorSlug);
|
||||||
|
AppRouter.navigatorKey.currentContext?.pushReplacementNamed(
|
||||||
|
'chatScreen',
|
||||||
|
extra: {
|
||||||
|
'chatId': chatIdOrError,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.file_present_rounded),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Файлы'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
cancelButton: CupertinoActionSheetAction(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
isDestructiveAction: true,
|
||||||
|
child: const Text('Отмена'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,15 +20,14 @@ class ChatEntryWidget extends StatefulWidget {
|
|||||||
class _ChatEntryWidgetState extends State<ChatEntryWidget> {
|
class _ChatEntryWidgetState extends State<ChatEntryWidget> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InkWell(
|
return GestureDetector(
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
onTap: () => GoRouter.of(context).pushNamed(
|
onTap: () => GoRouter.of(context).pushNamed(
|
||||||
'chatScreen',
|
'chatScreen',
|
||||||
extra: {
|
extra: {
|
||||||
'chatId': widget.chatRoom.id.toString(),
|
'chatId': widget.chatRoom.id.toString(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
child: Ink(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
@@ -61,7 +60,13 @@ class _ChatEntryWidgetState extends State<ChatEntryWidget> {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: MediaQuery.sizeOf(context).width * 0.5,
|
width: MediaQuery.sizeOf(context).width * 0.5,
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.chatRoom.lastMessage!.data.toString().trim(),
|
widget.chatRoom.lastMessage!.mediaData != null
|
||||||
|
? widget.chatRoom.lastMessage!.isCurrentUserAuthor == true
|
||||||
|
? 'Вы: 📎 Вложение'
|
||||||
|
: '📎 Вложение'
|
||||||
|
: widget.chatRoom.lastMessage!.isCurrentUserAuthor == true
|
||||||
|
? 'Вы: ${widget.chatRoom.lastMessage!.data.toString().trim()}'
|
||||||
|
: widget.chatRoom.lastMessage!.data.toString().trim(),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: CustomTextStyles.bodyMediumBluegray800,
|
style: CustomTextStyles.bodyMediumBluegray800,
|
||||||
@@ -77,7 +82,7 @@ class _ChatEntryWidgetState extends State<ChatEntryWidget> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 8),
|
padding: const EdgeInsets.only(left: 8),
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.chatRoom.lastMessage!.updatedAt.format('dd MMM', 'ru'),
|
calculateDifference(widget.chatRoom.lastMessage!.updatedAt, 'dd MMM'),
|
||||||
style: theme.textTheme.bodyMedium,
|
style: theme.textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class ChatSearchBarWidget extends StatelessWidget {
|
|||||||
return searchViewWidget(context);
|
return searchViewWidget(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
searchViewWidget(BuildContext context) => SizedBox(
|
SizedBox searchViewWidget(BuildContext context) => SizedBox(
|
||||||
width: width ?? MediaQuery.of(context).size.width,
|
width: width ?? MediaQuery.of(context).size.width,
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
textCapitalization: TextCapitalization.sentences,
|
textCapitalization: TextCapitalization.sentences,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
||||||
@@ -27,7 +28,7 @@ class ChatRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendMessage({
|
Future<void> sendMessage({
|
||||||
required String chatId,
|
required String slug,
|
||||||
String? data,
|
String? data,
|
||||||
List<File>? files,
|
List<File>? files,
|
||||||
}) async {
|
}) async {
|
||||||
@@ -46,12 +47,13 @@ class ChatRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await chatService.sendMessage(
|
await chatService.sendMessageByUserSlug(
|
||||||
id: chatId,
|
userSlug: slug,
|
||||||
data: data,
|
data: data,
|
||||||
mediaData: multipartFiles.isEmpty ? null : multipartFiles,
|
mediaData: multipartFiles.isEmpty ? null : multipartFiles,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e, st) {
|
||||||
|
log(e.toString(), stackTrace: st);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,4 +156,17 @@ class ChatRepository {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> getChatByUserSlug(String slug) async {
|
||||||
|
try {
|
||||||
|
final response = await chatService.getChatByUserSlug(slug: slug);
|
||||||
|
if (response.response.statusCode == 404) {
|
||||||
|
return '404';
|
||||||
|
} else {
|
||||||
|
return response.data['id'];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return 'error';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ abstract class ChatService {
|
|||||||
@Path() required String id,
|
@Path() required String id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@POST('/api/chat/{id}/message/send')
|
@POST('/api/v2/chat/message/send/{id}')
|
||||||
@MultiPart()
|
@MultiPart()
|
||||||
Future<void> sendMessage({
|
Future<void> sendMessage({
|
||||||
@Path() required String id,
|
@Path() required String id,
|
||||||
@@ -80,4 +80,17 @@ abstract class ChatService {
|
|||||||
@Path() required int id,
|
@Path() required int id,
|
||||||
@Body() required PlanComplainModel body,
|
@Body() required PlanComplainModel body,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@GET('/api/v2/chat/get_by_user/{slug}')
|
||||||
|
Future<HttpResponse> getChatByUserSlug({
|
||||||
|
@Path() required String slug,
|
||||||
|
});
|
||||||
|
|
||||||
|
@POST('/api/v2/chat/message/send/{userSlug}')
|
||||||
|
@MultiPart()
|
||||||
|
Future<void> sendMessageByUserSlug({
|
||||||
|
@Path() required String userSlug,
|
||||||
|
@Part() String? data,
|
||||||
|
@Part(name: 'media_data[]', contentType: 'multipart/form-data') List<MultipartFile>? mediaData,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getMyRequestStatus(String slug) async {
|
Future<void> getMyRequestStatus(String slug) async {
|
||||||
_update = true;
|
_update = true;
|
||||||
final myRequestStatus = await consultantRepository.getMyRequestStatus(slug);
|
final myRequestStatus = await consultantRepository.getMyRequestStatus(slug);
|
||||||
|
|
||||||
@@ -81,11 +81,11 @@ class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
disableLoading() {
|
void disableLoading() {
|
||||||
emit(state.copyWith(loading: false));
|
emit(state.copyWith(loading: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateConsultantProfileModel(ConsultantProfileModel consultant, [bool acceptOffer = false]) {
|
void updateConsultantProfileModel(ConsultantProfileModel consultant, [bool acceptOffer = false]) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
consultant: consultant,
|
consultant: consultant,
|
||||||
@@ -95,7 +95,7 @@ class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
registerConsultant() async {
|
Future<void> registerConsultant() async {
|
||||||
if (state.consultant.photos != null) {
|
if (state.consultant.photos != null) {
|
||||||
updateConsultantProfileModel(
|
updateConsultantProfileModel(
|
||||||
state.consultant.copyWith(
|
state.consultant.copyWith(
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class _AdditionalSettingsScreenState extends State<AdditionalSettingsScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_tile({
|
ListTile _tile({
|
||||||
required String title,
|
required String title,
|
||||||
required Function() onTap,
|
required Function() onTap,
|
||||||
required bool value,
|
required bool value,
|
||||||
@@ -102,7 +102,7 @@ class _AdditionalSettingsScreenState extends State<AdditionalSettingsScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_divider(BuildContext context) {
|
Center _divider(BuildContext context) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 0.4,
|
height: 0.4,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class _BecomeConsultantScreenState extends State<BecomeConsultantScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_startRegistrationButton() {
|
InkWell _startRegistrationButton() {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => GoRouter.of(context).pushReplacementNamed('stepperBecomeConsultantScreen'),
|
onTap: () => GoRouter.of(context).pushReplacementNamed('stepperBecomeConsultantScreen'),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class _CertificatesAndDiplomasScreenState extends State<CertificatesAndDiplomasS
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_divider(BuildContext context) {
|
Container _divider(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 0.4,
|
height: 0.4,
|
||||||
width: MediaQuery.of(context).size.width - 32,
|
width: MediaQuery.of(context).size.width - 32,
|
||||||
@@ -93,7 +93,7 @@ class _CertificatesAndDiplomasScreenState extends State<CertificatesAndDiplomasS
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_diplomaInfo({
|
Column _diplomaInfo({
|
||||||
required DiplomaEntity diploma,
|
required DiplomaEntity diploma,
|
||||||
}) {
|
}) {
|
||||||
return Column(
|
return Column(
|
||||||
@@ -159,7 +159,7 @@ class _CertificatesAndDiplomasScreenState extends State<CertificatesAndDiplomasS
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onTapCalendar(BuildContext context, DiplomaEntity diploma) {
|
Null Function() onTapCalendar(BuildContext context, DiplomaEntity diploma) {
|
||||||
return () {
|
return () {
|
||||||
_showDialog(
|
_showDialog(
|
||||||
CupertinoDatePicker(
|
CupertinoDatePicker(
|
||||||
@@ -198,7 +198,7 @@ class _CertificatesAndDiplomasScreenState extends State<CertificatesAndDiplomasS
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkLast() {
|
void checkLast() {
|
||||||
if (diplomasList.last.textEditingController.text.isNotEmpty || diplomasList.last.date != null) {
|
if (diplomasList.last.textEditingController.text.isNotEmpty || diplomasList.last.date != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
diplomasList.add(
|
diplomasList.add(
|
||||||
@@ -219,7 +219,7 @@ class _CertificatesAndDiplomasScreenState extends State<CertificatesAndDiplomasS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enableDeleteButton(DiplomaEntity diploma) {
|
void enableDeleteButton(DiplomaEntity diploma) {
|
||||||
if (diploma.textEditingController.text.isNotEmpty || diploma.date != null) {
|
if (diploma.textEditingController.text.isNotEmpty || diploma.date != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
diplomasList[diplomasList.indexOf(diploma)].enableDeleteButton = true;
|
diplomasList[diplomasList.indexOf(diploma)].enableDeleteButton = true;
|
||||||
@@ -228,7 +228,7 @@ class _CertificatesAndDiplomasScreenState extends State<CertificatesAndDiplomasS
|
|||||||
_saveState();
|
_saveState();
|
||||||
}
|
}
|
||||||
|
|
||||||
_saveState() {
|
void _saveState() {
|
||||||
context.read<RegisterConsultantCubit>().updateConsultantProfileModel(
|
context.read<RegisterConsultantCubit>().updateConsultantProfileModel(
|
||||||
context.read<RegisterConsultantCubit>().state.consultant.copyWith(
|
context.read<RegisterConsultantCubit>().state.consultant.copyWith(
|
||||||
certificates: diplomasList.map((e) => CertificatesModel(title: e.textEditingController.text, date: e.date.toString())).toList().sublist(
|
certificates: diplomasList.map((e) => CertificatesModel(title: e.textEditingController.text, date: e.date.toString())).toList().sublist(
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class _LastStepScreenState extends State<LastStepScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_divider(BuildContext context) {
|
Container _divider(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 0.4,
|
height: 0.4,
|
||||||
width: MediaQuery.of(context).size.width - 32,
|
width: MediaQuery.of(context).size.width - 32,
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ class _PhotoOrScansScreenState extends State<PhotoOrScansScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_saveState() {
|
void _saveState() {
|
||||||
context.read<RegisterConsultantCubit>().updateConsultantProfileModel(
|
context.read<RegisterConsultantCubit>().updateConsultantProfileModel(
|
||||||
context.read<RegisterConsultantCubit>().state.consultant.copyWith(
|
context.read<RegisterConsultantCubit>().state.consultant.copyWith(
|
||||||
photos: imagesPaths,
|
photos: imagesPaths,
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ class _RegistrationAsScreenState extends State<RegistrationAsScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_divider(BuildContext context) {
|
Container _divider(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 0.4,
|
height: 0.4,
|
||||||
width: MediaQuery.of(context).size.width - 32,
|
width: MediaQuery.of(context).size.width - 32,
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ class _SocialNetworksScreenState extends State<SocialNetworksScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setLinksState({
|
void setLinksState({
|
||||||
String? tgLink,
|
String? tgLink,
|
||||||
String? vkLink,
|
String? vkLink,
|
||||||
String? personalSiteLink,
|
String? personalSiteLink,
|
||||||
|
|||||||
@@ -1,42 +1,16 @@
|
|||||||
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
|
|
||||||
abstract class RatingPaginationModel with _$RatingPaginationModel {
|
|
||||||
factory RatingPaginationModel({
|
|
||||||
required List<RatingModel> data,
|
|
||||||
}) = _RatingPaginationModel;
|
|
||||||
|
|
||||||
factory RatingPaginationModel.fromJson(Map<String, dynamic> json) => _$RatingPaginationModelFromJson(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
abstract class RatingModel with _$RatingModel {
|
abstract class RatingModel with _$RatingModel {
|
||||||
|
|
||||||
factory RatingModel({
|
factory RatingModel({
|
||||||
@JsonKey(name: 'created_at', fromJson: parseDate) required DateTime date,
|
required DateTime date,
|
||||||
@JsonKey(name: 'log_data') required RatingEnum type,
|
required String title,
|
||||||
@JsonKey(name: 'log_scores') required int amount,
|
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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
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,16 +1,12 @@
|
|||||||
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/core/widgets/data_error_widget.dart';
|
import 'package:eatvio_app/features/consultant_rating/models/rating_model.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';
|
||||||
@@ -19,10 +15,7 @@ class ConsultantRatingScreen extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider<ConsultantRatingCubit>(
|
return const ConsultantRatingPageContent();
|
||||||
create: (context) => sl<ConsultantRatingCubit>()..getConsultantStatistics()..getScoreLogs(),
|
|
||||||
child: const ConsultantRatingPageContent(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,56 +25,52 @@ 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 BlocBuilder<ConsultantRatingCubit, ConsultantRatingState>(
|
return Scaffold(
|
||||||
builder: (context, state) {
|
backgroundColor: appTheme.gray100,
|
||||||
return Scaffold(
|
appBar: AppBar(
|
||||||
backgroundColor: appTheme.gray100,
|
centerTitle: true,
|
||||||
appBar: AppBar(
|
title: Text(
|
||||||
centerTitle: true,
|
s.rating_and_statistics,
|
||||||
title: Text(
|
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||||
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),
|
||||||
body: state.error != null
|
Padding(
|
||||||
? DataErrorWidget(onReload: () async {
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
await context.read<ConsultantRatingCubit>().getConsultantStatistics();
|
child: CustomOutlinedButton(
|
||||||
if (!context.mounted) return;
|
text: s.copy_referral_link,
|
||||||
await context.read<ConsultantRatingCubit>().getScoreLogs();
|
height: 52,
|
||||||
})
|
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||||
: SingleChildScrollView(
|
buttonStyle: ButtonStyle(
|
||||||
child: Column(
|
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||||
children: [
|
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||||
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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
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,29 +1,42 @@
|
|||||||
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, required this.isStatistic});
|
const PeriodFilterWidget({super.key});
|
||||||
final bool isStatistic;
|
|
||||||
@override
|
@override
|
||||||
State<PeriodFilterWidget> createState() => _PeriodFilterWidgetState();
|
State<PeriodFilterWidget> createState() => _PeriodFilterWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PeriodFilterWidgetState extends State<PeriodFilterWidget> {
|
class _PeriodFilterWidgetState extends State<PeriodFilterWidget> {
|
||||||
PeriodFilterStatus _currentState = PeriodFilterStatus.all;
|
final List<String> periodDropDownTitles = [];
|
||||||
|
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<PeriodFilterStatus>(
|
CustomDropDownButton(
|
||||||
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),
|
||||||
@@ -31,59 +44,24 @@ class _PeriodFilterWidgetState extends State<PeriodFilterWidget> {
|
|||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_currentState = value;
|
periodDropDownIndex = periodDropDownTitles.indexOf(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: PeriodFilterStatus.values
|
items: periodDropDownTitles
|
||||||
.mapIndexed(
|
.mapIndexed(
|
||||||
(index, value) => DropdownMenuItem(
|
(index, value) => DropdownMenuItem(
|
||||||
value: PeriodFilterStatus.values[index],
|
value: periodDropDownTitles[index],
|
||||||
child: Text(
|
child: Text(
|
||||||
PeriodFilterStatus.values[index].name,
|
periodDropDownTitles[index],
|
||||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
value: _currentState,
|
value: periodDropDownTitles[periodDropDownIndex],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,6 +11,7 @@ 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,
|
||||||
@@ -28,7 +29,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 SizedBox(height: 12),
|
const Spacer(),
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
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';
|
||||||
@@ -10,8 +9,7 @@ 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, required this.isLoading});
|
const RatingHistoryWidget({super.key, required this.ratingHistory});
|
||||||
final bool isLoading;
|
|
||||||
final List<RatingModel> ratingHistory;
|
final List<RatingModel> ratingHistory;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -22,60 +20,50 @@ class RatingHistoryWidget extends StatelessWidget {
|
|||||||
right: 16,
|
right: 16,
|
||||||
top: 16,
|
top: 16,
|
||||||
),
|
),
|
||||||
child: isLoading
|
child: Column(
|
||||||
? const Center(child: Loader())
|
children: [
|
||||||
: Column(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Text(
|
||||||
children: [
|
s.rating_history,
|
||||||
Text(
|
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||||
s.rating_history,
|
),
|
||||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
const Spacer(),
|
||||||
),
|
SvgPicture.asset(
|
||||||
const Spacer(),
|
'assets/icons/question_mark.svg',
|
||||||
SvgPicture.asset(
|
height: 22,
|
||||||
'assets/icons/question_mark.svg',
|
width: 22,
|
||||||
height: 22,
|
colorFilter: ColorFilter.mode(
|
||||||
width: 22,
|
appTheme.blueA400,
|
||||||
colorFilter: ColorFilter.mode(
|
BlendMode.srcIn,
|
||||||
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(
|
},
|
||||||
height: 12,
|
itemCount: ratingHistory.length,
|
||||||
),
|
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,8 +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/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';
|
||||||
@@ -10,15 +8,14 @@ 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, required this.isLoading, required this.statistics});
|
const StatisticWidget({super.key});
|
||||||
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: isLoading ? const Center(child: Loader()) : Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -41,17 +38,15 @@ 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: statistics?.countReferal ?? 0, title: s.invited_people_count)),
|
Expanded(child: StatisticBouble(number: 12, title: s.invited_people_count)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: StatisticBouble(number: statistics?.blogPostsCount ?? 0, title: s.published_posts_count)),
|
Expanded(child: StatisticBouble(number: 12, title: s.published_posts_count)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
@@ -59,9 +54,9 @@ class StatisticWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: StatisticBouble(number: statistics?.recipesCount ?? 0, title: s.published_recipes_count)),
|
Expanded(child: StatisticBouble(number: 12, title: s.published_recipes_count)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: StatisticBouble(number: statistics?.dietsCount ?? 0, title: s.published_diets_count)),
|
Expanded(child: StatisticBouble(number: 12, title: s.published_diets_count)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
@@ -69,9 +64,9 @@ class StatisticWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: StatisticBouble(number: statistics?.consultationsCount ?? 0, title: s.consultations_count)),
|
Expanded(child: StatisticBouble(number: 12, title: s.consultations_count)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: StatisticBouble(number: statistics?.qwe ?? 0, title: s.active_consultations_count)),
|
Expanded(child: StatisticBouble(number: 12, title: s.active_consultations_count)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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});
|
|
||||||
}
|
|
||||||
@@ -354,7 +354,7 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_legendTitle(String text, Color color) {
|
Row _legendTitle(String text, Color color) {
|
||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
isAuthor: widget.diet.author.authorIsMe,
|
isAuthor: widget.diet.author.authorIsMe,
|
||||||
path: 'diet/${widget.diet.slug}',
|
path: 'diet/${widget.diet.slug}',
|
||||||
complainedContentType: ComplainedContent.diet,
|
complainedContentType: ComplainedContent.diet,
|
||||||
|
userSlug: widget.diet.author.authorSlug,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
],
|
],
|
||||||
@@ -347,7 +348,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_dayOfPlan(Map<int, Map<int, DailySchedule>> day, int dayNumber, S s) {
|
Padding _dayOfPlan(Map<int, Map<int, DailySchedule>> day, int dayNumber, S s) {
|
||||||
choiceChipWidget(int index) {
|
choiceChipWidget(int index) {
|
||||||
return ChoiceChip(
|
return ChoiceChip(
|
||||||
selectedColor: appTheme.blueA400,
|
selectedColor: appTheme.blueA400,
|
||||||
@@ -434,7 +435,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_daily(DailySchedule? daily, S s) {
|
SingleChildRenderObjectWidget _daily(DailySchedule? daily, S s) {
|
||||||
if (daily == null) return const SizedBox.shrink();
|
if (daily == null) return const SizedBox.shrink();
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 12),
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ class _DayWidgetState extends State<DayWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_product(int index, String product, int quantity, String portion, int dayIndex, int mealIndex) {
|
Material _product(int index, String product, int quantity, String portion, int dayIndex, int mealIndex) {
|
||||||
return Material(
|
return Material(
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -313,7 +313,7 @@ class _DayWidgetState extends State<DayWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_modalBottomSheet({
|
Future _modalBottomSheet({
|
||||||
required CreateDietCubit createDietCubit,
|
required CreateDietCubit createDietCubit,
|
||||||
required int dayIndex,
|
required int dayIndex,
|
||||||
required int mealIndex,
|
required int mealIndex,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
addStep() {
|
void addStep() {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith.entity(
|
state.copyWith.entity(
|
||||||
steps: [
|
steps: [
|
||||||
@@ -43,13 +43,13 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeStep(StepEntity step) {
|
void removeStep(StepEntity step) {
|
||||||
List<StepEntity> steps = [...state.entity.steps];
|
List<StepEntity> steps = [...state.entity.steps];
|
||||||
steps.remove(step);
|
steps.remove(step);
|
||||||
emit(state.copyWith.entity(steps: steps));
|
emit(state.copyWith.entity(steps: steps));
|
||||||
}
|
}
|
||||||
|
|
||||||
addIngredient(i.Ingredient entity) {
|
void addIngredient(i.Ingredient entity) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith.entity(
|
state.copyWith.entity(
|
||||||
ingredients: [
|
ingredients: [
|
||||||
@@ -60,7 +60,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeIngredient(int index) {
|
void removeIngredient(int index) {
|
||||||
List<i.Ingredient> ingredients = [...state.entity.ingredients];
|
List<i.Ingredient> ingredients = [...state.entity.ingredients];
|
||||||
ingredients.removeAt(index);
|
ingredients.removeAt(index);
|
||||||
|
|
||||||
@@ -73,26 +73,26 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
|||||||
emit(state.copyWith.entity(ingredients: ingredients));
|
emit(state.copyWith.entity(ingredients: ingredients));
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceIngredientPosition(int oldIndex, int newIndex) {
|
void replaceIngredientPosition(int oldIndex, int newIndex) {
|
||||||
List<i.Ingredient> ingredients = [...state.entity.ingredients];
|
List<i.Ingredient> ingredients = [...state.entity.ingredients];
|
||||||
final item = ingredients.removeAt(oldIndex);
|
final item = ingredients.removeAt(oldIndex);
|
||||||
ingredients.insert(newIndex, item);
|
ingredients.insert(newIndex, item);
|
||||||
emit(state.copyWith.entity(ingredients: ingredients));
|
emit(state.copyWith.entity(ingredients: ingredients));
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceTitle(String title) {
|
void replaceTitle(String title) {
|
||||||
emit(state.copyWith.entity(title: title == '' ? null : title));
|
emit(state.copyWith.entity(title: title == '' ? null : title));
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceDescription(String description) {
|
void replaceDescription(String description) {
|
||||||
emit(state.copyWith.entity(description: description == '' ? null : description));
|
emit(state.copyWith.entity(description: description == '' ? null : description));
|
||||||
}
|
}
|
||||||
|
|
||||||
changeType(TypeOfRecipe typeOfRecipe) {
|
void changeType(TypeOfRecipe typeOfRecipe) {
|
||||||
emit(state.copyWith.entity(typeOfRecipe: typeOfRecipe));
|
emit(state.copyWith.entity(typeOfRecipe: typeOfRecipe));
|
||||||
}
|
}
|
||||||
|
|
||||||
addTag(String tag) {
|
void addTag(String tag) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith.entity(
|
state.copyWith.entity(
|
||||||
tags: [
|
tags: [
|
||||||
@@ -103,46 +103,46 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeTag(String tag) {
|
void removeTag(String tag) {
|
||||||
List<String> tags = [...state.entity.tags];
|
List<String> tags = [...state.entity.tags];
|
||||||
tags.remove(tag);
|
tags.remove(tag);
|
||||||
|
|
||||||
emit(state.copyWith.entity(tags: tags));
|
emit(state.copyWith.entity(tags: tags));
|
||||||
}
|
}
|
||||||
|
|
||||||
replacePortionsCount(int count) {
|
void replacePortionsCount(int count) {
|
||||||
emit(state.copyWith.entity(countOfPortions: count));
|
emit(state.copyWith.entity(countOfPortions: count));
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceEnegryInPortion(double energy) {
|
void replaceEnegryInPortion(double energy) {
|
||||||
emit(state.copyWith.entity(energyInOnePortion: energy));
|
emit(state.copyWith.entity(energyInOnePortion: energy));
|
||||||
}
|
}
|
||||||
|
|
||||||
setTotalCookingMinutes(int minutes) {
|
void setTotalCookingMinutes(int minutes) {
|
||||||
emit(state.copyWith.entity(totalCookMinutes: minutes));
|
emit(state.copyWith.entity(totalCookMinutes: minutes));
|
||||||
}
|
}
|
||||||
|
|
||||||
setTotalCookingHours(int hours) {
|
void setTotalCookingHours(int hours) {
|
||||||
emit(state.copyWith.entity(totalCookHours: hours));
|
emit(state.copyWith.entity(totalCookHours: hours));
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveCookingMinutes(int minutes) {
|
void setActiveCookingMinutes(int minutes) {
|
||||||
emit(state.copyWith.entity(activeCookMinutes: minutes));
|
emit(state.copyWith.entity(activeCookMinutes: minutes));
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveCookingHours(int hours) {
|
void setActiveCookingHours(int hours) {
|
||||||
emit(state.copyWith.entity(activeCookHours: hours));
|
emit(state.copyWith.entity(activeCookHours: hours));
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceVideoUrl(String videoUrl) {
|
void replaceVideoUrl(String videoUrl) {
|
||||||
emit(state.copyWith.entity(videoUrl: videoUrl));
|
emit(state.copyWith.entity(videoUrl: videoUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceImage(File? image) {
|
void replaceImage(File? image) {
|
||||||
emit(state.copyWith.entity(image: image));
|
emit(state.copyWith.entity(image: image));
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceDescriptionInStep(String description, int index) {
|
void replaceDescriptionInStep(String description, int index) {
|
||||||
List<StepEntity> steps = [...state.entity.steps];
|
List<StepEntity> steps = [...state.entity.steps];
|
||||||
|
|
||||||
steps[index] = steps[index].copyWith(description: description);
|
steps[index] = steps[index].copyWith(description: description);
|
||||||
@@ -150,7 +150,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
|||||||
emit(state.copyWith.entity(steps: steps));
|
emit(state.copyWith.entity(steps: steps));
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceImageInStep(File? image, int index) {
|
void replaceImageInStep(File? image, int index) {
|
||||||
List<StepEntity> steps = [...state.entity.steps];
|
List<StepEntity> steps = [...state.entity.steps];
|
||||||
|
|
||||||
steps[index] = steps[index].copyWith(image: image);
|
steps[index] = steps[index].copyWith(image: image);
|
||||||
@@ -182,7 +182,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disableLoading() {
|
void disableLoading() {
|
||||||
emit(state.copyWith(loading: false));
|
emit(state.copyWith(loading: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class _ImageWidgetState extends State<ImageWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_showImagePicker() {
|
void _showImagePicker() {
|
||||||
showCupertinoModalPopup<void>(
|
showCupertinoModalPopup<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ class _IngredientsWidgetState extends State<IngredientsWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_modalBottomSheet({
|
Future _modalBottomSheet({
|
||||||
required CreateRecipeCubit createRecipeCubit,
|
required CreateRecipeCubit createRecipeCubit,
|
||||||
int? portionIndex,
|
int? portionIndex,
|
||||||
String? productNameControllerText,
|
String? productNameControllerText,
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class _StepImageWidgetState extends State<StepImageWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_showImagePicker() {
|
void _showImagePicker() {
|
||||||
showCupertinoModalPopup<void>(
|
showCupertinoModalPopup<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:io';
|
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/const.dart';
|
import 'package:eatvio_app/core/const.dart';
|
||||||
import 'package:eatvio_app/core/firebase_api.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';
|
||||||
@@ -51,6 +52,29 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
|||||||
GetPlansComments(),
|
GetPlansComments(),
|
||||||
SendPlansComment(),
|
SendPlansComment(),
|
||||||
GetRecipesComments(),
|
GetRecipesComments(),
|
||||||
|
AuthServiceTest(),
|
||||||
|
DiaryServiceTest(),
|
||||||
|
BlockedUserServiceTest(),
|
||||||
|
PlanServiceTest(),
|
||||||
|
AboutRecipeServiceTest(),
|
||||||
|
BlogPostServiceTest(),
|
||||||
|
CalendarServiceTest(),
|
||||||
|
CardsServiceTest(),
|
||||||
|
ConsultantServiceTest(),
|
||||||
|
ConsultationServiceTest(),
|
||||||
|
CreateDietServiceTest(),
|
||||||
|
CreatePlanServiceTest(),
|
||||||
|
GlossaryServiceTest(),
|
||||||
|
MyDietsServiceTest(),
|
||||||
|
MyPlansServiceTest(),
|
||||||
|
MyRecipesServiceTest(),
|
||||||
|
NotificationsServiceTest(),
|
||||||
|
PlansServiceTest(),
|
||||||
|
ProfileServiceTest(),
|
||||||
|
RegistrationServiceTest(),
|
||||||
|
ResetPasswordServiceTest(),
|
||||||
|
SettingsServiceTest(),
|
||||||
|
WsServiceTest(),
|
||||||
];
|
];
|
||||||
final controller = TextEditingController();
|
final controller = TextEditingController();
|
||||||
late final List<TestState> testsStates;
|
late final List<TestState> testsStates;
|
||||||
@@ -81,6 +105,8 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
baseUrlForTest = controller.text;
|
baseUrlForTest = controller.text;
|
||||||
|
baseUrl = baseUrlForTest.toLowerCase();
|
||||||
|
sl<Dio>().options.baseUrl = baseUrl;
|
||||||
controller.clear();
|
controller.clear();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -89,43 +115,50 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
if (baseUrlForTest != baseUrl)
|
CustomOutlinedButton(
|
||||||
CustomOutlinedButton(
|
text: baseUrl == 'https://dev2.eatvio.com' ? 'Set production base url' : 'Set dev2 base url',
|
||||||
text: 'Set dev2 base url',
|
onPressed: () {
|
||||||
onPressed: () {
|
setState(() {
|
||||||
setState(() {
|
if (baseUrl == 'https://eatvio.ru') {
|
||||||
baseUrlForTest = baseUrl;
|
baseUrlForTest = 'https://dev2.eatvio.com';
|
||||||
});
|
} else {
|
||||||
},
|
baseUrlForTest = 'https://eatvio.ru';
|
||||||
buttonStyle: const ButtonStyle(),
|
}
|
||||||
),
|
baseUrl = baseUrlForTest.toLowerCase();
|
||||||
|
sl<Dio>().options.baseUrl = baseUrl;
|
||||||
|
controller.clear();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
buttonStyle: const ButtonStyle(),
|
||||||
|
),
|
||||||
SizedBox(height: baseUrlForTest != baseUrl ? 40 : 10),
|
SizedBox(height: baseUrlForTest != baseUrl ? 40 : 10),
|
||||||
ListView.builder(
|
if (baseUrl.toLowerCase() != 'https://eatvio.ru')
|
||||||
shrinkWrap: true,
|
ListView.builder(
|
||||||
itemCount: testCases.length,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
itemCount: testCases.length,
|
||||||
itemBuilder:
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
(context, index) => Padding(
|
itemBuilder: (context, index) => Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 10.0),
|
padding: const EdgeInsets.only(bottom: 10.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: Text('${index + 1}. ${testCases[index].name}')),
|
Expanded(child: Text('${index + 1}. ${testCases[index].name}')),
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
CustomOutlinedButton(
|
CustomOutlinedButton(
|
||||||
text: 'run',
|
text: 'run',
|
||||||
height: 20,
|
height: 20,
|
||||||
width: 60,
|
width: 60,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (testsStates[index] == TestState.loading) return;
|
if (testsStates[index] == TestState.loading) return;
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
testsStates[index] = TestState.loading;
|
testsStates[index] = TestState.loading;
|
||||||
});
|
});
|
||||||
|
try {
|
||||||
final result = await testCases[index].run(baseUrl: baseUrlForTest);
|
final result = await testCases[index].run(baseUrl: baseUrlForTest);
|
||||||
if (result) {
|
if (result.success) {
|
||||||
setState(() {
|
setState(() {
|
||||||
testsStates[index] = TestState.success;
|
testsStates[index] = TestState.success;
|
||||||
});
|
});
|
||||||
@@ -135,27 +168,65 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
|
|||||||
testsStates[index] = TestState.fail;
|
testsStates[index] = TestState.fail;
|
||||||
});
|
});
|
||||||
showErrorToast('${testCases[index].name} failed');
|
showErrorToast('${testCases[index].name} failed');
|
||||||
|
// Show AlertDialog with error description
|
||||||
|
if (context.mounted) {
|
||||||
|
showDialog(
|
||||||
|
context: AppRouter.navigatorKey.currentContext!,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Test Failed'),
|
||||||
|
content: SingleChildScrollView(child: Text(result.error ?? 'No error details.')),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
} catch (e, stack) {
|
||||||
buttonStyle: const ButtonStyle(),
|
setState(() {
|
||||||
),
|
testsStates[index] = TestState.fail;
|
||||||
const SizedBox(width: 5),
|
});
|
||||||
switch (testsStates[index]) {
|
showErrorToast('${testCases[index].name} failed');
|
||||||
TestState.success => Container(width: 20, height: 20, color: Colors.green),
|
// Show AlertDialog with error description
|
||||||
TestState.fail => Container(width: 20, height: 20, color: Colors.red),
|
if (context.mounted) {
|
||||||
TestState.notRun => Container(width: 20, height: 20, color: Colors.grey),
|
showDialog(
|
||||||
TestState.loading => const SizedBox(
|
context: AppRouter.navigatorKey.currentContext!,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Test Failed'),
|
||||||
|
content: SingleChildScrollView(child: Text('$e\n$stack')),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buttonStyle: const ButtonStyle(),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
switch (testsStates[index]) {
|
||||||
|
TestState.success => Container(width: 20, height: 20, color: Colors.green),
|
||||||
|
TestState.fail => Container(width: 20, height: 20, color: Colors.red),
|
||||||
|
TestState.notRun => Container(width: 20, height: 20, color: Colors.grey),
|
||||||
|
TestState.loading => const SizedBox(
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
child: SizedBox(height: 20, width: 20, child: Center(child: CircularProgressIndicator())),
|
child: SizedBox(height: 20, width: 20, child: Center(child: CircularProgressIndicator())),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
CustomOutlinedButton(
|
CustomOutlinedButton(
|
||||||
text: 'add token to server',
|
text: 'add token to server',
|
||||||
|
|||||||
@@ -4,19 +4,48 @@ import 'dart:developer';
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:eatvio_app/core/locator_service.dart';
|
import 'package:eatvio_app/core/locator_service.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/core/websockets/ws_service.dart';
|
||||||
|
import 'package:eatvio_app/features/auth/models/credentials_model.dart';
|
||||||
|
import 'package:eatvio_app/features/auth/services/reset_password_service.dart';
|
||||||
|
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
|
||||||
|
import 'package:eatvio_app/features/calendar/services/calendar_service.dart';
|
||||||
import 'package:eatvio_app/features/chat/services/chat_service.dart';
|
import 'package:eatvio_app/features/chat/services/chat_service.dart';
|
||||||
|
import 'package:eatvio_app/features/consultant/become_consultant/services/consultant_service.dart';
|
||||||
|
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
|
||||||
|
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
|
||||||
|
import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart';
|
||||||
import 'package:eatvio_app/features/create_recipe/models/recipe_model.dart';
|
import 'package:eatvio_app/features/create_recipe/models/recipe_model.dart';
|
||||||
import 'package:eatvio_app/features/create_recipe/models/step.dart';
|
import 'package:eatvio_app/features/create_recipe/models/step.dart';
|
||||||
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
|
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
|
||||||
import 'package:eatvio_app/features/diets/services/diet_service.dart';
|
import 'package:eatvio_app/features/diets/services/diet_service.dart';
|
||||||
import 'package:eatvio_app/features/feed/services/feed_service.dart';
|
import 'package:eatvio_app/features/feed/services/feed_service.dart';
|
||||||
|
import 'package:eatvio_app/features/glossary/services/glossary_service.dart';
|
||||||
|
import 'package:eatvio_app/features/menu/services/blocked_users_service.dart';
|
||||||
|
import 'package:eatvio_app/features/my_diets/services/my_diets_service.dart';
|
||||||
|
import 'package:eatvio_app/features/my_finance/services/cards_service.dart';
|
||||||
|
import 'package:eatvio_app/features/my_plans/services/my_plans_service.dart';
|
||||||
|
import 'package:eatvio_app/features/notifications/service/notifications_service.dart';
|
||||||
|
import 'package:eatvio_app/features/plans/services/plan_service.dart';
|
||||||
|
import 'package:eatvio_app/features/plans/services/plans_service.dart';
|
||||||
import 'package:eatvio_app/features/profile/services/profile_service.dart';
|
import 'package:eatvio_app/features/profile/services/profile_service.dart';
|
||||||
import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
|
import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
|
||||||
|
import 'package:eatvio_app/features/auth/services/auth_service.dart';
|
||||||
|
import 'package:eatvio_app/features/diary/services/diary_service.dart';
|
||||||
|
import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart';
|
||||||
|
import 'package:eatvio_app/features/recipe/services/my_recipes_service.dart';
|
||||||
|
import 'package:eatvio_app/features/registration/services/registration_service.dart';
|
||||||
|
import 'package:eatvio_app/features/settings/services/settings_service.dart';
|
||||||
|
|
||||||
|
class TestResult {
|
||||||
|
final bool success;
|
||||||
|
final String? error;
|
||||||
|
TestResult(this.success, [this.error]);
|
||||||
|
}
|
||||||
|
|
||||||
sealed class TestCases {
|
sealed class TestCases {
|
||||||
String get name => throw UnimplementedError();
|
String get name => throw UnimplementedError();
|
||||||
|
|
||||||
Future<bool> run({required String baseUrl}) async => throw UnimplementedError();
|
Future<TestResult> run({required String baseUrl}) async => throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreateRecipeTask implements TestCases {
|
class CreateRecipeTask implements TestCases {
|
||||||
@@ -24,7 +53,7 @@ class CreateRecipeTask implements TestCases {
|
|||||||
String get name => 'api/recipes/create';
|
String get name => 'api/recipes/create';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
final dio = sl<Dio>();
|
final dio = sl<Dio>();
|
||||||
final recipeRepository = RecipeRepository(recipeService: CreateRecipeService(dio, baseUrl: baseUrl));
|
final recipeRepository = RecipeRepository(recipeService: CreateRecipeService(dio, baseUrl: baseUrl));
|
||||||
@@ -41,10 +70,16 @@ class CreateRecipeTask implements TestCases {
|
|||||||
steps: [Step(description: 'desc')],
|
steps: [Step(description: 'desc')],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} on DioException catch (e) {
|
||||||
return false;
|
final res = e.response;
|
||||||
|
if (res != null && (res.statusCode == null || res.statusCode! < 200 || res.statusCode! >= 300)) {
|
||||||
|
return TestResult(false, 'DioError: statusCode=${res.statusCode}\n${res.data}');
|
||||||
|
}
|
||||||
|
return TestResult(false, e.toString());
|
||||||
|
} catch (e, stack) {
|
||||||
|
return TestResult(false, '$e\n$stack');
|
||||||
}
|
}
|
||||||
return true;
|
return TestResult(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,69 +88,54 @@ class Test850task implements TestCases {
|
|||||||
String get name => 'task #850';
|
String get name => 'task #850';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
// get all of needed services
|
|
||||||
final dio = sl<Dio>();
|
final dio = sl<Dio>();
|
||||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||||
final profileService = ProfileService(dio, baseUrl: baseUrl);
|
final profileService = ProfileService(dio, baseUrl: baseUrl);
|
||||||
final dietsService = DietService(dio, baseUrl: baseUrl);
|
final dietsService = DietService(dio, baseUrl: baseUrl);
|
||||||
|
|
||||||
// get all of needed data
|
|
||||||
final dietsPaginationModel = await feedService.getDiets(page: 1);
|
final dietsPaginationModel = await feedService.getDiets(page: 1);
|
||||||
final chosenDietsPaginationModel = await profileService.getChosenDiets();
|
final chosenDietsPaginationModel = await profileService.getChosenDiets();
|
||||||
final lastChosenDiet = chosenDietsPaginationModel.diets.firstOrNull;
|
final lastChosenDiet = chosenDietsPaginationModel.diets.firstOrNull;
|
||||||
final today = DateTime.now();
|
final today = DateTime.now();
|
||||||
final yesterday = today.subtract(const Duration(days: 1));
|
final yesterday = today.subtract(const Duration(days: 1));
|
||||||
final tomorrow = today.add(const Duration(days: 1));
|
final tomorrow = today.add(const Duration(days: 1));
|
||||||
|
|
||||||
// run test case
|
|
||||||
for (final diet in dietsPaginationModel.data) {
|
for (final diet in dietsPaginationModel.data) {
|
||||||
if (!diet.author.authorIsMe) {
|
if (!diet.author.authorIsMe) {
|
||||||
if (lastChosenDiet?.state == 'started' || lastChosenDiet?.state == 'inited') {
|
if (lastChosenDiet?.state == 'started' || lastChosenDiet?.state == 'inited') {
|
||||||
try {
|
try {
|
||||||
await dietsService.cancelDiet(id: lastChosenDiet?.dietId ?? 0);
|
await dietsService.cancelDiet(id: lastChosenDiet?.dietId ?? 0);
|
||||||
} catch (e) {
|
} catch (e, stack) {
|
||||||
log('unexpected error7: $e');
|
log('unexpected error7: $e');
|
||||||
return false;
|
return TestResult(false, '$e\n$stack');
|
||||||
}
|
}
|
||||||
await Future.delayed(const Duration(seconds: 3));
|
await Future.delayed(const Duration(seconds: 3));
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await dietsService.startDiet(id: diet.id, body: {'date': yesterday.format('yyyy-MM-dd')});
|
await dietsService.startDiet(id: diet.id, body: {'date': yesterday.format('yyyy-MM-dd')});
|
||||||
|
return TestResult(false, 'Expected error but got success');
|
||||||
return false;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('expect ot have error: $e');
|
log('expect ot have error: $e');
|
||||||
}
|
}
|
||||||
|
|
||||||
// test start diet with correct date
|
|
||||||
try {
|
try {
|
||||||
await dietsService.startDiet(id: diet.id, body: {'date': today.format('yyyy-MM-dd')});
|
await dietsService.startDiet(id: diet.id, body: {'date': today.format('yyyy-MM-dd')});
|
||||||
} catch (e) {
|
} catch (e, stack) {
|
||||||
log('unexpected error2: $e');
|
log('unexpected error2: $e');
|
||||||
return false;
|
return TestResult(false, '$e\n$stack');
|
||||||
}
|
}
|
||||||
|
|
||||||
// test cancelation of diet
|
|
||||||
try {
|
try {
|
||||||
await dietsService.cancelDiet(id: diet.id);
|
await dietsService.cancelDiet(id: diet.id);
|
||||||
} catch (e) {
|
} catch (e, stack) {
|
||||||
log('unexpected error3: $e');
|
log('unexpected error3: $e');
|
||||||
return false;
|
return TestResult(false, '$e\n$stack');
|
||||||
}
|
}
|
||||||
|
|
||||||
await Future.delayed(const Duration(seconds: 3));
|
await Future.delayed(const Duration(seconds: 3));
|
||||||
|
|
||||||
// test start diet with correct date
|
|
||||||
try {
|
try {
|
||||||
await dietsService.startDiet(id: diet.id, body: {'date': tomorrow.format('yyyy-MM-dd')});
|
await dietsService.startDiet(id: diet.id, body: {'date': tomorrow.format('yyyy-MM-dd')});
|
||||||
} catch (e) {
|
} catch (e, stack) {
|
||||||
log('unexpected error4: $e');
|
log('unexpected error4: $e');
|
||||||
return false;
|
return TestResult(false, '$e\n$stack');
|
||||||
}
|
}
|
||||||
|
|
||||||
// test cancelation of diet
|
|
||||||
try {
|
try {
|
||||||
await dietsService.cancelDiet(id: diet.id);
|
await dietsService.cancelDiet(id: diet.id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -124,11 +144,10 @@ class Test850task implements TestCases {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return TestResult(true);
|
||||||
return true;
|
} catch (e, stack) {
|
||||||
} catch (e) {
|
|
||||||
log('unexpected error6: $e');
|
log('unexpected error6: $e');
|
||||||
return false;
|
return TestResult(false, '$e\n$stack');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,16 +158,16 @@ class GetChatRoomPreviews implements TestCases {
|
|||||||
String get name => '/api/chat/index';
|
String get name => '/api/chat/index';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
// get all of needed services
|
// get all of needed services
|
||||||
// final dio = sl<Dio>();
|
// final dio = sl<Dio>();
|
||||||
// final chatService = ChatService(dio, baseUrl: baseUrl);
|
// final chatService = ChatService(dio, baseUrl: baseUrl);
|
||||||
|
|
||||||
// await chatService.getChatRooms();
|
// await chatService.getChatRooms();
|
||||||
return true;
|
return TestResult(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return TestResult(false, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,16 +177,16 @@ class GetChatRoom implements TestCases {
|
|||||||
String get name => '/api/chat/show/{id}';
|
String get name => '/api/chat/show/{id}';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
// final dio = sl<Dio>();
|
// final dio = sl<Dio>();
|
||||||
// final chatService = ChatService(dio, baseUrl: baseUrl);
|
// final chatService = ChatService(dio, baseUrl: baseUrl);
|
||||||
|
|
||||||
// final chatRoomsModels = await chatService.getChatRoomPreviews();
|
// final chatRoomsModels = await chatService.getChatRoomPreviews();
|
||||||
// await chatService.getChatRoom(id: chatRoomsModels.chatRooms.firstOrNull?.id ?? '');
|
// await chatService.getChatRoom(id: chatRoomsModels.chatRooms.firstOrNull?.id ?? '');
|
||||||
return true;
|
return TestResult(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return TestResult(false, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,16 +196,16 @@ class SendMessage implements TestCases {
|
|||||||
String get name => '/api/chat/{id}/message/send';
|
String get name => '/api/chat/{id}/message/send';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
// final dio = sl<Dio>();
|
// final dio = sl<Dio>();
|
||||||
// final chatService = ChatService(dio, baseUrl: baseUrl);
|
// final chatService = ChatService(dio, baseUrl: baseUrl);
|
||||||
|
|
||||||
// final chatRoomsModels = await chatService.getChatRoomPreviews();
|
// final chatRoomsModels = await chatService.getChatRoomPreviews();
|
||||||
// await chatService.sendMessage(id: chatRoomsModels.chatRooms.firstOrNull?.id ?? '', data: 'test message');
|
// await chatService.sendMessage(id: chatRoomsModels.chatRooms.firstOrNull?.id ?? '', data: 'test message');
|
||||||
return true;
|
return TestResult(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return TestResult(false, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,16 +215,16 @@ class GetChatMessages implements TestCases {
|
|||||||
String get name => '/api/chat/show/{id}/messages?page={page}';
|
String get name => '/api/chat/show/{id}/messages?page={page}';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
// final dio = sl<Dio>();
|
// final dio = sl<Dio>();
|
||||||
// final chatService = ChatService(dio, baseUrl: baseUrl);
|
// final chatService = ChatService(dio, baseUrl: baseUrl);
|
||||||
|
|
||||||
// final chatRoomsModels = await chatService.getChatRoomPreviews();
|
// final chatRoomsModels = await chatService.getChatRoomPreviews();
|
||||||
// await chatService.getChatMessages(id: chatRoomsModels.chatRooms.firstOrNull?.id ?? '', page: 1);
|
// await chatService.getChatMessages(id: chatRoomsModels.chatRooms.firstOrNull?.id ?? '', page: 1);
|
||||||
return true;
|
return TestResult(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return TestResult(false, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,7 +234,7 @@ class GetChatRoomIdByUserSlug implements TestCases {
|
|||||||
String get name => '/api/chat/get_by_user/{user}';
|
String get name => '/api/chat/get_by_user/{user}';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
final dio = sl<Dio>();
|
final dio = sl<Dio>();
|
||||||
final chatService = ChatService(dio, baseUrl: baseUrl);
|
final chatService = ChatService(dio, baseUrl: baseUrl);
|
||||||
@@ -230,9 +249,9 @@ class GetChatRoomIdByUserSlug implements TestCases {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return TestResult(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return TestResult(false, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,14 +262,14 @@ class GetBlogPostList implements TestCases {
|
|||||||
String get name => '/api/blogPosts/index';
|
String get name => '/api/blogPosts/index';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
final dio = sl<Dio>();
|
final dio = sl<Dio>();
|
||||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||||
await feedService.getBlogPostList(page: 1);
|
await feedService.getBlogPostList(page: 1);
|
||||||
return true;
|
return TestResult(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return TestResult(false, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,7 +279,7 @@ class GetRecipes implements TestCases {
|
|||||||
String get name => '/api/recipes/index';
|
String get name => '/api/recipes/index';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
final dio = sl<Dio>();
|
final dio = sl<Dio>();
|
||||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||||
@@ -269,9 +288,9 @@ class GetRecipes implements TestCases {
|
|||||||
final tags = tagsModel.data;
|
final tags = tagsModel.data;
|
||||||
final tagsIds = tags.map((e) => e.id).toList();
|
final tagsIds = tags.map((e) => e.id).toList();
|
||||||
await feedService.getRecipes(page: 1, tags: tagsIds, query: null);
|
await feedService.getRecipes(page: 1, tags: tagsIds, query: null);
|
||||||
return true;
|
return TestResult(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return TestResult(false, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,14 +300,14 @@ class GetDiets implements TestCases {
|
|||||||
String get name => '/api/diets/index';
|
String get name => '/api/diets/index';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
final dio = sl<Dio>();
|
final dio = sl<Dio>();
|
||||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||||
await feedService.getDiets(page: 1, query: null);
|
await feedService.getDiets(page: 1, query: null);
|
||||||
return true;
|
return TestResult(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return TestResult(false, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,14 +317,14 @@ class GetPlans implements TestCases {
|
|||||||
String get name => '/api/plans/index';
|
String get name => '/api/plans/index';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
final dio = sl<Dio>();
|
final dio = sl<Dio>();
|
||||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||||
await feedService.getPlans(page: 1, query: null);
|
await feedService.getPlans(page: 1, query: null);
|
||||||
return true;
|
return TestResult(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return TestResult(false, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,16 +334,16 @@ class GetBlogPostComments implements TestCases {
|
|||||||
String get name => '/api/blogPosts/{id}/comments/show';
|
String get name => '/api/blogPosts/{id}/comments/show';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
final dio = sl<Dio>();
|
final dio = sl<Dio>();
|
||||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||||
final blogPostsModel = await feedService.getBlogPostList(page: 1, query: null);
|
final blogPostsModel = await feedService.getBlogPostList(page: 1, query: null);
|
||||||
final blogPosts = blogPostsModel.feedBlogPostList;
|
final blogPosts = blogPostsModel.feedBlogPostList;
|
||||||
await feedService.getBlogPostComments(id: blogPosts.first.id.toString());
|
await feedService.getBlogPostComments(id: blogPosts.first.id.toString());
|
||||||
return true;
|
return TestResult(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return TestResult(false, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,16 +353,16 @@ class SendBlogPostComment implements TestCases {
|
|||||||
String get name => '/api/blogPosts/{id}/comment/create';
|
String get name => '/api/blogPosts/{id}/comment/create';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
final dio = sl<Dio>();
|
final dio = sl<Dio>();
|
||||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||||
final blogPostsModel = await feedService.getBlogPostList(page: 1, query: null);
|
final blogPostsModel = await feedService.getBlogPostList(page: 1, query: null);
|
||||||
final blogPosts = blogPostsModel.feedBlogPostList;
|
final blogPosts = blogPostsModel.feedBlogPostList;
|
||||||
await feedService.sendBlogPostComment(id: blogPosts.first.id.toString(), body: 'auto test message');
|
await feedService.sendBlogPostComment(id: blogPosts.first.id.toString(), body: 'auto test message');
|
||||||
return true;
|
return TestResult(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return TestResult(false, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,16 +372,16 @@ class GetDietsComments implements TestCases {
|
|||||||
String get name => '/api/diets/{id}/comments/show';
|
String get name => '/api/diets/{id}/comments/show';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
final dio = sl<Dio>();
|
final dio = sl<Dio>();
|
||||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||||
final dietsModel = await feedService.getDiets(page: 1, query: null);
|
final dietsModel = await feedService.getDiets(page: 1, query: null);
|
||||||
final diets = dietsModel.data;
|
final diets = dietsModel.data;
|
||||||
await feedService.getDietsComments(id: diets.first.id.toString());
|
await feedService.getDietsComments(id: diets.first.id.toString());
|
||||||
return true;
|
return TestResult(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return TestResult(false, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,16 +391,16 @@ class SendDietsComment implements TestCases {
|
|||||||
String get name => '/api/diets/{id}/comment/create';
|
String get name => '/api/diets/{id}/comment/create';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
final dio = sl<Dio>();
|
final dio = sl<Dio>();
|
||||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||||
final dietsModel = await feedService.getDiets(page: 1, query: null);
|
final dietsModel = await feedService.getDiets(page: 1, query: null);
|
||||||
final diets = dietsModel.data;
|
final diets = dietsModel.data;
|
||||||
await feedService.sendDietsComment(id: diets.first.id.toString(), body: 'auto test message');
|
await feedService.sendDietsComment(id: diets.first.id.toString(), body: 'auto test message');
|
||||||
return true;
|
return TestResult(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return TestResult(false, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -391,16 +410,16 @@ class GetPlansComments implements TestCases {
|
|||||||
String get name => '/api/plans/{slug}/comments/show';
|
String get name => '/api/plans/{slug}/comments/show';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
final dio = sl<Dio>();
|
final dio = sl<Dio>();
|
||||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||||
final plansModel = await feedService.getPlans(page: 1, query: null);
|
final plansModel = await feedService.getPlans(page: 1, query: null);
|
||||||
final plans = plansModel.data;
|
final plans = plansModel.data;
|
||||||
await feedService.getPlansComments(slug: plans.first.slug);
|
await feedService.getPlansComments(slug: plans.first.slug);
|
||||||
return true;
|
return TestResult(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return TestResult(false, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -410,16 +429,16 @@ class SendPlansComment implements TestCases {
|
|||||||
String get name => '/api/plans/{slug}/comment/create';
|
String get name => '/api/plans/{slug}/comment/create';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
final dio = sl<Dio>();
|
final dio = sl<Dio>();
|
||||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||||
final plansModel = await feedService.getPlans(page: 1, query: null);
|
final plansModel = await feedService.getPlans(page: 1, query: null);
|
||||||
final plans = plansModel.data;
|
final plans = plansModel.data;
|
||||||
await feedService.sendPlansComment(slug: plans.first.slug, body: 'auto test message');
|
await feedService.sendPlansComment(slug: plans.first.slug, body: 'auto test message');
|
||||||
return true;
|
return TestResult(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return TestResult(false, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -429,7 +448,7 @@ class GetRecipesComments implements TestCases {
|
|||||||
String get name => '/api/recipes/{id}/comments/show';
|
String get name => '/api/recipes/{id}/comments/show';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> run({required String baseUrl}) async {
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
try {
|
try {
|
||||||
final dio = sl<Dio>();
|
final dio = sl<Dio>();
|
||||||
final feedService = FeedService(dio, baseUrl: baseUrl);
|
final feedService = FeedService(dio, baseUrl: baseUrl);
|
||||||
@@ -440,9 +459,426 @@ class GetRecipesComments implements TestCases {
|
|||||||
final recipesModel = await feedService.getRecipes(page: 1, tags: tagsIds, query: null);
|
final recipesModel = await feedService.getRecipes(page: 1, tags: tagsIds, query: null);
|
||||||
final recipes = recipesModel.recipesList;
|
final recipes = recipesModel.recipesList;
|
||||||
await feedService.getRecipesComments(id: recipes.first.id.toString());
|
await feedService.getRecipesComments(id: recipes.first.id.toString());
|
||||||
return true;
|
return TestResult(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return TestResult(false, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthService
|
||||||
|
class AuthServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'AuthService: getTokenByCredentials';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final authService = AuthService(dio, baseUrl: baseUrl);
|
||||||
|
// Use dummy credentials, expecting failure but no crash
|
||||||
|
await authService.getTokenByCredentials(credentials: const CredentialsModel(email: 'reshetnick.mike+user1@gmail.com', password: '1234567890'));
|
||||||
|
return TestResult(false, 'Should not succeed');
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(true, 'Exception expected for dummy credentials');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiaryService
|
||||||
|
class DiaryServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'DiaryService: getDiaryIndexByDate';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final diaryService = DiaryService(dio, baseUrl: baseUrl);
|
||||||
|
await diaryService.getDiaryIndexByDate(date: '2023-01-01');
|
||||||
|
return TestResult(true);
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(false, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockedUserService
|
||||||
|
class BlockedUserServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'BlockedUserService: getBlockedUsers';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final blockedUserService = BlockedUserService(dio, baseUrl: baseUrl);
|
||||||
|
await blockedUserService.getBlockedUsers();
|
||||||
|
return TestResult(true);
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(false, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlanService
|
||||||
|
class PlanServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'PlanService: getPlan';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final planService = PlanService(dio, baseUrl: baseUrl);
|
||||||
|
// Use a dummy slug, expecting failure but no crash
|
||||||
|
await planService.getPlan('dummy-slug');
|
||||||
|
return TestResult(false, 'Should not succeed');
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(true, 'Exception expected for dummy slug');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AboutRecipeService
|
||||||
|
class AboutRecipeServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'AboutRecipeService: getRecipe';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final aboutRecipeService = AboutRecipeService(dio, baseUrl: baseUrl);
|
||||||
|
await aboutRecipeService.getRecipe('dummy-id');
|
||||||
|
return TestResult(false, 'Expected failure but got success');
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(true, 'Exception expected for dummy id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlogPostService
|
||||||
|
class BlogPostServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'BlogPostService: createBlogPost';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final blogPostService = BlogPostService(dio, baseUrl: baseUrl);
|
||||||
|
// Use empty FormData, expecting failure but no crash
|
||||||
|
await blogPostService.createBlogPost(formData: FormData());
|
||||||
|
return TestResult(false, 'Expected failure but got success');
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(true, 'Exception expected for empty FormData');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalendarService
|
||||||
|
class CalendarServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'CalendarService: getCalendar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final calendarService = CalendarService(dio, baseUrl: baseUrl);
|
||||||
|
await calendarService.getCalendar();
|
||||||
|
return TestResult(true);
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(false, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CardsService
|
||||||
|
class CardsServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'CardsService: getCards';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final cardsService = CardsService(dio, baseUrl: baseUrl);
|
||||||
|
await cardsService.getCards();
|
||||||
|
return TestResult(true);
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(false, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsultantService
|
||||||
|
class ConsultantServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'ConsultantService: getMyRequestStatus';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final consultantService = ConsultantService(dio, baseUrl: baseUrl);
|
||||||
|
await consultantService.getMyRequestStatus(slug: 'dummy-slug');
|
||||||
|
return TestResult(false, 'Expected failure but got success');
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(true, 'Exception expected for dummy slug');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsultationService
|
||||||
|
class ConsultationServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'ConsultationService: getConsultations';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final consultationService = ConsultationService(dio);
|
||||||
|
await consultationService.getConsultations();
|
||||||
|
return TestResult(true);
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(false, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDietService
|
||||||
|
class CreateDietServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'CreateDietService: getDietPortionsNutrition';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final createDietService = CreateDietService(dio, baseUrl: baseUrl);
|
||||||
|
await createDietService.getDietPortionsNutrition(diet: 0);
|
||||||
|
return TestResult(false, 'Expected failure but got success');
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(true, 'Exception expected for diet: 0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePlanService
|
||||||
|
class CreatePlanServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'CreatePlanService: createPlan';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final createPlanService = CreatePlanService(dio, baseUrl: baseUrl);
|
||||||
|
await createPlanService.createPlan(formData: FormData());
|
||||||
|
return TestResult(false, 'Expected failure but got success');
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(true, 'Exception expected for empty FormData');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlossaryService
|
||||||
|
class GlossaryServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'GlossaryService: getGlossaryByQuery';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final glossaryService = GlossaryService(dio, baseUrl: baseUrl);
|
||||||
|
await glossaryService.getGlossaryByQuery(query: 'test');
|
||||||
|
return TestResult(true);
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(false, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MyDietsService
|
||||||
|
class MyDietsServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'MyDietsService: getDiets';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final myDietsService = MyDietsService(dio, baseUrl: baseUrl);
|
||||||
|
await myDietsService.getDiets(page: 1);
|
||||||
|
return TestResult(true);
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(false, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MyPlansService
|
||||||
|
class MyPlansServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'MyPlansService: getPlans';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final myPlansService = MyPlansService(dio, baseUrl: baseUrl);
|
||||||
|
await myPlansService.getPlans(page: 1);
|
||||||
|
return TestResult(true);
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(false, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MyRecipesService
|
||||||
|
class MyRecipesServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'MyRecipesService: getMyRecipes';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final myRecipesService = MyRecipesService(dio, baseUrl: baseUrl);
|
||||||
|
await myRecipesService.getMyRecipes();
|
||||||
|
return TestResult(true);
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(false, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotificationsService
|
||||||
|
class NotificationsServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'NotificationsService: getNotifications';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final notificationsService = NotificationsService(dio);
|
||||||
|
await notificationsService.getNotifications(page: 1);
|
||||||
|
return TestResult(true);
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(false, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlansService
|
||||||
|
class PlansServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'PlansService: getPlansBySlug';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final plansService = PlansService(dio, baseUrl: baseUrl);
|
||||||
|
await plansService.getPlansBySlug(author: 'dummy-author');
|
||||||
|
return TestResult(false, 'Expected failure but got success');
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(true, 'Exception expected for dummy author');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProfileService
|
||||||
|
class ProfileServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'ProfileService: getMyProfile';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final profileService = ProfileService(dio, baseUrl: baseUrl);
|
||||||
|
await profileService.getMyProfile();
|
||||||
|
return TestResult(true);
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(false, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegistrationService
|
||||||
|
class RegistrationServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'RegistrationService: getLocationsByAddress';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final registrationService = RegistrationService(dio, baseUrl: baseUrl);
|
||||||
|
await registrationService.getLocationsByAddress(query: 'test');
|
||||||
|
return TestResult(true);
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(false, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetPasswordService
|
||||||
|
class ResetPasswordServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'ResetPasswordService: sendEmailCodeForPasswordChange';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final resetPasswordService = ResetPasswordService(dio, baseUrl: baseUrl);
|
||||||
|
await resetPasswordService.sendEmailCodeForPasswordChange(data: {'email': 'test@example.com'});
|
||||||
|
return TestResult(false, 'Expected failure but got success');
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(true, 'Exception expected for test@example.com');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SettingsService
|
||||||
|
class SettingsServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'SettingsService: getProfileSettings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final settingsService = SettingsService(dio, baseUrl: baseUrl);
|
||||||
|
await settingsService.getProfileSettings();
|
||||||
|
return TestResult(true);
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(false, e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WsService
|
||||||
|
class WsServiceTest implements TestCases {
|
||||||
|
@override
|
||||||
|
String get name => 'WsService: getWebsocket';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TestResult> run({required String baseUrl}) async {
|
||||||
|
try {
|
||||||
|
final dio = sl<Dio>();
|
||||||
|
final wsService = WsService(dio, baseUrl: baseUrl);
|
||||||
|
await wsService.getWebsocket();
|
||||||
|
return TestResult(true);
|
||||||
|
} catch (e) {
|
||||||
|
return TestResult(false, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ abstract class DiaryFutureDietDayModel with _$DiaryFutureDietDayModel {
|
|||||||
factory DiaryFutureDietDayModel.fromJson(Map<String, dynamic> json) => _$DiaryFutureDietDayModelFromJson(json);
|
factory DiaryFutureDietDayModel.fromJson(Map<String, dynamic> json) => _$DiaryFutureDietDayModelFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
DiaryDietModel? _parseDiet(json) {
|
DiaryDietModel? _parseDiet(dynamic json) {
|
||||||
if (json == null) return null;
|
if (json == null) return null;
|
||||||
return DiaryDietModel.fromJson(json['diet']);
|
return DiaryDietModel.fromJson(json['diet']);
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ abstract class DiaryDietModel with _$DiaryDietModel {
|
|||||||
factory DiaryDietModel.fromJson(Map<String, dynamic> json) => _$DiaryDietModelFromJson(json);
|
factory DiaryDietModel.fromJson(Map<String, dynamic> json) => _$DiaryDietModelFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeOfDiaryModel _parseDayTypeForFutureDay(json) {
|
TypeOfDiaryModel _parseDayTypeForFutureDay(dynamic json) {
|
||||||
return TypeOfDiaryModel(
|
return TypeOfDiaryModel(
|
||||||
hasChosenDiet: json['has_chosen_diet'] as bool,
|
hasChosenDiet: json['has_chosen_diet'] as bool,
|
||||||
hasChosenPlan: json['has_chosen_plan'] as bool,
|
hasChosenPlan: json['has_chosen_plan'] as bool,
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedMeal(int meal) {
|
void setSelectedMeal(int meal) {
|
||||||
selectedMeal = meal;
|
selectedMeal = meal;
|
||||||
final currentState = state;
|
final currentState = state;
|
||||||
if (currentState is DiaryStateLoaded) {
|
if (currentState is DiaryStateLoaded) {
|
||||||
@@ -154,7 +154,7 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchDiary([bool needLoading = false, int? diaryId, int? currentDay]) async {
|
Future<void> fetchDiary([bool needLoading = false, int? diaryId, int? currentDay]) async {
|
||||||
this.diaryId ??= diaryId;
|
this.diaryId ??= diaryId;
|
||||||
|
|
||||||
if (needLoading) {
|
if (needLoading) {
|
||||||
@@ -215,7 +215,7 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finishDiaryDay({
|
Future<void> finishDiaryDay({
|
||||||
required int diaryId,
|
required int diaryId,
|
||||||
required String wakeUp,
|
required String wakeUp,
|
||||||
required String retire,
|
required String retire,
|
||||||
@@ -233,7 +233,7 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
return (await diaryRepository.getNutritionData(index));
|
return (await diaryRepository.getNutritionData(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
addProductToMeal({
|
Future<void> addProductToMeal({
|
||||||
required int diaryId,
|
required int diaryId,
|
||||||
required int mealId,
|
required int mealId,
|
||||||
required String productName,
|
required String productName,
|
||||||
@@ -252,7 +252,7 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
await fetchDiary();
|
await fetchDiary();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProductInMeal({
|
Future<void> updateProductInMeal({
|
||||||
required int diaryId,
|
required int diaryId,
|
||||||
required int mealId,
|
required int mealId,
|
||||||
required int portionId,
|
required int portionId,
|
||||||
@@ -286,22 +286,22 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
await fetchDiary();
|
await fetchDiary();
|
||||||
}
|
}
|
||||||
|
|
||||||
markEaten({required int diaryId, required int mealId, required int portionId}) async {
|
Future<void> markEaten({required int diaryId, required int mealId, required int portionId}) async {
|
||||||
await diaryRepository.markEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
|
await diaryRepository.markEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
|
||||||
await fetchDiary();
|
await fetchDiary();
|
||||||
}
|
}
|
||||||
|
|
||||||
unmarkEaten({required int diaryId, required int mealId, required int portionId}) async {
|
Future<void> unmarkEaten({required int diaryId, required int mealId, required int portionId}) async {
|
||||||
await diaryRepository.unmarkEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
|
await diaryRepository.unmarkEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
|
||||||
await fetchDiary();
|
await fetchDiary();
|
||||||
}
|
}
|
||||||
|
|
||||||
restore({required int diaryId, required int mealId, required int portionId}) async {
|
Future<void> restore({required int diaryId, required int mealId, required int portionId}) async {
|
||||||
await diaryRepository.restore(diaryId: diaryId, mealId: mealId, portionId: portionId);
|
await diaryRepository.restore(diaryId: diaryId, mealId: mealId, portionId: portionId);
|
||||||
await fetchDiary();
|
await fetchDiary();
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async {
|
Future<void> deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async {
|
||||||
await diaryRepository.deleteProductFromMeal(diaryId: diaryId, mealId: mealId, portionId: portionId);
|
await diaryRepository.deleteProductFromMeal(diaryId: diaryId, mealId: mealId, portionId: portionId);
|
||||||
await fetchDiary();
|
await fetchDiary();
|
||||||
}
|
}
|
||||||
@@ -310,12 +310,12 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
return await glossaryRepository.getGlossaryListByQuery(query);
|
return await glossaryRepository.getGlossaryListByQuery(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadPhotos({required List<File> files, required int diaryId}) async {
|
Future<void> uploadPhotos({required List<File> files, required int diaryId}) async {
|
||||||
await diaryRepository.uploadPhotos(files: await ImageCompressUtility.compressFilesList(files), diaryId: diaryId, mealId: selectedMeal);
|
await diaryRepository.uploadPhotos(files: await ImageCompressUtility.compressFilesList(files), diaryId: diaryId, mealId: selectedMeal);
|
||||||
await fetchDiary();
|
await fetchDiary();
|
||||||
}
|
}
|
||||||
|
|
||||||
deletePhoto({required DiaryPhotoModel photo, required int diaryId}) async {
|
Future<void> deletePhoto({required DiaryPhotoModel photo, required int diaryId}) async {
|
||||||
await diaryRepository.deletePhoto(photo: photo, diaryId: diaryId, mealId: selectedMeal);
|
await diaryRepository.deletePhoto(photo: photo, diaryId: diaryId, mealId: selectedMeal);
|
||||||
await fetchDiary();
|
await fetchDiary();
|
||||||
}
|
}
|
||||||
@@ -335,32 +335,32 @@ class DiaryCubit extends Cubit<DiaryState> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fillTime({required int diaryId, required String time}) async {
|
Future<void> fillTime({required int diaryId, required String time}) async {
|
||||||
await diaryRepository.fillTime(diaryId: diaryId, mealId: selectedMeal, time: time);
|
await diaryRepository.fillTime(diaryId: diaryId, mealId: selectedMeal, time: time);
|
||||||
await fetchDiary();
|
await fetchDiary();
|
||||||
}
|
}
|
||||||
|
|
||||||
fillWakeup({required int diaryId, required String time}) async {
|
Future<void> fillWakeup({required int diaryId, required String time}) async {
|
||||||
await diaryRepository.fillWakeup(diaryId: diaryId, time: time);
|
await diaryRepository.fillWakeup(diaryId: diaryId, time: time);
|
||||||
await fetchDiary();
|
await fetchDiary();
|
||||||
}
|
}
|
||||||
|
|
||||||
fillRetire({required int diaryId, required String time}) async {
|
Future<void> fillRetire({required int diaryId, required String time}) async {
|
||||||
await diaryRepository.fillRetire(diaryId: diaryId, time: time);
|
await diaryRepository.fillRetire(diaryId: diaryId, time: time);
|
||||||
await fetchDiary();
|
await fetchDiary();
|
||||||
}
|
}
|
||||||
|
|
||||||
fillSatiety({required int diaryId, required int satiety}) async {
|
Future<void> fillSatiety({required int diaryId, required int satiety}) async {
|
||||||
await diaryRepository.fillSatiety(diaryId: diaryId, mealId: selectedMeal, satiety: satiety);
|
await diaryRepository.fillSatiety(diaryId: diaryId, mealId: selectedMeal, satiety: satiety);
|
||||||
await fetchDiary();
|
await fetchDiary();
|
||||||
}
|
}
|
||||||
|
|
||||||
fillMood({required int diaryId, required int mood}) async {
|
Future<void> fillMood({required int diaryId, required int mood}) async {
|
||||||
await diaryRepository.fillMood(diaryId: diaryId, mealId: selectedMeal, mood: mood);
|
await diaryRepository.fillMood(diaryId: diaryId, mealId: selectedMeal, mood: mood);
|
||||||
await fetchDiary();
|
await fetchDiary();
|
||||||
}
|
}
|
||||||
|
|
||||||
fillComment(String comment) {
|
void fillComment(String comment) {
|
||||||
this.comment = comment;
|
this.comment = comment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class DiaryScreen extends StatelessWidget {
|
class DiaryScreen extends StatelessWidget {
|
||||||
const DiaryScreen({super.key});
|
const DiaryScreen({super.key, this.date});
|
||||||
|
final DateTime? date;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const DiaryScreenContent();
|
return const DiaryScreenContent();
|
||||||
@@ -52,9 +52,10 @@ class UserDiaryScreen extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DiaryScreenContent extends StatelessWidget {
|
class DiaryScreenContent extends StatelessWidget {
|
||||||
const DiaryScreenContent({super.key, this.diaryId, this.currentPlanDay});
|
const DiaryScreenContent({super.key, this.diaryId, this.currentPlanDay, this.date});
|
||||||
final int? diaryId;
|
final int? diaryId;
|
||||||
final int? currentPlanDay;
|
final int? currentPlanDay;
|
||||||
|
final DateTime? date;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class _AddProductButtonWidgetState extends State<AddProductButtonWidget> {
|
|||||||
diaryCubit = context.read<DiaryCubit>();
|
diaryCubit = context.read<DiaryCubit>();
|
||||||
}
|
}
|
||||||
|
|
||||||
addProductInMeal(DiaryStateLoaded state, [int? portionId]) {
|
Future addProductInMeal(DiaryStateLoaded state, [int? portionId]) {
|
||||||
return showModalBottomSheet(
|
return showModalBottomSheet(
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
context: context,
|
context: context,
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:table_calendar/table_calendar.dart';
|
import 'package:table_calendar/table_calendar.dart';
|
||||||
|
|
||||||
class CalendarWidget extends StatefulWidget {
|
class CalendarWidget extends StatefulWidget {
|
||||||
const CalendarWidget({super.key});
|
const CalendarWidget({super.key,});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CalendarWidget> createState() => _CalendarWidgetState();
|
State<CalendarWidget> createState() => _CalendarWidgetState();
|
||||||
}
|
}
|
||||||
@@ -134,13 +133,13 @@ class _CalendarWidgetState extends State<CalendarWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_nextCalendarFormat() {
|
void _nextCalendarFormat() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_calendarFormat = CalendarFormat.values[(_calendarFormat.index - 1) % CalendarFormat.values.length];
|
_calendarFormat = CalendarFormat.values[(_calendarFormat.index - 1) % CalendarFormat.values.length];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_arrowDown() {
|
GestureDetector _arrowDown() {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onVerticalDragStart: (_) {
|
onVerticalDragStart: (_) {
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ class CaloriesChartWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_chartData(String title, String value) {
|
Row _chartData(String title, String value) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
|
|||||||
productNameController.text = widget.productName;
|
productNameController.text = widget.productName;
|
||||||
}
|
}
|
||||||
|
|
||||||
_alert() {
|
Padding _alert() {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -137,7 +137,7 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_success() {
|
Padding _success() {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -189,7 +189,7 @@ class _GlossaryModerationRequestModalWidgetState extends State<GlossaryModeratio
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_addNewGlossaryProduct() {
|
Padding _addNewGlossaryProduct() {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class _PhotoGalleryWidgetState extends State<PhotoGalleryWidget> {
|
|||||||
diaryCubit = context.read<DiaryCubit>();
|
diaryCubit = context.read<DiaryCubit>();
|
||||||
}
|
}
|
||||||
|
|
||||||
showImagePicker(DiaryDayModel diaryDay) {
|
void showImagePicker(DiaryDayModel diaryDay) {
|
||||||
showCupertinoModalPopup<void>(
|
showCupertinoModalPopup<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class _ProductWidgetState extends State<ProductWidget> {
|
|||||||
isEaten = widget.portion.portionState == PortionState.eatenFromDiet ? true : false;
|
isEaten = widget.portion.portionState == PortionState.eatenFromDiet ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProductInMeal(DiaryStateLoaded state, PortionModel portion) {
|
Future updateProductInMeal(DiaryStateLoaded state, PortionModel portion) {
|
||||||
return showModalBottomSheet(
|
return showModalBottomSheet(
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
context: context,
|
context: context,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class _TimeOfMealPickerWidgetState extends State<TimeOfMealPickerWidget> {
|
|||||||
diaryCubit = context.read<DiaryCubit>();
|
diaryCubit = context.read<DiaryCubit>();
|
||||||
}
|
}
|
||||||
|
|
||||||
showTimePicker(int diaryId) {
|
void showTimePicker(int diaryId) {
|
||||||
showModalBottomSheet<void>(
|
showModalBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class _WakeupAndRetireWidgetState extends State<WakeupAndRetireWidget> {
|
|||||||
diaryCubit = context.read<DiaryCubit>();
|
diaryCubit = context.read<DiaryCubit>();
|
||||||
}
|
}
|
||||||
|
|
||||||
showTimePicker(int diaryId, bool isWakeup) {
|
void showTimePicker(int diaryId, bool isWakeup) {
|
||||||
showModalBottomSheet<void>(
|
showModalBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
|||||||
import 'package:eatvio_app/core/utils/toasts.dart';
|
import 'package:eatvio_app/core/utils/toasts.dart';
|
||||||
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
|
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
|
||||||
import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_state.dart';
|
import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_state.dart';
|
||||||
|
import 'package:eatvio_app/features/feed/models/diets_pagination_model.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';
|
||||||
|
|
||||||
@@ -16,10 +17,11 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
bool isCurrentDietStarted = false;
|
bool isCurrentDietStarted = false;
|
||||||
bool hasStartedDiet = false;
|
bool hasStartedDiet = false;
|
||||||
String? userStartedDietName;
|
String? userStartedDietName;
|
||||||
|
List<int> _selectedIndexes = [];
|
||||||
|
|
||||||
Future<void> getDiet(int id, {int? index}) async {
|
Future<void> getDiet(String slug, {int? index}) async {
|
||||||
try {
|
try {
|
||||||
final diet = await dietsRepository.getDiet(id);
|
final diet = await dietsRepository.getDietBySlug(slug);
|
||||||
if (index != null) {
|
if (index != null) {
|
||||||
dietsRepository.emitCommentsStateToFeed(CommentsState(index: index, totalComments: diet.totalComments));
|
dietsRepository.emitCommentsStateToFeed(CommentsState(index: index, totalComments: diet.totalComments));
|
||||||
}
|
}
|
||||||
@@ -30,10 +32,10 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initDietPage(int id) async {
|
Future<void> initDietPage(String slug) async {
|
||||||
try {
|
try {
|
||||||
emit(const AboutDietState.loading());
|
emit(const AboutDietState.loading());
|
||||||
final diet = await dietsRepository.getDiet(id);
|
final diet = await dietsRepository.getDietBySlug(slug);
|
||||||
final chosenDiets = await profileRepository.getChosenDiets();
|
final chosenDiets = await profileRepository.getChosenDiets();
|
||||||
final lastChosenDiet = chosenDiets.firstOrNull;
|
final lastChosenDiet = chosenDiets.firstOrNull;
|
||||||
if (lastChosenDiet != null && ['inited', 'started'].contains(lastChosenDiet.state)) {
|
if (lastChosenDiet != null && ['inited', 'started'].contains(lastChosenDiet.state)) {
|
||||||
@@ -47,7 +49,13 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
_selectedIndexes = List.generate(diet.dailySchedule.length, (index) => 0);
|
||||||
|
emit(AboutDietState.loaded(
|
||||||
|
diet: diet,
|
||||||
|
isStarted: isCurrentDietStarted,
|
||||||
|
hasStartedDiet: hasStartedDiet,
|
||||||
|
selectedIndexes: _selectedIndexes,
|
||||||
|
));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
emit(AboutDietState.error(message: e.toString()));
|
emit(AboutDietState.error(message: e.toString()));
|
||||||
@@ -67,7 +75,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
if (dietIndexInFeed != null) {
|
if (dietIndexInFeed != null) {
|
||||||
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedDiet.wasLikedByMe, totalLikes: updatedDiet.totalLikes, index: dietIndexInFeed));
|
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedDiet.wasLikedByMe, totalLikes: updatedDiet.totalLikes, index: dietIndexInFeed));
|
||||||
}
|
}
|
||||||
emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet, selectedIndexes: _selectedIndexes));
|
||||||
} else {
|
} else {
|
||||||
final updatedDiet = currentDiet.copyWith(
|
final updatedDiet = currentDiet.copyWith(
|
||||||
totalLikes: currentDiet.totalLikes + 1,
|
totalLikes: currentDiet.totalLikes + 1,
|
||||||
@@ -76,7 +84,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
if (dietIndexInFeed != null) {
|
if (dietIndexInFeed != null) {
|
||||||
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedDiet.wasLikedByMe, totalLikes: updatedDiet.totalLikes, index: dietIndexInFeed));
|
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedDiet.wasLikedByMe, totalLikes: updatedDiet.totalLikes, index: dietIndexInFeed));
|
||||||
}
|
}
|
||||||
emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet, selectedIndexes: _selectedIndexes));
|
||||||
}
|
}
|
||||||
|
|
||||||
EasyDebounce.debounce(
|
EasyDebounce.debounce(
|
||||||
@@ -91,7 +99,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
dietsRepository
|
dietsRepository
|
||||||
.emitLikesStateToFeed(LikesState(wasLikedByMe: currentDiet.wasLikedByMe, totalLikes: currentDiet.totalLikes, index: dietIndexInFeed));
|
.emitLikesStateToFeed(LikesState(wasLikedByMe: currentDiet.wasLikedByMe, totalLikes: currentDiet.totalLikes, index: dietIndexInFeed));
|
||||||
}
|
}
|
||||||
emit(AboutDietState.loaded(diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -108,7 +116,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
if (dietIndex != null) {
|
if (dietIndex != null) {
|
||||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: updatedDiet.isFavorite, index: dietIndex));
|
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: updatedDiet.isFavorite, index: dietIndex));
|
||||||
}
|
}
|
||||||
emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
||||||
|
|
||||||
EasyDebounce.debounce(
|
EasyDebounce.debounce(
|
||||||
'add_or_remove_diet_to_favorites',
|
'add_or_remove_diet_to_favorites',
|
||||||
@@ -122,7 +130,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
if (dietIndex != null) {
|
if (dietIndex != null) {
|
||||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentDiet.isFavorite, index: dietIndex));
|
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentDiet.isFavorite, index: dietIndex));
|
||||||
}
|
}
|
||||||
emit(AboutDietState.loaded(diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -139,7 +147,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
if (dietIndex != null) {
|
if (dietIndex != null) {
|
||||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: updatedDiet.isFavorite, index: dietIndex));
|
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: updatedDiet.isFavorite, index: dietIndex));
|
||||||
}
|
}
|
||||||
emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
||||||
|
|
||||||
EasyDebounce.debounce(
|
EasyDebounce.debounce(
|
||||||
'add_or_remove_diet_to_favorites',
|
'add_or_remove_diet_to_favorites',
|
||||||
@@ -153,7 +161,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
if (dietIndex != null) {
|
if (dietIndex != null) {
|
||||||
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentDiet.isFavorite, index: dietIndex));
|
dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentDiet.isFavorite, index: dietIndex));
|
||||||
}
|
}
|
||||||
emit(AboutDietState.loaded(diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -191,12 +199,31 @@ class AboutDietCubit extends Cubit<AboutDietState> {
|
|||||||
void clearStartedDietsState() {
|
void clearStartedDietsState() {
|
||||||
if (state is! DietStateLoaded) return;
|
if (state is! DietStateLoaded) return;
|
||||||
final diet = (state as DietStateLoaded).diet;
|
final diet = (state as DietStateLoaded).diet;
|
||||||
emit(AboutDietState.loaded(diet: diet));
|
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: diet));
|
||||||
}
|
}
|
||||||
|
|
||||||
void emitCurrentStartedDietsState() {
|
void emitCurrentStartedDietsState() {
|
||||||
if (state is! DietStateLoaded) return;
|
if (state is! DietStateLoaded) return;
|
||||||
final diet = (state as DietStateLoaded).diet;
|
final diet = (state as DietStateLoaded).diet;
|
||||||
emit(AboutDietState.loaded(diet: diet, isStarted: true));
|
emit(AboutDietState.loaded(selectedIndexes: _selectedIndexes,diet: diet, isStarted: true));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Diet?> getDietBySlug(String slug) async {
|
||||||
|
try {
|
||||||
|
return await dietsRepository.getDietBySlug(slug);
|
||||||
|
} catch (e) {
|
||||||
|
log(e.toString());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSelectedIndex({required int dayNumber, required int index}) {
|
||||||
|
final currentState = state;
|
||||||
|
if (currentState is DietStateLoaded) {
|
||||||
|
final updatedSelectedIndexes = [...currentState.selectedIndexes];
|
||||||
|
updatedSelectedIndexes[dayNumber] = index;
|
||||||
|
_selectedIndexes = updatedSelectedIndexes;
|
||||||
|
emit(currentState.copyWith(selectedIndexes: _selectedIndexes));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ abstract class AboutDietState with _$AboutDietState {
|
|||||||
const factory AboutDietState.loading() = DietStateLoading;
|
const factory AboutDietState.loading() = DietStateLoading;
|
||||||
const factory AboutDietState.loaded({
|
const factory AboutDietState.loaded({
|
||||||
required Diet diet,
|
required Diet diet,
|
||||||
|
@Default([]) List<int> selectedIndexes,
|
||||||
@Default(false) bool isStarted,
|
@Default(false) bool isStarted,
|
||||||
@Default(false) bool hasStartedDiet,
|
@Default(false) bool hasStartedDiet,
|
||||||
}) = DietStateLoaded;
|
}) = DietStateLoaded;
|
||||||
|
|||||||
@@ -32,16 +32,14 @@ import 'package:eatvio_app/core/theme/theme_helper.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:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
class AboutDietScreen extends StatefulWidget {
|
class AboutDietScreen extends StatefulWidget {
|
||||||
final Diet diet;
|
final String slug;
|
||||||
final int? index;
|
final int? index;
|
||||||
final bool navigateToComments;
|
final bool navigateToComments;
|
||||||
late final List<int> selectedIndexes;
|
|
||||||
|
|
||||||
AboutDietScreen({super.key, required this.diet, required this.index, required this.navigateToComments}) {
|
const AboutDietScreen({super.key, required this.index, required this.navigateToComments, required this.slug});
|
||||||
selectedIndexes = List.generate(diet.dailySchedule.length, (_) => 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AboutDietScreen> createState() => _AboutDietScreenState();
|
State<AboutDietScreen> createState() => _AboutDietScreenState();
|
||||||
@@ -55,11 +53,11 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
final textEditingController = TextEditingController();
|
final textEditingController = TextEditingController();
|
||||||
bool isMoreOptionsVisible = false;
|
bool isMoreOptionsVisible = false;
|
||||||
bool _jumped = false;
|
bool _jumped = false;
|
||||||
|
bool _isFirstLaunch = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
context.read<FeedCubit>().viewDiet(widget.diet.id);
|
|
||||||
scrollController.addListener(() {
|
scrollController.addListener(() {
|
||||||
if (isMoreOptionsVisible) {
|
if (isMoreOptionsVisible) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -79,15 +77,11 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final s = S.of(context);
|
final s = S.of(context);
|
||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => dietsCubit..initDietPage(widget.diet.id),
|
create: (context) => dietsCubit..initDietPage(widget.slug),
|
||||||
child: BlocProvider<CommentsCubit>(
|
child: BlocProvider<CommentsCubit>(
|
||||||
create: (BuildContext context) => commentsCubit
|
create: (BuildContext context) => commentsCubit,
|
||||||
..getComments(
|
|
||||||
widget.diet.id.toString(),
|
|
||||||
CommentSourceType.diet,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
child: BlocConsumer<CommentsCubit, CommentsState>(
|
child: BlocConsumer<CommentsCubit, CommentsState>(
|
||||||
listener: (BuildContext context, CommentsState commentsState) {
|
listener: (BuildContext context, CommentsState commentsState) {
|
||||||
if (commentsState is CommentsStateLoaded) {
|
if (commentsState is CommentsStateLoaded) {
|
||||||
@@ -100,6 +94,15 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
return BlocBuilder<AboutDietCubit, AboutDietState>(
|
return BlocBuilder<AboutDietCubit, AboutDietState>(
|
||||||
builder: (context, dietState) {
|
builder: (context, dietState) {
|
||||||
if (dietState is DietStateLoaded) {
|
if (dietState is DietStateLoaded) {
|
||||||
|
if (_isFirstLaunch) {
|
||||||
|
context.read<FeedCubit>().viewDiet(dietState.diet.id);
|
||||||
|
context.read<CommentsCubit>().getComments(
|
||||||
|
dietState.diet.id.toString(),
|
||||||
|
CommentSourceType.diet,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
_isFirstLaunch = false;
|
||||||
|
}
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
if (widget.navigateToComments && !_jumped) {
|
if (widget.navigateToComments && !_jumped) {
|
||||||
_scrollToBottom();
|
_scrollToBottom();
|
||||||
@@ -176,9 +179,9 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
isFavorite: dietState.diet.isFavorite,
|
isFavorite: dietState.diet.isFavorite,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (dietState.diet.isFavorite) {
|
if (dietState.diet.isFavorite) {
|
||||||
dietsCubit.removeFromFavorite(widget.diet.id, widget.index);
|
dietsCubit.removeFromFavorite(dietState.diet.id, widget.index);
|
||||||
} else {
|
} else {
|
||||||
dietsCubit.addToFavorite(widget.diet.id, widget.index);
|
dietsCubit.addToFavorite(dietState.diet.id, widget.index);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
width: 52,
|
width: 52,
|
||||||
@@ -269,7 +272,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
if (textEditingController.text.length > 2) {
|
if (textEditingController.text.length > 2) {
|
||||||
commentsCubit.sendComment(
|
commentsCubit.sendComment(
|
||||||
id: widget.diet.id.toString(),
|
id: dietState.diet.id.toString(),
|
||||||
commentText: textEditingController.text,
|
commentText: textEditingController.text,
|
||||||
parentCommentId: replyId,
|
parentCommentId: replyId,
|
||||||
commentSourceType: CommentSourceType.diet,
|
commentSourceType: CommentSourceType.diet,
|
||||||
@@ -280,7 +283,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
replyId = null;
|
replyId = null;
|
||||||
});
|
});
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
dietsCubit.getDiet(widget.diet.id, index: widget.index);
|
dietsCubit.getDiet(dietState.diet.slug, index: widget.index);
|
||||||
});
|
});
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
}
|
}
|
||||||
@@ -307,8 +310,17 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
),
|
),
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
|
leading: BackButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (context.canPop()) {
|
||||||
|
GoRouter.of(context).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GoRouter.of(context).go('/feed-screen');
|
||||||
|
},
|
||||||
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
widget.diet.name,
|
dietState.diet.name,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
@@ -317,11 +329,12 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
CustomPopup(
|
CustomPopup(
|
||||||
isAuthor: widget.diet.author.authorIsMe,
|
isAuthor: dietState.diet.author.authorIsMe,
|
||||||
path: 'diet/${widget.diet.slug}',
|
path: 'diet/${dietState.diet.slug}',
|
||||||
complainedContentType: ComplainedContent.diet,
|
complainedContentType: ComplainedContent.diet,
|
||||||
id: widget.diet.id,
|
id: dietState.diet.id,
|
||||||
diet: widget.diet,
|
diet: dietState.diet,
|
||||||
|
userSlug: dietState.diet.author.authorSlug,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
],
|
],
|
||||||
@@ -340,7 +353,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
CachedNetworkImage(
|
CachedNetworkImage(
|
||||||
imageUrl: '$baseUrl/storage/${widget.diet.image}',
|
imageUrl: '$baseUrl/storage/${dietState.diet.image}',
|
||||||
height: 300,
|
height: 300,
|
||||||
width: MediaQuery.sizeOf(context).width,
|
width: MediaQuery.sizeOf(context).width,
|
||||||
fit: BoxFit.fill,
|
fit: BoxFit.fill,
|
||||||
@@ -366,7 +379,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
dietsCubit.likeDiet(widget.diet.id, widget.index);
|
dietsCubit.likeDiet(dietState.diet.id, widget.index);
|
||||||
},
|
},
|
||||||
child: InfoBouble(
|
child: InfoBouble(
|
||||||
backgroundColor: appTheme.whiteA700,
|
backgroundColor: appTheme.whiteA700,
|
||||||
@@ -412,17 +425,17 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
InfoRow(
|
InfoRow(
|
||||||
diet: widget.diet,
|
diet: dietState.diet,
|
||||||
type: ProductType.diet,
|
type: ProductType.diet,
|
||||||
),
|
),
|
||||||
ExpandableText(
|
ExpandableText(
|
||||||
text: widget.diet.details,
|
text: dietState.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,
|
author: dietState.diet.author,
|
||||||
title: s.diet_author,
|
title: s.diet_author,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -436,7 +449,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'${s.diet_plan}(${widget.diet.duration.toString()} ${getDayForm(widget.diet.duration, s)})',
|
'${s.diet_plan}(${dietState.diet.duration.toString()} ${getDayForm(dietState.diet.duration, s)})',
|
||||||
style: TextStyles.headingXLSemibold.copyWith(
|
style: TextStyles.headingXLSemibold.copyWith(
|
||||||
color: appTheme.black,
|
color: appTheme.black,
|
||||||
),
|
),
|
||||||
@@ -449,9 +462,15 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return _dayOfPlan(widget.diet.dailySchedule[index + 1] ?? {}, index, s);
|
return _dayOfPlan(
|
||||||
|
dietState.diet.dailySchedule[index + 1] ?? {},
|
||||||
|
index,
|
||||||
|
s,
|
||||||
|
dietState.selectedIndexes,
|
||||||
|
dietState.diet,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
itemCount: widget.diet.dailySchedule.length,
|
itemCount: dietState.diet.dailySchedule.length,
|
||||||
),
|
),
|
||||||
MainBackgroundBouble(
|
MainBackgroundBouble(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
@@ -471,7 +490,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
if (state is CommentsStateLoading) const Loader(),
|
if (state is CommentsStateLoading) const Loader(),
|
||||||
if (state is CommentsStateLoaded)
|
if (state is CommentsStateLoaded)
|
||||||
CommentsWidget(
|
CommentsWidget(
|
||||||
id: widget.diet.id.toString(),
|
id: dietState.diet.id.toString(),
|
||||||
commentSourceType: CommentSourceType.diet,
|
commentSourceType: CommentSourceType.diet,
|
||||||
comment: state.comment,
|
comment: state.comment,
|
||||||
replyId: replyId,
|
replyId: replyId,
|
||||||
@@ -488,51 +507,49 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
|
leading: BackButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (context.canPop()) {
|
||||||
|
GoRouter.of(context).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GoRouter.of(context).go('/feed-screen');
|
||||||
|
},
|
||||||
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
widget.diet.name,
|
'',
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||||
),
|
),
|
||||||
actions: [
|
|
||||||
CustomPopup(
|
|
||||||
isAuthor: widget.diet.author.authorIsMe,
|
|
||||||
path: 'diet/${widget.diet.slug}',
|
|
||||||
complainedContentType: ComplainedContent.diet,
|
|
||||||
id: widget.diet.id,
|
|
||||||
diet: widget.diet,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
body: DataErrorWidget(
|
body: DataErrorWidget(
|
||||||
onReload: () => context.read<AboutDietCubit>().getDiet(widget.diet.id, index: widget.index),
|
onReload: () => context.read<AboutDietCubit>().getDiet(widget.slug, index: widget.index),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
|
leading: BackButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (context.canPop()) {
|
||||||
|
GoRouter.of(context).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GoRouter.of(context).go('/feed-screen');
|
||||||
|
},
|
||||||
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
widget.diet.name,
|
'',
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||||
),
|
),
|
||||||
actions: [
|
|
||||||
CustomPopup(
|
|
||||||
isAuthor: widget.diet.author.authorIsMe,
|
|
||||||
path: 'diet/${widget.diet.slug}',
|
|
||||||
complainedContentType: ComplainedContent.diet,
|
|
||||||
id: widget.diet.id,
|
|
||||||
diet: widget.diet,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
body: const Loader(),
|
body: const Loader(),
|
||||||
);
|
);
|
||||||
@@ -544,7 +561,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_dayOfPlan(Map<int, Map<int, DailySchedule>> day, int dayNumber, S s) {
|
Padding _dayOfPlan(Map<int, Map<int, DailySchedule>> day, int dayNumber, S s, List<int> selectedIndexes, Diet diet) {
|
||||||
choiceChipWidget(int index) {
|
choiceChipWidget(int index) {
|
||||||
return ChoiceChip(
|
return ChoiceChip(
|
||||||
selectedColor: appTheme.blueA400,
|
selectedColor: appTheme.blueA400,
|
||||||
@@ -557,14 +574,12 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
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 == selectedIndexes[dayNumber] ? Colors.white : appTheme.gray600,
|
||||||
),
|
),
|
||||||
selected: widget.selectedIndexes[dayNumber] == index,
|
selected: selectedIndexes[dayNumber] == index,
|
||||||
backgroundColor: appTheme.gray100,
|
backgroundColor: appTheme.gray100,
|
||||||
onSelected: (bool selected) {
|
onSelected: (bool selected) {
|
||||||
setState(() {
|
dietsCubit.setSelectedIndex(dayNumber: dayNumber, index: index);
|
||||||
widget.selectedIndexes[dayNumber] = index;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -612,18 +627,18 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
ListView.builder(
|
ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemBuilder: (context, index) => _daily(day[widget.selectedIndexes[dayNumber] + 1]?[index + 1], s),
|
itemBuilder: (context, index) => _daily(day[selectedIndexes[dayNumber] + 1]?[index + 1], s),
|
||||||
itemCount: day[widget.selectedIndexes[dayNumber] + 1]?.length ?? 0,
|
itemCount: day[selectedIndexes[dayNumber] + 1]?.length ?? 0,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
CpfcContainer(
|
CpfcContainer(
|
||||||
calories: widget.diet.dailyNutrition[dayNumber].energy,
|
calories: diet.dailyNutrition[dayNumber].energy,
|
||||||
proteins: widget.diet.dailyNutrition[dayNumber].proteins,
|
proteins: diet.dailyNutrition[dayNumber].proteins,
|
||||||
fats: widget.diet.dailyNutrition[dayNumber].fats,
|
fats: diet.dailyNutrition[dayNumber].fats,
|
||||||
carbohydrates: widget.diet.dailyNutrition[dayNumber].carbohydrates,
|
carbohydrates: diet.dailyNutrition[dayNumber].carbohydrates,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -631,7 +646,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_daily(DailySchedule? daily, S s) {
|
SingleChildRenderObjectWidget _daily(DailySchedule? daily, S s) {
|
||||||
if (daily == null) return const SizedBox();
|
if (daily == null) return const SizedBox();
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 12),
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
|
|||||||
@@ -33,6 +33,15 @@ class DietRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Diet> getDietBySlug(String slug) async {
|
||||||
|
try {
|
||||||
|
final diet = await dietsService.getDietBySlug(slug: slug);
|
||||||
|
return diet;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Diet> addToFavorite(int id) async {
|
Future<Diet> addToFavorite(int id) async {
|
||||||
try {
|
try {
|
||||||
return await dietsService.addToFavorite(id: id);
|
return await dietsService.addToFavorite(id: id);
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ abstract class DietService {
|
|||||||
@Path() required int id,
|
@Path() required int id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@GET('/api/diets/show_by_slug/{slug}')
|
||||||
|
Future<Diet> getDietBySlug({
|
||||||
|
@Path() required String slug,
|
||||||
|
});
|
||||||
|
|
||||||
@POST('/api/diets/{id}/start')
|
@POST('/api/diets/{id}/start')
|
||||||
Future<void> startDiet({
|
Future<void> startDiet({
|
||||||
@Path() required int id,
|
@Path() required int id,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ abstract class PlanModel with _$PlanModel {
|
|||||||
factory PlanModel.fromJson(Map<String, dynamic> json) => _$PlanModelFromJson(json);
|
factory PlanModel.fromJson(Map<String, dynamic> json) => _$PlanModelFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
PacketOptions? parseOption(json) {
|
PacketOptions? parseOption(dynamic json) {
|
||||||
if (json is Map<String, dynamic>) {
|
if (json is Map<String, dynamic>) {
|
||||||
return PacketOptions.fromJson(json);
|
return PacketOptions.fromJson(json);
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ abstract class PacketOptions with _$PacketOptions {
|
|||||||
factory PacketOptions.empty() => PacketOptions(items: [], price: double.parse('1200'), short: '', description: '');
|
factory PacketOptions.empty() => PacketOptions(items: [], price: double.parse('1200'), short: '', description: '');
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> _parseItems(json) {
|
List<String> _parseItems(dynamic json) {
|
||||||
if (json is Map<String, dynamic>) {
|
if (json is Map<String, dynamic>) {
|
||||||
return json.values.map((e) => e as String).toList();
|
return json.values.map((e) => e as String).toList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ abstract class Ingredient with _$Ingredient {
|
|||||||
|
|
||||||
factory Ingredient.fromJson(Map<String, Object?> json) => _$IngredientFromJson(json);
|
factory Ingredient.fromJson(Map<String, Object?> json) => _$IngredientFromJson(json);
|
||||||
}
|
}
|
||||||
int _parseProductId(json) {
|
int _parseProductId(dynamic json) {
|
||||||
if (json is String) {
|
if (json is String) {
|
||||||
return int.parse(json);
|
return int.parse(json);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
required this.feedRepository,
|
required this.feedRepository,
|
||||||
}) : super(const CommentsState());
|
}) : super(const CommentsState());
|
||||||
|
|
||||||
setReplyId(int? replyId) {
|
void setReplyId(int? replyId) {
|
||||||
emit((state as CommentsStateLoaded).copyWith(replyId: replyId));
|
emit((state as CommentsStateLoaded).copyWith(replyId: replyId));
|
||||||
}
|
}
|
||||||
|
|
||||||
getComments(String id, CommentSourceType commentSourceType, [bool isFirstFetch = false]) async {
|
Future<void> getComments(String id, CommentSourceType commentSourceType, [bool isFirstFetch = false]) async {
|
||||||
if (isFirstFetch) emit(const CommentsState.loading());
|
if (isFirstFetch) emit(const CommentsState.loading());
|
||||||
try {
|
try {
|
||||||
switch (commentSourceType) {
|
switch (commentSourceType) {
|
||||||
@@ -36,7 +36,7 @@ class CommentsCubit extends Cubit<CommentsState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendComment({
|
Future<void> sendComment({
|
||||||
required String id,
|
required String id,
|
||||||
required String commentText,
|
required String commentText,
|
||||||
required CommentSourceType commentSourceType,
|
required CommentSourceType commentSourceType,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class ConsultantsCubit extends Cubit<ConsultantsState> {
|
|||||||
required this.feedRepository,
|
required this.feedRepository,
|
||||||
}) : super(const ConsultantsState());
|
}) : super(const ConsultantsState());
|
||||||
|
|
||||||
getConsultants() async {
|
Future<void> getConsultants() async {
|
||||||
emit(const ConsultantsState.loading());
|
emit(const ConsultantsState.loading());
|
||||||
try {
|
try {
|
||||||
emit(
|
emit(
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class DietsCubit extends Cubit<DietsState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadDiets({
|
Future<void> loadDiets({
|
||||||
String? query,
|
String? query,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ class FeedCubit extends Cubit<FeedState> {
|
|||||||
required this.feedRepository,
|
required this.feedRepository,
|
||||||
}) : super(const FeedState.loading());
|
}) : super(const FeedState.loading());
|
||||||
|
|
||||||
updateFeedBlogPostList(FeedBlogPostModel feedBlogPostModel, bool wasLikedByMe) {
|
void updateFeedBlogPostList(FeedBlogPostModel? feedBlogPostModel, bool wasLikedByMe) {
|
||||||
|
if (feedBlogPostModel == null) 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(
|
||||||
@@ -31,7 +32,7 @@ class FeedCubit extends Cubit<FeedState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFeedBlogPostList({
|
Future<void> loadFeedBlogPostList({
|
||||||
String? query,
|
String? query,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
@@ -79,7 +80,7 @@ class FeedCubit extends Cubit<FeedState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
likePost(int id) async {
|
Future<void> likePost(int id) async {
|
||||||
try {
|
try {
|
||||||
await feedRepository.likeBlogPost(id: id);
|
await feedRepository.likeBlogPost(id: id);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
@@ -87,10 +88,10 @@ class FeedCubit extends Cubit<FeedState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unlikedPost(int id) async => await feedRepository.unlikedBlogPost(id: id);
|
Future<void> unlikedPost(int id) async => await feedRepository.unlikedBlogPost(id: id);
|
||||||
|
|
||||||
viewPost(int id) async => await feedRepository.viewPost(id);
|
Future<void> viewPost(int id) async => await feedRepository.viewPost(id);
|
||||||
viewRecipe(String slug) async => await feedRepository.viewRecipe(slug);
|
Future<void> viewRecipe(String slug) async => await feedRepository.viewRecipe(slug);
|
||||||
viewDiet(int id) async => await feedRepository.viewDiet(id);
|
Future<void> viewDiet(int id) async => await feedRepository.viewDiet(id);
|
||||||
viewPlan(String slug) async => await feedRepository.viewPlan(slug);
|
Future<void> viewPlan(String slug) async => await feedRepository.viewPlan(slug);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ class PlansCubit extends Cubit<PlansState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadPlans({
|
Future<void> loadPlans({
|
||||||
String? query,
|
String? query,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class RecipesCubit extends Cubit<RecipesState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadRecipesList({
|
Future<void> loadRecipesList({
|
||||||
String? query,
|
String? query,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
@@ -99,7 +99,7 @@ class RecipesCubit extends Cubit<RecipesState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTags(RecipeTag tag) {
|
void updateTags(RecipeTag tag) {
|
||||||
_selectedTags.contains(tag.id) ? _selectedTags.remove(tag.id) : _selectedTags.add(tag.id);
|
_selectedTags.contains(tag.id) ? _selectedTags.remove(tag.id) : _selectedTags.add(tag.id);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
|
|||||||
import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart';
|
import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.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:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import '../widgets/recipes.dart';
|
import '../widgets/recipes.dart';
|
||||||
|
|
||||||
@@ -84,7 +85,7 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
_onScrollConsultants() {
|
void _onScrollConsultants() {
|
||||||
if (consultantsScrollController.offset >= consultantsScrollController.position.maxScrollExtent && !consultantsScrollController.position.outOfRange) {}
|
if (consultantsScrollController.offset >= consultantsScrollController.position.maxScrollExtent && !consultantsScrollController.position.outOfRange) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
FlutterNativeSplash.remove();
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
floatingActionButton: _selectedChipIndex == 1
|
floatingActionButton: _selectedChipIndex == 1
|
||||||
? FloatingActionButton.small(
|
? FloatingActionButton.small(
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
onTap: () => GoRouter.of(context).pushNamed(
|
onTap: () => GoRouter.of(context).pushNamed(
|
||||||
'blogPostScreen',
|
'blogPostScreen',
|
||||||
extra: state.feedBlogPostList[widget.index],
|
extra: state.feedBlogPostList[widget.index].id,
|
||||||
),
|
),
|
||||||
child: Material(
|
child: Material(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
@@ -56,81 +56,86 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'profileScreen',
|
'profileScreen',
|
||||||
extra: Author(
|
extra: Author(
|
||||||
authorSlug: state.feedBlogPostList[widget.index].author.authorSlug,
|
authorSlug: state.feedBlogPostList[widget.index].author.authorSlug,
|
||||||
authorName: state.feedBlogPostList[widget.index].author.authorName,
|
authorName: state.feedBlogPostList[widget.index].author.authorName,
|
||||||
authorProfileIcon: state.feedBlogPostList[widget.index].author.authorProfileIcon,
|
authorProfileIcon: state.feedBlogPostList[widget.index].author.authorProfileIcon.contains('https')
|
||||||
authorIsMe: state.feedBlogPostList[widget.index].author.authorIsMe,
|
? state.feedBlogPostList[widget.index].author.authorProfileIcon
|
||||||
),
|
: baseUrl + state.feedBlogPostList[widget.index].author.authorProfileIcon,
|
||||||
);
|
authorIsMe: state.feedBlogPostList[widget.index].author.authorIsMe,
|
||||||
},
|
|
||||||
child: CircleAvatar(
|
|
||||||
radius: 20,
|
|
||||||
backgroundColor: Colors.black12,
|
|
||||||
foregroundImage: NetworkImage(
|
|
||||||
baseUrl + state.feedBlogPostList[widget.index].author.authorProfileIcon,
|
|
||||||
),
|
|
||||||
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
// Имя и дата внутри Expanded
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
state.feedBlogPostList[widget.index].author.authorName,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: const TextStyle(
|
|
||||||
height: 1,
|
|
||||||
color: Colors.black87,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: CircleAvatar(
|
||||||
|
radius: 20,
|
||||||
|
backgroundColor: Colors.black12,
|
||||||
|
foregroundImage: NetworkImage(
|
||||||
|
state.feedBlogPostList[widget.index].author.authorProfileIcon.contains('https')
|
||||||
|
? state.feedBlogPostList[widget.index].author.authorProfileIcon
|
||||||
|
: baseUrl + state.feedBlogPostList[widget.index].author.authorProfileIcon,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
|
||||||
Text(
|
|
||||||
'${calculateDifference(state.feedBlogPostList[widget.index].createdAt)} в ${state.feedBlogPostList[widget.index].createdAt.hour.toString().padLeft(2, '0')}:${state.feedBlogPostList[widget.index].createdAt.minute.toString().padLeft(2, '0')}',
|
|
||||||
style: const TextStyle(
|
|
||||||
height: 1,
|
|
||||||
color: Colors.black45,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Кнопка подписки (если нужно)
|
|
||||||
if (!(state.feedBlogPostList[widget.index].author.isEntitySubscribedByMe ?? false) &&
|
|
||||||
!state.feedBlogPostList[widget.index].author.authorIsMe)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 8),
|
|
||||||
child: SubscribeButtonWidget(
|
|
||||||
slug: state.feedBlogPostList[widget.index].author.authorSlug,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Кнопка меню
|
const SizedBox(width: 8),
|
||||||
CustomPopup(
|
// Имя и дата внутри Expanded
|
||||||
isAuthor: state.feedBlogPostList[widget.index].author.authorIsMe,
|
Expanded(
|
||||||
complainedContentType: ComplainedContent.posts,
|
child: Column(
|
||||||
path: 'blog_post/${(state.feedBlogPostList[widget.index].id)}',
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
id: state.feedBlogPostList[widget.index].id,
|
children: [
|
||||||
feedBlogPostModel: state.feedBlogPostList[widget.index],
|
Text(
|
||||||
),
|
state.feedBlogPostList[widget.index].author.authorName,
|
||||||
],
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(
|
||||||
|
height: 1,
|
||||||
|
color: Colors.black87,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'${calculateDifference(state.feedBlogPostList[widget.index].createdAt)} в ${state.feedBlogPostList[widget.index].createdAt.hour.toString().padLeft(2, '0')}:${state.feedBlogPostList[widget.index].createdAt.minute.toString().padLeft(2, '0')}',
|
||||||
|
style: const TextStyle(
|
||||||
|
height: 1,
|
||||||
|
color: Colors.black45,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Кнопка подписки (если нужно)
|
||||||
|
if (!(state.feedBlogPostList[widget.index].author.isEntitySubscribedByMe ?? false) &&
|
||||||
|
!state.feedBlogPostList[widget.index].author.authorIsMe)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8),
|
||||||
|
child: SubscribeButtonWidget(
|
||||||
|
slug: state.feedBlogPostList[widget.index].author.authorSlug,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Кнопка меню
|
||||||
|
CustomPopup(
|
||||||
|
isAuthor: state.feedBlogPostList[widget.index].author.authorIsMe,
|
||||||
|
complainedContentType: ComplainedContent.posts,
|
||||||
|
path: 'blog_post/${(state.feedBlogPostList[widget.index].id)}',
|
||||||
|
id: state.feedBlogPostList[widget.index].id,
|
||||||
|
feedBlogPostModel: state.feedBlogPostList[widget.index],
|
||||||
|
userSlug: state.feedBlogPostList[widget.index].author.authorSlug,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
if (state.feedBlogPostList[widget.index].title.isNotEmpty) ...[
|
||||||
if (state.feedBlogPostList[widget.index].title.isNotEmpty) ...[
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
@@ -151,9 +156,9 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
child: Html(
|
child: Html(
|
||||||
data: state.feedBlogPostList[widget.index].description.length > 200
|
data: (state.feedBlogPostList[widget.index].description.length > 200
|
||||||
? state.feedBlogPostList[widget.index].description.substring(0, 200)
|
? state.feedBlogPostList[widget.index].description.substring(0, 200)
|
||||||
: state.feedBlogPostList[widget.index].description,
|
: state.feedBlogPostList[widget.index].description).replaceAll('\n', '<br>'),
|
||||||
style: {
|
style: {
|
||||||
"*": Style(
|
"*": Style(
|
||||||
fontSize: FontSize(16),
|
fontSize: FontSize(16),
|
||||||
@@ -234,7 +239,7 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
|||||||
text: state.feedBlogPostList[widget.index].totalComments.toString(),
|
text: state.feedBlogPostList[widget.index].totalComments.toString(),
|
||||||
onTap: () => GoRouter.of(context).pushNamed(
|
onTap: () => GoRouter.of(context).pushNamed(
|
||||||
'blogPostScreen',
|
'blogPostScreen',
|
||||||
extra: state.feedBlogPostList[widget.index],
|
extra: state.feedBlogPostList[widget.index].id,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class _CommentsWidgetState extends State<CommentsWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_comment(S s, Comment comment, [double radius = 18, bool isChild = false]) {
|
Container _comment(S s, Comment comment, [double radius = 18, bool isChild = false]) {
|
||||||
return Container(
|
return Container(
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -72,13 +72,15 @@ class _CommentsWidgetState extends State<CommentsWidget> {
|
|||||||
extra: Author(
|
extra: Author(
|
||||||
authorSlug: comment.authorSlug,
|
authorSlug: comment.authorSlug,
|
||||||
authorName: comment.authorName,
|
authorName: comment.authorName,
|
||||||
authorProfileIcon: comment.authorProfileIcon,
|
authorProfileIcon: comment.authorProfileIcon.contains('https') ? comment.authorProfileIcon : baseUrl + comment.authorProfileIcon,
|
||||||
authorIsMe: (sl<ProfileAvatarCubit>().state as ProfileAvatarStateLoaded).profile.slug == comment.authorSlug ? true : false,
|
authorIsMe: (sl<ProfileAvatarCubit>().state as ProfileAvatarStateLoaded).profile.slug == comment.authorSlug ? true : false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
foregroundImage: NetworkImage(baseUrl + comment.authorProfileIcon),
|
foregroundImage: NetworkImage(
|
||||||
|
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,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ class ConsultantWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_badges(List<BadgeModel> badges, BuildContext context) {
|
SingleChildScrollView _badges(List<BadgeModel> badges, BuildContext context) {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user