forked from eatvio/eatvio_app
Merge pull request 'Unread messages and updated my_diets' (#231) from misha/eatvio_app:main into main
Reviewed-on: eatvio/eatvio_app#231
This commit is contained in:
5
assets/icons/cloud.svg
Normal file
5
assets/icons/cloud.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="80" height="56" viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M29.0059 0.177004C24.0464 1.09374 19.6814 3.90505 16.8028 8.04562C15.2852 10.2305 14.1587 13.1793 13.8146 15.9448L13.6112 17.4727L12.4378 17.8088C6.24236 19.5964 1.59579 24.6232 0.18774 31.0403C-0.0625801 32.2321 -0.0625801 36.2657 0.18774 37.4574C1.83047 45.0052 7.83816 50.4292 15.4886 51.2848C16.5837 51.4071 24.4532 51.4376 42.6953 51.4071L68.3531 51.3612L69.6047 51.0098C74.3295 49.7111 77.9591 46.3497 79.3672 41.9647C81.401 35.6087 78.3972 28.7485 72.2956 25.8302C71.5447 25.4788 70.4339 25.051 69.8394 24.9135L68.7442 24.6385V23.7829C68.7442 22.6217 68.2436 20.4062 67.696 19.0464C65.8342 14.5239 62.0325 11.3459 57.0574 10.1694C55.3521 9.75686 52.3482 9.7263 50.6742 10.0777C50.0797 10.2152 49.5478 10.2916 49.4852 10.2458C49.4226 10.2152 49.1097 9.7263 48.7968 9.16098C46.4344 4.95929 42.132 1.73545 37.1882 0.452024C35.874 0.115889 35.2639 0.0547731 32.9171 0.00893645C31.0084 -0.0216213 29.8194 0.0242153 29.0059 0.177004ZM35.5768 5.3107C38.2677 5.90658 41.3341 7.78588 43.0864 9.92492C43.9312 10.9639 45.089 13.0265 45.4957 14.2488C45.9025 15.4559 46.8099 16.1434 48.0302 16.1434C48.3118 16.1434 49.141 15.9142 49.845 15.6239C52.3013 14.6461 54.3352 14.5544 56.7601 15.3489C59.2008 16.1281 61.2815 17.8547 62.4862 20.1312C63.4249 21.9036 63.6283 22.9731 63.5188 25.5094C63.4249 27.297 63.4562 27.679 63.6752 28.1373C64.0351 28.886 65.0364 29.436 66.022 29.436C68.2436 29.4513 70.6999 30.4444 72.2644 31.9723C75.6437 35.2726 75.675 40.4827 72.3269 43.7982C71.3256 44.7913 70.4339 45.3413 68.9163 45.9219L67.8837 46.3192L42.3824 46.365C23.843 46.3956 16.5681 46.365 15.7389 46.2428C10.7638 45.4789 6.77429 41.9953 5.42882 37.2283C5.0377 35.8226 5.05334 32.6446 5.44447 31.2695C6.14849 28.9166 7.21235 27.2053 9.02718 25.5247C11.0141 23.6759 13.2826 22.6064 15.9736 22.2397C18.3047 21.9341 18.8366 21.2618 18.8366 18.6339C18.8366 16.7851 19.0087 15.6545 19.4937 14.1572C20.5419 10.9486 23.0764 8.03034 26.1898 6.45662C29.2405 4.91345 32.1662 4.56204 35.5768 5.3107Z" fill="#323F4B"/>
|
||||
<path d="M48.6352 1.25136H61.9856V56H48.6352V1.25136Z" fill="#F0F2F5"/>
|
||||
<path d="M54.285 39.6226C53.5798 39.6226 52.9961 39.0869 52.9525 38.3996L50.7255 3.28724C50.6779 2.53706 51.2882 1.90313 52.058 1.90313H58.5655C59.3342 1.90313 59.9442 2.53546 59.8981 3.28473L57.7407 38.3971C57.6984 39.0855 57.1143 39.6226 56.408 39.6226H54.285ZM52.197 54.0447C51.4597 54.0447 50.862 53.4611 50.862 52.7412V46.547C50.862 45.8271 51.4597 45.2435 52.197 45.2435H58.4237C59.161 45.2435 59.7587 45.8271 59.7587 46.547V52.7412C59.7587 53.4611 59.161 54.0447 58.4237 54.0447H52.197Z" fill="#2F80ED"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
6
assets/icons/no_internet.svg
Normal file
6
assets/icons/no_internet.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="120" height="107" viewBox="0 0 120 107" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M60.0188 0C93.1495 0 120 14.4534 120 47.3937C120 80.2967 93.1495 107 60.0188 107C26.8505 107 0 80.2967 0 47.3937C0 14.4534 26.8505 0 60.0188 0Z" fill="#43A6F2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M60.0188 41.756C52.0463 41.756 44.2243 43.3619 36.9288 46.4618C29.6333 49.5989 23.0523 54.1926 17.5995 59.9441C15.0799 62.5584 15.2303 66.7413 17.8627 69.2063C20.5327 71.7085 24.7069 71.5591 27.2265 68.9448C31.4384 64.5005 36.5152 60.9525 42.156 58.5623C47.7969 56.1347 53.8514 54.9022 60.0188 54.9022C66.1485 54.9022 72.203 56.1347 77.8439 58.5623C83.4848 60.9525 88.5615 64.5005 92.7734 68.9448C95.293 71.5591 99.4672 71.7085 102.137 69.2063C104.77 66.7413 104.92 62.5584 102.4 59.9441C96.9476 54.1926 90.3666 49.5989 83.0711 46.4618C75.7756 43.3619 67.9536 41.756 60.0188 41.756Z" fill="white"/>
|
||||
<ellipse cx="59" cy="57" rx="48" ry="36" fill="#43A6F2"/>
|
||||
<path d="M79.975 81.05L55.325 56.2583C53.8611 56.5889 52.4562 57.0375 51.1104 57.6042C49.7646 58.1708 48.5014 58.8556 47.3208 59.6583C46.3292 60.3194 45.2312 60.6618 44.0271 60.6854C42.8229 60.709 41.8194 60.2958 41.0167 59.4458C40.1667 58.5958 39.7771 57.5688 39.8479 56.3646C39.9187 55.1604 40.4264 54.2042 41.3708 53.4958C42.4569 52.6931 43.6021 51.9611 44.8062 51.3C46.0104 50.6389 47.25 50.025 48.525 49.4583L42.15 43.0833C40.9222 43.7444 39.7298 44.441 38.5729 45.1729C37.416 45.9049 36.2944 46.6958 35.2083 47.5458C34.2639 48.3014 33.1896 48.6792 31.9854 48.6792C30.7812 48.6792 29.7778 48.2542 28.975 47.4042C28.125 46.5542 27.7236 45.5389 27.7708 44.3583C27.818 43.1778 28.3139 42.2097 29.2583 41.4542C30.2972 40.6042 31.3597 39.7896 32.4458 39.0104C33.5319 38.2312 34.6889 37.5111 35.9167 36.85L31.95 32.8833C31.4305 32.3639 31.1708 31.7028 31.1708 30.9C31.1708 30.0972 31.4305 29.4361 31.95 28.9167C32.4694 28.3972 33.1305 28.1375 33.9333 28.1375C34.7361 28.1375 35.3972 28.3972 35.9167 28.9167L84.0125 77.0125C84.5792 77.5792 84.8625 78.2521 84.8625 79.0313C84.8625 79.8104 84.5792 80.4833 84.0125 81.05C83.4458 81.5694 82.7729 81.841 81.9937 81.8646C81.2146 81.8882 80.5416 81.6167 79.975 81.05ZM60 78.5C58.0167 78.5 56.3403 77.8035 54.9708 76.4104C53.6014 75.0174 52.9167 73.3528 52.9167 71.4167C52.9167 69.4333 53.6014 67.7569 54.9708 66.3875C56.3403 65.0181 58.0167 64.3333 60 64.3333C61.9833 64.3333 63.6597 65.0181 65.0291 66.3875C66.3986 67.7569 67.0833 69.4333 67.0833 71.4167C67.0833 73.3528 66.3986 75.0174 65.0291 76.4104C63.6597 77.8035 61.9833 78.5 60 78.5ZM79.3375 59.0208C78.5819 59.7764 77.6965 60.1424 76.6812 60.1187C75.666 60.0951 74.7805 59.7056 74.025 58.95L73.3167 58.2417L72.6083 57.5333L65.8083 50.7333C65.1944 50.1194 65.0764 49.4819 65.4542 48.8208C65.8319 48.1597 66.493 47.9472 67.4375 48.1833C69.5625 48.7028 71.5812 49.4347 73.4937 50.3792C75.4062 51.3236 77.1889 52.4333 78.8416 53.7083C79.6917 54.3694 80.1757 55.2312 80.2937 56.2937C80.4118 57.3563 80.093 58.2653 79.3375 59.0208ZM91.025 47.4042C90.2222 48.2542 89.2305 48.691 88.05 48.7146C86.8694 48.7382 85.8069 48.3722 84.8625 47.6167C81.4625 44.8306 77.6493 42.6701 73.4229 41.1354C69.1965 39.6007 64.7222 38.8333 60 38.8333C59.0083 38.8333 58.0521 38.8687 57.1312 38.9396C56.2104 39.0104 55.2778 39.1167 54.3333 39.2583C53.1528 39.4472 52.0903 39.1993 51.1458 38.5146C50.2014 37.8299 49.6347 36.8972 49.4458 35.7167C49.2569 34.5361 49.5167 33.4736 50.225 32.5292C50.9333 31.5847 51.8778 31.0181 53.0583 30.8292C54.1917 30.6403 55.3368 30.5104 56.4937 30.4396C57.6507 30.3687 58.8194 30.3333 60 30.3333C65.9028 30.3333 71.4632 31.3132 76.6812 33.2729C81.8993 35.2326 86.5861 37.9361 90.7416 41.3833C91.6861 42.1861 92.1819 43.1778 92.2291 44.3583C92.2764 45.5389 91.875 46.5542 91.025 47.4042Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
3
assets/icons/play_icon.svg
Normal file
3
assets/icons/play_icon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.92 11L10.82 8.5C11.0067 8.38 11.1 8.21333 11.1 8C11.1 7.78667 11.0067 7.62 10.82 7.5L6.92 5C6.72 4.86667 6.51667 4.85667 6.31 4.97C6.10333 5.08333 6 5.26 6 5.5V10.5C6 10.74 6.10333 10.9167 6.31 11.03C6.51667 11.1433 6.72 11.1333 6.92 11ZM8 16C6.89333 16 5.85333 15.79 4.88 15.37C3.90667 14.95 3.06 14.38 2.34 13.66C1.62 12.94 1.05 12.0933 0.63 11.12C0.21 10.1467 0 9.10667 0 8C0 6.89333 0.21 5.85333 0.63 4.88C1.05 3.90667 1.62 3.06 2.34 2.34C3.06 1.62 3.90667 1.05 4.88 0.63C5.85333 0.21 6.89333 0 8 0C9.10667 0 10.1467 0.21 11.12 0.63C12.0933 1.05 12.94 1.62 13.66 2.34C14.38 3.06 14.95 3.90667 15.37 4.88C15.79 5.85333 16 6.89333 16 8C16 9.10667 15.79 10.1467 15.37 11.12C14.95 12.0933 14.38 12.94 13.66 13.66C12.94 14.38 12.0933 14.95 11.12 15.37C10.1467 15.79 9.10667 16 8 16ZM8 14.4C9.78667 14.4 11.3 13.78 12.54 12.54C13.78 11.3 14.4 9.78667 14.4 8C14.4 6.21333 13.78 4.7 12.54 3.46C11.3 2.22 9.78667 1.6 8 1.6C6.21333 1.6 4.7 2.22 3.46 3.46C2.22 4.7 1.6 6.21333 1.6 8C1.6 9.78667 2.22 11.3 3.46 12.54C4.7 13.78 6.21333 14.4 8 14.4Z" fill="#2F80ED"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
assets/icons/right_arrow.svg
Normal file
3
assets/icons/right_arrow.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="8" height="15" viewBox="0 0 8 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.71986 8.22017C8.09338 7.82242 8.09338 7.17757 7.71986 6.77982L1.63283 0.298304C1.25929 -0.0994345 0.653682 -0.0994345 0.280148 0.298304C-0.0933826 0.696046 -0.0933826 1.3409 0.280148 1.73865L5.69085 7.49999L0.280148 13.2614C-0.0933826 13.6591 -0.0933826 14.3039 0.280148 14.7017C0.653682 15.0994 1.25929 15.0994 1.63283 14.7017L7.71986 8.22017Z" fill="#323F4B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 476 B |
@@ -361,6 +361,8 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 16;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -382,6 +384,7 @@
|
||||
MARKETING_VERSION = 2.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
@@ -507,6 +510,8 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 16;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -528,6 +533,7 @@
|
||||
MARKETING_VERSION = 2.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -541,6 +547,8 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 16;
|
||||
DEVELOPMENT_TEAM = VL68C36HYS;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -562,6 +570,7 @@
|
||||
MARKETING_VERSION = 2.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:eatvio_app/core/initialization_cubit/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/token_storage.dart';
|
||||
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart';
|
||||
@@ -8,16 +8,17 @@ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/prof
|
||||
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/widgets/bottom_navigation_bar_widget.dart';
|
||||
import 'package:eatvio_app/features/calendar/presentation/pages/calendar_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/chats_screen.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/complaining_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/stepper_become_consultant_screen.dart';
|
||||
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_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_plan/presentation/create_plan_screen.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/presentation/consultant_rating_screen.dart';
|
||||
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/pages/about_diet_screen.dart';
|
||||
@@ -34,8 +35,8 @@ 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/profile/presentation/pages/profile_screen.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/pages/about_recipes_screen.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/pages/recipe_create_screen.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/about_recipe_screen.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/pages/webview_registration_screen.dart';
|
||||
import 'package:eatvio_app/features/settings/entities/settings_entity.dart';
|
||||
@@ -64,6 +65,8 @@ class AppRouter {
|
||||
path: '/auth-screen',
|
||||
name: 'authScreen',
|
||||
redirect: (context, state) async {
|
||||
// TokenStorage.removeTokenInStorage();
|
||||
// await TokenStorage.saveTokenInStorage('Bearer 3|laravel_sanctum_zoubsO9NPXAqgLZEdHV9R3XbuDUHJJMJyXcd77mhbb649f04');
|
||||
if (await TokenStorage.haveTokenInStorage()) {
|
||||
return '/my-profile-screen';
|
||||
}
|
||||
@@ -125,7 +128,7 @@ class AppRouter {
|
||||
GoRoute(
|
||||
path: '/chat-select-screen',
|
||||
name: 'chatSelectScreen',
|
||||
pageBuilder: (context, state) => NoTransitionPage(child: ChatSelectScreen()),
|
||||
pageBuilder: (context, state) => NoTransitionPage(child: ChatsScreen()),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/notifications-screen',
|
||||
@@ -159,9 +162,14 @@ class AppRouter {
|
||||
GoRoute(
|
||||
path: '/about-recipes-screen',
|
||||
name: 'aboutRecipesScreen',
|
||||
builder: (context, state) => AboutRecipesScreen(
|
||||
recipes: state.extra as Recipes,
|
||||
),
|
||||
builder: (context, state) {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
return AboutRecipeScreen(
|
||||
recipe: data['recipe'] as Recipe,
|
||||
index: data['index'] as int?,
|
||||
navigateToComments: data['navigateToComments'] as bool,
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: RecipeCreateScreen.path,
|
||||
@@ -212,7 +220,15 @@ class AppRouter {
|
||||
GoRoute(
|
||||
path: '/chat-screen',
|
||||
name: 'chatScreen',
|
||||
builder: (context, state) => ChatScreen(chatId: state.extra as String),
|
||||
builder: (context, state) {
|
||||
final data = state.extra as Map<String, dynamic>;
|
||||
final chatsCubit = data['chatsCubit'] as ChatsCubit;
|
||||
final chatId = data['chatId'] as String;
|
||||
return ChatScreen(
|
||||
chatId: chatId,
|
||||
chatsCubit: chatsCubit,
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/settings-screen',
|
||||
@@ -298,11 +314,6 @@ class AppRouter {
|
||||
index: int.parse(state.pathParameters['index']!),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/complaining-screen',
|
||||
name: 'complainingScreen',
|
||||
builder: (context, state) => ComplainingScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: CreateDietScreen.path,
|
||||
name: CreateDietScreen.name,
|
||||
@@ -348,6 +359,11 @@ class AppRouter {
|
||||
name: DevToolsScreen.name,
|
||||
builder: (context, state) => DevToolsScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: ConsultantRatingScreen.path,
|
||||
name: ConsultantRatingScreen.name,
|
||||
builder: (context, state) => ConsultantRatingScreen(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
String baseUrl = 'https://eatvio.ru';
|
||||
// String baseUrl = 'https://dev2.eatvio.com';
|
||||
// String baseUrl = 'https://eatvio.ru';
|
||||
String baseUrl = 'https://dev2.eatvio.com';
|
||||
|
||||
const List<int> durationValues = [7, 14, 21];
|
||||
|
||||
|
||||
6
lib/core/enums/base_state.dart
Normal file
6
lib/core/enums/base_state.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
enum BaseState {
|
||||
loading,
|
||||
error,
|
||||
initial,
|
||||
success
|
||||
}
|
||||
7
lib/core/enums/complained_content.dart
Normal file
7
lib/core/enums/complained_content.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
enum ComplainedContent {
|
||||
diet,
|
||||
plan,
|
||||
recipe,
|
||||
profile,
|
||||
posts
|
||||
}
|
||||
5
lib/core/enums/product_type.dart
Normal file
5
lib/core/enums/product_type.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
enum ProductType {
|
||||
diet,
|
||||
plan,
|
||||
recipe
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/initialization_cubit/ws_manager.dart';
|
||||
import 'package:eatvio_app/core/initialization_cubit/ws_service.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.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/repositories/auth_repository.dart';
|
||||
import 'package:eatvio_app/features/auth/services/auth_service.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/services/complaining_service.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
|
||||
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
|
||||
@@ -21,7 +24,7 @@ import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.da
|
||||
import 'package:eatvio_app/features/calendar/repositories/calendar_repository.dart';
|
||||
import 'package:eatvio_app/features/calendar/services/calendar_service.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_select_cubit/chat_select_cubit.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/services/chat_service.dart';
|
||||
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
|
||||
@@ -57,9 +60,12 @@ import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/prof
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:eatvio_app/features/profile/services/profile_service.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipes/reporitories/recipe_repository.dart';
|
||||
import 'package:eatvio_app/features/recipes/services/recipe_service.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
|
||||
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart';
|
||||
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
|
||||
import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart';
|
||||
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
|
||||
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
|
||||
import 'package:eatvio_app/features/registration/services/registration_service.dart';
|
||||
@@ -84,13 +90,13 @@ Future<void> init() async {
|
||||
// Cubit factories
|
||||
sl.registerFactory<RegistrationCubit>(() => RegistrationCubit(registrationRepository: sl()));
|
||||
sl.registerFactory<ChatRoomCubit>(() => ChatRoomCubit(chatRepository: sl(), wsManager: sl()));
|
||||
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl()));
|
||||
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl()));
|
||||
sl.registerFactory<CalendarCubit>(() => CalendarCubit(calendarRepository: sl()));
|
||||
sl.registerFactory<ProfileCubit>(() => ProfileCubit(profileRepository: sl(), chatRepository: sl()));
|
||||
sl.registerFactory<profilePlans.PlansCubit>(() => profilePlans.PlansCubit(plansRepository: sl()));
|
||||
sl.registerFactory<DietsCubit>(() => DietsCubit(feedRepository: sl(), dietsRepository: sl()));
|
||||
sl.registerFactory<CommentsCubit>(() => CommentsCubit(feedRepository: sl()));
|
||||
sl.registerFactory<ChatSelectCubit>(() => ChatSelectCubit(chatRepository: sl()));
|
||||
sl.registerFactory<ChatsCubit>(() => ChatsCubit(chatRepository: sl(), wsManager: sl(), profileRepository: sl()));
|
||||
sl.registerFactory<PlansCubit>(() => PlansCubit(feedRepository: sl(), planRepository: sl()));
|
||||
sl.registerFactory<SettingsCubit>(() => SettingsCubit(profileRepository: sl(), settingsRepository: sl()));
|
||||
sl.registerFactory<RegisterConsultantCubit>(() => RegisterConsultantCubit(consultantRepository: sl()));
|
||||
@@ -103,11 +109,13 @@ Future<void> init() async {
|
||||
sl.registerFactory<FeedProfileCubit>(() => FeedProfileCubit(profileRepository: sl()));
|
||||
sl.registerFactory<AboutPlanCubit>(() => AboutPlanCubit(planRepository: sl()));
|
||||
sl.registerFactory<CreateRecipeCubit>(() => CreateRecipeCubit(recipeRepository: sl()));
|
||||
sl.registerFactory<MyDietsCubit>(() => MyDietsCubit(myDietsRepository: sl()));
|
||||
sl.registerFactory<MyDietsCubit>(() => MyDietsCubit(myDietsRepository: sl(), createDietRepository: sl()));
|
||||
sl.registerFactory<MyPlansCubit>(() => MyPlansCubit(myPlansRepository: sl()));
|
||||
sl.registerFactory<UserDetailsCubit>(() => UserDetailsCubit(profileRepository: sl()));
|
||||
sl.registerFactory<ConsultantsCubit>(() => ConsultantsCubit(feedRepository: sl()));
|
||||
sl.registerFactory<AboutRecipeCubit>(() => AboutRecipeCubit(recipeRepository: sl(), profileRepository: sl()));
|
||||
sl.registerFactory<AuthCubit>(() => AuthCubit(authRepository: sl()));
|
||||
sl.registerFactory<ComplainingCubit>(() => ComplainingCubit(repository: sl()));
|
||||
|
||||
// Repository
|
||||
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
|
||||
@@ -127,6 +135,8 @@ Future<void> init() async {
|
||||
sl.registerLazySingleton<PlanRepository>(() => PlanRepository(planService: sl()));
|
||||
sl.registerLazySingleton<MyDietsRepository>(() => MyDietsRepository(myDietsService: sl()));
|
||||
sl.registerLazySingleton<MyPlansRepository>(() => MyPlansRepository(myPlansService: sl()));
|
||||
sl.registerLazySingleton<AboutRecipeRepository>(() => AboutRecipeRepository(recipeService: sl()));
|
||||
sl.registerLazySingleton<ComplainingRepository>(() => ComplainingRepository(service: sl()));
|
||||
sl.registerLazySingleton<AuthRepository>(() => AuthRepository(authService: sl()));
|
||||
|
||||
// Services
|
||||
@@ -135,7 +145,7 @@ Future<void> init() async {
|
||||
sl.registerLazySingleton<RegistrationService>(() => RegistrationService(sl()));
|
||||
sl.registerLazySingleton<CalendarService>(() => CalendarService(sl()));
|
||||
sl.registerLazySingleton<ProfileService>(() => ProfileService(sl()));
|
||||
sl.registerLazySingleton<RecipeService>(() => RecipeService(sl()));
|
||||
sl.registerLazySingleton<CreateRecipeService>(() => CreateRecipeService(sl()));
|
||||
sl.registerLazySingleton<SettingsService>(() => SettingsService(sl()));
|
||||
sl.registerLazySingleton<ConsultantService>(() => ConsultantService(sl()));
|
||||
sl.registerLazySingleton<DiaryService>(() => DiaryService(sl()));
|
||||
@@ -148,6 +158,8 @@ Future<void> init() async {
|
||||
sl.registerLazySingleton<MyDietsService>(() => MyDietsService(sl()));
|
||||
sl.registerLazySingleton<MyPlansService>(() => MyPlansService(sl()));
|
||||
sl.registerLazySingleton<WsService>(() => WsService(sl()));
|
||||
sl.registerLazySingleton<AboutRecipeService>(() => AboutRecipeService(sl()));
|
||||
sl.registerLazySingleton<ComplainingService>(() => ComplainingService(sl()));
|
||||
sl.registerLazySingleton<AuthService>(() => AuthService(sl()));
|
||||
|
||||
// External
|
||||
|
||||
@@ -184,6 +184,12 @@ abstract class TextStyles {
|
||||
height: 1.255,
|
||||
);
|
||||
|
||||
static const headingFS40FW600 = TextStyle(
|
||||
fontSize: 40,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.255,
|
||||
);
|
||||
|
||||
static const textLSemibold = TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -230,10 +236,10 @@ abstract class TextStyles {
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.255,
|
||||
);
|
||||
);
|
||||
|
||||
static const textFS10FW500 = TextStyle(
|
||||
fontSize: 12,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.255,
|
||||
);
|
||||
|
||||
@@ -190,6 +190,10 @@ class PrimaryColors {
|
||||
Color get transparentBlack => Color(0xFF1313264).withValues(alpha: 0.3);
|
||||
|
||||
Color get gray60004 => Color(0xFFD4DAE2).withValues(alpha: 0.2);
|
||||
|
||||
Color get blueGray => Color(0xFF809CB9);
|
||||
|
||||
Color get dialogShadow => Color.fromARGB(125, 0, 0, 0);
|
||||
}
|
||||
|
||||
PrimaryColors get appTheme => ThemeHelper().themeColor();
|
||||
|
||||
@@ -41,3 +41,33 @@ String getCommentForm(int number, S s) {
|
||||
return s.comments_plural;
|
||||
}
|
||||
}
|
||||
|
||||
String getMinuteForm(int number) {
|
||||
if (number % 10 == 1 && number % 100 != 11) {
|
||||
return S.current.minute;
|
||||
} else if ([2, 3, 4].contains(number % 10) && ![12, 13, 14].contains(number % 100)) {
|
||||
return S.current.minutes;
|
||||
} else {
|
||||
return S.current.another_form_of_minute;
|
||||
}
|
||||
}
|
||||
|
||||
String getHourForm(int number) {
|
||||
if (number % 10 == 1 && number % 100 != 11) {
|
||||
return S.current.hour;
|
||||
} else if ([2, 3, 4].contains(number % 10) && ![12, 13, 14].contains(number % 100)) {
|
||||
return S.current.another_form_of_hour;
|
||||
} else {
|
||||
return S.current.hours;
|
||||
}
|
||||
}
|
||||
|
||||
String getStepForm(int number) {
|
||||
if (number % 10 == 1 && number % 100 != 11) {
|
||||
return S.current.stage;
|
||||
} else if ([2, 3, 4].contains(number % 10) && ![12, 13, 14].contains(number % 100)) {
|
||||
return S.current.another_form_of_stage;
|
||||
} else {
|
||||
return S.current.stages;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'dart:developer';
|
||||
|
||||
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/initialization_cubit/ws_service.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_service.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
|
||||
class WsManager {
|
||||
68
lib/core/widgets/data_error_widget.dart
Normal file
68
lib/core/widgets/data_error_widget.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.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/loader.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class DataErrorWidget extends StatefulWidget {
|
||||
const DataErrorWidget({super.key, required this.onReload});
|
||||
|
||||
final Future<void> Function() onReload;
|
||||
|
||||
@override
|
||||
State<DataErrorWidget> createState() => _DataErrorWidgetState();
|
||||
}
|
||||
|
||||
class _DataErrorWidgetState extends State<DataErrorWidget> {
|
||||
bool isLoading = false;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SvgPicture.asset('assets/icons/cloud.svg'),
|
||||
SizedBox(height: 30),
|
||||
Text(
|
||||
s.something_went_wrong,
|
||||
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Text(
|
||||
s.can_not_load_data_please_try_again,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
isLoading
|
||||
? Loader()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: CustomOutlinedButton(
|
||||
text: s.try_again,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
await widget.onReload();
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/enums/product_type.dart';
|
||||
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/widgets/type_badges.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -11,21 +13,35 @@ import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class InfoRow extends StatelessWidget {
|
||||
const InfoRow({
|
||||
required this.type,
|
||||
super.key,
|
||||
this.diet,
|
||||
this.itemsBackgroundColor,
|
||||
this.disableDescriptionOfTypes = false,
|
||||
this.plan,
|
||||
this.recipe,
|
||||
});
|
||||
final ProductType type;
|
||||
final Color? itemsBackgroundColor;
|
||||
final Diet? diet;
|
||||
final PlanModel? plan;
|
||||
final Recipe? recipe;
|
||||
|
||||
final bool disableDescriptionOfTypes;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
|
||||
String? timeTitle;
|
||||
if (recipe != null) {
|
||||
final hours = recipe!.cookingTime ~/ 60;
|
||||
final minutes = recipe!.cookingTime % 60;
|
||||
if (hours > 0) {
|
||||
timeTitle = '$hours ${getHourForm(hours)} $minutes ${getMinuteForm(minutes)}';
|
||||
} else {
|
||||
timeTitle = '$minutes ${getMinuteForm(minutes)}';
|
||||
}
|
||||
}
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
scrollDirection: Axis.horizontal,
|
||||
@@ -41,7 +57,11 @@ class InfoRow extends StatelessWidget {
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
diet == null ? '${plan?.duration} ${getDayForm(plan?.duration ?? 7, s)}' : '${diet?.duration} ${getDayForm(diet?.duration ?? 7, s)}',
|
||||
switch (type) {
|
||||
ProductType.diet => '${diet?.duration} ${getDayForm(diet?.duration ?? 7, s)}',
|
||||
ProductType.plan => '${plan?.duration} ${getDayForm(plan?.duration ?? 7, s)}',
|
||||
ProductType.recipe => '${timeTitle ?? ''}',
|
||||
},
|
||||
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
],
|
||||
@@ -51,18 +71,42 @@ class InfoRow extends StatelessWidget {
|
||||
backgroundColor: itemsBackgroundColor,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/icons/people.svg',
|
||||
switch (type) {
|
||||
ProductType.diet => 'assets/icons/people.svg',
|
||||
ProductType.plan => 'assets/icons/people.svg',
|
||||
ProductType.recipe => 'assets/icons/calories.svg',
|
||||
},
|
||||
width: 13.3,
|
||||
height: 13.3,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
'80 человек прошли',
|
||||
switch (type) {
|
||||
ProductType.diet => '80 человек прошли',
|
||||
ProductType.plan => '80 человек прошли',
|
||||
ProductType.recipe => (recipe?.calories.toStringAsFixed(0) ?? '0') + ' ккал',
|
||||
},
|
||||
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
if (type == ProductType.recipe)
|
||||
InfoBouble(
|
||||
backgroundColor: itemsBackgroundColor,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/icons/steps.svg',
|
||||
width: 13.3,
|
||||
height: 13.3,
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
'${recipe?.steps?.length ?? 0} ${getStepForm(recipe?.steps?.length ?? 0)}',
|
||||
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (plan?.type.hasAtLeastOneType() == true || diet?.type.hasAtLeastOneType() == true)
|
||||
InfoBouble(
|
||||
backgroundColor: itemsBackgroundColor,
|
||||
|
||||
59
lib/core/widgets/show_error_dialog.dart
Normal file
59
lib/core/widgets/show_error_dialog.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
void showErrorDialog(BuildContext context, {String? title, String? description}) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierColor: Colors.transparent,
|
||||
builder: (BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.whiteA700,
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
boxShadow: [BoxShadow(color: appTheme.dialogShadow, blurRadius: 32)],
|
||||
),
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
title ?? s.some_error_occurred,
|
||||
style: TextStyles.textLSemibold.copyWith(color: appTheme.black, decoration: TextDecoration.none),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 72),
|
||||
child: Text(
|
||||
description ?? s.some_error_occurred_description,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black, decoration: TextDecoration.none),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton(
|
||||
onPressed: () => AppRouter.navigatorKey.currentContext?.pop(),
|
||||
child: Text(s.ok, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'package:eatvio_app/core/app_router.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/widgets/bottom_sheet_handler_bar.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@@ -10,6 +12,7 @@ class SuccessPublishedDietBottomSheet extends StatelessWidget {
|
||||
final String title;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
@@ -34,9 +37,9 @@ class SuccessPublishedDietBottomSheet extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: CustomOutlinedButton(
|
||||
onPressed: () {
|
||||
GoRouter.of(context).pop();
|
||||
AppRouter.navigatorKey.currentContext?.pop();
|
||||
},
|
||||
text: 'Хорошо',
|
||||
text: s.ok,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
57
lib/core/widgets/success_published_recipe_bottom_sheet.dart
Normal file
57
lib/core/widgets/success_published_recipe_bottom_sheet.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
import 'package:eatvio_app/core/app_router.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/widgets/bottom_sheet_handler_bar.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class SuccessPublishedRecipeBottomSheet extends StatelessWidget {
|
||||
const SuccessPublishedRecipeBottomSheet({super.key, required this.title});
|
||||
final String title;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
BottomSheetHandlerBar(),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
s.recipe_publishing,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
Text(
|
||||
'"$title" ${s.successfully_published}',
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: CustomOutlinedButton(
|
||||
onPressed: () {
|
||||
AppRouter.navigatorKey.currentContext?.pop();
|
||||
},
|
||||
text: s.ok,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
import 'dart:io';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
@@ -11,6 +14,7 @@ import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
@@ -191,9 +195,13 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
title: Text(widget.blogPost.title),
|
||||
actions: [
|
||||
PopupMenuButton(
|
||||
onSelected: (value) {
|
||||
onSelected: (value) async {
|
||||
if (value == 'delete') {
|
||||
sl<BlogPostCreateCubit>().deleteBlogPost(widget.blogPost.id);
|
||||
final result = await sl<BlogPostCreateCubit>().deleteBlogPost(widget.blogPost.id);
|
||||
if (!result) {
|
||||
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
|
||||
return;
|
||||
}
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
@@ -219,6 +227,16 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
PopupMenuItem(
|
||||
value: 'report',
|
||||
child: Text('Пожаловаться'),
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => ComplainingBottomSheet(
|
||||
complainedContentType: ComplainedContent.posts,
|
||||
id: widget.blogPost.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -10,23 +12,29 @@ class BlogPostCreateCubit extends Cubit<BlogPostCreateState> {
|
||||
required this.blogPostRepository,
|
||||
}) : super(BlogPostCreateState());
|
||||
|
||||
createBlogPost({
|
||||
Future<bool> createBlogPost({
|
||||
String? title,
|
||||
String? videoUrl,
|
||||
String? description,
|
||||
XFile? image,
|
||||
}) async {
|
||||
emit(BlogPostCreateState.loading());
|
||||
await blogPostRepository.createBlogPost(
|
||||
title,
|
||||
videoUrl,
|
||||
description,
|
||||
image,
|
||||
);
|
||||
emit(BlogPostCreateState.loaded());
|
||||
try {
|
||||
emit(BlogPostCreateState.loading());
|
||||
await blogPostRepository.createBlogPost(
|
||||
title,
|
||||
videoUrl,
|
||||
description,
|
||||
image,
|
||||
);
|
||||
return true;
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
emit(BlogPostCreateState.loaded());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
updateBlogPost({
|
||||
Future<bool> updateBlogPost({
|
||||
required int id,
|
||||
String? title,
|
||||
String? videoUrl,
|
||||
@@ -34,19 +42,32 @@ class BlogPostCreateCubit extends Cubit<BlogPostCreateState> {
|
||||
XFile? image,
|
||||
String? oldImageUrl,
|
||||
}) async {
|
||||
emit(BlogPostCreateState.loading());
|
||||
await blogPostRepository.updateBlogPost(
|
||||
id,
|
||||
title,
|
||||
videoUrl,
|
||||
description,
|
||||
image,
|
||||
oldImageUrl,
|
||||
);
|
||||
emit(BlogPostCreateState.loaded());
|
||||
try {
|
||||
emit(BlogPostCreateState.loading());
|
||||
await blogPostRepository.updateBlogPost(
|
||||
id,
|
||||
title,
|
||||
videoUrl,
|
||||
description,
|
||||
image,
|
||||
oldImageUrl,
|
||||
);
|
||||
emit(BlogPostCreateState.loaded());
|
||||
return true;
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
emit(BlogPostCreateState.loaded());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
deleteBlogPost(int id) async {
|
||||
await blogPostRepository.deleteBlogPost(id);
|
||||
Future<bool> deleteBlogPost(int id) async {
|
||||
try {
|
||||
await blogPostRepository.deleteBlogPost(id);
|
||||
return true;
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/theme/app_decoration.dart';
|
||||
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
@@ -9,10 +10,12 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
class BlogPostCreateScreen extends StatefulWidget {
|
||||
@@ -65,7 +68,6 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
listener: (BuildContext context, BlogPostCreateState state) {
|
||||
if (state is BlogPostCreateStateLoaded) {
|
||||
context.read<FeedCubit>().loadFeedBlogPostList();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
child: _buildBlogPostCreate(),
|
||||
@@ -148,8 +150,9 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
|
||||
if (widget.feedBlogPostModel != null) {
|
||||
await blogPostCubit.updateBlogPost(
|
||||
final result = await blogPostCubit.updateBlogPost(
|
||||
id: widget.feedBlogPostModel!.id,
|
||||
title: titleController.text,
|
||||
description: descriptionController.text,
|
||||
@@ -157,13 +160,29 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
image: image,
|
||||
oldImageUrl: widget.feedBlogPostModel!.image,
|
||||
);
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
if (!result) {
|
||||
showErrorDialog(context, title: S.current.could_not_update_blog_post);
|
||||
} else {
|
||||
context.pop();
|
||||
}
|
||||
} else {
|
||||
await blogPostCubit.createBlogPost(
|
||||
final result = await blogPostCubit.createBlogPost(
|
||||
title: titleController.text,
|
||||
description: descriptionController.text,
|
||||
videoUrl: videoUrlController.text,
|
||||
image: image,
|
||||
);
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
if (!result) {
|
||||
showErrorDialog(context, title: S.current.could_not_publish_blog_post);
|
||||
} else {
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:eatvio_app/core/initialization_cubit/ws_manager.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:badges/badges.dart' as badges;
|
||||
import 'package:eatvio_app/core/utils/image_constant.dart';
|
||||
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_state.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.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_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -160,15 +160,19 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
label: s.chat,
|
||||
icon: BlocBuilder<NewMessagesCubit, NewMessagesState>(
|
||||
builder: (BuildContext context, NewMessagesState state) {
|
||||
icon: BlocBuilder<ChatsCubit, ChatsState>(
|
||||
builder: (BuildContext context, ChatsState state) {
|
||||
final hasUnreadMessages = state.chatsWithUnreadMessagesCount > 0;
|
||||
return badges.Badge(
|
||||
position: badges.BadgePosition.topEnd(),
|
||||
badgeAnimation: badges.BadgeAnimation.fade(),
|
||||
badgeStyle: badges.BadgeStyle(
|
||||
badgeColor: hasUnreadMessages ? Colors.red : Colors.transparent,
|
||||
),
|
||||
badgeContent: Text(
|
||||
(state is NewMessagesStateLoaded) ? state.messagesCount.toString() : '0',
|
||||
hasUnreadMessages ? state.chatsWithUnreadMessagesCount.toString() : '',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
color: hasUnreadMessages ? Colors.white : Colors.transparent,
|
||||
fontSize: 8,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'chat_message_model.freezed.dart';
|
||||
@@ -10,7 +11,10 @@ class ChatMessageModel with _$ChatMessageModel {
|
||||
required String data,
|
||||
@JsonKey(name: 'media_data') required List<dynamic>? mediaData,
|
||||
@JsonKey(name: 'author_id') required int authorId,
|
||||
@JsonKey(name: 'updated_at') required String updatedAt,
|
||||
@JsonKey(name: 'updated_at', fromJson: parseDate) required DateTime updatedAt,
|
||||
@JsonKey(name: 'read_by_to_user', defaultValue: false) required bool isRead,
|
||||
@JsonKey(name: 'is_author_message_auth_user') required bool? isCurrentUserAuthor,
|
||||
@JsonKey(name: 'room_id') required String? roomId,
|
||||
}) = _ChatMessageModel;
|
||||
|
||||
factory ChatMessageModel.fromJson(Map<String, Object?> json) => _$ChatMessageModelFromJson(json);
|
||||
|
||||
@@ -26,8 +26,14 @@ mixin _$ChatMessageModel {
|
||||
List<dynamic>? get mediaData => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'author_id')
|
||||
int get authorId => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'updated_at')
|
||||
String get updatedAt => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'updated_at', fromJson: parseDate)
|
||||
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'read_by_to_user', defaultValue: false)
|
||||
bool get isRead => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'is_author_message_auth_user')
|
||||
bool? get isCurrentUserAuthor => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'room_id')
|
||||
String? get roomId => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this ChatMessageModel to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -50,7 +56,10 @@ abstract class $ChatMessageModelCopyWith<$Res> {
|
||||
String data,
|
||||
@JsonKey(name: 'media_data') List<dynamic>? mediaData,
|
||||
@JsonKey(name: 'author_id') int authorId,
|
||||
@JsonKey(name: 'updated_at') String updatedAt});
|
||||
@JsonKey(name: 'updated_at', fromJson: parseDate) DateTime updatedAt,
|
||||
@JsonKey(name: 'read_by_to_user', defaultValue: false) bool isRead,
|
||||
@JsonKey(name: 'is_author_message_auth_user') bool? isCurrentUserAuthor,
|
||||
@JsonKey(name: 'room_id') String? roomId});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -73,6 +82,9 @@ class _$ChatMessageModelCopyWithImpl<$Res, $Val extends ChatMessageModel>
|
||||
Object? mediaData = freezed,
|
||||
Object? authorId = null,
|
||||
Object? updatedAt = null,
|
||||
Object? isRead = null,
|
||||
Object? isCurrentUserAuthor = freezed,
|
||||
Object? roomId = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
@@ -94,7 +106,19 @@ class _$ChatMessageModelCopyWithImpl<$Res, $Val extends ChatMessageModel>
|
||||
updatedAt: null == updatedAt
|
||||
? _value.updatedAt
|
||||
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
as DateTime,
|
||||
isRead: null == isRead
|
||||
? _value.isRead
|
||||
: isRead // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isCurrentUserAuthor: freezed == isCurrentUserAuthor
|
||||
? _value.isCurrentUserAuthor
|
||||
: isCurrentUserAuthor // ignore: cast_nullable_to_non_nullable
|
||||
as bool?,
|
||||
roomId: freezed == roomId
|
||||
? _value.roomId
|
||||
: roomId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
@@ -112,7 +136,10 @@ abstract class _$$ChatMessageModelImplCopyWith<$Res>
|
||||
String data,
|
||||
@JsonKey(name: 'media_data') List<dynamic>? mediaData,
|
||||
@JsonKey(name: 'author_id') int authorId,
|
||||
@JsonKey(name: 'updated_at') String updatedAt});
|
||||
@JsonKey(name: 'updated_at', fromJson: parseDate) DateTime updatedAt,
|
||||
@JsonKey(name: 'read_by_to_user', defaultValue: false) bool isRead,
|
||||
@JsonKey(name: 'is_author_message_auth_user') bool? isCurrentUserAuthor,
|
||||
@JsonKey(name: 'room_id') String? roomId});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -133,6 +160,9 @@ class __$$ChatMessageModelImplCopyWithImpl<$Res>
|
||||
Object? mediaData = freezed,
|
||||
Object? authorId = null,
|
||||
Object? updatedAt = null,
|
||||
Object? isRead = null,
|
||||
Object? isCurrentUserAuthor = freezed,
|
||||
Object? roomId = freezed,
|
||||
}) {
|
||||
return _then(_$ChatMessageModelImpl(
|
||||
id: null == id
|
||||
@@ -154,7 +184,19 @@ class __$$ChatMessageModelImplCopyWithImpl<$Res>
|
||||
updatedAt: null == updatedAt
|
||||
? _value.updatedAt
|
||||
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
as DateTime,
|
||||
isRead: null == isRead
|
||||
? _value.isRead
|
||||
: isRead // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isCurrentUserAuthor: freezed == isCurrentUserAuthor
|
||||
? _value.isCurrentUserAuthor
|
||||
: isCurrentUserAuthor // ignore: cast_nullable_to_non_nullable
|
||||
as bool?,
|
||||
roomId: freezed == roomId
|
||||
? _value.roomId
|
||||
: roomId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -167,7 +209,12 @@ class _$ChatMessageModelImpl implements _ChatMessageModel {
|
||||
required this.data,
|
||||
@JsonKey(name: 'media_data') required final List<dynamic>? mediaData,
|
||||
@JsonKey(name: 'author_id') required this.authorId,
|
||||
@JsonKey(name: 'updated_at') required this.updatedAt})
|
||||
@JsonKey(name: 'updated_at', fromJson: parseDate) required this.updatedAt,
|
||||
@JsonKey(name: 'read_by_to_user', defaultValue: false)
|
||||
required this.isRead,
|
||||
@JsonKey(name: 'is_author_message_auth_user')
|
||||
required this.isCurrentUserAuthor,
|
||||
@JsonKey(name: 'room_id') required this.roomId})
|
||||
: _mediaData = mediaData;
|
||||
|
||||
factory _$ChatMessageModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
@@ -192,12 +239,21 @@ class _$ChatMessageModelImpl implements _ChatMessageModel {
|
||||
@JsonKey(name: 'author_id')
|
||||
final int authorId;
|
||||
@override
|
||||
@JsonKey(name: 'updated_at')
|
||||
final String updatedAt;
|
||||
@JsonKey(name: 'updated_at', fromJson: parseDate)
|
||||
final DateTime updatedAt;
|
||||
@override
|
||||
@JsonKey(name: 'read_by_to_user', defaultValue: false)
|
||||
final bool isRead;
|
||||
@override
|
||||
@JsonKey(name: 'is_author_message_auth_user')
|
||||
final bool? isCurrentUserAuthor;
|
||||
@override
|
||||
@JsonKey(name: 'room_id')
|
||||
final String? roomId;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChatMessageModel(id: $id, data: $data, mediaData: $mediaData, authorId: $authorId, updatedAt: $updatedAt)';
|
||||
return 'ChatMessageModel(id: $id, data: $data, mediaData: $mediaData, authorId: $authorId, updatedAt: $updatedAt, isRead: $isRead, isCurrentUserAuthor: $isCurrentUserAuthor, roomId: $roomId)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -212,13 +268,25 @@ class _$ChatMessageModelImpl implements _ChatMessageModel {
|
||||
(identical(other.authorId, authorId) ||
|
||||
other.authorId == authorId) &&
|
||||
(identical(other.updatedAt, updatedAt) ||
|
||||
other.updatedAt == updatedAt));
|
||||
other.updatedAt == updatedAt) &&
|
||||
(identical(other.isRead, isRead) || other.isRead == isRead) &&
|
||||
(identical(other.isCurrentUserAuthor, isCurrentUserAuthor) ||
|
||||
other.isCurrentUserAuthor == isCurrentUserAuthor) &&
|
||||
(identical(other.roomId, roomId) || other.roomId == roomId));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, id, data,
|
||||
const DeepCollectionEquality().hash(_mediaData), authorId, updatedAt);
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
id,
|
||||
data,
|
||||
const DeepCollectionEquality().hash(_mediaData),
|
||||
authorId,
|
||||
updatedAt,
|
||||
isRead,
|
||||
isCurrentUserAuthor,
|
||||
roomId);
|
||||
|
||||
/// Create a copy of ChatMessageModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -243,7 +311,13 @@ abstract class _ChatMessageModel implements ChatMessageModel {
|
||||
required final String data,
|
||||
@JsonKey(name: 'media_data') required final List<dynamic>? mediaData,
|
||||
@JsonKey(name: 'author_id') required final int authorId,
|
||||
@JsonKey(name: 'updated_at') required final String updatedAt}) =
|
||||
@JsonKey(name: 'updated_at', fromJson: parseDate)
|
||||
required final DateTime updatedAt,
|
||||
@JsonKey(name: 'read_by_to_user', defaultValue: false)
|
||||
required final bool isRead,
|
||||
@JsonKey(name: 'is_author_message_auth_user')
|
||||
required final bool? isCurrentUserAuthor,
|
||||
@JsonKey(name: 'room_id') required final String? roomId}) =
|
||||
_$ChatMessageModelImpl;
|
||||
|
||||
factory _ChatMessageModel.fromJson(Map<String, dynamic> json) =
|
||||
@@ -260,8 +334,17 @@ abstract class _ChatMessageModel implements ChatMessageModel {
|
||||
@JsonKey(name: 'author_id')
|
||||
int get authorId;
|
||||
@override
|
||||
@JsonKey(name: 'updated_at')
|
||||
String get updatedAt;
|
||||
@JsonKey(name: 'updated_at', fromJson: parseDate)
|
||||
DateTime get updatedAt;
|
||||
@override
|
||||
@JsonKey(name: 'read_by_to_user', defaultValue: false)
|
||||
bool get isRead;
|
||||
@override
|
||||
@JsonKey(name: 'is_author_message_auth_user')
|
||||
bool? get isCurrentUserAuthor;
|
||||
@override
|
||||
@JsonKey(name: 'room_id')
|
||||
String? get roomId;
|
||||
|
||||
/// Create a copy of ChatMessageModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
||||
@@ -13,7 +13,10 @@ _$ChatMessageModelImpl _$$ChatMessageModelImplFromJson(
|
||||
data: json['data'] as String,
|
||||
mediaData: json['media_data'] as List<dynamic>?,
|
||||
authorId: (json['author_id'] as num).toInt(),
|
||||
updatedAt: json['updated_at'] as String,
|
||||
updatedAt: parseDate(json['updated_at'] as String),
|
||||
isRead: json['read_by_to_user'] as bool? ?? false,
|
||||
isCurrentUserAuthor: json['is_author_message_auth_user'] as bool?,
|
||||
roomId: json['room_id'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ChatMessageModelImplToJson(
|
||||
@@ -23,5 +26,8 @@ Map<String, dynamic> _$$ChatMessageModelImplToJson(
|
||||
'data': instance.data,
|
||||
'media_data': instance.mediaData,
|
||||
'author_id': instance.authorId,
|
||||
'updated_at': instance.updatedAt,
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'read_by_to_user': instance.isRead,
|
||||
'is_author_message_auth_user': instance.isCurrentUserAuthor,
|
||||
'room_id': instance.roomId,
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'chat_room_preview_model.freezed.dart';
|
||||
@@ -17,26 +18,19 @@ class ChatRoom with _$ChatRoom {
|
||||
const factory ChatRoom({
|
||||
@JsonKey(name: "id") required String id,
|
||||
@JsonKey(name: "room_name") required String roomName,
|
||||
@JsonKey(name: "last_message") required dynamic lastMessage,
|
||||
@JsonKey(name: "last_message", fromJson: parseLastMessage) required ChatMessageModel? lastMessage,
|
||||
@JsonKey(name: "users") required Users users,
|
||||
@JsonKey(name: "unread_messages_count") required int unreadMessagesCount,
|
||||
}) = _ChatRoom;
|
||||
|
||||
factory ChatRoom.fromJson(Map<String, dynamic> json) => _$ChatRoomFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class LastMessageClass with _$LastMessageClass {
|
||||
const factory LastMessageClass({
|
||||
@JsonKey(name: "id") required int id,
|
||||
@JsonKey(name: "data") required String data,
|
||||
@JsonKey(name: "media_data") required List<dynamic> mediaData,
|
||||
@JsonKey(name: "author_id") required int authorId,
|
||||
@JsonKey(name: "is_author_message_auth_user") required bool isAuthorMessageAuthUser,
|
||||
@JsonKey(name: "read_by_to_user") required bool readByToUser,
|
||||
@JsonKey(name: "updated_at") required DateTime updatedAt,
|
||||
}) = _LastMessageClass;
|
||||
|
||||
factory LastMessageClass.fromJson(Map<String, dynamic> json) => _$LastMessageClassFromJson(json);
|
||||
ChatMessageModel? parseLastMessage(dynamic json) {
|
||||
if (json is List) {
|
||||
return null;
|
||||
}
|
||||
return ChatMessageModel.fromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
||||
@@ -190,10 +190,12 @@ mixin _$ChatRoom {
|
||||
String get id => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "room_name")
|
||||
String get roomName => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "last_message")
|
||||
dynamic get lastMessage => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "last_message", fromJson: parseLastMessage)
|
||||
ChatMessageModel? get lastMessage => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "users")
|
||||
Users get users => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "unread_messages_count")
|
||||
int get unreadMessagesCount => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this ChatRoom to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -213,9 +215,12 @@ abstract class $ChatRoomCopyWith<$Res> {
|
||||
$Res call(
|
||||
{@JsonKey(name: "id") String id,
|
||||
@JsonKey(name: "room_name") String roomName,
|
||||
@JsonKey(name: "last_message") dynamic lastMessage,
|
||||
@JsonKey(name: "users") Users users});
|
||||
@JsonKey(name: "last_message", fromJson: parseLastMessage)
|
||||
ChatMessageModel? lastMessage,
|
||||
@JsonKey(name: "users") Users users,
|
||||
@JsonKey(name: "unread_messages_count") int unreadMessagesCount});
|
||||
|
||||
$ChatMessageModelCopyWith<$Res>? get lastMessage;
|
||||
$UsersCopyWith<$Res> get users;
|
||||
}
|
||||
|
||||
@@ -238,6 +243,7 @@ class _$ChatRoomCopyWithImpl<$Res, $Val extends ChatRoom>
|
||||
Object? roomName = null,
|
||||
Object? lastMessage = freezed,
|
||||
Object? users = null,
|
||||
Object? unreadMessagesCount = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
@@ -251,14 +257,32 @@ class _$ChatRoomCopyWithImpl<$Res, $Val extends ChatRoom>
|
||||
lastMessage: freezed == lastMessage
|
||||
? _value.lastMessage
|
||||
: lastMessage // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,
|
||||
as ChatMessageModel?,
|
||||
users: null == users
|
||||
? _value.users
|
||||
: users // ignore: cast_nullable_to_non_nullable
|
||||
as Users,
|
||||
unreadMessagesCount: null == unreadMessagesCount
|
||||
? _value.unreadMessagesCount
|
||||
: unreadMessagesCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
/// Create a copy of ChatRoom
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ChatMessageModelCopyWith<$Res>? get lastMessage {
|
||||
if (_value.lastMessage == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $ChatMessageModelCopyWith<$Res>(_value.lastMessage!, (value) {
|
||||
return _then(_value.copyWith(lastMessage: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a copy of ChatRoom
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@@ -281,9 +305,13 @@ abstract class _$$ChatRoomImplCopyWith<$Res>
|
||||
$Res call(
|
||||
{@JsonKey(name: "id") String id,
|
||||
@JsonKey(name: "room_name") String roomName,
|
||||
@JsonKey(name: "last_message") dynamic lastMessage,
|
||||
@JsonKey(name: "users") Users users});
|
||||
@JsonKey(name: "last_message", fromJson: parseLastMessage)
|
||||
ChatMessageModel? lastMessage,
|
||||
@JsonKey(name: "users") Users users,
|
||||
@JsonKey(name: "unread_messages_count") int unreadMessagesCount});
|
||||
|
||||
@override
|
||||
$ChatMessageModelCopyWith<$Res>? get lastMessage;
|
||||
@override
|
||||
$UsersCopyWith<$Res> get users;
|
||||
}
|
||||
@@ -305,6 +333,7 @@ class __$$ChatRoomImplCopyWithImpl<$Res>
|
||||
Object? roomName = null,
|
||||
Object? lastMessage = freezed,
|
||||
Object? users = null,
|
||||
Object? unreadMessagesCount = null,
|
||||
}) {
|
||||
return _then(_$ChatRoomImpl(
|
||||
id: null == id
|
||||
@@ -318,11 +347,15 @@ class __$$ChatRoomImplCopyWithImpl<$Res>
|
||||
lastMessage: freezed == lastMessage
|
||||
? _value.lastMessage
|
||||
: lastMessage // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,
|
||||
as ChatMessageModel?,
|
||||
users: null == users
|
||||
? _value.users
|
||||
: users // ignore: cast_nullable_to_non_nullable
|
||||
as Users,
|
||||
unreadMessagesCount: null == unreadMessagesCount
|
||||
? _value.unreadMessagesCount
|
||||
: unreadMessagesCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -333,8 +366,11 @@ class _$ChatRoomImpl implements _ChatRoom {
|
||||
const _$ChatRoomImpl(
|
||||
{@JsonKey(name: "id") required this.id,
|
||||
@JsonKey(name: "room_name") required this.roomName,
|
||||
@JsonKey(name: "last_message") required this.lastMessage,
|
||||
@JsonKey(name: "users") required this.users});
|
||||
@JsonKey(name: "last_message", fromJson: parseLastMessage)
|
||||
required this.lastMessage,
|
||||
@JsonKey(name: "users") required this.users,
|
||||
@JsonKey(name: "unread_messages_count")
|
||||
required this.unreadMessagesCount});
|
||||
|
||||
factory _$ChatRoomImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ChatRoomImplFromJson(json);
|
||||
@@ -346,15 +382,18 @@ class _$ChatRoomImpl implements _ChatRoom {
|
||||
@JsonKey(name: "room_name")
|
||||
final String roomName;
|
||||
@override
|
||||
@JsonKey(name: "last_message")
|
||||
final dynamic lastMessage;
|
||||
@JsonKey(name: "last_message", fromJson: parseLastMessage)
|
||||
final ChatMessageModel? lastMessage;
|
||||
@override
|
||||
@JsonKey(name: "users")
|
||||
final Users users;
|
||||
@override
|
||||
@JsonKey(name: "unread_messages_count")
|
||||
final int unreadMessagesCount;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChatRoom(id: $id, roomName: $roomName, lastMessage: $lastMessage, users: $users)';
|
||||
return 'ChatRoom(id: $id, roomName: $roomName, lastMessage: $lastMessage, users: $users, unreadMessagesCount: $unreadMessagesCount)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -365,15 +404,17 @@ class _$ChatRoomImpl implements _ChatRoom {
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.roomName, roomName) ||
|
||||
other.roomName == roomName) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.lastMessage, lastMessage) &&
|
||||
(identical(other.users, users) || other.users == users));
|
||||
(identical(other.lastMessage, lastMessage) ||
|
||||
other.lastMessage == lastMessage) &&
|
||||
(identical(other.users, users) || other.users == users) &&
|
||||
(identical(other.unreadMessagesCount, unreadMessagesCount) ||
|
||||
other.unreadMessagesCount == unreadMessagesCount));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, id, roomName,
|
||||
const DeepCollectionEquality().hash(lastMessage), users);
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType, id, roomName, lastMessage, users, unreadMessagesCount);
|
||||
|
||||
/// Create a copy of ChatRoom
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -395,8 +436,11 @@ abstract class _ChatRoom implements ChatRoom {
|
||||
const factory _ChatRoom(
|
||||
{@JsonKey(name: "id") required final String id,
|
||||
@JsonKey(name: "room_name") required final String roomName,
|
||||
@JsonKey(name: "last_message") required final dynamic lastMessage,
|
||||
@JsonKey(name: "users") required final Users users}) = _$ChatRoomImpl;
|
||||
@JsonKey(name: "last_message", fromJson: parseLastMessage)
|
||||
required final ChatMessageModel? lastMessage,
|
||||
@JsonKey(name: "users") required final Users users,
|
||||
@JsonKey(name: "unread_messages_count")
|
||||
required final int unreadMessagesCount}) = _$ChatRoomImpl;
|
||||
|
||||
factory _ChatRoom.fromJson(Map<String, dynamic> json) =
|
||||
_$ChatRoomImpl.fromJson;
|
||||
@@ -408,11 +452,14 @@ abstract class _ChatRoom implements ChatRoom {
|
||||
@JsonKey(name: "room_name")
|
||||
String get roomName;
|
||||
@override
|
||||
@JsonKey(name: "last_message")
|
||||
dynamic get lastMessage;
|
||||
@JsonKey(name: "last_message", fromJson: parseLastMessage)
|
||||
ChatMessageModel? get lastMessage;
|
||||
@override
|
||||
@JsonKey(name: "users")
|
||||
Users get users;
|
||||
@override
|
||||
@JsonKey(name: "unread_messages_count")
|
||||
int get unreadMessagesCount;
|
||||
|
||||
/// Create a copy of ChatRoom
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -422,327 +469,6 @@ abstract class _ChatRoom implements ChatRoom {
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
LastMessageClass _$LastMessageClassFromJson(Map<String, dynamic> json) {
|
||||
return _LastMessageClass.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$LastMessageClass {
|
||||
@JsonKey(name: "id")
|
||||
int get id => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "data")
|
||||
String get data => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "media_data")
|
||||
List<dynamic> get mediaData => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "author_id")
|
||||
int get authorId => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "is_author_message_auth_user")
|
||||
bool get isAuthorMessageAuthUser => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "read_by_to_user")
|
||||
bool get readByToUser => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "updated_at")
|
||||
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this LastMessageClass to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of LastMessageClass
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$LastMessageClassCopyWith<LastMessageClass> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $LastMessageClassCopyWith<$Res> {
|
||||
factory $LastMessageClassCopyWith(
|
||||
LastMessageClass value, $Res Function(LastMessageClass) then) =
|
||||
_$LastMessageClassCopyWithImpl<$Res, LastMessageClass>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{@JsonKey(name: "id") int id,
|
||||
@JsonKey(name: "data") String data,
|
||||
@JsonKey(name: "media_data") List<dynamic> mediaData,
|
||||
@JsonKey(name: "author_id") int authorId,
|
||||
@JsonKey(name: "is_author_message_auth_user")
|
||||
bool isAuthorMessageAuthUser,
|
||||
@JsonKey(name: "read_by_to_user") bool readByToUser,
|
||||
@JsonKey(name: "updated_at") DateTime updatedAt});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$LastMessageClassCopyWithImpl<$Res, $Val extends LastMessageClass>
|
||||
implements $LastMessageClassCopyWith<$Res> {
|
||||
_$LastMessageClassCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of LastMessageClass
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? data = null,
|
||||
Object? mediaData = null,
|
||||
Object? authorId = null,
|
||||
Object? isAuthorMessageAuthUser = null,
|
||||
Object? readByToUser = null,
|
||||
Object? updatedAt = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
data: null == data
|
||||
? _value.data
|
||||
: data // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
mediaData: null == mediaData
|
||||
? _value.mediaData
|
||||
: mediaData // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>,
|
||||
authorId: null == authorId
|
||||
? _value.authorId
|
||||
: authorId // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
isAuthorMessageAuthUser: null == isAuthorMessageAuthUser
|
||||
? _value.isAuthorMessageAuthUser
|
||||
: isAuthorMessageAuthUser // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
readByToUser: null == readByToUser
|
||||
? _value.readByToUser
|
||||
: readByToUser // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
updatedAt: null == updatedAt
|
||||
? _value.updatedAt
|
||||
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$LastMessageClassImplCopyWith<$Res>
|
||||
implements $LastMessageClassCopyWith<$Res> {
|
||||
factory _$$LastMessageClassImplCopyWith(_$LastMessageClassImpl value,
|
||||
$Res Function(_$LastMessageClassImpl) then) =
|
||||
__$$LastMessageClassImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{@JsonKey(name: "id") int id,
|
||||
@JsonKey(name: "data") String data,
|
||||
@JsonKey(name: "media_data") List<dynamic> mediaData,
|
||||
@JsonKey(name: "author_id") int authorId,
|
||||
@JsonKey(name: "is_author_message_auth_user")
|
||||
bool isAuthorMessageAuthUser,
|
||||
@JsonKey(name: "read_by_to_user") bool readByToUser,
|
||||
@JsonKey(name: "updated_at") DateTime updatedAt});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$LastMessageClassImplCopyWithImpl<$Res>
|
||||
extends _$LastMessageClassCopyWithImpl<$Res, _$LastMessageClassImpl>
|
||||
implements _$$LastMessageClassImplCopyWith<$Res> {
|
||||
__$$LastMessageClassImplCopyWithImpl(_$LastMessageClassImpl _value,
|
||||
$Res Function(_$LastMessageClassImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of LastMessageClass
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? data = null,
|
||||
Object? mediaData = null,
|
||||
Object? authorId = null,
|
||||
Object? isAuthorMessageAuthUser = null,
|
||||
Object? readByToUser = null,
|
||||
Object? updatedAt = null,
|
||||
}) {
|
||||
return _then(_$LastMessageClassImpl(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
data: null == data
|
||||
? _value.data
|
||||
: data // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
mediaData: null == mediaData
|
||||
? _value._mediaData
|
||||
: mediaData // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>,
|
||||
authorId: null == authorId
|
||||
? _value.authorId
|
||||
: authorId // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
isAuthorMessageAuthUser: null == isAuthorMessageAuthUser
|
||||
? _value.isAuthorMessageAuthUser
|
||||
: isAuthorMessageAuthUser // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
readByToUser: null == readByToUser
|
||||
? _value.readByToUser
|
||||
: readByToUser // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
updatedAt: null == updatedAt
|
||||
? _value.updatedAt
|
||||
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$LastMessageClassImpl implements _LastMessageClass {
|
||||
const _$LastMessageClassImpl(
|
||||
{@JsonKey(name: "id") required this.id,
|
||||
@JsonKey(name: "data") required this.data,
|
||||
@JsonKey(name: "media_data") required final List<dynamic> mediaData,
|
||||
@JsonKey(name: "author_id") required this.authorId,
|
||||
@JsonKey(name: "is_author_message_auth_user")
|
||||
required this.isAuthorMessageAuthUser,
|
||||
@JsonKey(name: "read_by_to_user") required this.readByToUser,
|
||||
@JsonKey(name: "updated_at") required this.updatedAt})
|
||||
: _mediaData = mediaData;
|
||||
|
||||
factory _$LastMessageClassImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$LastMessageClassImplFromJson(json);
|
||||
|
||||
@override
|
||||
@JsonKey(name: "id")
|
||||
final int id;
|
||||
@override
|
||||
@JsonKey(name: "data")
|
||||
final String data;
|
||||
final List<dynamic> _mediaData;
|
||||
@override
|
||||
@JsonKey(name: "media_data")
|
||||
List<dynamic> get mediaData {
|
||||
if (_mediaData is EqualUnmodifiableListView) return _mediaData;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_mediaData);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey(name: "author_id")
|
||||
final int authorId;
|
||||
@override
|
||||
@JsonKey(name: "is_author_message_auth_user")
|
||||
final bool isAuthorMessageAuthUser;
|
||||
@override
|
||||
@JsonKey(name: "read_by_to_user")
|
||||
final bool readByToUser;
|
||||
@override
|
||||
@JsonKey(name: "updated_at")
|
||||
final DateTime updatedAt;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LastMessageClass(id: $id, data: $data, mediaData: $mediaData, authorId: $authorId, isAuthorMessageAuthUser: $isAuthorMessageAuthUser, readByToUser: $readByToUser, updatedAt: $updatedAt)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$LastMessageClassImpl &&
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.data, data) || other.data == data) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._mediaData, _mediaData) &&
|
||||
(identical(other.authorId, authorId) ||
|
||||
other.authorId == authorId) &&
|
||||
(identical(
|
||||
other.isAuthorMessageAuthUser, isAuthorMessageAuthUser) ||
|
||||
other.isAuthorMessageAuthUser == isAuthorMessageAuthUser) &&
|
||||
(identical(other.readByToUser, readByToUser) ||
|
||||
other.readByToUser == readByToUser) &&
|
||||
(identical(other.updatedAt, updatedAt) ||
|
||||
other.updatedAt == updatedAt));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
id,
|
||||
data,
|
||||
const DeepCollectionEquality().hash(_mediaData),
|
||||
authorId,
|
||||
isAuthorMessageAuthUser,
|
||||
readByToUser,
|
||||
updatedAt);
|
||||
|
||||
/// Create a copy of LastMessageClass
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$LastMessageClassImplCopyWith<_$LastMessageClassImpl> get copyWith =>
|
||||
__$$LastMessageClassImplCopyWithImpl<_$LastMessageClassImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$LastMessageClassImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _LastMessageClass implements LastMessageClass {
|
||||
const factory _LastMessageClass(
|
||||
{@JsonKey(name: "id") required final int id,
|
||||
@JsonKey(name: "data") required final String data,
|
||||
@JsonKey(name: "media_data") required final List<dynamic> mediaData,
|
||||
@JsonKey(name: "author_id") required final int authorId,
|
||||
@JsonKey(name: "is_author_message_auth_user")
|
||||
required final bool isAuthorMessageAuthUser,
|
||||
@JsonKey(name: "read_by_to_user") required final bool readByToUser,
|
||||
@JsonKey(name: "updated_at") required final DateTime updatedAt}) =
|
||||
_$LastMessageClassImpl;
|
||||
|
||||
factory _LastMessageClass.fromJson(Map<String, dynamic> json) =
|
||||
_$LastMessageClassImpl.fromJson;
|
||||
|
||||
@override
|
||||
@JsonKey(name: "id")
|
||||
int get id;
|
||||
@override
|
||||
@JsonKey(name: "data")
|
||||
String get data;
|
||||
@override
|
||||
@JsonKey(name: "media_data")
|
||||
List<dynamic> get mediaData;
|
||||
@override
|
||||
@JsonKey(name: "author_id")
|
||||
int get authorId;
|
||||
@override
|
||||
@JsonKey(name: "is_author_message_auth_user")
|
||||
bool get isAuthorMessageAuthUser;
|
||||
@override
|
||||
@JsonKey(name: "read_by_to_user")
|
||||
bool get readByToUser;
|
||||
@override
|
||||
@JsonKey(name: "updated_at")
|
||||
DateTime get updatedAt;
|
||||
|
||||
/// Create a copy of LastMessageClass
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$LastMessageClassImplCopyWith<_$LastMessageClassImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
Users _$UsersFromJson(Map<String, dynamic> json) {
|
||||
return _Users.fromJson(json);
|
||||
}
|
||||
|
||||
@@ -24,8 +24,9 @@ _$ChatRoomImpl _$$ChatRoomImplFromJson(Map<String, dynamic> json) =>
|
||||
_$ChatRoomImpl(
|
||||
id: json['id'] as String,
|
||||
roomName: json['room_name'] as String,
|
||||
lastMessage: json['last_message'],
|
||||
lastMessage: parseLastMessage(json['last_message']),
|
||||
users: Users.fromJson(json['users'] as Map<String, dynamic>),
|
||||
unreadMessagesCount: (json['unread_messages_count'] as num).toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ChatRoomImplToJson(_$ChatRoomImpl instance) =>
|
||||
@@ -34,30 +35,7 @@ Map<String, dynamic> _$$ChatRoomImplToJson(_$ChatRoomImpl instance) =>
|
||||
'room_name': instance.roomName,
|
||||
'last_message': instance.lastMessage,
|
||||
'users': instance.users,
|
||||
};
|
||||
|
||||
_$LastMessageClassImpl _$$LastMessageClassImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$LastMessageClassImpl(
|
||||
id: (json['id'] as num).toInt(),
|
||||
data: json['data'] as String,
|
||||
mediaData: json['media_data'] as List<dynamic>,
|
||||
authorId: (json['author_id'] as num).toInt(),
|
||||
isAuthorMessageAuthUser: json['is_author_message_auth_user'] as bool,
|
||||
readByToUser: json['read_by_to_user'] as bool,
|
||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$LastMessageClassImplToJson(
|
||||
_$LastMessageClassImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'data': instance.data,
|
||||
'media_data': instance.mediaData,
|
||||
'author_id': instance.authorId,
|
||||
'is_author_message_auth_user': instance.isAuthorMessageAuthUser,
|
||||
'read_by_to_user': instance.readByToUser,
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'unread_messages_count': instance.unreadMessagesCount,
|
||||
};
|
||||
|
||||
_$UsersImpl _$$UsersImplFromJson(Map<String, dynamic> json) => _$UsersImpl(
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
||||
import 'package:eatvio_app/core/initialization_cubit/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/presentation/bloc/chat_room_cubit/chat_room_state.dart';
|
||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||
@@ -14,6 +17,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
final ChatRepository chatRepository;
|
||||
final WsManager wsManager;
|
||||
|
||||
StreamSubscription<ChannelReadEvent>? typingSubscription;
|
||||
StreamSubscription<ChannelReadEvent>? messagesSubscription;
|
||||
int currentPage = 1;
|
||||
late final int lastPage;
|
||||
bool isLoading = false;
|
||||
@@ -74,6 +79,9 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
typingSubscription?.cancel();
|
||||
messagesSubscription?.cancel();
|
||||
wsManager.unsubscribeDirectChatChannel();
|
||||
emit(ChatRoomState.error(errorMessage: 'Ошибка получеия данных'));
|
||||
}
|
||||
}
|
||||
@@ -87,10 +95,12 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
wsManager.subscribeDirectChatChannel();
|
||||
wsManager.directChatStream?.listen((event) {
|
||||
AudioPlayer().play((AssetSource('audio/recive_message.wav')));
|
||||
final message = ChatMessageModel.fromJson(jsonDecode(event.data)['message']);
|
||||
List<ChatMessageModel> messagesList = [
|
||||
ChatMessageModel.fromJson(jsonDecode(event.data)['message']),
|
||||
message,
|
||||
...(state as ChatRoomStateLoaded).chatMessages,
|
||||
];
|
||||
chatRepository.emitNewMessageToChats(message);
|
||||
emit(
|
||||
ChatRoomState.loaded(
|
||||
chatRoom: (state as ChatRoomStateLoaded).chatRoom,
|
||||
@@ -125,17 +135,46 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
});
|
||||
}
|
||||
|
||||
sendMessage({
|
||||
Future<bool> sendMessage({
|
||||
required String chatId,
|
||||
String? data,
|
||||
List<File>? files,
|
||||
}) async {
|
||||
await chatRepository.sendMessage(
|
||||
chatId: chatId,
|
||||
data: data,
|
||||
files: files != null ? await ImageCompressUtility.compressFilesList(files) : null,
|
||||
);
|
||||
AudioPlayer().play((AssetSource('audio/send_message.wav')));
|
||||
try {
|
||||
await chatRepository.sendMessage(
|
||||
chatId: chatId,
|
||||
data: data,
|
||||
files: files != null ? await ImageCompressUtility.compressFilesList(files) : null,
|
||||
);
|
||||
AudioPlayer().play((AssetSource('audio/send_message.wav')));
|
||||
return true;
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> markMessagesAsRead(String chatId, List<({int id, int index})> messagesIds) async {
|
||||
try {
|
||||
if (state is ChatRoomStateLoaded) {
|
||||
final ids = messagesIds.map((e) => e.id).toList();
|
||||
await chatRepository.markMessagesAsRead(chatId, ids);
|
||||
final currentMessages = [...(state as ChatRoomStateLoaded).chatMessages];
|
||||
messagesIds.forEach((elem) {
|
||||
currentMessages[elem.index] = currentMessages[elem.index].copyWith(isRead: true);
|
||||
});
|
||||
if (!isClosed) {
|
||||
emit(
|
||||
(state as ChatRoomStateLoaded).copyWith(chatMessages: currentMessages),
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_select_cubit/chat_select_state.dart';
|
||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class ChatSelectCubit extends Cubit<ChatSelectState> {
|
||||
final ChatRepository chatRepository;
|
||||
|
||||
ChatSelectCubit({
|
||||
required this.chatRepository,
|
||||
}) : super(ChatSelectState());
|
||||
|
||||
getChatRooms({String? query}) async {
|
||||
emit(ChatSelectState.loading());
|
||||
try {
|
||||
final chatRooms = await chatRepository.getChatRoomPreviews(query: query);
|
||||
emit(
|
||||
ChatSelectState.loaded(
|
||||
chatRooms: chatRooms,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
ChatSelectState.error(errorMessage: e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import 'package:eatvio_app/features/chat/models/chat_room_preview_model.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'chat_select_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class ChatSelectState with _$ChatSelectState {
|
||||
const factory ChatSelectState() = _ChatSelectState;
|
||||
|
||||
const factory ChatSelectState.loading() = ChatSelectStateLoading;
|
||||
|
||||
const factory ChatSelectState.error({
|
||||
required String errorMessage,
|
||||
}) = ChatSelectStateError;
|
||||
|
||||
const factory ChatSelectState.loaded({
|
||||
required List<ChatRoom> chatRooms,
|
||||
}) = ChatSelectStateLoaded;
|
||||
}
|
||||
@@ -1,646 +0,0 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'chat_select_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ChatSelectState {
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function() $default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(String errorMessage) error,
|
||||
required TResult Function(List<ChatRoom> chatRooms) loaded,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult? Function()? $default, {
|
||||
TResult? Function()? loading,
|
||||
TResult? Function(String errorMessage)? error,
|
||||
TResult? Function(List<ChatRoom> chatRooms)? loaded,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function()? $default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(String errorMessage)? error,
|
||||
TResult Function(List<ChatRoom> chatRooms)? loaded,
|
||||
required TResult orElse(),
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>(
|
||||
TResult Function(_ChatSelectState value) $default, {
|
||||
required TResult Function(ChatSelectStateLoading value) loading,
|
||||
required TResult Function(ChatSelectStateError value) error,
|
||||
required TResult Function(ChatSelectStateLoaded value) loaded,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>(
|
||||
TResult? Function(_ChatSelectState value)? $default, {
|
||||
TResult? Function(ChatSelectStateLoading value)? loading,
|
||||
TResult? Function(ChatSelectStateError value)? error,
|
||||
TResult? Function(ChatSelectStateLoaded value)? loaded,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>(
|
||||
TResult Function(_ChatSelectState value)? $default, {
|
||||
TResult Function(ChatSelectStateLoading value)? loading,
|
||||
TResult Function(ChatSelectStateError value)? error,
|
||||
TResult Function(ChatSelectStateLoaded value)? loaded,
|
||||
required TResult orElse(),
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ChatSelectStateCopyWith<$Res> {
|
||||
factory $ChatSelectStateCopyWith(
|
||||
ChatSelectState value, $Res Function(ChatSelectState) then) =
|
||||
_$ChatSelectStateCopyWithImpl<$Res, ChatSelectState>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ChatSelectStateCopyWithImpl<$Res, $Val extends ChatSelectState>
|
||||
implements $ChatSelectStateCopyWith<$Res> {
|
||||
_$ChatSelectStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ChatSelectState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ChatSelectStateImplCopyWith<$Res> {
|
||||
factory _$$ChatSelectStateImplCopyWith(_$ChatSelectStateImpl value,
|
||||
$Res Function(_$ChatSelectStateImpl) then) =
|
||||
__$$ChatSelectStateImplCopyWithImpl<$Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ChatSelectStateImplCopyWithImpl<$Res>
|
||||
extends _$ChatSelectStateCopyWithImpl<$Res, _$ChatSelectStateImpl>
|
||||
implements _$$ChatSelectStateImplCopyWith<$Res> {
|
||||
__$$ChatSelectStateImplCopyWithImpl(
|
||||
_$ChatSelectStateImpl _value, $Res Function(_$ChatSelectStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of ChatSelectState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ChatSelectStateImpl implements _ChatSelectState {
|
||||
const _$ChatSelectStateImpl();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChatSelectState()';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType && other is _$ChatSelectStateImpl);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function() $default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(String errorMessage) error,
|
||||
required TResult Function(List<ChatRoom> chatRooms) loaded,
|
||||
}) {
|
||||
return $default();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult? Function()? $default, {
|
||||
TResult? Function()? loading,
|
||||
TResult? Function(String errorMessage)? error,
|
||||
TResult? Function(List<ChatRoom> chatRooms)? loaded,
|
||||
}) {
|
||||
return $default?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function()? $default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(String errorMessage)? error,
|
||||
TResult Function(List<ChatRoom> chatRooms)? loaded,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if ($default != null) {
|
||||
return $default();
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>(
|
||||
TResult Function(_ChatSelectState value) $default, {
|
||||
required TResult Function(ChatSelectStateLoading value) loading,
|
||||
required TResult Function(ChatSelectStateError value) error,
|
||||
required TResult Function(ChatSelectStateLoaded value) loaded,
|
||||
}) {
|
||||
return $default(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>(
|
||||
TResult? Function(_ChatSelectState value)? $default, {
|
||||
TResult? Function(ChatSelectStateLoading value)? loading,
|
||||
TResult? Function(ChatSelectStateError value)? error,
|
||||
TResult? Function(ChatSelectStateLoaded value)? loaded,
|
||||
}) {
|
||||
return $default?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>(
|
||||
TResult Function(_ChatSelectState value)? $default, {
|
||||
TResult Function(ChatSelectStateLoading value)? loading,
|
||||
TResult Function(ChatSelectStateError value)? error,
|
||||
TResult Function(ChatSelectStateLoaded value)? loaded,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if ($default != null) {
|
||||
return $default(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ChatSelectState implements ChatSelectState {
|
||||
const factory _ChatSelectState() = _$ChatSelectStateImpl;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ChatSelectStateLoadingImplCopyWith<$Res> {
|
||||
factory _$$ChatSelectStateLoadingImplCopyWith(
|
||||
_$ChatSelectStateLoadingImpl value,
|
||||
$Res Function(_$ChatSelectStateLoadingImpl) then) =
|
||||
__$$ChatSelectStateLoadingImplCopyWithImpl<$Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ChatSelectStateLoadingImplCopyWithImpl<$Res>
|
||||
extends _$ChatSelectStateCopyWithImpl<$Res, _$ChatSelectStateLoadingImpl>
|
||||
implements _$$ChatSelectStateLoadingImplCopyWith<$Res> {
|
||||
__$$ChatSelectStateLoadingImplCopyWithImpl(
|
||||
_$ChatSelectStateLoadingImpl _value,
|
||||
$Res Function(_$ChatSelectStateLoadingImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of ChatSelectState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ChatSelectStateLoadingImpl implements ChatSelectStateLoading {
|
||||
const _$ChatSelectStateLoadingImpl();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChatSelectState.loading()';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ChatSelectStateLoadingImpl);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function() $default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(String errorMessage) error,
|
||||
required TResult Function(List<ChatRoom> chatRooms) loaded,
|
||||
}) {
|
||||
return loading();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult? Function()? $default, {
|
||||
TResult? Function()? loading,
|
||||
TResult? Function(String errorMessage)? error,
|
||||
TResult? Function(List<ChatRoom> chatRooms)? loaded,
|
||||
}) {
|
||||
return loading?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function()? $default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(String errorMessage)? error,
|
||||
TResult Function(List<ChatRoom> chatRooms)? loaded,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (loading != null) {
|
||||
return loading();
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>(
|
||||
TResult Function(_ChatSelectState value) $default, {
|
||||
required TResult Function(ChatSelectStateLoading value) loading,
|
||||
required TResult Function(ChatSelectStateError value) error,
|
||||
required TResult Function(ChatSelectStateLoaded value) loaded,
|
||||
}) {
|
||||
return loading(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>(
|
||||
TResult? Function(_ChatSelectState value)? $default, {
|
||||
TResult? Function(ChatSelectStateLoading value)? loading,
|
||||
TResult? Function(ChatSelectStateError value)? error,
|
||||
TResult? Function(ChatSelectStateLoaded value)? loaded,
|
||||
}) {
|
||||
return loading?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>(
|
||||
TResult Function(_ChatSelectState value)? $default, {
|
||||
TResult Function(ChatSelectStateLoading value)? loading,
|
||||
TResult Function(ChatSelectStateError value)? error,
|
||||
TResult Function(ChatSelectStateLoaded value)? loaded,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (loading != null) {
|
||||
return loading(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ChatSelectStateLoading implements ChatSelectState {
|
||||
const factory ChatSelectStateLoading() = _$ChatSelectStateLoadingImpl;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ChatSelectStateErrorImplCopyWith<$Res> {
|
||||
factory _$$ChatSelectStateErrorImplCopyWith(_$ChatSelectStateErrorImpl value,
|
||||
$Res Function(_$ChatSelectStateErrorImpl) then) =
|
||||
__$$ChatSelectStateErrorImplCopyWithImpl<$Res>;
|
||||
@useResult
|
||||
$Res call({String errorMessage});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ChatSelectStateErrorImplCopyWithImpl<$Res>
|
||||
extends _$ChatSelectStateCopyWithImpl<$Res, _$ChatSelectStateErrorImpl>
|
||||
implements _$$ChatSelectStateErrorImplCopyWith<$Res> {
|
||||
__$$ChatSelectStateErrorImplCopyWithImpl(_$ChatSelectStateErrorImpl _value,
|
||||
$Res Function(_$ChatSelectStateErrorImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of ChatSelectState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? errorMessage = null,
|
||||
}) {
|
||||
return _then(_$ChatSelectStateErrorImpl(
|
||||
errorMessage: null == errorMessage
|
||||
? _value.errorMessage
|
||||
: errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ChatSelectStateErrorImpl implements ChatSelectStateError {
|
||||
const _$ChatSelectStateErrorImpl({required this.errorMessage});
|
||||
|
||||
@override
|
||||
final String errorMessage;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChatSelectState.error(errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ChatSelectStateErrorImpl &&
|
||||
(identical(other.errorMessage, errorMessage) ||
|
||||
other.errorMessage == errorMessage));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, errorMessage);
|
||||
|
||||
/// Create a copy of ChatSelectState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ChatSelectStateErrorImplCopyWith<_$ChatSelectStateErrorImpl>
|
||||
get copyWith =>
|
||||
__$$ChatSelectStateErrorImplCopyWithImpl<_$ChatSelectStateErrorImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function() $default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(String errorMessage) error,
|
||||
required TResult Function(List<ChatRoom> chatRooms) loaded,
|
||||
}) {
|
||||
return error(errorMessage);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult? Function()? $default, {
|
||||
TResult? Function()? loading,
|
||||
TResult? Function(String errorMessage)? error,
|
||||
TResult? Function(List<ChatRoom> chatRooms)? loaded,
|
||||
}) {
|
||||
return error?.call(errorMessage);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function()? $default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(String errorMessage)? error,
|
||||
TResult Function(List<ChatRoom> chatRooms)? loaded,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (error != null) {
|
||||
return error(errorMessage);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>(
|
||||
TResult Function(_ChatSelectState value) $default, {
|
||||
required TResult Function(ChatSelectStateLoading value) loading,
|
||||
required TResult Function(ChatSelectStateError value) error,
|
||||
required TResult Function(ChatSelectStateLoaded value) loaded,
|
||||
}) {
|
||||
return error(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>(
|
||||
TResult? Function(_ChatSelectState value)? $default, {
|
||||
TResult? Function(ChatSelectStateLoading value)? loading,
|
||||
TResult? Function(ChatSelectStateError value)? error,
|
||||
TResult? Function(ChatSelectStateLoaded value)? loaded,
|
||||
}) {
|
||||
return error?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>(
|
||||
TResult Function(_ChatSelectState value)? $default, {
|
||||
TResult Function(ChatSelectStateLoading value)? loading,
|
||||
TResult Function(ChatSelectStateError value)? error,
|
||||
TResult Function(ChatSelectStateLoaded value)? loaded,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (error != null) {
|
||||
return error(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ChatSelectStateError implements ChatSelectState {
|
||||
const factory ChatSelectStateError({required final String errorMessage}) =
|
||||
_$ChatSelectStateErrorImpl;
|
||||
|
||||
String get errorMessage;
|
||||
|
||||
/// Create a copy of ChatSelectState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ChatSelectStateErrorImplCopyWith<_$ChatSelectStateErrorImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ChatSelectStateLoadedImplCopyWith<$Res> {
|
||||
factory _$$ChatSelectStateLoadedImplCopyWith(
|
||||
_$ChatSelectStateLoadedImpl value,
|
||||
$Res Function(_$ChatSelectStateLoadedImpl) then) =
|
||||
__$$ChatSelectStateLoadedImplCopyWithImpl<$Res>;
|
||||
@useResult
|
||||
$Res call({List<ChatRoom> chatRooms});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ChatSelectStateLoadedImplCopyWithImpl<$Res>
|
||||
extends _$ChatSelectStateCopyWithImpl<$Res, _$ChatSelectStateLoadedImpl>
|
||||
implements _$$ChatSelectStateLoadedImplCopyWith<$Res> {
|
||||
__$$ChatSelectStateLoadedImplCopyWithImpl(_$ChatSelectStateLoadedImpl _value,
|
||||
$Res Function(_$ChatSelectStateLoadedImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of ChatSelectState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? chatRooms = null,
|
||||
}) {
|
||||
return _then(_$ChatSelectStateLoadedImpl(
|
||||
chatRooms: null == chatRooms
|
||||
? _value._chatRooms
|
||||
: chatRooms // ignore: cast_nullable_to_non_nullable
|
||||
as List<ChatRoom>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ChatSelectStateLoadedImpl implements ChatSelectStateLoaded {
|
||||
const _$ChatSelectStateLoadedImpl({required final List<ChatRoom> chatRooms})
|
||||
: _chatRooms = chatRooms;
|
||||
|
||||
final List<ChatRoom> _chatRooms;
|
||||
@override
|
||||
List<ChatRoom> get chatRooms {
|
||||
if (_chatRooms is EqualUnmodifiableListView) return _chatRooms;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_chatRooms);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChatSelectState.loaded(chatRooms: $chatRooms)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ChatSelectStateLoadedImpl &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._chatRooms, _chatRooms));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, const DeepCollectionEquality().hash(_chatRooms));
|
||||
|
||||
/// Create a copy of ChatSelectState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ChatSelectStateLoadedImplCopyWith<_$ChatSelectStateLoadedImpl>
|
||||
get copyWith => __$$ChatSelectStateLoadedImplCopyWithImpl<
|
||||
_$ChatSelectStateLoadedImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function() $default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(String errorMessage) error,
|
||||
required TResult Function(List<ChatRoom> chatRooms) loaded,
|
||||
}) {
|
||||
return loaded(chatRooms);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult? Function()? $default, {
|
||||
TResult? Function()? loading,
|
||||
TResult? Function(String errorMessage)? error,
|
||||
TResult? Function(List<ChatRoom> chatRooms)? loaded,
|
||||
}) {
|
||||
return loaded?.call(chatRooms);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function()? $default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(String errorMessage)? error,
|
||||
TResult Function(List<ChatRoom> chatRooms)? loaded,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (loaded != null) {
|
||||
return loaded(chatRooms);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>(
|
||||
TResult Function(_ChatSelectState value) $default, {
|
||||
required TResult Function(ChatSelectStateLoading value) loading,
|
||||
required TResult Function(ChatSelectStateError value) error,
|
||||
required TResult Function(ChatSelectStateLoaded value) loaded,
|
||||
}) {
|
||||
return loaded(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>(
|
||||
TResult? Function(_ChatSelectState value)? $default, {
|
||||
TResult? Function(ChatSelectStateLoading value)? loading,
|
||||
TResult? Function(ChatSelectStateError value)? error,
|
||||
TResult? Function(ChatSelectStateLoaded value)? loaded,
|
||||
}) {
|
||||
return loaded?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>(
|
||||
TResult Function(_ChatSelectState value)? $default, {
|
||||
TResult Function(ChatSelectStateLoading value)? loading,
|
||||
TResult Function(ChatSelectStateError value)? error,
|
||||
TResult Function(ChatSelectStateLoaded value)? loaded,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (loaded != null) {
|
||||
return loaded(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ChatSelectStateLoaded implements ChatSelectState {
|
||||
const factory ChatSelectStateLoaded(
|
||||
{required final List<ChatRoom> chatRooms}) = _$ChatSelectStateLoadedImpl;
|
||||
|
||||
List<ChatRoom> get chatRooms;
|
||||
|
||||
/// Create a copy of ChatSelectState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ChatSelectStateLoadedImplCopyWith<_$ChatSelectStateLoadedImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
144
lib/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart
Normal file
144
lib/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart
Normal file
@@ -0,0 +1,144 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:eatvio_app/core/enums/base_state.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/presentation/bloc/chats_cubit/chats_state.dart';
|
||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class ChatsCubit extends Cubit<ChatsState> {
|
||||
final ChatRepository chatRepository;
|
||||
final ProfileRepository profileRepository;
|
||||
final WsManager wsManager;
|
||||
late StreamSubscription? newIncomingMessagesSubscription;
|
||||
late StreamSubscription? newLastMessageSubscription;
|
||||
|
||||
ChatsCubit({
|
||||
required this.chatRepository,
|
||||
required this.wsManager,
|
||||
required this.profileRepository,
|
||||
}) : super(ChatsState()) {
|
||||
newIncomingMessagesSubscription = wsManager.newMessagesStream?.listen((event) {
|
||||
_handleNewMessageViaSocket(event.data);
|
||||
});
|
||||
|
||||
newLastMessageSubscription = chatRepository.newUserMessagesStream.listen((message) {
|
||||
_setNewLastMessage(message);
|
||||
});
|
||||
}
|
||||
String? _query;
|
||||
Future<void> getChatRoomsWithLoading({String? query}) async {
|
||||
_query = query;
|
||||
emit(state.copyWith(state: BaseState.loading));
|
||||
try {
|
||||
final chatRooms = await chatRepository.getChatRoomPreviews(query: query);
|
||||
final unreadChats = chatRooms.where(
|
||||
(element) => element.unreadMessagesCount > 0,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
chatRooms: chatRooms,
|
||||
chatsWithUnreadMessagesCount: unreadChats.length,
|
||||
state: BaseState.success,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(errorMessage: e.toString(), state: BaseState.error),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getChatRooms({String? query}) async {
|
||||
_query = query;
|
||||
try {
|
||||
final chatRooms = await chatRepository.getChatRoomPreviews(query: query);
|
||||
final unreadChats = chatRooms.where(
|
||||
(element) => element.unreadMessagesCount > 0,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
chatRooms: chatRooms,
|
||||
chatsWithUnreadMessagesCount: unreadChats.length,
|
||||
state: BaseState.success,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(errorMessage: e.toString(), state: BaseState.error),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void reduceUnreadMessagesCount({required String chatId, required int count}) {
|
||||
bool isChatRead = false;
|
||||
final updatedChatRooms = state.chatRooms.map((room) {
|
||||
if (room.id == chatId) {
|
||||
int unreadMessagesCount = room.unreadMessagesCount - count;
|
||||
if (unreadMessagesCount <= 0) {
|
||||
unreadMessagesCount = 0;
|
||||
isChatRead = true;
|
||||
}
|
||||
return room.copyWith(unreadMessagesCount: unreadMessagesCount);
|
||||
}
|
||||
return room;
|
||||
}).toList();
|
||||
emit(
|
||||
state.copyWith(
|
||||
chatRooms: updatedChatRooms,
|
||||
chatsWithUnreadMessagesCount: isChatRead ? state.chatsWithUnreadMessagesCount - 1 : state.chatsWithUnreadMessagesCount,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleNewMessageViaSocket(dynamic data) async {
|
||||
final json = jsonDecode(data);
|
||||
final message = ChatMessageModel.fromJson(json['message']);
|
||||
|
||||
final currentChatRooms = [...state.chatRooms];
|
||||
|
||||
bool wasChanged = false;
|
||||
bool willIncrementUnreadChats = false;
|
||||
final updatedChatRooms = currentChatRooms.map((chatRoom) {
|
||||
if (chatRoom.id == message.roomId) {
|
||||
wasChanged = true;
|
||||
if (chatRoom.unreadMessagesCount == 0) {
|
||||
willIncrementUnreadChats = true;
|
||||
}
|
||||
return chatRoom.copyWith(lastMessage: message, unreadMessagesCount: chatRoom.unreadMessagesCount + 1);
|
||||
}
|
||||
return chatRoom;
|
||||
}).toList();
|
||||
|
||||
if (!wasChanged && _query == null) {
|
||||
getChatRooms();
|
||||
} else {
|
||||
emit(
|
||||
state.copyWith(
|
||||
chatRooms: updatedChatRooms,
|
||||
chatsWithUnreadMessagesCount: willIncrementUnreadChats ? state.chatsWithUnreadMessagesCount + 1 : state.chatsWithUnreadMessagesCount,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _setNewLastMessage(ChatMessageModel message) {
|
||||
final chats = state.chatRooms.map((chat) {
|
||||
if (chat.id == message.roomId) {
|
||||
return chat.copyWith(lastMessage: message);
|
||||
}
|
||||
return chat;
|
||||
}).toList();
|
||||
|
||||
emit(state.copyWith(chatRooms: chats));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
newIncomingMessagesSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:eatvio_app/core/enums/base_state.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_room_preview_model.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'chats_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class ChatsState with _$ChatsState {
|
||||
|
||||
factory ChatsState({
|
||||
@Default(BaseState.initial) BaseState state,
|
||||
@Default(0) int chatsWithUnreadMessagesCount,
|
||||
@Default([]) List<ChatRoom> chatRooms,
|
||||
@Default(null) String? errorMessage,
|
||||
}) = _ChatsState;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'chats_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ChatsState {
|
||||
BaseState get state => throw _privateConstructorUsedError;
|
||||
int get chatsWithUnreadMessagesCount => throw _privateConstructorUsedError;
|
||||
List<ChatRoom> get chatRooms => throw _privateConstructorUsedError;
|
||||
String? get errorMessage => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of ChatsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ChatsStateCopyWith<ChatsState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ChatsStateCopyWith<$Res> {
|
||||
factory $ChatsStateCopyWith(
|
||||
ChatsState value, $Res Function(ChatsState) then) =
|
||||
_$ChatsStateCopyWithImpl<$Res, ChatsState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{BaseState state,
|
||||
int chatsWithUnreadMessagesCount,
|
||||
List<ChatRoom> chatRooms,
|
||||
String? errorMessage});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ChatsStateCopyWithImpl<$Res, $Val extends ChatsState>
|
||||
implements $ChatsStateCopyWith<$Res> {
|
||||
_$ChatsStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ChatsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? state = null,
|
||||
Object? chatsWithUnreadMessagesCount = null,
|
||||
Object? chatRooms = null,
|
||||
Object? errorMessage = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
state: null == state
|
||||
? _value.state
|
||||
: state // ignore: cast_nullable_to_non_nullable
|
||||
as BaseState,
|
||||
chatsWithUnreadMessagesCount: null == chatsWithUnreadMessagesCount
|
||||
? _value.chatsWithUnreadMessagesCount
|
||||
: chatsWithUnreadMessagesCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
chatRooms: null == chatRooms
|
||||
? _value.chatRooms
|
||||
: chatRooms // ignore: cast_nullable_to_non_nullable
|
||||
as List<ChatRoom>,
|
||||
errorMessage: freezed == errorMessage
|
||||
? _value.errorMessage
|
||||
: errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ChatsStateImplCopyWith<$Res>
|
||||
implements $ChatsStateCopyWith<$Res> {
|
||||
factory _$$ChatsStateImplCopyWith(
|
||||
_$ChatsStateImpl value, $Res Function(_$ChatsStateImpl) then) =
|
||||
__$$ChatsStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{BaseState state,
|
||||
int chatsWithUnreadMessagesCount,
|
||||
List<ChatRoom> chatRooms,
|
||||
String? errorMessage});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ChatsStateImplCopyWithImpl<$Res>
|
||||
extends _$ChatsStateCopyWithImpl<$Res, _$ChatsStateImpl>
|
||||
implements _$$ChatsStateImplCopyWith<$Res> {
|
||||
__$$ChatsStateImplCopyWithImpl(
|
||||
_$ChatsStateImpl _value, $Res Function(_$ChatsStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of ChatsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? state = null,
|
||||
Object? chatsWithUnreadMessagesCount = null,
|
||||
Object? chatRooms = null,
|
||||
Object? errorMessage = freezed,
|
||||
}) {
|
||||
return _then(_$ChatsStateImpl(
|
||||
state: null == state
|
||||
? _value.state
|
||||
: state // ignore: cast_nullable_to_non_nullable
|
||||
as BaseState,
|
||||
chatsWithUnreadMessagesCount: null == chatsWithUnreadMessagesCount
|
||||
? _value.chatsWithUnreadMessagesCount
|
||||
: chatsWithUnreadMessagesCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
chatRooms: null == chatRooms
|
||||
? _value._chatRooms
|
||||
: chatRooms // ignore: cast_nullable_to_non_nullable
|
||||
as List<ChatRoom>,
|
||||
errorMessage: freezed == errorMessage
|
||||
? _value.errorMessage
|
||||
: errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ChatsStateImpl implements _ChatsState {
|
||||
_$ChatsStateImpl(
|
||||
{this.state = BaseState.initial,
|
||||
this.chatsWithUnreadMessagesCount = 0,
|
||||
final List<ChatRoom> chatRooms = const [],
|
||||
this.errorMessage = null})
|
||||
: _chatRooms = chatRooms;
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final BaseState state;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int chatsWithUnreadMessagesCount;
|
||||
final List<ChatRoom> _chatRooms;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<ChatRoom> get chatRooms {
|
||||
if (_chatRooms is EqualUnmodifiableListView) return _chatRooms;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_chatRooms);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final String? errorMessage;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChatsState(state: $state, chatsWithUnreadMessagesCount: $chatsWithUnreadMessagesCount, chatRooms: $chatRooms, errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ChatsStateImpl &&
|
||||
(identical(other.state, state) || other.state == state) &&
|
||||
(identical(other.chatsWithUnreadMessagesCount,
|
||||
chatsWithUnreadMessagesCount) ||
|
||||
other.chatsWithUnreadMessagesCount ==
|
||||
chatsWithUnreadMessagesCount) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._chatRooms, _chatRooms) &&
|
||||
(identical(other.errorMessage, errorMessage) ||
|
||||
other.errorMessage == errorMessage));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
state,
|
||||
chatsWithUnreadMessagesCount,
|
||||
const DeepCollectionEquality().hash(_chatRooms),
|
||||
errorMessage);
|
||||
|
||||
/// Create a copy of ChatsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ChatsStateImplCopyWith<_$ChatsStateImpl> get copyWith =>
|
||||
__$$ChatsStateImplCopyWithImpl<_$ChatsStateImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _ChatsState implements ChatsState {
|
||||
factory _ChatsState(
|
||||
{final BaseState state,
|
||||
final int chatsWithUnreadMessagesCount,
|
||||
final List<ChatRoom> chatRooms,
|
||||
final String? errorMessage}) = _$ChatsStateImpl;
|
||||
|
||||
@override
|
||||
BaseState get state;
|
||||
@override
|
||||
int get chatsWithUnreadMessagesCount;
|
||||
@override
|
||||
List<ChatRoom> get chatRooms;
|
||||
@override
|
||||
String? get errorMessage;
|
||||
|
||||
/// Create a copy of ChatsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ChatsStateImplCopyWith<_$ChatsStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||
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_state.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.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/theme/custom_text_style.dart';
|
||||
@@ -12,12 +15,14 @@ import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
|
||||
class ChatScreen extends StatefulWidget {
|
||||
final String chatId;
|
||||
|
||||
final ChatsCubit chatsCubit;
|
||||
const ChatScreen({
|
||||
required this.chatId,
|
||||
required this.chatsCubit,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@@ -29,6 +34,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
final scrollController = ScrollController();
|
||||
final TextEditingController textEditingController = TextEditingController();
|
||||
final ChatRoomCubit chatRoomCubit = sl<ChatRoomCubit>();
|
||||
final List<({int id, int index})> unreadMessagesIds = [];
|
||||
final List<({int id, int index})> unreadMessagesIdsCopy = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -60,9 +67,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
} else if (state is ChatRoomStateError) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: Center(
|
||||
child: Text(state.errorMessage),
|
||||
),
|
||||
body: DataErrorWidget(onReload: () => chatRoomCubit.initChat(widget.chatId)),
|
||||
);
|
||||
} else {
|
||||
return Scaffold(
|
||||
@@ -152,25 +157,65 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
(context, index) {
|
||||
if (index < state.chatMessages.length) {
|
||||
if ((index + 1 != state.chatMessages.length &&
|
||||
DateTime.parse(state.chatMessages[index].updatedAt).day != DateTime.parse(state.chatMessages[index + 1].updatedAt).day) ||
|
||||
state.chatMessages[index].updatedAt.day != state.chatMessages[index + 1].updatedAt.day) ||
|
||||
index + 1 == state.chatMessages.length) {
|
||||
final isCurrentUserAuthor = state.chatRoom.users['to_chat_user']!.id != state.chatMessages[index].authorId;
|
||||
return Column(
|
||||
children: [
|
||||
_messageDate(state.chatMessages[index].updatedAt),
|
||||
ChatBubbleWidget(
|
||||
sendTime: state.chatMessages[index].updatedAt,
|
||||
text: state.chatMessages[index].data.trim(),
|
||||
isCurrentUser: state.chatRoom.users['to_chat_user']!.id != state.chatMessages[index].authorId,
|
||||
mediaData: state.chatMessages[index].mediaData ?? [],
|
||||
VisibilityDetector(
|
||||
key: Key(state.chatMessages[index].id.toString()),
|
||||
onVisibilityChanged: (VisibilityInfo info) {
|
||||
if (!state.chatMessages[index].isRead && !isCurrentUserAuthor && info.visibleFraction > 0) {
|
||||
unreadMessagesIdsCopy.add((id: state.chatMessages[index].id, index: index));
|
||||
EasyDebounce.debounce(
|
||||
'mark_as_read',
|
||||
Duration(milliseconds: 500),
|
||||
() async {
|
||||
final result = await context.read<ChatRoomCubit>().markMessagesAsRead(state.chatRoom.id, unreadMessagesIdsCopy);
|
||||
if (!result) return;
|
||||
widget.chatsCubit.reduceUnreadMessagesCount(chatId: state.chatRoom.id, count: unreadMessagesIdsCopy.length);
|
||||
unreadMessagesIdsCopy.clear();
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
child: ChatBubbleWidget(
|
||||
isRead: state.chatMessages[index].isRead,
|
||||
sendTime: state.chatMessages[index].updatedAt,
|
||||
text: state.chatMessages[index].data.trim(),
|
||||
isCurrentUser: isCurrentUserAuthor,
|
||||
mediaData: state.chatMessages[index].mediaData ?? [],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return ChatBubbleWidget(
|
||||
sendTime: state.chatMessages[index].updatedAt,
|
||||
text: state.chatMessages[index].data.trim(),
|
||||
isCurrentUser: state.chatRoom.users['to_chat_user']!.id != state.chatMessages[index].authorId,
|
||||
mediaData: state.chatMessages[index].mediaData ?? [],
|
||||
final isCurrentUserAuthor = state.chatRoom.users['to_chat_user']!.id != state.chatMessages[index].authorId;
|
||||
return VisibilityDetector(
|
||||
key: Key(state.chatMessages[index].id.toString()),
|
||||
onVisibilityChanged: (VisibilityInfo info) {
|
||||
if (!state.chatMessages[index].isRead && !isCurrentUserAuthor && info.visibleFraction > 0) {
|
||||
unreadMessagesIds.add((id: state.chatMessages[index].id, index: index));
|
||||
EasyDebounce.debounce(
|
||||
'mark_as_read',
|
||||
Duration(milliseconds: 500),
|
||||
() async {
|
||||
final result = await context.read<ChatRoomCubit>().markMessagesAsRead(state.chatRoom.id, unreadMessagesIds);
|
||||
if (!result) return;
|
||||
widget.chatsCubit.reduceUnreadMessagesCount(chatId: state.chatRoom.id, count: unreadMessagesIds.length);
|
||||
unreadMessagesIds.clear();
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
child: ChatBubbleWidget(
|
||||
isRead: state.chatMessages[index].isRead,
|
||||
sendTime: state.chatMessages[index].updatedAt,
|
||||
text: state.chatMessages[index].data.trim(),
|
||||
isCurrentUser: isCurrentUserAuthor,
|
||||
mediaData: state.chatMessages[index].mediaData ?? [],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
@@ -222,7 +267,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
|
||||
if (result != null) {
|
||||
List<File> files = result.paths.map((path) => File(path!)).toList();
|
||||
chatRoomCubit.sendMessage(
|
||||
await chatRoomCubit.sendMessage(
|
||||
chatId: widget.chatId,
|
||||
files: files,
|
||||
);
|
||||
@@ -329,13 +374,13 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
_messageDate(String date) {
|
||||
_messageDate(DateTime date) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
padding: const EdgeInsets.only(top: 32),
|
||||
child: Text(
|
||||
DateFormat.yMMMMd('RU').format(
|
||||
DateTime.parse(date),
|
||||
date,
|
||||
),
|
||||
style: const TextStyle(
|
||||
height: 1,
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_select_cubit/chat_select_state.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/chat_entry_widget.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/chat_search_bar_widget.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class ChatSelectScreen extends StatefulWidget {
|
||||
const ChatSelectScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ChatSelectScreen> createState() => _ChatSelectScreenState();
|
||||
}
|
||||
|
||||
class _ChatSelectScreenState extends State<ChatSelectScreen> {
|
||||
final ChatSelectCubit chatSelectCubit = sl<ChatSelectCubit>();
|
||||
final searchController = TextEditingController();
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<ChatSelectCubit>(
|
||||
create: (context) => chatSelectCubit..getChatRooms(),
|
||||
child: BlocBuilder<ChatSelectCubit, ChatSelectState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
toolbarHeight: 0,
|
||||
),
|
||||
body: NestedScrollView(
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
sliver: SliverAppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
stretchTriggerOffset: 1,
|
||||
scrolledUnderElevation: 0,
|
||||
floating: true,
|
||||
snap: true,
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: ChatSearchBarWidget(
|
||||
onSuffixIconTap: () => context.read<ChatSelectCubit>().getChatRooms(),
|
||||
onChanged: (query) {
|
||||
if (query.length > 2) {
|
||||
EasyDebounce.debounce(
|
||||
'searchChatRoom',
|
||||
Duration(milliseconds: 300),
|
||||
() => context.read<ChatSelectCubit>().getChatRooms(query: query),
|
||||
);
|
||||
} else {
|
||||
context.read<ChatSelectCubit>().getChatRooms();
|
||||
}
|
||||
},
|
||||
controller: searchController,
|
||||
fillColor: Color(0xFFF0F2F5),
|
||||
hintText: "Поиск",
|
||||
hintStyle: TextStyle(
|
||||
color: Color(0xFF7B8794),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
body: stateBuilder(state),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
stateBuilder(state) {
|
||||
if (state is ChatSelectStateLoading) {
|
||||
return Scaffold(
|
||||
body: const Loader(),
|
||||
);
|
||||
} else if (state is ChatSelectStateLoaded) {
|
||||
return _buildChatList(context, state);
|
||||
} else if (state is ChatSelectStateError) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(child: Text(state.errorMessage)),
|
||||
);
|
||||
} else {
|
||||
return Scaffold(
|
||||
body: const Loader(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_buildChatList(BuildContext context, ChatSelectStateLoaded state) {
|
||||
return ListView.separated(
|
||||
padding: EdgeInsets.all(16),
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(
|
||||
height: 8,
|
||||
);
|
||||
},
|
||||
itemCount: state.chatRooms.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ChatEntryWidget(chatRoom: state.chatRooms[index]);
|
||||
},
|
||||
);
|
||||
}
|
||||
109
lib/features/chat/presentation/pages/chats_screen.dart
Normal file
109
lib/features/chat/presentation/pages/chats_screen.dart
Normal file
@@ -0,0 +1,109 @@
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/enums/base_state.dart';
|
||||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.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_state.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/chat_entry_widget.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/chat_search_bar_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class ChatsScreen extends StatefulWidget {
|
||||
const ChatsScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ChatsScreen> createState() => _ChatsScreenState();
|
||||
}
|
||||
|
||||
class _ChatsScreenState extends State<ChatsScreen> {
|
||||
final searchController = TextEditingController();
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ChatsCubit, ChatsState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
toolbarHeight: 0,
|
||||
),
|
||||
body: NestedScrollView(
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
sliver: SliverAppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
stretchTriggerOffset: 1,
|
||||
scrolledUnderElevation: 0,
|
||||
floating: true,
|
||||
snap: true,
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: ChatSearchBarWidget(
|
||||
onSuffixIconTap: () => context.read<ChatsCubit>().getChatRooms(),
|
||||
onChanged: (query) {
|
||||
if (query.length > 2) {
|
||||
EasyDebounce.debounce(
|
||||
'searchChatRoom',
|
||||
Duration(milliseconds: 300),
|
||||
() => context.read<ChatsCubit>().getChatRooms(query: query),
|
||||
);
|
||||
} else {
|
||||
context.read<ChatsCubit>().getChatRooms();
|
||||
}
|
||||
},
|
||||
controller: searchController,
|
||||
fillColor: Color(0xFFF0F2F5),
|
||||
hintText: "Поиск",
|
||||
hintStyle: TextStyle(
|
||||
color: Color(0xFF7B8794),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
body: stateBuilder(state),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
stateBuilder(ChatsState state) {
|
||||
if (state.state == BaseState.loading) {
|
||||
return Scaffold(
|
||||
body: const Loader(),
|
||||
);
|
||||
} else if (state.state == BaseState.success) {
|
||||
return _buildChatList(context, state);
|
||||
} else if (state.state == BaseState.error) {
|
||||
return Scaffold(
|
||||
body: DataErrorWidget(onReload: () => context.read<ChatsCubit>().getChatRooms()),
|
||||
);
|
||||
} else {
|
||||
return Scaffold(
|
||||
body: const Loader(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_buildChatList(BuildContext context, ChatsState state) {
|
||||
return ListView.separated(
|
||||
padding: EdgeInsets.all(16),
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(
|
||||
height: 8,
|
||||
);
|
||||
},
|
||||
itemCount: state.chatRooms.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ChatEntryWidget(chatRoom: state.chatRooms[index]);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,118 +15,146 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
required this.text,
|
||||
required this.isCurrentUser,
|
||||
required this.mediaData,
|
||||
required this.isRead,
|
||||
});
|
||||
|
||||
final String sendTime;
|
||||
final DateTime sendTime;
|
||||
final String text;
|
||||
final bool isCurrentUser;
|
||||
final bool isRead;
|
||||
final List<dynamic> mediaData;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(isCurrentUser ? 64 : 16, 4, isCurrentUser ? 16 : 64, 4),
|
||||
child: Align(
|
||||
alignment: isCurrentUser ? Alignment.centerRight : Alignment.centerLeft,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: isCurrentUser ? appTheme.gray50 : appTheme.gray60004,
|
||||
borderRadius: mediaData.length > 0
|
||||
? BorderRadius.circular(24)
|
||||
: BorderRadius.horizontal(
|
||||
left: isCurrentUser ? Radius.circular(32) : Radius.circular(16),
|
||||
right: isCurrentUser ? Radius.circular(16) : Radius.circular(24),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: mediaData.length > 0 ? EdgeInsets.all(8) : EdgeInsets.only(top: 8, bottom: 8, left: isCurrentUser ? 16 : 8, right: isCurrentUser ? 8 : 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: isCurrentUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (mediaData.isNotEmpty)
|
||||
CustomScrollView(
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverList.separated(
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
if (!mediaData[index].toString().contains('.png') &&
|
||||
!mediaData[index].toString().contains('.jpg') &&
|
||||
!mediaData[index].toString().contains('.jpeg')) {
|
||||
return ListTile(
|
||||
contentPadding: EdgeInsets.all(4),
|
||||
horizontalTitleGap: 8,
|
||||
onTap: () async {
|
||||
if (!await launchUrl(Uri.parse(baseUrl + '/storage/' + mediaData[index].toString()))) {
|
||||
throw Exception('Could not launch ${mediaData[index].toString()}');
|
||||
}
|
||||
},
|
||||
leading: Icon(
|
||||
Icons.file_copy_outlined,
|
||||
color: Colors.white,
|
||||
),
|
||||
trailing: Icon(
|
||||
Icons.download_outlined,
|
||||
color: Colors.white,
|
||||
),
|
||||
title: Text(
|
||||
mediaData[index].toString().replaceAll('chat_files/', ''),
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
context.pushTransparentRoute(
|
||||
PhotoViewScreen(
|
||||
imageUrls: mediaData.map((e) => baseUrl + '/storage/' + e.toString()).toList(),
|
||||
index: index,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Hero(
|
||||
tag: baseUrl + '/storage/' + '${mediaData[index]}',
|
||||
transitionOnUserGestures: true,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: baseUrl + '/storage/' + '${mediaData[index]}',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) => const SizedBox(height: 8),
|
||||
itemCount: mediaData.length,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (mediaData.length > 0 && text.isNotEmpty) SizedBox(height: 8),
|
||||
if (text.isNotEmpty)
|
||||
Text(
|
||||
text,
|
||||
style: TextStyles.textFS13FW400.copyWith(color: appTheme.black, fontSize: 16),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: mediaData.length > 0 ? const EdgeInsets.only(right: 8, top: 4, left: 8) : EdgeInsets.zero,
|
||||
child: Text(
|
||||
DateTime.parse(sendTime).format('hh:mm'),
|
||||
style: TextStyle(
|
||||
color: Color(0xFF7B8794),
|
||||
height: 1,
|
||||
fontSize: 10,
|
||||
),
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
return Container(
|
||||
width: size.width,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(isCurrentUser ? 64 : 16, 10, isCurrentUser ? 16 : 64, 4),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: isCurrentUser ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||
children: [
|
||||
if (isCurrentUser)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 4, bottom: 4),
|
||||
child: Text(
|
||||
sendTime.format('hh:mm'),
|
||||
style: TextStyle(
|
||||
color: Color(0xFF7B8794),
|
||||
height: 1,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
constraints: BoxConstraints(maxWidth: size.width * 0.7),
|
||||
decoration: BoxDecoration(
|
||||
color: isCurrentUser ? appTheme.gray50 : appTheme.gray60004,
|
||||
borderRadius: mediaData.length > 0
|
||||
? BorderRadius.circular(24)
|
||||
: BorderRadius.horizontal(
|
||||
left: isCurrentUser ? Radius.circular(32) : Radius.circular(16),
|
||||
right: isCurrentUser ? Radius.circular(16) : Radius.circular(24),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding:
|
||||
mediaData.isNotEmpty ? EdgeInsets.all(8) : EdgeInsets.only(top: 8, bottom: 8, left: isCurrentUser ? 16 : 8, right: isCurrentUser ? 8 : 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: isCurrentUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (mediaData.isNotEmpty)
|
||||
SizedBox(
|
||||
width: size.width * 0.7,
|
||||
child: CustomScrollView(
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverList.separated(
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
if (!mediaData[index].toString().contains('.png') &&
|
||||
!mediaData[index].toString().contains('.jpg') &&
|
||||
!mediaData[index].toString().contains('.jpeg')) {
|
||||
return ListTile(
|
||||
contentPadding: EdgeInsets.all(4),
|
||||
horizontalTitleGap: 8,
|
||||
onTap: () async {
|
||||
if (!await launchUrl(Uri.parse(baseUrl + '/storage/' + mediaData[index].toString()))) {
|
||||
throw Exception('Could not launch ${mediaData[index].toString()}');
|
||||
}
|
||||
},
|
||||
leading: Icon(
|
||||
Icons.file_copy_outlined,
|
||||
color: Colors.white,
|
||||
),
|
||||
trailing: Icon(
|
||||
Icons.download_outlined,
|
||||
color: Colors.white,
|
||||
),
|
||||
title: Text(
|
||||
mediaData[index].toString().replaceAll('chat_files/', ''),
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
context.pushTransparentRoute(
|
||||
PhotoViewScreen(
|
||||
imageUrls: mediaData.map((e) => baseUrl + '/storage/' + e.toString()).toList(),
|
||||
index: index,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Hero(
|
||||
tag: baseUrl + '/storage/' + '${mediaData[index]}',
|
||||
transitionOnUserGestures: true,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: baseUrl + '/storage/' + '${mediaData[index]}',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) => const SizedBox(height: 8),
|
||||
itemCount: mediaData.length,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (mediaData.isNotEmpty && text.isNotEmpty) SizedBox(height: 8),
|
||||
if (text.isNotEmpty)
|
||||
Text(
|
||||
text,
|
||||
style: TextStyles.textFS13FW400.copyWith(color: appTheme.black, fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isCurrentUser)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 4, bottom: 4),
|
||||
child: Text(
|
||||
sendTime.format('hh:mm'),
|
||||
style: TextStyle(
|
||||
color: Color(0xFF7B8794),
|
||||
height: 1,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,7 +2,9 @@ import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_room_preview_model.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/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class ChatEntryWidget extends StatefulWidget {
|
||||
@@ -18,26 +20,19 @@ class ChatEntryWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ChatEntryWidgetState extends State<ChatEntryWidget> {
|
||||
LastMessageClass? lastMessage;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.chatRoom.lastMessage is Map<String, dynamic>) {
|
||||
lastMessage = LastMessageClass.fromJson(widget.chatRoom.lastMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
onTap: () => GoRouter.of(context).pushNamed(
|
||||
'chatScreen',
|
||||
extra: widget.chatRoom.id.toString(),
|
||||
extra: {
|
||||
'chatId': widget.chatRoom.id.toString(),
|
||||
'chatsCubit': context.read<ChatsCubit>(),
|
||||
},
|
||||
),
|
||||
child: Ink(
|
||||
padding: EdgeInsets.all(16),
|
||||
padding: EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: Color(0xFFF6FAFF),
|
||||
@@ -54,39 +49,54 @@ class _ChatEntryWidgetState extends State<ChatEntryWidget> {
|
||||
backgroundColor: Colors.white,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
widget.chatRoom.users.toChatUser.name.length <= 20
|
||||
? widget.chatRoom.users.toChatUser.name
|
||||
: widget.chatRoom.users.toChatUser.name.substring(0, 20) + '...',
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
Spacer(),
|
||||
if (lastMessage != null)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 8),
|
||||
child: Text(
|
||||
lastMessage!.updatedAt.format('dd MMM', 'ru'),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
if (lastMessage != null)
|
||||
Text(
|
||||
lastMessage!.data.toString().trim(),
|
||||
maxLines: 2,
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.chatRoom.users.toChatUser.name.length <= 20
|
||||
? widget.chatRoom.users.toChatUser.name
|
||||
: widget.chatRoom.users.toChatUser.name.substring(0, 20) + '...',
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
if (widget.chatRoom.lastMessage != null)
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.5,
|
||||
child: Text(
|
||||
widget.chatRoom.lastMessage!.data.toString().trim(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: CustomTextStyles.bodyMediumBluegray800,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Spacer(),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (widget.chatRoom.lastMessage != null)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 8),
|
||||
child: Text(
|
||||
widget.chatRoom.lastMessage!.updatedAt.format('dd MMM', 'ru'),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
if (widget.chatRoom.unreadMessagesCount > 0)
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
child: Text(
|
||||
widget.chatRoom.unreadMessagesCount.toString(),
|
||||
style: TextStyles.textXSSemibold.copyWith(color: appTheme.whiteA700),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_messages_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_room_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_room_preview_model.dart';
|
||||
@@ -10,10 +12,18 @@ import 'package:http_parser/http_parser.dart';
|
||||
class ChatRepository {
|
||||
final ChatService chatService;
|
||||
|
||||
final _newUserMessagesController = StreamController<ChatMessageModel>();
|
||||
|
||||
Stream<ChatMessageModel> get newUserMessagesStream => _newUserMessagesController.stream;
|
||||
|
||||
ChatRepository({
|
||||
required this.chatService,
|
||||
});
|
||||
|
||||
void emitNewMessageToChats(ChatMessageModel message) {
|
||||
_newUserMessagesController.add(message);
|
||||
}
|
||||
|
||||
Future<void> sendMessage({
|
||||
required String chatId,
|
||||
String? data,
|
||||
@@ -66,7 +76,7 @@ class ChatRepository {
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<ChatRoomModel> getChatRoomIdByUserSlug({required String slug}) async {
|
||||
try {
|
||||
@@ -75,4 +85,12 @@ class ChatRepository {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> markMessagesAsRead(String chatId, List<int> messageIds) async {
|
||||
try {
|
||||
await chatService.markMessagesAsRead(id: chatId, body: {'messages_ids': messageIds});
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,4 +38,7 @@ abstract class ChatService {
|
||||
Future<ChatRoomModel> getChatRoomIdByUserSlug({
|
||||
@Path() required String user,
|
||||
});
|
||||
|
||||
@POST('/api/chat/{id}/messages/markAsRead')
|
||||
Future<void> markMessagesAsRead({@Path() required String id, @Body() required Map<String, List<int>> body});
|
||||
}
|
||||
|
||||
@@ -167,6 +167,29 @@ class _ChatService implements ChatService {
|
||||
return _value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> markMessagesAsRead({
|
||||
required String id,
|
||||
required Map<String, List<int>> body,
|
||||
}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
final _data = <String, dynamic>{};
|
||||
_data.addAll(body);
|
||||
final _options = _setStreamType<void>(
|
||||
Options(method: 'POST', headers: _headers, extra: _extra)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/chat/${id}/messages/markAsRead',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
|
||||
);
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
|
||||
if (T != dynamic &&
|
||||
!(requestOptions.responseType == ResponseType.bytes ||
|
||||
|
||||
15
lib/features/complaining/models/complain_model.dart
Normal file
15
lib/features/complaining/models/complain_model.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'complain_model.freezed.dart';
|
||||
part 'complain_model.g.dart';
|
||||
|
||||
@freezed
|
||||
class ComplainModel with _$ComplainModel {
|
||||
|
||||
const factory ComplainModel({
|
||||
@Default(null) String? description,
|
||||
@Default([]) List<String> tags,
|
||||
}) = _ComplainModel;
|
||||
|
||||
factory ComplainModel.fromJson(Map<String, dynamic> json) => _$ComplainModelFromJson(json);
|
||||
}
|
||||
193
lib/features/complaining/models/complain_model.freezed.dart
Normal file
193
lib/features/complaining/models/complain_model.freezed.dart
Normal file
@@ -0,0 +1,193 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'complain_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
ComplainModel _$ComplainModelFromJson(Map<String, dynamic> json) {
|
||||
return _ComplainModel.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ComplainModel {
|
||||
String? get description => throw _privateConstructorUsedError;
|
||||
List<String> get tags => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this ComplainModel to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of ComplainModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ComplainModelCopyWith<ComplainModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ComplainModelCopyWith<$Res> {
|
||||
factory $ComplainModelCopyWith(
|
||||
ComplainModel value, $Res Function(ComplainModel) then) =
|
||||
_$ComplainModelCopyWithImpl<$Res, ComplainModel>;
|
||||
@useResult
|
||||
$Res call({String? description, List<String> tags});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ComplainModelCopyWithImpl<$Res, $Val extends ComplainModel>
|
||||
implements $ComplainModelCopyWith<$Res> {
|
||||
_$ComplainModelCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ComplainModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? description = freezed,
|
||||
Object? tags = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
description: freezed == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
tags: null == tags
|
||||
? _value.tags
|
||||
: tags // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ComplainModelImplCopyWith<$Res>
|
||||
implements $ComplainModelCopyWith<$Res> {
|
||||
factory _$$ComplainModelImplCopyWith(
|
||||
_$ComplainModelImpl value, $Res Function(_$ComplainModelImpl) then) =
|
||||
__$$ComplainModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String? description, List<String> tags});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ComplainModelImplCopyWithImpl<$Res>
|
||||
extends _$ComplainModelCopyWithImpl<$Res, _$ComplainModelImpl>
|
||||
implements _$$ComplainModelImplCopyWith<$Res> {
|
||||
__$$ComplainModelImplCopyWithImpl(
|
||||
_$ComplainModelImpl _value, $Res Function(_$ComplainModelImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of ComplainModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? description = freezed,
|
||||
Object? tags = null,
|
||||
}) {
|
||||
return _then(_$ComplainModelImpl(
|
||||
description: freezed == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
tags: null == tags
|
||||
? _value._tags
|
||||
: tags // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ComplainModelImpl implements _ComplainModel {
|
||||
const _$ComplainModelImpl(
|
||||
{this.description = null, final List<String> tags = const []})
|
||||
: _tags = tags;
|
||||
|
||||
factory _$ComplainModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ComplainModelImplFromJson(json);
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final String? description;
|
||||
final List<String> _tags;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<String> get tags {
|
||||
if (_tags is EqualUnmodifiableListView) return _tags;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_tags);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ComplainModel(description: $description, tags: $tags)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ComplainModelImpl &&
|
||||
(identical(other.description, description) ||
|
||||
other.description == description) &&
|
||||
const DeepCollectionEquality().equals(other._tags, _tags));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType, description, const DeepCollectionEquality().hash(_tags));
|
||||
|
||||
/// Create a copy of ComplainModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ComplainModelImplCopyWith<_$ComplainModelImpl> get copyWith =>
|
||||
__$$ComplainModelImplCopyWithImpl<_$ComplainModelImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ComplainModelImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ComplainModel implements ComplainModel {
|
||||
const factory _ComplainModel(
|
||||
{final String? description,
|
||||
final List<String> tags}) = _$ComplainModelImpl;
|
||||
|
||||
factory _ComplainModel.fromJson(Map<String, dynamic> json) =
|
||||
_$ComplainModelImpl.fromJson;
|
||||
|
||||
@override
|
||||
String? get description;
|
||||
@override
|
||||
List<String> get tags;
|
||||
|
||||
/// Create a copy of ComplainModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ComplainModelImplCopyWith<_$ComplainModelImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
21
lib/features/complaining/models/complain_model.g.dart
Normal file
21
lib/features/complaining/models/complain_model.g.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'complain_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$ComplainModelImpl _$$ComplainModelImplFromJson(Map<String, dynamic> json) =>
|
||||
_$ComplainModelImpl(
|
||||
description: json['description'] as String? ?? null,
|
||||
tags:
|
||||
(json['tags'] as List<dynamic>?)?.map((e) => e as String).toList() ??
|
||||
const [],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ComplainModelImplToJson(_$ComplainModelImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'description': instance.description,
|
||||
'tags': instance.tags,
|
||||
};
|
||||
@@ -0,0 +1,590 @@
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_state.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
class ComplainingBottomSheet extends StatefulWidget {
|
||||
const ComplainingBottomSheet({super.key, required this.complainedContentType, this.slug, this.id});
|
||||
final ComplainedContent complainedContentType;
|
||||
final String? slug;
|
||||
final int? id;
|
||||
@override
|
||||
State<ComplainingBottomSheet> createState() => _ComplainingBottomSheetState();
|
||||
}
|
||||
|
||||
class _ComplainingBottomSheetState extends State<ComplainingBottomSheet> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => sl<ComplainingCubit>()..setContentTypeAndIdentifier(contentType: widget.complainedContentType, slug: widget.slug, id: widget.id),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 8, bottom: 24, left: 16, right: 16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
|
||||
),
|
||||
child: BlocBuilder<ComplainingCubit, ComplainingState>(
|
||||
builder: (context, state) {
|
||||
final category = state.category;
|
||||
if (state.wasSentSuccessfully == true) {
|
||||
return _ComplaintSentWidget();
|
||||
} else if (state.wasSentSuccessfully == false) {
|
||||
return _ComplaintFailedWidget();
|
||||
}
|
||||
if (category != null) {
|
||||
return switch (category) {
|
||||
ComplainType.spam => _SpamWidget(),
|
||||
ComplainType.lawViolation => _LawViolationWidget(),
|
||||
ComplainType.deception => _DeceptionWidget(),
|
||||
ComplainType.violenceDndHostility => _ViolenceAndHostility(),
|
||||
ComplainType.explicitImage => _ExplicitImageWidget(),
|
||||
};
|
||||
}
|
||||
return _CategoryWidget();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CategoryWidget extends StatelessWidget {
|
||||
const _CategoryWidget();
|
||||
final categories = ComplainType.values;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
BottomSheetHandlerBar(),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
s.complaint,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
s.complaint_question,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
...List.generate(
|
||||
categories.length,
|
||||
(index) => Column(
|
||||
children: [
|
||||
_CategoryButton(
|
||||
category: categories[index],
|
||||
onTap: () {
|
||||
context.read<ComplainingCubit>().setCategory(categories[index]);
|
||||
},
|
||||
),
|
||||
if (index != categories.length - 1) SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SpamWidget extends StatelessWidget {
|
||||
const _SpamWidget();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
BottomSheetHandlerBar(),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
s.spam,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.7,
|
||||
child: Text(
|
||||
s.spam_description,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
_ComplainButton(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ExplicitImageWidget extends StatelessWidget {
|
||||
const _ExplicitImageWidget();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
BottomSheetHandlerBar(),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
s.explicitImages,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.7,
|
||||
child: Text(
|
||||
s.explicitImagesDescription,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
_ComplainButton(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LawViolationWidget extends StatefulWidget {
|
||||
const _LawViolationWidget();
|
||||
|
||||
@override
|
||||
State<_LawViolationWidget> createState() => _LawViolationWidgetState();
|
||||
}
|
||||
|
||||
class _LawViolationWidgetState extends State<_LawViolationWidget> {
|
||||
final List<(bool, String)> tags = [];
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final s = S.of(context);
|
||||
if (tags.isEmpty) {
|
||||
tags.addAll([
|
||||
(false, s.alcohol),
|
||||
(false, s.drugs),
|
||||
(false, s.nicotine),
|
||||
(false, s.weapon),
|
||||
(false, s.fake_documents),
|
||||
(false, s.prostitution),
|
||||
(false, s.other),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
BottomSheetHandlerBar(),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
s.lawViolation,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.7,
|
||||
child: Text(
|
||||
s.what_do_you_want_to_complaint_about,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
...tags.mapIndexed((index, element) {
|
||||
return Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
activeColor: appTheme.blueA400,
|
||||
side: BorderSide(width: 2, color: tags[index].$1 ? appTheme.blueA400 : appTheme.blueGray),
|
||||
value: element.$1,
|
||||
onChanged: (isChecked) {
|
||||
setState(() {
|
||||
if (isChecked == null) return;
|
||||
if (index == tags.length - 1 && !isChecked) {
|
||||
context.read<ComplainingCubit>().toggleDescription();
|
||||
}
|
||||
tags[index] = (isChecked, element.$2);
|
||||
final activeTags = tags.where((elem) => elem.$1).map((e) => e.$2).toList();
|
||||
context.read<ComplainingCubit>().setTags(activeTags);
|
||||
});
|
||||
},
|
||||
),
|
||||
Text(
|
||||
element.$2,
|
||||
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
if (tags.last.$1) ...[
|
||||
SizedBox(height: 12),
|
||||
CustomTextFormField(
|
||||
hintText: s.type_here_your_complaint,
|
||||
onChanged: (value) {
|
||||
context.read<ComplainingCubit>().setDescription(value);
|
||||
},
|
||||
),
|
||||
],
|
||||
SizedBox(height: 20),
|
||||
_ComplainButton(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ViolenceAndHostility extends StatefulWidget {
|
||||
const _ViolenceAndHostility();
|
||||
|
||||
@override
|
||||
State<_ViolenceAndHostility> createState() => _ViolenceAndHostilityState();
|
||||
}
|
||||
|
||||
class _ViolenceAndHostilityState extends State<_ViolenceAndHostility> {
|
||||
final List<(bool, String)> tags = [];
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final s = S.of(context);
|
||||
if (tags.isEmpty) {
|
||||
tags.addAll([
|
||||
(false, s.violence_against_people_or_animal),
|
||||
(false, s.hatred),
|
||||
(false, s.suicide),
|
||||
(false, s.hatred_description),
|
||||
(false, s.extremism),
|
||||
(false, s.calls_for_bullying),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
BottomSheetHandlerBar(),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
s.lawViolation,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.7,
|
||||
child: Text(
|
||||
s.what_do_you_want_to_complaint_about,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
...tags.mapIndexed((index, element) {
|
||||
return Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
activeColor: appTheme.blueA400,
|
||||
side: BorderSide(width: 2, color: tags[index].$1 ? appTheme.blueA400 : appTheme.blueGray),
|
||||
value: element.$1,
|
||||
onChanged: (isChecked) {
|
||||
setState(() {
|
||||
if (isChecked == null) return;
|
||||
tags[index] = (isChecked, element.$2);
|
||||
final activeTags = tags.where((elem) => elem.$1).map((e) => e.$2).toList();
|
||||
context.read<ComplainingCubit>().setTags(activeTags);
|
||||
});
|
||||
},
|
||||
),
|
||||
Text(
|
||||
element.$2,
|
||||
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
SizedBox(height: 20),
|
||||
_ComplainButton(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DeceptionWidget extends StatefulWidget {
|
||||
const _DeceptionWidget();
|
||||
|
||||
@override
|
||||
State<_DeceptionWidget> createState() => _DeceptionWidgetState();
|
||||
}
|
||||
|
||||
class _DeceptionWidgetState extends State<_DeceptionWidget> {
|
||||
final List<(bool, String)> tags = [];
|
||||
final List<String> descriptions = [];
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final s = S.of(context);
|
||||
if (tags.isEmpty && descriptions.isEmpty) {
|
||||
tags.addAll([
|
||||
(false, s.misrepresentation),
|
||||
(false, s.fraud),
|
||||
]);
|
||||
|
||||
descriptions.addAll([
|
||||
s.misrepresentation_description,
|
||||
s.fraud_description,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
BottomSheetHandlerBar(),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
s.lawViolation,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.7,
|
||||
child: Text(
|
||||
s.what_do_you_want_to_complaint_about,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
...tags.mapIndexed((index, element) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Checkbox(
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
activeColor: appTheme.blueA400,
|
||||
side: BorderSide(width: 2, color: tags[index].$1 ? appTheme.blueA400 : appTheme.blueGray),
|
||||
value: element.$1,
|
||||
onChanged: (isChecked) {
|
||||
setState(() {
|
||||
if (isChecked == null) return;
|
||||
tags[index] = (isChecked, element.$2);
|
||||
final activeTags = tags.where((elem) => elem.$1).map((e) => e.$2).toList();
|
||||
context.read<ComplainingCubit>().setTags(activeTags);
|
||||
});
|
||||
},
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
element.$2,
|
||||
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.7,
|
||||
child: Text(
|
||||
descriptions[index],
|
||||
style: TextStyles.textSSemibold.copyWith(color: appTheme.lightGray600),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
SizedBox(height: 20),
|
||||
_ComplainButton(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ComplaintSentWidget extends StatelessWidget {
|
||||
const _ComplaintSentWidget();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
BottomSheetHandlerBar(),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
s.complaint_sent,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.7,
|
||||
child: Text(
|
||||
s.complaint_sent_description,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
CustomOutlinedButton(
|
||||
text: s.ok,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
onPressed: () {
|
||||
AppRouter.navigatorKey.currentContext?.pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ComplaintFailedWidget extends StatelessWidget {
|
||||
const _ComplaintFailedWidget();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
BottomSheetHandlerBar(),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
s.could_not_send_complain,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.7,
|
||||
child: Text(
|
||||
s.please_try_again_later,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
CustomOutlinedButton(
|
||||
text: s.ok,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
onPressed: () {
|
||||
AppRouter.navigatorKey.currentContext?.pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ComplainButton extends StatelessWidget {
|
||||
const _ComplainButton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return BlocBuilder<ComplainingCubit, ComplainingState>(
|
||||
builder: (context, state) {
|
||||
if (state.isLoading) {
|
||||
return Loader();
|
||||
}
|
||||
return CustomOutlinedButton(
|
||||
text: s.send_complain,
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
onPressed: () async {
|
||||
await context.read<ComplainingCubit>().complain();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CategoryButton extends StatelessWidget {
|
||||
const _CategoryButton({required this.category, required this.onTap});
|
||||
final ComplainType category;
|
||||
final void Function() onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Ink(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
category.getTranslatedText(),
|
||||
style: TextStyles.textLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
Spacer(),
|
||||
SvgPicture.asset('assets/icons/right_arrow.svg'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum ComplainType {
|
||||
spam,
|
||||
lawViolation,
|
||||
deception,
|
||||
violenceDndHostility,
|
||||
explicitImage;
|
||||
|
||||
String getTranslatedText() => switch (this) {
|
||||
ComplainType.spam => S.current.spam,
|
||||
ComplainType.lawViolation => S.current.lawViolation,
|
||||
ComplainType.deception => S.current.deception,
|
||||
ComplainType.violenceDndHostility => S.current.violenceAndHostility,
|
||||
ComplainType.explicitImage => S.current.explicitImage,
|
||||
};
|
||||
|
||||
String getValue() => switch (this) {
|
||||
ComplainType.spam => 'spam',
|
||||
ComplainType.lawViolation => 'legalViolation',
|
||||
ComplainType.deception => 'fraud',
|
||||
ComplainType.violenceDndHostility => 'violenceAndHatred',
|
||||
ComplainType.explicitImage => 'explicitImage',
|
||||
};
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ComplainingScreen extends StatelessWidget {
|
||||
const ComplainingScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_state.dart';
|
||||
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class ComplainingCubit extends Cubit<ComplainingState> {
|
||||
final ComplainingRepository repository;
|
||||
ComplainingCubit({required this.repository}) : super(ComplainingState());
|
||||
|
||||
void setContentTypeAndIdentifier({required ComplainedContent contentType, int? id, String? slug}) {
|
||||
emit(state.copyWith(contentType: contentType, contentId: id, contentSlug: slug));
|
||||
}
|
||||
|
||||
void setCategory(ComplainType category) {
|
||||
emit(state.copyWith(category: category));
|
||||
}
|
||||
|
||||
void setTags(List<String> tags) {
|
||||
emit(state.copyWith.complainInfo(tags: tags));
|
||||
}
|
||||
|
||||
void toggleDescription() {
|
||||
if (state.complainInfo.description != null) {
|
||||
emit(state.copyWith.complainInfo(description: null));
|
||||
} else {
|
||||
emit(state.copyWith.complainInfo(description: ''));
|
||||
}
|
||||
}
|
||||
|
||||
void setDescription(String description) {
|
||||
emit(state.copyWith.complainInfo(description: description));
|
||||
}
|
||||
|
||||
Future<void> complain() async {
|
||||
try {
|
||||
final category = state.category;
|
||||
final id = state.contentId;
|
||||
final slug = state.contentSlug;
|
||||
final contentType = state.contentType;
|
||||
if (category != null && contentType != null) {
|
||||
switch (contentType) {
|
||||
case ComplainedContent.diet:
|
||||
emit(state.copyWith(wasSentSuccessfully: true));
|
||||
return;
|
||||
case ComplainedContent.plan:
|
||||
if (slug != null) {
|
||||
await repository.sendPlanComplain(slug: slug, category: category.getValue(), body: state.complainInfo);
|
||||
emit(state.copyWith(wasSentSuccessfully: true));
|
||||
return;
|
||||
}
|
||||
case ComplainedContent.recipe:
|
||||
if (slug != null) {
|
||||
await repository.sendRecipeComplain(slug: slug, category: category.getValue(), body: state.complainInfo);
|
||||
emit(state.copyWith(wasSentSuccessfully: true));
|
||||
return;
|
||||
}
|
||||
case ComplainedContent.profile:
|
||||
if (slug != null) {
|
||||
await repository.sendProfileComplain(slug: slug, category: category.getValue(), body: state.complainInfo);
|
||||
emit(state.copyWith(wasSentSuccessfully: true));
|
||||
return;
|
||||
}
|
||||
case ComplainedContent.posts:
|
||||
if (id != null) {
|
||||
await repository.sendBlogPostsComplain(id: id, category: category.getValue(), body: state.complainInfo);
|
||||
emit(state.copyWith(wasSentSuccessfully: true));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
}
|
||||
emit(state.copyWith(wasSentSuccessfully: false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/features/complaining/models/complain_model.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'complaining_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class ComplainingState with _$ComplainingState {
|
||||
|
||||
factory ComplainingState({
|
||||
@Default(false) bool isLoading,
|
||||
@Default(null) bool? wasSentSuccessfully,
|
||||
@Default(null) ComplainType? category,
|
||||
@Default(null) ComplainedContent? contentType,
|
||||
@Default(null) int? contentId,
|
||||
@Default(null) String? contentSlug,
|
||||
@Default(ComplainModel()) ComplainModel complainInfo,
|
||||
}) = _ComplainingState;
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'complaining_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ComplainingState {
|
||||
bool get isLoading => throw _privateConstructorUsedError;
|
||||
bool? get wasSentSuccessfully => throw _privateConstructorUsedError;
|
||||
ComplainType? get category => throw _privateConstructorUsedError;
|
||||
ComplainedContent? get contentType => throw _privateConstructorUsedError;
|
||||
int? get contentId => throw _privateConstructorUsedError;
|
||||
String? get contentSlug => throw _privateConstructorUsedError;
|
||||
ComplainModel get complainInfo => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of ComplainingState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ComplainingStateCopyWith<ComplainingState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ComplainingStateCopyWith<$Res> {
|
||||
factory $ComplainingStateCopyWith(
|
||||
ComplainingState value, $Res Function(ComplainingState) then) =
|
||||
_$ComplainingStateCopyWithImpl<$Res, ComplainingState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{bool isLoading,
|
||||
bool? wasSentSuccessfully,
|
||||
ComplainType? category,
|
||||
ComplainedContent? contentType,
|
||||
int? contentId,
|
||||
String? contentSlug,
|
||||
ComplainModel complainInfo});
|
||||
|
||||
$ComplainModelCopyWith<$Res> get complainInfo;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ComplainingStateCopyWithImpl<$Res, $Val extends ComplainingState>
|
||||
implements $ComplainingStateCopyWith<$Res> {
|
||||
_$ComplainingStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ComplainingState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? isLoading = null,
|
||||
Object? wasSentSuccessfully = freezed,
|
||||
Object? category = freezed,
|
||||
Object? contentType = freezed,
|
||||
Object? contentId = freezed,
|
||||
Object? contentSlug = freezed,
|
||||
Object? complainInfo = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
isLoading: null == isLoading
|
||||
? _value.isLoading
|
||||
: isLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
wasSentSuccessfully: freezed == wasSentSuccessfully
|
||||
? _value.wasSentSuccessfully
|
||||
: wasSentSuccessfully // ignore: cast_nullable_to_non_nullable
|
||||
as bool?,
|
||||
category: freezed == category
|
||||
? _value.category
|
||||
: category // ignore: cast_nullable_to_non_nullable
|
||||
as ComplainType?,
|
||||
contentType: freezed == contentType
|
||||
? _value.contentType
|
||||
: contentType // ignore: cast_nullable_to_non_nullable
|
||||
as ComplainedContent?,
|
||||
contentId: freezed == contentId
|
||||
? _value.contentId
|
||||
: contentId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
contentSlug: freezed == contentSlug
|
||||
? _value.contentSlug
|
||||
: contentSlug // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
complainInfo: null == complainInfo
|
||||
? _value.complainInfo
|
||||
: complainInfo // ignore: cast_nullable_to_non_nullable
|
||||
as ComplainModel,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
/// Create a copy of ComplainingState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ComplainModelCopyWith<$Res> get complainInfo {
|
||||
return $ComplainModelCopyWith<$Res>(_value.complainInfo, (value) {
|
||||
return _then(_value.copyWith(complainInfo: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ComplainingStateImplCopyWith<$Res>
|
||||
implements $ComplainingStateCopyWith<$Res> {
|
||||
factory _$$ComplainingStateImplCopyWith(_$ComplainingStateImpl value,
|
||||
$Res Function(_$ComplainingStateImpl) then) =
|
||||
__$$ComplainingStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{bool isLoading,
|
||||
bool? wasSentSuccessfully,
|
||||
ComplainType? category,
|
||||
ComplainedContent? contentType,
|
||||
int? contentId,
|
||||
String? contentSlug,
|
||||
ComplainModel complainInfo});
|
||||
|
||||
@override
|
||||
$ComplainModelCopyWith<$Res> get complainInfo;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ComplainingStateImplCopyWithImpl<$Res>
|
||||
extends _$ComplainingStateCopyWithImpl<$Res, _$ComplainingStateImpl>
|
||||
implements _$$ComplainingStateImplCopyWith<$Res> {
|
||||
__$$ComplainingStateImplCopyWithImpl(_$ComplainingStateImpl _value,
|
||||
$Res Function(_$ComplainingStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of ComplainingState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? isLoading = null,
|
||||
Object? wasSentSuccessfully = freezed,
|
||||
Object? category = freezed,
|
||||
Object? contentType = freezed,
|
||||
Object? contentId = freezed,
|
||||
Object? contentSlug = freezed,
|
||||
Object? complainInfo = null,
|
||||
}) {
|
||||
return _then(_$ComplainingStateImpl(
|
||||
isLoading: null == isLoading
|
||||
? _value.isLoading
|
||||
: isLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
wasSentSuccessfully: freezed == wasSentSuccessfully
|
||||
? _value.wasSentSuccessfully
|
||||
: wasSentSuccessfully // ignore: cast_nullable_to_non_nullable
|
||||
as bool?,
|
||||
category: freezed == category
|
||||
? _value.category
|
||||
: category // ignore: cast_nullable_to_non_nullable
|
||||
as ComplainType?,
|
||||
contentType: freezed == contentType
|
||||
? _value.contentType
|
||||
: contentType // ignore: cast_nullable_to_non_nullable
|
||||
as ComplainedContent?,
|
||||
contentId: freezed == contentId
|
||||
? _value.contentId
|
||||
: contentId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
contentSlug: freezed == contentSlug
|
||||
? _value.contentSlug
|
||||
: contentSlug // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
complainInfo: null == complainInfo
|
||||
? _value.complainInfo
|
||||
: complainInfo // ignore: cast_nullable_to_non_nullable
|
||||
as ComplainModel,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ComplainingStateImpl implements _ComplainingState {
|
||||
_$ComplainingStateImpl(
|
||||
{this.isLoading = false,
|
||||
this.wasSentSuccessfully = null,
|
||||
this.category = null,
|
||||
this.contentType = null,
|
||||
this.contentId = null,
|
||||
this.contentSlug = null,
|
||||
this.complainInfo = const ComplainModel()});
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isLoading;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool? wasSentSuccessfully;
|
||||
@override
|
||||
@JsonKey()
|
||||
final ComplainType? category;
|
||||
@override
|
||||
@JsonKey()
|
||||
final ComplainedContent? contentType;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int? contentId;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String? contentSlug;
|
||||
@override
|
||||
@JsonKey()
|
||||
final ComplainModel complainInfo;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ComplainingState(isLoading: $isLoading, wasSentSuccessfully: $wasSentSuccessfully, category: $category, contentType: $contentType, contentId: $contentId, contentSlug: $contentSlug, complainInfo: $complainInfo)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ComplainingStateImpl &&
|
||||
(identical(other.isLoading, isLoading) ||
|
||||
other.isLoading == isLoading) &&
|
||||
(identical(other.wasSentSuccessfully, wasSentSuccessfully) ||
|
||||
other.wasSentSuccessfully == wasSentSuccessfully) &&
|
||||
(identical(other.category, category) ||
|
||||
other.category == category) &&
|
||||
(identical(other.contentType, contentType) ||
|
||||
other.contentType == contentType) &&
|
||||
(identical(other.contentId, contentId) ||
|
||||
other.contentId == contentId) &&
|
||||
(identical(other.contentSlug, contentSlug) ||
|
||||
other.contentSlug == contentSlug) &&
|
||||
(identical(other.complainInfo, complainInfo) ||
|
||||
other.complainInfo == complainInfo));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, isLoading, wasSentSuccessfully,
|
||||
category, contentType, contentId, contentSlug, complainInfo);
|
||||
|
||||
/// Create a copy of ComplainingState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ComplainingStateImplCopyWith<_$ComplainingStateImpl> get copyWith =>
|
||||
__$$ComplainingStateImplCopyWithImpl<_$ComplainingStateImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _ComplainingState implements ComplainingState {
|
||||
factory _ComplainingState(
|
||||
{final bool isLoading,
|
||||
final bool? wasSentSuccessfully,
|
||||
final ComplainType? category,
|
||||
final ComplainedContent? contentType,
|
||||
final int? contentId,
|
||||
final String? contentSlug,
|
||||
final ComplainModel complainInfo}) = _$ComplainingStateImpl;
|
||||
|
||||
@override
|
||||
bool get isLoading;
|
||||
@override
|
||||
bool? get wasSentSuccessfully;
|
||||
@override
|
||||
ComplainType? get category;
|
||||
@override
|
||||
ComplainedContent? get contentType;
|
||||
@override
|
||||
int? get contentId;
|
||||
@override
|
||||
String? get contentSlug;
|
||||
@override
|
||||
ComplainModel get complainInfo;
|
||||
|
||||
/// Create a copy of ComplainingState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ComplainingStateImplCopyWith<_$ComplainingStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import 'package:eatvio_app/features/complaining/models/complain_model.dart';
|
||||
import 'package:eatvio_app/features/complaining/services/complaining_service.dart';
|
||||
|
||||
class ComplainingRepository {
|
||||
final ComplainingService service;
|
||||
ComplainingRepository({required this.service});
|
||||
|
||||
Future<void> sendPlanComplain({
|
||||
required String slug,
|
||||
required String category,
|
||||
required ComplainModel body,
|
||||
}) async {
|
||||
try {
|
||||
service.sendPlanComplain(slug: slug, category: category, body: body);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendRecipeComplain({
|
||||
required String slug,
|
||||
required String category,
|
||||
required ComplainModel body,
|
||||
}) async {
|
||||
try {
|
||||
service.sendRecipeComplain(slug: slug, category: category, body: body);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendBlogPostsComplain({
|
||||
required int id,
|
||||
required String category,
|
||||
required ComplainModel body,
|
||||
}) async {
|
||||
try {
|
||||
service.sendBlogPostsComplain(id: id, category: category, body: body);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendProfileComplain({
|
||||
required String slug,
|
||||
required String category,
|
||||
required ComplainModel body,
|
||||
}) async {
|
||||
try {
|
||||
service.sendProfileComplain(slug: slug, category: category, body: body);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
lib/features/complaining/services/complaining_service.dart
Normal file
39
lib/features/complaining/services/complaining_service.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/complaining/models/complain_model.dart';
|
||||
import 'package:retrofit/retrofit.dart';
|
||||
|
||||
part 'complaining_service.g.dart';
|
||||
|
||||
@RestApi()
|
||||
abstract class ComplainingService {
|
||||
factory ComplainingService(Dio dio, {String baseUrl}) = _ComplainingService;
|
||||
|
||||
@POST('/api/plans/{slug}/complain/{category}')
|
||||
Future<void> sendPlanComplain({
|
||||
@Path() required String slug,
|
||||
@Path() required String category,
|
||||
@Body() required ComplainModel body,
|
||||
});
|
||||
|
||||
@POST('/api/recipes/{slug}/complain/{category}')
|
||||
Future<void> sendRecipeComplain({
|
||||
@Path() required String slug,
|
||||
@Path() required String category,
|
||||
@Body() required ComplainModel body,
|
||||
});
|
||||
|
||||
@POST('/api/blogPosts/{id}/complain/{category}')
|
||||
Future<void> sendBlogPostsComplain({
|
||||
@Path() required int id,
|
||||
@Path() required String category,
|
||||
@Body() required ComplainModel body,
|
||||
});
|
||||
|
||||
@POST('/api/profile/{slug}/complain/{category}')
|
||||
Future<void> sendProfileComplain({
|
||||
@Path() required String slug,
|
||||
@Path() required String category,
|
||||
@Body() required ComplainModel body,
|
||||
});
|
||||
}
|
||||
|
||||
138
lib/features/complaining/services/complaining_service.g.dart
Normal file
138
lib/features/complaining/services/complaining_service.g.dart
Normal file
@@ -0,0 +1,138 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'complaining_service.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RetrofitGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations
|
||||
|
||||
class _ComplainingService implements ComplainingService {
|
||||
_ComplainingService(this._dio, {this.baseUrl, this.errorLogger});
|
||||
|
||||
final Dio _dio;
|
||||
|
||||
String? baseUrl;
|
||||
|
||||
final ParseErrorLogger? errorLogger;
|
||||
|
||||
@override
|
||||
Future<void> sendPlanComplain({
|
||||
required String slug,
|
||||
required String category,
|
||||
required ComplainModel body,
|
||||
}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
final _data = body;
|
||||
final _options = _setStreamType<void>(
|
||||
Options(method: 'POST', headers: _headers, extra: _extra)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/plans/${slug}/complain/${category}',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
|
||||
);
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> sendRecipeComplain({
|
||||
required String slug,
|
||||
required String category,
|
||||
required ComplainModel body,
|
||||
}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
final _data = body;
|
||||
final _options = _setStreamType<void>(
|
||||
Options(method: 'POST', headers: _headers, extra: _extra)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/recipes/${slug}/complain/${category}',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
|
||||
);
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> sendBlogPostsComplain({
|
||||
required int id,
|
||||
required String category,
|
||||
required ComplainModel body,
|
||||
}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
final _data = body;
|
||||
final _options = _setStreamType<void>(
|
||||
Options(method: 'POST', headers: _headers, extra: _extra)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/blogPosts/${id}/complain/${category}',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
|
||||
);
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> sendProfileComplain({
|
||||
required String slug,
|
||||
required String category,
|
||||
required ComplainModel body,
|
||||
}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
final _data = body;
|
||||
final _options = _setStreamType<void>(
|
||||
Options(method: 'POST', headers: _headers, extra: _extra)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/profile/${slug}/complain/${category}',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
|
||||
);
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
|
||||
if (T != dynamic &&
|
||||
!(requestOptions.responseType == ResponseType.bytes ||
|
||||
requestOptions.responseType == ResponseType.stream)) {
|
||||
if (T == String) {
|
||||
requestOptions.responseType = ResponseType.plain;
|
||||
} else {
|
||||
requestOptions.responseType = ResponseType.json;
|
||||
}
|
||||
}
|
||||
return requestOptions;
|
||||
}
|
||||
|
||||
String _combineBaseUrls(String dioBaseUrl, String? baseUrl) {
|
||||
if (baseUrl == null || baseUrl.trim().isEmpty) {
|
||||
return dioBaseUrl;
|
||||
}
|
||||
|
||||
final url = Uri.parse(baseUrl);
|
||||
|
||||
if (url.isAbsolute) {
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
return Uri.parse(dioBaseUrl).resolveUri(url).toString();
|
||||
}
|
||||
}
|
||||
16
lib/features/consultant_rating/models/rating_model.dart
Normal file
16
lib/features/consultant_rating/models/rating_model.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'rating_model.freezed.dart';
|
||||
part 'rating_model.g.dart';
|
||||
|
||||
@freezed
|
||||
class RatingModel with _$RatingModel {
|
||||
|
||||
factory RatingModel({
|
||||
required DateTime date,
|
||||
required String title,
|
||||
required int amount,
|
||||
}) = _RatingModel;
|
||||
|
||||
factory RatingModel.fromJson(Map<String, dynamic> json) => _$RatingModelFromJson(json);
|
||||
}
|
||||
200
lib/features/consultant_rating/models/rating_model.freezed.dart
Normal file
200
lib/features/consultant_rating/models/rating_model.freezed.dart
Normal file
@@ -0,0 +1,200 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'rating_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
RatingModel _$RatingModelFromJson(Map<String, dynamic> json) {
|
||||
return _RatingModel.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$RatingModel {
|
||||
DateTime get date => throw _privateConstructorUsedError;
|
||||
String get title => throw _privateConstructorUsedError;
|
||||
int get amount => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this RatingModel to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of RatingModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$RatingModelCopyWith<RatingModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $RatingModelCopyWith<$Res> {
|
||||
factory $RatingModelCopyWith(
|
||||
RatingModel value, $Res Function(RatingModel) then) =
|
||||
_$RatingModelCopyWithImpl<$Res, RatingModel>;
|
||||
@useResult
|
||||
$Res call({DateTime date, String title, int amount});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$RatingModelCopyWithImpl<$Res, $Val extends RatingModel>
|
||||
implements $RatingModelCopyWith<$Res> {
|
||||
_$RatingModelCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of RatingModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? date = null,
|
||||
Object? title = null,
|
||||
Object? amount = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
date: null == date
|
||||
? _value.date
|
||||
: date // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
title: null == title
|
||||
? _value.title
|
||||
: title // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
amount: null == amount
|
||||
? _value.amount
|
||||
: amount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$RatingModelImplCopyWith<$Res>
|
||||
implements $RatingModelCopyWith<$Res> {
|
||||
factory _$$RatingModelImplCopyWith(
|
||||
_$RatingModelImpl value, $Res Function(_$RatingModelImpl) then) =
|
||||
__$$RatingModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({DateTime date, String title, int amount});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$RatingModelImplCopyWithImpl<$Res>
|
||||
extends _$RatingModelCopyWithImpl<$Res, _$RatingModelImpl>
|
||||
implements _$$RatingModelImplCopyWith<$Res> {
|
||||
__$$RatingModelImplCopyWithImpl(
|
||||
_$RatingModelImpl _value, $Res Function(_$RatingModelImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of RatingModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? date = null,
|
||||
Object? title = null,
|
||||
Object? amount = null,
|
||||
}) {
|
||||
return _then(_$RatingModelImpl(
|
||||
date: null == date
|
||||
? _value.date
|
||||
: date // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
title: null == title
|
||||
? _value.title
|
||||
: title // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
amount: null == amount
|
||||
? _value.amount
|
||||
: amount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$RatingModelImpl implements _RatingModel {
|
||||
_$RatingModelImpl(
|
||||
{required this.date, required this.title, required this.amount});
|
||||
|
||||
factory _$RatingModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$RatingModelImplFromJson(json);
|
||||
|
||||
@override
|
||||
final DateTime date;
|
||||
@override
|
||||
final String title;
|
||||
@override
|
||||
final int amount;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'RatingModel(date: $date, title: $title, amount: $amount)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$RatingModelImpl &&
|
||||
(identical(other.date, date) || other.date == date) &&
|
||||
(identical(other.title, title) || other.title == title) &&
|
||||
(identical(other.amount, amount) || other.amount == amount));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, date, title, amount);
|
||||
|
||||
/// Create a copy of RatingModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$RatingModelImplCopyWith<_$RatingModelImpl> get copyWith =>
|
||||
__$$RatingModelImplCopyWithImpl<_$RatingModelImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$RatingModelImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _RatingModel implements RatingModel {
|
||||
factory _RatingModel(
|
||||
{required final DateTime date,
|
||||
required final String title,
|
||||
required final int amount}) = _$RatingModelImpl;
|
||||
|
||||
factory _RatingModel.fromJson(Map<String, dynamic> json) =
|
||||
_$RatingModelImpl.fromJson;
|
||||
|
||||
@override
|
||||
DateTime get date;
|
||||
@override
|
||||
String get title;
|
||||
@override
|
||||
int get amount;
|
||||
|
||||
/// Create a copy of RatingModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$RatingModelImplCopyWith<_$RatingModelImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
21
lib/features/consultant_rating/models/rating_model.g.dart
Normal file
21
lib/features/consultant_rating/models/rating_model.g.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'rating_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$RatingModelImpl _$$RatingModelImplFromJson(Map<String, dynamic> json) =>
|
||||
_$RatingModelImpl(
|
||||
date: DateTime.parse(json['date'] as String),
|
||||
title: json['title'] as String,
|
||||
amount: (json['amount'] as num).toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$RatingModelImplToJson(_$RatingModelImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'date': instance.date.toIso8601String(),
|
||||
'title': instance.title,
|
||||
'amount': instance.amount,
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
|
||||
import 'package:eatvio_app/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/statistic_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ConsultantRatingScreen extends StatelessWidget {
|
||||
static const name = 'consultantRatingScreen';
|
||||
static const path = '/consultant-rating-screen';
|
||||
const ConsultantRatingScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const ConsultantRatingPageContent();
|
||||
}
|
||||
}
|
||||
|
||||
class ConsultantRatingPageContent extends StatelessWidget {
|
||||
const ConsultantRatingPageContent({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Scaffold(
|
||||
backgroundColor: appTheme.gray100,
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
s.rating_and_statistics,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 16),
|
||||
RatingWidget(
|
||||
rating: 1058,
|
||||
),
|
||||
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: WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
StatisticWidget(),
|
||||
SizedBox(height: 16),
|
||||
RatingHistoryWidget(
|
||||
ratingHistory: [
|
||||
RatingModel(date: DateTime.now(), title: ' djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 201300),
|
||||
RatingModel(date: DateTime.now(), title: 'djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 89323),
|
||||
RatingModel(date: DateTime.now(), title: 'djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 20),
|
||||
RatingModel(date: DateTime.now(), title: 'djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 1212),
|
||||
RatingModel(date: DateTime.now(), title: 'qweqweeq', amount: 323),
|
||||
RatingModel(date: DateTime.now(), title: 'ee', amount: 1),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
class PeriodFilterWidget extends StatefulWidget {
|
||||
const PeriodFilterWidget({super.key});
|
||||
|
||||
@override
|
||||
State<PeriodFilterWidget> createState() => _PeriodFilterWidgetState();
|
||||
}
|
||||
|
||||
class _PeriodFilterWidgetState extends State<PeriodFilterWidget> {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CustomDropDownButton(
|
||||
hint: Text(
|
||||
s.choose_diet_duration,
|
||||
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
setState(() {
|
||||
periodDropDownIndex = periodDropDownTitles.indexOf(value);
|
||||
});
|
||||
}
|
||||
},
|
||||
items: periodDropDownTitles
|
||||
.mapIndexed(
|
||||
(index, value) => DropdownMenuItem(
|
||||
child: Text(
|
||||
periodDropDownTitles[index],
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
value: periodDropDownTitles[index],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
value: periodDropDownTitles[periodDropDownIndex],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RatingHistoryItem extends StatelessWidget {
|
||||
const RatingHistoryItem({super.key, required this.title, required this.amount, required this.date});
|
||||
final String title;
|
||||
final int amount;
|
||||
final DateTime date;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 80,
|
||||
padding: EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.gray100,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
date.format('dd.MM.yyyy'),
|
||||
style: TextStyles.textXSSemibold.copyWith(color: appTheme.gray),
|
||||
),
|
||||
Spacer(),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'+$amount',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.greenA700),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.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/presentation/widgets/period_filter_widget.dart';
|
||||
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/rating_history_item.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class RatingHistoryWidget extends StatelessWidget {
|
||||
const RatingHistoryWidget({super.key, required this.ratingHistory});
|
||||
final List<RatingModel> ratingHistory;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return MainBackgroundBouble(
|
||||
padding: EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: 16,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
s.rating_history,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
Spacer(),
|
||||
SvgPicture.asset(
|
||||
'assets/icons/question_mark.svg',
|
||||
height: 22,
|
||||
width: 22,
|
||||
colorFilter: ColorFilter.mode(
|
||||
appTheme.blueA400,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
PeriodFilterWidget(),
|
||||
SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
ListView.builder(
|
||||
physics: 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,
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: ratingHistory.length,
|
||||
shrinkWrap: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class RatingWidget extends StatelessWidget {
|
||||
const RatingWidget({super.key, required this.rating});
|
||||
|
||||
final int rating;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return MainBackgroundBouble(
|
||||
child: SizedBox(
|
||||
height: 130,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
right: 12,
|
||||
top: 12,
|
||||
child: SvgPicture.asset(
|
||||
'assets/icons/question_mark.svg',
|
||||
height: 22,
|
||||
width: 22,
|
||||
colorFilter: ColorFilter.mode(
|
||||
appTheme.blueA400,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
rating.toString(),
|
||||
style: TextStyles.headingFS40FW600.copyWith(color: appTheme.blueA400),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
s.your_rating,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StatisticBouble extends StatelessWidget {
|
||||
const StatisticBouble({super.key, required this.number, required this.title});
|
||||
final int number;
|
||||
final String title;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 80,
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.gray100,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
number.toString(),
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.blueA400),
|
||||
),
|
||||
SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/main_background_bouble.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/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class StatisticWidget extends StatelessWidget {
|
||||
const StatisticWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return MainBackgroundBouble(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
s.statistic,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
Spacer(),
|
||||
SvgPicture.asset(
|
||||
'assets/icons/question_mark.svg',
|
||||
height: 22,
|
||||
width: 22,
|
||||
colorFilter: ColorFilter.mode(
|
||||
appTheme.blueA400,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
PeriodFilterWidget(),
|
||||
SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: StatisticBouble(number: 12, title: s.invited_people_count)),
|
||||
SizedBox(width: 8),
|
||||
Expanded(child: StatisticBouble(number: 12, title: s.published_posts_count)),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: StatisticBouble(number: 12, title: s.published_recipes_count)),
|
||||
SizedBox(width: 8),
|
||||
Expanded(child: StatisticBouble(number: 12, title: s.published_diets_count)),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: StatisticBouble(number: 12, title: s.consultations_count)),
|
||||
SizedBox(width: 8),
|
||||
Expanded(child: StatisticBouble(number: 12, title: s.active_consultations_count)),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -19,5 +19,3 @@ class CreateDietModel with _$CreateDietModel {
|
||||
List<List<List<DailySchedule>>>? dailySchedule,
|
||||
}) = _CreateDietModel;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@ import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
|
||||
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.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/show_error_dialog.dart';
|
||||
import 'package:eatvio_app/core/widgets/success_published_diet_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/widgets/day_widget.dart';
|
||||
@@ -48,6 +49,7 @@ class CreateDietScreenContent extends StatefulWidget {
|
||||
class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
||||
final nameController = TextEditingController();
|
||||
final descriptionController = TextEditingController();
|
||||
final scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -65,49 +67,22 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
||||
builder: (context, state) {
|
||||
if (state.published) {
|
||||
Future.delayed(Duration(milliseconds: 500), () {
|
||||
context.pop('${state.dietData.name ?? 'Диета'}');
|
||||
context.pop();
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
child: SuccessPublishedDietBottomSheet(
|
||||
title: '${state.dietData.name ?? 'Диета'}',
|
||||
),
|
||||
);
|
||||
});
|
||||
} else if (state.drafted) {
|
||||
Future.delayed(Duration(milliseconds: 500), () {
|
||||
context.pop('${state.dietData.name ?? 'Диета'}');
|
||||
context.pop();
|
||||
showBottomSheetWithBlur(
|
||||
context: context,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
BottomSheetHandlerBar(),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
'${state.dietData.name ?? 'Диета'} сохраненна в черновиках',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: CustomOutlinedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
},
|
||||
text: 'Хорошо',
|
||||
height: 52,
|
||||
buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700),
|
||||
buttonStyle: ButtonStyle(
|
||||
side: WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
],
|
||||
child: SuccessPublishedDietBottomSheet(
|
||||
title: '${state.dietData.name ?? 'Диета'} сохраненна в черновиках',
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -232,8 +207,20 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
|
||||
Loader()
|
||||
else
|
||||
CustomOutlinedButton(
|
||||
onPressed: () {
|
||||
context.read<CreateDietCubit>().publishDiet();
|
||||
onPressed: () async {
|
||||
if (widget.diet != null) {
|
||||
final result = await context.read<CreateDietCubit>().updateDiet(
|
||||
widget.diet!.id,
|
||||
);
|
||||
if (result == 'error') {
|
||||
showErrorDialog(context, title: '${s.could_not_update_diet} ${state.dietData.name}');
|
||||
}
|
||||
} else {
|
||||
final result = await context.read<CreateDietCubit>().createDiet();
|
||||
if (result == 'error') {
|
||||
showErrorDialog(context, title: '${s.could_not_publish_diet} ${state.dietData.name}');
|
||||
}
|
||||
}
|
||||
},
|
||||
text: widget.diet != null ? s.save : s.publish,
|
||||
height: 52,
|
||||
|
||||
@@ -18,6 +18,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
final ProfileRepository profileRepository;
|
||||
final GlossaryRepository glossaryRepository;
|
||||
String? tempImagePath;
|
||||
int? dietId;
|
||||
static const _dietStateValues = DietState.values;
|
||||
|
||||
CreateDietCubit({required this.profileRepository, required this.createDietRepository, required this.glossaryRepository}) : super(CreateDietState.initial());
|
||||
@@ -25,10 +26,12 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
Future<void> setInitialData(Diet? diet) async {
|
||||
if (diet == null) return;
|
||||
emit(state.copyWith(isLoading: true, isDietBeingEdited: true));
|
||||
final countOfDays = diet.dailySchedule.length;
|
||||
dietId = diet.id;
|
||||
final dailyScheduleWithNutrition = await createDietRepository.getDietPortionsNutrition(diet.id);
|
||||
final countOfDays = dailyScheduleWithNutrition.length;
|
||||
final List<List<List<DailySchedule>>> dailySchedule = List.generate(countOfDays, (index) => [[], [], [], [], []]);
|
||||
|
||||
for (final dayEntry in diet.dailySchedule.entries) {
|
||||
for (final dayEntry in dailyScheduleWithNutrition.entries) {
|
||||
for (final mealEntry in dayEntry.value.entries) {
|
||||
for (final product in mealEntry.value.values) {
|
||||
dailySchedule[dayEntry.key - 1][mealEntry.key - 1].add(product);
|
||||
@@ -318,9 +321,9 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
return diet;
|
||||
}
|
||||
|
||||
Future<String?> publishDiet() async {
|
||||
Future<String?> createDiet() async {
|
||||
emit(state.copyWith(isDietPublishing: true));
|
||||
if (!_isFromValid()) return null;
|
||||
if (!_isFromValid()) return 'validation';
|
||||
try {
|
||||
final image = await ImageCompressUtility.compressFile(File(state.dietData.image!.path));
|
||||
final dietState = state.dietData.state!;
|
||||
@@ -339,12 +342,39 @@ class CreateDietCubit extends Cubit<CreateDietState> {
|
||||
);
|
||||
|
||||
emit(state.copyWith(published: dietState != DietState.draft, drafted: dietState == DietState.draft));
|
||||
return state.dietData.name;
|
||||
return null;
|
||||
} on DioException catch (e, stackTrace) {
|
||||
log(e.toString(), stackTrace: stackTrace);
|
||||
}
|
||||
emit(state.copyWith(isDietPublishing: false));
|
||||
return null;
|
||||
return 'error';
|
||||
}
|
||||
|
||||
Future<String?> updateDiet(int dietId) async {
|
||||
if (!_isFromValid()) return 'error';
|
||||
try {
|
||||
File? image;
|
||||
if (state.dietData.image != null) {
|
||||
image = await ImageCompressUtility.compressFile(File(state.dietData.image!.path));
|
||||
}
|
||||
await createDietRepository.updateDiet(
|
||||
dietId: dietId,
|
||||
name: state.dietData.name,
|
||||
duration: state.dietData.duration,
|
||||
state: state.dietData.state ?? DietState.active,
|
||||
image: image,
|
||||
meatType: state.dietData.type['meat'] == true ? '1' : '0',
|
||||
carbType: state.dietData.type['carb'] == true ? '1' : '0',
|
||||
vegType: state.dietData.type['veg'] == true ? '1' : '0',
|
||||
lightType: state.dietData.type['light'] == true ? '1' : '0',
|
||||
details: state.dietData.details,
|
||||
dailySchedule: state.dietData.dailySchedule,
|
||||
);
|
||||
return null;
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
|
||||
bool _isFromValid() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:eatvio_app/core/enums/product_type.dart';
|
||||
import 'package:eatvio_app/core/utils/localization_utils.dart';
|
||||
import 'package:eatvio_app/core/utils/string_extentions.dart';
|
||||
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
|
||||
@@ -300,7 +301,7 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
InfoRow(diet: widget.diet),
|
||||
InfoRow(diet: widget.diet, type: ProductType.diet,),
|
||||
ExpandableText(
|
||||
text: widget.diet.details,
|
||||
),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_divider.dart';
|
||||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
|
||||
@@ -7,7 +9,6 @@ import 'package:eatvio_app/features/diary/presentation/widgets/glossary_moderati
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||
|
||||
class AddMealWidget extends StatefulWidget {
|
||||
const AddMealWidget({
|
||||
@@ -33,6 +34,7 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
||||
GlossaryModel? selectedGlossary;
|
||||
final productNameController = TextEditingController();
|
||||
final quantityController = TextEditingController();
|
||||
List<GlossaryModel> glossariesList = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -66,47 +68,27 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.8 - 32,
|
||||
child: TypeAheadField<GlossaryModel>(
|
||||
child: CustomTextFormField(
|
||||
controller: productNameController,
|
||||
itemBuilder: (context, glossary) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
glossary.name,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
autofocus: true,
|
||||
hintText: 'Введите название продукта/блюда',
|
||||
onChanged: (query) {
|
||||
EasyDebounce.debounce(
|
||||
'glossary-search',
|
||||
Duration(milliseconds: 250),
|
||||
() async {
|
||||
selectedGlossary = null;
|
||||
if (query.isEmpty) {
|
||||
glossariesList = [];
|
||||
selectedGlossary = null;
|
||||
setState(() {});
|
||||
return;
|
||||
}
|
||||
glossariesList = await widget.createDietCubit.getGlossaryListByQuery(query);
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
},
|
||||
itemSeparatorBuilder: (context, index) => _divider(context),
|
||||
onSelected: (newSelectedGlossary) async {
|
||||
setState(() {
|
||||
selectedGlossary = newSelectedGlossary;
|
||||
productNameController.text = selectedGlossary!.name;
|
||||
});
|
||||
},
|
||||
suggestionsCallback: (query) async {
|
||||
if (query.length < 1) return [];
|
||||
final suggestionsList = await widget.createDietCubit.getGlossaryListByQuery(query);
|
||||
return suggestionsList;
|
||||
},
|
||||
builder: (context, controller, focusNode) {
|
||||
return CustomTextFormField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
focusNode: focusNode,
|
||||
hintText: 'Введите название продукта/блюда',
|
||||
);
|
||||
},
|
||||
emptyBuilder: (context) {
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
errorBuilder: (context, object) => Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text('Нет подходящих вариантов'),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
@@ -129,7 +111,56 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
glossariesList.isEmpty ? const SizedBox(height: 16) : const SizedBox.shrink(),
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: 256,
|
||||
),
|
||||
child: ListView.separated(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, i) {
|
||||
return ListTile(
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
selectedGlossary = glossariesList[i];
|
||||
productNameController.text = selectedGlossary!.name;
|
||||
});
|
||||
glossariesList = [];
|
||||
setState(() {});
|
||||
FocusManager.instance.primaryFocus?.nextFocus();
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
title: Text(
|
||||
glossariesList[i].name,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
subtitle: glossariesList[i].grInPortion == 0
|
||||
? null
|
||||
: Text(
|
||||
'${glossariesList[i].grInPortion} г',
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
trailing: Icon(
|
||||
Icons.add_rounded,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
contentPadding: EdgeInsets.only(left: 12),
|
||||
);
|
||||
},
|
||||
itemCount: glossariesList.length,
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return CustomDivider();
|
||||
},
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
if (widget.portionIndex != null)
|
||||
@@ -188,7 +219,7 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
||||
|
||||
Future<void> addPortion() async {
|
||||
final portionIndex = widget.portionIndex;
|
||||
final measurementUnits = selectedGlossary?.grInPortion == null
|
||||
var measurementUnits = selectedGlossary?.grInPortion == null
|
||||
? Portion.PCS
|
||||
: selectedGlossary?.grInPortion == 0
|
||||
? Portion.GRAMS
|
||||
@@ -199,7 +230,7 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
||||
final portion = DailySchedule(
|
||||
dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates),
|
||||
quantity: int.parse(quantityController.text),
|
||||
name: glossary.name,
|
||||
name: glossary.name,
|
||||
productNutritionalValueId: glossary.id.toString(),
|
||||
portion: measurementUnits,
|
||||
);
|
||||
@@ -234,7 +265,8 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
||||
),
|
||||
);
|
||||
if (newGlossary != null) {
|
||||
final [calories, carbohydrates, proteins, fats] = _getCPFC(newGlossary, Portion.GRAMS);
|
||||
measurementUnits = Portion.GRAMS;
|
||||
final [calories, carbohydrates, proteins, fats] = _getCPFC(newGlossary, measurementUnits);
|
||||
final portion = DailySchedule(
|
||||
dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates),
|
||||
quantity: int.parse(quantityController.text),
|
||||
@@ -278,15 +310,5 @@ class _AddMealWidgetState extends State<AddMealWidget> {
|
||||
}
|
||||
return [calories, carbohydrates, proteins, fats];
|
||||
}
|
||||
|
||||
_divider(BuildContext context) {
|
||||
return Container(
|
||||
height: 0.4,
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black26,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class CreateDietRepository {
|
||||
),
|
||||
});
|
||||
|
||||
final formData = FormData.fromMap(dietMap);
|
||||
final formData = FormData.fromMap(dietMap);
|
||||
switch (state) {
|
||||
case DietState.active:
|
||||
return createDietService.createPublishedDiet(formData: formData);
|
||||
@@ -58,4 +58,97 @@ class CreateDietRepository {
|
||||
return createDietService.createPersonalDiet(formData: formData);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<int, Map<int, Map<int, DailySchedule>>>> getDietPortionsNutrition(int dietId) async {
|
||||
try {
|
||||
final response = await createDietService.getDietPortionsNutrition(diet: dietId);
|
||||
final data = response.data;
|
||||
final dailySchedule = (data as Map<String, dynamic>).map((key, value) {
|
||||
return MapEntry(
|
||||
int.parse(key),
|
||||
(value as Map<String, dynamic>).map((key, value) {
|
||||
return MapEntry(
|
||||
int.parse(key),
|
||||
(value as Map<String, dynamic>).map((key, value) {
|
||||
return MapEntry(int.parse(key), DailySchedule.fromJson(value as Map<String, dynamic>));
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
return dailySchedule;
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateDiet({
|
||||
required int dietId,
|
||||
required DietState state,
|
||||
String? name,
|
||||
int? duration,
|
||||
String? meatType,
|
||||
String? carbType,
|
||||
String? vegType,
|
||||
String? lightType,
|
||||
String? details,
|
||||
File? image,
|
||||
List<List<List<DailySchedule>>>? dailySchedule,
|
||||
}) {
|
||||
final dietMap = <String, dynamic>{};
|
||||
if (dailySchedule != null) {
|
||||
for (final (dayIndex, day) in dailySchedule.indexed) {
|
||||
for (final (mealIndex, meal) in day.indexed) {
|
||||
for (final (productIndex, product) in meal.indexed) {
|
||||
final jsonProduct = product.toJson();
|
||||
dietMap.addAll({
|
||||
'daily_schedule[${dayIndex + 1}][${mealIndex + 1}][${productIndex}][name]': jsonProduct['name'],
|
||||
'daily_schedule[${dayIndex + 1}][${mealIndex + 1}][${productIndex}][portion]': jsonProduct['portion'],
|
||||
'daily_schedule[${dayIndex + 1}][${mealIndex + 1}][${productIndex}][quantity]': jsonProduct['quantity'],
|
||||
'daily_schedule[${dayIndex + 1}][${mealIndex + 1}][${productIndex}][product_nutritional_value_id]': jsonProduct['product_nutritional_value_id'],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (name != null) {
|
||||
dietMap.addAll({'name': name});
|
||||
}
|
||||
if (details != null) {
|
||||
dietMap.addAll({'details': details});
|
||||
}
|
||||
if (duration != null) {
|
||||
dietMap.addAll({'duration': duration});
|
||||
}
|
||||
if (meatType != null) {
|
||||
dietMap.addAll({'type[meat]': meatType});
|
||||
}
|
||||
if (carbType != null) {
|
||||
dietMap.addAll({'type[carb]': carbType});
|
||||
}
|
||||
if (vegType != null) {
|
||||
dietMap.addAll({'type[veg]': vegType});
|
||||
}
|
||||
if (lightType != null) {
|
||||
dietMap.addAll({'type[light]': lightType});
|
||||
}
|
||||
if (image != null) {
|
||||
dietMap.addAll({
|
||||
'image': MultipartFile.fromFileSync(
|
||||
image.path,
|
||||
filename: image.path.split(Platform.pathSeparator).last,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
final formData = FormData.fromMap(dietMap);
|
||||
switch (state) {
|
||||
case DietState.active:
|
||||
return createDietService.updatePublished(diet: dietId, formData: formData);
|
||||
case DietState.draft:
|
||||
return createDietService.updateDrafted(diet: dietId, formData: formData);
|
||||
case DietState.personal:
|
||||
return createDietService.updatePersonal(diet: dietId, formData: formData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,4 +25,30 @@ abstract class CreateDietService {
|
||||
Future<CreatedDiet> createPersonalDiet({
|
||||
@Body() required FormData formData,
|
||||
});
|
||||
|
||||
@POST('/api/diets/update/published/{diet}')
|
||||
@MultiPart()
|
||||
Future<void> updatePublished({
|
||||
@Path() required int diet,
|
||||
@Body() required FormData formData,
|
||||
});
|
||||
|
||||
@POST('/api/diets/update/drafted/{diet}')
|
||||
@MultiPart()
|
||||
Future<void> updateDrafted({
|
||||
@Path() required int diet,
|
||||
@Body() required FormData formData,
|
||||
});
|
||||
|
||||
@POST('/api/diets/update/personalized/{diet}')
|
||||
@MultiPart()
|
||||
Future<void> updatePersonal({
|
||||
@Path() required int diet,
|
||||
@Body() required FormData formData,
|
||||
});
|
||||
|
||||
@GET('/api/diets/show/{diet}/per_portion_nutritions')
|
||||
Future<HttpResponse> getDietPortionsNutrition({
|
||||
@Path() required int diet,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -113,6 +113,111 @@ class _CreateDietService implements CreateDietService {
|
||||
return _value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updatePublished({
|
||||
required int diet,
|
||||
required FormData formData,
|
||||
}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
final _data = formData;
|
||||
final _options = _setStreamType<void>(
|
||||
Options(
|
||||
method: 'POST',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
contentType: 'multipart/form-data',
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/diets/update/published/${diet}',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
|
||||
);
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateDrafted({
|
||||
required int diet,
|
||||
required FormData formData,
|
||||
}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
final _data = formData;
|
||||
final _options = _setStreamType<void>(
|
||||
Options(
|
||||
method: 'POST',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
contentType: 'multipart/form-data',
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/diets/update/drafted/${diet}',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
|
||||
);
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updatePersonal({
|
||||
required int diet,
|
||||
required FormData formData,
|
||||
}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
final _data = formData;
|
||||
final _options = _setStreamType<void>(
|
||||
Options(
|
||||
method: 'POST',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
contentType: 'multipart/form-data',
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/diets/update/personalized/${diet}',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
|
||||
);
|
||||
await _dio.fetch<void>(_options);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<HttpResponse<dynamic>> getDietPortionsNutrition({
|
||||
required int diet,
|
||||
}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
const Map<String, dynamic>? _data = null;
|
||||
final _options = _setStreamType<HttpResponse<dynamic>>(
|
||||
Options(method: 'GET', headers: _headers, extra: _extra)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/diets/show/${diet}/per_portion_nutritions',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)),
|
||||
);
|
||||
final _result = await _dio.fetch(_options);
|
||||
final _value = _result.data;
|
||||
final httpResponse = HttpResponse(_value, _result);
|
||||
return httpResponse;
|
||||
}
|
||||
|
||||
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
|
||||
if (T != dynamic &&
|
||||
!(requestOptions.responseType == ResponseType.bytes ||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:eatvio_app/features/recipes/models/ingredient.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/recipe_model.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/step.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/models/ingredient.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/models/recipe_model.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/models/step.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'recipe_create_entity.freezed.dart';
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:eatvio_app/features/recipes/models/ingredient.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/step.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/models/ingredient.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/models/step.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'recipe_create_model.freezed.dart';
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:eatvio_app/features/recipes/models/ingredient.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/step.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/models/ingredient.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/models/step.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'recipe_model.freezed.dart';
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:eatvio_app/features/recipes/entities/recipe_create_entity.dart';
|
||||
import 'package:eatvio_app/features/recipes/models/ingredient.dart';
|
||||
import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
|
||||
import 'package:eatvio_app/features/recipes/reporitories/recipe_repository.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/entities/recipe_create_entity.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/models/ingredient.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_state.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:eatvio_app/features/recipes/entities/recipe_create_entity.dart';
|
||||
import 'package:eatvio_app/features/create_recipe/entities/recipe_create_entity.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'create_recipe_state.freezed.dart';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user